From 30b40ea2c711bbc3b07cf1feb52183fa671ae349 Mon Sep 17 00:00:00 2001 From: NickTheDevOpsGuy Date: Mon, 5 Jan 2026 13:26:57 -0500 Subject: [PATCH 1/2] Adding in these changes --- README.md | 1 + src/services/discord/cooldowns.ts | 44 ++++++++++++++++++++++ src/services/discord/interactionHandler.ts | 44 +++++++++++++++++++++- 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/services/discord/cooldowns.ts diff --git a/README.md b/README.md index fd94106c..9cccbbc9 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,7 @@ OmegaBot is online │ │ ├── discord │ │ │ ├── commandLoader.ts │ │ │ ├── commandMeta.ts +│ │ │ ├── cooldowns.ts │ │ │ ├── fetchChannelMessages.ts │ │ │ └── interactionHandler.ts │ │ ├── faq diff --git a/src/services/discord/cooldowns.ts b/src/services/discord/cooldowns.ts new file mode 100644 index 00000000..1adb84f5 --- /dev/null +++ b/src/services/discord/cooldowns.ts @@ -0,0 +1,44 @@ +// src/services/discord/cooldowns.ts + +/** + * In-memory cooldown store. + * + * Key format: `${userId}:${commandName}` + * Value: timestamp (ms) when the command can be used again + */ +const cooldowns = new Map(); + +function makeKey(userId: string, commandName: string): string { + return `${userId}:${commandName}`; +} + +/** + * Returns remaining cooldown time in milliseconds. + * Returns 0 if no cooldown is active. + */ +export function getCooldownRemainingMs( + userId: string, + commandName: string, +): number { + const key = makeKey(userId, commandName); + const expiresAt = cooldowns.get(key); + + if (!expiresAt) return 0; + + const remaining = expiresAt - Date.now(); + return remaining > 0 ? remaining : 0; +} + +/** + * Sets a cooldown for a user + command. + */ +export function setCooldown( + userId: string, + commandName: string, + durationMs: number, +): void { + if (durationMs <= 0) return; + + const key = makeKey(userId, commandName); + cooldowns.set(key, Date.now() + durationMs); +} \ No newline at end of file diff --git a/src/services/discord/interactionHandler.ts b/src/services/discord/interactionHandler.ts index 0808bd00..c5d79a90 100644 --- a/src/services/discord/interactionHandler.ts +++ b/src/services/discord/interactionHandler.ts @@ -17,6 +17,23 @@ import type { Interaction, ChatInputCommandInteraction } from "discord.js"; import type { CommandClient } from "./commandLoader.js"; import { logger } from "../../utils/logger.js"; +import { getCooldownRemainingMs, setCooldown } from "./cooldowns.js"; + +/** + * Per-command cooldown durations (ms). + * + * Keep this list small and focused: + * - Only expensive commands should be throttled (LLM calls, external APIs). + * - Defaults to 0 (no cooldown) for commands not listed here. + * + * Adjust as needed. + */ +const COOLDOWN_MS: Record = { + summary: 30_000, + // github lookups (example) + // gh: 5_000, + // pr: 5_000, +}; /** * Handle a single Discord interaction. @@ -73,6 +90,31 @@ export async function handleInteraction( return; } + /** + * Cooldowns (per-user, per-command) + * + * Cooldown is enforced here so every command benefits consistently. + * We set cooldown BEFORE execution to prevent spam even if the command fails. + */ + const userId = interaction.user.id; + const commandName = interaction.commandName; + + const remainingMs = getCooldownRemainingMs(userId, commandName); + if (remainingMs > 0) { + const seconds = Math.ceil(remainingMs / 1000); + + await interaction.reply({ + content: `⏳ That command is on cooldown. Try again in ${seconds}s.`, + ephemeral: true, + }); + return; + } + + const durationMs = COOLDOWN_MS[commandName] ?? 0; + if (durationMs > 0) { + setCooldown(userId, commandName, durationMs); + } + /** * Execute the command safely. * @@ -103,4 +145,4 @@ export async function handleInteraction( await interaction.reply({ content: message, ephemeral: true }); } } -} +} \ No newline at end of file From 8ad0d90c4dc659f8a6f6a4bab496931391d62aa1 Mon Sep 17 00:00:00 2001 From: NickTheDevOpsGuy Date: Mon, 5 Jan 2026 13:28:16 -0500 Subject: [PATCH 2/2] Fixing linting issues --- src/services/discord/cooldowns.ts | 7 ++----- src/services/discord/interactionHandler.ts | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/services/discord/cooldowns.ts b/src/services/discord/cooldowns.ts index 1adb84f5..0e059c08 100644 --- a/src/services/discord/cooldowns.ts +++ b/src/services/discord/cooldowns.ts @@ -16,10 +16,7 @@ function makeKey(userId: string, commandName: string): string { * Returns remaining cooldown time in milliseconds. * Returns 0 if no cooldown is active. */ -export function getCooldownRemainingMs( - userId: string, - commandName: string, -): number { +export function getCooldownRemainingMs(userId: string, commandName: string): number { const key = makeKey(userId, commandName); const expiresAt = cooldowns.get(key); @@ -41,4 +38,4 @@ export function setCooldown( const key = makeKey(userId, commandName); cooldowns.set(key, Date.now() + durationMs); -} \ No newline at end of file +} diff --git a/src/services/discord/interactionHandler.ts b/src/services/discord/interactionHandler.ts index c5d79a90..75571a4d 100644 --- a/src/services/discord/interactionHandler.ts +++ b/src/services/discord/interactionHandler.ts @@ -145,4 +145,4 @@ export async function handleInteraction( await interaction.reply({ content: message, ephemeral: true }); } } -} \ No newline at end of file +}