import { TextChannel, WebhookClient } from 'discord.js'; import { logger } from '../utils/logger'; export class WebhookService { private static readonly MAX_WEBHOOKS = 10; private static readonly WEBHOOK_NAME = 'Kord Mimic Webhook'; private static readonly WEBHOOK_CACHE_TTL_MS = 86400 * 1000; private static readonly webhookCache = new Map< string, { id: string; token: string; expiresAt: number } >(); public static async getWebhookClient(channel: TextChannel): Promise { try { const now = Date.now(); const cached = this.webhookCache.get(channel.id); if (cached && now < cached.expiresAt) { return new WebhookClient({ id: cached.id, token: cached.token }); } // 2. Fetch from Discord API const webhooks = await channel.fetchWebhooks(); let kordWebhook = webhooks.find(wh => wh.name === this.WEBHOOK_NAME && wh.token !== null); if (!kordWebhook) { if (webhooks.size >= this.MAX_WEBHOOKS) { // If we hit limits, delete the oldest webhook const oldestWebhook = webhooks.last(); if (oldestWebhook) { await oldestWebhook.delete('Hit max webhook limit for Kord'); logger.warn(`Deleted oldest webhook in channel ${channel.id}`); } else { logger.error(`Webhook limits reached in ${channel.id} but no webhook could be deleted.`); return null; } } kordWebhook = await channel.createWebhook({ name: this.WEBHOOK_NAME, avatar: channel.client.user?.displayAvatarURL(), reason: 'Webhook needed for Kord Mimic & Prank feature', }); logger.info(`Created new webhook for channel ${channel.id}`); } if (kordWebhook.token) { this.webhookCache.set(channel.id, { id: kordWebhook.id, token: kordWebhook.token, expiresAt: Date.now() + this.WEBHOOK_CACHE_TTL_MS, }); return new WebhookClient({ id: kordWebhook.id, token: kordWebhook.token }); } return null; } catch (error) { logger.error(`WebhookService Error on channel ${channel.id}:`, error); return null; } } }