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;