galactic-bot/structure/moderation/ModerationManager.js
2020-06-15 17:15:13 -05:00

223 lines
8.0 KiB
JavaScript

const { stripIndents } = require('common-tags');
const { Collection, Util } = require('../../util/');
const { Unmute, Unban } = require('./infractions/');
const Constants = {
MaxTargets: 10, //10+(10*premium-tier), theoretical max = 40
Infractions: {
UNMUTE: Unmute,
UNBAN: Unban
},
Opposites: {
MUTE: "UNMUTE",
BAN: "UNBAN"
}
};
class ModerationManager {
constructor(client) {
this.client = client;
this.expirations = new Collection();
}
async initialize() {
this.client.transactionHandler.send({
provider: 'mongodb',
request: {
collection: 'infractions',
type: 'find',
query: {
duration: { $gt: 0 },
guild: { $in: this.client.guilds.cache.keyArray() },
expired: false
} //should definitely filter this more...
}
}).then((results) => {
this.client.logger.debug(`Filtering ${results.length} infractions for expirations.`);
this._handleExpirations(results);
});
}
async handleInfraction(Infraction, message, { targets, reason, duration }) {
const maxTargets = Constants.MaxTargets + message.guild.premium*Constants.MaxTargets;
if(targets.length > maxTargets) {
return message.respond(stripIndents`${message.format('MODERATIONMANAGER_INFRACTION_MAXTARGETS', { maxTargets, type: Infraction.targetType })}
${maxTargets < 40 ? message.format('MODERATIONMANAGER_INFRACTION_MAXTARGETSALT') : ''}`, { emoji: 'failure' });
}
const silent = Boolean(message.guild._settings.silent || message.arguments.silent);
this.client.logger.debug(`Silent infraction: ${silent}`);
const promises = [];
for(const target of targets) {
promises.push(new Infraction(this.client, {
executor: message.member,
guild: message.guild,
channel: message.channel,
arguments: message.arguments,
message,
target,
reason,
duration,
silent
}).execute());
}
const responses = await Promise.all(promises);
let success = Boolean(responses.some((r) => !r.error));
const succeeded = responses.filter((r) => !r.error);
const failed = responses.filter((r) => r.error);
const succeededTargets = succeeded.map((s) => s.infraction.target);
const actions = await this._handleArguments(message, succeededTargets); //Handle prune arguments, etc. ONLY IF INFRACTION SUCCEEDS.
//NOTE: I'm not translating: infraction types (KICK, MUTE, ETC.), infraction target types (channel(s), user(s)),
/* Message Handling */
const { dictionary, targetType } = responses[0].infraction;
//Handle fatal errors, if necessary.
const fatals = failed.filter((f) => f.fatal);
if(fatals.length > 0) {
const [ error ] = fatals;
return message.respond(error.reason, { emoji: 'failure' });
}
let string = "";
if(success && !silent) {
string = message.format('MODERATIONMANAGER_INFRACTION_SUCCESS', {
infraction: dictionary.past,
targetType: `${targetType}${succeeded.length > 1 ? 's' : ''}`,
target: succeeded.map((s) => `**${Util.escapeMarkdown(s.infraction.targetName)}**`).join(', '),
action: actions.prune ? ` and pruned \`${actions.prune}\` message${actions.prune > 1 ? 's' : ''}` : ''
});
} else if((silent && failed.length > 0)) {
if(silent) success = false;
const format = failed.length === 1 ? "MODERATIONMANAGER_INFRACTION_SINGULARFAIL" : "MODERATIONMANAGER_INFRACTION_MULTIPLEFAIL";
string = message.format(format, {
infraction: dictionary.present,
targetType: `${targetType}${failed.length > 1 ? 's' : ''}`,
target: failed.length === 1 ? `**${Util.escapeMarkdown(failed[0].infraction.targetName)}**` : failed.map((f) => `**${f.infraction.targetName}**`).join(', '),
reason: failed[0].reason
});
}
if((success && failed.length > 0)
|| (!success && failed.length > 1)) {
for(const fail of failed) {
string += `\n${message.format('MODERATIONMANAGER_INFRACTION_FAIL', {
infraction: dictionary.present,
target: Util.escapeMarkdown(fail.infraction.targetName),
reason: fail.reason
})}`;
}
}
if(string) message.respond(string, { emoji: success ? 'success' : 'failure' });
return succeeded;
}
async _handleArguments(message, targets) {
const actions = {
prune: async (message, argument, targets) => {
const users = targets.map((t) => t.id);
let messages = await message.channel.messages.fetch({
limit: argument.value
});
messages = messages.filter((m) => users.includes(m.author.id));
try {
await message.channel.bulkDelete(messages, true);
} catch(err) {} //eslint-disable-line no-empty
return messages.size;
}
};
const responses = {};
for(const arg of Object.values(message.arguments)) {
if(actions[arg.name]) {
let action = actions[arg.name](message, arg, targets);
if(action instanceof Promise) action = await action;
responses[arg.name] = action;
}
}
return responses;
}
async _handleExpirations(infractions = []) {
const currentDate = Date.now();
const resolve = async (i) => {
const undoClass = Constants.Infractions[Constants.Opposites[i.type]];
if(!undoClass) return false;
const guild = this.client.guilds.resolve(i.guild);
await guild.settings(); //just incase
let target = null;
if(i.targetType === 'user') {
target = await guild.members.resolve(i.target);
if(!target) {
try {
target = await guild.members.fetch(i.target);
} catch(e) {} //eslint-disable-line no-empty
//Shouldn't attempt to fetch users.
}
} else if(i.targetType === 'channel') {
target = guild.channels.resolve(i.target);
}
if(!target) {
this.client.logger.debug(`User left the guild..? Unable to find a guild member.\n${i}`);
return false;
}
const executor = guild.members.resolve(i.executor) || guild.me;
const infrac = await new undoClass(this.client, {
reason: `AUTO-${Constants.Opposites[i.type]} from Case ${i.case}`,
channel: guild.channels.resolve(i.channel),
hyperlink: i.logMessage && i.moderationLogs ? `https://discord.com/channels/${i.guild}/${i.moderationLog}/${i.logMessage}` : null,
data: i.data,
guild,
target,
executor
}).execute();
return true;
};
for(const infraction of infractions) {
const expiration = infraction.timestamp + (infraction.duration*1000);
if(expiration-currentDate < 0) {
await resolve(infraction);
continue;
}
this.expirations.set(infraction.id, {
timeout: setTimeout(() => {
resolve(infraction);
}, expiration-currentDate),
infraction
});
}
}
}
module.exports = ModerationManager;