summaryrefslogtreecommitdiff
path: root/packages/gateway
diff options
context:
space:
mode:
authorFuwn <[email protected]>2025-10-03 15:51:51 -0700
committerFuwn <[email protected]>2025-10-03 15:51:51 -0700
commitd05907c16e3f224fa0e634d5be9e26ff6fd98887 (patch)
treeead81536b654e2fea63db43dbf595f2f3eed6bac /packages/gateway
parentfeat(gateway:moderationAgent): Delete agent (diff)
downloadumabotdiscord-d05907c16e3f224fa0e634d5be9e26ff6fd98887.tar.xz
umabotdiscord-d05907c16e3f224fa0e634d5be9e26ff6fd98887.zip
feat(gateway:aiCommandHandler): Add purge command
Diffstat (limited to 'packages/gateway')
-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.ts197
-rw-r--r--packages/gateway/src/listeners/messageCreate/aiCommandHandler/slowmode.ts47
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("❌");
+ }
+};