forked from Galactic/galactic-bot
infraction resolving
This commit is contained in:
parent
9b85ca9585
commit
303f1dfe81
44
src/structure/components/commands/moderation/Resolve.js
Normal file
44
src/structure/components/commands/moderation/Resolve.js
Normal file
@ -0,0 +1,44 @@
|
||||
const { SlashCommand, Infraction } = require("../../../interfaces");
|
||||
|
||||
class ResolveCommand extends SlashCommand {
|
||||
|
||||
constructor(client) {
|
||||
super(client, {
|
||||
name: 'resolve',
|
||||
description: 'Resolve infraction(s)',
|
||||
module: 'moderation',
|
||||
memberPermissions: ['MANAGE_GUILD'],
|
||||
options: [{
|
||||
name: 'case',
|
||||
type: 'INTEGER',
|
||||
description: 'Case ID (Integer)',
|
||||
required: true,
|
||||
minimum: 0
|
||||
}, {
|
||||
name: 'reason',
|
||||
description: 'Reason for resolving case',
|
||||
type: 'STRING'
|
||||
}, {
|
||||
name: 'notify',
|
||||
description: 'Attempt to notify the user about the resolve, may not always be possible',
|
||||
type: 'BOOLEAN'
|
||||
}] // Potentially add another option to enable a range of cases
|
||||
});
|
||||
}
|
||||
|
||||
async execute(invoker, { case: caseId, reason, notify }) {
|
||||
|
||||
const { guild, member } = invoker;
|
||||
const infraction = await new Infraction(this.client, { guild, case: caseId.value }).fetch(true);//.catch(() => null);
|
||||
if (!infraction) return { emoji: 'failure', index: 'INFRACTION_NOT_FOUND' };
|
||||
if (infraction.resolved) return { emoji: 'failure', index: 'INFRACTION_ALREADY_RESOLVED' };
|
||||
|
||||
const response = await infraction.resolve(member, reason?.value || null, notify?.value || false);
|
||||
if (response?.error) return { emoji: 'warning', index: 'COMMAND_RESOLVE_WARNING', params: { message: response.message } };
|
||||
return { emoji: 'success', index: 'COMMAND_RESOLVE_SUCCESS' };
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = ResolveCommand;
|
@ -8,7 +8,8 @@ class BanInfraction extends Infraction {
|
||||
|
||||
constructor(client, opts = {}) {
|
||||
|
||||
super(client, {
|
||||
if (opts.fetched) super(client, opts);
|
||||
else super(client, {
|
||||
targetType: 'USER',
|
||||
type: opts.type,
|
||||
guild: opts.guild,
|
||||
@ -61,7 +62,7 @@ class BanInfraction extends Infraction {
|
||||
|
||||
let alreadyBanned = null;
|
||||
try {
|
||||
alreadyBanned = await this.guild.fetchBan(this.member.id);
|
||||
alreadyBanned = await this.guild.bans.fetch(this.member.id);
|
||||
} catch (e) { } //eslint-disable-line no-empty
|
||||
|
||||
if (alreadyBanned) return super._fail('C_BAN_ALREADYBANNED');
|
||||
@ -70,6 +71,25 @@ class BanInfraction extends Infraction {
|
||||
|
||||
}
|
||||
|
||||
async resolve(...args) {
|
||||
const member = await this.guild.memberWrapper(this.targetId);
|
||||
const callback = await member.getCallback(this.type);
|
||||
if (callback) this.client.moderationManager.removeCallback(callback);
|
||||
|
||||
const banned = await this.guild.bans.fetch(this.targetId).catch(() => null);
|
||||
if (banned) {
|
||||
const inf = await this.client.mongodb.infractions.findOne({
|
||||
type: this.type, guild: this.guild.id, target: this.targetId
|
||||
}, {
|
||||
sort: { timestamp: -1 },
|
||||
projection: { id: 1 }
|
||||
});
|
||||
// Don't unban unless the resolved case is the same one that banned them
|
||||
if(inf.id === this.id) await this.guild.bans.remove(this.targetId, `Case ${this.case} resolve`);
|
||||
}
|
||||
return super.resolve(...args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = BanInfraction;
|
@ -8,26 +8,29 @@ class MuteInfraction extends Infraction {
|
||||
|
||||
constructor(client, opts = {}) {
|
||||
|
||||
super(client, {
|
||||
targetType: 'USER',
|
||||
type: opts.type,
|
||||
invoker: opts.invoker,
|
||||
executor: opts.executor.user,
|
||||
target: opts.target.user,
|
||||
reason: opts.reason,
|
||||
guild: opts.guild,
|
||||
channel: opts.channel,
|
||||
arguments: opts.arguments,
|
||||
silent: opts.silent,
|
||||
duration: opts.duration,
|
||||
points: opts.points,
|
||||
expiration: opts.expiration,
|
||||
data: opts.data,
|
||||
hyperlink: opts.hyperlink
|
||||
});
|
||||
if (opts.fetched) super(client, opts);
|
||||
else {
|
||||
super(client, {
|
||||
targetType: 'USER',
|
||||
type: opts.type,
|
||||
invoker: opts.invoker,
|
||||
executor: opts.executor.user,
|
||||
target: opts.target.user,
|
||||
reason: opts.reason,
|
||||
guild: opts.guild,
|
||||
channel: opts.channel,
|
||||
arguments: opts.arguments,
|
||||
silent: opts.silent,
|
||||
duration: opts.duration,
|
||||
points: opts.points,
|
||||
expiration: opts.expiration,
|
||||
data: opts.data,
|
||||
hyperlink: opts.hyperlink
|
||||
});
|
||||
|
||||
if (!(opts.target instanceof GuildMember)) throw new Error('Guild member required');
|
||||
this.member = opts.target;
|
||||
if (!(opts.target instanceof GuildMember)) throw new Error('Guild member required');
|
||||
this.member = opts.target;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -47,7 +50,7 @@ class MuteInfraction extends Infraction {
|
||||
this.member.roles.add(role, this._reason);
|
||||
} catch (e) {
|
||||
this.client.logger.debug(`Mute fail, type 1:\n${e.stack}`);
|
||||
return this._fail('C_MUTE_1FAIL');
|
||||
return this._fail('COMMAND_MUTE_1FAIL');
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
@ -63,7 +66,7 @@ class MuteInfraction extends Infraction {
|
||||
], this._reason);
|
||||
} catch (error) {
|
||||
this.client.logger.error(`Mute infraction failed to calculate removeable roles, might want to check this out.\n${error.stack || error}`);
|
||||
return this._fail('C_MUTE_2FAIL');
|
||||
return this._fail('COMMAND_MUTE_2FAIL');
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
@ -76,7 +79,7 @@ class MuteInfraction extends Infraction {
|
||||
r.id === this.guild.id), this._reason);
|
||||
} catch (error) {
|
||||
this.client.logger.error(`Mute infraction failed to calculate removeable roles, might want to check this out.\n${error.stack || error}`);
|
||||
return this._fail('C_MUTE_3FAIL');
|
||||
return this._fail('COMMAND_MUTE_3FAIL');
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -89,7 +92,6 @@ class MuteInfraction extends Infraction {
|
||||
|
||||
const callback = this.client.moderationManager.callbacks.filter((c) => c.infraction.type === 'MUTE'
|
||||
&& c.infraction.target === this.target.id).first();
|
||||
// console.log(callback);
|
||||
|
||||
if (callback) {
|
||||
this.data.removedRoles = [...new Set([...this.data.removedRoles, ...callback.infraction.data.removedRoles])];
|
||||
@ -124,6 +126,60 @@ class MuteInfraction extends Infraction {
|
||||
|
||||
}
|
||||
|
||||
async resolve(...args) {
|
||||
const inf = await this.client.mongodb.infractions.findOne({
|
||||
type: this.type, guild: this.guild.id, target: this.targetId
|
||||
}, {
|
||||
sort: { timestamp: -1 },
|
||||
projection: { id: 1 }
|
||||
});
|
||||
let message = null,
|
||||
error = false;
|
||||
|
||||
const { removedRoles, muteType, muteRole } = this.data;
|
||||
const member = await this.guild.memberWrapper(this.targetId);
|
||||
const callback = await member.getCallback(this.type);
|
||||
if (callback) this.client.moderationManager.removeCallback(callback);
|
||||
|
||||
if (inf.id === this.id && member) {
|
||||
const reason = `Case ${this.case} resolve`;
|
||||
const roles = [...new Set([...member.roles.cache.map((r) => r.id), ...removedRoles])];
|
||||
switch (muteType) {
|
||||
case 0:
|
||||
try {
|
||||
await member.roles.remove(muteRole, reason);
|
||||
} catch (e) {
|
||||
this.client.logger.debug(`Mute fail, type 1:\n${e.stack}`);
|
||||
error = true;
|
||||
message = this.guild.format('INFRACTION_RESOLVE_MUTE_FAIL1');
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const index = roles.indexOf(muteRole);
|
||||
if (index >= 0) roles.splice(index, 1);
|
||||
try {
|
||||
await member.roles.set(roles, reason);
|
||||
} catch (e) {
|
||||
this.client.logger.debug(`Mute fail, type 2:\n${e.stack}`);
|
||||
error = true;
|
||||
message = this.guild.format('INFRACTION_RESOLVE_MUTE_FAIL23');
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
try {
|
||||
await member.roles.set(roles, reason);
|
||||
} catch (e) {
|
||||
this.client.logger.debug(`Mute fail, type 3:\n${e.stack}`);
|
||||
error = true;
|
||||
message = this.guild.format('INFRACTION_RESOLVE_MUTE_FAIL23');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { result: await super.resolve(...args), message, error };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = MuteInfraction;
|
@ -56,7 +56,7 @@ class Infraction {
|
||||
|
||||
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._hyperlink = data.hyperlink || null; // To overwrite hyperlink (if it's from a callback)
|
||||
|
||||
this.modLogMessageId = null;
|
||||
this.dmLogMessageId = null;
|
||||
@ -106,7 +106,7 @@ class Infraction {
|
||||
}
|
||||
}
|
||||
|
||||
if (dminfraction.enabled) {
|
||||
if (dminfraction.enabled && !this.silent) {
|
||||
if (this.targetType === 'USER') {
|
||||
let message = dminfraction.messages[this.type] || dminfraction.messages.default;
|
||||
if (!message) message = '';
|
||||
@ -147,20 +147,35 @@ class Infraction {
|
||||
});
|
||||
}
|
||||
|
||||
hyperlink(bool = false) {
|
||||
if (bool) return `https://discord.com/channels/${this.guildId}/${this.modlogId}/${this.modLogMessageId}`;
|
||||
hyperlink(modLogMessage = false) {
|
||||
if(this._hyperlink) return this._hyperlink;
|
||||
if (modLogMessage) return `https://discord.com/channels/${this.guildId}/${this.modlogId}/${this.modLogMessageId}`;
|
||||
return `https://discord.com/channels/${this.guildId}/${this.channelId}/${this.messageId}`;
|
||||
}
|
||||
|
||||
_embed(dm) {
|
||||
|
||||
const embed = {
|
||||
author: {
|
||||
name: `${this.target.displayName || this.target.username || this.target.name} (${this.targetId})`,
|
||||
icon_url: this.targetIcon //eslint-disable-line camelcase
|
||||
},
|
||||
timestamp: this.timestamp,
|
||||
color: this.color,
|
||||
footer: {
|
||||
text: `》 Case ${this.case}`
|
||||
},
|
||||
fields: []
|
||||
};
|
||||
let description = "";
|
||||
|
||||
description += `${this.guild.format('INFRACTION_DESCRIPTION', {
|
||||
type: this.dictionary.past.toUpperCase(),
|
||||
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 })
|
||||
this.reason, { italic: false, underline: false, strikethrough: false }),
|
||||
// resolved: this.resolved ? this.guild.format('INFRACTION_RESOLVED') : ''
|
||||
})}`;
|
||||
|
||||
if (this.duration) {
|
||||
@ -175,29 +190,30 @@ class Infraction {
|
||||
})}`;
|
||||
}
|
||||
|
||||
// Function implemented in subclasses for additional case data
|
||||
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}`
|
||||
})}`;
|
||||
if (this.resolved) {
|
||||
description += '\n' + this.guild.format('INFRACTION_RESOLVED');
|
||||
if (!dm) {
|
||||
const resolveReason = this.changes.sort((a, b) => b.timestamp - a.timestamp).find((change) => change.type === 'RESOLVE');
|
||||
embed.fields.push({
|
||||
name: this.guild.format('INFRACTION_RESOLVE_REASON'),
|
||||
value: resolveReason.reason || this.guild.format(`INFRACTION_RESOLVE_NO_REASON`)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const embed = {
|
||||
author: {
|
||||
name: `${this.target.displayName || this.target.username || this.target.name} (${this.targetId})`,
|
||||
icon_url: this.targetIcon //eslint-disable-line camelcase
|
||||
},
|
||||
timestamp: this.timestamp,
|
||||
color: this.color,
|
||||
footer: {
|
||||
text: `》 Case ${this.case}`
|
||||
},
|
||||
description
|
||||
};
|
||||
if (!dm && (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}`
|
||||
})}`;
|
||||
}
|
||||
|
||||
embed.description = description;
|
||||
|
||||
return embed;
|
||||
|
||||
@ -398,12 +414,44 @@ class Infraction {
|
||||
this.changes.push(log);
|
||||
}
|
||||
|
||||
async fetch() { //Data from Mongodb (id-based data)
|
||||
async resolve(staff, reason, notify) {
|
||||
this.changes.push({
|
||||
type: 'RESOLVE',
|
||||
staff: staff.id,
|
||||
timestamp: Date.now(),
|
||||
reason
|
||||
});
|
||||
this.resolved = true;
|
||||
await this.updateMessages();
|
||||
await this.save();
|
||||
if(notify && this.target) await this.target.send(`Your infraction **#${this.case}** on **${this.guild.name}** was resolved.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} [resolveToRightClass=false] Whether the function should instead return
|
||||
* @return {*}
|
||||
* @memberof Infraction
|
||||
*/
|
||||
async fetch(resolveToRightClass = false) { //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');
|
||||
if (!data) throw new Error('No such case');
|
||||
if (resolveToRightClass) {
|
||||
const InfClass = this.client.moderationManager.infractionClasses[data.type];
|
||||
const infraction = new InfClass(this.client, { fetched: true, guild: this.guild, case: this.case });
|
||||
await infraction._patch(data);
|
||||
return infraction;
|
||||
}
|
||||
|
||||
this._patch(data);
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
async _patch(data) {
|
||||
|
||||
this._mongoId = ObjectId(data._id);
|
||||
this._callbacked = data._callbacked;
|
||||
this._fetched = true;
|
||||
|
||||
this.targetType = data.targetType;
|
||||
this.targetId = data.target;
|
||||
@ -416,7 +464,6 @@ class Infraction {
|
||||
|
||||
this.timestamp = data.timestamp;
|
||||
this.duration = data.duration;
|
||||
// this.callback = data.callback;
|
||||
|
||||
this.expiration = data.expiration;
|
||||
this.points = data.points;
|
||||
@ -439,10 +486,7 @@ class Infraction {
|
||||
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) {
|
||||
|
Loading…
Reference in New Issue
Block a user