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; join: () => Promise; }; const batches = new Map(); /** * 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 { 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), ); }