From c03a32dbb4e5931cc497c71b8f1c3fb8dc72d498 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 18 May 2021 22:08:49 +0100 Subject: [PATCH] FAQs/tags/canned responses --- src/commands/settings.js | 7 -- src/commands/tag.js | 125 ++++++++++++++++++++++++++++++++ src/locales/en-GB.json | 27 +++++++ src/modules/commands/command.js | 2 +- src/modules/commands/manager.js | 2 +- 5 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 src/commands/tag.js diff --git a/src/commands/settings.js b/src/commands/settings.js index c4e37e4..b40c487 100644 --- a/src/commands/settings.js +++ b/src/commands/settings.js @@ -60,7 +60,6 @@ module.exports = class SettingsCommand extends Command { for (const c of data.categories) { if (c.id) { - // existing category const cat_row = await this.client.db.models.Category.findOne({ where: { @@ -95,9 +94,7 @@ module.exports = class SettingsCommand extends Command { }, `Tickets category updated by ${message.author.tag}`); } } - } else { - // create a new category const allowed_permissions = ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'SEND_MESSAGES', 'EMBED_LINKS', 'ATTACH_FILES']; const cat_channel = await message.guild.channels.create(c.name, { @@ -139,7 +136,6 @@ module.exports = class SettingsCommand extends Command { roles: c.roles, survey: c.survey }); - } } @@ -158,9 +154,7 @@ module.exports = class SettingsCommand extends Command { this.client.log.success(`Updated guild settings for "${message.guild.name}"`); return await message.channel.send(i18n('commands.settings.response.updated')); - } else { - // upload settings as json to be edited const categories = await this.client.db.models.Category.findAll({ @@ -216,7 +210,6 @@ module.exports = class SettingsCommand extends Command { message.channel.send({ files: [attachment] }); - } } }; \ No newline at end of file diff --git a/src/commands/tag.js b/src/commands/tag.js new file mode 100644 index 0000000..88a51fa --- /dev/null +++ b/src/commands/tag.js @@ -0,0 +1,125 @@ +const Command = require('../modules/commands/command'); +// eslint-disable-next-line no-unused-vars +const { MessageEmbed, Message } = require('discord.js'); +const { parseArgsStringToArgv: argv } = require('string-argv'); +const parseArgs = require('command-line-args'); + +module.exports = class TagCommand extends Command { + constructor(client) { + const i18n = client.i18n.getLocale(client.config.locale); + super(client, { + internal: true, + name: i18n('commands.tag.name'), + description: i18n('commands.tag.description'), + aliases: [ + i18n('commands.tag.aliases.faq'), + i18n('commands.tag.aliases.t'), + i18n('commands.tag.aliases.tags'), + ], + process_args: false, + args: [ + { + name: i18n('commands.tag.args.tag.name'), + description: i18n('commands.tag.args.command.description'), + example: i18n('commands.tag.args.tag.example'), + required: false, + } + ] + }); + } + + /** + * @param {Message} message + * @param {string} args + * @returns {Promise} + */ + async execute(message, args) { + const settings = await message.guild.settings; + const i18n = this.client.i18n.getLocale(settings.locale); + + const t_row = await this.client.db.models.Ticket.findOne({ + where: { + id: message.channel.id + } + }); + + args = args.split(/\s/g); // convert to an array + const tag_name = args.shift(); // shift the first element + args = args.join(' '); // convert back to a string with the first word removed + + if (tag_name && settings.tags[tag_name]) { + const tag = settings.tags[tag_name]; + const placeholders = [...tag.matchAll(/(? p[1]); + const requires_ticket = placeholders.some(p => p.startsWith('ticket.')); + + if (requires_ticket && !t_row) { + return await message.channel.send( + new MessageEmbed() + .setColor(settings.error_colour) + .setTitle(i18n('commands.tag.response.not_a_ticket.title')) + .setDescription(i18n('commands.tag.response.not_a_ticket.description')) + .setFooter(settings.footer, message.guild.iconURL()) + ); + } + + let expected = placeholders + .filter(p => p.startsWith(':')) + .map(p => { + return { + name: p.substr(1, p.length), + type: String, + }; + }); + + if (expected.length >= 1) { + try { + args = parseArgs(expected, { argv: argv(args) }); + } catch (error) { + return await message.channel.send( + new MessageEmbed() + .setColor(settings.error_colour) + .setTitle(i18n('commands.tag.response.error')) + .setDescription(`\`\`\`${error.message}\`\`\``) + .setFooter(settings.footer, message.guild.iconURL()) + ); + } + } else { + args = {}; + } + + for (const p of expected) { + if (!args[p.name]) { + const list = expected.map(p => `\`${p.name}\``); + return await message.channel.send( + new MessageEmbed() + .setColor(settings.error_colour) + .setTitle(i18n('commands.tag.response.error')) + .setDescription(i18n('commands.tag.response.missing', list.join(', '))) + .setFooter(settings.footer, message.guild.iconURL()) + ); + } + } + + if (requires_ticket) { + args.ticket = t_row.toJSON(); + args.ticket.topic = this.client.cryptr.decrypt(args.ticket.topic); + } + const text = tag.replace(/(? this.client.i18n.resolve(args, $1)); + return await message.channel.send( + new MessageEmbed() + .setColor(settings.colour) + .setDescription(text) + ); + } else { + const list = Object.keys(settings.tags).map(t => `❯ **\`${t}\`**`); + return await message.channel.send( + new MessageEmbed() + .setColor(settings.colour) + .setTitle(i18n('commands.tag.response.list.title')) + .setDescription(list.join('\n')) + .setFooter(settings.footer, message.guild.iconURL()) + ); + } + + } +}; \ No newline at end of file diff --git a/src/locales/en-GB.json b/src/locales/en-GB.json index 7f6ecb6..d95c7e5 100644 --- a/src/locales/en-GB.json +++ b/src/locales/en-GB.json @@ -359,6 +359,33 @@ } } }, + "tag": { + "aliases": { + "faq": "faq", + "t": "t", + "tags": "tags" + }, + "args": { + "tag": { + "description": "The name of the tag to use", + "example": "website", + "name": "tag" + } + }, + "description": "Use a tag response", + "name": "tag", + "response": { + "error": "❌ Error", + "list": { + "title": "📃 Tag list" + }, + "missing": "This tag requires the following arguments:\n%s", + "not_a_ticket": { + "description": "This tag can only be used within a ticket channel as it uses ticket references.", + "title": "❌ This isn't a ticket channel" + } + } + }, "topic": { "aliases": {}, "args": { diff --git a/src/modules/commands/command.js b/src/modules/commands/command.js index fa26f18..500f75e 100644 --- a/src/modules/commands/command.js +++ b/src/modules/commands/command.js @@ -117,7 +117,7 @@ module.exports = class Command { * Send a message with the command usage * @param {TextChannel} channel - The channel to send the message to * @param {string} [alias] - The command alias - * @returns {Message} + * @returns {Promise} */ async sendUsage(channel, alias) { const settings = await channel.guild.settings; diff --git a/src/modules/commands/manager.js b/src/modules/commands/manager.js index 1eca5e3..13faacd 100644 --- a/src/modules/commands/manager.js +++ b/src/modules/commands/manager.js @@ -176,7 +176,7 @@ module.exports = class CommandManager { } } } else { - const args_num = raw_args.split(' ').filter(arg => arg.length !== 0).length; // count the number of single-word args were given + const args_num = raw_args.split(/\s/g).filter(arg => arg.length !== 0).length; // count the number of single-word args were given const required_args = cmd.args.reduce((acc, arg) => arg.required ? acc + 1 : acc, 0); // count how many of the args are required if (args_num < required_args) { return await cmd.sendUsage(message.channel, cmd_name);