Plugin
CustomCommands
Allows you to create custom slash commands / tags
1
import "./styles.css";2
3
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage } from "@api/Commands";4
import { migratePluginSettings } from "@api/Settings";5
import { Devs } from "@utils/constants";6
import { sendMessage } from "@utils/discord";7
import definePlugin from "@utils/types";8
import { FluxDispatcher, MessageActions, PendingReplyStore } from "@webpack/common";9
10
import { openCreateTagModal } from "./CreateTagModal";11
import { getTag, getTags, removeTag, settings, Tag } from "./settings";12
13
const CustomCommandsMarker = Symbol("CustomCommands");14
const ArgumentRegex = /{{(.+?)}}/g;15
16
export 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
31
export 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 === null44
})),45
{46
name: "ephemeral",47
description: "Whether the response should only be visible to you",48
type: ApplicationCommandOptionType.BOOLEAN,49
required: false50
}51
],52
53
execute: async (args, { channel }) => {54
const ephemeral = findOption(args, "ephemeral", false);55
56
const response = tag.message57
.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
72
migratePluginSettings("CustomCommands", "MessageTags");73
export 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: true115
}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