Plugin

CustomCommands

Allows you to create custom slash commands / tags

Commands Customisation Utility
index.ts
Download

Source

src/plugins/customCommands/index.ts
1import "./styles.css";
2
3import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage } from "@api/Commands";
4import { migratePluginSettings } from "@api/Settings";
5import { Devs } from "@utils/constants";
6import { sendMessage } from "@utils/discord";
7import definePlugin from "@utils/types";
8import { FluxDispatcher, MessageActions, PendingReplyStore } from "@webpack/common";
9
10import { openCreateTagModal } from "./CreateTagModal";
11import { getTag, getTags, removeTag, settings, Tag } from "./settings";
12
13const CustomCommandsMarker = Symbol("CustomCommands");
14const ArgumentRegex = /{{(.+?)}}/g;
15
16export function parseTagArguments(message: string) {
17 const args = [] as { name: string, defaultValue: string | null; }[];
18
19 for (const [, value] of message.matchAll(ArgumentRegex)) {
20 const [name, defaultValue] = value.split("=").map(s => s.trim());
21
22 if (!name) continue;
23 if (args.some(arg => arg.name === name)) continue;
24
25 args.push({ name: name.toLowerCase(), defaultValue: defaultValue ?? null });
26 }
27
28 return args;
29}
30
31export function registerTagCommand(tag: Tag) {
32 const tagArguments = parseTagArguments(tag.message);
33
34 registerCommand({
35 name: tag.name,
36 description: tag.name,
37 inputType: ApplicationCommandInputType.BUILT_IN,
38 options: [
39 ...tagArguments.map(arg => ({
40 name: arg.name,
41 description: arg.name,
42 type: ApplicationCommandOptionType.STRING,
43 required: arg.defaultValue === null
44 })),
45 {
46 name: "ephemeral",
47 description: "Whether the response should only be visible to you",
48 type: ApplicationCommandOptionType.BOOLEAN,
49 required: false
50 }
51 ],
52
53 execute: async (args, { channel }) => {
54 const ephemeral = findOption(args, "ephemeral", false);
55
56 const response = tag.message
57 .replace(ArgumentRegex, (fullMatch, value: string) => {
58 const [argName, defaultValue] = value.split("=").map(s => s.trim());
59 return findOption(args, argName, null) ?? defaultValue ?? fullMatch;
60 })
61 .replaceAll("\\n", "\n");
62
63 const doSend = ephemeral ? sendBotMessage : sendMessage;
64 doSend(channel.id, { content: response }, false, MessageActions.getSendMessageOptionsForReply(PendingReplyStore.getPendingReply(channel.id)));
65 FluxDispatcher.dispatch({ type: "DELETE_PENDING_REPLY", channelId: channel.id });
66 },
67 [CustomCommandsMarker]: true,
68 }, "CustomCommands");
69}
70
71
72migratePluginSettings("CustomCommands", "MessageTags");
73export default definePlugin({
74 name: "CustomCommands",
75 description: "Allows you to create custom slash commands / tags",
76 searchTerms: ["MessageTags"],
77 authors: [Devs.Ven, Devs.Luna,],
78 tags: ["Commands", "Customisation", "Utility"],
79 settings,
80
81 async start() {
82 const tags = getTags();
83 for (const tagName in tags) {
84 registerTagCommand(tags[tagName]);
85 }
86 },
87
88 commands: [
89 {
90 name: "tags",
91 description: "Manage all custom commands",
92 inputType: ApplicationCommandInputType.BUILT_IN,
93 options: [
94 {
95 name: "create",
96 description: "Create a new tag",
97 type: ApplicationCommandOptionType.SUB_COMMAND,
98 },
99 {
100 name: "list",
101 description: "List all your tags",
102 type: ApplicationCommandOptionType.SUB_COMMAND,
103 options: []
104 },
105 {
106 name: "delete",
107 description: "Remove a tag by name",
108 type: ApplicationCommandOptionType.SUB_COMMAND,
109 options: [
110 {
111 name: "tag-name",
112 description: "The name of the tag",
113 type: ApplicationCommandOptionType.STRING,
114 required: true
115 }
116 ]
117 },
118 ],
119
120 async execute(args, ctx) {
121 switch (args[0].name) {
122 case "create": {
123 openCreateTagModal();
124 break;
125 }
126
127 case "delete": {
128 const name: string = findOption(args[0].options, "tag-name", "");
129
130 if (!getTag(name))
131 return sendBotMessage(ctx.channel.id, {
132 content: `A Tag with the name **${name}** does not exist!`
133 });
134
135 removeTag(name);
136
137 sendBotMessage(ctx.channel.id, {
138 content: `Successfully deleted the tag **${name}**!`
139 });
140
141 break;
142 }
143
144 case "list": {
145 const content = Object.values(getTags())
146 .map(tag => `\`${tag.name}\`: ${tag.message.slice(0, 72).replaceAll("\\n", " ")}${tag.message.length > 72 ? "..." : ""}`)
147 .join("\n");
148
149 sendBotMessage(ctx.channel.id, {
150 content: content || "Woops! There are no tags yet, use `/tags create` to create one!",
151 });
152
153 break;
154 }
155 }
156 }
157 }
158 ]
159});
160