From 17de1018647c7615bb6ae001abc0dc4a4bee705e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 12:19:51 +0000 Subject: [PATCH 1/3] feat!: remove /amikool and friendship-listener novelty features The /amikool command (random reply gated on a "Kool" role) and the passive friendship-listener (auto-reply on "best ship" / "worst ship" mentions) were pure novelty surface that didn't earn their maintenance cost. They're orphaned from the bot's voice-tracking-and-gamification core and were the last carriers of the amikool.* and fun.* config categories. Removes: - /amikool command, content pool, and tests - FriendshipListener service, trigger pool, and tests - amikool.enabled / amikool.role.name / fun.friendship config keys (interface, defaults, metadata, migrator entries) - amikool and fun categories from the Mongo Config enum - Wizard card, dashboard tile, write-route group, help registry - Documentation references across README, COMMANDS, WEBUI, SETTINGS, DEVELOPER_GUIDE, and src/content/README.md BREAKING CHANGE: /amikool is no longer registered with Discord and the amikool.* / fun.friendship settings are no longer recognised. Existing DB rows for these keys become inert and will be reported as "unknown settings" until removed. --- COMMANDS.md | 32 +----- DEVELOPER_GUIDE.md | 1 - README.md | 1 - SETTINGS.md | 18 --- WEBUI.md | 2 +- __tests__/commands/amikool.test.ts | 103 ------------------ __tests__/services/config-schema.test.ts | 5 - .../services/friendship-listener.test.ts | 38 ------- __tests__/services/settings-metadata.test.ts | 2 - src/commands/amikool.ts | 45 -------- src/commands/help.ts | 5 - src/commands/index.ts | 6 - src/content/README.md | 2 - src/content/amikool-responses.ts | 31 ------ src/content/friendship-triggers.ts | 23 ---- src/content/index.ts | 2 - src/index.ts | 21 ---- src/models/config.ts | 2 - src/scripts/migrate-config.ts | 14 --- src/scripts/update-settings-references.ts | 10 -- src/services/command-manager.ts | 2 - src/services/config-schema.ts | 24 ---- src/services/config-service.ts | 4 - src/services/friendship-listener.ts | 64 ----------- src/services/startup-migrator.ts | 18 --- src/web/admin-views.ts | 4 - src/web/read-only-routes.ts | 1 - src/web/write-routes.ts | 2 - 28 files changed, 2 insertions(+), 480 deletions(-) delete mode 100644 __tests__/commands/amikool.test.ts delete mode 100644 __tests__/services/friendship-listener.test.ts delete mode 100644 src/commands/amikool.ts delete mode 100644 src/content/amikool-responses.ts delete mode 100644 src/content/friendship-triggers.ts delete mode 100644 src/services/friendship-listener.ts diff --git a/COMMANDS.md b/COMMANDS.md index 0bb05251..a077bdfe 100644 --- a/COMMANDS.md +++ b/COMMANDS.md @@ -4,7 +4,7 @@ Complete reference for every slash command KoolBot registers with Discord. KoolBot's slash-command surface is intentionally small. All **day-to-day chat interaction** stays in Discord (`/ping`, `/voicestats`, -`/seen`, `/quote`, `/achievements`, `/amikool`, `/help`). All +`/seen`, `/quote`, `/achievements`, `/help`). All **administration and configuration** lives in the Web UI, reached via the single `/config` launcher. @@ -24,7 +24,6 @@ single `/config` launcher. - [/seen](#seen) - [/achievements](#achievements) - [/quote](#quote) - - [/amikool](#amikool) - [Admin: Web UI launcher](#-admin-web-ui-launcher) - [/config](#config) - [Voice Channel Control Panel](#voice-channel-control-panel) @@ -370,33 +369,6 @@ and is auto-recreated if deleted. --- -### `/amikool` - -**Description:** Check if you have a specific role (fun role verification). - -**Enable:** Web UI → Settings: - -- `amikool.enabled = true` -- `amikool.role.name = "Kool Members"` (or whatever role you check for) - -Then reload commands. - -**Usage:** - -```text -/amikool -``` - -**Example responses:** - -```text -✅ Yes, you are kool! You have the "Kool Members" role. - -❌ Sorry, you don't have the "Kool Members" role. -``` - ---- - ## 🔧 Admin: Web UI launcher KoolBot has exactly one admin slash command. It does one thing: mint a @@ -608,7 +580,6 @@ button to admit them. **🗑️ Remove Waiting Room** deletes it. | `/achievements` | Everyone\* | Achievements enabled | | `/seen` | Everyone\* | Voice tracking + seen enabled | | `/quote` | Everyone\* | Quotes enabled | -| `/amikool` | Everyone\* | Command enabled + role configured | \* Per-command role gating can be added in the Web UI's **Permissions** page. @@ -665,7 +636,6 @@ The bot needs these Discord permissions: /seen user:@User # Last-seen lookup /quote add text:"..." author:@User # Add a quote /quote edit id:"..." [text:"..."] [author:@User] -/amikool # Role verification ``` ### Admin command diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 8fa226e7..19040bb6 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -26,7 +26,6 @@ src/ ├── index.ts # Entry point, service initialization, Express bootstrap ├── commands/ # Discord slash commands (small surface from v1.0) │ ├── achievements.ts -│ ├── amikool.ts │ ├── config.ts # Web UI launcher (the only admin command) │ ├── help.ts │ ├── ping.ts diff --git a/README.md b/README.md index 485706e2..4f3dff22 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,6 @@ commands always registered): - `/seen` — Last-seen lookup (`voicetracking.seen.enabled`) - `/achievements` — View earned accolades (`achievements.enabled`) - `/quote add` / `/quote edit` — Manage memorable quotes (`quotes.enabled`) -- `/amikool` — Role verification (`amikool.enabled`) A fresh install only sees `/help` and `/config` in Discord until you enable the others on the Settings page and click **Reload commands to diff --git a/SETTINGS.md b/SETTINGS.md index 57aca7b9..0749cbe9 100644 --- a/SETTINGS.md +++ b/SETTINGS.md @@ -141,8 +141,6 @@ effect on whether the command appears in Discord.) | Setting | Default | Description | | --- | --- | --- | | `ping.enabled` | `false` | Enable/disable the `/ping` command | -| `amikool.enabled` | `false` | Enable/disable the `/amikool` command | -| `amikool.role.name` | `""` | Role name to check for `/amikool` verification | | `quotes.enabled` | `false` | Enable/disable the quote system | After changing any `*.enabled` value, click **Reload commands to @@ -634,16 +632,6 @@ or split them across `#bot-status`, `#admin-alerts`, `#bot-logs`, etc. --- -## 🎭 Fun Features - -Easter eggs and passive listeners. - -| Setting | Default | Description | -| --- | --- | --- | -| `fun.friendship` | `false` | Respond to "best ship" and "worst ship" mentions | - ---- - ## 🔒 Rate Limiting Protect your bot from command spam with global rate limiting. @@ -768,8 +756,6 @@ above). #### Commands - `ping.enabled` (bool, default: false) -- `amikool.enabled` (bool, default: false) -- `amikool.role.name` (string, default: "") - `quotes.enabled` (bool, default: false) #### Setup Wizard @@ -868,10 +854,6 @@ above). - `core.cron.enabled` (bool, default: false) - `core.cron.channel_id` (string, default: "") -#### Fun Features - -- `fun.friendship` (bool, default: false) - #### Rate Limiting - `ratelimit.enabled` (bool, default: false) diff --git a/WEBUI.md b/WEBUI.md index cfc02c53..77aeb1d1 100644 --- a/WEBUI.md +++ b/WEBUI.md @@ -599,7 +599,7 @@ side effect, since the HMAC key changes. | **Bootstrap** | (new — read-only env diagnostics) | User-facing commands (`/ping`, `/voicestats`, `/seen`, `/quote`, -`/achievements`, `/amikool`, `/help`) are **not** affected and stay in +`/achievements`, `/help`) are **not** affected and stay in Discord exactly as before. The per-voice-channel control panel (rename, privacy, invite, transfer, live, waiting room) also stays in Discord — it's a member-facing feature, not an admin tool. diff --git a/__tests__/commands/amikool.test.ts b/__tests__/commands/amikool.test.ts deleted file mode 100644 index 8260c624..00000000 --- a/__tests__/commands/amikool.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { describe, it, expect, jest, beforeEach } from '@jest/globals'; -import { data, execute } from '../../src/commands/amikool.js'; -import type { CommandInteraction, GuildMember, User, Role } from 'discord.js'; -import { createMockCollection } from '../test-utils.js'; - -// Mock logger -jest.mock('../../src/utils/logger.js'); - -describe('Amikool Command', () => { - describe('command metadata', () => { - it('should have correct command name', () => { - expect(data.name).toBe('amikool'); - }); - - it('should have a description', () => { - expect(data.description).toBe('Check if you are kool'); - }); - - it('should be a valid slash command', () => { - expect(data.toJSON()).toHaveProperty('name', 'amikool'); - expect(data.toJSON()).toHaveProperty('description', 'Check if you are kool'); - }); - }); - - describe('execute', () => { - let mockInteraction: Partial; - let mockMember: Partial; - let mockUser: Partial; - - beforeEach(() => { - jest.clearAllMocks(); - - mockUser = { - tag: 'TestUser#1234', - }; - - const mockRoles = createMockCollection([ - ['role1', { name: 'Kool', id: 'role1' } as Role], - ]); - - mockMember = { - roles: { - cache: mockRoles, - } as any, - }; - - mockInteraction = { - user: mockUser as User, - member: mockMember as GuildMember, - reply: jest.fn().mockResolvedValue(undefined), - }; - }); - - it('should reply with a response', async () => { - await execute(mockInteraction as CommandInteraction); - - expect(mockInteraction.reply).toHaveBeenCalled(); - const reply = (mockInteraction.reply as jest.Mock).mock.calls[0][0]; - expect(typeof reply).toBe('string'); - }); - - it('should reply with kool response if user has kool role', async () => { - await execute(mockInteraction as CommandInteraction); - - expect(mockInteraction.reply).toHaveBeenCalled(); - const reply = (mockInteraction.reply as jest.Mock).mock.calls[0][0]; - expect(typeof reply).toBe('string'); - // Should contain "kool" (case insensitive) - expect(reply.toLowerCase()).toContain('kool'); - }); - - it('should reply with not kool response if user does not have kool role', async () => { - mockMember!.roles = { - cache: createMockCollection([]), - } as any; - - await execute(mockInteraction as CommandInteraction); - - expect(mockInteraction.reply).toHaveBeenCalled(); - const reply = (mockInteraction.reply as jest.Mock).mock.calls[0][0]; - expect(typeof reply).toBe('string'); - }); - - it('should handle member being null', async () => { - mockInteraction.member = null; - - await execute(mockInteraction as CommandInteraction); - - expect(mockInteraction.reply).toHaveBeenCalled(); - }); - - it('should handle errors when replying', async () => { - mockInteraction.reply = jest.fn() - .mockRejectedValueOnce(new Error('First error')) - .mockResolvedValueOnce(undefined); - - await execute(mockInteraction as CommandInteraction); - - // When error occurs, command should still try to reply - expect(mockInteraction.reply).toHaveBeenCalled(); - }); - }); -}); diff --git a/__tests__/services/config-schema.test.ts b/__tests__/services/config-schema.test.ts index 0df92a8b..97b08487 100644 --- a/__tests__/services/config-schema.test.ts +++ b/__tests__/services/config-schema.test.ts @@ -7,7 +7,6 @@ describe('Config Schema', () => { expect(defaultConfig['voicechannels.enabled']).toBe(false); expect(defaultConfig['voicetracking.enabled']).toBe(false); expect(defaultConfig['ping.enabled']).toBe(false); - expect(defaultConfig['amikool.enabled']).toBe(false); expect(defaultConfig['quotes.enabled']).toBe(false); }); @@ -39,10 +38,6 @@ describe('Config Schema', () => { expect(typeof defaultConfig['voicetracking.cleanup.schedule']).toBe('string'); }); - it('should have fun features as boolean values', () => { - expect(typeof defaultConfig['fun.friendship']).toBe('boolean'); - }); - it('should have channel_id fields as strings', () => { expect(typeof defaultConfig['core.cleanup.channel_id']).toBe('string'); expect(typeof defaultConfig['quotes.channel_id']).toBe('string'); diff --git a/__tests__/services/friendship-listener.test.ts b/__tests__/services/friendship-listener.test.ts deleted file mode 100644 index fed45ef9..00000000 --- a/__tests__/services/friendship-listener.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { describe, it, expect, beforeEach, jest } from '@jest/globals'; -import { FriendshipListener } from '../../src/services/friendship-listener.js'; - -// Mock logger -jest.mock('../../src/utils/logger.js'); - -describe('FriendshipListener', () => { - let listener: FriendshipListener; - let mockClient: any; - - beforeEach(() => { - jest.clearAllMocks(); - mockClient = { - on: jest.fn(), - }; - listener = FriendshipListener.getInstance(mockClient); - }); - - describe('singleton pattern', () => { - it('should create a singleton instance', () => { - const instance1 = FriendshipListener.getInstance(mockClient); - const instance2 = FriendshipListener.getInstance(mockClient); - - expect(instance1).toBe(instance2); - }); - }); - - describe('initialization', () => { - it('should create an instance with a client', () => { - expect(listener).toBeDefined(); - expect(listener).toBeInstanceOf(FriendshipListener); - }); - - it('should have initialize method', () => { - expect(typeof listener.initialize).toBe('function'); - }); - }); -}); diff --git a/__tests__/services/settings-metadata.test.ts b/__tests__/services/settings-metadata.test.ts index 3f9257ec..c7c2d204 100644 --- a/__tests__/services/settings-metadata.test.ts +++ b/__tests__/services/settings-metadata.test.ts @@ -41,10 +41,8 @@ describe("settingsMetadata", () => { "voicetracking", "ping", "help", - "amikool", "quotes", "core", - "fun", "ratelimit", "announcements", "achievements", diff --git a/src/commands/amikool.ts b/src/commands/amikool.ts deleted file mode 100644 index 345f8cf3..00000000 --- a/src/commands/amikool.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - CommandInteraction, - SlashCommandBuilder, - GuildMember, -} from "discord.js"; -import logger from "../utils/logger.js"; -import { ConfigService } from "../services/config-service.js"; -import { - koolResponses, - notKoolResponses, -} from "../content/amikool-responses.js"; - -const configService = ConfigService.getInstance(); - -export const data = new SlashCommandBuilder() - .setName("amikool") - .setDescription("Check if you are kool"); - -export async function execute(interaction: CommandInteraction): Promise { - try { - logger.info(`Executing amikool command for user ${interaction.user.tag}`); - - const member = interaction.member as GuildMember; - const coolRoleName = await configService.getString( - "COOL_ROLE_NAME", - "Kool", - ); - const hasCoolRole = member?.roles.cache.some( - (role) => role.name === coolRoleName, - ); - - const response = hasCoolRole - ? koolResponses[Math.floor(Math.random() * koolResponses.length)] - : notKoolResponses[Math.floor(Math.random() * notKoolResponses.length)]; - - await interaction.reply(response); - logger.info(`Amikool command completed for user ${interaction.user.tag}`); - } catch (error) { - logger.error("Error executing amikool command:", error); - await interaction.reply({ - content: "An error occurred while checking your kool status.", - ephemeral: true, - }); - } -} diff --git a/src/commands/help.ts b/src/commands/help.ts index 16f095b0..1c5dae48 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -35,11 +35,6 @@ const commandDetails: Record< usage: "/quote text: author:", configKey: "quotes.enabled", }, - amikool: { - description: "Check if you have the kool role.", - usage: "/amikool", - configKey: "amikool.enabled", - }, voicestats: { description: "View voice channel statistics (top users or per-user).", usage: "/voicestats [options]", diff --git a/src/commands/index.ts b/src/commands/index.ts index c8206b8a..a30effdd 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -1,7 +1,6 @@ import { ChatInputCommandInteraction } from "discord.js"; import logger from "../utils/logger.js"; import { execute as ping } from "./ping.js"; -import { execute as amikool } from "./amikool.js"; import { execute as voicestats } from "./voicestats.js"; import { execute as seen } from "./seen.js"; import { execute as configCommand } from "./config.js"; @@ -21,11 +20,6 @@ const commands: Record< await ping(interaction); } }, - amikool: async (interaction) => { - if (await configService.getBoolean("amikool.enabled", false)) { - await amikool(interaction); - } - }, voicestats: async (interaction) => { if (await configService.getBoolean("voicetracking.enabled", false)) { await voicestats(interaction); diff --git a/src/content/README.md b/src/content/README.md index afbc01da..396b61d0 100644 --- a/src/content/README.md +++ b/src/content/README.md @@ -10,8 +10,6 @@ contributors edit copy without touching unrelated code. | File | Used by | What it is | | --- | --- | --- | | `statuses.ts` | `services/bot-status-service.ts` | Random-rotation Discord presence pools (lonely / single-user / multi-user) | -| `amikool-responses.ts` | `commands/amikool.ts` | Reply pools for the `/amikool` command | -| `friendship-triggers.ts` | `services/friendship-listener.ts` | Phrase triggers for the passive "best ship is friendship" reply | | `notice-categories.ts` | `services/notices-channel-manager.ts` | Per-category emoji + embed color + label | | `accolades.ts` | `services/achievements-service.ts` | Display metadata (emoji / name / description) for every accolade | | `achievements.ts` | `services/achievements-service.ts` | Display metadata for time-based achievements | diff --git a/src/content/amikool-responses.ts b/src/content/amikool-responses.ts deleted file mode 100644 index 6335ecae..00000000 --- a/src/content/amikool-responses.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Reply pools for the `/amikool` slash command. - * One is picked at random depending on whether the caller has the - * configured "kool" role. - */ - -export const koolResponses = [ - "Yes, you are kool! 😎", - "Absolutely kool! 🌟", - "You're the koolest! 🎸", - "Kool status: Confirmed! ✅", - "100% kool certified! 🏆", - "Kool as ice! ❄️", - "Much kool, Such wow! 👑", - "Kool vibes detected! 🎵", - "Maximum koolness achieved! 🚀", - "Kool level: Legendary! 🏅", -] as const; - -export const notKoolResponses = [ - "No, you are not kool... yet! 😢", - "Kool status: Pending... ⏳", - "Not quite kool enough... 🥺", - "Koolness level: Needs improvement 📈", - "Almost kool, but not quite! 🎯", - "Kool potential detected! 💫", - "Koolness in progress... 🔄", - "Future kool kid! 🌱", - "Kool training required! 🎓", - "Koolness upgrade available! 💎", -] as const; diff --git a/src/content/friendship-triggers.ts b/src/content/friendship-triggers.ts deleted file mode 100644 index 2ddc4041..00000000 --- a/src/content/friendship-triggers.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Phrase triggers for the passive FriendshipListener. - * - * Matched as case-insensitive substrings against incoming messages - * (the listener lowercases the message before checking). Keep all - * entries lowercase. - */ - -export const bestTriggers = [ - "best ship", - "best eve ship", - "best eve online ship", - "what is the best ship", - "what's the best ship", -] as const; - -export const worstTriggers = [ - "worst ship", - "worst eve ship", - "worst eve online ship", - "what is the worst ship", - "what's the worst ship", -] as const; diff --git a/src/content/index.ts b/src/content/index.ts index 91d5aa86..18807bc5 100644 --- a/src/content/index.ts +++ b/src/content/index.ts @@ -1,6 +1,4 @@ export * from "./statuses.js"; -export * from "./amikool-responses.js"; -export * from "./friendship-triggers.js"; export * from "./notice-categories.js"; export * from "./accolades.js"; export * from "./achievements.js"; diff --git a/src/index.ts b/src/index.ts index 523c44df..4d5dcd42 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,7 +31,6 @@ import { BotStatusService } from "./services/bot-status-service.js"; import { QuoteChannelManager } from "./services/quote-channel-manager.js"; import { NoticesChannelManager } from "./services/notices-channel-manager.js"; import { PermissionsService } from "./services/permissions-service.js"; -import FriendshipListener from "./services/friendship-listener.js"; import { ReactionRoleService } from "./services/reaction-role-service.js"; import { PollService } from "./services/poll-service.js"; import { LeaderboardRoleService } from "./services/leaderboard-role-service.js"; @@ -606,26 +605,6 @@ async function initializeServices(): Promise { botStatusService.setOperationalStatus(); botStatusService.startVcMonitoring(); - // Initialize passive friendship listener if enabled - try { - const friendshipEnabled = await configService.getBoolean( - "fun.friendship", - false, - ); - if (friendshipEnabled) { - FriendshipListener.getInstance(client).initialize(); - } else { - logger.debug( - "Friendship listener disabled via config (fun.friendship=false)", - ); - } - } catch (flError) { - logger.warn( - "Friendship listener failed to initialize (non-critical)", - flError, - ); - } - logger.info("All services initialized successfully"); } catch (error) { logger.error("Error initializing services:", error); diff --git a/src/models/config.ts b/src/models/config.ts index b7140145..105389d9 100644 --- a/src/models/config.ts +++ b/src/models/config.ts @@ -29,10 +29,8 @@ const ConfigSchema = new Schema( required: true, enum: [ "achievements", - "amikool", "announcements", "core", - "fun", "gamification", // Kept for backward compatibility during migration "help", "leaderboard_roles", diff --git a/src/scripts/migrate-config.ts b/src/scripts/migrate-config.ts index c7a6bbb7..97bff516 100644 --- a/src/scripts/migrate-config.ts +++ b/src/scripts/migrate-config.ts @@ -100,20 +100,6 @@ const configMigrations: ConfigMigration[] = [ description: "Enable/disable ping command", defaultValue: "true", }, - { - oldKey: "ENABLE_AMIKOOL", - newKey: "amikool.enabled", - category: "amikool", - description: "Enable/disable amikool command", - defaultValue: "true", - }, - { - oldKey: "COOL_ROLE_NAME", - newKey: "amikool.role.name", - category: "amikool", - description: "Role name required to use amikool command", - defaultValue: "", - }, // Quote System (if they exist in old format) { diff --git a/src/scripts/update-settings-references.ts b/src/scripts/update-settings-references.ts index dec0fdc4..c225dac1 100644 --- a/src/scripts/update-settings-references.ts +++ b/src/scripts/update-settings-references.ts @@ -79,16 +79,6 @@ const settingReferences: SettingReference[] = [ newKey: "ping.enabled", description: "Enable/disable ping command", }, - { - oldKey: "ENABLE_AMIKOOL", - newKey: "amikool.enabled", - description: "Enable/disable amikool command", - }, - { - oldKey: "COOL_ROLE_NAME", - newKey: "amikool.role.name", - description: "Role name required to use amikool command", - }, ]; async function updateSettingsReferences(): Promise { diff --git a/src/services/command-manager.ts b/src/services/command-manager.ts index 47afc055..23c40888 100644 --- a/src/services/command-manager.ts +++ b/src/services/command-manager.ts @@ -71,7 +71,6 @@ export class CommandManager { const commandConfigs = [ { name: "ping", configKey: "ping.enabled", file: "ping" }, { name: "help", configKey: null, file: "help" }, // Always enabled - core feature - { name: "amikool", configKey: "amikool.enabled", file: "amikool" }, { name: "voicestats", configKey: "voicetracking.enabled", @@ -294,7 +293,6 @@ export class CommandManager { const commandConfigs = [ { name: "ping", configKey: "ping.enabled", file: "ping" }, { name: "help", configKey: null, file: "help" }, // Always enabled - core feature - { name: "amikool", configKey: "amikool.enabled", file: "amikool" }, { name: "voicestats", configKey: "voicetracking.enabled", diff --git a/src/services/config-schema.ts b/src/services/config-schema.ts index 4049951e..c6c13f37 100644 --- a/src/services/config-schema.ts +++ b/src/services/config-schema.ts @@ -30,8 +30,6 @@ export interface ConfigSchema { // Individual Features "ping.enabled": boolean; - "amikool.enabled": boolean; - "amikool.role.name": string; // Role name required for amikool // Quote System Settings "quotes.enabled": boolean; @@ -48,9 +46,6 @@ export interface ConfigSchema { // were declared but never read and have been removed. See issues #440/#443. "core.cleanup.channel_id": string; - // Fun / Easter Eggs - "fun.friendship": boolean; - // Rate Limiting "ratelimit.enabled": boolean; "ratelimit.max_commands": number; // Maximum commands per time window @@ -124,8 +119,6 @@ export const defaultConfig: ConfigSchema = { // Individual Features "ping.enabled": false, - "amikool.enabled": false, - "amikool.role.name": "", // Quote System Defaults "quotes.enabled": false, @@ -141,9 +134,6 @@ export const defaultConfig: ConfigSchema = { // Core Bot Logging (Discord) - only cleanup is wired up. "core.cleanup.channel_id": "", - // Fun defaults - "fun.friendship": false, - // Rate Limiting defaults "ratelimit.enabled": false, "ratelimit.max_commands": 5, // 5 commands @@ -312,14 +302,6 @@ export const settingsMetadata: Record = { description: "Enable the /ping latency check command.", category: "ping", }, - "amikool.enabled": { - description: "Enable the /amikool fun role check command.", - category: "amikool", - }, - "amikool.role.name": { - description: 'Role name required to be considered "kool" by /amikool.', - category: "amikool", - }, // Quote System "quotes.enabled": { @@ -368,12 +350,6 @@ export const settingsMetadata: Record = { category: "core", }, - // Fun / Easter Eggs - "fun.friendship": { - description: "Enable the friendship easter-egg trigger phrases.", - category: "fun", - }, - // Rate Limiting "ratelimit.enabled": { description: "Enable per-user command rate limiting.", diff --git a/src/services/config-service.ts b/src/services/config-service.ts index 488967bc..1796e8d5 100644 --- a/src/services/config-service.ts +++ b/src/services/config-service.ts @@ -155,8 +155,6 @@ export class ConfigService { "VC_ANNOUNCEMENT_CHANNEL", "VC_TRACKING_ADMIN_ROLES", "ENABLE_PING", - "ENABLE_AMIKOOL", - "COOL_ROLE_NAME", "ENABLE_QUOTES", "QUOTE_ADD_ROLES", "QUOTE_DELETE_ROLES", @@ -189,10 +187,8 @@ export class ConfigService { // Valid categories from the enum const validCategories = new Set([ "achievements", - "amikool", "announcements", "core", - "fun", "help", "ping", "quotes", diff --git a/src/services/friendship-listener.ts b/src/services/friendship-listener.ts deleted file mode 100644 index e6b9bc8f..00000000 --- a/src/services/friendship-listener.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Client, Message } from "discord.js"; -import logger from "../utils/logger.js"; -import { bestTriggers, worstTriggers } from "../content/friendship-triggers.js"; - -/** - * Passive friendship listener. - * Listens for users asking about the "best ship" or "worst ship" and replies. - */ -export class FriendshipListener { - private static instance: FriendshipListener; - private readonly client: Client; - private readonly channelCooldownMs = 30_000; // 30s per channel - private lastSent: Map = new Map(); - - private constructor(client: Client) { - this.client = client; - } - - public static getInstance(client: Client): FriendshipListener { - if (!FriendshipListener.instance) { - FriendshipListener.instance = new FriendshipListener(client); - } - return FriendshipListener.instance; - } - - public initialize(): void { - this.client.on("messageCreate", (message) => this.handleMessage(message)); - logger.info("Friendship listener initialized"); - } - - private handleMessage(message: Message): void { - if (message.author.bot) return; // ignore bots - if (!message.content) return; - if (!message.guild) return; // guild text only - - const content = message.content.toLowerCase(); - - const isBest = bestTriggers.some((t) => content.includes(t)); - const isWorst = worstTriggers.some((t) => content.includes(t)); - if (!isBest && !isWorst) return; - - // Channel cooldown logic - const channelId = message.channel.id; - const now = Date.now(); - const last = this.lastSent.get(channelId) || 0; - if (now - last < this.channelCooldownMs) { - logger.debug( - `Friendship listener cooldown active for channel ${channelId}, suppressing reply`, - ); - return; - } - - const reply = isBest - ? "<3 The best ship is friendship <3" - : "I don't know what the worst ship is, but the best ship is friendship <3"; - - message - .reply(reply) - .then(() => this.lastSent.set(channelId, now)) - .catch((err) => logger.error("Error sending friendship reply", err)); - } -} - -export default FriendshipListener; diff --git a/src/services/startup-migrator.ts b/src/services/startup-migrator.ts index b7b5c5da..4ba61f2f 100644 --- a/src/services/startup-migrator.ts +++ b/src/services/startup-migrator.ts @@ -106,20 +106,6 @@ const configMigrations: ConfigMigration[] = [ description: "Enable/disable ping command", defaultValue: true, }, - { - oldKey: "ENABLE_AMIKOOL", - newKey: "amikool.enabled", - category: "amikool", - description: "Enable/disable amikool command", - defaultValue: true, - }, - { - oldKey: "COOL_ROLE_NAME", - newKey: "amikool.role.name", // Fixed: match expected dot notation - category: "amikool", - description: "Name of the cool role for amikool command", - defaultValue: "HR", - }, // Quote System { @@ -150,7 +136,6 @@ const configMigrations: ConfigMigration[] = [ description: "Cooldown in seconds between quote additions", defaultValue: "60", }, - // Fun / Easter eggs ]; export class StartupMigrator { @@ -273,13 +258,10 @@ export class StartupMigrator { "voicetracking.announcements.schedule": "0 16 * * 5", "voicetracking.announcements.channel": "announcement", "ping.enabled": true, - "amikool.enabled": true, - "amikool.role.name": "HR", "quotes.enabled": true, "quotes.delete_roles": "None", "quotes.max_length": 1000, "quotes.cooldown": 60, - "fun.friendship": false, }; return defaultValues[key] === value; diff --git a/src/web/admin-views.ts b/src/web/admin-views.ts index 3a17d6fc..313b8730 100644 --- a/src/web/admin-views.ts +++ b/src/web/admin-views.ts @@ -499,10 +499,6 @@ const WIZARD_FEATURE_LABELS: Record = { name: "Achievements", desc: "Achievement system for voice activity.", }, - amikool: { - name: "Am I Kool", - desc: "Fun command to check kool status based on a role.", - }, reactionroles: { name: "Reaction Roles", desc: "Let users self-assign roles via reactions.", diff --git a/src/web/read-only-routes.ts b/src/web/read-only-routes.ts index a87a0d0a..1a98cbaf 100644 --- a/src/web/read-only-routes.ts +++ b/src/web/read-only-routes.ts @@ -243,7 +243,6 @@ export function createReadOnlyRouter( { key: "polls.enabled", label: "Polls" }, { key: "reactionroles.enabled", label: "Reaction Roles" }, { key: "notices.enabled", label: "Notices" }, - { key: "fun.friendship", label: "Friendship" }, ]; const features = await Promise.all( featureKeys.map(async (f) => ({ diff --git a/src/web/write-routes.ts b/src/web/write-routes.ts index e9ffd297..796ff10c 100644 --- a/src/web/write-routes.ts +++ b/src/web/write-routes.ts @@ -187,7 +187,6 @@ const WIZARD_FEATURE_SETTINGS: Record = { "achievements.announcements.enabled", "achievements.dm_notifications.enabled", ], - amikool: ["amikool.enabled", "amikool.role.name"], reactionroles: ["reactionroles.enabled", "reactionroles.message_channel_id"], announcements: ["announcements.enabled"], notices: ["notices.enabled", "notices.channel_id", "notices.header_enabled"], @@ -203,7 +202,6 @@ const WIZARD_FEATURE_ORDER = [ "voicetracking", "quotes", "achievements", - "amikool", "reactionroles", "announcements", "notices", From a0d1a3f808628b6dacb2ebc07c17237b79f6f212 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 12:38:52 +0000 Subject: [PATCH 2/3] fix(config): keep amikool/fun in Mongo category enum for legacy rows Reviewer flagged that removing these from the enum would make any re-save of an existing config row with those categories fail Mongoose enum validation. Match the existing gamification pattern: keep the enum entries with a backward-compat comment so legacy rows remain operable until ops cleans them up. The keys themselves are still gone from the schema/defaults/migrator, so they'll continue to surface as unknown-setting warnings on startup. --- src/models/config.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/models/config.ts b/src/models/config.ts index 105389d9..74ea7719 100644 --- a/src/models/config.ts +++ b/src/models/config.ts @@ -29,8 +29,10 @@ const ConfigSchema = new Schema( required: true, enum: [ "achievements", + "amikool", // Kept for backward compatibility; key removed but legacy rows may exist "announcements", "core", + "fun", // Kept for backward compatibility; key removed but legacy rows may exist "gamification", // Kept for backward compatibility during migration "help", "leaderboard_roles", From 842a9fb12dbe632be5cb1a23821799a38812016a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 12:52:38 +0000 Subject: [PATCH 3/3] docs(settings): drop Fun Features TOC link to removed section --- SETTINGS.md | 1 - 1 file changed, 1 deletion(-) diff --git a/SETTINGS.md b/SETTINGS.md index 0749cbe9..e173c3ca 100644 --- a/SETTINGS.md +++ b/SETTINGS.md @@ -37,7 +37,6 @@ Complete configuration reference for all KoolBot settings. - [Reaction Roles](#-reaction-roles) - [Leaderboard Role Rewards](#-leaderboard-role-rewards) - [Discord Logging](#-discord-logging) -- [Fun Features](#-fun-features) - [Rate Limiting](#-rate-limiting) - [Permissions & Access Control](#-permissions--access-control) - [Configuration Management](#-configuration-management) — Using the Web UI