/** * @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'); const { plural } = require('../modules/utils'); const { Op } = require('sequelize'); const toTime = require('to-time-monthsfork'); // 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 older than a specified time length', usage: '', aliases: ['ca'], example: 'closeall 1y 2m', 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; if (args.length > 0) { let time, maxDate; let timestamp = args.join(' '); try { time = toTime(timestamp).milliseconds(); maxDate = new Date(Date.now() - time); } catch (error) { return message.channel.send( new MessageEmbed() .setColor(config.err_colour) .setAuthor(message.author.username, message.author.displayAvatarURL()) .setTitle('❌ **Invalid Timestamp**') .setDescription(`The timestamp that you specified, \`${timestamp}\`, was invalid.`) .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()) ); } tickets = await Ticket.findAndCountAll({ where: { open: true, updatedAt: { [Op.lte]: maxDate, } }, }); } else { 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} ${plural('ticket', tickets.count)} 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); } } }); } }, };