infraction editing

This commit is contained in:
Erik 2022-04-30 17:25:58 +03:00
parent 9d36ed4981
commit 7a2fa4df3c
Signed by untrusted user: Navy.gif
GPG Key ID: 811EC0CD80E7E5FB
5 changed files with 288 additions and 77 deletions

View File

@ -171,6 +171,8 @@ exports.InfractionOpposites = {
REMOVEROLE: 'ADDROLE'
};
exports.TimedInfractions = Object.keys(exports.InfractionOpposites);
exports.InfractionColors = {
NOTE: 0xEBEBEB,
WARN: 0xffe15c,

View File

@ -433,5 +433,22 @@ No changes have been made to the case.
Please respond with the new reason.
Timeout in {time} seconds
[COMMAND_EDIT_NO_ARGS]
Must specify at least one property to edit.
[COMMAND_EDIT_NOT_FOUND]
Could not find the infraction.
Could not find the infraction.
[COMMAND_EDIT_ISSUES]
The command ran into the following errors:
{issues}
*Note that some of the edits were successful*
[COMMAND_EDIT_FAILURE]
Failed to edit the infraction.
The following errors occurred:
- {issues}
[COMMAND_EDIT_SUCCESS]
Successfully edited the infraction.

View File

@ -39,6 +39,24 @@ Failed to {infraction} {targetType} {target} because {reason}.
[INFRACTION_DESCRIPTIONJUMPTO]
**[Jump To {name}]({link})**
[INFRACTION_EDIT_DURATION_RESOLVED]
Cannot edit duration of a resolved infraction.
[INFRACTION_EDIT_DURATION_CALLEDBACK]
Cannot edit duration of an infraction whose timer expired.
[INFRACTION_EDIT_INVALID_TARGETTYPE]
Cannot edit expiration of an infraction with non-user target.
[INFRACTION_EDIT_MODPOINTS_NOT_ON]
Cannot edit modpoints as they are not enabled.
[INFRACTION_EDIT_EXPIRATION_NOT_ON]
Cannot edit expiration as modpoints are not enabled.
[INFRACTION_EDIT_DURATION_NOTTIMED]
Cannot edit duration of a non-timed infraction.
//Prune Command
[INFRACTION_DESCRIPTIONAMOUNT]
**Amount:** {amount}

View File

@ -0,0 +1,77 @@
const { SlashCommand, Infraction } = require("../../../interfaces");
class EditCommand extends SlashCommand {
constructor(client) {
super(client, {
name: 'edit',
description: 'Edit case data',
module: 'moderation',
showUsage: true,
guildOnly: true,
memberPermissions: ['MANAGE_MESSAGES'],
options: [{
name: 'case',
type: 'INTEGER',
description: 'Case ID (Integer)',
required: true,
minimum: 0
}, {
name: 'reason',
description: 'Give "long" as a reason to instead enter through a prompt to bypass length limit',
type: 'STRING'
}, {
name: 'points',
type: 'INTEGER',
description: 'New point value for case',
minimum: 0, maximum: 100
}, {
name: ['expiration', 'duration'],
type: 'TIME',
description: [
'New expiration for points, starts from the time the infraction was issued',
'Duration if the infraction is timed'
]
}]
});
}
async execute(invoker, { case: caseId, reason, points, expiration, duration }) {
if (!(reason || points || expiration || duration)) return { emoji: 'failure', index: 'COMMAND_EDIT_NO_ARGS' };
const { guild, member } = invoker;
// const data = await this.client.storageManager.mongodb.infractions.findOne({ id: `${guild.id}:${caseId.value}` });
const infraction = await new Infraction(this.client, { guild, case: caseId.value }).fetch().catch(() => null);
if(!infraction) return { emoji: 'failure', index: 'COMMAND_EDIT_NOT_FOUND' };
const time = 120;
if (reason?.value === 'long') reason = await invoker.promptMessage(invoker.format('COMMAND_EDIT_LONG', { time }), { time });
else if (reason) reason = reason.value;
const results = [];
if (reason) results.push(await infraction.editReason(member, reason));
if (points) results.push(await infraction.editPoints(member, points.value));
if (expiration) results.push(await infraction.editExpiration(member, expiration.value * 1000));
if (duration) results.push(await infraction.editDuration(member, duration.value * 1000));
const successful = results.filter((result) => !result).length;
const set = new Set(results.filter((result) => Boolean(result)).map((result) => result.index));
const messages = [];
for (const index of set) {
messages.push(invoker.format(index));
}
if (successful) {
await infraction.updateMessages();
await infraction.save();
}
if (messages.length && successful) return { emoji: 'warning', index: 'COMMAND_EDIT_ISSUES', params: { issues: messages.join('\n') } };
else if (!successful) return { emoji: 'failure', index: 'COMMAND_EDIT_FAILURE', params: { issues: messages.join('\n- ') } };
return { emoji: 'success', index: 'COMMAND_EDIT_SUCCESS' };
}
}
module.exports = EditCommand;

View File

@ -1,8 +1,10 @@
const { ObjectId } = require('mongodb');
const {
Constants: {
InfractionTargetTypes,
InfractionDictionary,
InfractionColors
InfractionColors,
TimedInfractions
}
} = require('../../constants');
@ -19,8 +21,8 @@ class Infraction {
this.client = client;
this.type = data.type || null;
this.id = null;
this.case = null;
// this.id = null;
this.case = data.case || null;
this.arguments = data.arguments || {};
@ -44,20 +46,20 @@ class Infraction {
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.callback = isNaN(data.duration) ? null : Date.now() + data.duration; //At what 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.expiration = data.expiration > 0 ? Date.now() + data.expiration : null; // Time when the points expire in milliseconds
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.modLogMessageId = null;
this.dmLogMessageId = null;
this.modlogId = null;
this.changes = [];
@ -65,12 +67,15 @@ class Infraction {
this.timestamp = Date.now();
this._callbacked = Boolean(data._callbacked);
this._fetched = Boolean(data);
this._fetched = false; //Boolean(data);
this._mongoId = null;
}
async handle() {
// Infraction was fetched from database, i.e. was already executed previously
if (this._fetched) throw new Error(`Cannot execute a fetched Infraction`);
//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.`);
@ -94,7 +99,7 @@ class Infraction {
this.modlogId = this._moderationLog.id;
try {
this._logMessage = await this._moderationLog.send({ embeds: [this._embed()] });
this.modlogMessageId = this._logMessage.id;
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.`);
@ -111,16 +116,17 @@ class Infraction {
.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, {
const logMessage = await this.target.send({
content: message,
embeds: [this._embed(true)]
});
this.dmlogMessageId = logMessage.id;
this.dmLogMessageId = logMessage.id;
} catch (e) { } //eslint-disable-line no-empty
}
}
if (this.duration) {
await this.client.moderationManager._handleExpirations([this.json]);
await this.client.moderationManager.handleCallbacks([this.json]);
}
/* LMAOOOO PLEASE DONT JUDGE ME */
@ -133,14 +139,16 @@ class Infraction {
}
async save() {
return this.client.storageManager.mongodb.infractions.insertOne(this.json)
const filter = { id: this.id };
if(this._mongoId) filter._id = this._mongoId;
return this.client.storageManager.mongodb.infractions.updateOne(filter, 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}`;
if (bool) return `https://discord.com/channels/${this.guildId}/${this.modlogId}/${this.modLogMessageId}`;
return `https://discord.com/channels/${this.guildId}/${this.channelId}/${this.messageId}`;
}
@ -156,7 +164,7 @@ class Infraction {
})}`;
if (this.duration) {
description += `\n${this.guild.format('INFRACTION_DESCRIPTIONDURATION', { duration: Util.duration(this.duration) })}`;
description += `\n${this.guild.format('INFRACTION_DESCRIPTIONDURATION', { duration: Util.humanise(Math.floor(this.duration / 1000)) })}`;
}
if (this.points && this.points > 0) { //TODO: Add expiration to INFRACTION_DESCRIPTIONPOINTS
@ -195,17 +203,22 @@ class Infraction {
}
get callback() {
if (isNaN(this.duration)) return null;
return this.duration + this.timestamp;
}
get json() {
return {
id: `${this.guildId}:${this.case}`,
id: this.id,
guild: this.guildId,
channel: this.channelId,
channelName: this.channel?.display,
channelName: this.channel?.name,
message: this.messageId,
executor: this.executorId,
executorTag: this.executor.display,
executorTag: this.executor.tag || this.executor.user?.tag,
target: this.targetId,
targetTag: this.target.display || this.target.username || this.target.name,
targetTag: this.target.tag || this.target.user?.tag || this.target.name,
targetType: this.targetType,
type: this.type,
case: this.case,
@ -217,8 +230,8 @@ class Infraction {
flags: this.flags,
points: this.points,
expiration: this.expiration,
modLogMessage: this.modlogMessageId,
dmLogMessage: this.dmlogMessageId,
modLogMessage: this.modLogMessageId,
dmLogMessage: this.dmLogMessageId,
modLogChannel: this.modlogId,
resolved: this.resolved,
changes: this.changes,
@ -226,6 +239,10 @@ class Infraction {
};
}
get id() {
return `${this.guildId}:${this.case}`;
}
get targetIcon() {
return this.targetType === 'USER'
? this.target.displayAvatarURL()
@ -299,68 +316,148 @@ class Infraction {
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;
// }
_error(reason) {
if (!this._fetched) throw new Error(reason || `Cannot edit an unfetched infraction`);
}
// 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;
// }
// }
async updateMessages() {
this._error(`Cannot update messages for unfetched infractions`);
if (this._dmLogMessage) await this._dmLogMessage.edit({ embeds: [this._embed(true)] });
if (this._modLogMessage) await this._modLogMessage.edit({ embeds: [this._embed()] });
}
// 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;
async editReason(staff, reason) {
this._error();
const log = {
reason: this.reason,
staff: staff.id,
timestamp: Date.now(),
type: 'REASON'
};
this.reason = reason;
this.changes.push(log);
}
// this.dictionary = InfractionDictionary[this.type];
// this.color = InfractionColors[this.type];
async editPoints(staff, points) {
this._error();
if (this.targetType !== 'USER') return { error: true, index: 'INFRACTION_EDIT_INVALID_TARGETTYPE' };
if (!this.guild._settings.modpoints.enabled) return { error: true, index: 'INFRACTION_EDIT_MODPOINTS_NOT_ON' };
const log = {
points: this.points,
staff: staff.id,
timestamp: Date.now(),
type: 'POINTS'
};
const diff = points - this.points;
this.points = points;
const userWrapper = await this.client.getUserWrapper(this.target.id);
this.totalPoints = await userWrapper.editPoints(this.guild, { diff, id: this.id, expiration: this.expiration });
this.changes.push(log);
}
// this.modlogMessageId = data.modlogMessageId;
// this.dmlogMessageId = data.dmlogMessageId;
async editExpiration(staff, expiration) {
this._error();
if (this.targetType !== 'USER') return { error: true, index: 'INFRACTION_EDIT_INVALID_TARGETTYPE' };
if (!this.guild._settings.modpoints.enabled) return { error: true, index: 'INFRACTION_EDIT_EXPIRATION_NOT_ON' };
// const now = Date.now();
const log = {
type: 'EXPIRATION',
staff: staff.id,
timestamp: Date.now(),
expiration: this.expiration ? this.expiration - this.timestamp : null
};
this.expiration = expiration + this.timestamp;
const userWrapper = await this.client.getUserWrapper(this.target.id);
this.totalPoints = await userWrapper.editExpiration(this.guild, {
id: this.id,
expiration: this.expiration,
points: this.points
});
this.changes.push(log);
}
// this._fetched = Boolean(data._fetched);
// return this;
async editDuration(staff, duration) {
this._error();
if (this.resolved) return { error: true, index: 'INFRACTION_EDIT_DURATION_RESOLVED' };
if (this._callbacked) return { error: true, index: 'INFRACTION_EDIT_DURATION_CALLEDBACK' };
if (!TimedInfractions.includes(this.type)) return { error: true, index: 'INFRACTION_EDIT_DURATION_NOTTIMED' };
const now = Date.now();
const log = {
type: 'DURATION',
staff: staff.id,
timestamp: now,
duration: this.duration
};
const member = await this.guild.memberWrapper(this.target);
const callback = await member.getCallback(this.type, true);
// }
this.duration = duration;
if(callback) await this.client.moderationManager.removeCallback(callback);
await this.client.moderationManager.handleCallbacks([this.json]);
// 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;
// }
this.changes.push(log);
}
async fetch() { //Data from Mongodb (id-based data)
const data = await this.client.storageManager.mongodb.infractions.findOne({ id: this.id });
if(!data) throw new Error('No such case');
this._mongoId = ObjectId(data._id);
this._callbacked = data._callbacked;
this.targetType = data.targetType;
this.targetId = data.target;
this.target = await this._fetchTarget(data.target);
this.executorId = data.executor;
this.executor = await this.client.users.fetch(data.executor).catch(() => null);
this.type = data.type;
this.timestamp = data.timestamp;
this.duration = data.duration;
// this.callback = data.callback;
this.expiration = data.expiration;
this.points = data.points;
this.reason = data.reason;
this.resolved = data.resolved;
this.changes = data.changes;
this.channelId = data.channel;
this.channel = { name: data.channelName };
this.data = data.data;
this.flags = data.flags;
this.modLogMessageId = data.modLogMessage;
this.dmLogMessageId = data.dmLogMessage;
this.messageId = data.message;
this.modlogId = data.modLogChannel;
const logChannel = await this.guild.resolveChannel(data.modLogChannel).catch(() => null);
if (logChannel) this._modLogMessage = await logChannel.messages.fetch(data.modLogMessage).catch(() => null);
const dm = await this.target.createDM().catch(() => null);
if (dm) this._dmLogMessage = await dm.messages.fetch(data.dmLogMessage).catch(() => null);
this._fetched = true;
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;
}
}