diff options
Diffstat (limited to 'packages/gateway/src/commands/webhook.ts')
| -rw-r--r-- | packages/gateway/src/commands/webhook.ts | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/packages/gateway/src/commands/webhook.ts b/packages/gateway/src/commands/webhook.ts new file mode 100644 index 0000000..a3b6b72 --- /dev/null +++ b/packages/gateway/src/commands/webhook.ts @@ -0,0 +1,157 @@ +import { Message, WebhookClient } from "discord.js"; +import { logUnexpectedDiscordAPIError, replyWithCleanup } from "../utilities"; + +const webhookCache = new Map<string, WebhookClient>(); + +const getOrCreateWebhook = async ( + message: Message, + channelId: string, + webhookName: string, + avatarUrl?: string, +): Promise<WebhookClient | null> => { + 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<boolean> => { + if (message.author.bot) 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; + } +}; |