diff options
| author | Fuwn <[email protected]> | 2025-10-03 15:51:51 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2025-10-03 15:51:51 -0700 |
| commit | d05907c16e3f224fa0e634d5be9e26ff6fd98887 (patch) | |
| tree | ead81536b654e2fea63db43dbf595f2f3eed6bac | |
| parent | feat(gateway:moderationAgent): Delete agent (diff) | |
| download | umabotdiscord-d05907c16e3f224fa0e634d5be9e26ff6fd98887.tar.xz umabotdiscord-d05907c16e3f224fa0e634d5be9e26ff6fd98887.zip | |
feat(gateway:aiCommandHandler): Add purge command
| -rw-r--r-- | packages/gateway/src/listeners/messageCreate/aiCommandHandler/index.ts (renamed from packages/gateway/src/listeners/messageCreate/aiCommandHandler.ts) | 72 | ||||
| -rw-r--r-- | packages/gateway/src/listeners/messageCreate/aiCommandHandler/purge.ts | 197 | ||||
| -rw-r--r-- | packages/gateway/src/listeners/messageCreate/aiCommandHandler/slowmode.ts | 47 |
3 files changed, 267 insertions, 49 deletions
diff --git a/packages/gateway/src/listeners/messageCreate/aiCommandHandler.ts b/packages/gateway/src/listeners/messageCreate/aiCommandHandler/index.ts index 6f91e3c..ef30716 100644 --- a/packages/gateway/src/listeners/messageCreate/aiCommandHandler.ts +++ b/packages/gateway/src/listeners/messageCreate/aiCommandHandler/index.ts @@ -1,4 +1,6 @@ import { Message } from "discord.js"; +import { handleSlowmodeCommand } from "./slowmode.js"; +import { handlePurgeCommand } from "./purge.js"; const MODERATOR_ROLE_IDS = [ "1406422617765712095", @@ -7,10 +9,11 @@ const MODERATOR_ROLE_IDS = [ "1406422617724026910", ]; -interface AICommandResponse { +export interface AICommandResponse { command: string; action: string; value?: number; + user?: string; } export const handleAICommand = async (message: Message) => { @@ -29,7 +32,10 @@ export const handleAICommand = async (message: Message) => { if (!hasModeratorRole && !isOwner) return; - const content = message.content.replace(/<@!?\d+>/g, "").trim(); + const mentionMatches = message.content.match(/<@!?(\d+)>/g); + const mentionedUserIds = mentionMatches ? mentionMatches.map(match => match.match(/<@!?(\d+)>/)?.[1]).filter(Boolean) : []; + const botMention = message.content.match(/<@!?\d+>/); + const content = botMention ? message.content.replace(botMention[0], "").trim() : message.content.trim(); if (!content) return; @@ -53,9 +59,10 @@ CRITICAL: Respond with ONLY valid JSON. No explanations, no markdown, no other t Available commands: - slowmode: Toggle, enable, or disable slowmode in the current channel +- purge: Delete messages from the current channel Respond with ONLY a JSON object in this exact format: -{"command": "slowmode", "action": "toggle|enable|disable", "value": number} +{"command": "slowmode|purge", "action": "toggle|enable|disable|last|from|lastfrom", "value": number, "user": "userid"} Actions for slowmode: - "toggle" = switch current state @@ -63,11 +70,21 @@ Actions for slowmode: - "disable" = turn off (0 seconds) - "value" = seconds (optional, defaults to 5 for enable) +Actions for purge: +- "last" = purge last N messages (value = number of messages) +- "from" = purge all messages from specific user (user = user ID) +- "lastfrom" = purge last N messages from specific user (value = number, user = user ID) + +IMPORTANT: When user mentions someone with @username, use the provided user ID from the context. + Examples: "toggle slowmode" → {"command": "slowmode", "action": "toggle"} "enable slowmode" → {"command": "slowmode", "action": "enable", "value": 5} "set slowmode to 10" → {"command": "slowmode", "action": "enable", "value": 10} "disable slowmode" → {"command": "slowmode", "action": "disable", "value": 0} +"purge the last 5 messages" → {"command": "purge", "action": "last", "value": 5} +"purge all messages from @user" → {"command": "purge", "action": "from", "user": "userid"} +"purge the last 5 messages from @user" → {"command": "purge", "action": "lastfrom", "value": 5, "user": "userid"} If input doesn't match available commands, respond with: {"command": "unknown", "action": "none", "value": 0} @@ -76,7 +93,7 @@ RESPONSE MUST BE ONLY JSON. NO OTHER TEXT.`, }, { role: "user", - content: content, + content: content + (mentionedUserIds.length > 0 ? `\n\nMentioned user IDs: ${mentionedUserIds.join(', ')}` : ''), }, ], max_tokens: 100, @@ -126,49 +143,6 @@ const executeAICommand = async ( ) => { if (commandData.command === "slowmode") await handleSlowmodeCommand(message, commandData); -}; - -const handleSlowmodeCommand = async ( - message: Message, - commandData: AICommandResponse, -) => { - if (!message.guild || !message.channel.isTextBased()) return; - - const channel = message.channel; - - if (!("rateLimitPerUser" in channel)) { - await message.react("❌"); - - return; - } - - const currentSlowmode = channel.rateLimitPerUser || 0; - let newSlowmode = 0; - - switch (commandData.action) { - case "toggle": - newSlowmode = currentSlowmode > 0 ? 0 : commandData.value || 5; - - break; - case "enable": - newSlowmode = commandData.value || 5; - - break; - case "disable": - newSlowmode = 0; - - break; - default: - await message.react("❌"); - - return; - } - - try { - await (channel as any).setRateLimitPerUser(newSlowmode); - await message.react("✅"); - } catch (error) { - console.error("Error setting slowmode:", error); - await message.react("❌"); - } + else if (commandData.command === "purge") + await handlePurgeCommand(message, commandData); }; diff --git a/packages/gateway/src/listeners/messageCreate/aiCommandHandler/purge.ts b/packages/gateway/src/listeners/messageCreate/aiCommandHandler/purge.ts new file mode 100644 index 0000000..bbbf89a --- /dev/null +++ b/packages/gateway/src/listeners/messageCreate/aiCommandHandler/purge.ts @@ -0,0 +1,197 @@ +import { Message, Collection } from "discord.js"; +import { AICommandResponse } from "./index"; + +const safeReact = async (message: Message, emoji: string) => { + try { + await message.react(emoji); + } catch (reactError) { + console.log("Could not react to message (likely deleted):", reactError); + } +}; + +export const handlePurgeCommand = async ( + message: Message, + commandData: AICommandResponse, +) => { + if (!message.guild || !message.channel.isTextBased()) return; + + const channel = message.channel; + const PURGE_LIMIT = 25; + + try { + let messagesToDelete: Message[] = []; + let totalCount = 0; + + switch (commandData.action) { + case "last": { + const count = commandData.value || 1; + + if (count > PURGE_LIMIT) { + await safeReact(message, "❌"); + + return; + } + + const allMessages: Message[] = []; + let lastMessageId: string | undefined; + const targetCount = count + 1; + + while (allMessages.length < targetCount) { + const fetchLimit = Math.min(100, targetCount - allMessages.length); + const fetchOptions: any = { limit: fetchLimit }; + + if (lastMessageId) + fetchOptions.before = lastMessageId; + + const messages = await channel.messages.fetch(fetchOptions); + + if (messages instanceof Collection) { + if (messages.size === 0) break; + + const messageArray = Array.from(messages.values()); + + allMessages.push(...messageArray); + + lastMessageId = messageArray[messageArray.length - 1]?.id; + } else { + allMessages.push(messages); + + lastMessageId = messages.id; + } + } + + messagesToDelete = allMessages.filter(msg => msg.id !== message.id).slice(0, count); + totalCount = messagesToDelete.length; + + break; + } + + case "from": { + if (!commandData.user) { + await safeReact(message, "❌"); + + return; + } + + const allMessages: Message[] = []; + let lastMessageId: string | undefined; + + while (true) { + const fetchOptions: any = { limit: 100 }; + + if (lastMessageId) + fetchOptions.before = lastMessageId; + + const messages = await channel.messages.fetch(fetchOptions); + + if (messages instanceof Collection) { + if (messages.size === 0) break; + + const messageArray = Array.from(messages.values()); + + allMessages.push(...messageArray); + + lastMessageId = messageArray[messageArray.length - 1]?.id; + } else { + allMessages.push(messages); + + lastMessageId = messages.id; + } + } + + const userMessages = allMessages.filter(msg => msg.author.id === commandData.user && msg.id !== message.id); + + if (userMessages.length > PURGE_LIMIT) { + await safeReact(message, "❌"); + + return; + } + + messagesToDelete = userMessages; + totalCount = messagesToDelete.length; + + break; + } + + case "lastfrom": { + if (!commandData.user || !commandData.value) { + await safeReact(message, "❌"); + + return; + } + + const count = commandData.value; + + if (count > PURGE_LIMIT) { + await safeReact(message, "❌"); + + return; + } + + const allMessages: Message[] = []; + let lastMessageId: string | undefined; + + while (allMessages.length < count * 2) { + const fetchOptions: any = { limit: 100 }; + + if (lastMessageId) + fetchOptions.before = lastMessageId; + + const messages = await channel.messages.fetch(fetchOptions); + + if (messages instanceof Collection) { + if (messages.size === 0) break; + + const messageArray = Array.from(messages.values()); + + allMessages.push(...messageArray); + + lastMessageId = messageArray[messageArray.length - 1]?.id; + } else { + allMessages.push(messages); + + lastMessageId = messages.id; + } + } + + const userMessages = allMessages.filter(msg => msg.author.id === commandData.user && msg.id !== message.id); + + messagesToDelete = userMessages.slice(0, count); + totalCount = messagesToDelete.length; + + break; + } + + default: + await safeReact(message, "❌"); + + return; + } + + if (messagesToDelete.length === 0) { + await safeReact(message, "❌"); + + return; + } + + const batches = []; + + for (let i = 0; i < messagesToDelete.length; i += 100) + batches.push(messagesToDelete.slice(i, i + 100)); + + for (const batch of batches) + if (batch.length === 1) { + await batch[0].delete(); + } else if ('bulkDelete' in channel) { + await (channel as any).bulkDelete(batch); + } else { + for (const msg of batch) + await msg.delete(); + } + + await safeReact(message, "✅"); + } catch (error) { + console.error("Error purging messages:", error); + await safeReact(message, "❌"); + } +}; diff --git a/packages/gateway/src/listeners/messageCreate/aiCommandHandler/slowmode.ts b/packages/gateway/src/listeners/messageCreate/aiCommandHandler/slowmode.ts new file mode 100644 index 0000000..d23e70c --- /dev/null +++ b/packages/gateway/src/listeners/messageCreate/aiCommandHandler/slowmode.ts @@ -0,0 +1,47 @@ +import { Message } from "discord.js"; +import { AICommandResponse } from "./index"; + +export const handleSlowmodeCommand = async ( + message: Message, + commandData: AICommandResponse, +) => { + if (!message.guild || !message.channel.isTextBased()) return; + + const channel = message.channel; + + if (!("rateLimitPerUser" in channel)) { + await message.react("❌"); + + return; + } + + const currentSlowmode = channel.rateLimitPerUser || 0; + let newSlowmode = 0; + + switch (commandData.action) { + case "toggle": + newSlowmode = currentSlowmode > 0 ? 0 : commandData.value || 5; + + break; + case "enable": + newSlowmode = commandData.value || 5; + + break; + case "disable": + newSlowmode = 0; + + break; + default: + await message.react("❌"); + + return; + } + + try { + await (channel as any).setRateLimitPerUser(newSlowmode); + await message.react("✅"); + } catch (error) { + console.error("Error setting slowmode:", error); + await message.react("❌"); + } +}; |