From 0a2a37f23e1cec16a98b1dade524830536fc65ea Mon Sep 17 00:00:00 2001 From: "Navy.gif" Date: Thu, 28 Dec 2023 03:00:02 +0200 Subject: [PATCH] Rewrite how modpoints are handled --- src/client/components/ModerationManager.ts | 41 +++- .../components/commands/moderation/Case.ts | 10 +- src/client/components/wrappers/UserWrapper.ts | 200 +++++++++--------- src/client/infractions/Unmute.ts | 4 +- src/client/interfaces/Infraction.ts | 55 +++-- src/constants/FilterPresets.ts | 1 + 6 files changed, 172 insertions(+), 139 deletions(-) diff --git a/src/client/components/ModerationManager.ts b/src/client/components/ModerationManager.ts index b35dfbe..79c23e9 100644 --- a/src/client/components/ModerationManager.ts +++ b/src/client/components/ModerationManager.ts @@ -23,7 +23,7 @@ import { Warn } from '../infractions/index.js'; import { CommandOption, Infraction as InfractionClass, Initialisable } from '../interfaces/index.js'; -import { InvokerWrapper, MemberWrapper, UserWrapper } from './wrappers/index.js'; +import { GuildWrapper, InvokerWrapper, MemberWrapper, UserWrapper } from './wrappers/index.js'; import { InfractionFail, InfractionSuccess } from '../../../@types/Infractions.js'; @@ -393,7 +393,7 @@ class ModerationManager implements Initialisable if (infraction.targetType === 'CHANNEL') return verification; - const oldPoints = await targetUser!.totalPoints(guild); + const oldPoints = await this.calculatePoints(targetUser!, guild); if (infraction.points === null) throw new Error('Null points when they should be defined'); const newPoints = oldPoints + infraction.points; @@ -498,16 +498,16 @@ class ModerationManager implements Initialisable { if (!targetUser) throw new Error('Missing target user for a user type infraction'); - response.infraction.totalPoints = await targetUser.totalPoints(guild); + response.infraction.totalPoints = await this.calculatePoints(targetUser, guild); response.infraction.totalPoints += points; } const result = await response.infraction.execute(); // Infraction doesn't have an ID until it is executed, hence this after execute - if (response.infraction.targetType === 'USER') - await targetUser!.totalPoints(guild, { - points, expiration, timestamp: response.infraction.timestamp, id: response.infraction.id - }); + // if (response.infraction.targetType === 'USER') + // await targetUser!.totalPoints(guild, { + // points, expiration, timestamp: response.infraction.timestamp, id: response.infraction.id + // }); return result; } @@ -537,7 +537,7 @@ class ModerationManager implements Initialisable if (!undoClass) return false; - const guild = this.#client.getGuildWrapper(i.guild!); + const guild = await this.#client.getGuildWrapper(i.guild!); if (!guild) throw new Error('Missing guild'); // await guild.settings(); //just incase @@ -636,7 +636,7 @@ class ModerationManager implements Initialisable { $set: { _callbacked: true } } ).catch((e) => { - this.#logger.error(`Error during update of infraction ${infraction.id}:\n${e.stack || e}\n${e.errInfo}`); + this.#logger.error(`Error during update of infraction ${infraction.id}:\n${e.stack || e}\n${inspect(e.errInfo)}`); }); const cb = this.#callbacks.get(infraction.id); if (cb) @@ -691,6 +691,29 @@ class ModerationManager implements Initialisable return result || null; } + async calculatePoints (user: UserWrapper, guild: GuildWrapper) + { + const [ result ] = await this.#client.mongodb.infractions.aggregate([{ + $match: { + target: user.id, + guild: guild.id, + resolved: false, + $or: [{ expiration: { $gt: Date.now() } }, { expiration: 0 }, { expiration: null }], + } + }, { + $group: { + _id: null, + points: { + $sum: '$points' + } + } + }]); + let points = 0; + if (result) + ({ points } = result); + return points; + } + } export default ModerationManager; \ No newline at end of file diff --git a/src/client/components/commands/moderation/Case.ts b/src/client/components/commands/moderation/Case.ts index df8a5a5..abea520 100644 --- a/src/client/components/commands/moderation/Case.ts +++ b/src/client/components/commands/moderation/Case.ts @@ -23,7 +23,7 @@ class CaseCommand extends SlashCommand minimum: 0, required: true }, { - name: [ 'export', 'verbose', 'changes', 'delete' ], // + name: [ 'raw', 'verbose', 'changes', 'delete' ], // type: CommandOptionType.BOOLEAN, description: [ 'Print out raw infraction data in JSON form', @@ -45,7 +45,7 @@ class CaseCommand extends SlashCommand async execute (invoker: InvokerWrapper, opts: CommandParams) { - const { id, verbose, export: exp, changes, delete: remove } = opts; + const { id, verbose, raw, changes, delete: remove } = opts; const guild = invoker.guild!; const infraction = await new Infraction(this.client, this.logger, { guild, case: id!.asNumber }).fetch(true).catch(() => null); @@ -62,9 +62,9 @@ class CaseCommand extends SlashCommand }); return { emoji: 'success', index: 'COMMAND_CASE_DELETED', params: { caseID: id!.asNumber } }; } - if (exp?.asString) - return `\`\`\`js\n${inspect(infraction)}\`\`\``; - if (changes?.asString) + if (raw?.asBool) + return `\`\`\`js\n${inspect(infraction.json)}\`\`\``; + if (changes?.asBool) return { embed: await this._listChanges(invoker, infraction.json) }; const target = infraction.targetType === 'USER' ? await this.client.resolver.resolveUser(infraction.targetId!) : await guild.resolveChannel(infraction.targetId); diff --git a/src/client/components/wrappers/UserWrapper.ts b/src/client/components/wrappers/UserWrapper.ts index 82fd9df..e188f32 100644 --- a/src/client/components/wrappers/UserWrapper.ts +++ b/src/client/components/wrappers/UserWrapper.ts @@ -1,10 +1,10 @@ import { ImageURLOptions, MessageCreateOptions, MessagePayload, User } from 'discord.js'; import DiscordClient from '../../DiscordClient.js'; import { UserSettings } from '../../../../@types/Settings.js'; -import GuildWrapper from './GuildWrapper.js'; +// import GuildWrapper from './GuildWrapper.js'; import { LoggerClient } from '@navy.gif/logger'; -type Expiry = {points: number, expiration: number, id: string} +// type Expiry = {points: number, expiration: number, id: string} class UserWrapper { @@ -12,12 +12,12 @@ class UserWrapper #user: User; #settings: UserSettings; - #points: { - [key: string]: { - expirations: Expiry[], - points: number - } - }; + // #points: { + // [key: string]: { + // expirations: Expiry[], + // points: number + // } + // }; #logger: LoggerClient; @@ -28,7 +28,7 @@ class UserWrapper this.#logger = client.createLogger({ name: `User: ${user.id}` }); this.#settings = {}; - this.#points = {}; + // this.#points = {}; } async settings (forceFetch = false) @@ -44,105 +44,105 @@ class UserWrapper return this.#settings; } - async fetchPoints (guild: GuildWrapper) - { - let index = this.#points[guild.id]; - if (index) - return Promise.resolve(index); - this.#points[guild.id] = { - expirations: [], - points: 0 - }; - index = this.#points[guild.id]; + // async fetchPoints (guild: GuildWrapper) + // { + // let index = this.#points[guild.id]; + // if (index) + // return Promise.resolve(index); + // this.#points[guild.id] = { + // expirations: [], + // points: 0 + // }; + // index = this.#points[guild.id]; - const filter = { - guild: guild.id, - target: this.id, - resolved: false, - // points: { $gte: 0 }, - // $or: [{ expiration: 0 }, { expiration: { $gte: now } }] - }; - const find = await this.#client.mongodb.infractions.find( - filter, - { projection: { id: 1, points: 1, expiration: 1 } } - ); + // const filter = { + // guild: guild.id, + // target: this.id, + // resolved: false, + // // points: { $gte: 0 }, + // // $or: [{ expiration: 0 }, { expiration: { $gte: now } }] + // }; + // const find = await this.#client.mongodb.infractions.find( + // filter, + // { projection: { id: 1, points: 1, expiration: 1 } } + // ); - if (find.length) - { - for (const { points: p, expiration, id } of find) - { - let points = p; - // Imported cases may have false or null - if (typeof points !== 'number') - points = 0; - if (expiration > 0) - { - index.expirations.push({ points, expiration, id }); - } - else - { - index.points += points; - } - } - } - return index; - } + // if (find.length) + // { + // for (const { points: p, expiration, id } of find) + // { + // let points = p; + // // Imported cases may have false or null + // if (typeof points !== 'number') + // points = 0; + // if (expiration > 0) + // { + // index.expirations.push({ points, expiration, id }); + // } + // else + // { + // index.points += points; + // } + // } + // } + // return index; + // } - async editPoints (guild: GuildWrapper, { id, diff, expiration }: {id: string, diff: number, expiration: unknown }) - { - const points = await this.fetchPoints(guild); - if (expiration) - { - const expiry = points.expirations.find((exp) => exp.id === id) as Expiry; - expiry.points += diff; - } - else - points.points += diff; - return this.totalPoints(guild); - } + // async editPoints (guild: GuildWrapper, { id, diff, expiration }: {id: string, diff: number, expiration: unknown }) + // { + // const points = await this.fetchPoints(guild); + // if (expiration) + // { + // const expiry = points.expirations.find((exp) => exp.id === id) as Expiry; + // expiry.points += diff; + // } + // else + // points.points += diff; + // return this.totalPoints(guild); + // } - async editExpiration (guild: GuildWrapper, { id, expiration, points }: { id: string, expiration: number, points: number }) - { - const index = await this.fetchPoints(guild); - const i = index.expirations.findIndex((exp) => exp.id === id); - if (i > -1) - index.expirations[i].expiration = expiration; - else - { - index.points -= points; - index.expirations.push({ id, points, expiration }); - } - return this.totalPoints(guild); - } + // async editExpiration (guild: GuildWrapper, { id, expiration, points }: { id: string, expiration: number, points: number }) + // { + // const index = await this.fetchPoints(guild); + // const i = index.expirations.findIndex((exp) => exp.id === id); + // if (i > -1) + // index.expirations[i].expiration = expiration; + // else + // { + // index.points -= points; + // index.expirations.push({ id, points, expiration }); + // } + // return this.totalPoints(guild); + // } - async totalPoints (guild: GuildWrapper, point?: { id: string, points: number, expiration: number, timestamp: number }) - { // point = { points: x, expiration: x, timestamp: x} - const index = await this.fetchPoints(guild); - const now = Date.now(); + // async totalPoints (guild: GuildWrapper, point?: { id: string, points: number, expiration: number, timestamp: number }) + // { // point = { points: x, expiration: x, timestamp: x} + // const index = await this.fetchPoints(guild); + // const now = Date.now(); - if (point) - { - if (point.expiration > 0) - { - index.expirations.push({ id: point.id, points: point.points, expiration: point.expiration + point.timestamp }); - } - else - { - index.points += point.points; - } - } + // if (point) + // { + // if (point.expiration > 0) + // { + // index.expirations.push({ id: point.id, points: point.points, expiration: point.expiration + point.timestamp }); + // } + // else + // { + // index.points += point.points; + // } + // } - let expirationPoints = index.expirations.map((e) => - { - if (e.expiration >= now) - return e.points; - return 0; - }); + // let expirationPoints = index.expirations.map((e) => + // { + // if (e.expiration >= now) + // return e.points; + // return 0; + // }); - if (expirationPoints.length === 0) - expirationPoints = [ 0 ]; - return expirationPoints.reduce((p, v) => p + v) + index.points; - } + // if (expirationPoints.length === 0) + // expirationPoints = [ 0 ]; + // return expirationPoints.reduce((p, v) => p + v) + index.points; + // } async updateSettings (data: UserSettings) { // Update property (upsert true) - updateOne diff --git a/src/client/infractions/Unmute.ts b/src/client/infractions/Unmute.ts index ffffa37..4fc5024 100644 --- a/src/client/infractions/Unmute.ts +++ b/src/client/infractions/Unmute.ts @@ -60,8 +60,6 @@ class UnmuteInfraction extends Infraction now = Date.now(); let callback = null; - // TODO: Make this not rely on a member wrapper - const memberWrapper = await this.guild.memberWrapper(this.member!).catch(() => null); if (Object.keys(this.data).length) { removedRoles = this.data.removedRoles!; @@ -70,6 +68,8 @@ class UnmuteInfraction extends Infraction } else { + // TODO: Make this not rely on a member wrapper + const memberWrapper = await this.guild.memberWrapper(this.member!).catch(() => null); callback = await memberWrapper?.getCallback('MUTE'); if (callback) { diff --git a/src/client/interfaces/Infraction.ts b/src/client/interfaces/Infraction.ts index 05e1fe3..0162781 100644 --- a/src/client/interfaces/Infraction.ts +++ b/src/client/interfaces/Infraction.ts @@ -143,7 +143,15 @@ class Infraction this.#silent = data.silent || false; this.#points = data.points || 0; - this.#expiration = typeof data.expiration !== 'undefined' && data.expiration > 0 ? Date.now() + data.expiration : null; // Time when the points expire in milliseconds + + // Null, not set, 0 never expires, otherwise timestamp of when it expires + if (typeof data.expiration === 'undefined') + this.#expiration = null; + else if (data.expiration > 0) + this.#expiration = Date.now() + data.expiration; + else + this.#expiration = 0; + // this.#expiration = typeof data.expiration !== 'undefined' && 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. @@ -652,7 +660,7 @@ class Infraction return this._succeed(); } - #error (reason?: string) + #errorCheck (reason?: string) { if (!this.#fetched) throw new Error(reason || 'Cannot edit an unfetched infraction'); @@ -660,7 +668,7 @@ class Infraction async updateMessages () { - this.#error('Cannot update messages for unfetched infractions'); + this.#errorCheck('Cannot update messages for unfetched infractions'); // console.log(this._dmLogMessage, this.dmLogMessageId, this.dmLogMessage); if (this.#dmLogMessage) await this.#dmLogMessage.edit({ embeds: [ await this.#embed(true) ] }); @@ -670,7 +678,7 @@ class Infraction async editReason (staff: UserResolveable, reason: string): Promise { - this.#error(); + this.#errorCheck(); const log: InfractionChange = { reason: this.#reason, staff: typeof staff === 'string' ? staff : staff.id, @@ -683,7 +691,7 @@ class Infraction async editPoints (staff: UserResolveable, points: number): Promise { - this.#error(); + this.#errorCheck(); const settings = await this.#guild.settings(); if (this.#targetType !== 'USER') return { error: true, index: 'INFRACTION_EDIT_INVALID_TARGETTYPE' }; @@ -697,18 +705,19 @@ class Infraction }; const diff = points - (this.#points ?? 0); this.#points = points; - if (!this.#target) - throw new Error('Missing target?'); - const userWrapper = await this.#client.getUserWrapper(this.#target.id); - if (!userWrapper) - throw new Error('Missing user wrapper'); - this.#totalPoints = await userWrapper.editPoints(this.#guild, { diff, id: this.id, expiration: this.#expiration }); + // if (!this.#target) + // throw new Error('Missing target?'); + // const userWrapper = await this.#client.getUserWrapper(this.#target.id); + // if (!userWrapper) + // throw new Error('Missing user wrapper'); + // this.#totalPoints = await userWrapper.editPoints(this.#guild, { diff, id: this.id, expiration: this.#expiration }); + this.#totalPoints += diff; this.#changes.push(log); } async editExpiration (staff: UserResolveable, expiration: number): Promise { - this.#error(); + this.#errorCheck(); const settings = await this.#guild.settings(); if (this.#targetType !== 'USER') return { error: true, index: 'INFRACTION_EDIT_INVALID_TARGETTYPE' }; @@ -724,22 +733,22 @@ class Infraction expiration: this.#expiration ? this.#expiration - this.#timestamp : null }; this.#expiration = expiration + this.#timestamp; - if (!this.#target) - throw new Error('Missing target?'); - const userWrapper = await this.#client.getUserWrapper(this.#target.id); - if (!userWrapper) - throw new Error('Missing user wrapper'); - this.#totalPoints = await userWrapper.editExpiration(this.#guild, { - id: this.id, - expiration: this.#expiration, - points: this.#points - }); + // if (!this.#target) + // throw new Error('Missing target?'); + // const userWrapper = await this.#client.getUserWrapper(this.#target.id); + // if (!userWrapper) + // throw new Error('Missing user wrapper'); + // this.#totalPoints = await userWrapper.editExpiration(this.#guild, { + // id: this.id, + // expiration: this.#expiration, + // points: this.#points + // }); this.#changes.push(log); } async editDuration (staff: UserResolveable, duration: number): Promise { - this.#error(); + this.#errorCheck(); if (this.#resolved) return { error: true, index: 'INFRACTION_EDIT_DURATION_RESOLVED' }; if (this.#callbacked) diff --git a/src/constants/FilterPresets.ts b/src/constants/FilterPresets.ts index 2aede2d..49738c9 100644 --- a/src/constants/FilterPresets.ts +++ b/src/constants/FilterPresets.ts @@ -228,6 +228,7 @@ const FilterPresets: { "restarts", "restarted", "reskin", + "refraction", "rated", "suspicious", "racoon",