Skip to content

Commit

Permalink
fix: fix
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranet committed Mar 18, 2024
1 parent b097ad2 commit ddc88b0
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 36 deletions.
10 changes: 7 additions & 3 deletions src/lib/moderation/actions/base/ModerationAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,9 @@ export abstract class ModerationAction<ContextType = never, Type extends TypeVar
* @param options - The options to fetch the moderation entry.
* @returns The canceled moderation entry, or `null` if no entry was found.
*/
protected async cancelLastModerationEntryTaskFromUser(options: ModerationAction.ModerationEntryFetchOptions<Type>) {
protected async cancelLastModerationEntryTaskFromUser(
options: ModerationAction.ModerationEntryFetchOptions<Type>
): Promise<ModerationManager.Entry<Type> | null> {
const entry = await this.retrieveLastModerationEntryFromUser(options);
if (isNullish(entry)) return null;

Expand All @@ -212,7 +214,9 @@ export abstract class ModerationAction<ContextType = never, Type extends TypeVar
* @param options - The options for fetching the moderation entry.
* @returns The last moderation entry from the user, or `null` if no entry is found.
*/
protected async retrieveLastModerationEntryFromUser(options: ModerationAction.ModerationEntryFetchOptions<Type>) {
protected async retrieveLastModerationEntryFromUser(
options: ModerationAction.ModerationEntryFetchOptions<Type>
): Promise<ModerationManager.Entry<Type> | null> {
// Retrieve all the entries
const entries = await getModeration(options.guild).fetch({ userId: options.userId });

Expand All @@ -230,7 +234,7 @@ export abstract class ModerationAction<ContextType = never, Type extends TypeVar
// If the extra check fails, skip it:
if (!extra(entry as ModerationManager.Entry<Type>)) continue;

return entry;
return entry as ModerationManager.Entry<Type>;
}

return null;
Expand Down
47 changes: 22 additions & 25 deletions src/lib/moderation/managers/ModerationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { AsyncQueue } from '@sapphire/async-queue';
import type { GuildTextBasedChannelTypes } from '@sapphire/discord.js-utilities';
import { UserError, container } from '@sapphire/framework';
import { isNullish } from '@sapphire/utilities';
import { DiscordAPIError, type Guild, type Snowflake } from 'discord.js';
import type { Guild, Snowflake } from 'discord.js';

enum CacheActions {
None,
Expand All @@ -22,7 +22,7 @@ export class ModerationManager {
/**
* The Guild instance that manages this manager
*/
public guild: Guild;
public readonly guild: Guild;

/**
* The cache of the moderation entries, sorted by their case ID in
Expand All @@ -33,7 +33,7 @@ export class ModerationManager {
/**
* A queue for save tasks, prevents case_id duplication
*/
#saveQueue = new AsyncQueue();
readonly #saveQueue = new AsyncQueue();

/**
* The latest moderation case ID.
Expand Down Expand Up @@ -73,21 +73,13 @@ export class ModerationManager {
}

/**
* Fetch 100 messages from the modlogs channel
* Retrieves the latest recent cached entry for a given user created in the last 30 seconds.
*
* @param userId - The ID of the user.
* @returns The latest recent cached entry for the user, or `null` if no entry is found.
*/
public async fetchChannelMessages(remainingRetries = 5): Promise<void> {
const channel = await this.fetchChannel();
if (channel === null) return;
try {
await channel.messages.fetch({ limit: 100 });
} catch (error) {
if (error instanceof DiscordAPIError) throw error;
return this.fetchChannelMessages(--remainingRetries);
}
}

public getLatestLogForUser(userId: string) {
const minimumTime = Date.now() - seconds(15);
public getLatestRecentCachedEntryForUser(userId: string) {
const minimumTime = Date.now() - seconds(30);
for (const entry of this.#cache.values()) {
if (entry.userId !== userId) continue;
if (entry.createdAt < minimumTime) break;
Expand Down Expand Up @@ -119,7 +111,7 @@ export class ModerationManager {
const id = (await this.getCurrentId()) + 1;
const entry = new ModerationManagerEntry({ ...data.toData(), id, createdAt: Date.now() });
await this.#performInsert(entry);
return this._cache(entry, CacheActions.Insert);
return this.#addToCache(entry, CacheActions.Insert);
} finally {
this.#saveQueue.shift();
}
Expand Down Expand Up @@ -192,17 +184,17 @@ export class ModerationManager {
): Promise<ModerationManager.Entry | SortedCollection<number, ModerationManager.Entry> | null> {
// Case number
if (typeof options === 'number') {
return this.#getSingle(options) ?? this._cache(await this.#fetchSingle(options), CacheActions.None);
return this.#getSingle(options) ?? this.#addToCache(await this.#fetchSingle(options), CacheActions.None);
}

if (options.moderatorId || options.userId) {
return this.#count === this.#cache.size //
? this.#getMany(options)
: this._cache(await this.#fetchMany(options), CacheActions.None);
: this.#addToCache(await this.#fetchMany(options), CacheActions.None);
}

if (this.#count !== this.#cache.size) {
this._cache(await this.#fetchAll(), CacheActions.Fetch);
this.#addToCache(await this.#fetchAll(), CacheActions.Fetch);
}

return this.#cache;
Expand Down Expand Up @@ -280,9 +272,9 @@ export class ModerationManager {
return false;
}

private _cache(entry: ModerationManagerEntry | null, type: CacheActions): ModerationManagerEntry;
private _cache(entries: ModerationManagerEntry[], type: CacheActions): SortedCollection<number, ModerationManagerEntry>;
private _cache(
#addToCache(entry: ModerationManagerEntry | null, type: CacheActions): ModerationManagerEntry;
#addToCache(entries: ModerationManagerEntry[], type: CacheActions): SortedCollection<number, ModerationManagerEntry>;
#addToCache(
entries: ModerationManagerEntry | ModerationManagerEntry[] | null,
type: CacheActions
): SortedCollection<number, ModerationManagerEntry> | ModerationManagerEntry | null {
Expand All @@ -306,7 +298,12 @@ export class ModerationManager {
}, seconds(30));
}

return Array.isArray(entries) ? new SortedCollection(entries.map((entry) => [entry.id, entry])) : entries;
return Array.isArray(entries)
? new SortedCollection(
entries.map((entry) => [entry.id, entry]),
desc
)
: entries;
}

async #resolveEntry(entryOrId: ModerationManager.EntryResolvable) {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/structures/managers/ScheduleManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class ScheduleManager {
}

private _remove(entity: ScheduleEntity) {
const index = this.queue.findIndex((entry) => entry === entity);
const index = this.queue.indexOf(entity);
if (index !== -1) this.queue.splice(index, 1);
}

Expand Down
2 changes: 1 addition & 1 deletion src/listeners/guilds/members/rawMemberRemoveNotify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class UserListener extends Listener {
const moderation = getModeration(guild);
await moderation.waitLock();

const latestLogForUser = moderation.getLatestLogForUser(user.id);
const latestLogForUser = moderation.getLatestRecentCachedEntryForUser(user.id);

if (latestLogForUser === null) {
return {
Expand Down
27 changes: 21 additions & 6 deletions src/listeners/moderation/moderationEntryEdit.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { GuildSettings, writeSettings } from '#lib/database';
import type { ModerationManager } from '#lib/moderation';
import { getEmbed, getUndoTaskName } from '#lib/moderation/common';
import type { GuildMessage } from '#lib/types';
import { resolveOnErrorCodes } from '#utils/common';
import { getModeration } from '#utils/functions';
import { SchemaKeys } from '#utils/moderationConstants';
import { isUserSelf } from '#utils/util';
import { canSendEmbeds, type GuildTextBasedChannelTypes } from '@sapphire/discord.js-utilities';
import { Listener } from '@sapphire/framework';
import { fetchT } from '@sapphire/plugin-i18next';
import { isNullish, isNumber } from '@sapphire/utilities';
import { RESTJSONErrorCodes, type Embed, type Message } from 'discord.js';
import { DiscordAPIError, RESTJSONErrorCodes, type Collection, type Embed, type Message, type Snowflake } from 'discord.js';

export class UserListener extends Listener {
public run(old: ModerationManager.Entry, entry: ModerationManager.Entry) {
Expand Down Expand Up @@ -45,7 +47,7 @@ export class UserListener extends Listener {
if (channel === null || !canSendEmbeds(channel)) return;

const t = await fetchT(entry.guild);
const previous = this.fetchModerationLogMessage(entry, channel);
const previous = await this.fetchModerationLogMessage(entry, channel);
const options = { embeds: [await getEmbed(t, entry)] };
try {
await resolveOnErrorCodes(
Expand All @@ -58,21 +60,34 @@ export class UserListener extends Listener {
}
}

private fetchModerationLogMessage(entry: ModerationManager.Entry, channel: GuildTextBasedChannelTypes) {
for (const message of channel.messages.cache.values()) {
private async fetchModerationLogMessage(entry: ModerationManager.Entry, channel: GuildTextBasedChannelTypes) {
const messages = await this.fetchChannelMessages(channel);
for (const message of messages.values()) {
if (this.validateModerationLogMessage(message, entry.id)) return message;
}

return null;
}

/**
* Fetch 100 messages from the modlogs channel
*/
private async fetchChannelMessages(channel: GuildTextBasedChannelTypes, remainingRetries = 5): Promise<Collection<Snowflake, GuildMessage>> {
try {
return (await channel.messages.fetch({ limit: 100 })) as Collection<Snowflake, GuildMessage>;
} catch (error) {
if (error instanceof DiscordAPIError) throw error;
return this.fetchChannelMessages(channel, --remainingRetries);
}
}

private validateModerationLogMessage(message: Message, caseId: number) {
return (
message.author.id === process.env.CLIENT_ID &&
isUserSelf(message.author.id) &&
message.attachments.size === 0 &&
message.embeds.length === 1 &&
this.validateModerationLogMessageEmbed(message.embeds[0]) &&
message.embeds[0].footer!.text === `Case ${caseId}`
message.embeds[0].footer!.text.includes(caseId.toString())
);
}

Expand Down

0 comments on commit ddc88b0

Please sign in to comment.