From e2d423941edc9e5b2aab772739a0a06e036176cc Mon Sep 17 00:00:00 2001 From: Benjas333 Date: Mon, 10 Feb 2025 16:55:51 -0600 Subject: [PATCH] Added custom params support Added listing existing tags in /tag delete and /tag preview Refactored file extension to tsx in order to use the modal --- src/plugins/messageTags/index.ts | 247 -------------------- src/plugins/messageTags/index.tsx | 369 ++++++++++++++++++++++++++++++ 2 files changed, 369 insertions(+), 247 deletions(-) delete mode 100644 src/plugins/messageTags/index.ts create mode 100644 src/plugins/messageTags/index.tsx diff --git a/src/plugins/messageTags/index.ts b/src/plugins/messageTags/index.ts deleted file mode 100644 index 49e88c42d..000000000 --- a/src/plugins/messageTags/index.ts +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands"; -import * as DataStore from "@api/DataStore"; -import { definePluginSettings } from "@api/Settings"; -import { Devs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; - -const EMOTE = "<:luna:1035316192220553236>"; -const DATA_KEY = "MessageTags_TAGS"; -const MessageTagsMarker = Symbol("MessageTags"); - -interface Tag { - name: string; - message: string; -} - -function getTags() { - return settings.store.tagsList; -} - -function getTag(name: string) { - return settings.store.tagsList[name] ?? null; -} - -function addTag(tag: Tag) { - settings.store.tagsList[tag.name] = tag; -} - -function removeTag(name: string) { - delete settings.store.tagsList[name]; -} - -function createTagCommand(tag: Tag) { - registerCommand({ - name: tag.name, - description: tag.name, - inputType: ApplicationCommandInputType.BUILT_IN_TEXT, - execute: async (_, ctx) => { - if (!getTag(tag.name)) { - sendBotMessage(ctx.channel.id, { - content: `${EMOTE} The tag **${tag.name}** does not exist anymore! Please reload ur Discord to fix :)` - }); - return { content: `/${tag.name}` }; - } - - if (settings.store.clyde) sendBotMessage(ctx.channel.id, { - content: `${EMOTE} The tag **${tag.name}** has been sent!` - }); - return { content: tag.message.replaceAll("\\n", "\n") }; - }, - [MessageTagsMarker]: true, - }, "CustomTags"); -} - -const settings = definePluginSettings({ - clyde: { - name: "Clyde message on send", - description: "If enabled, clyde will send you an ephemeral message when a tag was used.", - type: OptionType.BOOLEAN, - default: true - }, - tagsList: { - type: OptionType.CUSTOM, - default: {} as Record, - } -}); - -export default definePlugin({ - name: "MessageTags", - description: "Allows you to save messages and to use them with a simple command.", - authors: [Devs.Luna], - settings, - - async start() { - // TODO(OptionType.CUSTOM Related): Remove DataStore tags migration once enough time has passed - const oldTags = await DataStore.get(DATA_KEY); - if (oldTags != null) { - // @ts-ignore - settings.store.tagsList = Object.fromEntries(oldTags.map(oldTag => (delete oldTag.enabled, [oldTag.name, oldTag]))); - await DataStore.del(DATA_KEY); - } - - const tags = getTags(); - for (const tagName in tags) { - createTagCommand(tags[tagName]); - } - }, - - commands: [ - { - name: "tags", - description: "Manage all the tags for yourself", - inputType: ApplicationCommandInputType.BUILT_IN, - options: [ - { - name: "create", - description: "Create a new tag", - type: ApplicationCommandOptionType.SUB_COMMAND, - options: [ - { - name: "tag-name", - description: "The name of the tag to trigger the response", - type: ApplicationCommandOptionType.STRING, - required: true - }, - { - name: "message", - description: "The message that you will send when using this tag", - type: ApplicationCommandOptionType.STRING, - required: true - } - ] - }, - { - name: "list", - description: "List all tags from yourself", - type: ApplicationCommandOptionType.SUB_COMMAND, - options: [] - }, - { - name: "delete", - description: "Remove a tag from your yourself", - type: ApplicationCommandOptionType.SUB_COMMAND, - options: [ - { - name: "tag-name", - description: "The name of the tag to trigger the response", - type: ApplicationCommandOptionType.STRING, - required: true - } - ] - }, - { - name: "preview", - description: "Preview a tag without sending it publicly", - type: ApplicationCommandOptionType.SUB_COMMAND, - options: [ - { - name: "tag-name", - description: "The name of the tag to trigger the response", - type: ApplicationCommandOptionType.STRING, - required: true - } - ] - } - ], - - async execute(args, ctx) { - - switch (args[0].name) { - case "create": { - const name: string = findOption(args[0].options, "tag-name", ""); - const message: string = findOption(args[0].options, "message", ""); - - if (getTag(name)) - return sendBotMessage(ctx.channel.id, { - content: `${EMOTE} A Tag with the name **${name}** already exists!` - }); - - const tag = { - name: name, - message: message - }; - - createTagCommand(tag); - addTag(tag); - - sendBotMessage(ctx.channel.id, { - content: `${EMOTE} Successfully created the tag **${name}**!` - }); - break; // end 'create' - } - case "delete": { - const name: string = findOption(args[0].options, "tag-name", ""); - - if (!getTag(name)) - return sendBotMessage(ctx.channel.id, { - content: `${EMOTE} A Tag with the name **${name}** does not exist!` - }); - - unregisterCommand(name); - removeTag(name); - - sendBotMessage(ctx.channel.id, { - content: `${EMOTE} Successfully deleted the tag **${name}**!` - }); - break; // end 'delete' - } - case "list": { - sendBotMessage(ctx.channel.id, { - embeds: [ - { - title: "All Tags:", - description: Object.values(getTags()) - .map(tag => `\`${tag.name}\`: ${tag.message.slice(0, 72).replaceAll("\\n", " ")}${tag.message.length > 72 ? "..." : ""}`) - .join("\n") || `${EMOTE} Woops! There are no tags yet, use \`/tags create\` to create one!`, - // @ts-ignore - color: 0xd77f7f, - type: "rich", - } - ] - }); - break; // end 'list' - } - case "preview": { - const name: string = findOption(args[0].options, "tag-name", ""); - const tag = getTag(name); - - if (!tag) - return sendBotMessage(ctx.channel.id, { - content: `${EMOTE} A Tag with the name **${name}** does not exist!` - }); - - sendBotMessage(ctx.channel.id, { - content: tag.message.replaceAll("\\n", "\n") - }); - break; // end 'preview' - } - - default: { - sendBotMessage(ctx.channel.id, { - content: "Invalid sub-command" - }); - break; - } - } - } - } - ] -}); diff --git a/src/plugins/messageTags/index.tsx b/src/plugins/messageTags/index.tsx new file mode 100644 index 000000000..3036763fd --- /dev/null +++ b/src/plugins/messageTags/index.tsx @@ -0,0 +1,369 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, CommandContext, findOption, Option, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands"; +import * as DataStore from "@api/DataStore"; +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import { openModal } from "@utils/modal"; +import definePlugin, { OptionType } from "@utils/types"; + +import { ParamsConfigModal, ParamsPreviewModal } from "./components/ParamsConfigModal"; + +const EMOTE = "<:luna:1035316192220553236>"; +const DATA_KEY = "MessageTags_TAGS"; +const MessageTagsMarker = Symbol("MessageTags"); + +export interface Param { + name: string; + default?: string; +} + +interface Tag { + name: string; + message: string; + params?: Param[]; +} + +function getTags() { + return settings.store.tagsList; +} + +function getTag(name: string) { + return settings.store.tagsList[name] ?? null; +} + +function addTag(tag: Tag) { + settings.store.tagsList[tag.name] = tag; +} + +function removeTag(name: string) { + delete settings.store.tagsList[name]; +} + +function generateOptionsFromParamsTag(params: Param[] | null) { + const options: Option[] = []; + params?.forEach(param => { + const notDefault = param.default === undefined; + options.push({ + name: param.name, + description: param.name + (!notDefault ? ` (Default: "${param.default}")` : ""), + type: ApplicationCommandOptionType.STRING, + required: notDefault, + }); + }); + return options; +} + +function createTagCommand(tag: Tag) { + const options = generateOptionsFromParamsTag(tag.params || null); + registerCommand({ + name: tag.name, + description: tag.name, + inputType: ApplicationCommandInputType.BUILT_IN_TEXT, + options, + execute: async (args, ctx) => { + if (!getTag(tag.name)) { + sendBotMessage(ctx.channel.id, { + content: `${EMOTE} The tag **${tag.name}** does not exist anymore! Please reload ur Discord to fix :)` + }); + return { content: `/${tag.name}` }; + } + + if (settings.store.clyde) sendBotMessage(ctx.channel.id, { + content: `${EMOTE} The tag **${tag.name}** has been sent!` + }); + let finalMessage = tag.message.replaceAll("\\n", "\n"); + args.forEach(arg => { + finalMessage = finalMessage.replaceAll(`$${arg.name}$`, arg.value); + }); + tag.params?.forEach(param => { + if (!param.default) return; + finalMessage = finalMessage.replaceAll(`$${param.name}$`, param.default); + }); + return { content: finalMessage }; + }, + [MessageTagsMarker]: true, + }, "CustomTags"); +} + +const settings = definePluginSettings({ + clyde: { + name: "Clyde message on send", + description: "If enabled, clyde will send you an ephemeral message when a tag was used.", + type: OptionType.BOOLEAN, + default: true + }, + tagsList: { + type: OptionType.CUSTOM, + default: {} as Record, + } +}); + +const execute = async (args: Argument[], ctx: CommandContext) => { + switch (args[0].name) { + case "create": { + const name: string = findOption(args[0].options, "tag-name", ""); + const message: string = findOption(args[0].options, "message", ""); + + if (getTag(name)) + return sendBotMessage(ctx.channel.id, { + content: `${EMOTE} A Tag with the name **${name}** already exists!` + }); + + const matches = new Set(); + message.matchAll(/\$(\S+?)\$/g).forEach(match => { matches.add(match[1]); }); + const paramNames = Array.from(matches); + + const tag: Tag = { + name: name, + message: message, + params: paramNames.length ? [] : undefined + }; + + paramNames.forEach(p => { tag.params?.push({ name: p }); }); + + const createTag = () => { + createTagCommand(tag); + addTag(tag); + sendBotMessage(ctx.channel.id, { + content: `${EMOTE} Successfully created the tag **${name}**!` + }); + updateCommandsList(); + }; + if (!tag.params?.lastIndexOf) { + createTag(); + break; + } + openModal(modalProps => ( { + tag.params = tag.params?.map(param => ({ + ...param, + default: values[param.name] || undefined, + })); + createTag(); + }} + />)); + break; // end 'create' + } + case "delete": { + const name: string = findOption(args[0].options, "tag-name", ""); + + if (!getTag(name)) + return sendBotMessage(ctx.channel.id, { + content: `${EMOTE} A Tag with the name **${name}** does not exist!` + }); + + unregisterCommand(name); + removeTag(name); + updateCommandsList(); + + sendBotMessage(ctx.channel.id, { + content: `${EMOTE} Successfully deleted the tag **${name}**!` + }); + break; // end 'delete' + } + case "list": { + sendBotMessage(ctx.channel.id, { + embeds: [ + { + title: "All Tags:", + description: Object.values(getTags()) + .map(tag => `\`${tag.name}\`: ${tag.message.slice(0, 72).replaceAll("\\n", " ")}${tag.message.length > 72 ? "..." : ""}`) + .join("\n") || `${EMOTE} Woops! There are no tags yet, use \`/tags create\` to create one!`, + // @ts-ignore + color: 0xd77f7f, + type: "rich", + } + ] + }); + break; // end 'list' + } + case "preview": { + const name: string = findOption(args[0].options, "tag-name", ""); + const tag = getTag(name); + + if (!tag) + return sendBotMessage(ctx.channel.id, { + content: `${EMOTE} A Tag with the name **${name}** does not exist!` + }); + let finalMessage = tag.message.replaceAll("\\n", "\n"); + const preview = () => { + sendBotMessage(ctx.channel.id, { + content: finalMessage + }); + }; + if (!tag.params?.length) { + preview(); + break; + } + openModal(modalProps => ( { + tag.params?.forEach(p => { + finalMessage = finalMessage.replaceAll(`$${p.name}$`, values[p.name]); + }); + preview(); + }} + />)); + break; // end 'preview' + } + + default: { + sendBotMessage(ctx.channel.id, { + content: "Invalid sub-command" + }); + break; + } + } +}; + +const updateCommandsList = () => { + unregisterCommand("tags delete"); + unregisterCommand("tags preview"); + const newChoicesList = Object.keys(getTags()).map(e => ({ + label: e, + value: e, + name: e + })); + registerCommand({ + name: "tags", + description: "Manage all the tags for yourself", + inputType: ApplicationCommandInputType.BUILT_IN, + options: [ + { + name: "delete", + description: "Remove a tag from yourself", + type: ApplicationCommandOptionType.SUB_COMMAND, + options: [ + { + name: "tag-name", + description: "The name of the tag to delete", + type: ApplicationCommandOptionType.STRING, + required: true, + choices: newChoicesList + } + ] + }, + { + name: "preview", + description: "Preview a tag without sending it publicly", + type: ApplicationCommandOptionType.SUB_COMMAND, + options: [ + { + name: "tag-name", + description: "The name of the tag to preview the response", + type: ApplicationCommandOptionType.STRING, + required: true, + choices: newChoicesList + } + ] + } + ], + execute + }, "MessageTags"); +}; + +export default definePlugin({ + name: "MessageTags", + description: "Allows you to save messages and to use them with a simple command.", + authors: [Devs.Luna, Devs.Benjas333], + settings, + + async start() { + // TODO(OptionType.CUSTOM Related): Remove DataStore tags migration once enough time has passed + const oldTags = await DataStore.get(DATA_KEY); + if (oldTags != null) { + // @ts-ignore + settings.store.tagsList = Object.fromEntries(oldTags.map(oldTag => (delete oldTag.enabled, [oldTag.name, oldTag]))); + await DataStore.del(DATA_KEY); + } + + const tags = getTags(); + for (const tagName in tags) { + createTagCommand(tags[tagName]); + } + updateCommandsList(); + }, + + commands: [ + { + name: "tags", + description: "Manage all the tags for yourself", + inputType: ApplicationCommandInputType.BUILT_IN, + options: [ + { + name: "create", + description: "Create a new tag", + type: ApplicationCommandOptionType.SUB_COMMAND, + options: [ + { + name: "tag-name", + description: "The name of the tag to trigger the response", + type: ApplicationCommandOptionType.STRING, + required: true + }, + { + name: "message", + description: "The message that you will send when using this tag. For custom parameters use double $ (example: $name$)", + type: ApplicationCommandOptionType.STRING, + required: true + } + ] + }, + { + name: "list", + description: "List all tags from yourself", + type: ApplicationCommandOptionType.SUB_COMMAND, + options: [] + }, + { + name: "delete", + description: "Remove a tag from your yourself", + type: ApplicationCommandOptionType.SUB_COMMAND, + options: [ + { + name: "tag-name", + description: "The name of the tag to delete", + type: ApplicationCommandOptionType.STRING, + required: true + } + ] + }, + { + name: "preview", + description: "Preview a tag without sending it publicly", + type: ApplicationCommandOptionType.SUB_COMMAND, + options: [ + { + name: "tag-name", + description: "The name of the tag to preview the response", + type: ApplicationCommandOptionType.STRING, + required: true + } + ] + } + ], + + execute, + } + ] +});