diff --git a/COMMANDS.md b/COMMANDS.md index 0bb0525..a077bdf 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 8fa226e..19040bb 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 485706e..4f3dff2 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 57aca7b..e173c3c 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 @@ -141,8 +140,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 +631,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 +755,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 +853,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 cfc02c5..77aeb1d 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 8260c62..0000000 --- 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 0df92a8..97b0848 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 fed45ef..0000000 --- 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 3f9257e..c7c2d20 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 fed3b61..0000000 --- a/src/commands/amikool.ts +++ /dev/null @@ -1,42 +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 coolRoleId = await configService.getString("amikool.role_id", ""); - const hasCoolRole = coolRoleId - ? Boolean(member?.roles.cache.has(coolRoleId)) - : false; - - 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 16f095b..1c5dae4 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 c8206b8..a30effd 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 afbc01d..396b61d 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 6335eca..0000000 --- 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 2ddc404..0000000 --- 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 91d5aa8..18807bc 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 dacd7f5..e3e03fe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,7 +32,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"; @@ -612,26 +611,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 b714014..74ea771 100644 --- a/src/models/config.ts +++ b/src/models/config.ts @@ -29,10 +29,10 @@ const ConfigSchema = new Schema( required: true, enum: [ "achievements", - "amikool", + "amikool", // Kept for backward compatibility; key removed but legacy rows may exist "announcements", "core", - "fun", + "fun", // Kept for backward compatibility; key removed but legacy rows may exist "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 c7a6bbb..97bff51 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 dec0fdc..c225dac 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 47afc05..23c4088 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 076c280..458f154 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_id": string; // Role ID required to be considered "kool" by /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_id": "", // 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 @@ -313,15 +303,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_id": { - description: - 'Discord role ID required to be considered "kool" by /amikool.', - category: "amikool", - }, // Quote System "quotes.enabled": { @@ -370,12 +351,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 48f02d2..5753cfd 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", @@ -177,7 +175,6 @@ export class ConfigService { // Pre-rename keys preserved until the Discord-ready nameβ†’ID // migrator runs (see name-id-migrator.ts). "voicetracking.announcements.channel", - "amikool.role.name", ]; knownOldKeys.forEach((key) => validKeys.add(key)); @@ -193,10 +190,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 e6b9bc8..0000000 --- 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/name-id-migrator.ts b/src/services/name-id-migrator.ts index 43d1881..641233a 100644 --- a/src/services/name-id-migrator.ts +++ b/src/services/name-id-migrator.ts @@ -8,11 +8,10 @@ import { ConfigService } from "./config-service.js"; * name-keyed row. * * Several config keys historically stored Discord entity references as - * **names** (e.g. `voicetracking.announcements.channel = "voice-stats"`, - * `amikool.role.name = "HR"`). Names aren't unique and aren't selector- - * friendly: the v1.0 WebUI work renders these fields as `` dropdowns populated from the guild + * cache, which fundamentally requires the stored value to be an ID. * * This module is the one-shot translator. It runs once per bot start, after * Discord is ready (so the guild cache is hydrated) and before services @@ -52,15 +51,6 @@ const RENAMES: RenameSpec[] = [ return channel ? channel.id : null; }, }, - { - oldKey: "amikool.role.name", - newKey: "amikool.role_id", - description: "/amikool role", - resolve: (guild, name) => { - const role = guild.roles.cache.find((r) => r.name === name); - return role ? role.id : null; - }, - }, ]; export async function runNameToIdMigrations( diff --git a/src/services/startup-migrator.ts b/src/services/startup-migrator.ts index dfc6d24..5063584 100644 --- a/src/services/startup-migrator.ts +++ b/src/services/startup-migrator.ts @@ -100,13 +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, - }, // Quote System { @@ -137,7 +130,6 @@ const configMigrations: ConfigMigration[] = [ description: "Cooldown in seconds between quote additions", defaultValue: "60", }, - // Fun / Easter eggs ]; export class StartupMigrator { @@ -259,12 +251,10 @@ export class StartupMigrator { "voicetracking.announcements.enabled": true, "voicetracking.announcements.schedule": "0 16 * * 5", "ping.enabled": true, - "amikool.enabled": true, "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 3a17d6f..313b873 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 a87a0d0..1a98cba 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 3146fde..5a3c817 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_id"], 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",