diff --git a/.eslintrc.js b/.eslintrc.js
index 6e8a582..9a258ff 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -100,7 +100,8 @@ module.exports = {
}
],
'max-lines': [
- 'warn'
+ 'warn',
+ 500
],
'max-statements-per-line': [
'error'
diff --git a/README.md b/README.md
index 1dfa983..8e04037 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,11 @@
[![GitHub stars](https://img.shields.io/github/stars/discord-tickets/bot?style=flat-square)](https://github.com/discord-tickets/bot/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/discord-tickets/bot?style=flat-square)](https://github.com/discord-tickets/bot/stargazers)
[![License](https://img.shields.io/github/license/discord-tickets/bot?style=flat-square)](https://github.com/discord-tickets/bot/blob/main/LICENSE)
+![](https://img.shields.io/badge/dynamic/json?color=5865F2&label=bots&query=clients&url=https%3A%2F%2Fstats.discordtickets.app&logo=discord&logoColor=white&style=flat-square)
+![](https://img.shields.io/badge/dynamic/json?color=5865F2&label=tickets&query=tickets&url=https%3A%2F%2Fstats.discordtickets.app&logo=discord&logoColor=white&style=flat-square)
[![Codacy](https://img.shields.io/codacy/grade/b974eb5f984c40868e07d82c968bd02d?logo=codacy&style=flat-square)](https://www.codacy.com/gh/discord-tickets/bot/dashboard?utm_source=github.com&utm_medium=referral&utm_content=discord-tickets/bot&utm_campaign=Badge_Grade)
[![Discord](https://img.shields.io/discord/451745464480432129?label=discord&color=7289DA&style=flat-square)](https://go.eartharoid.me/discord)
-[![Crowdin](https://badges.crowdin.net/discord-tickets/localized.svg)](https://i18n.discordtickets.app/project/discord-tickets)
+[![Weblate](https://i18n.capestar.net/widgets/discord-tickets/en_GB/bot/svg-badge.svg)](https://i18n.capestar.net/engage/discord-tickets/en_GB/)
@@ -37,6 +39,8 @@ You can also configure the functionality of the bot to your liking and add comma
If the bot hasn't already been translated to your (community's) language, you can [translate](https://github.com/discord-tickets/.github/blob/main//CONTRIBUTING.md#translating) it yourself.
Plugin authors are encouraged to support multiple languages as well.
+[![Weblate](https://i18n.capestar.net/widgets/discord-tickets/en_GB/bot/multi-auto.svg)](https://i18n.capestar.net/engage/discord-tickets/en_GB/)
+
3. **Multiple ticket categories**
Each ticket category has its own settings for messages and the support team roles. There's also multiple methods of creating a ticket.
@@ -88,7 +92,13 @@ Thank you to everyone to has contributed to Discord Tickets, including everyone
## Sponsors
-Does your community or company use Discord Tickets? Sponsor the project to get your logo shown here.
+*Does your community or company use Discord Tickets? [Sponsor the project](https://github.com/discord-tickets/bot/?sponsor=1) to get your logo shown here.*
+
+**These awesome people and communities sponsor Discord Tickets:**
+
+- [reSkybounds](https://reskybounds.com/) ([Discord](https://discord.reskybounds.com/))
+- [Cal#0004](https://discord.com/users/239036926152146944)
+
### Donate
diff --git a/package.json b/package.json
index 8d1277d..c3d8f90 100644
--- a/package.json
+++ b/package.json
@@ -33,22 +33,18 @@
"dependencies": {
"@eartharoid/i18n": "^1.0.0",
"boxen": "^5.0.1",
- "command-line-args": "^5.2.0",
"cryptr": "^6.0.2",
"discord.js": "^13.1.0",
"dotenv": "^8.6.0",
- "jsonschema": "^1.4.0",
"keyv": "^4.0.3",
"leeks.js": "^0.2.2",
"leekslazylogger": "^3.0.2",
+ "ms": "^2.1.3",
"mustache": "^4.2.0",
- "node-emoji": "^1.11.0",
"node-fetch": "^2.6.1",
"semver": "^7.3.5",
"sequelize": "^6.6.5",
- "string-argv": "^0.3.1",
- "terminal-link": "^2.1.1",
- "to-time-monthsfork": "^1.1.4"
+ "terminal-link": "^2.1.1"
},
"devDependencies": {
"all-contributors-cli": "^6.20.0",
@@ -64,10 +60,10 @@
"sqlite3": "^5.0.2"
},
"peerDependencies": {
- "mariadb": "^2.5.2",
- "mysql2": "^2.2.5",
- "pg": "^8.5.1",
- "pg-hstore": "^2.3.3",
- "tedious": "^11.0.3"
+ "mariadb": "^2.5.4",
+ "mysql2": "^2.3.0",
+ "pg": "^8.7.1",
+ "pg-hstore": "^2.3.4",
+ "tedious": "^11.4.0"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index fc6940e..3c7097c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4,19 +4,17 @@ specifiers:
'@eartharoid/i18n': ^1.0.0
all-contributors-cli: ^6.20.0
boxen: ^5.0.1
- command-line-args: ^5.2.0
cryptr: ^6.0.2
discord.js: ^13.1.0
dotenv: ^8.6.0
eslint: ^7.32.0
- jsonschema: ^1.4.0
keyv: ^4.0.3
leeks.js: ^0.2.2
leekslazylogger: ^3.0.2
mariadb: ^2.5.4
+ ms: ^2.1.3
mustache: ^4.2.0
mysql2: ^2.3.0
- node-emoji: ^1.11.0
node-fetch: ^2.6.1
nodemon: ^2.0.12
pg: ^8.7.1
@@ -24,30 +22,24 @@ specifiers:
semver: ^7.3.5
sequelize: ^6.6.5
sqlite3: ^5.0.2
- string-argv: ^0.3.1
tedious: ^11.4.0
terminal-link: ^2.1.1
- to-time-monthsfork: ^1.1.4
dependencies:
'@eartharoid/i18n': 1.0.0
boxen: 5.0.1
- command-line-args: 5.2.0
cryptr: 6.0.2
discord.js: 13.1.0
dotenv: 8.6.0
- jsonschema: 1.4.0
keyv: 4.0.3
leeks.js: 0.2.2
leekslazylogger: 3.0.2
+ ms: 2.1.3
mustache: 4.2.0
- node-emoji: 1.11.0
node-fetch: 2.6.1
semver: 7.3.5
sequelize: 6.6.5_aa1b3c7f5b5df187fb1a5c6073dca637
- string-argv: 0.3.1
terminal-link: 2.1.1
- to-time-monthsfork: 1.1.4
optionalDependencies:
sqlite3: 5.0.2
@@ -591,11 +583,6 @@ packages:
sprintf-js: 1.0.3
dev: true
- /array-back/3.1.0:
- resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==}
- engines: {node: '>=6'}
- dev: false
-
/asn1/0.2.4:
resolution: {integrity: sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==}
dependencies:
@@ -659,10 +646,6 @@ packages:
dev: false
optional: true
- /bignumber.js/2.4.0:
- resolution: {integrity: sha1-g4qZLan51zfg9LLbC+YrsJ3Qxeg=}
- dev: false
-
/binary-extensions/2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
@@ -908,16 +891,6 @@ packages:
dependencies:
delayed-stream: 1.0.0
- /command-line-args/5.2.0:
- resolution: {integrity: sha512-4zqtU1hYsSJzcJBOcNZIbW5Fbk9BkjCp1pZVhQKoRaWL5J7N4XphDLwo8aWwdQpTugxwu+jf9u2ZhkXiqp5Z6A==}
- engines: {node: '>=4.0.0'}
- dependencies:
- array-back: 3.1.0
- find-replace: 3.0.0
- lodash.camelcase: 4.3.0
- typical: 4.0.0
- dev: false
-
/concat-map/0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
@@ -1343,13 +1316,6 @@ packages:
to-regex-range: 5.0.1
dev: true
- /find-replace/3.0.0:
- resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==}
- engines: {node: '>=4.0.0'}
- dependencies:
- array-back: 3.1.0
- dev: false
-
/find-up/4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
@@ -1903,10 +1869,6 @@ packages:
dev: false
optional: true
- /jsonschema/1.4.0:
- resolution: {integrity: sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw==}
- dev: false
-
/jsonwebtoken/8.5.1:
resolution: {integrity: sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==}
engines: {node: '>=4', npm: '>=1.4.28'}
@@ -2019,10 +1981,6 @@ packages:
p-locate: 4.1.0
dev: true
- /lodash.camelcase/4.3.0:
- resolution: {integrity: sha1-soqmKIorn8ZRA1x3EfZathkDMaY=}
- dev: false
-
/lodash.clonedeep/4.5.0:
resolution: {integrity: sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=}
dev: true
@@ -2273,12 +2231,6 @@ packages:
resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==}
optional: true
- /node-emoji/1.11.0:
- resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==}
- dependencies:
- lodash: 4.17.21
- dev: false
-
/node-fetch/2.6.1:
resolution: {integrity: sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==}
engines: {node: 4.x || >=6.0.0}
@@ -3102,11 +3054,6 @@ packages:
engines: {node: '>=4', npm: '>=6'}
dev: true
- /string-argv/0.3.1:
- resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==}
- engines: {node: '>=0.6.19'}
- dev: false
-
/string-width/1.0.2:
resolution: {integrity: sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=}
engines: {node: '>=0.10.0'}
@@ -3314,13 +3261,6 @@ packages:
is-number: 7.0.0
dev: true
- /to-time-monthsfork/1.1.4:
- resolution: {integrity: sha512-3bWuIwm9QeOAq/UClxFp86QMSJ4GVHmAT8X+pkM0mIMVrpJPLfSieY5qvSsfLJugLNWTVpYJ2ayKWXH3jcAdow==}
- engines: {node: '>=4.6'}
- dependencies:
- bignumber.js: 2.4.0
- dev: false
-
/toposort-class/1.0.1:
resolution: {integrity: sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=}
dev: false
@@ -3417,11 +3357,6 @@ packages:
is-typedarray: 1.0.0
dev: true
- /typical/4.0.0:
- resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==}
- engines: {node: '>=8'}
- dev: false
-
/undefsafe/2.0.3:
resolution: {integrity: sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==}
dependencies:
diff --git a/src/commands/add.js b/src/commands/add.js
index 54ea608..b6afbd0 100644
--- a/src/commands/add.js
+++ b/src/commands/add.js
@@ -1,6 +1,6 @@
const Command = require('../modules/commands/command');
const {
- Message, // eslint-disable-line no-unused-vars
+ Interaction, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
@@ -8,111 +8,111 @@ module.exports = class AddCommand extends Command {
constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale);
super(client, {
- aliases: [],
- args: [
- {
- description: i18n('commands.add.args.member.description'),
- example: i18n('commands.add.args.member.example'),
- name: i18n('commands.add.args.member.name'),
- required: true
- },
- {
- description: i18n('commands.add.args.ticket.description'),
- example: i18n('commands.add.args.ticket.example'),
- name: i18n('commands.add.args.ticket.name'),
- required: false
- }
- ],
description: i18n('commands.add.description'),
internal: true,
name: i18n('commands.add.name'),
- process_args: false
+ options: [
+ {
+ description: i18n('commands.add.options.member.description'),
+ name: i18n('commands.add.options.member.name'),
+ required: true,
+ type: Command.option_types.USER
+ },
+ {
+ description: i18n('commands.add.options.ticket.description'),
+ name: i18n('commands.add.options.ticket.name'),
+ required: false,
+ type: Command.option_types.CHANNEL
+ }
+ ]
});
}
/**
- * @param {Message} message
- * @param {string} args
+ * @param {Interaction} interaction
* @returns {Promise}
*/
- async execute(message, args) {
- const settings = await this.client.utils.getSettings(message.guild);
+ async execute(interaction) {
+ const settings = await this.client.utils.getSettings(interaction.guild.id);
+ const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale
const i18n = this.client.i18n.getLocale(settings.locale);
- const ticket = message.mentions.channels.first() ?? message.channel;
- const t_row = await this.client.tickets.resolve(ticket.id, message.guild.id);
+ const channel = interaction.options.getChannel(default_i18n('commands.add.options.ticket.name')) ?? interaction.channel;
+ const t_row = await this.client.tickets.resolve(channel.id, interaction.guild.id);
if (!t_row) {
- return await message.channel.send({
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('commands.add.response.not_a_ticket.title'))
.setDescription(i18n('commands.add.response.not_a_ticket.description'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
}
- const member = message.mentions.members.first() ?? message.guild.members.cache.get(args);
+ const member = interaction.options.getMember(default_i18n('commands.add.options.member.name'));
if (!member) {
- return await message.channel.send({
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('commands.add.response.no_member.title'))
.setDescription(i18n('commands.add.response.no_member.description'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
}
- if (t_row.creator !== message.author.id && !await this.client.utils.isStaff(message.member)) {
- return await message.channel.send({
+ if (t_row.creator !== interaction.member.id && !await this.client.utils.isStaff(interaction.member)) {
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('commands.add.response.no_permission.title'))
.setDescription(i18n('commands.add.response.no_permission.description'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
}
- if (message.channel.id !== ticket.id) {
- await message.channel.send({
- embeds: [
- new MessageEmbed()
- .setColor(settings.success_colour)
- .setAuthor(member.user.username, member.user.displayAvatarURL())
- .setTitle(i18n('commands.add.response.added.title'))
- .setDescription(i18n('commands.add.response.added.description', member.toString(), ticket.toString()))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
- });
- }
+ await interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.success_colour)
+ .setAuthor(member.user.username, member.user.displayAvatarURL())
+ .setTitle(i18n('commands.add.response.added.title'))
+ .setDescription(i18n('commands.add.response.added.description', member.toString(), channel.toString()))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
- await ticket.send({
+ await channel.send({
embeds: [
new MessageEmbed()
.setColor(settings.colour)
.setAuthor(member.user.username, member.user.displayAvatarURL())
.setTitle(i18n('ticket.member_added.title'))
- .setDescription(i18n('ticket.member_added.description', member.toString(), message.author.toString()))
- .setFooter(settings.footer, message.guild.iconURL())
+ .setDescription(i18n('ticket.member_added.description', member.toString(), interaction.user.toString()))
+ .setFooter(settings.footer, interaction.guild.iconURL())
]
});
- await ticket.permissionOverwrites.edit(member, {
+ await channel.permissionOverwrites.edit(member, {
ATTACH_FILES: true,
READ_MESSAGE_HISTORY: true,
SEND_MESSAGES: true,
VIEW_CHANNEL: true
- }, `${message.author.tag} added ${member.user.tag} to the ticket`);
+ }, `${interaction.user.tag} added ${member.user.tag} to the ticket`);
- await this.client.tickets.archives.updateMember(ticket.id, member);
+ await this.client.tickets.archives.updateMember(channel.id, member);
- this.client.log.info(`${message.author.tag} added ${member.user.tag} to ${ticket.id}`);
+ this.client.log.info(`${interaction.user.tag} added ${member.user.tag} to ${channel.id}`);
}
};
diff --git a/src/commands/blacklist.js b/src/commands/blacklist.js
index ef7b90c..29c7997 100644
--- a/src/commands/blacklist.js
+++ b/src/commands/blacklist.js
@@ -1,125 +1,156 @@
const Command = require('../modules/commands/command');
const {
- Message, // eslint-disable-line no-unused-vars
- MessageEmbed
+ Interaction, // eslint-disable-line no-unused-vars
+ MessageEmbed,
+ Role
} = require('discord.js');
module.exports = class BlacklistCommand extends Command {
constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale);
super(client, {
- aliases: [
- i18n('commands.blacklist.aliases.unblacklist')
- ],
- args: [
- {
- description: i18n('commands.blacklist.args.member_or_role.description'),
- example: i18n('commands.blacklist.args.member_or_role.example'),
- name: i18n('commands.blacklist.args.member_or_role.name'),
- required: false
- }
- ],
description: i18n('commands.blacklist.description'),
internal: true,
name: i18n('commands.blacklist.name'),
- permissions: ['MANAGE_GUILD'],
- process_args: false
+ options: [
+ {
+ description: i18n('commands.blacklist.options.add.description'),
+ name: i18n('commands.blacklist.options.add.name'),
+ options: [
+ {
+ description: i18n('commands.blacklist.options.add.options.member_or_role.description'),
+ name: i18n('commands.blacklist.options.add.options.member_or_role.name'),
+ required: true,
+ type: Command.option_types.MENTIONABLE
+ }
+ ],
+ type: Command.option_types.SUB_COMMAND
+ },
+ {
+ description: i18n('commands.blacklist.options.remove.description'),
+ name: i18n('commands.blacklist.options.remove.name'),
+ options: [
+ {
+ description: i18n('commands.blacklist.options.remove.options.member_or_role.description'),
+ name: i18n('commands.blacklist.options.remove.options.member_or_role.name'),
+ required: true,
+ type: Command.option_types.MENTIONABLE
+ }
+ ],
+ type: Command.option_types.SUB_COMMAND
+ },
+ {
+ description: i18n('commands.blacklist.options.show.description'),
+ name: i18n('commands.blacklist.options.show.name'),
+ type: Command.option_types.SUB_COMMAND
+ }
+ ],
+ staff_only: true
});
}
/**
- * @param {Message} message
- * @param {string} args
+ * @param {Interaction} interaction
* @returns {Promise}
*/
- async execute(message, args) {
- const settings = await this.client.utils.getSettings(message.guild);
+ async execute(interaction) {
+ const settings = await this.client.utils.getSettings(interaction.guild.id);
+ const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale
const i18n = this.client.i18n.getLocale(settings.locale);
+ const blacklist = JSON.parse(JSON.stringify(settings.blacklist)); // not the same as `const blacklist = { ...settings.blacklist };` ..?
- const member = message.mentions.members.first();
+ switch (interaction.options.getSubcommand()) {
+ case default_i18n('commands.blacklist.options.add.name'): {
+ const member_or_role = interaction.options.getMentionable(default_i18n('commands.blacklist.options.add.options.member_or_role.name'));
+ const type = member_or_role instanceof Role ? 'role' : 'member';
- if (member && (await this.client.utils.isStaff(member) || member.permissions.has(this.permissions))) {
- return await message.channel.send({
+ if (type === 'member' && await this.client.utils.isStaff(member_or_role)) {
+ return await interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setTitle(i18n('commands.blacklist.response.illegal_action.title'))
+ .setDescription(i18n('commands.blacklist.response.illegal_action.description', member_or_role.toString()))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
+ }
+
+ blacklist[type + 's'].push(member_or_role.id);
+ await interaction.reply({
embeds: [
new MessageEmbed()
- .setColor(settings.colour)
- .setTitle(i18n('commands.blacklist.response.illegal_action.title'))
- .setDescription(i18n('commands.blacklist.response.illegal_action.description', `<@${member.id}>`))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setColor(settings.success_colour)
+ .setTitle(i18n(`commands.blacklist.response.${type}_added.title`))
+ .setDescription(i18n(`commands.blacklist.response.${type}_added.description`, member_or_role.id))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
+ await settings.update({ blacklist });
+ break;
}
+ case default_i18n('commands.blacklist.options.remove.name'): {
+ const member_or_role = interaction.options.getMentionable(default_i18n('commands.blacklist.options.remove.options.member_or_role.name'));
+ const type = member_or_role instanceof Role ? 'role' : 'member';
+ const index = blacklist[type + 's'].findIndex(element => element === member_or_role.id);
+ if (index === -1) {
+ return await interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setTitle(i18n('commands.blacklist.response.invalid.title'))
+ .setDescription(i18n('commands.blacklist.response.invalid.description'))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
+ }
- const role = message.mentions.roles.first();
- let id;
- const input = args.trim().split(/\s/g)[0];
-
- if (member) {
- id = member.id;
- } else if (role) {
- id = role.id;
- } else if (/\d{17,19}/.test(input)) {
- id = input;
- } else if (settings.blacklist.length === 0) {
- return await message.channel.send({
+ blacklist[type + 's'].splice(index, 1);
+ await interaction.reply({
embeds: [
new MessageEmbed()
- .setColor(settings.colour)
- .setTitle(i18n('commands.blacklist.response.empty_list.title'))
- .setDescription(i18n('commands.blacklist.response.empty_list.description', settings.command_prefix))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
- });
- } else {
- // list blacklisted members
- const blacklist = settings.blacklist.map(element => {
- const is_role = message.guild.roles.cache.has(element);
- if (is_role) return `» <@&${element}> (\`${element}\`)`;
- else return `» <@${element}> (\`${element}\`)`;
- });
- return await message.channel.send({
- embeds: [
- new MessageEmbed()
- .setColor(settings.colour)
- .setTitle(i18n('commands.blacklist.response.list.title'))
- .setDescription(blacklist.join('\n'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setColor(settings.success_colour)
+ .setTitle(i18n(`commands.blacklist.response.${type}_removed.title`))
+ .setDescription(i18n(`commands.blacklist.response.${type}_removed.description`, member_or_role.id))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
+ await settings.update({ blacklist });
+ break;
+ }
+ case default_i18n('commands.blacklist.options.show.name'): {
+ if (blacklist.members.length === 0 && blacklist.roles.length === 0) {
+ return await interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.colour)
+ .setTitle(i18n('commands.blacklist.response.empty_list.title'))
+ .setDescription(i18n('commands.blacklist.response.empty_list.description'))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
+ } else {
+ const members = blacklist.members.map(id => `**·** <@${id}>`);
+ const roles = blacklist.roles.map(id => `**·** <@&${id}>`);
+ return await interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.colour)
+ .setTitle(i18n('commands.blacklist.response.list.title'))
+ .addField(i18n('commands.blacklist.response.list.fields.members'), members.join('\n') || 'none')
+ .addField(i18n('commands.blacklist.response.list.fields.roles'), roles.join('\n') || 'none')
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
+ }
}
-
- const is_role = role !== undefined || message.guild.roles.cache.has(id);
- const member_or_role = is_role ? 'role' : 'member';
- const index = settings.blacklist.findIndex(element => element === id);
-
- const new_blacklist = [...settings.blacklist];
-
- if (index === -1) {
- new_blacklist.push(id);
- await message.channel.send({
- embeds: [
- new MessageEmbed()
- .setColor(settings.colour)
- .setTitle(i18n(`commands.blacklist.response.${member_or_role}_added.title`))
- .setDescription(i18n(`commands.blacklist.response.${member_or_role}_added.description`, id))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
- });
- } else {
- new_blacklist.splice(index, 1);
- await message.channel.send({
- embeds: [
- new MessageEmbed()
- .setColor(settings.colour)
- .setTitle(i18n(`commands.blacklist.response.${member_or_role}_removed.title`))
- .setDescription(i18n(`commands.blacklist.response.${member_or_role}_removed.description`, id))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
- });
}
-
- settings.blacklist = new_blacklist;
- await settings.save();
}
};
diff --git a/src/commands/close.js b/src/commands/close.js
index a63f958..9060ab1 100644
--- a/src/commands/close.js
+++ b/src/commands/close.js
@@ -1,260 +1,297 @@
const Command = require('../modules/commands/command');
-// eslint-disable-next-line no-unused-vars
const {
- Message, // eslint-disable-line no-unused-vars
- MessageEmbed,
- MessageMentions
+ Interaction, // eslint-disable-line no-unused-vars
+ MessageActionRow,
+ MessageButton,
+ MessageEmbed
} = require('discord.js');
const { Op } = require('sequelize');
-const toTime = require('to-time-monthsfork');
+const ms = require('ms');
module.exports = class CloseCommand extends Command {
constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale);
super(client, {
- aliases: [
- i18n('commands.close.aliases.delete'),
- i18n('commands.close.aliases.lock')
- ],
- args: [
- {
- alias: i18n('commands.close.args.ticket.alias'),
- description: i18n('commands.close.args.ticket.description'),
- example: i18n('commands.close.args.ticket.example'),
- name: i18n('commands.close.args.ticket.name'),
- required: false,
- type: String
- },
- {
- alias: i18n('commands.close.args.reason.alias'),
- description: i18n('commands.close.args.reason.description'),
- example: i18n('commands.close.args.reason.example'),
- name: i18n('commands.close.args.reason.name'),
- required: false,
- type: String
- },
- {
- alias: i18n('commands.close.args.time.alias'),
- description: i18n('commands.close.args.time.description'),
- example: i18n('commands.close.args.time.example'),
- name: i18n('commands.close.args.time.name'),
- required: false,
- type: String
- }
- ],
description: i18n('commands.close.description'),
internal: true,
name: i18n('commands.close.name'),
- process_args: true
+ options: [
+ {
+ description: i18n('commands.close.options.reason.description'),
+ name: i18n('commands.close.options.reason.name'),
+ required: false,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.close.options.ticket.description'),
+ name: i18n('commands.close.options.ticket.name'),
+ required: false,
+ type: Command.option_types.INTEGER
+ },
+ {
+ description: i18n('commands.close.options.time.description'),
+ name: i18n('commands.close.options.time.name'),
+ required: false,
+ type: Command.option_types.STRING
+ }
+ ]
});
}
/**
- * @param {Message} message
- * @param {*} args
+ * @param {Interaction} interaction
* @returns {Promise}
*/
- async execute(message, args) {
- const arg_ticket = this.args[0].name;
- const arg_reason = this.args[1].name;
- const arg_time = this.args[2].name;
-
- const settings = await this.client.utils.getSettings(message.guild);
+ async execute(interaction) {
+ const settings = await this.client.utils.getSettings(interaction.guild.id);
+ const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale
const i18n = this.client.i18n.getLocale(settings.locale);
- if (args[arg_time]) {
- let period;
+ const reason = interaction.options.getString(default_i18n('commands.close.options.reason.name'));
+ const ticket = interaction.options.getInteger(default_i18n('commands.close.options.ticket.name'));
+ const time = interaction.options.getString(default_i18n('commands.close.options.time.name'));
+ if (time) {
+ let period;
try {
- period = toTime(args[arg_time]).ms();
+ period = ms(time);
} catch {
- return await message.channel.send({
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('commands.close.response.invalid_time.title'))
.setDescription(i18n('commands.close.response.invalid_time.description'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
}
-
const tickets = await this.client.db.models.Ticket.findAndCountAll({
where: {
- guild: message.guild.id,
- last_message: { [Op.lte]: new Date(Date.now() - period) }
+ guild: interaction.guild.id,
+ last_message: { [Op.lte]: new Date(Date.now() - period) },
+ open: true
}
});
if (tickets.count === 0) {
- return await message.channel.send({
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('commands.close.response.no_tickets.title'))
.setDescription(i18n('commands.close.response.no_tickets.description'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
} else {
- const collector_message = await message.channel.send({
+ await interaction.reply({
+ components: [
+ new MessageActionRow()
+ .addComponents(
+ new MessageButton()
+ .setCustomId(`confirm_close_multiple:${interaction.id}`)
+ .setLabel(i18n('commands.close.response.confirm_multiple.buttons.confirm', tickets.count, tickets.count))
+ .setEmoji('✅')
+ .setStyle('SUCCESS')
+ )
+ .addComponents(
+ new MessageButton()
+ .setCustomId(`cancel_close_multiple:${interaction.id}`)
+ .setLabel(i18n('commands.close.response.confirm_multiple.buttons.cancel'))
+ .setEmoji('❌')
+ .setStyle('SECONDARY')
+ )
+ ],
embeds: [
new MessageEmbed()
.setColor(settings.colour)
.setTitle(i18n('commands.close.response.confirm_multiple.title'))
.setDescription(i18n('commands.close.response.confirm_multiple.description', tickets.count, tickets.count))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setFooter(this.client.utils.footer(settings.footer, i18n('collector_expires_in', 30)), interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
- await collector_message.react('✅');
- const filter = (reaction, user) => user.id === message.author.id && reaction.emoji.name === '✅';
- const collector = collector_message.createReactionCollector({
+ const filter = i => i.user.id === interaction.user.id && i.customId.includes(interaction.id);
+ const collector = interaction.channel.createMessageComponentCollector({
filter,
time: 30000
});
- collector.on('collect', async () => {
- await collector_message.reactions.removeAll();
+ collector.on('collect', async i => {
+ await i.deferUpdate();
- await message.channel.send({
- embeds: [
- new MessageEmbed()
- .setColor(settings.success_colour)
- .setTitle(i18n('commands.close.response.closed_multiple.title', tickets.count, tickets.count))
- .setDescription(i18n('commands.close.response.closed_multiple.description', tickets.count, tickets.count))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
- });
+ if (i.customId === `confirm_close_multiple:${interaction.id}`) {
+ for (const ticket of tickets.rows) {
+ await this.client.tickets.close(ticket.id, interaction.user.id, interaction.guild.id, reason);
+ }
- for (const ticket of tickets.rows) {
- await this.client.tickets.close(ticket.id, message.author.id, message.guild.id, args[arg_reason]);
+ await i.editReply({
+ components: [],
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.success_colour)
+ .setTitle(i18n('commands.close.response.closed_multiple.title', tickets.count, tickets.count))
+ .setDescription(i18n('commands.close.response.closed_multiple.description', tickets.count, tickets.count))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
+ } else {
+ await i.editReply({
+ components: [],
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setTitle(i18n('commands.close.response.canceled.title'))
+ .setDescription(i18n('commands.close.response.canceled.description'))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
}
+ collector.stop();
});
collector.on('end', async collected => {
if (collected.size === 0) {
- await collector_message.reactions.removeAll();
- await collector_message.edit({
+ await interaction.editReply({
+ components: [],
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
- .setAuthor(message.author.username, message.author.displayAvatarURL())
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
.setTitle(i18n('commands.close.response.confirmation_timeout.title'))
.setDescription(i18n('commands.close.response.confirmation_timeout.description'))
- .setFooter(this.client.utils.footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
- setTimeout(async () => {
- await collector_message
- .delete()
- .catch(() => this.client.log.warn('Failed to delete response (collector) message'));
- await message
- .delete()
- .catch(() => this.client.log.warn('Failed to delete original message'));
- }, 15000);
}
});
}
-
} else {
- let t_row;
- if (args[arg_ticket]) {
- args[arg_ticket] = args[arg_ticket].replace(MessageMentions.CHANNELS_PATTERN, '$1');
- t_row = await this.client.tickets.resolve(args[arg_ticket], message.guild.id);
+ let t_row;
+ if (ticket) {
+ t_row = await this.client.tickets.resolve(ticket, interaction.guild.id);
if (!t_row) {
- return await message.channel.send({
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('commands.close.response.unresolvable.title'))
- .setDescription(i18n('commands.close.response.unresolvable.description', args[arg_ticket]))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setDescription(i18n('commands.close.response.unresolvable.description', ticket))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
}
} else {
- t_row = await this.client.db.models.Ticket.findOne({ where: { id: message.channel.id } });
-
+ t_row = await this.client.db.models.Ticket.findOne({ where: { id: interaction.channel.id } });
if (!t_row) {
- return await message.channel.send({
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('commands.close.response.not_a_ticket.title'))
- .setDescription(i18n('commands.close.response.not_a_ticket.description', settings.command_prefix))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setDescription(i18n('commands.close.response.not_a_ticket.description'))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
}
}
- const collector_message = await message.channel.send({
+ await interaction.reply({
+ components: [
+ new MessageActionRow()
+ .addComponents(
+ new MessageButton()
+ .setCustomId(`confirm_close:${interaction.id}`)
+ .setLabel(i18n('commands.close.response.confirm.buttons.confirm'))
+ .setEmoji('✅')
+ .setStyle('SUCCESS')
+ )
+ .addComponents(
+ new MessageButton()
+ .setCustomId(`cancel_close:${interaction.id}`)
+ .setLabel(i18n('commands.close.response.confirm.buttons.cancel'))
+ .setEmoji('❌')
+ .setStyle('SECONDARY')
+ )
+ ],
embeds: [
new MessageEmbed()
.setColor(settings.colour)
.setTitle(i18n('commands.close.response.confirm.title'))
- .setDescription(i18n('commands.close.response.confirm.description', t_row.number))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setDescription(settings.log_messages ? i18n('commands.close.response.confirm.description_with_archive') : i18n('commands.close.response.confirm.description'))
+ .setFooter(this.client.utils.footer(settings.footer, i18n('collector_expires_in', 30)), interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
- await collector_message.react('✅');
- const filter = (reaction, user) => user.id === message.author.id && reaction.emoji.name === '✅';
- const collector = collector_message.createReactionCollector({
+ const filter = i => i.user.id === interaction.user.id && i.customId.includes(interaction.id);
+ const collector = interaction.channel.createMessageComponentCollector({
filter,
time: 30000
});
- collector.on('collect', async () => {
- collector.stop();
+ collector.on('collect', async i => {
+ await i.deferUpdate();
- if (message.channel.id === t_row.id) {
- await collector_message.delete();
- } else {
- await collector_message.reactions.removeAll();
- await collector_message.edit({
+ if (i.customId === `confirm_close:${interaction.id}`) {
+ await this.client.tickets.close(t_row.id, interaction.user.id, interaction.guild.id, reason);
+ await i.editReply({
+ components: [],
embeds: [
new MessageEmbed()
.setColor(settings.success_colour)
- .setTitle(i18n('commands.close.response.closed.title'))
+ .setTitle(i18n('commands.close.response.closed.title', t_row.number))
.setDescription(i18n('commands.close.response.closed.description', t_row.number))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
+ } else {
+ await i.editReply({
+ components: [],
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setTitle(i18n('commands.close.response.canceled.title'))
+ .setDescription(i18n('commands.close.response.canceled.description'))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
}
- await this.client.tickets.close(t_row.id, message.author.id, message.guild.id, args[arg_reason]);
+ collector.stop();
});
collector.on('end', async collected => {
if (collected.size === 0) {
- await collector_message.reactions.removeAll();
- await collector_message.edit({
+ await interaction.editReply({
+ components: [],
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
- .setAuthor(message.author.username, message.author.displayAvatarURL())
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
.setTitle(i18n('commands.close.response.confirmation_timeout.title'))
.setDescription(i18n('commands.close.response.confirmation_timeout.description'))
- .setFooter(this.client.utils.footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
- setTimeout(async () => {
- await collector_message
- .delete()
- .catch(() => this.client.log.warn('Failed to delete response (collector) message'));
- await message
- .delete()
- .catch(() => this.client.log.warn('Failed to delete original message'));
- }, 15000);
}
});
-
}
}
};
diff --git a/src/commands/extra/settings.schema.json b/src/commands/extra/settings.schema.json
deleted file mode 100644
index 20853ad..0000000
--- a/src/commands/extra/settings.schema.json
+++ /dev/null
@@ -1,113 +0,0 @@
-{
- "type": "object",
- "properties": {
- "categories": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "id": {
- "type": "string"
- },
- "claiming": {
- "type": "boolean"
- },
- "image": {
- "type": [
- "string",
- "null"
- ]
- },
- "max_per_member": {
- "type": "number"
- },
- "name": {
- "type": "string"
- },
- "name_format": {
- "type": "string"
- },
- "opening_message": {
- "type": "string"
- },
- "opening_questions": {
- "type": [
- "array",
- "null"
- ],
- "items": {
- "type": "string"
- }
- },
- "ping": {
- "type": [
- "array",
- "null"
- ],
- "items": {
- "type": "string"
- }
- },
- "require_topic": {
- "type": "boolean"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "survey": {
- "type": [
- "string",
- "null"
- ]
- }
- },
- "required": [
- "name",
- "roles"
- ]
- }
- },
- "colour": {
- "type": "string"
- },
- "command_prefix": {
- "type": "string"
- },
- "error_colour": {
- "type": "string"
- },
- "footer": {
- "type": "string"
- },
- "locale": {
- "type": "string"
- },
- "log_messages": {
- "type": "boolean"
- },
- "success_colour": {
- "type": "string"
- },
- "surveys": {
- "type": "object"
- },
- "tags": {
- "type": "object"
- }
- },
- "required": [
- "categories",
- "colour",
- "command_prefix",
- "error_colour",
- "footer",
- "locale",
- "log_messages",
- "success_colour",
- "surveys",
- "tags"
- ]
-}
diff --git a/src/commands/help.js b/src/commands/help.js
index ed500cc..eed7c8a 100644
--- a/src/commands/help.js
+++ b/src/commands/help.js
@@ -1,6 +1,6 @@
const Command = require('../modules/commands/command');
const {
- Message, // eslint-disable-line no-unused-vars
+ Interaction, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
@@ -8,61 +8,42 @@ module.exports = class HelpCommand extends Command {
constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale);
super(client, {
- aliases: [
- i18n('commands.help.aliases.command'),
- i18n('commands.help.aliases.commands')
- ],
- args: [
- {
- description: i18n('commands.help.args.command.description'),
- example: i18n('commands.help.args.command.example'),
- name: i18n('commands.help.args.command.name'),
- required: false
- }
- ],
description: i18n('commands.help.description'),
internal: true,
- name: i18n('commands.help.name'),
- process_args: false
+ name: i18n('commands.help.name')
});
}
/**
- * @param {Message} message
- * @param {string} args
+ * @param {Interaction} interaction
* @returns {Promise}
*/
- async execute(message, args) {
- const settings = await this.client.utils.getSettings(message.guild);
+ async execute(interaction) {
+ const settings = await this.client.utils.getSettings(interaction.guild.id);
const i18n = this.client.i18n.getLocale(settings.locale);
- const cmd = this.manager.commands.find(command => command.aliases.includes(args.toLowerCase()));
-
- if (cmd) {
- return await cmd.sendUsage(message.channel, args);
- } else {
- const is_staff = await this.client.utils.isStaff(message.member);
- const commands = this.manager.commands.filter(command => {
- if (command.permissions.length >= 1) return message.member.permissions.has(command.permissions);
- else if (command.staff_only) return is_staff;
- else return true;
- });
- const list = commands.map(command => {
- const description = command.description.length > 50
- ? command.description.substring(0, 50) + '...'
- : command.description;
- return `**\`${settings.command_prefix}${command.name}\` ·** ${description}`;
- });
- return await message.channel.send({
- embeds: [
- new MessageEmbed()
- .setColor(settings.colour)
- .setTitle(i18n('commands.help.response.list.title'))
- .setDescription(i18n('commands.help.response.list.description', { prefix: settings.command_prefix }))
- .addField(i18n('commands.help.response.list.fields.commands'), list.join('\n'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
- });
- }
+ const is_staff = await this.client.utils.isStaff(interaction.member);
+ const commands = this.manager.commands.filter(command => {
+ if (command.permissions.length >= 1) return interaction.member.permissions.has(command.permissions);
+ else if (command.staff_only) return is_staff;
+ else return true;
+ });
+ const list = commands.map(command => {
+ const description = command.description.length > 50
+ ? command.description.substring(0, 50) + '...'
+ : command.description;
+ return `**\`/${command.name}\` ·** ${description}`;
+ });
+ return await interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.colour)
+ .setTitle(i18n('commands.help.response.list.title'))
+ .setDescription(i18n('commands.help.response.list.description'))
+ .addField(i18n('commands.help.response.list.fields.commands'), list.join('\n'))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
}
};
diff --git a/src/commands/new.js b/src/commands/new.js
index cc91203..8bec13e 100644
--- a/src/commands/new.js
+++ b/src/commands/new.js
@@ -1,72 +1,64 @@
const Command = require('../modules/commands/command');
const {
- Message, // eslint-disable-line no-unused-vars
- MessageEmbed
+ Interaction, // eslint-disable-line no-unused-vars,
+ MessageActionRow,
+ MessageEmbed,
+ MessageSelectMenu
} = require('discord.js');
-const { letters } = require('../utils/emoji');
-const { wait } = require('../utils');
module.exports = class NewCommand extends Command {
constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale);
super(client, {
- aliases: [
- i18n('commands.new.aliases.create'),
- i18n('commands.new.aliases.open'),
- i18n('commands.new.aliases.ticket')
- ],
- args: [
- {
- description: i18n('commands.new.args.topic.description'),
- example: i18n('commands.new.args.topic.example'),
- name: i18n('commands.new.args.topic.name'),
- required: false
- }
- ],
description: i18n('commands.new.description'),
internal: true,
name: i18n('commands.new.name'),
- process_args: false
+ options: [
+ {
+ description: i18n('commands.new.options.topic.description'),
+ name: i18n('commands.new.options.topic.name'),
+ required: false,
+ type: Command.option_types.STRING
+ }
+ ]
});
}
/**
- * @param {Message} message
- * @param {string} args
+ * @param {Interaction} interaction
* @returns {Promise}
*/
- async execute(message, args) {
- const settings = await this.client.utils.getSettings(message.guild);
+ async execute(interaction) {
+ const settings = await this.client.utils.getSettings(interaction.guild.id);
+ const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale
const i18n = this.client.i18n.getLocale(settings.locale);
- const editOrSend = async (msg, content) => {
- if (msg) return await msg.edit(content);
- else return await message.channel.send(content);
- };
+ const topic = interaction.options.getString(default_i18n('commands.new.options.topic.name'));
- const create = async (cat_row, response) => {
+ const create = async (cat_row, i) => {
const tickets = await this.client.db.models.Ticket.findAndCountAll({
where: {
category: cat_row.id,
- creator: message.author.id,
+ creator: interaction.user.id,
open: true
}
});
if (tickets.count >= cat_row.max_per_member) {
if (cat_row.max_per_member === 1) {
- response = await editOrSend(response,
- {
- embeds: [
- 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(this.client.utils.footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL())
- ]
- }
- );
+ const response = {
+ components: [],
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
+ .setTitle(i18n('commands.new.response.has_a_ticket.title'))
+ .setDescription(i18n('commands.new.response.has_a_ticket.description', tickets.rows[0].id))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ };
+ await i ? i.editReply(response) : interaction.reply(response);
} else {
const list = tickets.rows.map(row => {
if (row.topic) {
@@ -77,136 +69,120 @@ module.exports = class NewCommand extends Command {
return `<#${row.id}>`;
}
});
- response = await editOrSend(response,
- {
- embeds: [
- 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(this.client.utils.footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL())
- ]
- }
- );
+ const response = {
+ components: [],
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
+ .setTitle(i18n('commands.new.response.max_tickets.title', tickets.count))
+ .setDescription(i18n('commands.new.response.max_tickets.description', list.join('\n')))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ };
+ await i ? i.editReply(response) : interaction.reply(response);
}
} else {
try {
- const t_row = await this.client.tickets.create(message.guild.id, message.author.id, cat_row.id, args);
- response = await editOrSend(response,
- {
- embeds: [
- 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(this.client.utils.footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL())
- ]
- }
- );
+ const t_row = await this.client.tickets.create(interaction.guild.id, interaction.user.id, cat_row.id, topic);
+ const response = {
+ components: [],
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.success_colour)
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
+ .setTitle(i18n('commands.new.response.created.title'))
+ .setDescription(i18n('commands.new.response.created.description', `<#${t_row.id}>`))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ };
+ await i ? i.editReply(response) : interaction.reply(response);
} catch (error) {
- response = await editOrSend(response,
- {
- embeds: [
- new MessageEmbed()
- .setColor(settings.error_colour)
- .setAuthor(message.author.username, message.author.displayAvatarURL())
- .setTitle(i18n('commands.new.response.error.title'))
- .setDescription(error.message)
- .setFooter(this.client.utils.footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL())
- ]
- }
- );
+ const response = {
+ components: [],
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
+ .setTitle(i18n('commands.new.response.error.title'))
+ .setDescription(error.message)
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ };
+ await i ? i.editReply(response) : interaction.reply(response);
}
}
-
- setTimeout(async () => {
- await response
- .delete()
- .catch(() => this.client.log.warn('Failed to delete response message'));
- await message
- .delete()
- .catch(() => this.client.log.warn('Failed to delete original message'));
- }, 15000);
};
- const categories = await this.client.db.models.Category.findAndCountAll({ where: { guild: message.guild.id } });
+ const categories = await this.client.db.models.Category.findAndCountAll({ where: { guild: interaction.guild.id } });
if (categories.count === 0) {
- return await message.channel.send({
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
- .setAuthor(message.author.username, message.author.displayAvatarURL())
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
.setTitle(i18n('commands.new.response.no_categories.title'))
.setDescription(i18n('commands.new.response.no_categories.description'))
- .setFooter(settings.footer, message.guild.iconURL())
+ .setFooter(settings.footer, interaction.guild.iconURL())
]
});
} else if (categories.count === 1) {
create(categories.rows[0]); // skip the category selection
} else {
- const letters_array = Object.values(letters); // convert the A-Z emoji object to an array
- const category_list = categories.rows.map((category, i) => `${letters_array[i]} » ${category.name}`); // list category names with an A-Z emoji
- const collector_message = await message.channel.send({
+ await interaction.reply({
+ components: [
+ new MessageActionRow()
+ .addComponents(
+ new MessageSelectMenu()
+ .setCustomId(`select_category:${interaction.id}`)
+ .setPlaceholder('Select a category')
+ .addOptions(categories.rows.map(row => ({
+ label: row.name,
+ value: row.id
+ })))
+ )
+ ],
embeds: [
new MessageEmbed()
.setColor(settings.colour)
- .setAuthor(message.author.username, message.author.displayAvatarURL())
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
.setTitle(i18n('commands.new.response.select_category.title'))
- .setDescription(i18n('commands.new.response.select_category.description', category_list.join('\n')))
- .setFooter(this.client.utils.footer(settings.footer, i18n('collector_expires_in', 30)), message.guild.iconURL())
- ]
+ .setDescription(i18n('commands.new.response.select_category.description'))
+ .setFooter(this.client.utils.footer(settings.footer, i18n('collector_expires_in', 30)), interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
- for (const i in categories.rows) {
- collector_message.react(letters_array[i]); // add the correct number of letter reactions
- await wait(1000); // 1 reaction per second rate-limit
- }
-
- const filter = (reaction, user) => {
- const allowed = letters_array.slice(0, categories.count); // get the first x letters of the emoji array
- return user.id === message.author.id && allowed.includes(reaction.emoji.name);
- };
- const collector = collector_message.createReactionCollector({
+ const filter = i => i.user.id === interaction.user.id && i.customId.includes(interaction.id);
+ const collector = interaction.channel.createMessageComponentCollector({
filter,
time: 30000
});
- collector.on('collect', async reaction => {
+ collector.on('collect', async i => {
+ await i.deferUpdate();
+ create(categories.rows.find(row => row.id === i.values[0]), i);
collector.stop();
- const index = letters_array.findIndex(value => value === reaction.emoji.name); // find where the letter is in the alphabet
- if (index === -1) {
- return setTimeout(async () => {
- await collector_message.delete();
- }, 15000);
- }
- await collector_message.reactions.removeAll();
- create(categories.rows[index], collector_message); // create the ticket, passing the existing response message to be edited instead of creating a new one
});
collector.on('end', async collected => {
if (collected.size === 0) {
- await collector_message.reactions.removeAll();
- await collector_message.edit({
+ await interaction.editReply({
+ components: [],
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
- .setAuthor(message.author.username, message.author.displayAvatarURL())
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
.setTitle(i18n('commands.new.response.select_category_timeout.title'))
.setDescription(i18n('commands.new.response.select_category_timeout.description'))
- .setFooter(this.client.utils.footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
- setTimeout(async () => {
- await collector_message
- .delete()
- .catch(() => this.client.log.warn('Failed to delete response (collector) message'));
- await message
- .delete()
- .catch(() => this.client.log.warn('Failed to delete original message'));
- }, 15000);
}
});
}
diff --git a/src/commands/panel.js b/src/commands/panel.js
index d7399d4..a791374 100644
--- a/src/commands/panel.js
+++ b/src/commands/panel.js
@@ -1,85 +1,98 @@
const Command = require('../modules/commands/command');
const {
- Message, // eslint-disable-line no-unused-vars
- MessageEmbed
+ Interaction, // eslint-disable-line no-unused-vars
+ MessageActionRow,
+ MessageButton,
+ MessageEmbed,
+ MessageSelectMenu
} = require('discord.js');
-const {
- some, wait
-} = require('../utils');
-const { emojify } = require('node-emoji');
+const { some } = require('../utils');
module.exports = class PanelCommand extends Command {
constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale);
super(client, {
- aliases: [],
- args: [
- {
- alias: i18n('commands.panel.args.title.alias'),
- description: i18n('commands.panel.args.title.description'),
- example: i18n('commands.panel.args.title.example'),
- name: i18n('commands.panel.args.title.name'),
- required: false,
- type: String
- },
- {
- alias: i18n('commands.panel.args.description.alias'),
- description: i18n('commands.panel.args.description.description'),
- example: i18n('commands.panel.args.description.example'),
- name: i18n('commands.panel.args.description.name'),
- required: true,
- type: String
- },
- {
- alias: i18n('commands.panel.args.emoji.alias'),
- description: i18n('commands.panel.args.emoji.description'),
- example: i18n('commands.panel.args.emoji.example'),
- multiple: true,
- name: i18n('commands.panel.args.emoji.name'),
- required: false,
- type: String
- },
- {
- alias: i18n('commands.panel.args.categories.alias'),
- description: i18n('commands.panel.args.categories.description'),
- example: i18n('commands.panel.args.categories.example'),
- multiple: true,
- name: i18n('commands.panel.args.categories.name'),
- required: true,
- type: String
- }
- ],
description: i18n('commands.panel.description'),
internal: true,
name: i18n('commands.panel.name'),
- permissions: ['MANAGE_GUILD'],
- process_args: true
+ options: [
+ {
+ description: i18n('commands.panel.options.categories.description'),
+ multiple: true,
+ name: i18n('commands.panel.options.categories.name'),
+ required: true,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.panel.options.description.description'),
+ name: i18n('commands.panel.options.description.name'),
+ required: false,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.panel.options.image.description'),
+ name: i18n('commands.panel.options.image.name'),
+ required: false,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.panel.options.just_type.description') + ' (false)',
+ name: i18n('commands.panel.options.just_type.name'),
+ required: false,
+ type: Command.option_types.BOOLEAN
+ },
+ {
+ description: i18n('commands.panel.options.title.description'),
+ name: i18n('commands.panel.options.title.name'),
+ required: false,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.panel.options.thumbnail.description'),
+ name: i18n('commands.panel.options.thumbnail.name'),
+ required: false,
+ type: Command.option_types.STRING
+ }
+ ],
+ staff_only: true
});
}
/**
- * @param {Message} message
- * @param {*} args
+ * @param {Interaction} interaction
* @returns {Promise}
*/
- async execute(message, args) {
- // localised command and arg names are a pain
- const arg_title = this.args[0].name;
- const arg_description = this.args[1].name;
- const arg_emoji = this.args[2].name;
- const arg_categories = this.args[3].name;
-
- const settings = await this.client.utils.getSettings(message.guild);
+ async execute(interaction) {
+ const settings = await this.client.utils.getSettings(interaction.guild.id);
+ const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale
const i18n = this.client.i18n.getLocale(settings.locale);
- if (!args[arg_emoji]) args[arg_emoji] = [];
+ const categories = interaction.options.getString(default_i18n('commands.panel.options.categories.name'))
+ .replace(/\s/g, '')
+ .split(',');
+ const description = interaction.options.getString(default_i18n('commands.panel.options.description.name'));
+ const image = interaction.options.getString(default_i18n('commands.panel.options.image.name'));
+ const just_type = interaction.options.getBoolean(default_i18n('commands.panel.options.just_type.name'));
+ const title = interaction.options.getString(default_i18n('commands.panel.options.title.name'));
+ const thumbnail = interaction.options.getString(default_i18n('commands.panel.options.thumbnail.name'));
- args[arg_emoji] = args[arg_emoji].map(emoji => emojify(emoji.replace(/\\/g, '')));
+ if (just_type && categories.length > 1) {
+ return await interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setTitle(i18n('commands.panel.response.too_many_categories.title'))
+ .setDescription(i18n('commands.panel.response.too_many_categories.description'))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
+ }
- const invalid_category = await some(args[arg_categories], async id => {
+ const invalid_category = await some(categories, async id => {
const cat_row = await this.client.db.models.Category.findOne({
where: {
- guild: message.guild.id,
+ guild: interaction.guild.id,
id
}
});
@@ -87,36 +100,36 @@ module.exports = class PanelCommand extends Command {
});
if (invalid_category) {
- return await message.channel.send({
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('commands.panel.response.invalid_category.title'))
.setDescription(i18n('commands.panel.response.invalid_category.description'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
}
- let panel_channel,
- panel_message;
-
- let categories_map = args[arg_categories][0];
+ let panel_channel;
const embed = new MessageEmbed()
.setColor(settings.colour)
- .setFooter(settings.footer, message.guild.iconURL());
+ .setFooter(settings.footer, interaction.guild.iconURL());
- if (args[arg_title]) embed.setTitle(args[arg_title]);
+ if (description) embed.setDescription(description);
+ if (image) embed.setImage(image);
+ if (title) embed.setTitle(title);
+ if (thumbnail) embed.setThumbnail(thumbnail);
- if (args[arg_emoji].length === 0) {
- // reaction-less panel
- panel_channel = await message.guild.channels.create('create-a-ticket', {
+ if (just_type) {
+ panel_channel = await interaction.guild.channels.create('create-a-ticket', {
permissionOverwrites: [
{
allow: ['VIEW_CHANNEL', 'SEND_MESSAGES', 'READ_MESSAGE_HISTORY'],
deny: ['ATTACH_FILES', 'EMBED_LINKS', 'ADD_REACTIONS'],
- id: message.guild.roles.everyone
+ id: interaction.guild.roles.everyone
},
{
allow: ['SEND_MESSAGES', 'EMBED_LINKS', 'ADD_REACTIONS'],
@@ -125,92 +138,75 @@ module.exports = class PanelCommand extends Command {
],
position: 1,
rateLimitPerUser: 30,
- reason: `${message.author.tag} created a new reaction-less panel`,
+ reason: `${interaction.user.tag} created a new message panel`,
+ type: 'GUILD_TEXT'
+ });
+ await panel_channel.send({ embeds: [embed] });
+ this.client.log.info(`${interaction.user.tag} has created a new message panel`);
+ } else {
+ panel_channel = await interaction.guild.channels.create('create-a-ticket', {
+ permissionOverwrites: [
+ {
+ allow: ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY'],
+ deny: ['SEND_MESSAGES', 'ADD_REACTIONS'],
+ id: interaction.guild.roles.everyone
+ },
+ {
+ allow: ['SEND_MESSAGES', 'EMBED_LINKS', 'ADD_REACTIONS'],
+ id: this.client.user.id
+ }
+ ],
+ position: 1,
+ reason: `${interaction.user.tag} created a new panel`,
type: 'GUILD_TEXT'
});
- embed.setDescription(args[arg_description]);
- panel_message = await panel_channel.send({ embeds: [embed] });
-
- this.client.log.info(`${message.author.tag} has created a new reaction-less panel`);
- } else {
- if (args[arg_categories].length !== args[arg_emoji].length) {
- // send error
- return await message.channel.send({
- embeds: [
- new MessageEmbed()
- .setColor(settings.error_colour)
- .setTitle(i18n('commands.panel.response.mismatch.title'))
- .setDescription(i18n('commands.panel.response.mismatch.description'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
- });
- } else {
- panel_channel = await message.guild.channels.create('create-a-ticket', {
- permissionOverwrites: [
- {
- allow: ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY'],
- deny: ['SEND_MESSAGES', 'ADD_REACTIONS'],
- id: message.guild.roles.everyone
- },
- {
- allow: ['SEND_MESSAGES', 'EMBED_LINKS', 'ADD_REACTIONS'],
- id: this.client.user.id
- }
+ if (categories.length === 1) {
+ // single category
+ await panel_channel.send({
+ components: [
+ new MessageActionRow()
+ .addComponents(
+ new MessageButton()
+ .setCustomId(`panel.single:${categories[0]}`)
+ .setLabel(i18n('panel.create_ticket'))
+ .setStyle('PRIMARY')
+ )
],
- position: 1,
- reason: `${message.author.tag} created a new panel`,
- type: 'GUILD_TEXT'
+ embeds: [embed]
});
-
- if (args[arg_emoji].length === 1) {
- // single category
- categories_map = {};
- categories_map[args[arg_emoji][0]] = args[arg_categories][0];
- embed.setDescription(args[arg_description]);
- panel_message = await panel_channel.send({ embeds: [embed] });
- await panel_message.react(args[arg_emoji][0]);
- } else {
- // multi category
- let description = '';
- categories_map = {};
-
- for (const i in args[arg_emoji]) {
- categories_map[args[arg_emoji][i]] = args[arg_categories][i];
- const cat_row = await this.client.db.models.Category.findOne({
- where: {
- guild: message.guild.id,
- id: args[arg_categories][i]
- }
- });
- description += `\n> ${args[arg_emoji][i]} | ${cat_row.name}`;
- }
-
- embed.setDescription(args[arg_description] + '\n' + description);
- panel_message = await panel_channel.send({
- embeds: [
- embed
- ]
- });
-
- for (const emoji of args[arg_emoji]) {
- await panel_message.react(emoji);
- await wait(1000); // 1 reaction per second rate-limit
- }
-
- }
-
- this.client.log.info(`${message.author.tag} has created a new panel`);
+ this.client.log.info(`${interaction.user.tag} has created a new button panel`);
+ } else {
+ // multi category
+ const rows = await this.client.db.models.Category.findAll({ where: { guild: interaction.guild.id } });
+ await panel_channel.send({
+ components: [
+ new MessageActionRow()
+ .addComponents(
+ new MessageSelectMenu()
+ .setCustomId(`panel.multiple:${panel_channel.id}`)
+ .setPlaceholder('Select a category')
+ .addOptions(rows.map(row => ({
+ label: row.name,
+ value: row.id
+ })))
+ )
+ ],
+ embeds: [embed]
+ });
+ this.client.log.info(`${interaction.user.tag} has created a new select panel`);
}
}
- message.channel.send({ content: `✅ ${panel_channel}` });
+ interaction.reply({
+ content: `✅ ${panel_channel}`,
+ ephemeral: true
+ });
await this.client.db.models.Panel.create({
- categories: categories_map,
+ category: categories.length === 1 ? categories[0] : null,
channel: panel_channel.id,
- guild: message.guild.id,
- message: panel_message.id
+ guild: interaction.guild.id
});
}
};
diff --git a/src/commands/remove.js b/src/commands/remove.js
index 0b148c8..85de1da 100644
--- a/src/commands/remove.js
+++ b/src/commands/remove.js
@@ -1,6 +1,6 @@
const Command = require('../modules/commands/command');
const {
- Message, // eslint-disable-line no-unused-vars
+ Interaction, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
@@ -8,106 +8,104 @@ module.exports = class RemoveCommand extends Command {
constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale);
super(client, {
- aliases: [],
- args: [
- {
- description: i18n('commands.remove.args.member.description'),
- example: i18n('commands.remove.args.member.example'),
- name: i18n('commands.remove.args.member.name'),
- required: true
- },
- {
- description: i18n('commands.remove.args.ticket.description'),
- example: i18n('commands.remove.args.ticket.example'),
- name: i18n('commands.remove.args.ticket.name'),
- required: false
- }
- ],
description: i18n('commands.remove.description'),
internal: true,
name: i18n('commands.remove.name'),
- process_args: false
+ options: [
+ {
+ description: i18n('commands.remove.options.member.description'),
+ name: i18n('commands.remove.options.member.name'),
+ required: true,
+ type: Command.option_types.USER
+ },
+ {
+ description: i18n('commands.remove.options.ticket.description'),
+ name: i18n('commands.remove.options.ticket.name'),
+ required: false,
+ type: Command.option_types.CHANNEL
+ }
+ ]
});
}
/**
- * @param {Message} message
- * @param {string} args
+ * @param {Interaction} interaction
* @returns {Promise}
*/
- async execute(message, args) {
- const settings = await this.client.utils.getSettings(message.guild);
+ async execute(interaction) {
+ const settings = await this.client.utils.getSettings(interaction.guild.id);
+ const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale
const i18n = this.client.i18n.getLocale(settings.locale);
- const ticket = message.mentions.channels.first() ?? message.channel;
- const t_row = await this.client.tickets.resolve(ticket.id, message.guild.id);
+ const channel = interaction.options.getChannel(default_i18n('commands.remove.options.channel.name')) ?? interaction.channel;
+ const t_row = await this.client.tickets.resolve(channel.id, interaction.guild.id);
if (!t_row) {
- return await message.channel.send({
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
- .setTitle(i18n('commands.remove.response.not_a_ticket.title'))
- .setDescription(i18n('commands.remove.response.not_a_ticket.description'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setTitle(i18n('commands.remove.response.not_a_channel.title'))
+ .setDescription(i18n('commands.remove.response.not_a_channel.description'))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
}
- const member = message.mentions.members.first() ?? message.guild.members.cache.get(args);
+ const member = interaction.options.getMember(default_i18n('commands.remove.options.member.name'));
if (!member) {
- return await message.channel.send({
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('commands.remove.response.no_member.title'))
.setDescription(i18n('commands.remove.response.no_member.description'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
}
- if (t_row.creator !== message.author.id && !await this.client.utils.isStaff(message.member)) {
- return await message.channel.send({
+ if (t_row.creator !== interaction.user.id && !await this.client.utils.isStaff(interaction.member)) {
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('commands.remove.response.no_permission.title'))
.setDescription(i18n('commands.remove.response.no_permission.description'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
}
- if (message.channel.id !== ticket.id) {
- await message.channel.send({
- embeds: [
- new MessageEmbed()
- .setColor(settings.success_colour)
- .setAuthor(member.user.username, member.user.displayAvatarURL())
- .setTitle(i18n('commands.remove.response.removed.title'))
- .setDescription(i18n('commands.remove.response.removed.description', member.toString(), ticket.toString()))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
- });
- }
+ await interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.success_colour)
+ .setAuthor(member.user.username, member.user.displayAvatarURL())
+ .setTitle(i18n('commands.remove.response.removed.title'))
+ .setDescription(i18n('commands.remove.response.removed.description', member.toString(), channel.toString()))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
- await ticket.send({
+ await channel.send({
embeds: [
new MessageEmbed()
.setColor(settings.colour)
.setAuthor(member.user.username, member.user.displayAvatarURL())
.setTitle(i18n('ticket.member_removed.title'))
- .setDescription(i18n('ticket.member_removed.description', member.toString(), message.author.toString()))
- .setFooter(settings.footer, message.guild.iconURL())
+ .setDescription(i18n('ticket.member_removed.description', member.toString(), interaction.user.toString()))
+ .setFooter(settings.footer, interaction.guild.iconURL())
]
});
- await ticket.permissionOverwrites
- .get(member.user.id)
- ?.delete(`${message.author.tag} removed ${member.user.tag} from the ticket`);
+ await channel.permissionOverwrites.delete(member.user.id, `${interaction.user.tag} removed ${member.user.tag} from the ticket`);
- this.client.log.info(`${message.author.tag} removed ${member.user.tag} from ${ticket.id}`);
+ this.client.log.info(`${interaction.user.tag} removed ${member.user.tag} from ${channel.id}`);
}
};
diff --git a/src/commands/settings.js b/src/commands/settings.js
index 1b9cdea..17d3170 100644
--- a/src/commands/settings.js
+++ b/src/commands/settings.js
@@ -1,201 +1,350 @@
+/* eslint-disable max-lines */
const Command = require('../modules/commands/command');
-const fetch = require('node-fetch');
const {
- Message, // eslint-disable-line no-unused-vars
- MessageAttachment
+ Interaction, // eslint-disable-line no-unused-vars
+ MessageEmbed
} = require('discord.js');
-const { Validator } = require('jsonschema');
module.exports = class SettingsCommand extends Command {
constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale);
super(client, {
- aliases: [
- i18n('commands.settings.aliases.config')
- ],
- args: [],
description: i18n('commands.settings.description'),
internal: true,
name: i18n('commands.settings.name'),
- permissions: ['MANAGE_GUILD'],
- process_args: false
+ options: [
+ {
+ description: i18n('commands.settings.options.categories.description'),
+ name: i18n('commands.settings.options.categories.name'),
+ options: [
+ {
+ description: i18n('commands.settings.options.categories.options.create.description'),
+ name: i18n('commands.settings.options.categories.options.create.name'),
+ options: [
+ {
+ description: i18n('commands.settings.options.categories.options.create.options.name.description'),
+ name: i18n('commands.settings.options.categories.options.create.options.name.name'),
+ required: true,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.settings.options.categories.options.create.options.roles.description'),
+ name: i18n('commands.settings.options.categories.options.create.options.roles.name'),
+ required: true,
+ type: Command.option_types.STRING
+ }
+ ],
+ type: Command.option_types.SUB_COMMAND
+ },
+ {
+ description: i18n('commands.settings.options.categories.options.delete.description'),
+ name: i18n('commands.settings.options.categories.options.delete.name'),
+ options: [
+ {
+ description: i18n('commands.settings.options.categories.options.delete.options.id.description'),
+ name: i18n('commands.settings.options.categories.options.delete.options.id.name'),
+ required: true,
+ type: Command.option_types.STRING
+ }
+ ],
+ type: Command.option_types.SUB_COMMAND
+ },
+ {
+ description: i18n('commands.settings.options.categories.options.edit.description'),
+ name: i18n('commands.settings.options.categories.options.edit.name'),
+ options: [
+ {
+ description: i18n('commands.settings.options.categories.options.edit.options.id.description'),
+ name: i18n('commands.settings.options.categories.options.edit.options.id.name'),
+ required: true,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.settings.options.categories.options.edit.options.claiming.description'),
+ name: i18n('commands.settings.options.categories.options.edit.options.claiming.name'),
+ required: false,
+ type: Command.option_types.BOOLEAN
+ },
+ {
+ description: i18n('commands.settings.options.categories.options.edit.options.image.description'),
+ name: i18n('commands.settings.options.categories.options.edit.options.image.name'),
+ required: false,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.settings.options.categories.options.edit.options.max_per_member.description'),
+ name: i18n('commands.settings.options.categories.options.edit.options.max_per_member.name'),
+ required: false,
+ type: Command.option_types.INTEGER
+ },
+ {
+ description: i18n('commands.settings.options.categories.options.edit.options.name.description'),
+ name: i18n('commands.settings.options.categories.options.edit.options.name.name'),
+ required: false,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.settings.options.categories.options.edit.options.name_format.description'),
+ name: i18n('commands.settings.options.categories.options.edit.options.name_format.name'),
+ required: false,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.settings.options.categories.options.edit.options.opening_message.description'),
+ name: i18n('commands.settings.options.categories.options.edit.options.opening_message.name'),
+ required: false,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.settings.options.categories.options.edit.options.ping.description'),
+ name: i18n('commands.settings.options.categories.options.edit.options.ping.name'),
+ required: false,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.settings.options.categories.options.edit.options.require_topic.description'),
+ name: i18n('commands.settings.options.categories.options.edit.options.require_topic.name'),
+ required: false,
+ type: Command.option_types.BOOLEAN
+ },
+ {
+ description: i18n('commands.settings.options.categories.options.edit.options.roles.description'),
+ name: i18n('commands.settings.options.categories.options.edit.options.roles.name'),
+ required: false,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.settings.options.categories.options.edit.options.survey.description'),
+ name: i18n('commands.settings.options.categories.options.edit.options.survey.name'),
+ required: false,
+ type: Command.option_types.STRING
+ }
+ ],
+ type: Command.option_types.SUB_COMMAND
+ },
+ {
+ description: i18n('commands.settings.options.categories.options.list.description'),
+ name: i18n('commands.settings.options.categories.options.list.name'),
+ type: Command.option_types.SUB_COMMAND
+ }
+ ],
+ type: Command.option_types.SUB_COMMAND_GROUP
+ },
+ {
+ description: i18n('commands.settings.options.set.description'),
+ name: i18n('commands.settings.options.set.name'),
+ options: [
+ {
+ description: i18n('commands.settings.options.set.options.close_button.description'),
+ name: i18n('commands.settings.options.set.options.close_button.name'),
+ required: false,
+ type: Command.option_types.BOOLEAN
+ },
+ {
+ description: i18n('commands.settings.options.set.options.colour.description'),
+ name: i18n('commands.settings.options.set.options.colour.name'),
+ required: false,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.settings.options.set.options.error_colour.description'),
+ name: i18n('commands.settings.options.set.options.error_colour.name'),
+ required: false,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.settings.options.set.options.footer.description'),
+ name: i18n('commands.settings.options.set.options.footer.name'),
+ required: false,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.settings.options.set.options.locale.description'),
+ name: i18n('commands.settings.options.set.options.locale.name'),
+ required: false,
+ type: Command.option_types.STRING
+ },
+ {
+ description: i18n('commands.settings.options.set.options.log_messages.description'),
+ name: i18n('commands.settings.options.set.options.log_messages.name'),
+ required: false,
+ type: Command.option_types.BOOLEAN
+ },
+ {
+ description: i18n('commands.settings.options.set.options.success_colour.description'),
+ name: i18n('commands.settings.options.set.options.success_colour.name'),
+ required: false,
+ type: Command.option_types.STRING
+ }
+ ],
+ type: Command.option_types.SUB_COMMAND
+ }
+ ],
+ permissions: ['MANAGE_GUILD']
});
- this.schema = require('./extra/settings.schema.json');
- this.v = new Validator();
}
/**
- * @param {Message} message
- * @param {string} args
+ * @param {Interaction} interaction
* @returns {Promise}
*/
- async execute(message) {
- const settings = await this.client.utils.getSettings(message.guild);
+ async execute(interaction) {
+ const settings = await this.client.utils.getSettings(interaction.guild.id);
+ const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale
const i18n = this.client.i18n.getLocale(settings.locale);
- const attachments = [...message.attachments.values()];
-
- if (attachments.length >= 1) {
-
- // load settings from json
- this.client.log.info(`Downloading settings for "${message.guild.name}"`);
- const data = await (await fetch(attachments[0].url)).json();
-
- const {
- valid, errors
- } = this.v.validate(data, this.schema);
-
- if (!valid) {
- this.client.log.warn('Settings validation error');
- return await message.channel.send({ content: i18n('commands.settings.response.invalid', errors.map(error => `\`${error.stack}\``).join(',\n')) });
- }
-
- 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;
- settings.tags = data.tags;
- await settings.save();
-
- for (const c of data.categories) {
- if (c.id) {
- // existing category
- const cat_row = await this.client.db.models.Category.findOne({ where: { id: c.id } });
- cat_row.claiming = c.claiming;
- cat_row.image = c.image;
- cat_row.max_per_member = c.max_per_member;
- cat_row.name = c.name;
- cat_row.name_format = c.name_format;
- cat_row.opening_message = c.opening_message;
- cat_row.opening_questions = c.opening_questions;
- cat_row.ping = c.ping;
- cat_row.require_topic = c.require_topic;
- cat_row.roles = c.roles;
- cat_row.survey = c.survey;
- cat_row.save();
-
- const cat_channel = await this.client.channels.fetch(c.id);
-
- if (cat_channel) {
- if (cat_channel.name !== c.name) await cat_channel.setName(c.name, `Tickets category updated by ${message.author.tag}`);
-
- for (const r of c.roles) {
- await cat_channel.permissionOverwrites.edit(r, {
- ATTACH_FILES: true,
- READ_MESSAGE_HISTORY: true,
- SEND_MESSAGES: true,
- VIEW_CHANNEL: true
- }, `Tickets category updated by ${message.author.tag}`);
+ switch (interaction.options.getSubcommand()) {
+ case default_i18n('commands.settings.options.categories.options.create.name'): {
+ const name = interaction.options.getString(default_i18n('commands.settings.options.categories.options.create.options.name.name'));
+ const roles = interaction.options.getString(default_i18n('commands.settings.options.categories.options.create.options.roles.name'))?.replace(/\s/g, '').split(',');
+ const allowed_permissions = ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'SEND_MESSAGES', 'EMBED_LINKS', 'ATTACH_FILES'];
+ const cat_channel = await interaction.guild.channels.create(name, {
+ permissionOverwrites: [
+ ...[
+ {
+ deny: ['VIEW_CHANNEL'],
+ id: interaction.guild.roles.everyone
+ },
+ {
+ allow: allowed_permissions,
+ id: this.client.user.id
}
- }
- } 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, {
- permissionOverwrites: [
- ...[
- {
- deny: ['VIEW_CHANNEL'],
- id: message.guild.roles.everyone
- },
- {
- allow: allowed_permissions,
- id: this.client.user.id
- }
- ],
- ...c.roles.map(r => ({
- allow: allowed_permissions,
- id: r
- }))
- ],
- position: 1,
- reason: `Tickets category created by ${message.author.tag}`,
- type: 'GUILD_CATEGORY'
- });
-
- await this.client.db.models.Category.create({
- claiming: c.claiming,
- guild: message.guild.id,
- id: cat_channel.id,
- image: c.image,
- max_per_member: c.max_per_member,
- name: c.name,
- name_format: c.name_format,
- opening_message: c.opening_message,
- opening_questions: c.opening_questions,
- ping: c.ping,
- require_topic: c.require_topic,
- roles: c.roles,
- survey: c.survey
- });
- }
- }
-
- for (const survey in data.surveys) {
- const survey_data = {
- guild: message.guild.id,
- name: survey
- };
- const [s_row] = await this.client.db.models.Survey.findOrCreate({
- defaults: survey_data,
- where: survey_data
+ ],
+ ...roles.map(r => ({
+ allow: allowed_permissions,
+ id: r
+ }))
+ ],
+ position: 1,
+ reason: `Tickets category created by ${interaction.user.tag}`,
+ type: 'GUILD_CATEGORY'
+ });
+ await this.client.db.models.Category.create({
+ guild: interaction.guild.id,
+ id: cat_channel.id,
+ name,
+ roles
+ });
+ await this.client.commands.updatePermissions(interaction.guild);
+ interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.success_colour)
+ .setTitle(i18n('commands.settings.response.category_created', name))
+ ],
+ ephemeral: true
+ });
+ break;
+ }
+ case default_i18n('commands.settings.options.categories.options.delete.name'): {
+ const category = await this.client.db.models.Category.findOne({ where: { id: interaction.options.getString(default_i18n('commands.settings.options.categories.options.delete.options.id.name')) } });
+ if (category) {
+ const channel = this.client.channels.cache.get(interaction.options.getString(default_i18n('commands.settings.options.categories.options.delete.options.id.name')));
+ if (channel) channel.delete();
+ await category.destroy();
+ interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.success_colour)
+ .setTitle(i18n('commands.settings.response.category_deleted', category.name))
+ ],
+ ephemeral: true
+ });
+ } else {
+ interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setTitle(i18n('commands.settings.response.category_does_not_exist'))
+ ],
+ ephemeral: true
});
- s_row.questions = data.surveys[survey];
- await s_row.save();
}
-
- this.client.log.success(`Updated guild settings for "${message.guild.name}"`);
- return await message.channel.send({ content: i18n('commands.settings.response.updated') });
- } else {
- // upload settings as json to be edited
-
- const categories = await this.client.db.models.Category.findAll({ where: { guild: message.guild.id } });
-
- const surveys = await this.client.db.models.Survey.findAll({ where: { guild: message.guild.id } });
-
- const data = {
- categories: categories.map(c => ({
- claiming: c.claiming,
- id: c.id,
- image: c.image,
- max_per_member: c.max_per_member,
- name: c.name,
- name_format: c.name_format,
- opening_message: c.opening_message,
- opening_questions: c.opening_questions,
- ping: c.ping,
- require_topic: c.require_topic,
- roles: c.roles,
- survey: c.survey
- })),
- 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,
- surveys: {},
- tags: settings.tags
- };
-
- for (const survey in surveys) {
- const {
- name, questions
- } = surveys[survey];
- data.surveys[name] = questions;
+ break;
+ }
+ case default_i18n('commands.settings.options.categories.options.edit.name'): {
+ const category = await this.client.db.models.Category.findOne({ where: { id: interaction.options.getString(default_i18n('commands.settings.options.categories.options.delete.options.id.name')) } });
+ if (!category) {
+ return interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setTitle(i18n('commands.settings.response.category_does_not_exist'))
+ ],
+ ephemeral: true
+ });
}
-
- const attachment = new MessageAttachment(
- Buffer.from(JSON.stringify(data, null, 2)),
- `Settings for ${message.guild.name}.json`
- );
-
- return await message.channel.send({ files: [attachment] });
+ const claiming = interaction.options.getBoolean(default_i18n('commands.settings.options.categories.options.edit.options.claiming.name'));
+ const image = interaction.options.getString(default_i18n('commands.settings.options.categories.options.edit.options.image.name'));
+ const max_per_member = interaction.options.getInteger(default_i18n('commands.settings.options.categories.options.edit.options.max_per_member.name'));
+ const name = interaction.options.getString(default_i18n('commands.settings.options.categories.options.edit.options.name.name'));
+ const name_format = interaction.options.getString(default_i18n('commands.settings.options.categories.options.edit.options.name_format.name'));
+ const opening_message = interaction.options.getString(default_i18n('commands.settings.options.categories.options.edit.options.opening_message.name'));
+ const ping = interaction.options.getString(default_i18n('commands.settings.options.categories.options.edit.options.ping.name'));
+ const require_topic = interaction.options.getBoolean(default_i18n('commands.settings.options.categories.options.edit.options.require_topic.name'));
+ const roles = interaction.options.getString(default_i18n('commands.settings.options.categories.options.edit.options.roles.name'));
+ const survey = interaction.options.getString(default_i18n('commands.settings.options.categories.options.edit.options.survey.name'));
+ if (claiming !== null) category.set('claiming', claiming);
+ if (max_per_member !== null) category.set('max_per_member', max_per_member);
+ if (image !== null) category.set('image', image);
+ if (name !== null) category.set('name', name);
+ if (name_format !== null) category.set('name_format', name_format);
+ if (opening_message !== null) category.set('opening_message', opening_message);
+ if (ping !== null) category.set('ping', ping.replace(/\s/g, '').split(','));
+ if (require_topic !== null) category.set('require_topic', require_topic);
+ if (roles !== null) category.set('roles', roles.replace(/\s/g, '').split(','));
+ if (survey !== null) category.set('survey', survey);
+ await category.save();
+ interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.success_colour)
+ .setTitle(i18n('commands.settings.response.category_updated', category.name))
+ ],
+ ephemeral: true
+ });
+ break;
+ }
+ case default_i18n('commands.settings.options.categories.options.list.name'): {
+ const categories = await this.client.db.models.Category.findAll({ where: { guild: interaction.guild.id } });
+ await interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.colour)
+ .setTitle(i18n('commands.settings.response.category_list'))
+ .setDescription(categories.map(c => `- ${c.name} (\`${c.id}\`)`).join('\n'))
+ ],
+ ephemeral: true
+ });
+ break;
+ }
+ case default_i18n('commands.settings.options.set.name'): {
+ const close_button = interaction.options.getBoolean(default_i18n('commands.settings.options.set.options.close_button.name'));
+ const colour = interaction.options.getString(default_i18n('commands.settings.options.set.options.colour.name'));
+ const error_colour = interaction.options.getString(default_i18n('commands.settings.options.set.options.error_colour.name'));
+ const footer = interaction.options.getString(default_i18n('commands.settings.options.set.options.footer.name'));
+ const locale = interaction.options.getString(default_i18n('commands.settings.options.set.options.locale.name'));
+ const log_messages = interaction.options.getBoolean(default_i18n('commands.settings.options.set.options.log_messages.name'));
+ const success_colour = interaction.options.getString(default_i18n('commands.settings.options.set.options.success_colour.name'));
+ if (close_button !== null) settings.set('close_button', close_button);
+ if (colour !== null) settings.set('colour', colour.toUpperCase());
+ if (error_colour !== null) settings.set('error_colour', error_colour.toUpperCase());
+ if (footer !== null) settings.set('footer', footer);
+ if (locale !== null) settings.set('locale', locale);
+ if (log_messages !== null) settings.set('log_messages', log_messages);
+ if (success_colour !== null) settings.set('success_colour', success_colour.toUpperCase());
+ await settings.save();
+ interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.success_colour)
+ .setTitle(i18n('commands.settings.response.settings_updated'))
+ ],
+ ephemeral: true
+ });
+ break;
+ }
}
}
};
\ No newline at end of file
diff --git a/src/commands/stats.js b/src/commands/stats.js
index 91ad106..79a832d 100644
--- a/src/commands/stats.js
+++ b/src/commands/stats.js
@@ -1,7 +1,7 @@
const Command = require('../modules/commands/command');
const Keyv = require('keyv');
const {
- Message, // eslint-disable-line no-unused-vars
+ Interaction, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
@@ -9,12 +9,9 @@ module.exports = class StatsCommand extends Command {
constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale);
super(client, {
- aliases: [],
- args: [],
description: i18n('commands.stats.description'),
internal: true,
name: i18n('commands.stats.name'),
- process_args: false,
staff_only: true
});
@@ -22,25 +19,24 @@ module.exports = class StatsCommand extends Command {
}
/**
- * @param {Message} message
- * @param {string} args
+ * @param {Interaction} interaction
* @returns {Promise}
*/
- async execute(message) {
- const settings = await this.client.utils.getSettings(message.guild);
+ async execute(interaction) {
+ const settings = await this.client.utils.getSettings(interaction.guild.id);
const i18n = this.client.i18n.getLocale(settings.locale);
const messages = await this.client.db.models.Message.findAndCountAll();
- let stats = await this.cache.get(message.guild.id);
+ let stats = await this.cache.get(interaction.guild.id);
if (!stats) {
- const tickets = await this.client.db.models.Ticket.findAndCountAll({ where: { guild: message.guild.id } });
+ const tickets = await this.client.db.models.Ticket.findAndCountAll({ where: { guild: interaction.guild.id } });
stats = { // maths
messages: settings.log_messages
? await messages.rows
.reduce(async (acc, row) => (await this.client.db.models.Ticket.findOne({ where: { id: row.ticket } }))
- .guild === message.guild.id
+ .guild === interaction.guild.id
? await acc + 1
: await acc, 0)
: null,
@@ -49,38 +45,51 @@ module.exports = class StatsCommand extends Command {
: acc, 0) / tickets.count),
tickets: tickets.count
};
- await this.cache.set(message.guild.id, stats, 60 * 60 * 1000); // cache for an hour
+ await this.cache.set(interaction.guild.id, stats, 60 * 60 * 1000); // cache for an hour
}
const guild_embed = new MessageEmbed()
.setColor(settings.colour)
.setTitle(i18n('commands.stats.response.guild.title'))
.setDescription(i18n('commands.stats.response.guild.description'))
- .addField(i18n('commands.stats.fields.tickets'), stats.tickets, true)
+ .addField(i18n('commands.stats.fields.tickets'), String(stats.tickets), true)
.addField(i18n('commands.stats.fields.response_time.title'), i18n('commands.stats.fields.response_time.minutes', stats.response_time), true)
- .setFooter(settings.footer, message.guild.iconURL());
+ .setFooter(settings.footer, interaction.guild.iconURL());
- if (stats.messages) guild_embed.addField(i18n('commands.stats.fields.messages'), stats.messages, true);
+ if (stats.messages) guild_embed.addField(i18n('commands.stats.fields.messages'), String(stats.messages), true);
- await message.channel.send({
- embeds: [
- guild_embed
- ]
- });
+ const embeds = [guild_embed];
if (this.client.guilds.cache.size > 1) {
- await message.channel.send({
- embeds: [
- new MessageEmbed()
- .setColor(settings.colour)
- .setTitle(i18n('commands.stats.response.global.title'))
- .setDescription(i18n('commands.stats.response.global.description'))
- .addField(i18n('commands.stats.fields.tickets'), stats.tickets, true)
- .addField(i18n('commands.stats.fields.response_time.title'), i18n('commands.stats.fields.response_time.minutes', stats.response_time), true)
- .addField(i18n('commands.stats.fields.messages'), stats.messages, true)
- .setFooter(settings.footer, message.guild.iconURL())
- ]
- });
+ let global = await this.cache.get('global');
+
+ if (!global) {
+ const tickets = await this.client.db.models.Ticket.findAndCountAll();
+ global = { // maths
+ messages: settings.log_messages
+ ? await messages.count
+ : null,
+ response_time: Math.floor(tickets.rows.reduce((acc, row) => row.first_response
+ ? acc + ((Math.abs(new Date(row.createdAt) - new Date(row.first_response)) / 1000) / 60)
+ : acc, 0) / tickets.count),
+ tickets: tickets.count
+ };
+ await this.cache.set('global', global, 60 * 60 * 1000); // cache for an hour
+ }
+
+ const global_embed = new MessageEmbed()
+ .setColor(settings.colour)
+ .setTitle(i18n('commands.stats.response.global.title'))
+ .setDescription(i18n('commands.stats.response.global.description'))
+ .addField(i18n('commands.stats.fields.tickets'), String(global.tickets), true)
+ .addField(i18n('commands.stats.fields.response_time.title'), i18n('commands.stats.fields.response_time.minutes', global.response_time), true)
+ .setFooter(settings.footer, interaction.guild.iconURL());
+
+ if (stats.messages) global_embed.addField(i18n('commands.stats.fields.messages'), String(global.messages), true);
+
+ embeds.push(global_embed);
}
+
+ await interaction.reply({ embeds });
}
};
diff --git a/src/commands/survey.js b/src/commands/survey.js
index 313d532..86be0a7 100644
--- a/src/commands/survey.js
+++ b/src/commands/survey.js
@@ -13,38 +13,44 @@ module.exports = class SurveyCommand extends Command {
constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale);
super(client, {
- aliases: [
- i18n('commands.survey.aliases.surveys')
- ],
- args: [
- {
- description: i18n('commands.survey.args.survey.description'),
- example: i18n('commands.survey.args.survey.example'),
- name: i18n('commands.survey.args.survey.name'),
- required: false
- }
- ],
description: i18n('commands.survey.description'),
internal: true,
name: i18n('commands.survey.name'),
- process_args: false,
+ options: async guild => {
+ const surveys = await this.client.db.models.Survey.findAll({ where: { guild: guild.id } });
+ return [
+ {
+ choices: surveys.map(survey => ({
+ name: survey.name,
+ value: survey.name
+ })),
+ description: i18n('commands.survey.options.survey.description'),
+ name: i18n('commands.survey.options.survey.name'),
+ required: true,
+ type: Command.option_types.STRING
+ }
+ ];
+ },
staff_only: true
+
});
}
/**
- * @param {Message} message
- * @param {string} args
+ * @param {Interaction} interaction
* @returns {Promise}
*/
- async execute(message, args) {
- const settings = await this.client.utils.getSettings(message.guild);
+ async execute(interaction) {
+ const settings = await this.client.utils.getSettings(interaction.guild.id);
+ const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale
const i18n = this.client.i18n.getLocale(settings.locale);
+ const name = interaction.options.getString(default_i18n('commands.survey.options.survey.name'));
+
const survey = await this.client.db.models.Survey.findOne({
where: {
- guild: message.guild.id,
- name: args
+ guild: interaction.guild.id,
+ name
}
});
@@ -55,7 +61,6 @@ module.exports = class SurveyCommand extends Command {
const users = new Set();
-
for (const i in responses) {
const ticket = await this.client.db.models.Ticket.findOne({ where: { id: responses[i].ticket } });
users.add(ticket.creator);
@@ -85,19 +90,23 @@ module.exports = class SurveyCommand extends Command {
`${survey.name}.html`
);
- return await message.channel.send({ files: [attachment] });
+ return await interaction.reply({
+ ephemeral: true,
+ files: [attachment]
+ });
} else {
- const surveys = await this.client.db.models.Survey.findAll({ where: { guild: message.guild.id } });
+ const surveys = await this.client.db.models.Survey.findAll({ where: { guild: interaction.guild.id } });
const list = surveys.map(s => `❯ **\`${s.name}\`**`);
- return await message.channel.send({
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.colour)
.setTitle(i18n('commands.survey.response.list.title'))
.setDescription(list.join('\n'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
}
}
diff --git a/src/commands/tag.js b/src/commands/tag.js
index 805375b..e356e22 100644
--- a/src/commands/tag.js
+++ b/src/commands/tag.js
@@ -1,132 +1,72 @@
const Command = require('../modules/commands/command');
const {
- Message, // eslint-disable-line no-unused-vars
+ Interaction, // eslint-disable-line no-unused-vars
MessageEmbed
} = 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, {
- aliases: [
- i18n('commands.tag.aliases.faq'),
- i18n('commands.tag.aliases.t'),
- i18n('commands.tag.aliases.tags')
- ],
- args: [
- {
- description: i18n('commands.tag.args.command.description'),
- example: i18n('commands.tag.args.tag.example'),
- name: i18n('commands.tag.args.tag.name'),
- required: false
- }
- ],
description: i18n('commands.tag.description'),
internal: true,
name: i18n('commands.tag.name'),
- process_args: false,
+ options: async guild => {
+ const settings = await client.utils.getSettings(guild.id);
+ return Object.keys(settings.tags).map(tag => ({
+ description: settings.tags[tag].substring(0, 100),
+ name: tag,
+ options: [...settings.tags[tag].matchAll(/(? ({
+ description: match[1],
+ name: match[1],
+ required: true,
+ type: Command.option_types.STRING
+ })),
+ required: false,
+ type: Command.option_types.SUB_COMMAND
+ }));
+ },
staff_only: true
});
}
/**
- * @param {Message} message
- * @param {string} args
+ * @param {Interaction} interaction
* @returns {Promise}
*/
- async execute(message, args) {
- const settings = await this.client.utils.getSettings(message.guild);
+ async execute(interaction) {
+ const settings = await this.client.utils.getSettings(interaction.guild.id);
const i18n = this.client.i18n.getLocale(settings.locale);
- const t_row = await this.client.db.models.Ticket.findOne({ where: { id: message.channel.id } });
+ const tag_name = interaction.options.getSubcommand();
+ const tag = settings.tags[tag_name];
+ const args = interaction.options.data[0]?.options;
- 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({
- embeds: [
- 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())
- ]
- });
- }
-
- const expected = placeholders
- .filter(p => p.startsWith(':'))
- .map(p => ({
- 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({
- embeds: [
- 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({
- embeds: [
- 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 = t_row.topic ? this.client.cryptr.decrypt(t_row.topic) : null;
- }
-
- // note that this regex is slightly different to the other
- const text = tag.replace(/(? this.client.i18n.resolve(args, $1));
- return await message.channel.send({
+ if (tag) {
+ const text = tag.replace(/(? {
+ const arg = args.find(arg => arg.name === $1);
+ return arg ? arg.value : $;
+ });
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.colour)
.setDescription(text)
- ]
+ ],
+ ephemeral: false
});
} else {
const list = Object.keys(settings.tags).map(t => `❯ **\`${t}\`**`);
- return await message.channel.send({
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.colour)
.setTitle(i18n('commands.tag.response.list.title'))
.setDescription(list.join('\n'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
}
diff --git a/src/commands/topic.js b/src/commands/topic.js
index 9da4d42..7fa6139 100644
--- a/src/commands/topic.js
+++ b/src/commands/topic.js
@@ -1,6 +1,6 @@
const Command = require('../modules/commands/command');
const {
- Message, // eslint-disable-line no-unused-vars
+ Interaction, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
@@ -8,55 +8,56 @@ module.exports = class TopicCommand extends Command {
constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale);
super(client, {
- aliases: [],
- args: [
- {
- description: i18n('commands.topic.args.new_topic.description'),
- example: i18n('commands.topic.args.new_topic.example'),
- name: i18n('commands.topic.args.new_topic.name'),
- required: true
- }
- ],
description: i18n('commands.topic.description'),
internal: true,
name: i18n('commands.topic.name'),
- process_args: false
+ options: [
+ {
+ description: i18n('commands.topic.options.new_topic.description'),
+ name: i18n('commands.topic.options.new_topic.name'),
+ required: true,
+ type: Command.option_types.STRING
+ }
+ ]
});
}
/**
- * @param {Message} message
- * @param {string} args
+ * @param {Interaction} interaction
* @returns {Promise}
*/
- async execute(message, args) {
- const settings = await this.client.utils.getSettings(message.guild);
+ async execute(interaction) {
+ const settings = await this.client.utils.getSettings(interaction.guild.id);
+ const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale
const i18n = this.client.i18n.getLocale(settings.locale);
- const t_row = await this.client.db.models.Ticket.findOne({ where: { id: message.channel.id } });
+ const topic = interaction.options.getString(default_i18n('commands.topic.options.new_topic.name'));
+
+ const t_row = await this.client.db.models.Ticket.findOne({ where: { id: interaction.channel.id } });
if (!t_row) {
- return await message.channel.send({
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('commands.topic.response.not_a_ticket.title'))
.setDescription(i18n('commands.topic.response.not_a_ticket.description'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
});
}
- await t_row.update({ topic: this.client.cryptr.encrypt(args) });
+ await t_row.update({ topic: this.client.cryptr.encrypt(topic) });
- const member = await message.guild.members.fetch(t_row.creator);
- /* await */message.channel.setTopic(`${member} | ${args}`, { reason: 'User updated ticket topic' });
+ const member = await interaction.guild.members.fetch(t_row.creator);
+ interaction.channel.setTopic(`${member} | ${topic}`, { reason: 'User updated ticket topic' });
const cat_row = await this.client.db.models.Category.findOne({ where: { id: t_row.category } });
const description = cat_row.opening_message
.replace(/{+\s?(user)?name\s?}+/gi, member.displayName)
.replace(/{+\s?(tag|ping|mention)?\s?}+/gi, member.user.toString());
- const opening_message = await message.channel.messages.fetch(t_row.opening_message);
+ const opening_message = await interaction.channel.messages.fetch(t_row.opening_message);
await opening_message.edit({
embeds: [
@@ -64,22 +65,23 @@ module.exports = class TopicCommand extends Command {
.setColor(settings.colour)
.setAuthor(member.user.username, member.user.displayAvatarURL())
.setDescription(description)
- .addField(i18n('ticket.opening_message.fields.topic'), args)
- .setFooter(settings.footer, message.guild.iconURL())
+ .addField(i18n('ticket.opening_message.fields.topic'), topic)
+ .setFooter(settings.footer, interaction.guild.iconURL())
]
});
- await message.channel.send({
+ await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.success_colour)
- .setAuthor(message.author.username, message.author.displayAvatarURL())
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
.setTitle(i18n('commands.topic.response.changed.title'))
.setDescription(i18n('commands.topic.response.changed.description'))
- .setFooter(settings.footer, message.guild.iconURL())
- ]
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: false
});
- this.client.log.info(`${message.author.tag} changed the topic of ${message.channel.id}`);
+ this.client.log.info(`${interaction.user.tag} changed the topic of #${interaction.channel.name}`);
}
};
diff --git a/src/database/index.js b/src/database/index.js
index 5784cf2..9df4cbd 100644
--- a/src/database/index.js
+++ b/src/database/index.js
@@ -39,7 +39,8 @@ module.exports = async client => {
logging: text => client.log.debug(text),
storage: path('./user/database.sqlite')
});
- client.log.warn('SQLite is not sufficient for a production environment if you want to use ticket archives. You should disable "log_messages" in your servers\' settings or use a different database.');
+ client.config.defaults.log_messages = false;
+ client.log.warn('Message logging is disabled due to insufficient database');
} else {
client.log.info(`Connecting to ${types[type].name} database...`);
sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASS, {
@@ -66,7 +67,7 @@ module.exports = async client => {
require(`./models/${model}`)(client, sequelize);
}
- await sequelize.sync({ alter: { drop: false } });
+ await sequelize.sync({ alter: true });
return sequelize;
};
\ No newline at end of file
diff --git a/src/database/models/category.model.js b/src/database/models/category.model.js
index c346133..d9887e8 100644
--- a/src/database/models/category.model.js
+++ b/src/database/models/category.model.js
@@ -86,5 +86,8 @@ module.exports = ({ config }, sequelize) => {
allowNull: true,
type: DataTypes.STRING
}
- }, { tableName: DB_TABLE_PREFIX + 'categories' });
+ }, {
+ paranoid: true,
+ tableName: DB_TABLE_PREFIX + 'categories'
+ });
};
\ No newline at end of file
diff --git a/src/database/models/guild.model.js b/src/database/models/guild.model.js
index 5e95f1d..bf8bfbf 100644
--- a/src/database/models/guild.model.js
+++ b/src/database/models/guild.model.js
@@ -3,7 +3,10 @@ module.exports = ({ config }, sequelize) => {
const { DB_TABLE_PREFIX } = process.env;
sequelize.define('Guild', {
blacklist: {
- defaultValue: [],
+ defaultValue: {
+ members: [],
+ roles: []
+ },
get() {
const raw_value = this.getDataValue('blacklist');
return raw_value
@@ -14,14 +17,14 @@ module.exports = ({ config }, sequelize) => {
},
type: DataTypes.JSON
},
+ close_button: {
+ defaultValue: false,
+ type: DataTypes.BOOLEAN
+ },
colour: {
defaultValue: config.defaults.colour,
type: DataTypes.STRING
},
- command_prefix: {
- defaultValue: config.defaults.command_prefix,
- type: DataTypes.STRING
- },
error_colour: {
defaultValue: 'RED',
type: DataTypes.STRING
diff --git a/src/database/models/panel.model.js b/src/database/models/panel.model.js
index aba5b3c..ea36aef 100644
--- a/src/database/models/panel.model.js
+++ b/src/database/models/panel.model.js
@@ -2,17 +2,9 @@ const { DataTypes } = require('sequelize');
module.exports = (client, sequelize) => {
const { DB_TABLE_PREFIX } = process.env;
sequelize.define('Panel', {
- categories: {
- allowNull: false,
- get() {
- const raw_value = this.getDataValue('categories');
- return raw_value
- ? typeof raw_value === 'string'
- ? JSON.parse(raw_value)
- : raw_value
- : null;
- },
- type: DataTypes.JSON
+ category: {
+ allowNull: true,
+ type: DataTypes.CHAR(19)
},
channel: {
allowNull: false,
@@ -25,10 +17,6 @@ module.exports = (client, sequelize) => {
model: DB_TABLE_PREFIX + 'guilds'
},
type: DataTypes.CHAR(19)
- },
- message: {
- allowNull: false,
- type: DataTypes.CHAR(19)
}
}, { tableName: DB_TABLE_PREFIX + 'panels' });
};
\ No newline at end of file
diff --git a/src/listeners/debug.js b/src/listeners/debug.js
index cf54593..ad62c68 100644
--- a/src/listeners/debug.js
+++ b/src/listeners/debug.js
@@ -6,7 +6,7 @@ module.exports = class DebugEventListener extends EventListener {
}
async execute(data) {
- if (this.client.config.debug) {
+ if (this.client.config.developer.debug) {
this.client.log.debug(data);
}
}
diff --git a/src/listeners/guildCreate.js b/src/listeners/guildCreate.js
index 355ec00..0d01ae5 100644
--- a/src/listeners/guildCreate.js
+++ b/src/listeners/guildCreate.js
@@ -7,5 +7,6 @@ module.exports = class GuildCreateEventListener extends EventListener {
async execute(guild) {
this.client.log.info(`Added to "${guild.name}"`);
+ this.client.commands.publish(guild);
}
};
\ No newline at end of file
diff --git a/src/listeners/guildDelete.js b/src/listeners/guildDelete.js
index 6d06581..a4b55cd 100644
--- a/src/listeners/guildDelete.js
+++ b/src/listeners/guildDelete.js
@@ -7,6 +7,5 @@ module.exports = class GuildDeleteEventListener extends EventListener {
async execute(guild) {
this.client.log.info(`Removed from "${guild.name}"`);
- await guild.deleteSettings();
}
};
\ No newline at end of file
diff --git a/src/listeners/interactionCreate.js b/src/listeners/interactionCreate.js
new file mode 100644
index 0000000..c1449c3
--- /dev/null
+++ b/src/listeners/interactionCreate.js
@@ -0,0 +1,323 @@
+const EventListener = require('../modules/listeners/listener');
+const {
+ Interaction, // eslint-disable-line no-unused-vars
+ MessageActionRow,
+ MessageButton,
+ MessageEmbed
+} = require('discord.js');
+
+module.exports = class InteractionCreateEventListener extends EventListener {
+ constructor(client) {
+ super(client, { event: 'interactionCreate' });
+ }
+
+ /**
+ * @param {Interaction} interaction
+ */
+ async execute(interaction) {
+ this.client.log.debug(interaction);
+
+ const settings = await this.client.utils.getSettings(interaction.guild.id);
+ const i18n = this.client.i18n.getLocale(settings.locale);
+
+ const blacklisted = settings.blacklist.members.includes[interaction.user.id] ||
+ interaction.member?.roles.cache?.some(role => settings.blacklist.roles.includes(role));
+ if (blacklisted) {
+ return interaction.reply({
+ content: i18n('blacklisted'),
+ ephemeral: true
+ });
+ }
+
+ const handlePanel = async id => {
+ const cat_row = await this.client.db.models.Category.findOne({ where: { id } });
+
+ if (!cat_row) {
+ this.client.log.warn('Could not find a category with the ID given by a panel interaction');
+ return interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setTitle(i18n('command_execution_error.title'))
+ .setDescription(i18n('command_execution_error.description'))
+ ],
+ ephemeral: true
+ });
+ }
+
+ const tickets = await this.client.db.models.Ticket.findAndCountAll({
+ where: {
+ category: cat_row.id,
+ creator: interaction.user.id,
+ open: true
+ }
+ });
+
+ if (tickets.count >= cat_row.max_per_member) {
+ if (cat_row.max_per_member === 1) {
+ return interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
+ .setTitle(i18n('commands.new.response.has_a_ticket.title'))
+ .setDescription(i18n('commands.new.response.has_a_ticket.description', tickets.rows[0].id))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
+ } else {
+ const list = tickets.rows.map(row => {
+ if (row.topic) {
+ const description = row.topic.substring(0, 30);
+ const ellipses = row.topic.length > 30 ? '...' : '';
+ return `<#${row.id}>: \`${description}${ellipses}\``;
+ } else {
+ return `<#${row.id}>`;
+ }
+ });
+ return interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
+ .setTitle(i18n('commands.new.response.max_tickets.title', tickets.count))
+ .setDescription(i18n('commands.new.response.max_tickets.description', list.join('\n')))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
+ }
+ } else {
+ try {
+ const t_row = await this.client.tickets.create(interaction.guild.id, interaction.user.id, id);
+ return interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.success_colour)
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
+ .setTitle(i18n('commands.new.response.created.title'))
+ .setDescription(i18n('commands.new.response.created.description', `<#${t_row.id}>`))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
+ } catch (error) {
+ this.client.log.error(error);
+ return interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
+ .setTitle(i18n('commands.new.response.error.title'))
+ .setDescription(error.message)
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
+ }
+ }
+ };
+
+ if (interaction.isCommand()) {
+ // handle slash commands
+ this.client.commands.handle(interaction);
+ } else if (interaction.isButton()) {
+ if (interaction.customId.startsWith('panel.single')) {
+ // handle single-category panels
+ handlePanel(interaction.customId.split(':')[1]);
+ } else if (interaction.customId.startsWith('ticket.claim')) {
+ // handle ticket claiming
+ if (!(await this.client.utils.isStaff(interaction.member))) return;
+ const t_row = await this.client.db.models.Ticket.findOne({ where: { id: interaction.channel.id } });
+ await t_row.update({ claimed_by: interaction.user.id });
+ await interaction.channel.permissionOverwrites.edit(interaction.user.id, { VIEW_CHANNEL: true }, `Ticket claimed by ${interaction.user.tag}`);
+
+ const cat_row = await this.client.db.models.Category.findOne({ where: { id: t_row.category } });
+
+ for (const role of cat_row.roles) {
+ await interaction.channel.permissionOverwrites.edit(role, { VIEW_CHANNEL: false }, `Ticket claimed by ${interaction.user.tag}`);
+ }
+
+ this.client.log.info(`${interaction.user.tag} has claimed "${interaction.channel.name}" in "${interaction.guild.name}"`);
+
+ await interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.colour)
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
+ .setTitle(i18n('ticket.claimed.title'))
+ .setDescription(i18n('ticket.claimed.description', interaction.member.toString()))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ]
+ });
+
+ const components = new MessageActionRow();
+
+ if (cat_row.claiming) {
+ components.addComponents(
+ new MessageButton()
+ .setCustomId('ticket.unclaim')
+ .setLabel(i18n('ticket.unclaim'))
+ .setEmoji('♻️')
+ .setStyle('SECONDARY')
+ );
+ }
+
+ if (settings.close_button) {
+ components.addComponents(
+ new MessageButton()
+ .setCustomId('ticket.close')
+ .setLabel(i18n('ticket.close'))
+ .setEmoji('✖️')
+ .setStyle('DANGER')
+ );
+ }
+
+ await interaction.message.edit({ components: [components] });
+ } else if (interaction.customId.startsWith('ticket.unclaim')) {
+ // handle ticket unclaiming
+ if (!(await this.client.utils.isStaff(interaction.member))) return;
+ const t_row = await this.client.db.models.Ticket.findOne({ where: { id: interaction.channel.id } });
+ await t_row.update({ claimed_by: null });
+
+ await interaction.channel.permissionOverwrites.delete(interaction.user.id, `Ticket released by ${interaction.user.tag}`);
+
+ const cat_row = await this.client.db.models.Category.findOne({ where: { id: t_row.category } });
+
+ for (const role of cat_row.roles) {
+ await interaction.channel.permissionOverwrites.edit(role, { VIEW_CHANNEL: true }, `Ticket released by ${interaction.user.tag}`);
+ }
+
+ this.client.log.info(`${interaction.user.tag} has released "${interaction.channel.name}" in "${interaction.guild.name}"`);
+
+ await interaction.reply({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.colour)
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
+ .setTitle(i18n('ticket.released.title'))
+ .setDescription(i18n('ticket.released.description', interaction.member.toString()))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ]
+ });
+
+ const components = new MessageActionRow();
+
+ if (cat_row.claiming) {
+ components.addComponents(
+ new MessageButton()
+ .setCustomId('ticket.claim')
+ .setLabel(i18n('ticket.claim'))
+ .setEmoji('🙌')
+ .setStyle('SECONDARY')
+ );
+ }
+
+ if (settings.close_button) {
+ components.addComponents(
+ new MessageButton()
+ .setCustomId('ticket.close')
+ .setLabel(i18n('ticket.close'))
+ .setEmoji('✖️')
+ .setStyle('DANGER')
+ );
+ }
+
+ await interaction.message.edit({ components: [components] });
+ } else if (interaction.customId.startsWith('ticket.close')) {
+ // handle ticket close button
+ const t_row = await this.client.db.models.Ticket.findOne({ where: { id: interaction.channel.id } });
+ await interaction.reply({
+ components: [
+ new MessageActionRow()
+ .addComponents(
+ new MessageButton()
+ .setCustomId(`confirm_close:${interaction.id}`)
+ .setLabel(i18n('commands.close.response.confirm.buttons.confirm'))
+ .setEmoji('✅')
+ .setStyle('SUCCESS')
+ )
+ .addComponents(
+ new MessageButton()
+ .setCustomId(`cancel_close:${interaction.id}`)
+ .setLabel(i18n('commands.close.response.confirm.buttons.cancel'))
+ .setEmoji('❌')
+ .setStyle('SECONDARY')
+ )
+ ],
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.colour)
+ .setTitle(i18n('commands.close.response.confirm.title'))
+ .setDescription(settings.log_messages ? i18n('commands.close.response.confirm.description_with_archive') : i18n('commands.close.response.confirm.description'))
+ .setFooter(this.client.utils.footer(settings.footer, i18n('collector_expires_in', 30)), interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
+
+
+ const filter = i => i.user.id === interaction.user.id && i.customId.includes(interaction.id);
+ const collector = interaction.channel.createMessageComponentCollector({
+ filter,
+ time: 30000
+ });
+
+ collector.on('collect', async i => {
+ await i.deferUpdate();
+
+ if (i.customId === `confirm_close:${interaction.id}`) {
+ await this.client.tickets.close(t_row.id, interaction.user.id, interaction.guild.id);
+ await i.editReply({
+ components: [],
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.success_colour)
+ .setTitle(i18n('commands.close.response.closed.title', t_row.number))
+ .setDescription(i18n('commands.close.response.closed.description', t_row.number))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
+ } else {
+ await i.editReply({
+ components: [],
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setTitle(i18n('commands.close.response.canceled.title'))
+ .setDescription(i18n('commands.close.response.canceled.description'))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
+ }
+
+ collector.stop();
+ });
+
+ collector.on('end', async collected => {
+ if (collected.size === 0) {
+ await interaction.editReply({
+ components: [],
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.error_colour)
+ .setAuthor(interaction.user.username, interaction.user.displayAvatarURL())
+ .setTitle(i18n('commands.close.response.confirmation_timeout.title'))
+ .setDescription(i18n('commands.close.response.confirmation_timeout.description'))
+ .setFooter(settings.footer, interaction.guild.iconURL())
+ ],
+ ephemeral: true
+ });
+ }
+ });
+ }
+ } else if (interaction.isSelectMenu()) {
+ if (interaction.customId.startsWith('panel.multiple')) {
+ // handle multi-category panels and new command
+ handlePanel(interaction.values[0]);
+ }
+ }
+ }
+};
\ No newline at end of file
diff --git a/src/listeners/messageCreate.js b/src/listeners/messageCreate.js
index f1de5d6..fbab7a4 100644
--- a/src/listeners/messageCreate.js
+++ b/src/listeners/messageCreate.js
@@ -1,6 +1,9 @@
const EventListener = require('../modules/listeners/listener');
-
-const { MessageEmbed } = require('discord.js');
+const fetch = require('node-fetch');
+const {
+ MessageAttachment,
+ MessageEmbed
+} = require('discord.js');
module.exports = class MessageCreateEventListener extends EventListener {
constructor(client) {
@@ -10,30 +13,107 @@ module.exports = class MessageCreateEventListener extends EventListener {
async execute(message) {
if (!message.guild) return;
- const settings = await this.client.utils.getSettings(message.guild);
+ const settings = await this.client.utils.getSettings(message.guild.id);
const i18n = this.client.i18n.getLocale(settings.locale);
const t_row = await this.client.db.models.Ticket.findOne({ where: { id: message.channel.id } });
if (t_row) {
- if (settings.log_messages && !message.system) this.client.tickets.archives.addMessage(message); // add the message to the archives (if it is in a ticket channel)
+ const should_log_message = process.env.DB_TYPE.toLowerCase() !== 'sqlite' && settings.log_messages && !message.system;
+ if (should_log_message) this.client.tickets.archives.addMessage(message); // add the message to the archives (if it is in a ticket channel)
const ignore = [this.client.user.id, t_row.creator];
if (!t_row.first_response && !ignore.includes(message.author.id)) t_row.first_response = new Date();
t_row.last_message = new Date();
await t_row.save();
+ } else if (message.content.startsWith('tickets/')) {
+ if (!message.member.permissions.has('MANAGE_GUILD')) return;
+
+ const match = message.content.toLowerCase().match(/tickets\/(\w+)/i);
+
+ if (!match) return;
+
+ switch (match[1]) {
+ case 'surveys': {
+ const attachments = [...message.attachments.values()];
+ if (attachments.length >= 1) {
+ this.client.log.info(`Downloading surveys for "${message.guild.name}"`);
+ const data = await (await fetch(attachments[0].url)).json();
+ for (const survey in data) {
+ const survey_data = {
+ guild: message.guild.id,
+ name: survey
+ };
+ const [s_row] = await this.client.db.models.Survey.findOrCreate({
+ defaults: survey_data,
+ where: survey_data
+ });
+ s_row.questions = data[survey];
+ await s_row.save();
+ }
+ this.client.log.success(`Updated surveys for "${message.guild.name}"`);
+ message.channel.send({ content: i18n('commands.settings.response.settings_updated') });
+ } else {
+ const surveys = await this.client.db.models.Survey.findAll({ where: { guild: message.guild.id } });
+ const data = {};
+
+ for (const survey in surveys) {
+ const {
+ name, questions
+ } = surveys[survey];
+ data[name] = questions;
+ }
+
+ const attachment = new MessageAttachment(
+ Buffer.from(JSON.stringify(data, null, 2)),
+ 'surveys.json'
+ );
+ message.channel.send({ files: [attachment] });
+ }
+ break;
+ }
+ case 'tags': {
+ const attachments = [...message.attachments.values()];
+ if (attachments.length >= 1) {
+ this.client.log.info(`Downloading tags for "${message.guild.name}"`);
+ const data = await (await fetch(attachments[0].url)).json();
+ settings.tags = data;
+ await settings.save();
+ this.client.log.success(`Updated tags for "${message.guild.name}"`);
+ this.client.commands.publish(message.guild);
+ message.channel.send({ content: i18n('commands.settings.response.settings_updated') });
+ } else {
+ const list = Object.keys(settings.tags).map(t => `❯ **\`${t}\`**`);
+ const attachment = new MessageAttachment(
+ Buffer.from(JSON.stringify(settings.tags, null, 2)),
+ 'tags.json'
+ );
+ return await message.channel.send({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.colour)
+ .setTitle(i18n('commands.tag.response.list.title'))
+ .setDescription(list.join('\n'))
+ .setFooter(settings.footer, message.guild.iconURL())
+ ],
+ files: [attachment]
+ });
+ }
+ break;
+ }
+ }
} else {
if (message.author.bot) return;
const p_row = await this.client.db.models.Panel.findOne({ where: { channel: message.channel.id } });
- if (p_row && typeof p_row.categories === 'string') {
- // handle reaction-less panel
+ if (p_row) {
+ // handle message panels
await message.delete();
- const cat_row = await this.client.db.models.Category.findOne({ where: { id: p_row.categories } });
+ const cat_row = await this.client.db.models.Category.findOne({ where: { id: p_row.category } });
const tickets = await this.client.db.models.Ticket.findAndCountAll({
where: {
@@ -72,7 +152,7 @@ module.exports = class MessageCreateEventListener extends EventListener {
.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')))
+ .setDescription(i18n('commands.new.response.max_tickets.description', list.join('\n')))
.setFooter(this.client.utils.footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.author.iconURL());
try {
response = await message.author.send({ embeds: [embed] });
@@ -105,8 +185,6 @@ module.exports = class MessageCreateEventListener extends EventListener {
}
}
}
-
- this.client.commands.handle(message); // pass the message to the command handler
}
};
diff --git a/src/listeners/messageDelete.js b/src/listeners/messageDelete.js
index d817a59..540a2e9 100644
--- a/src/listeners/messageDelete.js
+++ b/src/listeners/messageDelete.js
@@ -8,7 +8,7 @@ module.exports = class MessageDeleteEventListener extends EventListener {
async execute(message) {
if (!message.guild) return;
- const settings = await this.client.utils.getSettings(message.guild);
+ const settings = await this.client.utils.getSettings(message.guild.id);
if (settings.log_messages && !message.system) this.client.tickets.archives.deleteMessage(message); // mark the message as deleted in the database (if it exists)
}
diff --git a/src/listeners/messageReactionAdd.js b/src/listeners/messageReactionAdd.js
deleted file mode 100644
index 0ae9fda..0000000
--- a/src/listeners/messageReactionAdd.js
+++ /dev/null
@@ -1,164 +0,0 @@
-const EventListener = require('../modules/listeners/listener');
-
-const { MessageEmbed } = require('discord.js');
-
-module.exports = class MessageReactionAddEventListener extends EventListener {
- constructor(client) {
- super(client, { event: 'messageReactionAdd' });
- }
-
- async execute(reaction, user) {
-
- if (reaction.partial) {
- try {
- await reaction.fetch();
- } catch (error) {
- return this.client.log.error(error);
- }
- }
-
- if (user.partial) {
- try {
- await user.fetch();
- } catch (error) {
- return this.client.log.error(error);
- }
- }
-
- if (user.id === this.client.user.id) return;
-
- const guild = reaction.message.guild;
- if (!guild) return;
-
- const settings = await this.client.utils.getSettings(guild);
- const i18n = this.client.i18n.getLocale(settings.locale);
-
- const channel = reaction.message.channel;
- const member = await guild.members.fetch(user.id);
-
- if (settings.blacklist.includes(user.id)) {
- return this.client.log.info(`Ignoring blacklisted member ${user.tag}`);
- } else {
- settings.blacklist.forEach(element => {
- if (guild.roles.cache.has(element) && member.roles.cache.has(element)) {
- return this.client.log.info(`Ignoring member ${user.tag} with blacklisted role`);
- }
- });
- }
-
- const t_row = await this.client.db.models.Ticket.findOne({ where: { id: channel.id } });
-
- if (t_row && t_row.opening_message === reaction.message.id) {
- if (reaction.emoji.name === '🙌' && await this.client.utils.isStaff(member)) {
- // ticket claiming
-
- await t_row.update({ claimed_by: member.user.id });
-
- await channel.permissionOverwrites.edit(member.user.id, { VIEW_CHANNEL: true }, `Ticket claimed by ${member.user.tag}`);
-
- const cat_row = await this.client.db.models.Category.findOne({ where: { id: t_row.category } });
-
- for (const role of cat_row.roles) {
- await channel.permissionOverwrites.edit(role, { VIEW_CHANNEL: false }, `Ticket claimed by ${member.user.tag}`);
- }
-
- this.client.log.info(`${member.user.tag} has claimed "${channel.name}" in "${guild.name}"`);
-
- await channel.send({
- embeds: [
- new MessageEmbed()
- .setColor(settings.colour)
- .setAuthor(member.user.username, member.user.displayAvatarURL())
- .setTitle(i18n('ticket.claimed.title'))
- .setDescription(i18n('ticket.claimed.description', member.toString()))
- .setFooter(settings.footer, guild.iconURL())
- ]
- });
- } else {
- await reaction.users.remove(user.id);
- }
- } else {
- const p_row = await this.client.db.models.Panel.findOne({ where: { message: reaction.message.id } });
-
- if (p_row && typeof p_row.categories !== 'string') {
- // panels
- await reaction.users.remove(user.id);
-
- const category_id = p_row.categories[reaction.emoji.name];
- if (!category_id) return;
-
- const cat_row = await this.client.db.models.Category.findOne({ where: { id: category_id } });
-
- const tickets = await this.client.db.models.Ticket.findAndCountAll({
- where: {
- category: cat_row.id,
- creator: user.id,
- open: true
- }
- });
-
- let response;
-
- if (tickets.count >= cat_row.max_per_member) {
- if (cat_row.max_per_member === 1) {
- const embed = new MessageEmbed()
- .setColor(settings.error_colour)
- .setAuthor(user.username, user.displayAvatarURL())
- .setTitle(i18n('commands.new.response.has_a_ticket.title'))
- .setDescription(i18n('commands.new.response.has_a_ticket.description', tickets.rows[0].id))
- .setFooter(this.client.utils.footer(settings.footer, i18n('message_will_be_deleted_in', 15)), guild.iconURL());
- try {
- response = await user.send({ embeds: [embed] });
- } catch {
- response = await channel.send({ embeds: [embed] });
- }
- } else {
- const list = tickets.rows.map(row => {
- if (row.topic) {
- const description = row.topic.substring(0, 30);
- const ellipses = row.topic.length > 30 ? '...' : '';
- return `<#${row.id}>: \`${description}${ellipses}\``;
- } else {
- return `<#${row.id}>`;
- }
- });
- const embed = new MessageEmbed()
- .setColor(settings.error_colour)
- .setAuthor(user.username, user.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(this.client.utils.footer(settings.footer, i18n('message_will_be_deleted_in', 15)), user.iconURL());
- try {
- response = await user.send({ embeds: [embed] });
- } catch {
- response = await channel.send({ embeds: [embed] });
- }
- }
- } else {
- try {
- await this.client.tickets.create(guild.id, user.id, cat_row.id);
- } catch (error) {
- const embed = new MessageEmbed()
- .setColor(settings.error_colour)
- .setAuthor(user.username, user.displayAvatarURL())
- .setTitle(i18n('commands.new.response.error.title'))
- .setDescription(error.message)
- .setFooter(this.client.utils.footer(settings.footer, i18n('message_will_be_deleted_in', 15)), guild.iconURL());
- try {
- response = await user.send({ embeds: [embed] });
- } catch {
- response = await channel.send({ embeds: [embed] });
- }
- }
- }
-
- if (response) {
- setTimeout(async () => {
- await response.delete();
- }, 15000);
- }
- }
- }
-
- }
-};
diff --git a/src/listeners/messageReactionRemove.js b/src/listeners/messageReactionRemove.js
deleted file mode 100644
index 6d72dbe..0000000
--- a/src/listeners/messageReactionRemove.js
+++ /dev/null
@@ -1,72 +0,0 @@
-const EventListener = require('../modules/listeners/listener');
-
-const { MessageEmbed } = require('discord.js');
-
-module.exports = class MessageReactionRemoveEventListener extends EventListener {
- constructor(client) {
- super(client, { event: 'messageReactionRemove' });
- }
-
- async execute(reaction, user) {
- // release (unclaim) ticket
- if (reaction.partial) {
- try {
- await reaction.fetch();
- } catch (error) {
- return this.client.log.error(error);
- }
- }
-
- if (user.partial) {
- try {
- await user.fetch();
- } catch (error) {
- return this.client.log.error(error);
- }
- }
-
- if (user.id === this.client.user.id) return;
-
- const guild = reaction.message.guild;
- if (!guild) return;
-
- const settings = await this.client.utils.getSettings(guild);
- const i18n = this.client.i18n.getLocale(settings.locale);
-
- const channel = reaction.message.channel;
- const member = await guild.members.fetch(user.id);
-
- const t_row = await this.client.db.models.Ticket.findOne({ where: { id: channel.id } });
-
- if (t_row && t_row.opening_message === reaction.message.id) {
- if (reaction.emoji.name === '🙌' && await this.client.utils.isStaff(member)) {
- // ticket claiming
-
- await t_row.update({ claimed_by: null });
-
- await channel.permissionOverwrites
- .get(member.user.id)
- ?.delete(`Ticket released by ${member.user.tag}`);
-
- const cat_row = await this.client.db.models.Category.findOne({ where: { id: t_row.category } });
-
- for (const role of cat_row.roles) {
- await channel.permissionOverwrites.edit(role, { VIEW_CHANNEL: true }, `Ticket released by ${member.user.tag}`);
- }
-
- this.client.log.info(`${member.user.tag} has released "${channel.name}" in "${guild.name}"`);
-
- await channel.send({
- embeds: [
- new MessageEmbed()
- .setColor(settings.colour)
- .setAuthor(member.user.username, member.user.displayAvatarURL())
- .setTitle(i18n('ticket.released.title'))
- .setDescription(i18n('ticket.released.description', member.toString()))
- .setFooter(settings.footer, guild.iconURL())
- ]
- });
- }
- }
- }
-};
diff --git a/src/listeners/messageUpdate.js b/src/listeners/messageUpdate.js
index 43481a1..9f06053 100644
--- a/src/listeners/messageUpdate.js
+++ b/src/listeners/messageUpdate.js
@@ -16,7 +16,7 @@ module.exports = class MessageUpdateEventListener extends EventListener {
if (!newm.guild) return;
- const settings = await this.client.utils.getSettings(newm.guild);
+ const settings = await this.client.utils.getSettings(newm.guild.id);
if (settings.log_messages && !newm.system) this.client.tickets.archives.updateMessage(newm); // update the message in the database
}
diff --git a/src/listeners/ready.js b/src/listeners/ready.js
index 3230b54..dc01439 100644
--- a/src/listeners/ready.js
+++ b/src/listeners/ready.js
@@ -11,9 +11,10 @@ module.exports = class ReadyEventListener extends EventListener {
async execute() {
this.client.log.success(`Connected to Discord as "${this.client.user.tag}"`);
+ this.client.log.info('Loading commands');
this.client.commands.load(); // load internal commands
-
this.client.plugins.plugins.forEach(p => p.load()); // call load function for each plugin
+ this.client.commands.publish(); // send commands to discord
if (this.client.config.presence.presences.length > 1) {
const { selectPresence } = require('../utils/discord');
diff --git a/src/locales/en-GB.json b/src/locales/en-GB.json
index 59b4a7e..3fdbf12 100644
--- a/src/locales/en-GB.json
+++ b/src/locales/en-GB.json
@@ -1,488 +1,600 @@
{
- "bot": {
- "missing_permissions": {
- "description": "Discord Tickets requires the following permissions:\n%s",
- "title": "⚠️"
- },
- "version": "[Discord Tickets](%s) v%s by [eartharoid](%s)"
- },
- "cmd_usage": {
- "args": {
- "description": "**Description:** %s",
- "example": "**Example:** `%s`"
- },
- "description": "**Usage:**\n`%s`\n\n**Example:**\n`%s`\n\nRequired arguments are prefixed with `❗`.",
- "invalid_named_args": {
- "description": "There is an error in your command syntax: `%s`.\nType `%s` for an example.\nPlease ask a member of staff if you are unsure.",
- "title": "❌ Invalid syntax"
- },
- "named_args": "This command uses named arguments.\n\n",
- "title": "`%s` command usage"
- },
- "collector_expires_in": "Expires in %d seconds",
- "commands": {
- "add": {
- "args": {
- "member": {
- "description": "The member to add to the ticket",
- "example": "@someone",
- "name": "member"
- },
- "ticket": {
- "description": "The ticket to add the member to",
- "example": "217",
- "name": "ticket"
- }
- },
- "description": "Add a member to a ticket",
- "name": "add",
- "response": {
- "added": {
- "description": "%s has been added to %s.",
- "title": "✅ Member added"
- },
- "no_member": {
- "description": "Please mention the member you want to add.",
- "title": "❌ Unknown member"
- },
- "no_permission": {
- "description": "You are not the creator of this ticket and you are not a staff member; you can't add members to this ticket.",
- "title": "❌ Insufficient permission"
- },
- "not_a_ticket": {
- "description": "Please use this command in the ticket channel, or mention the channel.",
- "title": "❌ This isn't a ticket channel"
- }
- }
- },
- "blacklist": {
- "aliases": {
- "unblacklist": "unblacklist"
- },
- "args": {
- "member_or_role": {
- "description": "The member or role to add/remove",
- "example": "@NaughtyMember",
- "name": "memberOrRole"
- }
- },
- "description": "Blacklist/unblacklist a member from interacting with the bot",
- "name": "blacklist",
- "response": {
- "empty_list": {
- "description": "There are no members or roles blacklisted. Type `%sblacklist ` to add a member or role to the blacklist.",
- "title": "📃 Blacklisted members and roles"
- },
- "illegal_action": {
- "description": "%s is a staff member and cannot be blacklisted.",
- "title": "❌ You can't blacklist this member"
- },
- "list": {
- "title": "📃 Blacklisted members and roles"
- },
- "member_added": {
- "description": "<@%s> has been added to the blacklist. They will no longer be able to interact with the bot.",
- "title": "✅ Added member to blacklist"
- },
- "member_removed": {
- "description": "<@%s> has been removed from the blacklist. They can now use the bot again.",
- "title": "✅ Removed member from blacklist"
- },
- "role_added": {
- "description": "<@&%s> has been added to the blacklist. Members with this role will no longer be able to interact with the bot.",
- "title": "✅ Added role to blacklist"
- },
- "role_removed": {
- "description": "<@&%s> has been removed from the blacklist. Members with this role can now use the bot again.",
- "title": "✅ Removed role from blacklist"
- }
- }
- },
- "close": {
- "aliases": {
- "delete": "delete",
- "lock": "lock"
- },
- "args": {
- "reason": {
- "alias": "r",
- "description": "The reason for closing the ticket(s)",
- "example": "Resolved",
- "name": "reason"
- },
- "ticket": {
- "alias": "t",
- "description": "The ticket to close, either the number or the channel mention/ID",
- "example": "217",
- "name": "ticket"
- },
- "time": {
- "alias": "T",
- "description": "Close all tickets that have been inactive for the specified time",
- "example": "1w",
- "name": "time"
- }
- },
- "description": "Close a ticket channel",
- "name": "close",
- "response": {
- "closed": {
- "description": "Ticket #%s has been closed.",
- "title": "✅ Ticket closed"
- },
- "closed_multiple": {
- "description": [
- "%d ticket has been closed.",
- "%d tickets have been closed."
- ],
- "title": [
- "✅ Ticket closed",
- "✅ Tickets closed"
- ]
- },
- "confirm": {
- "description": "React with ✅ to close this ticket.",
- "description_with_archive": "You will be able to view an archived version of it after.\nReact with ✅ to close this ticket.",
- "title": "❔ Are you sure?"
- },
- "confirmation_timeout": {
- "description": "You took too long to confirm.",
- "title": "❌ Reaction time expired"
- },
- "confirm_multiple": {
- "description": [
- "React with ✅ to close %d ticket.",
- "React with ✅ to close %d tickets."
- ],
- "title": "❔ Are you sure?"
- },
- "invalid_time": {
- "description": "The time period provided could not be parsed.",
- "title": "❌ Invalid input"
- },
- "not_a_ticket": {
- "description": "Please use this command in a ticket channel or use the ticket flag.\nType `%shelp close` for more information.",
- "title": "❌ This isn't a ticket channel"
- },
- "no_tickets": {
- "description": "There are no tickets which have been inactive for this time period.",
- "title": "❌ No tickets to close"
- },
- "unresolvable": {
- "description": "`%s` could not be resolved to a ticket. Please provide the ticket ID/mention or number.",
- "title": "❌ Error"
- }
- }
- },
- "help": {
- "aliases": {
- "command": "command",
- "commands": "commands"
- },
- "args": {
- "command": {
- "description": "The command to display information about",
- "example": "new",
- "name": "command"
- }
- },
- "description": "List commands you have access to, or find out more about a command",
- "name": "help",
- "response": {
- "list": {
- "description": "The commands you have access to are listed below. For more information about a command, type `{prefix}help [command]`. To create a ticket, type `{prefix}new [topic]`.",
- "fields": {
- "commands": "Commands"
- },
- "title": "❔ Help"
- }
- }
- },
- "new": {
- "aliases": {
- "create": "create",
- "open": "open",
- "ticket": "ticket"
- },
- "args": {
- "topic": {
- "description": "The topic of the ticket",
- "example": "Problem with billing",
- "name": "topic"
- }
- },
- "description": "Create a new ticket",
- "name": "new",
- "response": {
- "created": {
- "description": "Your ticket has been created: %s.",
- "title": "✅ Ticket created"
- },
- "error": {
- "title": "❌ Error"
- },
- "has_a_ticket": {
- "description": "Please use your existing ticket (<#%s>) or close it before creating another.",
- "title": "❌ You already have an open ticket"
- },
- "max_tickets": {
- "description": "Please use `%sclose` to close any unneeded tickets.\n\n%s",
- "title": "❌ You already have %d open tickets"
- },
- "no_categories": {
- "description": "A server administrator must create at least one ticket category before a new ticket can be opened.",
- "title": "❌ Can't create ticket"
- },
- "select_category": {
- "description": "Select the category most relevant to your ticket's topic:\n\n%s",
- "title": "🔤 Please select the ticket category"
- },
- "select_category_timeout": {
- "description": "You took too long to select the ticket category.",
- "title": "❌ Reaction time expired"
- }
- },
- "request_topic": {
- "description": "Please briefly state what this ticket is about in a a few words.",
- "title": "Ticket topic"
- }
- },
- "panel": {
- "args": {
- "categories": {
- "alias": "c",
- "description": "A category ID",
- "example": "451745464954650634",
- "name": "categories"
- },
- "description": {
- "alias": "d",
- "description": "The description for the panel message",
- "example": "\"React to this message to open a ticket.\"",
- "name": "description"
- },
- "emoji": {
- "alias": "e",
- "description": "An emoji",
- "example": "🎫",
- "name": "emoji"
- },
- "title": {
- "alias": "t",
- "description": "The title for the panel message",
- "example": "\"Support tickets\"",
- "name": "title"
- }
- },
- "description": "Create a new ticket panel",
- "name": "panel",
- "response": {
- "invalid_category": {
- "description": "One or more of the specified category IDs is invalid.",
- "title": "❌ Invalid category"
- },
- "mismatch": {
- "description": "Please provide the name number of emojis and category IDs.",
- "title": "❌ Invalid input"
- }
- }
- },
- "remove": {
- "args": {
- "member": {
- "description": "The member to remove from the ticket",
- "example": "@someone",
- "name": "member"
- },
- "ticket": {
- "description": "The ticket to remove the member from",
- "example": "217",
- "name": "ticket"
- }
- },
- "description": "Remove a member from a ticket",
- "name": "remove",
- "response": {
- "removed": {
- "description": "%s has been removed from %s.",
- "title": "✅ Member removed"
- },
- "no_member": {
- "description": "Please mention the member you want to remove.",
- "title": "❌ Unknown member"
- },
- "no_permission": {
- "description": "You are not the creator of this ticket and you are not a staff member; you can't remove members from this ticket.",
- "title": "❌ Insufficient permission"
- },
- "not_a_ticket": {
- "description": "Please use this command in the ticket channel, or mention the channel.",
- "title": "❌ This isn't a ticket channel"
- }
- }
- },
- "settings": {
- "aliases": {
- "config": "config"
- },
- "description": "Configure Discord Tickets",
- "name": "settings",
- "response": {
- "invalid": "❌ Settings data is invalid; please refer to the documentation.\n%s",
- "updated": "✅ Settings have been updated."
- }
- },
- "stats": {
- "description": "Display ticket statistics",
- "fields": {
- "messages": "Messages",
- "response_time": {
- "minutes": "%s minutes",
- "title": "Avg. response time"
- },
- "tickets": "Tickets"
- },
- "name": "stats",
- "response": {
- "global": {
- "description": "Statistics about tickets across all guilds where this Discord TIckets instance is used.",
- "title": "📊 Global stats"
- },
- "guild": {
- "description": "Statistics about tickets within this guild. This data is cached for an hour.",
- "title": "📊 This server's stats"
- }
- }
- },
- "survey": {
- "aliases": {
- "surveys": "surveys"
- },
- "args": {
- "survey": {
- "description": "The name of the survey to view responses of",
- "example": "support",
- "name": "survey"
- }
- },
- "description": "View survey responses",
- "name": "survey",
- "response": {
- "list": {
- "title": "📃 Surveys"
- }
- }
- },
- "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": {
- "args": {
- "new_topic": {
- "description": "The new topic of the ticket",
- "example": "billing issue",
- "name": "new_topic"
- }
- },
- "description": "Change the topic of the ticket",
- "name": "topic",
- "response": {
- "changed": {
- "description": "This ticket's topic has been changed.",
- "title": "✅ Topic changed"
- },
- "not_a_ticket": {
- "description": "Please use this command in the ticket channel you want to change the topic of.",
- "title": "❌ This isn't a ticket channel"
- }
- }
- }
- },
- "command_execution_error": {
- "description": "An unexpected error occurred during command execution.\nPlease ask an administrator to check the console output / logs for details.",
- "title": "⚠️"
- },
- "message_will_be_deleted_in": "This message will be deleted in %d seconds",
- "missing_permissions": {
- "description": "You do not have the permissions required to use this command:\n%s",
- "title": "❌"
- },
- "staff_only": {
- "description": "You must be a member of staff to use this command.",
- "title": "❌"
- },
- "ticket": {
- "claimed": {
- "description": "%s has claimed this ticket.",
- "title": "✅ Ticket claimed"
- },
- "closed": {
- "description": "This ticket has been closed.\nThe channel will be deleted in 5 seconds.",
- "title": "✅ Ticket closed"
- },
- "closed_by_member": {
- "description": "This ticket has been closed by %s.\nThe channel will be deleted in 5 seconds.",
- "title": "✅ Ticket closed"
- },
- "closed_by_member_with_reason": {
- "description": "This ticket has been closed by %s: `%s`\nThe channel will be deleted in 5 seconds.",
- "title": "✅ Ticket closed"
- },
- "closed_with_reason": {
- "description": "This ticket has been closed: `%s`\nThe channel will be deleted in 5 seconds.",
- "title": "✅ Ticket closed"
- },
- "member_added": {
- "description": "%s has been added by %s",
- "title": "Member added"
- },
- "member_removed": {
- "description": "%s has been removed by %s",
- "title": "Member removed"
- },
- "opening_message": {
- "fields": {
- "topic": "Topic"
- }
- },
- "questions": "Please answer the following questions:\n\n%s",
- "released": {
- "description": "%s has released this ticket.",
- "title": "✅ Ticket released"
- },
- "survey": {
- "complete": {
- "description": "Thank you for your feedback.",
- "title": "✅ Thank you"
- },
- "start": {
- "description": "Hey, %s. Before this channel is deleted, would you mind completing a quick %d-question survey? React with ✅ to start, or ignore this message.",
- "title": "❔ Feedback"
- }
- }
- }
-}
+ "blacklisted": "❌ You are blacklisted",
+ "bot": {
+ "missing_permissions": {
+ "description": "Discord Tickets requires the following permissions:\n%s",
+ "title": "⚠️"
+ },
+ "version": "[Discord Tickets](%s) v%s by [eartharoid](%s)"
+ },
+ "collector_expires_in": "Expires in %d seconds",
+ "command_execution_error": {
+ "description": "An unexpected error occurred during command execution.\nPlease ask an administrator to check the console output / logs for details.",
+ "title": "⚠️"
+ },
+ "commands": {
+ "add": {
+ "description": "Add a member to a ticket",
+ "name": "add",
+ "options": {
+ "member": {
+ "description": "The member to add to the ticket",
+ "name": "member"
+ },
+ "ticket": {
+ "description": "The ticket to add the member to",
+ "name": "ticket"
+ }
+ },
+ "response": {
+ "added": {
+ "description": "%s has been added to %s.",
+ "title": "✅ Member added"
+ },
+ "no_member": {
+ "description": "Please mention the member you want to add.",
+ "title": "❌ Unknown member"
+ },
+ "no_permission": {
+ "description": "You are not the creator of this ticket and you are not a staff member; you can't add members to this ticket.",
+ "title": "❌ Insufficient permission"
+ },
+ "not_a_ticket": {
+ "description": "Please use this command in the ticket channel, or mention the channel.",
+ "title": "❌ This isn't a ticket channel"
+ }
+ }
+ },
+ "blacklist": {
+ "description": "View or modify the blacklist",
+ "name": "blacklist",
+ "options": {
+ "add": {
+ "description": "Add a member or role to the blacklist",
+ "name": "add",
+ "options": {
+ "member_or_role": {
+ "description": "The member or role to add to the blacklist",
+ "name": "member_or_role"
+ }
+ }
+ },
+ "remove": {
+ "description": "Remove a member or role from the blacklist",
+ "name": "remove",
+ "options": {
+ "member_or_role": {
+ "description": "The member or role to remove from the blacklist",
+ "name": "member_or_role"
+ }
+ }
+ },
+ "show": {
+ "description": "Show the members and roles in the blacklist",
+ "name": "show"
+ }
+ },
+ "response": {
+ "empty_list": {
+ "description": "There are no members or roles blacklisted. Type `/blacklist add` to add a member or role to the blacklist.",
+ "title": "📃 Blacklisted members and roles"
+ },
+ "illegal_action": {
+ "description": "%s is a staff member and cannot be blacklisted.",
+ "title": "❌ You can't blacklist this member"
+ },
+ "invalid": {
+ "description": "This member or role can not be removed from the blacklist as they are not blacklisted.",
+ "title": "❌ Error"
+ },
+ "list": {
+ "fields": {
+ "members": "Members",
+ "roles": "Roles"
+ },
+ "title": "📃 Blacklisted members and roles"
+ },
+ "member_added": {
+ "description": "<@%s> has been added to the blacklist. They will no longer be able to interact with the bot.",
+ "title": "✅ Added member to blacklist"
+ },
+ "member_removed": {
+ "description": "<@%s> has been removed from the blacklist. They can now use the bot again.",
+ "title": "✅ Removed member from blacklist"
+ },
+ "role_added": {
+ "description": "<@&%s> has been added to the blacklist. Members with this role will no longer be able to interact with the bot.",
+ "title": "✅ Added role to blacklist"
+ },
+ "role_removed": {
+ "description": "<@&%s> has been removed from the blacklist. Members with this role can now use the bot again.",
+ "title": "✅ Removed role from blacklist"
+ }
+ }
+ },
+ "close": {
+ "description": "Close a ticket channel",
+ "name": "close",
+ "options": {
+ "reason": {
+ "description": "The reason for closing the ticket(s)",
+ "name": "reason"
+ },
+ "ticket": {
+ "description": "The ticket to close, either the number or the channel ID",
+ "name": "ticket"
+ },
+ "time": {
+ "description": "Close all tickets that have been inactive for the specified time",
+ "name": "time"
+ }
+ },
+ "response": {
+ "canceled": {
+ "description": "You canceled the operation.",
+ "title": "🚫 Canceled"
+ },
+ "closed": {
+ "description": "Ticket #%s has been closed.",
+ "title": "✅ Ticket closed"
+ },
+ "closed_multiple": {
+ "description": [
+ "%d ticket has been closed.",
+ "%d tickets have been closed."
+ ],
+ "title": [
+ "✅ Ticket closed",
+ "✅ Tickets closed"
+ ]
+ },
+ "confirm": {
+ "buttons": {
+ "cancel": "Cancel",
+ "confirm": "Close"
+ },
+ "description": "Please confirm your decision.",
+ "description_with_archive": "The ticket will be archived for future reference.",
+ "title": "❔ Are you sure?"
+ },
+ "confirm_multiple": {
+ "buttons": {
+ "cancel": "Cancel",
+ "confirm": [
+ "Close %d ticket",
+ "Close %d tickets"
+ ]
+ },
+ "description": [
+ "You are about to close %d ticket.",
+ "You are about to close %d tickets."
+ ],
+ "title": "❔ Are you sure?"
+ },
+ "confirmation_timeout": {
+ "description": "You took too long to confirm.",
+ "title": "❌ Interaction time expired"
+ },
+ "invalid_time": {
+ "description": "The time period provided could not be parsed.",
+ "title": "❌ Invalid input"
+ },
+ "no_tickets": {
+ "description": "There are no tickets which have been inactive for this time period.",
+ "title": "❌ No tickets to close"
+ },
+ "not_a_ticket": {
+ "description": "Please use this command in a ticket channel or use the ticket flag.\nType `/help close` for more information.",
+ "title": "❌ This isn't a ticket channel"
+ },
+ "unresolvable": {
+ "description": "`%s` could not be resolved to a ticket. Please provide the ticket ID/mention or number.",
+ "title": "❌ Error"
+ }
+ }
+ },
+ "help": {
+ "description": "List the commands you have access to",
+ "name": "help",
+ "options": {},
+ "response": {
+ "list": {
+ "description": "The commands you have access to are listed below. To create a ticket, type **`/new`**.",
+ "fields": {
+ "commands": "Commands"
+ },
+ "title": "❔ Help"
+ }
+ }
+ },
+ "new": {
+ "description": "Create a new ticket",
+ "name": "new",
+ "options": {
+ "topic": {
+ "description": "The topic of the ticket",
+ "name": "topic"
+ }
+ },
+ "request_topic": {
+ "description": "Please briefly state what this ticket is about in a a few words.",
+ "title": "⚠️ Ticket topic"
+ },
+ "response": {
+ "created": {
+ "description": "Your ticket has been created: %s.",
+ "title": "✅ Ticket created"
+ },
+ "error": {
+ "title": "❌ Error"
+ },
+ "has_a_ticket": {
+ "description": "Please use your existing ticket (<#%s>) or close it before creating another.",
+ "title": "❌ You already have an open ticket"
+ },
+ "max_tickets": {
+ "description": "Please use `/close` to close any unneeded tickets.\n\n%s",
+ "title": "❌ You already have %d open tickets"
+ },
+ "no_categories": {
+ "description": "A server administrator must create at least one ticket category before a new ticket can be opened.",
+ "title": "❌ Can't create ticket"
+ },
+ "select_category": {
+ "description": "Select the category most relevant to your ticket's topic.",
+ "title": "🔤 Please select the ticket category"
+ },
+ "select_category_timeout": {
+ "description": "You took too long to select the ticket category.",
+ "title": "❌ Interaction time expired"
+ }
+ }
+ },
+ "panel": {
+ "description": "Create a new ticket panel",
+ "name": "panel",
+ "options": {
+ "categories": {
+ "description": "A comma-separated list of category IDs",
+ "name": "categories"
+ },
+ "description": {
+ "description": "The description for the panel message",
+ "name": "description"
+ },
+ "image": {
+ "description": "An image URL for the panel message",
+ "name": "image"
+ },
+ "just_type": {
+ "description": "Create a \"just type\" panel?",
+ "name": "just_type"
+ },
+ "title": {
+ "description": "The title for the panel message",
+ "name": "title"
+ },
+ "thumbnail": {
+ "description": "A thumbnail image URL for the panel message",
+ "name": "thumbnail"
+ }
+ },
+ "response": {
+ "invalid_category": {
+ "description": "One or more of the specified category IDs is invalid.",
+ "title": "❌ Invalid category"
+ },
+ "too_many_categories": {
+ "description": "The \"just type\" panel can only be used with a single category.",
+ "title": "❌ Too many categories"
+ }
+ }
+ },
+ "remove": {
+ "description": "Remove a member from a ticket",
+ "name": "remove",
+ "options": {
+ "member": {
+ "description": "The member to remove from the ticket",
+ "name": "member"
+ },
+ "ticket": {
+ "description": "The ticket to remove the member from",
+ "name": "ticket"
+ }
+ },
+ "response": {
+ "no_member": {
+ "description": "Please mention the member you want to remove.",
+ "title": "❌ Unknown member"
+ },
+ "no_permission": {
+ "description": "You are not the creator of this ticket and you are not a staff member; you can't remove members from this ticket.",
+ "title": "❌ Insufficient permission"
+ },
+ "not_a_ticket": {
+ "description": "Please use this command in the ticket channel, or mention the channel.",
+ "title": "❌ This isn't a ticket channel"
+ },
+ "removed": {
+ "description": "%s has been removed from %s.",
+ "title": "✅ Member removed"
+ }
+ }
+ },
+ "settings": {
+ "description": "Configure Discord Tickets",
+ "name": "settings",
+ "options": {
+ "categories": {
+ "description": "Manage your ticket categories",
+ "name": "categories",
+ "options": {
+ "create": {
+ "description": "Create a new category",
+ "name": "create",
+ "options": {
+ "name": {
+ "description": "The name of the category",
+ "name": "name"
+ },
+ "roles": {
+ "description": "A comma-separated list of staff role IDs for this category",
+ "name": "roles"
+ }
+ }
+ },
+ "delete": {
+ "description": "Delete a category",
+ "name": "delete",
+ "options": {
+ "id": {
+ "description": "The ID of the category to delete",
+ "name": "id"
+ }
+ }
+ },
+ "edit": {
+ "description": "Make changes to a category's configuration",
+ "name": "edit",
+ "options": {
+ "claiming": {
+ "description": "Enable ticket claiming?",
+ "name": "claiming"
+ },
+ "id": {
+ "description": "The ID of the category to edit",
+ "name": "id"
+ },
+ "image": {
+ "description": "An image URL",
+ "name": "image"
+ },
+ "max_per_member": {
+ "description": "The maximum number of tickets a member can have in this category",
+ "name": "max_per_member"
+ },
+ "name": {
+ "description": "The category name",
+ "name": "name"
+ },
+ "name_format": {
+ "description": "The ticket name format",
+ "name": "name_format"
+ },
+ "opening_message": {
+ "description": "The text to send when a ticket is opened",
+ "name": "opening_message"
+ },
+ "ping": {
+ "description": "A comma-separated list of role IDs to ping",
+ "name": "ping"
+ },
+ "require_topic": {
+ "description": "Require the user to give the topic of the ticket?",
+ "name": "require_topic"
+ },
+ "roles": {
+ "description": "A comma-separated list of staff role IDs",
+ "name": "roles"
+ },
+ "survey": {
+ "description": "The survey to use",
+ "name": "survey"
+ }
+ }
+ },
+ "list": {
+ "description": "List categories",
+ "name": "list"
+ }
+ }
+ },
+ "set": {
+ "description": "Set options",
+ "name": "set",
+ "options": {
+ "close_button": {
+ "description": "Enable closing with a button?",
+ "name": "close_button"
+ },
+ "colour": {
+ "description": "The standard colour",
+ "name": "colour"
+ },
+ "error_colour": {
+ "description": "The error colour",
+ "name": "error_colour"
+ },
+ "footer": {
+ "description": "The embed footer text",
+ "name": "footer"
+ },
+ "locale": {
+ "description": "The locale (language)",
+ "name": "locale"
+ },
+ "log_messages": {
+ "description": "Store messages from tickets?",
+ "name": "log_messages"
+ },
+ "success_colour": {
+ "description": "The success colour",
+ "name": "success_colour"
+ }
+ }
+ }
+ },
+ "response": {
+ "category_created": "✅ The `%s` ticket category has been created",
+ "category_deleted": "✅ The `%s` ticket category has been deleted",
+ "category_does_not_exist": "❌ No category exists with the provided ID",
+ "category_updated": "✅ The `%s` ticket category has been updated",
+ "category_list": "Ticket categories",
+ "settings_updated": "✅ Settings have been updated"
+ }
+ },
+ "stats": {
+ "description": "Display ticket statistics",
+ "fields": {
+ "messages": "Messages",
+ "response_time": {
+ "minutes": "%s minutes",
+ "title": "Avg. response time"
+ },
+ "tickets": "Tickets"
+ },
+ "name": "stats",
+ "options": {},
+ "response": {
+ "global": {
+ "description": "Statistics about tickets across all guilds where this Discord TIckets instance is used.",
+ "title": "📊 Global stats"
+ },
+ "guild": {
+ "description": "Statistics about tickets within this guild. This data is cached for an hour.",
+ "title": "📊 This server's stats"
+ }
+ }
+ },
+ "survey": {
+ "description": "View survey responses",
+ "name": "survey",
+ "options": {
+ "survey": {
+ "description": "The name of the survey to view responses of",
+ "name": "survey"
+ }
+ },
+ "response": {
+ "list": {
+ "title": "📃 Surveys"
+ }
+ }
+ },
+ "tag": {
+ "description": "Use a tag response",
+ "name": "tag",
+ "options": {
+ "tag": {
+ "description": "The name of the tag to use",
+ "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": {
+ "description": "Change the topic of the ticket",
+ "name": "topic",
+ "options": {
+ "new_topic": {
+ "description": "The new topic of the ticket",
+ "name": "new_topic"
+ }
+ },
+ "response": {
+ "changed": {
+ "description": "This ticket's topic has been changed.",
+ "title": "✅ Topic changed"
+ },
+ "not_a_ticket": {
+ "description": "Please use this command in the ticket channel you want to change the topic of.",
+ "title": "❌ This isn't a ticket channel"
+ }
+ }
+ }
+ },
+ "message_will_be_deleted_in": "This message will be deleted in %d seconds",
+ "missing_permissions": {
+ "description": "You do not have the permissions required to use this command:\n%s",
+ "title": "❌ Error"
+ },
+ "panel": {
+ "create_ticket": "Create a ticket"
+ },
+ "ticket": {
+ "claim": "Claim",
+ "claimed": {
+ "description": "%s has claimed this ticket.",
+ "title": "✅ Ticket claimed"
+ },
+ "close": "Close",
+ "closed": {
+ "description": "This ticket has been closed.\nThe channel will be deleted in 5 seconds.",
+ "title": "✅ Ticket closed"
+ },
+ "closed_by_member": {
+ "description": "This ticket has been closed by %s.\nThe channel will be deleted in 5 seconds.",
+ "title": "✅ Ticket closed"
+ },
+ "closed_by_member_with_reason": {
+ "description": "This ticket has been closed by %s: `%s`\nThe channel will be deleted in 5 seconds.",
+ "title": "✅ Ticket closed"
+ },
+ "closed_with_reason": {
+ "description": "This ticket has been closed: `%s`\nThe channel will be deleted in 5 seconds.",
+ "title": "✅ Ticket closed"
+ },
+ "member_added": {
+ "description": "%s has been added by %s",
+ "title": "Member added"
+ },
+ "member_removed": {
+ "description": "%s has been removed by %s",
+ "title": "Member removed"
+ },
+ "opening_message": {
+ "content": "%s\n%s has created a new ticket",
+ "fields": {
+ "topic": "Topic"
+ }
+ },
+ "questions": "Please answer the following questions:\n\n%s",
+ "released": {
+ "description": "%s has released this ticket.",
+ "title": "✅ Ticket released"
+ },
+ "survey": {
+ "complete": {
+ "description": "Thank you for your feedback.",
+ "title": "✅ Thank you"
+ },
+ "start": {
+ "description": "Hey, %s. Before this channel is deleted, would you mind completing a quick %d-question survey? React with ✅ to start, or ignore this message.",
+ "title": "❔ Feedback"
+ }
+ },
+ "unclaim": "Release"
+ },
+ "updated_permissions": "✅ Slash command permissions updated"
+}
\ No newline at end of file
diff --git a/src/logger.js b/src/logger.js
index 7768cd6..f6e8dfc 100644
--- a/src/logger.js
+++ b/src/logger.js
@@ -2,7 +2,7 @@ const { path } = require('./utils/fs');
const config = require('../user/config');
const Logger = require('leekslazylogger');
module.exports = new Logger({
- debug: config.debug,
+ debug: config.developer.debug,
directory: path('./logs/'),
keepFor: config.logs.keep_for,
levels: {
diff --git a/src/modules/commands/command.js b/src/modules/commands/command.js
index f4a0237..8a193a9 100644
--- a/src/modules/commands/command.js
+++ b/src/modules/commands/command.js
@@ -1,6 +1,6 @@
const {
Message, // eslint-disable-line no-unused-vars
- MessageEmbed
+ Interaction // eslint-disable-line no-unused-vars
} = require('discord.js');
/**
@@ -9,11 +9,13 @@ const {
module.exports = class Command {
/**
*
- * @typedef CommandArgument
- * @property {string} name - The argument's name
- * @property {string} description - The argument's description
- * @property {string} example - An example value
- * @property {boolean?} required - Is this arg required? Defaults to `false`
+ * @typedef CommandOption
+ * @property {string} name - The option's name
+ * @property {number} type - The option's type (use `Command.option_types`)
+ * @property {string} description - The option's description
+ * @property {CommandOption[]} [options] - The option's options
+ * @property {(string|number)[]} [choices] - The option's choices
+ * @property {boolean} [required] - Is this arg required? Defaults to `false`
*/
/**
* Create a new Command
@@ -23,8 +25,7 @@ module.exports = class Command {
* @param {string} data.description - The description of the command (1-100)
* @param {boolean} [data.staff_only] - Only allow staff to use this command?
* @param {string[]} [data.permissions] - Array of permissions needed for a user to use this command
- * @param {boolean} [data.process_args] - Should the command handler process named arguments?
- * @param {CommandArgument[]} [data.args] - The command's arguments (see [docs](https://github.com/75lb/command-line-args/blob/master/doc/option-definition.md) if using processed args)
+ * @param {CommandOption[]} [data.options] - The command's options
*/
constructor(client, data) {
@@ -44,14 +45,6 @@ module.exports = class Command {
*/
this.name = data.name;
- /**
- * The command's aliases
- * @type {string[]}
- */
- this.aliases = data.aliases ?? [];
-
- if (!this.aliases.includes(this.name)) this.aliases.unshift(this.name);
-
/**
* The command description
* @type {string}
@@ -71,18 +64,11 @@ module.exports = class Command {
*/
this.permissions = data.permissions ?? [];
- /**
- * Should the command handler process named arguments?
- * @type {boolean}
- * @default false
- */
- this.process_args = data.process_args === true;
-
/**
* The command options
- * @type {CommandArgument[]}
+ * @type {CommandOption[]}
*/
- this.args = data.args ?? [];
+ this.options = data.options ?? [];
/**
* True if command is internal, false if it is from a plugin
@@ -100,64 +86,40 @@ module.exports = class Command {
try {
this.manager.register(this); // register the command
- } catch (e) {
- return this.client.log.error(e);
+ } catch (error) {
+ return this.client.log.error(error);
}
-
-
}
/**
* The code to be executed when a command is invoked
* @abstract
- * @param {Message} message - The message that invoked this command
- * @param {(object|string)} [args] - Named command arguments, or the message content with the prefix and command removed
+ * @param {Interaction} interaction - The message that invoked this command
*/
- async execute(message, args) { } // eslint-disable-line no-unused-vars
+ async execute(interaction) { } // 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} [alias] - The command alias
- * @returns {Promise}
- */
- async sendUsage(channel, alias) {
- const settings = await this.client.utils.getSettings(channel.guild);
- if (!alias) alias = this.name;
-
- const prefix = settings.command_prefix;
- const i18n = this.client.i18n.getLocale(settings.locale);
-
- const addArgs = (embed, arg) => {
- const required = arg.required ? '`❗` ' : '';
- let description = `» ${i18n('cmd_usage.args.description', arg.description)}`;
- if (arg.example) description += `\n» ${i18n('cmd_usage.args.example', arg.example)}`;
- embed.addField(required + arg.name, description);
+ async build(guild) {
+ return {
+ defaultPermission: !this.staff_only,
+ description: this.description,
+ name: this.name,
+ options: typeof this.options === 'function' ? await this.options(guild) : this.options
};
+ }
- let usage,
- example,
- embed;
-
- if (this.process_args) {
- usage = `${prefix + alias} ${this.args.map(arg => arg.required ? `<${arg.name}>` : `[${arg.name}]`).join(' ')}`;
- example = `${prefix + alias} \n${this.args.map(arg => `--${arg.name} ${arg.example || ''}`).join('\n')}`;
- embed = new MessageEmbed()
- .setColor(settings.error_colour)
- .setTitle(i18n('cmd_usage.title', alias))
- .setDescription(i18n('cmd_usage.named_args') + i18n('cmd_usage.description', usage, example));
- } else {
- usage = `${prefix + alias} ${this.args.map(arg => arg.required ? `<${arg.name}>` : `[${arg.name}]`).join(' ')}`;
- example = `${prefix + alias} ${this.args.map(arg => `${arg.example || ''}`).join(' ')}`;
- embed = new MessageEmbed()
- .setColor(settings.error_colour)
- .setTitle(i18n('cmd_usage.title', alias))
- .setDescription(i18n('cmd_usage.description', usage, example));
- }
-
- this.args.forEach(arg => addArgs(embed, arg));
- return await channel.send({ embeds: [embed] });
-
+ static get option_types() {
+ return {
+ SUB_COMMAND: 1,
+ SUB_COMMAND_GROUP: 2,
+ STRING: 3, // eslint-disable-line sort-keys
+ INTEGER: 4, // eslint-disable-line sort-keys
+ BOOLEAN: 5, // eslint-disable-line sort-keys
+ USER: 6,
+ CHANNEL: 7, // eslint-disable-line sort-keys
+ ROLE: 8,
+ MENTIONABLE: 9, // eslint-disable-line sort-keys
+ NUMBER: 10
+ };
}
};
\ No newline at end of file
diff --git a/src/modules/commands/manager.js b/src/modules/commands/manager.js
index ff99568..9bb637f 100644
--- a/src/modules/commands/manager.js
+++ b/src/modules/commands/manager.js
@@ -2,16 +2,13 @@
const {
Client, // eslint-disable-line no-unused-vars
Collection,
- Message, // eslint-disable-line no-unused-vars
+ Interaction, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
const fs = require('fs');
const { path } = require('../../utils/fs');
-const { parseArgsStringToArgv: argv } = require('string-argv');
-const parseArgs = require('command-line-args');
-
/**
* Manages the loading and execution of commands
*/
@@ -48,81 +45,127 @@ module.exports = class CommandManager {
}
/** Register a command */
- register(cmd) {
- const exists = this.commands.has(cmd.name);
- const is_internal = (exists && cmd.internal) || (exists && this.commands.get(cmd.name).internal);
+ register(command) {
+ const exists = this.commands.has(command.name);
+ const is_internal = (exists && command.internal) || (exists && this.commands.get(command.name).internal);
if (is_internal) {
- const plugin = this.client.plugins.plugins.find(p => p.commands.includes(cmd.name));
- if (plugin) this.client.log.commands(`The "${plugin.name}" plugin has overridden the internal "${cmd.name}" command`);
- else this.client.log.commands(`An unknown plugin has overridden the internal "${cmd.name}" command`);
- if(cmd.internal) return;
+ const plugin = this.client.plugins.plugins.find(p => p.commands.includes(command.name));
+ if (plugin) this.client.log.commands(`The "${plugin.name}" plugin has overridden the internal "${command.name}" command`);
+ else this.client.log.commands(`An unknown plugin has overridden the internal "${command.name}" command`);
+ if(command.internal) return;
} else if (exists) {
- throw new Error(`A non-internal command with the name "${cmd.name}" already exists`);
+ throw new Error(`A non-internal command with the name "${command.name}" already exists`);
}
- this.commands.set(cmd.name, cmd);
- this.client.log.commands(`Loaded "${cmd.name}" command`);
+ this.commands.set(command.name, command);
+ this.client.log.commands(`Loaded "${command.name}" command`);
+ }
+
+ async publish(guild) {
+ if (!guild) {
+ return this.client.guilds.cache.forEach(guild => {
+ this.publish(guild);
+ });
+ }
+
+ try {
+ const commands = await Promise.all(this.client.commands.commands.map(async command => await command.build(guild)));
+ await this.client.application.commands.set(commands, guild.id);
+ await this.updatePermissions(guild);
+ this.client.log.success(`Published ${this.client.commands.commands.size} commands to "${guild.name}"`);
+ } catch (error) {
+ this.client.log.warn('An error occurred whilst publishing the commands');
+ this.client.log.error(error);
+ }
+ }
+
+ async updatePermissions(guild) {
+ guild.commands.fetch().then(async commands => {
+ const permissions = [];
+ const settings = await this.client.utils.getSettings(guild.id);
+ const blacklist = [];
+ settings.blacklist.users?.forEach(userId => {
+ blacklist.push({
+ id: userId,
+ permission: false,
+ type: 'USER'
+ });
+ });
+ settings.blacklist.roles?.forEach(roleId => {
+ blacklist.push({
+ id: roleId,
+ permission: false,
+ type: 'ROLE'
+ });
+ });
+
+ const categories = await this.client.db.models.Category.findAll({ where: { guild: guild.id } });
+ const staff_roles = new Set(categories.map(category => category.roles).flat());
+
+ commands.forEach(async g_cmd => {
+ const cmd_permissions = [...blacklist];
+ const command = this.client.commands.commands.get(g_cmd.name);
+
+ if (command.staff_only) {
+ cmd_permissions.push({
+ id: guild.roles.everyone.id,
+ permission: false,
+ type: 'ROLE'
+ });
+ staff_roles.forEach(roleId => {
+ cmd_permissions.push({
+ id: roleId,
+ permission: true,
+ type: 'ROLE'
+ });
+ });
+ }
+
+ permissions.push({
+ id: g_cmd.id,
+ permissions: cmd_permissions
+ });
+ });
+
+ this.client.log.debug(`Command permissions for "${guild.name}"`, require('util').inspect(permissions, {
+ colors: true,
+ depth: 10
+ }));
+
+ try {
+ await guild.commands.permissions.set({ fullPermissions: permissions });
+ } catch (error) {
+ this.client.log.warn('An error occurred whilst updating command permissions');
+ this.client.log.error(error);
+ }
+ });
}
/**
* Execute a command
- * @param {Message} message - Command message
+ * @param {Interaction} interaction - Command message
*/
- async handle(message) {
- if (message.author.bot) return; // ignore self and other bots
-
- const settings = await this.client.utils.getSettings(message.guild);
+ async handle(interaction) {
+ if (!interaction.guild) return this.client.log.debug('Ignoring non-guild command interaction');
+ const settings = await this.client.utils.getSettings(interaction.guild.id);
const i18n = this.client.i18n.getLocale(settings.locale);
- const prefix = settings.command_prefix;
- const escaped_prefix = prefix.toLowerCase().replace(/(?=\W)/g, '\\'); // (lazy) escape every character so it can be used in a RexExp
- const client_mention = `<@!?${this.client.user.id}>`;
- let cmd_name = message.content.match(new RegExp(`^(${escaped_prefix}|${client_mention}\\s?)(\\S+)`, 'mi')); // capture prefix and command
- if (!cmd_name) return; // stop here if the message is not a command
+ const command = this.commands.get(interaction.commandName);
+ if (!command) return;
- const raw_args = message.content.replace(cmd_name[0], '').trim(); // remove the prefix and command
- cmd_name = cmd_name[2].toLowerCase(); // set cmd_name to the actual command alias, effectively removing the prefix
-
- const cmd = this.commands.find(cmd => cmd.aliases.includes(cmd_name));
- if (!cmd) return;
-
- let is_blacklisted = false;
- if (settings.blacklist.includes(message.author.id)) {
- is_blacklisted = true;
- this.client.log.info(`Ignoring blacklisted member ${message.author.tag}`);
- } else {
- settings.blacklist.forEach(element => {
- if (message.guild.roles.cache.has(element) && message.member.roles.cache.has(element)) {
- is_blacklisted = true;
- this.client.log.info(`Ignoring member ${message.author.tag} with blacklisted role`);
- }
- });
- }
-
- if (is_blacklisted) {
- try {
- return message.react('❌');
- } catch (error) {
- return this.client.log.warn('Failed to react to a message');
- }
- }
-
- const bot_permissions = message.guild.me.permissionsIn(message.channel);
+ const bot_permissions = interaction.guild.me.permissionsIn(interaction.channel);
const required_bot_permissions = [
- 'ADD_REACTIONS',
'ATTACH_FILES',
'EMBED_LINKS',
'MANAGE_CHANNELS',
- 'MANAGE_MESSAGES',
- 'READ_MESSAGE_HISTORY',
- 'SEND_MESSAGES'
+ 'MANAGE_MESSAGES'
];
if (!bot_permissions.has(required_bot_permissions)) {
const perms = required_bot_permissions.map(p => `\`${p}\``).join(', ');
- if (bot_permissions.has(['EMBED_LINKS', 'SEND_MESSAGES'])) {
- await message.channel.send({
+ if (bot_permissions.has('EMBED_LINKS')) {
+ await interaction.reply({
embeds: [
new MessageEmbed()
.setColor('ORANGE')
@@ -130,76 +173,33 @@ module.exports = class CommandManager {
.setDescription(i18n('bot.missing_permissions.description', perms))
]
});
- } else if (bot_permissions.has('SEND_MESSAGES')) {
- await message.channel.send({ content: '⚠️ ' + i18n('bot.missing_permissions.description', perms) });
- } else if (bot_permissions.has('ADD_REACTIONS')) {
- await message.react('⚠️');
} else {
- this.client.log.warn('Unable to respond to command due to missing permissions');
+ await interaction.reply({ content: i18n('bot.missing_permissions.description', perms) });
}
return;
}
- const missing_permissions = cmd.permissions instanceof Array && !message.member.permissions.has(cmd.permissions);
+ const missing_permissions = command.permissions instanceof Array && !interaction.member.permissions.has(command.permissions);
if (missing_permissions) {
- const perms = cmd.permissions.map(p => `\`${p}\``).join(', ');
- return await message.channel.send({
+ const perms = command.permissions.map(p => `\`${p}\``).join(', ');
+ return await interaction.reply({
embeds: [
new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('missing_permissions.title'))
.setDescription(i18n('missing_permissions.description', perms))
- ]
+ ],
+ ephemeral: true
});
}
- if (cmd.staff_only && await this.client.utils.isStaff(message.member) === false) {
- return await message.channel.send({
- embeds: [
- new MessageEmbed()
- .setColor(settings.error_colour)
- .setTitle(i18n('staff_only.title'))
- .setDescription(i18n('staff_only.description'))
- ]
- });
- }
-
- let args = raw_args;
-
- if (cmd.process_args) {
- try {
- args = parseArgs(cmd.args, { argv: argv(raw_args) });
- } catch (error) {
- const help_cmd = `${settings.command_prefix}${i18n('commands.help.name')} ${cmd_name}`;
- return await message.channel.send({
- embeds: [
- new MessageEmbed()
- .setColor(settings.error_colour)
- .setTitle(i18n('cmd_usage.invalid_named_args.title'))
- .setDescription(i18n('cmd_usage.invalid_named_args.description', error.message, help_cmd))
- ]
- });
- }
- for (const arg of cmd.args) {
- if (arg.required && args[arg.name] === undefined) {
- return await cmd.sendUsage(message.channel, cmd_name); // send usage if any required arg is missing
- }
- }
- } else {
- 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);
- }
- }
-
try {
- this.client.log.commands(`Executing "${cmd.name}" command (invoked by ${message.author.tag})`);
- await cmd.execute(message, args); // execute the command
+ this.client.log.commands(`Executing "${command.name}" command (invoked by ${interaction.user.tag})`);
+ await command.execute(interaction); // execute the command
} catch (e) {
- this.client.log.warn(`An error occurred whilst executing the ${cmd.name} command`);
+ this.client.log.warn(`An error occurred whilst executing the ${command.name} command`);
this.client.log.error(e);
- await message.channel.send({
+ await interaction.reply({
embeds: [
new MessageEmbed()
.setColor('ORANGE')
diff --git a/src/modules/tickets/manager.js b/src/modules/tickets/manager.js
index da9d228..e97befa 100644
--- a/src/modules/tickets/manager.js
+++ b/src/modules/tickets/manager.js
@@ -1,7 +1,11 @@
/* eslint-disable max-lines */
const EventEmitter = require('events');
const TicketArchives = require('./archives');
-const { MessageEmbed } = require('discord.js');
+const {
+ MessageActionRow,
+ MessageButton,
+ MessageEmbed
+} = require('discord.js');
/** Manages tickets */
module.exports = class TicketManager extends EventEmitter {
@@ -70,23 +74,13 @@ module.exports = class TicketManager extends EventEmitter {
});
(async () => {
- const settings = await this.client.utils.getSettings(guild);
+ const settings = await this.client.utils.getSettings(guild.id);
const i18n = this.client.i18n.getLocale(settings.locale);
topic = t_row.topic
? this.client.cryptr.decrypt(t_row.topic)
: '';
- if (cat_row.ping instanceof Array && cat_row.ping.length > 0) {
- const mentions = cat_row.ping.map(id => id === 'everyone'
- ? '@everyone'
- : id === 'here'
- ? '@here'
- : `<@&${id}>`);
-
- await t_channel.send({ content: mentions.join(', ') });
- }
-
if (cat_row.image) {
await t_channel.send({ content: cat_row.image });
}
@@ -102,8 +96,39 @@ module.exports = class TicketManager extends EventEmitter {
if (topic) embed.addField(i18n('ticket.opening_message.fields.topic'), topic);
+ const components = new MessageActionRow();
+
+ if (cat_row.claiming) {
+ components.addComponents(
+ new MessageButton()
+ .setCustomId('ticket.claim')
+ .setLabel(i18n('ticket.claim'))
+ .setEmoji('🙌')
+ .setStyle('SECONDARY')
+ );
+ }
+
+ if (settings.close_button) {
+ components.addComponents(
+ new MessageButton()
+ .setCustomId('ticket.close')
+ .setLabel(i18n('ticket.close'))
+ .setEmoji('✖️')
+ .setStyle('DANGER')
+ );
+ }
+
+ const mentions = cat_row.ping instanceof Array && cat_row.ping.length > 0
+ ? cat_row.ping.map(id => id === 'everyone'
+ ? '@everyone'
+ : id === 'here'
+ ? '@here'
+ : `<@&${id}>`)
+ .join(', ')
+ : '';
const sent = await t_channel.send({
- content: creator.user.toString(),
+ components: [components],
+ content: i18n('ticket.opening_message.content', mentions, creator.user.toString()),
embeds: [embed]
});
await sent.pin({ reason: 'Ticket opening message' });
@@ -118,10 +143,6 @@ module.exports = class TicketManager extends EventEmitter {
.catch(() => this.client.log.warn('Failed to delete system pin message'));
}
- if (cat_row.claiming) {
- await sent.react('🙌');
- }
-
let questions;
if (cat_row.opening_questions) {
questions = cat_row.opening_questions
@@ -134,7 +155,7 @@ module.exports = class TicketManager extends EventEmitter {
embeds: [
new MessageEmbed()
.setColor(settings.colour)
- .setTitle('⚠️ ' + i18n('commands.new.request_topic.title'))
+ .setTitle(i18n('commands.new.request_topic.title'))
.setDescription(i18n('commands.new.request_topic.description'))
.setFooter(this.client.utils.footer(settings.footer, i18n('collector_expires_in', 120)), guild.iconURL())
]
@@ -213,7 +234,7 @@ module.exports = class TicketManager extends EventEmitter {
this.emit('beforeClose', ticket_id);
const guild = this.client.guilds.cache.get(t_row.guild);
- const settings = await this.client.utils.getSettings(guild);
+ const settings = await this.client.utils.getSettings(guild.id);
const i18n = this.client.i18n.getLocale(settings.locale);
const channel = await this.client.channels.fetch(t_row.id);
@@ -273,98 +294,102 @@ module.exports = class TicketManager extends EventEmitter {
};
if (channel) {
- const creator = await guild.members.fetch(t_row.creator);
+ guild.members.fetch(t_row.creator)
+ .then(async creator => {
+ const cat_row = await this.client.db.models.Category.findOne({ where: { id: t_row.category } });
+ if (creator && cat_row.survey) {
+ const survey = await this.client.db.models.Survey.findOne({
+ where: {
+ guild: t_row.guild,
+ name: cat_row.survey
+ }
+ });
- const cat_row = await this.client.db.models.Category.findOne({ where: { id: t_row.category } });
-
- if (creator && cat_row.survey) {
- const survey = await this.client.db.models.Survey.findOne({
- where: {
- guild: t_row.guild,
- name: cat_row.survey
- }
- });
-
- if (survey) {
- const r_collector_message = await channel.send({
- content: creator.toString(),
- embeds: [
- new MessageEmbed()
- .setColor(settings.colour)
- .setTitle(i18n('ticket.survey.start.title'))
- .setDescription(i18n('ticket.survey.start.description', creator.toString(), survey.questions.length))
- .setFooter(i18n('collector_expires_in', 60))
- ]
- });
-
- await r_collector_message.react('✅');
-
- const filter = (reaction, user) => user.id === creator.user.id && reaction.emoji.name === '✅';
-
- const r_collector = r_collector_message.createReactionCollector({
- filter,
- time: 60000
- });
-
- r_collector.on('collect', async () => {
- r_collector.stop();
- const filter = message => message.author.id === creator.id;
- let answers = [];
- let number = 1;
- for (const question of survey.questions) {
- await channel.send({
+ if (survey) {
+ const r_collector_message = await channel.send({
+ content: creator.toString(),
embeds: [
new MessageEmbed()
.setColor(settings.colour)
- .setTitle(`${number++}/${survey.questions.length}`)
- .setDescription(question)
+ .setTitle(i18n('ticket.survey.start.title'))
+ .setDescription(i18n('ticket.survey.start.description', creator.toString(), survey.questions.length))
.setFooter(i18n('collector_expires_in', 60))
]
});
- try {
- const collected = await channel.awaitMessages({
- errors: ['time'],
- filter,
- max: 1,
- time: 60000
+ await r_collector_message.react('✅');
+
+ const filter = (reaction, user) => user.id === creator.user.id && reaction.emoji.name === '✅';
+
+ const r_collector = r_collector_message.createReactionCollector({
+ filter,
+ time: 60000
+ });
+
+ r_collector.on('collect', async () => {
+ r_collector.stop();
+ const filter = message => message.author.id === creator.id;
+ let answers = [];
+ let number = 1;
+ for (const question of survey.questions) {
+ await channel.send({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.colour)
+ .setTitle(`${number++}/${survey.questions.length}`)
+ .setDescription(question)
+ .setFooter(i18n('collector_expires_in', 60))
+ ]
+ });
+
+ try {
+ const collected = await channel.awaitMessages({
+ errors: ['time'],
+ filter,
+ max: 1,
+ time: 60000
+ });
+ answers.push(collected.first().content);
+ } catch (collected) {
+ return await close();
+ }
+ }
+
+ await channel.send({
+ embeds: [
+ new MessageEmbed()
+ .setColor(settings.success_colour)
+ .setTitle(i18n('ticket.survey.complete.title'))
+ .setDescription(i18n('ticket.survey.complete.description'))
+ .setFooter(settings.footer, guild.iconURL())
+ ]
});
- answers.push(collected.first().content);
- } catch (collected) {
- return await close();
- }
+
+ answers = answers.map(a => this.client.cryptr.encrypt(a));
+ await this.client.db.models.SurveyResponse.create({
+ answers,
+ survey: survey.id,
+ ticket: t_row.id
+ });
+
+ await close();
+
+ });
+
+ r_collector.on('end', async collected => {
+ if (collected.size === 0) {
+ await close();
+ }
+ });
}
-
- await channel.send({
- embeds: [
- new MessageEmbed()
- .setColor(settings.success_colour)
- .setTitle(i18n('ticket.survey.complete.title'))
- .setDescription(i18n('ticket.survey.complete.description'))
- .setFooter(settings.footer, guild.iconURL())
- ]
- });
-
- answers = answers.map(a => this.client.cryptr.encrypt(a));
- await this.client.db.models.SurveyResponse.create({
- answers,
- survey: survey.id,
- ticket: t_row.id
- });
-
+ } else {
await close();
-
- });
-
- r_collector.on('end', async collected => {
- if (collected.size === 0) {
- await close();
- }
- });
- }
- } else {
- await close();
- }
+ }
+ })
+ .catch(async () => {
+ this.client.log.debug('Skipping survey as member has left');
+ await close();
+ });
}
this.emit('close', ticket_id);
diff --git a/src/utils/discord.js b/src/utils/discord.js
index 6b97178..77d4800 100644
--- a/src/utils/discord.js
+++ b/src/utils/discord.js
@@ -1,8 +1,6 @@
-const {
- Guild, // eslint-disable-line no-unused-vars
- GuildMember // eslint-disable-line no-unused-vars
-} = require('discord.js');
+const { GuildMember } = require('discord.js'); // eslint-disable-line no-unused-vars
+const { Model } = require('sequelize'); // eslint-disable-line no-unused-vars
const config = require('../../user/config');
let current_presence = -1;
@@ -33,12 +31,12 @@ module.exports = class DiscordUtils {
}
/**
- * get a guild's settings
- * @param {Guild} guild - The Guild
+ * Fet a guild's settings
+ * @param {string} id - The guild's ID
* @returns {Promise}
*/
- async getSettings(guild) {
- const data = { id: guild.id };
+ async getSettings(id) {
+ const data = { id };
const [settings] = await this.client.db.models.Guild.findOrCreate({
defaults: data,
where: data
@@ -48,11 +46,11 @@ module.exports = class DiscordUtils {
/**
* Delete a guild's settings
- * @param {Guild} guild - The Guild
+ * @param {string} id - The guild ID
* @returns {Promise}
*/
- async deleteSettings(guild) {
- const row = await this.getSettings(guild);
+ async deleteSettings(id) {
+ const row = await this.getSettings(id);
return await row.destroy();
}
diff --git a/user/example.config.js b/user/example.config.js
index 3ea17fe..406af9e 100644
--- a/user/example.config.js
+++ b/user/example.config.js
@@ -16,17 +16,15 @@
* ###############################################################################################
*/
-const prefix = '-';
module.exports = {
- debug: false,
defaults: {
colour: '#009999',
- command_prefix: prefix,
log_messages: true,
name_format: 'ticket-{number}',
opening_message: '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.__'
},
+ developer: { debug: false },
locale: 'en-GB',
logs: {
enabled: true,
@@ -39,7 +37,7 @@ module.exports = {
duration: 60,
presences: [
{
- activity: `${prefix}new`,
+ activity: '/new',
type: 'PLAYING'
},
{