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 Constants = { PageSize: 5, MaxCharacters: 256, MaxCharactersVerbose: 128, //Displays more information in the field, decreases characters. Types: { 'NOTE': ['note', 'notes'], 'WARN': ['warn', 'warning', 'warns', 'warnings'], 'MUTE': ['mute', 'mutes', 'tempmute', 'tempmutes'], 'UNMUTE': ['unmute', 'unmutes', 'untempmute', 'untempmutes'], 'KICK': ['kick', 'kicks'], 'SOFTBAN': ['softban', 'softbans'], 'BAN': ['ban', 'bans', 'hardban', 'hardbans'], 'UNBAN': ['unban', 'unbans', 'unhardban', 'unhardbans'], 'VCMUTE': ['vcmute', 'vcmutes', 'vctempmute', 'vctempmutes'], 'VCUNMUTE': ['vcunmute', 'vcunmutes', 'vctempunmute', 'vctempunmutes'], 'VCKICK': ['vckick', 'vckicks'], 'VCBAN': ['vcban', 'vcbans'], 'VCUNBAN': ['vcunban', 'vcunbans'], 'PRUNE': ['prune', 'prunes', 'purge', 'purges'], 'SLOWMODE': ['slowmode', 'slowmodes'], 'ADDROLE': ['addrole', 'addroles', 'roleadd', 'roleadds'], 'REMOVEROLE': ['removerole', 'removeroles', 'roleremove', 'roleremoves'], 'NICKNAME': ['nickname', 'nicknames', 'dehoist', 'dehoists'], 'LOCKDOWN': ['lockdown', 'lockdowns'], 'UNLOCKDOWN': ['unlockdown', 'unlockdowns'] } }; 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', 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 }) { if(args.export) return this._exportLogs(message, Boolean(args.private)); 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(Constants.Types)) { 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` } }; 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 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) }); } 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;