galactic-bot/structure/moderation/interfaces/Infraction.js

354 lines
13 KiB
JavaScript

const {
InfractionTargetTypes,
InfractionDictionary,
InfractionColors
} = require('../../../util/Constants.js');
const { Util } = require('../../../util/');
const Constants = {
MaxCharacters: 1024, // Max embed description is 2048 characters, however some of those description characters are going to usernames, types, filler text, etc.
RemovedInfractions: ['BAN', 'SOFTBAN', 'KICK']
};
class Infraction {
constructor(client, data = {}) {
this.client = client;
this.type = data.type || null;
this.id = null;
this.case = null;
this.message = data.message; //NOT REQUIRED
this.arguments = data.arguments || {};
this.guildId = data.guild ? data.guild.id : null;
this.guild = data.guild || null;
this.channelId = data.channel ? data.channel.id : null;
this.channel = data.channel || null;
this.messageId = data.message ? data.message.id : null;
this.message = data.message || null;
this.targetId = data.target ? data.target.id : null;
this.target = data.target || null;
this.executorId = data.executor ? data.executor.id : null;
this.executor = data.executor || null;
this.targetType = data.targetType && InfractionTargetTypes.includes(data.targetType) ? data.targetType : 'USER';
this.resolved = false;
this.duration = isNaN(data.duration) ? null : data.duration; //How long the action will last. Must be in milliseconds.
this.callback = isNaN(data.duration) ? null : Date.now() + data.duration * 1000; //At what epoch(?) time it will callback.
this.reason = data.reason || 'N/A';
this.points = data.points || 0;
this.expiration = isNaN(data.expiration) ? null : Date.now() + data.expiration * 1000;
this.totalPoints = 0;
this.data = data.data || {}; //Miscellaneous data that may need to be saved for future use.
this.flags = data.arguments ? Object.keys(data.arguments) : [];
this.hyperlink = data.hyperlink || null; // To overwrite hyperlink (if it's from a callback)
this.modlogMessageId = null;
this.dmlogMessageId = null;
this.modlogId = null;
this.changes = [];
this.timestamp = Date.now();
this._callbacked = Boolean(data._callbacked);
this._fetched = Boolean(data);
}
async handle() {
//NOTE: Temporary logging, making sure there isn't any other issues.
if(typeof this.reason !== 'string') this.client.logger.error(`Infraction type ${this.type} was passed an invalid type to the reason.`);
const { moderationLog, dmInfraction } = await this.guild.settings();
//Increment CaseId, should only be called if making a new infraction.
this.guild._settings.caseId++;
this.case = this.guild._settings.caseId;
await this.guild._updateSettings({
caseId: this.case
});
/* Logging */
if(moderationLog.channel) {
if(moderationLog.infractions.includes(this.type)) {
this._moderationLog = await this.client.resolver.resolveChannel(moderationLog.channel, true, this.guild);
if(!this._moderationLog) return undefined;
this.modlogId = this._moderationLog.id;
try {
this._logMessage = await this._moderationLog.send('', { embed: this._embed() });
this.modlogMessageId = this._logMessage.id;
} catch(e) {} //eslint-disable-line no-empty
} else {
this.client.logger.debug(`Did not log infraction ${this.type} because it is not in the infractions.`);
}
}
if(dmInfraction.enabled) {
if(this.targetType === 'USER') {
let message = dmInfraction.messages[this.type] || dmInfraction.messages.default;
if(!message) message = '';
message = message
.replace(/\{(guild|server)\}/ugim, this.guild.name)
.replace(/\{user\}/ugim, this.target.tag)
.replace(/\{infraction\}/ugim, this.dictionary.past)
.replace(/\{from\|on\}/ugim, Constants.RemovedInfractions.includes(this.type) ? 'from' : 'on'); //add more if you want i should probably add a better system for this...
try {
const logMessage = await this.target.send(message, {
embed: this._embed(true)
});
this.dmlogMessageId = logMessage.id;
} catch(e) {} //eslint-disable-line no-empty
}
}
if(this.duration) {
await this.client.moderationManager._handleExpirations([this.json]);
}
/* LMAOOOO PLEASE DONT JUDGE ME */
if(this.data.roles) {
delete this.data.roles;
}
return this.save();
}
async save() {
return this.client.storageManager.mongodb.infractions.insertOne(this.json)
.catch((error) => {
this.client.logger.error(`There was an issue saving infraction data to the database.\n${error.stack || error}`);
});
}
hyperlink(bool = false) {
if(bool) return `https://discord.com/channels/${this.guildId}/${this.modlogId}/${this.modlogMessageId}`;
return `https://discord.com/channels/${this.guildId}/${this.channelId}/${this.messageId}`;
}
_embed(dm) {
let description = "";
description += `${this.guild.format('INFRACTION_DESCRIPTION', {
type: this.dictionary.past.toUpperCase(),
moderator: `${Util.escapeMarkdown(this.executor.tag)}`,
reason: Util.escapeMarkdown(this.reason.length > Constants.MaxCharacters ? `${this.reason.substring(0, Constants.MaxCharacters-3)}...` : this.reason, { italic: false, underline: false, strikethrough: false })
})}`;
if(this.duration) {
description += `\n${this.guild.format('INFRACTION_DESCRIPTIONDURATION', { duration: Util.duration(this.duration) })}`;
}
if(this.points && this.points > 0) { //TODO: Add expiration to INFRACTION_DESCRIPTIONPOINTS
description += `\n${this.guild.format('INFRACTION_DESCRIPTIONPOINTS', { points: this.points, total: this.totalPoints, expires: Util.duration(this.pointsExpiration) })}`;
}
if(this.description && this.description instanceof Function) {
description += this.description(dm);
}
if(!this.silent && (this.message || this.hyperlink)) {
description += `\n${this.guild.format('INFRACTION_DESCRIPTIONJUMPTO', { name: this.hyperlink ? 'Case' : 'Message', link: this.hyperlink ? this.hyperlink : `https://discord.com/channels/${this.guildId}/${this.channelId}/${this.messageId}` })}`;
}
const blah = {
author: {
name: `${this.target.display} (${this.targetId})`,
icon_url: this.targetIcon //eslint-disable-line camelcase
},
timestamp: this.timestamp,
color: this.color,
footer: {
text: `》 Case ${this.case}`
},
description
};
return blah;
}
get json() {
return {
id: `${this.guildId}:${this.case}`,
guild: this.guildId,
channel: this.channelId,
channelName: this.channel?.display,
message: this.messageId,
executor: this.executorId,
executorTag: this.executor.display,
target: this.targetId,
targetTag: this.target.display,
targetType: this.targetType,
type: this.type,
case: this.case,
timestamp: this.timestamp,
duration: this.duration,
callback: this.callback,
reason: this.reason,
data: this.data,
flags: this.flags,
points: this.points,
expiration: this.expiration,
modLogMessage: this.modlogMessageId,
dmLogMessage: this.dmlogMessageId,
modLogChannel: this.modlogId,
resolved: this.resolved,
changes: this.changes,
_callbacked: this._callbacked || false
};
}
get targetIcon() {
return this.targetType === 'USER'
? this.target.displayAvatarURL()
: this.guild.iconURL();
}
get actions() {
const actions = [];
for(const argument of Object.values(this.arguments)) {
if(['silent', 'prune', 'force'].includes(argument)) actions.push(argument);
}
return actions;
}
get _reason() {
let str = `[${this.type}][targetId:${this.target.id}] Executed by ${this.executor.tag} (${this.executor.id}) because: ${this.reason}`;
if(str.length > 512) str = `${this.reason.substring(0, 509)}...`;
return str;
}
get dictionary() {
return InfractionDictionary[this.type];
}
get color() {
return InfractionColors[this.type];
}
//Super Functions
_succeed() {
return {
error: false,
infraction: this
};
}
_fail(message, fatal = false) {
return {
error: true,
infraction: this,
reason: message,
fatal
};
}
verify() {
return this._verify();
}
async _verify() {
const { protection } = await this.guild.settings();
if (this.executor.id === this.guild.ownerID) return this._succeed();
if (this.guild && protection.enabled && this.targetType === 'USER') {
const executor = await this.guild.members.fetch(this.executor.id).catch(() => {}); //eslint-disable-line no-empty-function
const target = await this.guild.members.fetch(this.target.id).catch(() => {}); //eslint-disable-line no-empty-function
if (!target) return this._succeed();
if(protection.type === 'position') {
const executorHighest = executor.roles.highest;
const targetHighest = target.roles.highest;
if (executorHighest.comparePositionTo(targetHighest) < 0) {
return this._fail('INFRACTION_PROTECTIONPOSITIONERROR');
}
} else if(protection.type === 'role') {
const contains = target.roles.cache.some((r) => protection.roles.includes(r.id));
if(contains) {
return this._fail('INFRACTION_PROTECTIONROLEERROR');
}
}
}
return this._succeed();
}
// async fetch() { //Data from Mongodb (id-based data)
// const data = await this.client.storageManager.mongodb.infractions.findOne({ id: this.id });
// if(!data) {
// this.client.logger.error(`Case ${this.id} is missing infraction data in database.`);
// return null;
// }
// if(data.guild) {
// let guild = null;
// try {
// guild = await this.client.guilds.fetch(data.guild);
// } catch(error) {
// this.client.logger.error(`Unable to fetch guild: ${data.guild}\n${error.stack || error}`);
// guild = null;
// }
// if(!guild) return null;
// if(data.targets) {
// this.targetIds = data.targets;
// for(const target of data.targets) {
// const fetchedTarget = await this._fetchTarget(target);
// if(fetchedTarget) this.targets.push(fetchedTarget);
// }
// }
// if(data.executor) {
// this.executorId = data.executor;
// const fetchedExecutor = await this._fetchTarget(data.executor, 'USER');
// if(fetchedExecutor) this.executor = fetchedExecutor;
// }
// }
// this.type = data.type;
// this.timestamp = data.timestamp;
// this.duration = data.duration;
// this.reason = data.reason;
// this.channelId = data.channel;
// this.resolved = data.resolved;
// this._callbacked = data._callbacked;
// this.dictionary = InfractionDictionary[this.type];
// this.color = InfractionColors[this.type];
// this.modlogMessageId = data.modlogMessageId;
// this.dmlogMessageId = data.dmlogMessageId;
// this._fetched = Boolean(data._fetched);
// return this;
// }
// async _fetchTarget(target, type = null) {
// type = type || this.targetType;
// let fetched = null;
// if(type === 'CHANNEL') {
// fetched = await this.client.resolver.resolveChannel(target, true, this.guild);
// } else if (type) {
// fetched = await this.client.resolver.resolveMember(target, true, this.guild);
// if(!fetched) {
// fetched = await this.client.resolver.resolveUser(target, true);
// }
// }
// return fetched || null;
// }
}
module.exports = Infraction;