Rewrite how modpoints are handled

This commit is contained in:
Erik 2023-12-28 03:00:02 +02:00
parent bc787738ac
commit 0a2a37f23e
6 changed files with 172 additions and 139 deletions

View File

@ -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;

View File

@ -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<true>, 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);

View File

@ -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

View File

@ -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)
{

View File

@ -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<InfractionEditResult>
{
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<InfractionEditResult>
{
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<InfractionEditResult>
{
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<InfractionEditResult>
{
this.#error();
this.#errorCheck();
if (this.#resolved)
return { error: true, index: 'INFRACTION_EDIT_DURATION_RESOLVED' };
if (this.#callbacked)

View File

@ -228,6 +228,7 @@ const FilterPresets: {
"restarts",
"restarted",
"reskin",
"refraction",
"rated",
"suspicious",
"racoon",