diff --git a/src/commands/blacklist.js b/src/commands/blacklist.js new file mode 100644 index 0000000..268cb3e --- /dev/null +++ b/src/commands/blacklist.js @@ -0,0 +1,106 @@ +const { MessageEmbed } = require('discord.js'); +const Command = require('../modules/commands/command'); + +module.exports = class BlacklistCommand extends Command { + constructor(client) { + const i18n = client.i18n.get(client.config.locale); + super(client, { + internal: true, + name: i18n('commands.blacklist.name'), + description: i18n('commands.blacklist.description'), + aliases: [ + i18n('commands.blacklist.aliases.unblacklist'), + ], + permissions: ['MANAGE_GUILD'], // staff_only: true, + process_args: false, + args: [ + { + name: i18n('commands.blacklist.args.member_or_role.name'), + description: i18n('commands.blacklist.args.member_or_role.description'), + example: i18n('commands.blacklist.args.member_or_role.example'), + required: false, + } + ] + }); + } + + async execute(message, args) { + + let settings = await message.guild.settings; + const i18n = this.client.i18n.get(settings.locale); + + let member = message.mentions.members.first(); + + if (member && (await member.isStaff() || member.hasPermission(this.permissions))) { + return await message.channel.send( + 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()) + ); + } + + + let role = message.mentions.roles.first(); + let id; + let 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( + 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 + let 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( + new MessageEmbed() + .setColor(settings.colour) + .setTitle(i18n('commands.blacklist.response.list.title')) + .setDescription(blacklist.join('\n')) + .setFooter(settings.footer, message.guild.iconURL()) + ); + } + + const is_role = role !== undefined || message.guild.roles.cache.has(id); + let member_or_role = is_role ? 'role' : 'member'; + let index = settings.blacklist.findIndex(element => element === id); + + let new_blacklist = [ ...settings.blacklist]; + + if (index === -1) { + new_blacklist.push(id); + await message.channel.send( + 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( + 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(); + + } +}; \ No newline at end of file diff --git a/src/database/models/guild.model.js b/src/database/models/guild.model.js index b8473bc..7498179 100644 --- a/src/database/models/guild.model.js +++ b/src/database/models/guild.model.js @@ -7,6 +7,10 @@ module.exports = ({ config }, sequelize) => { primaryKey: true, allowNull: false, }, + blacklist: { + type: DataTypes.JSON, + defaultValue: [], + }, colour: { type: DataTypes.STRING, defaultValue: config.defaults.colour diff --git a/src/database/models/ticket.model.js b/src/database/models/ticket.model.js index 1ffebcb..1ca4461 100644 --- a/src/database/models/ticket.model.js +++ b/src/database/models/ticket.model.js @@ -23,6 +23,10 @@ module.exports = (client, sequelize) => { type: DataTypes.CHAR(18), allowNull: false, }, + first_response: { + type: DataTypes.DATE, + allowNull: true, + }, guild: { type: DataTypes.CHAR(18), allowNull: false, diff --git a/src/listeners/message.js b/src/listeners/message.js index 67fab76..30fcaaa 100644 --- a/src/listeners/message.js +++ b/src/listeners/message.js @@ -6,6 +6,27 @@ module.exports = { let settings = await message.guild.settings; if (!settings) settings = await message.guild.createSettings(); + let is_blacklisted = false; + if (settings.blacklist.includes(message.author.id)) { + is_blacklisted = true; + 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; + client.log.info(`Ignoring member ${message.author.tag} with blacklisted role`); + } + }); + } + + if (is_blacklisted) { + try { + return message.react('❌'); + } catch (error) { + return client.log.debug('Failed to react to a message'); + } + } + if (settings.log_messages && !message.system) client.tickets.archives.addMessage(message); // add the message to the archives (if it is in a ticket channel) client.commands.handle(message); // pass the message to the command handler diff --git a/src/listeners/messageReactionAdd.js b/src/listeners/messageReactionAdd.js index cdc2444..c97cf7e 100644 --- a/src/listeners/messageReactionAdd.js +++ b/src/listeners/messageReactionAdd.js @@ -1,5 +1,22 @@ module.exports = { event: 'messageReactionAdd', - execute: (client, r, u) => { + execute: async (client, r, u) => { + const guild = r.message; + if (!guild) return; + + let settings = await guild.settings; + if (!settings) settings = await guild.createSettings(); + + if (settings.blacklist.includes(u.id)) { + return client.log.info(`Ignoring blacklisted member ${u.tag}`); + } else { + let member = await guild.members.fetch(u.id); + settings.blacklist.forEach(element => { + if (guild.roles.cache.has(element) && member.roles.cache.has(element)) { + return client.log.info(`Ignoring member ${u.tag} with blacklisted role`); + } + }); + } + } }; \ No newline at end of file diff --git a/src/locales/en-GB.json b/src/locales/en-GB.json index 3c3db90..a78fd44 100644 --- a/src/locales/en-GB.json +++ b/src/locales/en-GB.json @@ -18,37 +18,41 @@ "unblacklist": "unblacklist" }, "args": { - "member": { - "name": "member", - "description": "A member mention or ID", - "example": "@naughty" + "member_or_role": { + "name": "memberOrRole", + "description": "A member mention, a role mention, or the ID of a member or role", + "example": "@naughty-member" } }, "description": "Blacklist/unblacklist a member from interacting with the bot", "name": "blacklist", "response": { - "added": { - "title": "✅ Added member to blacklist", - "description": "%s has been added to the blacklist. They will no longer be able to interact with the bot." - }, - "already_in_list": { - "title": "❌ Can't add member to blacklist", - "description": "%s is already blacklisted, so can not be added to the blacklist." - }, - "list": { - "title": "📃 Blacklisted members" - }, - "not_in_list": { - "title": "❌ Can't remove member from blacklist", - "description": "%s is not blacklisted, so can not be unblacklisted." + "empty_list": { + "title": "📃 Blacklisted members and roles", + "description": "There are no members or roles blacklisted. Type `%sblacklist ` to add a member or role to the blacklist." }, "illegal_action": { "title": "❌ You can't blacklist this member", "description": "%s is a staff member and cannot be blacklisted." }, - "removed": { + "list": { + "title": "📃 Blacklisted members and roles" + }, + "member_added": { + "title": "✅ Added member to blacklist", + "description": "<@%s> has been added to the blacklist. They will no longer be able to interact with the bot." + }, + "member_removed": { "title": "✅ Removed member from blacklist", - "description": "%s has been removed from the blacklist. They can now use the bot again." + "description": "<@%s> has been removed from the blacklist. They can now use the bot again." + }, + "role_added": { + "title": "✅ Added role to blacklist", + "description": "<@&%s> has been added to the blacklist. Members with this role will no longer be able to interact with the bot." + }, + "role_removed": { + "title": "✅ Removed role from blacklist", + "description": "<@&%s> has been removed from the blacklist. Members with this role can now use the bot again." } } }, diff --git a/src/modules/commands/manager.js b/src/modules/commands/manager.js index db285b3..ec5f73b 100644 --- a/src/modules/commands/manager.js +++ b/src/modules/commands/manager.js @@ -53,9 +53,7 @@ module.exports = class CommandManager { throw new Error(`A non-internal command with the name "${cmd.name}" already exists`); this.commands.set(cmd.name, cmd); - - let internal = cmd.internal ? 'internal ' : ''; - this.client.log.commands(`Loaded ${internal}"${cmd.name}" command`); + this.client.log.commands(`Loaded "${cmd.name}" command`); } /** @@ -110,26 +108,13 @@ module.exports = class CommandManager { ); } - if (cmd.staff_only) { - let staff_roles = new Set(); - let guild_categories = await this.client.db.models.Category.findAll({ - where: { - guild: message.guild.id - } - }); - guild_categories.forEach(cat => { - cat.roles.forEach(r => staff_roles.add(r)); - }); // add all of the staff role IDs to the Set - staff_roles = staff_roles.filter(r => message.member.roles.cache.has(r)); // filter out any roles that the member does not have - const not_staff = staff_roles.length === 0; - if (not_staff) { - return await message.channel.send( - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('staff_only.title')) - .setDescription(i18n('staff_only.description')) - ); - } + if (cmd.staff_only && await message.member.isStaff() === false) { + return await message.channel.send( + new MessageEmbed() + .setColor(settings.error_colour) + .setTitle(i18n('staff_only.title')) + .setDescription(i18n('staff_only.description')) + ); } try { diff --git a/src/structures/guild_member.js b/src/structures/guild_member.js new file mode 100644 index 0000000..7f913cc --- /dev/null +++ b/src/structures/guild_member.js @@ -0,0 +1,26 @@ +const { Structures } = require('discord.js'); + +Structures.extend('GuildMember', GuildMember => { + return class extends GuildMember { + constructor(client, data, guild) { + super(client, data, guild); + } + + async isStaff() { + let guild_categories = await this.client.db.models.Category.findAll({ + where: { + guild: this.guild.id + } + }); + + guild_categories.forEach(cat => { + cat.roles.forEach(r => { + if (this.roles.cache.has(r)) return true; + }); + }); + + return false; + } + + }; +}); \ No newline at end of file diff --git a/src/updater.js b/src/updater.js index 78fa2b0..7008261 100644 --- a/src/updater.js +++ b/src/updater.js @@ -2,6 +2,7 @@ const fetch = require('node-fetch'); const boxen = require('boxen'); const link = require('terminal-link'); const semver = require('semver'); +const { format } = require('leekslazylogger-fastify'); let { version: current } = require('../package.json'); @@ -17,13 +18,14 @@ module.exports = async client => { if (semver.lt(current, latest)) { client.log.notice(client.log.f(`There is an update available for Discord Tickets (${current} -> ${update.tag_name})`)); - let notice = []; - notice.push(`&6You are currently using &c${current}&6, the latest is &a${update.tag_name}&6.`); - notice.push(`&6Download "&f${update.name}&6" from`); - notice.push(link('&6the GitHub releases page', 'https://github.com/discord-tickets/bot/releases/')); + let lines = [ + `&6You are currently using &c${current}&6, the latest is &a${update.tag_name}&6.`, + `&6Download "&f${update.name}&6" from`, + link('&6the GitHub releases page', 'https://github.com/discord-tickets/bot/releases/') + ]; console.log( - boxen(client.log.f(notice.join('\n')), { + boxen(format(lines.join('\n')), { padding: 1, margin: 1, align: 'center',