diff --git a/package.json b/package.json index 548d440..76f697a 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ }, "devDependencies": { "eslint": "^7.21.0", - "jsdoc": "^3.6.6", "mariadb": "^2.5.2", "mysql2": "^2.2.5", "nodemon": "^2.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7b7a30..3d26e66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,6 @@ dependencies: terminal-link: 2.1.1 devDependencies: eslint: 7.21.0 - jsdoc: 3.6.6 mariadb: 2.5.2 mysql2: 2.2.5 nodemon: 2.0.7 @@ -86,13 +85,6 @@ packages: dev: true resolution: integrity: sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw== - /@babel/parser/7.13.4: - dev: true - engines: - node: '>=6.0.0' - hasBin: true - resolution: - integrity: sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA== /@discordjs/collection/0.1.6: dev: false resolution: @@ -416,10 +408,6 @@ packages: optional: true resolution: integrity: sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= - /bluebird/3.7.2: - dev: true - resolution: - integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== /boxen/4.2.0: dependencies: ansi-align: 3.0.0 @@ -516,14 +504,6 @@ packages: /caseless/0.12.0: resolution: integrity: sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - /catharsis/0.8.11: - dependencies: - lodash: 4.17.21 - dev: true - engines: - node: '>= 8' - resolution: - integrity: sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g== /chalk/2.4.2: dependencies: ansi-styles: 3.2.1 @@ -829,10 +809,6 @@ packages: node: '>=8.6' resolution: integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== - /entities/2.0.3: - dev: true - resolution: - integrity: sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== /escape-goat/2.1.1: dev: true engines: @@ -845,12 +821,6 @@ packages: node: '>=0.8.0' resolution: integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - /escape-string-regexp/2.0.0: - dev: true - engines: - node: '>=8' - resolution: - integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== /eslint-scope/5.1.1: dependencies: esrecurse: 4.3.0 @@ -1469,12 +1439,6 @@ packages: hasBin: true resolution: integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - /js2xmlparser/4.0.1: - dependencies: - xmlcreate: 2.0.3 - dev: true - resolution: - integrity: sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw== /jsbi/3.1.4: dev: true resolution: @@ -1482,28 +1446,6 @@ packages: /jsbn/0.1.1: resolution: integrity: sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - /jsdoc/3.6.6: - dependencies: - '@babel/parser': 7.13.4 - bluebird: 3.7.2 - catharsis: 0.8.11 - escape-string-regexp: 2.0.0 - js2xmlparser: 4.0.1 - klaw: 3.0.0 - markdown-it: 10.0.0 - markdown-it-anchor: 5.3.0_markdown-it@10.0.0 - marked: 0.8.2 - mkdirp: 1.0.4 - requizzle: 0.2.3 - strip-json-comments: 3.1.1 - taffydb: 2.6.2 - underscore: 1.10.2 - dev: true - engines: - node: '>=8.15.0' - hasBin: true - resolution: - integrity: sha512-znR99e1BHeyEkSvgDDpX0sTiTu+8aQyDl9DawrkOGZTTW8hv0deIFXx87114zJ7gRaDZKVQD/4tr1ifmJp9xhQ== /json-buffer/3.0.0: dev: true resolution: @@ -1564,12 +1506,6 @@ packages: dev: true resolution: integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - /klaw/3.0.0: - dependencies: - graceful-fs: 4.2.6 - dev: true - resolution: - integrity: sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g== /latest-version/5.1.0: dependencies: package-json: 6.5.0 @@ -1611,12 +1547,6 @@ packages: node: '>= 0.8.0' resolution: integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - /linkify-it/2.2.0: - dependencies: - uc.micro: 1.0.6 - dev: true - resolution: - integrity: sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw== /lodash/4.17.20: resolution: integrity: sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -1676,36 +1606,6 @@ packages: node: '>= 10.13' resolution: integrity: sha512-SfaBl5/LiX2qJNNr7wCQvizVjtWxVm1CUWYKe+y4OMeyYMM6g0GhwX7/BbGtv/O3WthnGrM+Kj1imFnlescO0w== - /markdown-it-anchor/5.3.0_markdown-it@10.0.0: - dependencies: - markdown-it: 10.0.0 - dev: true - peerDependencies: - markdown-it: '*' - resolution: - integrity: sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA== - /markdown-it/10.0.0: - dependencies: - argparse: 1.0.10 - entities: 2.0.3 - linkify-it: 2.2.0 - mdurl: 1.0.1 - uc.micro: 1.0.6 - dev: true - hasBin: true - resolution: - integrity: sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg== - /marked/0.8.2: - dev: true - engines: - node: '>= 8.16.2' - hasBin: true - resolution: - integrity: sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw== - /mdurl/1.0.1: - dev: true - resolution: - integrity: sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= /mime-db/1.45.0: engines: node: '>= 0.6' @@ -1755,13 +1655,6 @@ packages: optional: true resolution: integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - /mkdirp/1.0.4: - dev: true - engines: - node: '>=10' - hasBin: true - resolution: - integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== /moment-timezone/0.5.33: dependencies: moment: 2.29.1 @@ -2347,12 +2240,6 @@ packages: node: '>=0.10.0' resolution: integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - /requizzle/0.2.3: - dependencies: - lodash: 4.17.21 - dev: true - resolution: - integrity: sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ== /resolve-from/4.0.0: dev: true engines: @@ -2698,10 +2585,6 @@ packages: node: '>=10.0.0' resolution: integrity: sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g== - /taffydb/2.6.2: - dev: true - resolution: - integrity: sha1-fLy2S1oUG2ou/CxdLGe04VCyomg= /tar/2.2.2: dependencies: block-stream: 0.0.9 @@ -2865,20 +2748,12 @@ packages: dev: true resolution: integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - /uc.micro/1.0.6: - dev: true - resolution: - integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== /undefsafe/2.0.3: dependencies: debug: 2.6.9 dev: true resolution: integrity: sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A== - /underscore/1.10.2: - dev: true - resolution: - integrity: sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg== /underscore/1.12.0: dev: true resolution: @@ -3061,10 +2936,6 @@ packages: node: '>=4.0' resolution: integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== - /xmlcreate/2.0.3: - dev: true - resolution: - integrity: sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ== /xmldom/0.4.0: dev: true engines: @@ -3102,7 +2973,6 @@ specifiers: dotenv: ^8.2.0 eslint: ^7.21.0 fs-extra: ^9.1.0 - jsdoc: ^3.6.6 leeks.js: ^0.0.9 leekslazylogger-fastify: ^0.1.0 mariadb: ^2.5.2 diff --git a/src/commands/close.js b/src/commands/close.js new file mode 100644 index 0000000..600a0ae --- /dev/null +++ b/src/commands/close.js @@ -0,0 +1,33 @@ +const { MessageEmbed } = require('discord.js'); +const Command = require('../modules/commands/command'); +const { footer } = require('../utils/discord'); + +module.exports = class CloseCommand extends Command { + constructor(client) { + const i18n = client.i18n.get(client.config.locale); + super(client, { + internal: true, + name: i18n('commands.close.name'), + description: i18n('commands.close.description'), + aliases: [ + i18n('commands.close.aliases.delete'), + ], + process_args: false, + args: [ + { + name: i18n('commands.close.args.ticket.name'), + description: i18n('commands.close.args.ticket.description'), + example: i18n('commands.close.args.ticket.example'), + required: false, + } + ] + }); + } + + async execute(message, args) { + + let settings = await message.guild.settings; + const i18n = this.client.i18n.get(settings.locale); + + } +}; \ No newline at end of file diff --git a/src/commands/new.js b/src/commands/new.js index 89e4744..9a9b4b3 100644 --- a/src/commands/new.js +++ b/src/commands/new.js @@ -1,5 +1,7 @@ const { MessageEmbed } = require('discord.js'); const Command = require('../modules/commands/command'); +const { footer } = require('../utils/discord'); +const { letters } = require('../utils/emoji'); module.exports = class NewCommand extends Command { constructor(client) { @@ -29,27 +31,131 @@ module.exports = class NewCommand extends Command { let settings = await message.guild.settings; const i18n = this.client.i18n.get(settings.locale); - let { count: cat_count, rows: categories } = await this.client.db.models.Category.findAndCountAll({ + const editOrSend = async (msg, content) => { + if (msg) return await msg.edit(content); + else return await message.channel.send(content); + }; + + const create = async (cat_row, response) => { + let tickets = await this.client.db.models.Ticket.findAndCountAll({ + where: { + category: cat_row.id, + creator: message.author.id, + open: true + } + }); + + if (tickets.count >= cat_row.max_per_member) { + if (cat_row.max_per_member === 1) { + response = await editOrSend(response, + new MessageEmbed() + .setColor(settings.error_colour) + .setAuthor(message.author.username, message.author.displayAvatarURL()) + .setTitle(i18n('commands.new.response.has_a_ticket.title')) + .setDescription(i18n('commands.new.response.has_a_ticket.description', tickets.rows[0].id)) + .setFooter(footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL()) + ); + } else { + let list = tickets.rows.map(row => { + if (row.topic) { + let description = row.topic.substring(0, 30); + let ellipses = description.length > 30 ? '...' : ''; + return `<#${row.id}>: \`${description}${ellipses}\``; + } else { + return `<#${row.id}>`; + } + }); + response = await editOrSend(response, + new MessageEmbed() + .setColor(settings.error_colour) + .setAuthor(message.author.username, message.author.displayAvatarURL()) + .setTitle(i18n('commands.new.response.max_tickets.title', tickets.count)) + .setDescription(i18n('commands.new.response.max_tickets.description', settings.command_prefix, list.join('\n'))) + .setFooter(footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL()) + ); + } + } else { + let t_row = await this.client.tickets.create(message.guild.id, message.author.id, cat_row.id, args); + response = await editOrSend(response, + new MessageEmbed() + .setColor(settings.success_colour) + .setAuthor(message.author.username, message.author.displayAvatarURL()) + .setTitle(i18n('commands.new.response.created.title')) + .setDescription(i18n('commands.new.response.created.description', `<#${t_row.id}>`)) + .setFooter(footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL()) + ); + } + + setTimeout(async () => { + await response.delete(); + await message.delete(); + }, 15000); + }; + + let categories = await this.client.db.models.Category.findAndCountAll({ where: { guild: message.guild.id } }); - switch (cat_count) { - case 0: + if (categories.count === 0) { return await message.channel.send( new MessageEmbed() .setColor(settings.error_colour) + .setAuthor(message.author.username, message.author.displayAvatarURL()) .setTitle(i18n('commands.new.response.no_categories.title')) .setDescription(i18n('commands.new.response.no_categories.description')) + .setFooter(settings.footer, message.guild.iconURL()) ); - case 1: - break; - default: + } else if (categories.count === 1) { + create(categories.rows[0]); + } else { + let letters_array = Object.values(letters); + let category_list = categories.rows.map((category, i) => `${letters_array[i]} » ${category.name}`); + let collector_message = await message.channel.send( + new MessageEmbed() + .setColor(settings.colour) + .setAuthor(message.author.username, message.author.displayAvatarURL()) + .setTitle(i18n('commands.new.response.select_category.title')) + .setDescription(i18n('commands.new.response.select_category.description', category_list.join('\n'))) + .setFooter(footer(settings.footer, i18n('collector_expires_in', 30)), message.guild.iconURL()) + ); + + for (let i in categories.rows) { + await collector_message.react(letters_array[i]); + } + + const collector_filter = (reaction, user) => { + let allowed = letters_array.slice(0, categories.count); + return user.id === message.author.id && allowed.includes(reaction.emoji.name); + }; + + let collector = collector_message.createReactionCollector(collector_filter, { + time: 30000 + }); + + collector.on('collect', async (reaction) => { + let index = letters_array.findIndex(value => value === reaction.emoji.name); + if (index === -1) return await collector_message.delete({ timeout: 15000 }); + await collector_message.reactions.removeAll(); + create(categories.rows[index], collector_message); + }); + + collector.on('end', async (collected) => { + if (collected.size === 0) { + await collector_message.reactions.removeAll(); + await collector_message.edit( + new MessageEmbed() + .setColor(settings.error_colour) + .setAuthor(message.author.username, message.author.displayAvatarURL()) + .setTitle(i18n('commands.new.response.select_category_timeout.title')) + .setDescription(i18n('commands.new.response.select_category_timeout.description', category_list.join('\n'))) + .setFooter(footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL()) + ); + collector_message.delete({ timeout: 15000 }); + } + }); } - - - // this.client.tickets.create(message.guild.id, message.member.id, '825861413687787560', args.topic); } }; \ No newline at end of file diff --git a/src/commands/settings.js b/src/commands/settings.js index 07bcfcf..2e04145 100644 --- a/src/commands/settings.js +++ b/src/commands/settings.js @@ -31,6 +31,7 @@ module.exports = class SettingsCommand extends Command { settings.colour = data.colour; settings.command_prefix = data.command_prefix; settings.error_colour = data.error_colour; + settings.footer = data.footer; settings.locale = data.locale; settings.log_messages = data.log_messages; settings.success_colour = data.success_colour; @@ -114,6 +115,7 @@ module.exports = class SettingsCommand extends Command { colour: settings.colour, command_prefix: settings.command_prefix, error_colour: settings.error_colour, + footer: settings.footer, locale: settings.locale, log_messages: settings.log_messages, success_colour: settings.success_colour, diff --git a/src/database/index.js b/src/database/index.js index 4cdcc01..d9e8761 100644 --- a/src/database/index.js +++ b/src/database/index.js @@ -6,7 +6,7 @@ const { path } = require('../utils/fs'); const config = require('../../user/config'); const types = require('./dialects'); -module.exports = async (log) => { +module.exports = async (client) => { const { DB_TYPE, @@ -22,7 +22,7 @@ module.exports = async (log) => { const supported = Object.keys(types); if (!supported.includes(type)) { - log.error(new Error(`DB_TYPE (${type}) is not a valid type`)); + client.log.error(new Error(`DB_TYPE (${type}) is not a valid type`)); return process.exit(); } @@ -30,35 +30,35 @@ module.exports = async (log) => { types[type].packages.forEach(pkg => require(pkg)); } catch { let required = types[type].packages.map(i => `"${i}"`).join(' and '); - log.error(new Error(`Please install the package(s) for your selected database type: ${required}`)); + client.log.error(new Error(`Please install the package(s) for your selected database type: ${required}`)); return process.exit(); } let sequelize; if (type === 'sqlite') { - log.info('Using SQLite storage'); + client.log.info('Using SQLite storage'); sequelize = new Sequelize({ dialect: types[type].dialect, storage: path('./user/database.sqlite'), - logging: text => log.debug(text) + logging: text => client.log.debug(text) }); } else { - log.info(`Connecting to ${types[type].name} database...`); + client.log.info(`Connecting to ${types[type].name} database...`); sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASS, { dialect: types[type].dialect, host: DB_HOST, port: DB_PORT, - logging: text => log.debug(text) + logging: text => client.log.debug(text) }); } try { await sequelize.authenticate(); - log.success('Connected to database successfully'); + client.log.success('Connected to database successfully'); } catch (error) { - log.warn('Failed to connect to database'); - log.error(error); + client.log.warn('Failed to connect to database'); + client.log.error(error); return process.exit(); } @@ -92,6 +92,10 @@ module.exports = async (log) => { type: DataTypes.BOOLEAN, defaultValue: config.defaults.log_messages }, + footer: { + type: DataTypes.STRING, + defaultValue: 'Discord Tickets by eartharoid' + }, }, { tableName: DB_TABLE_PREFIX + 'guilds' }); @@ -160,6 +164,10 @@ module.exports = async (log) => { key: 'id' }, }, + topic: { + type: DataTypes.STRING, + allowNull: true, + }, creator: { type: DataTypes.CHAR(18), allowNull: false, diff --git a/src/index.js b/src/index.js index cc633e9..24146ec 100644 --- a/src/index.js +++ b/src/index.js @@ -97,15 +97,15 @@ class Bot extends Client { /** The global bot configuration */ this.config = config; - /** A sequelize instance */ - this.db = await require('./database')(log), // this.db.models.Ticket... - /** A leekslazylogger instance */ this.log = log; /** An @eartharoid/i18n instance */ this.i18n = new I18n(path('./src/locales'), 'en-GB'); + /** A sequelize instance */ + this.db = await require('./database')(this), // this.db.models.Ticket... + this.setMaxListeners(this.config.max_listeners); // set the max listeners for each event require('./updater')(this); // check for updates diff --git a/src/locales/en-GB.json b/src/locales/en-GB.json index 0db1215..1131caa 100644 --- a/src/locales/en-GB.json +++ b/src/locales/en-GB.json @@ -4,18 +4,26 @@ }, "cmd_usage": { "args": { - "description": "**Description:**", - "example": "**Example:**" + "description": "**Description:** %s", + "example": "**Example:** `%s`" }, "description": "**Usage:**\n`%s`\n\n**Example:**\n`%s`\n\nRequired arguments are prefixed with `❗`.", "named_args": "This command uses named arguments.\n\n", "title": "`%s` command usage" }, + "collector_expires_in": "Expires in %d seconds", "commands": { "close": { "aliases": { "delete": "delete" }, + "args": { + "ticket": { + "name": "ticket", + "description": "The number or a channel mention of the ticket to close", + "example": "217" + } + }, "description": "Close a ticket channel", "name": "close", "response": { @@ -31,10 +39,6 @@ "open": "open" }, "args": { - "category": { - "name": "category", - "description": "The category you would like to create a new ticket for" - }, "topic": { "name": "topic", "description": "The topic of the ticket", @@ -46,11 +50,27 @@ "response": { "created": { "title": "✅ Ticket created", - "description": "" + "description": "Your ticket has been created: %s." + }, + "has_a_ticket": { + "title": "❌ You already have an open ticket", + "description": "Please use your existing ticket (<#%s>) or close it before creating another." + }, + "max_tickets": { + "title": "❌ You already have %d open tickets", + "description": "Please use `%sclose` to close any unneeded tickets.\n\n%s" }, "no_categories": { "title": "❌ Can't create ticket", "description": "A server administrator must create at least one ticket category before a new ticket can be opened." + }, + "select_category": { + "title": "Please select the ticket category", + "description": "Select the category most relevant to your ticket's topic:\n\n%s" + }, + "select_category_timeout": { + "title": "❌ Reaction time expired", + "description": "You took too long to select the ticket category." } } }, @@ -67,8 +87,9 @@ }, "command_execution_error": { "title": "⚠️", - "description": "An unexpected error occurred during command execution.\nPlease ask a server administrator to check the console output / logs for details." + "description": "An unexpected error occurred during command execution.\nPlease ask an administrator to check the console output / logs for details." }, + "message_will_be_deleted_in": "This message will be deleted in %d seconds", "missing_perms": { "title": "❌", "description": "You do not have the permissions required to use this command:\n%s" diff --git a/src/modules/commands/command.js b/src/modules/commands/command.js index 616926d..0f06230 100644 --- a/src/modules/commands/command.js +++ b/src/modules/commands/command.js @@ -1,3 +1,5 @@ +const { MessageEmbed } = require('discord.js'); + /** * A command */ @@ -115,4 +117,47 @@ module.exports = class Command { */ async execute(message, args) { } // eslint-disable-line no-unused-vars + /** + * Send a message with the command usage + * @param {TextChannel} channel - The channel to send the message to + * @param {string} [cmd_name] - The command alias + * @returns {Message} + */ + async sendUsage(channel, cmd_name) { + let settings = await channel.guild.settings; + if (!cmd_name) cmd_name = this.name; + + const prefix = settings.command_prefix; + const i18n = this.client.i18n.get(settings.locale); + + const addArgs = (embed, arg) => { + let required = arg.required ? '`❗` ' : ''; + embed.addField(required + arg.name, `» ${i18n('cmd_usage.args.description', arg.description)}}\n» ${i18n('cmd_usage.args.example', arg.example)}`); + }; + + let usage, + example, + embed; + + if (this.process_args) { + usage = `${prefix + cmd_name} ${this.args.map(arg => arg.required ? `<${arg.name};>` : `[${arg.name};]`).join(' ')}`; + example = `${prefix + cmd_name} ${this.args.map(arg => `${arg.name}: ${arg.example};`).join(' ')}`; + embed = new MessageEmbed() + .setColor(settings.error_colour) + .setTitle(i18n('cmd_usage.title', cmd_name)) + .setDescription(i18n('cmd_usage.named_args') + i18n('cmd_usage.description', usage, example)); + } else { + usage = `${prefix + cmd_name} ${this.args.map(arg => arg.required ? `<${arg.name}>` : `[${arg.name}]`).join(' ')}`; + example = `${prefix + cmd_name} ${this.args.map(arg => `${arg.example}`).join(' ')}`; + embed = new MessageEmbed() + .setColor(settings.error_colour) + .setTitle(i18n('cmd_usage.title', cmd_name)) + .setDescription(i18n('cmd_usage.description', usage, example)); + } + + this.args.forEach(arg => addArgs(embed, arg)); + return await channel.send(embed); + + } + }; \ No newline at end of file diff --git a/src/modules/commands/manager.js b/src/modules/commands/manager.js index 8f4248b..50cd9cd 100644 --- a/src/modules/commands/manager.js +++ b/src/modules/commands/manager.js @@ -83,39 +83,20 @@ module.exports = class CommandManager { let args = raw_args; - const addArgs = (embed, arg) => { - let required = arg.required ? '`❗` ' : ''; - embed.addField(required + arg.name, `» ${i18n('cmd_usage.args.description')} ${arg.description}\n» ${i18n('cmd_usage.args.example')} \`${arg.example}\``); - }; - if (cmd.process_args) { args = {}; let data = [...raw_args.matchAll(/(?\w+)\??\s?:\s?(?([^;]|;{2})*);/gmi)]; data.forEach(arg => args[arg.groups.key] = arg.groups.value.replace(/;{2}/gm, ';')); for (let arg of cmd.args) { if (arg.required && !args[arg]) { - let usage = `${prefix + cmd_name} ${cmd.args.map(arg => arg.required ? `<${arg.name};>` : `[${arg.name};]`).join(' ')}`; - let example = `${prefix + cmd_name} ${cmd.args.map(arg => `${arg.name}: ${arg.example};`).join(' ')}`; - let embed = new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('cmd_usage.title', cmd_name)) - .setDescription(i18n('cmd_usage.named_args') + i18n('cmd_usage.description', usage, example)); - cmd.args.forEach(a => addArgs(embed, a)); - return message.channel.send(embed); + return await cmd.sendUsage(message.channel, cmd_name); } } } else { const args_num = raw_args.split(' ').filter(arg => arg.length !== 0).length; const required_args = cmd.args.reduce((acc, arg) => arg.required ? acc + 1 : acc, 0); if (args_num < required_args) { - let usage = `${prefix + cmd_name} ${cmd.args.map(arg => arg.required ? `<${arg.name}>` : `[${arg.name}]`).join(' ')}`; - let example = `${prefix + cmd_name} ${cmd.args.map(arg => `${arg.example}`).join(' ')}`; - let embed = new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('cmd_usage.title', cmd_name)) - .setDescription(i18n('cmd_usage.description', usage, example)); - cmd.args.forEach(a => addArgs(embed, a)); - return message.channel.send(embed); + return await cmd.sendUsage(message.channel, cmd_name); } } @@ -158,7 +139,6 @@ module.exports = class CommandManager { } catch (e) { this.client.log.warn(`An error occurred whilst executing the ${cmd.name} command`); this.client.log.error(e); - // await message.channel.send(i18n('command_execution_error')); await message.channel.send( new MessageEmbed() .setColor('ORANGE') diff --git a/src/modules/tickets/archives.js b/src/modules/tickets/archives.js index 35967fc..851cc36 100644 --- a/src/modules/tickets/archives.js +++ b/src/modules/tickets/archives.js @@ -21,9 +21,6 @@ module.exports = class TicketArchives { }); if (t_row) { - let embeds = []; - for (let embed in message.embeds) embeds.push({ ...message.embeds[embed] }); - await this.client.db.models.Message.create({ id: message.id, ticket: t_row.id, @@ -31,7 +28,9 @@ module.exports = class TicketArchives { data: { content: message.content, // time: message.createdTimestamp, - embeds, + embeds: message.embeds.map(embed => { + return { embed }; + }), attachments: [...message.attachments.values()] } }); @@ -48,14 +47,11 @@ module.exports = class TicketArchives { }); if (m_row) { - let embeds = []; - for (let embed in message.embeds) embeds.push({ ...message.embeds[embed] }); - m_row.data = { content: message.content, // time: message.editedTimestamp, embeds: message.embeds.map(embed => { - return { ...message.embeds[embed] }; + return { embed }; }), attachments: [...message.attachments.values()] }; @@ -90,77 +86,77 @@ module.exports = class TicketArchives { } }); - if (m_row) { - // message author - let u_model_data = { - user: message.author.id, + if (!m_row) return; + + // message author + let u_model_data = { + user: message.author.id, + ticket: message.channel.id + }; + let [u_row] = await this.client.db.models.UserEntity.findOrCreate({ + where: u_model_data, + defaults: u_model_data + }); + await u_row.update({ + avatar: message.author.displayAvatarURL(), + username: message.author.username, + discriminator: message.author.discriminator, + display_name: message.member.displayName, + colour: message.member.displayColor === 0 ? null : int2hex(message.member.displayColor), + bot: message.author.bot + }); + + // mentioned members + message.mentions.members.forEach(async member => { + let m_model_data = { + user: member.user.id, ticket: message.channel.id }; - let [u_row] = await this.client.db.models.UserEntity.findOrCreate({ - where: u_model_data, - defaults: u_model_data - }); - await u_row.update({ - avatar: message.author.displayAvatarURL(), - username: message.author.username, - discriminator: message.author.discriminator, - display_name: message.member.displayName, - colour: message.member.displayColor === 0 ? null : int2hex(message.member.displayColor), - bot: message.author.bot + let [m_row] = await this.client.db.models.UserEntity.findOrCreate({ + where: m_model_data, + defaults: m_model_data }); - // mentioned members - message.mentions.members.forEach(async member => { - let m_model_data = { - user: member.user.id, - ticket: message.channel.id - }; - let [m_row] = await this.client.db.models.UserEntity.findOrCreate({ - where: m_model_data, - defaults: m_model_data - }); - - await m_row.update({ - avatar: member.user.displayAvatarURL(), - username: member.user.username, - discriminator: member.user.discriminator, - display_name: member.displayName, - colour: member.displayColor === 0 ? null : int2hex(member.displayColor), - bot: member.user.bot - }); + await m_row.update({ + avatar: member.user.displayAvatarURL(), + username: member.user.username, + discriminator: member.user.discriminator, + display_name: member.displayName, + colour: member.displayColor === 0 ? null : int2hex(member.displayColor), + bot: member.user.bot }); + }); - // mentioned channels - message.mentions.channels.forEach(async channel => { - let c_model_data = { - channel: channel.id, - ticket: message.channel.id - }; - let [c_row] = await this.client.db.models.ChannelEntity.findOrCreate({ - where: c_model_data, - defaults: c_model_data - }); - await c_row.update({ - name: channel.name - }); + // mentioned channels + message.mentions.channels.forEach(async channel => { + let c_model_data = { + channel: channel.id, + ticket: message.channel.id + }; + let [c_row] = await this.client.db.models.ChannelEntity.findOrCreate({ + where: c_model_data, + defaults: c_model_data }); + await c_row.update({ + name: channel.name + }); + }); - // mentioned roles - message.mentions.roles.forEach(async role => { - let r_model_data = { - role: role.id, - ticket: message.channel.id - }; - let [r_row] = await this.client.db.models.RoleEntity.findOrCreate({ - where: r_model_data, - defaults: r_model_data - }); - await r_row.update({ - name: role.name, - colour: role.color === 0 ? '7289DA' : int2hex(role.color) // 7289DA = 7506394 - }); + // mentioned roles + message.mentions.roles.forEach(async role => { + let r_model_data = { + role: role.id, + ticket: message.channel.id + }; + let [r_row] = await this.client.db.models.RoleEntity.findOrCreate({ + where: r_model_data, + defaults: r_model_data }); - } + await r_row.update({ + name: role.name, + colour: role.color === 0 ? '7289DA' : int2hex(role.color) // 7289DA = 7506394 + }); + }); } diff --git a/src/modules/tickets/manager.js b/src/modules/tickets/manager.js index ecaa557..fdba983 100644 --- a/src/modules/tickets/manager.js +++ b/src/modules/tickets/manager.js @@ -75,6 +75,7 @@ module.exports = class TicketManager extends EventEmitter { }); this.emit('create', t_row.id, creator_id); + return t_row; } /** @@ -144,6 +145,7 @@ module.exports = class TicketManager extends EventEmitter { } this.emit('close', ticket_id); + return t_row; } /** diff --git a/src/utils/discord.js b/src/utils/discord.js index 8dd5e09..b4675f4 100644 --- a/src/utils/discord.js +++ b/src/utils/discord.js @@ -3,9 +3,19 @@ const config = require('../../user/config'); let current_presence = -1; module.exports = { + /** + * + * @param {string} text + * @param {string} [additional] + * @returns {string} + */ + footer: (text, additional) => { + if (text && additional) return `${text} | ${additional}`; + else return text || additional || ''; + }, /** * Select a presence from the config - * @returns {Discord.PresenceData} + * @returns {PresenceData} */ selectPresence: () => { let length = config.presence.presences.length; diff --git a/src/utils/emoji.js b/src/utils/emoji.js new file mode 100644 index 0000000..c41ef92 --- /dev/null +++ b/src/utils/emoji.js @@ -0,0 +1,43 @@ +module.exports = { + letters: { + A: '🇦', + B: '🇧', + C: '🇨', + D: '🇩', + E: '🇪', + F: '🇫', + G: '🇬', + H: '🇭', + I: '🇮', + J: '🇯', + K: '🇰', + L: '🇱', + M: '🇲', + N: '🇳', + O: '🇴', + P: '🇵', + Q: '🇶', + R: '🇷', + S: '🇸', + T: '🇹', + U: '🇺', + V: '🇻', + W: '🇼', + X: '🇽', + Y: '🇾', + Z: '🇿', + }, + numbers: { + 0: '0️⃣', + 1: '1️⃣', + 2: '2️⃣', + 3: '3️⃣', + 4: '4️⃣', + 5: '5️⃣', + 6: '6️⃣', + 7: '7️⃣', + 8: '8️⃣', + 9: '9️⃣', + 10: '🔟', + } +}; \ No newline at end of file diff --git a/user/example.config.js b/user/example.config.js index 3aadcb8..80d53c8 100644 --- a/user/example.config.js +++ b/user/example.config.js @@ -25,7 +25,6 @@ module.exports = { command_prefix: prefix, log_messages: true, // transcripts/archives will be empty if false name_format: 'ticket-{number}', - ticket_welcome: 'Hello {name}, thank you for creating a ticket. A member of staff will soon be available to assist you.\n\n__All messages in this channel are stored for future reference.__', }, locale: 'en-GB', // used for globals (such as commands) and the default guild locale logs: {