diff --git a/src/commands/closeall.js b/src/commands/closeall.js new file mode 100644 index 0000000..f6fe8b8 --- /dev/null +++ b/src/commands/closeall.js @@ -0,0 +1,225 @@ +/** + * @name DiscordTickets + * @author eartharoid + * @license GNU-GPLv3 + * + */ + +const Logger = require('leekslazylogger'); +const log = new Logger(); +const { MessageEmbed } = require('discord.js'); +const fs = require('fs'); +const { join } = require('path'); +const config = require(join(__dirname, '../../user/', require('../').config)); +const archive = require('../modules/archive'); + +// A slight modification to the 'close' command to allow multiple tickets to be closed at once + +module.exports = { + name: 'closeall', + description: 'Closes all currently open tickets.', + usage: '', + aliases: ['ca'], + example: 'closeall', + args: false, + disabled: !config.commands.closeall.enabled, + async execute(client, message, args, { + config, + Ticket + }) { + const guild = client.guilds.cache.get(config.guild); + + if (!message.member.roles.cache.has(config.staff_role)) + return message.channel.send( + new MessageEmbed() + .setColor(config.err_colour) + .setAuthor(message.author.username, message.author.displayAvatarURL()) + .setTitle('❌ **No permission**') + .setDescription('You do not have permission to use this command as you are not a staff member.') + .addField('Usage', `\`${config.prefix}${this.name}${' ' + this.usage}\`\n`) + .addField('Help', `Type \`${config.prefix}help ${this.name}\` for more information`) + .setFooter(guild.name, guild.iconURL()) + ); + + let tickets = await Ticket.findAndCountAll({ + where: { + open: true, + }, + }); + + if (tickets.count === 0) + return message.channel.send( + new MessageEmbed() + .setColor(config.err_colour) + .setAuthor(message.author.username, message.author.display) + .setTitle('❌ **No open tickets**') + .setDescription('There are no open tickets to close.') + .setFooter(guild.name, guild.iconURL()) + ); + + log.info(`Found ${tickets.count} open tickets`); + + if (config.commands.close.confirmation) { + let success; + let pre = config.transcripts.text.enabled || config.transcripts.web.enabled + ? `You will be able to view an archived version of each ticket later with \`${config.prefix}transcript \`` + : ''; + + let confirm = await message.channel.send( + new MessageEmbed() + .setColor(config.colour) + .setAuthor(message.author.username, message.author.displayAvatarURL()) + .setTitle(`❔ Are you sure you want to close **${tickets.count}** tickets?`) + .setDescription(`${pre}\n**React with ✅ to confirm.**`) + .setFooter(guild.name + ' | Expires in 15 seconds', guild.iconURL()) + ); + + await confirm.react('✅'); + + const collector = confirm.createReactionCollector( + (reaction, user) => reaction.emoji.name === '✅' && user.id === message.author.id, { + time: 15000, + }); + + collector.on('collect', async () => { + message.channel.send( + new MessageEmbed() + .setColor(config.colour) + .setAuthor(message.author.username, message.author.displayAvatarURL()) + .setTitle(`**\`${tickets.count}\` tickets closed**`) + .setDescription(`**\`${tickets.count}\`** tickets closed by ${message.author}`) + .setFooter(guild.name, guild.iconURL()) + ); + + confirm.reactions.removeAll(); + confirm.edit( + new MessageEmbed() + .setColor(config.colour) + .setAuthor(message.author.username, message.author.displayAvatarURL()) + .setTitle(`✅ ** \`${tickets.count}\` tickets closed**`) + .setDescription('The channels will be automatically deleted in a few seconds, once the contents have been archived.') + .setFooter(guild.name, guild.iconURL()) + ); + + message.delete({ + timeout: 5000, + }).then(() => confirm.delete()); + + success = true; + closeAll(); + }); + + collector.on('end', () => { + if (!success) { + confirm.reactions.removeAll(); + confirm.edit( + new MessageEmbed() + .setColor(config.err_colour) + .setAuthor(message.author.username, message.author.displayAvatarURL()) + .setTitle('❌ **Expired**') + .setDescription('You took too long to react; confirmation failed.') + .setFooter(guild.name, guild.iconURL())); + + message.delete({ + timeout: 10000 + }).then(() => confirm.delete()); + } + }); + } else { + closeAll(); + } + + + async function closeAll() { + tickets.rows.forEach(async ticket => { + let users = []; + + if (config.transcripts.text.enabled || config.transcripts.web.enabled) { + let { + channel, + id, + creator + } = ticket; + + let user = await client.users.fetch(creator); + let paths = { + text: join(__dirname, `../../user/transcripts/text/${channel}.txt`), + log: join(__dirname, `../../user/transcripts/raw/${channel}.log`), + json: join(__dirname, `../../user/transcripts/raw/entities/${channel}.json`) + }; + + if (user) { + let dm; + try { + dm = user.dmChannel || await user.createDM(); + } catch (e) { + log.warn(`Could not create DM channel with ${user.tag}`); + } + + let res = {}; + const embed = new MessageEmbed() + .setColor(config.colour) + .setAuthor(message.author.username) + .setTitle(`Ticket ${id}`) + .setFooter(guild.name, guild.iconURL()); + + if (fs.existsSync(paths.text)) { + embed.addField('Text Transcript', 'See attachment'); + res.files = [{ + attachment: paths.text, + name: `ticket-${id}-${channel}.txt` + }]; + } + + if (fs.existsSync(paths.log) && fs.existsSync(paths.json)) { + let data = JSON.parse(fs.readFileSync(paths.json)); + data.entities.users.forEach(u => users.push(u)); + embed.addField('Web archive', await archive.export(Ticket, channel)); + } + + res.embed = embed; + + try { + if (config.commands.close.send_transcripts) dm.send(res); + if (config.transcripts.channel.length > 1) client.channels.cache.get(config.transcripts.channel).send(res); + } catch (e) { + message.channel.send('❌ Couldn\'t send DM or transcript log message'); + } + } + + await Ticket.update({ + open: false, + }, { + where: { + id, + } + }); + + log.info(log.format(`${message.author.tag} closed ticket &7${id}&f`)); + + client.channels.fetch(channel) + .then(c => c.delete() + .then(o => log.info(`Deleted channel with name: '#${o.name}' <${o.id}>`)) + .catch(e => log.error(e))) + .catch(e => log.error(e)); + + if (config.logs.discord.enabled) { + let embed = new MessageEmbed() + .setColor(config.colour) + .setAuthor(message.author.username, message.author.displayAvatarURL()) + .setTitle(`${tickets.count} ticket${tickets.count > 1 ? 's' : ''} closed (${config.prefix}closeall)`) + .addField('Closed by', message.author, true) + .setFooter(guild.name, guild.iconURL()) + .setTimestamp(); + + if (users.length > 1) + embed.addField('Members', users.map(u => `<@${u}>`).join('\n')); + + client.channels.cache.get(config.logs.discord.channel).send(embed); + } + } + }); + } + + }, +}; \ No newline at end of file diff --git a/user/config.js b/user/config.js index 7e182e5..e5083ab 100644 --- a/user/config.js +++ b/user/config.js @@ -76,6 +76,9 @@ module.exports = { new: { enabled: true }, + closeall: { + enabled: true, + }, }, transcripts: {