68 lines
2 KiB
TypeScript
68 lines
2 KiB
TypeScript
import { Guild, GuildAuditLogsAction, GuildAuditLogsEntry } from "discord.js";
|
|
import { SECONDS, sleep } from "../utils";
|
|
|
|
const BATCH_DEBOUNCE_TIME = 2 * SECONDS;
|
|
const BATCH_FETCH_COUNT_INCREMENT = 10;
|
|
|
|
type Batch = {
|
|
_waitUntil: number;
|
|
_fetchCount: number;
|
|
_promise: Promise<GuildAuditLogsEntry[]>;
|
|
join: () => Promise<GuildAuditLogsEntry[]>;
|
|
};
|
|
|
|
const batches = new Map<string, Batch>();
|
|
|
|
/**
|
|
* Find a recent audit log entry matching the given criteria.
|
|
* This function will debounce and batch simultaneous calls into one audit log request.
|
|
*/
|
|
export async function findMatchingAuditLogEntry(
|
|
guild: Guild,
|
|
action?: GuildAuditLogsAction,
|
|
targetId?: string,
|
|
): Promise<GuildAuditLogsEntry | undefined> {
|
|
let candidates: GuildAuditLogsEntry[];
|
|
|
|
if (batches.has(guild.id)) {
|
|
candidates = await batches.get(guild.id)!.join();
|
|
} else {
|
|
const batch: Batch = {
|
|
_waitUntil: Date.now(),
|
|
_fetchCount: 0,
|
|
_promise: new Promise(async (resolve) => {
|
|
await sleep(BATCH_DEBOUNCE_TIME);
|
|
|
|
do {
|
|
await sleep(Math.max(0, batch._waitUntil - Date.now()));
|
|
} while (Date.now() < batch._waitUntil);
|
|
|
|
const result = await guild
|
|
.fetchAuditLogs({
|
|
limit: batch._fetchCount,
|
|
})
|
|
.catch((err) => {
|
|
// tslint:disable-next-line:no-console
|
|
console.warn(`[DEBUG] Audit log error in ${guild.id} (${guild.name}): ${err.message}`);
|
|
return null;
|
|
});
|
|
const _candidates = Array.from(result?.entries.values() ?? []);
|
|
|
|
batches.delete(guild.id);
|
|
resolve(_candidates);
|
|
}),
|
|
join() {
|
|
batch._waitUntil = Date.now() + BATCH_DEBOUNCE_TIME;
|
|
batch._fetchCount = Math.min(100, batch._fetchCount + BATCH_FETCH_COUNT_INCREMENT);
|
|
return batch._promise;
|
|
},
|
|
};
|
|
batches.set(guild.id, batch);
|
|
candidates = await batch.join();
|
|
}
|
|
|
|
return candidates.find(
|
|
(entry) =>
|
|
(action == null || entry.action === action) && (targetId == null || (entry.target as any)?.id === targetId),
|
|
);
|
|
}
|