import { Message, WebhookClient } from "discord.js"; import { logUnexpectedDiscordAPIError, replyWithCleanup } from "../utilities"; const webhookCache = new Map(); const getOrCreateWebhook = async ( message: Message, channelId: string, webhookName: string, avatarUrl?: string, ): Promise => { const cacheKey = `${channelId}-${webhookName}`; if (webhookCache.has(cacheKey)) return webhookCache.get(cacheKey)!; try { const channel = await message.client.channels.fetch(channelId); if (!channel) { logUnexpectedDiscordAPIError(new Error(`Channel ${channelId} not found`)); return null; } if (!channel.isTextBased()) { logUnexpectedDiscordAPIError( new Error(`Channel ${channelId} is not text-based`), ); return null; } if (channel.isDMBased()) { logUnexpectedDiscordAPIError( new Error(`Channel ${channelId} is DM-based`), ); return null; } if (!("fetchWebhooks" in channel)) { logUnexpectedDiscordAPIError( new Error(`Channel ${channelId} does not support webhooks`), ); return null; } const webhooks = await channel.fetchWebhooks(); let existingWebhook = webhooks.find( (webhook) => webhook.name === webhookName, ); if (existingWebhook) { const webhookClient = new WebhookClient({ url: existingWebhook.url }); webhookCache.set(cacheKey, webhookClient); return webhookClient; } if ("createWebhook" in channel) { const webhook = await channel.createWebhook({ name: webhookName, avatar: avatarUrl || "https://cdn.discordapp.com/embed/avatars/0.png", }); const webhookClient = new WebhookClient({ url: webhook.url }); webhookCache.set(cacheKey, webhookClient); return webhookClient; } logUnexpectedDiscordAPIError( new Error(`Channel ${channelId} does not support createWebhook`), ); return null; } catch (error) { logUnexpectedDiscordAPIError(error); return null; } }; export const handleWebhookCommand = async ( message: Message, ): Promise => { if (message.author.bot) return false; const application = await message.client.application?.fetch(); const ownerId = application?.owner?.id; if (message.author.id !== ownerId) return false; const content = message.content.trim(); const commandMatch = content.match( /^uma!wh\s+(\d+)\s*,\s*([^\s,]+)\s*,\s*([^,]+)\s*,\s*(.+)$/s, ); if (!commandMatch) return false; const [, channelId, avatarUrl, webhookName, messageContent] = commandMatch; if (!messageContent.trim()) { await replyWithCleanup( message, "❌ You need to provide a message to send.", ); return true; } try { const webhookClient = await getOrCreateWebhook( message, channelId, webhookName.trim(), avatarUrl.trim(), ); if (!webhookClient) { await replyWithCleanup( message, "❌ Failed to create or access webhook. Make sure the channel exists and I have permission to manage webhooks.", ); return true; } await webhookClient.send({ content: messageContent.trim(), username: webhookName.trim(), avatarURL: avatarUrl.trim(), }); await replyWithCleanup( message, `✅ Message sent via webhook to <#${channelId}>.`, ); return true; } catch (error) { logUnexpectedDiscordAPIError(error); let errorMessage = "❌ Failed to send message via webhook."; if (error instanceof Error) if (error.message.includes("Missing Permissions")) { errorMessage = "❌ Missing permissions to manage webhooks in this channel."; } else if (error.message.includes("Invalid Form Body")) { errorMessage = "❌ Invalid webhook data. Check your avatar URL and webhook name."; } else if (error.message.includes("Unknown Channel")) { errorMessage = "❌ Channel not found or inaccessible."; } await replyWithCleanup(message, errorMessage); return true; } };