const { Command } = require('../../../../interfaces/'); const { MessageAttachment } = require('discord.js'); const { stripIndents } = require('common-tags'); const moment = require('moment'); const { UploadLimit } = require('../../../../../util/Constants.js'); const { Util } = require('../../../../../util/'); const { InfractionResolves } = require('../../../../../util/Constants.js'); const Constants = { PageSize: 5, MaxCharacters: 256, MaxCharactersVerbose: 128 //Displays more information in the field, decreases characters. }; class HistoryCommand extends Command { constructor(client) { super(client, { name: 'history', module: 'moderation', usage: "[user..|channel..]", aliases: [ 'moderation' ], memberPermissions: ['MANAGE_MESSAGES'], guildOnly: true, arguments: [ { name: 'before', //Search for moderation actions before x usage: '', type: 'DATE', types: ['FLAG'], required: true }, { name: 'after', //Search for moderation actions after x usage: '', type: 'DATE', types: ['FLAG'], required: true }, { name: 'oldest', aliases: ['old'], type: 'BOOLEAN', types: ['FLAG'], default: true }, { name: 'type', aliases: ['types'], type: 'STRING', types: ['FLAG'], infinite: true, required: true }, { name: 'pagesize', usage: '<1-10>', type: 'INTEGER', types: ['FLAG'], required: true, default: 10, min: 1, max: 10 }, { name: 'verbose', //Shows IDs for users/channels. type: 'BOOLEAN', types: ['FLAG'], default: true }, { name: 'export', //Export moderation actions in a JSON. type: 'BOOLEAN', types: ['FLAG'], default: true }, { name: 'private', //Send moderation history in DMs. type: 'BOOLEAN', types: ['FLAG'], default: true } //filter, exclude, verbose (NO PAGESIZE) ], throttling: { usages: 2, duration: 10 } }); this.client = client; } async execute(message, { params, args }) { const priv = Boolean(args.private); if(args.export) return this._exportLogs(message, priv); const query = { guild: message.guild.id }; const { parsed, parameters } = await this.client.resolver.infinite(params, [ this.client.resolver.resolveMember.bind(this.client.resolver), this.client.resolver.resolveUser.bind(this.client.resolver), this.client.resolver.resolveChannel.bind(this.client.resolver) ], true, message.guild, (c) => c.type === 'text'); if(parsed.length > 0) query.target = { $in: parsed.map((p) => p.id) }; //Add resolved ids to the query. if(args.before || args.after) { query.timestamp = {}; if(args.before) query.timestamp.$lt = args.before.value.valueOf(); //Add before timestamps to the query. if(args.after) query.timestamp.$gt = args.after.value.valueOf(); //Add after timestamps to the query. } if(args.type) { const filter = []; for(const value of args.type.value) { for(const [ type, matches ] of Object.entries(InfractionResolves)) { if(matches.includes(value.toLowerCase())) { filter.push(type); } } } query.type = { $in: filter }; } const pageSize = args.pagesize ? args.pagesize.value : Constants.PageSize; let page = 1; if(parameters.length > 0) { const number = parseInt(parameters[0]); if(!Number.isNaN(number) && number > 1) { page = number; } } const collectionSize = await this.client.storageManager.mongodb.infractions.count(query); if(collectionSize === 0) { return message.respond(message.format('C_HISTORY_NORESULTS'), { emoji: 'failure' }); } const maxPage = Math.ceil(collectionSize/pageSize); if(page > maxPage) page = maxPage; const infractions = await this.client.storageManager.mongodb.db.collection('infractions').find(query) .sort({ timestamp: args.oldest ? 1 : -1 }) .skip((page-1)*pageSize).limit(pageSize) .toArray(); const embed = { author: { name: 'Infraction History', icon_url: message.guild.iconURL() //eslint-disable-line camelcase }, fields: [], footer: { text: `• Page ${page}/${maxPage} | ${collectionSize} Results` }, color: message.guild.me.roles.highest.color }; let long = false; const handleReason = (text) => { const MaxCharacters = Constants[args.verbose ? 'MaxCharactersVerbose' : 'MaxCharacters']; text = Util.escapeMarkdown(text); if(text.length > MaxCharacters) { text = `${text.substring(0, MaxCharacters-3)}...`; long = true; } text = text.replace(/\\n/giu, ' '); return text; }; for(let i = 0; i `<@&${r}>`).join(' ') })}`; } if(infraction.duration) string += `\n**Duration:** ${Util.duration(infraction.duration)}`; if(infraction.points) string += `\n**Points:** ${infraction.points}`; string += `\n**Reason:** \`${handleReason(infraction.reason)}\``; if(i !== infractions.length-1) string += `\n\u200b`; //Space out cases (as long as its not at the end) embed.fields.push({ name: `__**${infraction.type} \`[case-${infraction.case}]\`** *(${moment(infraction.timestamp).fromNow()})*__`, value: string }); } if(long) embed.footer.text += ` • To see the full reason, use the ${message.guild.prefix}case command.`; const type = message.format('C_HISTORY_SUCCESSTYPE', { old: Boolean(args.oldest) }, true); try { message.respond(message.format('C_HISTORY_SUCCESS', { targets: parsed.length > 0 ? message.format('C_HISTORY_SUCCESSTARGETS', { plural: parsed.length === 1 ? '' : 's', targets: parsed.map((p) => `**${Util.escapeMarkdown(p.display)}**`).join(' ') }) : '', type }), { emoji: 'success', embed, dm: Boolean(args.private) }); } catch(e) { //eslint-disable-line no-unused-vars message.respond(message.format('C_HISTORY_FAILTOOLONG'), { emoji: 'failure' }); } } async _exportLogs(message, priv = false) { const logs = await this.client.storageManager.mongodb.infractions.find({ guild: message.guild.id }); const string = JSON.stringify(logs); const attachment = new MessageAttachment(Buffer.from(string), `${message.guild.id}-moderation.json`); const bytes = Buffer.byteLength(attachment.attachment); const premium = priv ? '0' : message.guild.premiumTier; if(bytes > UploadLimit[premium]*1024*1024) { message.respond(message.format('C_HISTORY_FAILEXPORT', { amount: bytes, max: UploadLimit[premium]*1024*1024 })); } message.respond(message.format('C_HISTORY_SUCCESSEXPORT'), { files: [ attachment ], emoji: 'success', dm: priv }); } } module.exports = HistoryCommand;