265 lines
9.6 KiB
JavaScript
265 lines
9.6 KiB
JavaScript
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: '<date>',
|
|
type: 'DATE',
|
|
types: ['FLAG'],
|
|
required: true
|
|
},
|
|
{
|
|
name: 'after', //Search for moderation actions after x
|
|
usage: '<date>',
|
|
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<infractions.length; i++) {
|
|
const infraction = infractions[i];
|
|
let target = null;
|
|
if(infraction.targetType === 'USER') {
|
|
target = await this.client.resolver.resolveUser(infraction.target);
|
|
} else {
|
|
target = await this.client.resolver.resolveChannel(infraction.target, true, message.guild);
|
|
}
|
|
|
|
const executor = await this.client.resolver.resolveUser(infraction.executor);
|
|
|
|
let string = stripIndents`**Target:** ${Util.escapeMarkdown(target.display)}${args.verbose ? ` (${target.id})` : ''}
|
|
**Moderator:** ${executor ? `${Util.escapeMarkdown(executor.tag)}${args.verbose ? ` (${infraction.executor})` : ''}` : infraction.executor}`;
|
|
|
|
if(infraction.data.roleIds) {
|
|
string += `\n${message.guild.format('INFRACTION_DESCRIPTIONROLES', {
|
|
plural: infraction.data.roleIds.length === 1 ? '' : 's',
|
|
roles: priv ? infraction.data.roleNames.join(', ') : infraction.data.roleIds.map((r) => `<@&${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; |