feat: channel-level quiet mode via bot mention (#173)#213
Conversation
- Add quietMode.js module with Redis+memory storage - Parse duration from natural language (30m, 1 hour, etc.) - Permission gated via config.quietMode.allowedRoles - Commands: quiet, unquiet, status - Suppress AI responses during quiet mode in events.js - Add quietMode section to config.json (disabled by default) - Add quietMode to configAllowlist.js for dashboard editing
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (7)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Filename | Overview |
|---|---|
| src/modules/quietMode.js | New quiet mode module with Redis/memory fallback, natural language duration parsing, permission checks, and comprehensive error handling |
| src/modules/events.js | Integrated quiet mode checks in message handler with proper error isolation and feature gating |
| src/api/utils/configValidation.js | Added quietMode validation schema; missing defaultDurationMinutes property (acknowledged in previous review threads) |
| tests/modules/quietMode.test.js | Comprehensive test suite with 41 tests covering duration parsing, permissions, storage (Redis + memory fallback), and all command scenarios |
Sequence Diagram
sequenceDiagram
participant User
participant Discord
participant EventHandler as events.js
participant QuietMode as quietMode.js
participant Storage as Redis/Memory
participant AI as Triage/AI
User->>Discord: @bot quiet 30m
Discord->>EventHandler: MessageCreate event
EventHandler->>QuietMode: handleQuietCommand(message, config)
QuietMode->>QuietMode: Check permissions
QuietMode->>QuietMode: Parse duration (30m → 1800s)
QuietMode->>Storage: setQuiet(guildId, channelId, until, userId)
Storage-->>QuietMode: OK
QuietMode-->>EventHandler: return true (was quiet command)
EventHandler->>User: "Going quiet for 30 minutes"
Note over EventHandler,AI: Bot is now in quiet mode for this channel
User->>Discord: Regular message
Discord->>EventHandler: MessageCreate event
EventHandler->>QuietMode: isQuietMode(guildId, channelId)
QuietMode->>Storage: getQuiet(guildId, channelId)
Storage-->>QuietMode: {until: timestamp, by: userId}
QuietMode-->>EventHandler: true (quiet mode active)
EventHandler->>EventHandler: return early (suppress AI)
Note over EventHandler,AI: AI response suppressed
User->>Discord: @bot unquiet
Discord->>EventHandler: MessageCreate event
EventHandler->>QuietMode: handleQuietCommand(message, config)
QuietMode->>Storage: clearQuiet(guildId, channelId)
Storage-->>QuietMode: OK
QuietMode-->>EventHandler: return true
EventHandler->>User: "Quiet mode lifted — I'm back!"
Last reviewed commit: 4c3f2c7
There was a problem hiding this comment.
Pull request overview
Adds a per-channel “quiet mode” feature that lets moderators (or other configured roles) temporarily suppress the bot’s AI behavior in a channel via bot-mention commands, with Redis-backed TTL storage and an in-memory fallback.
Changes:
- Introduces
src/modules/quietMode.jsimplementing quiet/unquiet/status commands, duration parsing, and Redis+memory storage. - Integrates quiet mode checks into the message event flow to suppress AI responses / triage accumulation while active.
- Exposes
quietModeto the dashboard config editor allowlist and adds a defaultquietModesection toconfig.json, plus comprehensive module tests.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/modules/quietMode.js |
New module for parsing, permission checks, and Redis/memory-backed quiet state management. |
src/modules/events.js |
Hooks quiet mode command handling + quiet checks into mention/reply and accumulation paths. |
src/api/utils/configAllowlist.js |
Adds quietMode to SAFE_CONFIG_KEYS so it can be edited via API/dashboard. |
config.json |
Adds default quietMode config (disabled), but also includes a full-file reformat. |
tests/modules/quietMode.test.js |
Adds Vitest coverage for parsing, permissions, storage, and command handling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…urationMinutes config - events.js: Wrap isQuietMode() calls in guildConfig.quietMode?.enabled check to avoid unnecessary Redis lookups and prevent stale records from suppressing AI responses when the feature is disabled (PRRT_kwDORICdSM5xdbmp, PRRT_kwDORICdSM5xdbmx) - quietMode.js: Add TTL validation in setQuiet() to guard against 0, negative, or NaN values that would error in Redis (PRRT_kwDORICdSM5xdbm3) - quietMode.js: Update parseDurationFromContent() to accept config parameter and honor guildConfig.quietMode.maxDurationMinutes. Also clamp defaultSeconds to the effective max (PRRT_kwDORICdSM5xdbm_) - configValidation.js: Add quietMode schema entry with enabled, maxDurationMinutes, and allowedRoles properties (PRRT_kwDORICdSM5xdbnH)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
* feat: quiet mode per-channel via bot mention (#173) - Add quietMode.js module with Redis+memory storage - Parse duration from natural language (30m, 1 hour, etc.) - Permission gated via config.quietMode.allowedRoles - Commands: quiet, unquiet, status - Suppress AI responses during quiet mode in events.js - Add quietMode section to config.json (disabled by default) - Add quietMode to configAllowlist.js for dashboard editing * test: add quiet mode tests (41 tests, all passing) * style: fix biome formatting in quietMode.js, events.js, and test * fix(web): fix ai-feedback-stats TypeScript and formatting errors * fix: gate quiet mode checks on enabled flag, validate TTL, honor maxDurationMinutes config - events.js: Wrap isQuietMode() calls in guildConfig.quietMode?.enabled check to avoid unnecessary Redis lookups and prevent stale records from suppressing AI responses when the feature is disabled (PRRT_kwDORICdSM5xdbmp, PRRT_kwDORICdSM5xdbmx) - quietMode.js: Add TTL validation in setQuiet() to guard against 0, negative, or NaN values that would error in Redis (PRRT_kwDORICdSM5xdbm3) - quietMode.js: Update parseDurationFromContent() to accept config parameter and honor guildConfig.quietMode.maxDurationMinutes. Also clamp defaultSeconds to the effective max (PRRT_kwDORICdSM5xdbm_) - configValidation.js: Add quietMode schema entry with enabled, maxDurationMinutes, and allowedRoles properties (PRRT_kwDORICdSM5xdbnH) * style: fix biome formatting in quietMode.js and ai-feedback-stats.tsx
* feat: quiet mode per-channel via bot mention (#173) - Add quietMode.js module with Redis+memory storage - Parse duration from natural language (30m, 1 hour, etc.) - Permission gated via config.quietMode.allowedRoles - Commands: quiet, unquiet, status - Suppress AI responses during quiet mode in events.js - Add quietMode section to config.json (disabled by default) - Add quietMode to configAllowlist.js for dashboard editing * test: add quiet mode tests (41 tests, all passing) * style: fix biome formatting in quietMode.js, events.js, and test * fix(web): fix ai-feedback-stats TypeScript and formatting errors * fix: gate quiet mode checks on enabled flag, validate TTL, honor maxDurationMinutes config - events.js: Wrap isQuietMode() calls in guildConfig.quietMode?.enabled check to avoid unnecessary Redis lookups and prevent stale records from suppressing AI responses when the feature is disabled (PRRT_kwDORICdSM5xdbmp, PRRT_kwDORICdSM5xdbmx) - quietMode.js: Add TTL validation in setQuiet() to guard against 0, negative, or NaN values that would error in Redis (PRRT_kwDORICdSM5xdbm3) - quietMode.js: Update parseDurationFromContent() to accept config parameter and honor guildConfig.quietMode.maxDurationMinutes. Also clamp defaultSeconds to the effective max (PRRT_kwDORICdSM5xdbm_) - configValidation.js: Add quietMode schema entry with enabled, maxDurationMinutes, and allowedRoles properties (PRRT_kwDORICdSM5xdbnH) * style: fix biome formatting in quietMode.js and ai-feedback-stats.tsx
* security: escape user content in triage prompt delimiters (#164) Add escapePromptDelimiters() to HTML-encode < and > in user-supplied message content before it is inserted between XML-style section tags in the LLM prompt. Without escaping, a crafted message containing the literal text `</messages-to-evaluate>` could break out of the user-content section and inject attacker-controlled instructions into the prompt structure. Changes: - Add escapePromptDelimiters(text) utility exported from triage-prompt.js - Apply escape to m.content and m.replyTo.content in buildConversationText() - Add 13 new tests covering the escape function and injection scenarios Closes #164 * security: escape & chars and author fields in prompt delimiters * fix(security): escape & in prompt delimiters and escape author fields - Add & → & escape first in escapePromptDelimiters() to prevent HTML entity bypass attacks (e.g. </messages-to-evaluate>) - Also escape m.author and m.replyTo.author since Discord display names are user-controlled and can contain < / > characters Addresses review feedback on PR #204. * fix: guard replyTo.content before .slice() to handle null/undefined * perf: SQL-based conversation pagination + missing DB indexes (#221) Fixes three performance bottlenecks identified in code review of recently merged features (PR #121 conversations viewer, PR #190 AI feedback). ## Changes ### migrations/004_performance_indexes.cjs (new) Four new indexes targeting hot query paths: - idx_ai_feedback_guild_created (guild_id, created_at DESC) getFeedbackTrend() and getRecentFeedback() filtered by guild_id AND created_at but only had a single-column guild_id index, forcing a full guild scan + sort on every trend/recent call. - idx_conversations_content_trgm (GIN, pg_trgm) content ILIKE '%...%' search was a sequential scan. GIN/trgm index reduces this from O(n) to O(log n * trigram matches). Requires pg_trgm extension (added idempotently). - idx_conversations_guild_created (guild_id, created_at DESC) Default 30-day listing query filters guild_id + created_at. The existing 3-column (guild_id, channel_id, created_at) composite is suboptimal when channel_id is not in the predicate. - idx_flagged_messages_guild_message (guild_id, message_id) Conversation detail + flag endpoints query flagged_messages by guild_id AND message_id = ANY(...). Existing index only covers (guild_id, status). ### src/api/routes/conversations.js **GET / — Replace in-memory pagination with SQL CTE grouping** Before: fetched up to 10,000 message rows into Node memory, grouped them in JavaScript (O(n) time + memory), then sliced for pagination. Every page request loaded the full 10k row dataset. After: single SQL query using window functions (LAG + SUM OVER) to identify conversation boundaries and aggregate summaries directly. COUNT(*) OVER() provides total count without a second query. Pagination happens at the DB with LIMIT/OFFSET on summary rows. Memory overhead is now proportional to page size (default 25), not total conversation volume. Removed now-unused buildConversationSummary() helper (logic inlined into the SQL-side aggregation). **POST /:conversationId/flag — Parallel verification queries** Before: msgCheck and anchorCheck ran sequentially (~2× RTT). After: both run in parallel via Promise.all (1× RTT for verification). ### tests/api/routes/conversations.test.js Updated 'should return paginated conversations' test to mock the new SQL CTE response shape (pre-aggregated summary rows) instead of raw message rows. All 41 conversation tests pass. * feat: channel-level quiet mode via bot mention (#173) (#213) * feat: quiet mode per-channel via bot mention (#173) - Add quietMode.js module with Redis+memory storage - Parse duration from natural language (30m, 1 hour, etc.) - Permission gated via config.quietMode.allowedRoles - Commands: quiet, unquiet, status - Suppress AI responses during quiet mode in events.js - Add quietMode section to config.json (disabled by default) - Add quietMode to configAllowlist.js for dashboard editing * test: add quiet mode tests (41 tests, all passing) * style: fix biome formatting in quietMode.js, events.js, and test * fix(web): fix ai-feedback-stats TypeScript and formatting errors * fix: gate quiet mode checks on enabled flag, validate TTL, honor maxDurationMinutes config - events.js: Wrap isQuietMode() calls in guildConfig.quietMode?.enabled check to avoid unnecessary Redis lookups and prevent stale records from suppressing AI responses when the feature is disabled (PRRT_kwDORICdSM5xdbmp, PRRT_kwDORICdSM5xdbmx) - quietMode.js: Add TTL validation in setQuiet() to guard against 0, negative, or NaN values that would error in Redis (PRRT_kwDORICdSM5xdbm3) - quietMode.js: Update parseDurationFromContent() to accept config parameter and honor guildConfig.quietMode.maxDurationMinutes. Also clamp defaultSeconds to the effective max (PRRT_kwDORICdSM5xdbm_) - configValidation.js: Add quietMode schema entry with enabled, maxDurationMinutes, and allowedRoles properties (PRRT_kwDORICdSM5xdbnH) * style: fix biome formatting in quietMode.js and ai-feedback-stats.tsx * feat: audit log improvements — CSV/JSON export and real-time WebSocket stream (#215) * feat: audit log improvements — CSV/JSON export, real-time WebSocket stream - Add GET /:id/audit-log/export endpoint (CSV and JSON, up to 10k rows) - Add /ws/audit-log WebSocket server for real-time audit entry broadcast - Refactor buildFilters() shared helper to eliminate duplication - Hook broadcastAuditEntry() into insertAuditEntry (RETURNING id+created_at) - Wire setupAuditStream/stopAuditStream into startServer/stopServer lifecycle - Add escapeCsvValue/rowsToCsv helpers with full test coverage - 30 route tests + 17 WebSocket stream tests, all green Closes #136 * fix: PR #215 review feedback - audit stream fixes - ws.ping() crash: guard with readyState check + try/catch to avoid crashing heartbeat interval when socket not OPEN - stopAuditStream race: make setupAuditStream async and await stopAuditStream() to prevent concurrent WebSocketServer creation - Query param array coercion: add typeof === 'string' checks for startDate/endDate to handle Express string|string[]|undefined - CSV CRLF quoting: add \r to RFC 4180 special-char check for proper Windows line ending handling - Test timeouts: make AUTH_TIMEOUT_MS configurable via AUDIT_STREAM_AUTH_TIMEOUT_MS env var, use 100ms in tests * feat: voice channel activity tracking — join/leave/move, leaderboard, export (#212) * feat: add voice_sessions migration (#135) * feat: add voice tracking module — join/leave/move/flush/leaderboard (#135) * feat: wire voiceStateUpdate handler into event registration (#135) * feat: add /voice command — leaderboard, stats, export subcommands (#135) * feat: add voice config defaults to config.json (#135) * feat: wire voice flush start/stop into bot lifecycle (#135) * feat: add voice to config API allowlist (#135) * fix: SQL UPDATE subquery for closeSession, fix import order (#135) * fix(voice): resolve race conditions and missing config schema - Fix openSession: update in-memory state only AFTER DB INSERT succeeds - Fix closeSession: delete from in-memory state only AFTER DB UPDATE succeeds - Fix: allow closeSession on leave/move even when feature is disabled - Fix migration: add UNIQUE constraint to partial index to prevent duplicates - Fix: move 'Voice join' log to after openSession succeeds - Add voice config to CONFIG_SCHEMA for validation --------- Co-authored-by: Bill <[email protected]> * feat(dashboard): auto-save config with 500ms debounce (#199) * feat(dashboard): replace manual save with auto-save (500ms debounce) - Remove 'Save Changes' button; saving now fires automatically 500ms after the last config change (no changes → no network call) - Add saveStatus state ('idle' | 'saving' | 'saved' | 'error') with AutoSaveStatus component showing spinner, check, or error+retry - Add isLoadingConfigRef guard so the initial fetchConfig load never triggers a spurious PATCH - Ctrl+S still works: clears debounce timer and saves immediately - Keep 'beforeunload' warning for validation errors that block save - Replace yellow unsaved-changes banner with a destructive validation error banner (only shown when save is actually blocked) - Error state shows 'Save failed' + 'Retry' button for user recovery Closes #189 * test(dashboard): add auto-save tests for ConfigEditor - No PATCH on initial config load - Validation error banner suppresses auto-save - 'Saving...' spinner visible while PATCH in-flight - 'Save failed' + Retry button on PATCH error * fix(dashboard): prevent fetchConfig from overwriting saveStatus after successful save Add skipSaveStatusReset parameter to fetchConfig so that post-save reloads preserve the 'saved' status indicator instead of immediately resetting to 'idle'. * test(dashboard): use fake timers, restore vi.stubGlobal, fix assertions, add idle/saved coverage - Replace real setTimeout delays with vi.useFakeTimers() + vi.advanceTimersByTimeAsync() for deterministic, fast debounce tests - Add afterEach cleanup: vi.unstubAllGlobals() + vi.useRealTimers() - Replace toBeTruthy() with toBeInTheDocument() for Testing Library queries - Add idle state test (no status indicator shown after load) - Add saved state test (shows 'Saved' after successful save) - Update file-level comment to list all four states --------- Co-authored-by: Bill Chirico <[email protected]> * feat: Reaction role menus (#162) (#205) * feat: reaction role menus - core module, command, event hooks, migration Implements issue #162: reaction role menus. - Add migration 004 creating reaction_role_menus and reaction_role_entries tables - Add src/modules/reactionRoles.js with DB helpers, embed builder, event handlers - Add src/commands/reactionrole.js with /reactionrole create|add|remove|delete|list - Wire handleReactionRoleAdd/Remove into registerReactionHandlers in events.js Roles are granted on reaction add and revoked on reaction remove. All mappings persist in PostgreSQL across bot restarts. * test: reaction role menus - 40 tests covering module and command - tests/modules/reactionRoles.test.js: resolveEmojiString, buildReactionRoleEmbed, all DB helpers, handleReactionRoleAdd, handleReactionRoleRemove - tests/commands/reactionrole.test.js: all 5 subcommands (create, add, remove, delete, list) including error paths and guild ownership checks - Fix biome lint: import sort order + unused import removal * fix: remove unused import in reactionrole command --------- Co-authored-by: Bill Chirico <[email protected]> * fix(security): validate GitHub owner/repo format before gh CLI call (#198) * fix(security): validate GitHub owner/repo format before gh CLI call Prevents API path traversal by validating owner/repo segments against a strict allowlist regex before interpolating them into the gh CLI invocation. Adds: - VALID_GH_NAME regex (/^[a-zA-Z0-9._-]+$/) - isValidGhRepo() helper (exported for testing) - Guard in fetchRepoEvents() — returns [] and warns on invalid input - Strengthened guard in pollGuildFeed() split logic Fixes #160 * test(security): add validation tests for GitHub owner/repo format Covers isValidGhRepo(), VALID_GH_NAME regex, and fetchRepoEvents() validation guard introduced in fix for #160. 19 new tests verify: - Valid alphanumeric/dot/hyphen/underscore names pass - Path traversal (../../etc/passwd) is rejected at both entry points - Slashes, empty strings, non-strings, spaces all rejected - Shell metacharacters (; && $()) blocked - gh CLI is NOT invoked when validation fails - warn() fires with the invalid values (observable audit trail) - Valid owner/repo still reach gh CLI unchanged * fix(security): reject pure-dot owner/repo names to prevent path traversal * test(githubFeed): add tests for pure-dot path traversal bypass --------- Co-authored-by: Bill Chirico <[email protected]> --------- Co-authored-by: Bill <[email protected]> Co-authored-by: Bill Chirico <[email protected]>
* feat: add role_menu_templates migration (#135) * feat: add roleMenuTemplates module with built-ins, CRUD, and share (#135) * feat: add /rolemenu command with template CRUD, apply, share (#135) * feat: seed built-in role menu templates on startup (#135) * test: add roleMenuTemplates tests — 36 passing (#135) * test: add /rolemenu command tests — 19 passing (#135) * fix: typo hasModeatorPerms → hasModeratorPerms * perf: SQL-based conversation pagination + missing DB indexes (#221) Fixes three performance bottlenecks identified in code review of recently merged features (PR #121 conversations viewer, PR #190 AI feedback). ## Changes ### migrations/004_performance_indexes.cjs (new) Four new indexes targeting hot query paths: - idx_ai_feedback_guild_created (guild_id, created_at DESC) getFeedbackTrend() and getRecentFeedback() filtered by guild_id AND created_at but only had a single-column guild_id index, forcing a full guild scan + sort on every trend/recent call. - idx_conversations_content_trgm (GIN, pg_trgm) content ILIKE '%...%' search was a sequential scan. GIN/trgm index reduces this from O(n) to O(log n * trigram matches). Requires pg_trgm extension (added idempotently). - idx_conversations_guild_created (guild_id, created_at DESC) Default 30-day listing query filters guild_id + created_at. The existing 3-column (guild_id, channel_id, created_at) composite is suboptimal when channel_id is not in the predicate. - idx_flagged_messages_guild_message (guild_id, message_id) Conversation detail + flag endpoints query flagged_messages by guild_id AND message_id = ANY(...). Existing index only covers (guild_id, status). ### src/api/routes/conversations.js **GET / — Replace in-memory pagination with SQL CTE grouping** Before: fetched up to 10,000 message rows into Node memory, grouped them in JavaScript (O(n) time + memory), then sliced for pagination. Every page request loaded the full 10k row dataset. After: single SQL query using window functions (LAG + SUM OVER) to identify conversation boundaries and aggregate summaries directly. COUNT(*) OVER() provides total count without a second query. Pagination happens at the DB with LIMIT/OFFSET on summary rows. Memory overhead is now proportional to page size (default 25), not total conversation volume. Removed now-unused buildConversationSummary() helper (logic inlined into the SQL-side aggregation). **POST /:conversationId/flag — Parallel verification queries** Before: msgCheck and anchorCheck ran sequentially (~2× RTT). After: both run in parallel via Promise.all (1× RTT for verification). ### tests/api/routes/conversations.test.js Updated 'should return paginated conversations' test to mock the new SQL CTE response shape (pre-aggregated summary rows) instead of raw message rows. All 41 conversation tests pass. * feat: channel-level quiet mode via bot mention (#173) (#213) * feat: quiet mode per-channel via bot mention (#173) - Add quietMode.js module with Redis+memory storage - Parse duration from natural language (30m, 1 hour, etc.) - Permission gated via config.quietMode.allowedRoles - Commands: quiet, unquiet, status - Suppress AI responses during quiet mode in events.js - Add quietMode section to config.json (disabled by default) - Add quietMode to configAllowlist.js for dashboard editing * test: add quiet mode tests (41 tests, all passing) * style: fix biome formatting in quietMode.js, events.js, and test * fix(web): fix ai-feedback-stats TypeScript and formatting errors * fix: gate quiet mode checks on enabled flag, validate TTL, honor maxDurationMinutes config - events.js: Wrap isQuietMode() calls in guildConfig.quietMode?.enabled check to avoid unnecessary Redis lookups and prevent stale records from suppressing AI responses when the feature is disabled (PRRT_kwDORICdSM5xdbmp, PRRT_kwDORICdSM5xdbmx) - quietMode.js: Add TTL validation in setQuiet() to guard against 0, negative, or NaN values that would error in Redis (PRRT_kwDORICdSM5xdbm3) - quietMode.js: Update parseDurationFromContent() to accept config parameter and honor guildConfig.quietMode.maxDurationMinutes. Also clamp defaultSeconds to the effective max (PRRT_kwDORICdSM5xdbm_) - configValidation.js: Add quietMode schema entry with enabled, maxDurationMinutes, and allowedRoles properties (PRRT_kwDORICdSM5xdbnH) * style: fix biome formatting in quietMode.js and ai-feedback-stats.tsx * Fix: unterminated string in rolemenu.js * Fix: lint issues and formatting * fix: deterministic template lookup and correct roleId precedence - Add ORDER BY to getTemplateByName for deterministic results - Fix roleId precedence to preserve existing roleIds during merge - Truncate Discord embed field values to 1024 chars * fix: test assertion matches comment intent The test expected template roleId to win, but the comment said existing should take precedence. Fixed assertion to match documented behavior. * fix: filter empty roleIds and only enable when valid options exist - Filter out options with empty roleIds before saving - Only enable role menu for non-built-in templates with valid options - Add user-facing note when options are filtered * chore: remove unused _MAX_DESCRIPTION_LEN constant * fix: case-insensitive unique index for template names Use LOWER(name) in unique index to match case-insensitive queries and prevent duplicate templates differing only by case. * fix(roleMenuTemplates): add type validation for roleId and description - validateTemplateOptions now validates that optional roleId and description fields are strings when present - Update JSDoc @see reference from issue #135 (voice tracking) to issue #216 (role menu templates) - Update ON CONFLICT clause to use constraint name for consistency with the new LOWER(name) index --------- Co-authored-by: Bill <[email protected]> Co-authored-by: Bill Chirico <[email protected]>
Summary
Implements channel-level quiet mode — moderators can silence the bot in a specific channel by mentioning it with
@bot quiet.Closes #173
Changes
Core Module (
src/modules/quietMode.js)handleQuietCommand(message, config)— parses quiet/unquiet/status commands from bot mentionsisQuietMode(guildId, channelId)— check if bot is currently silenced in a channel30m,2h,1 hour,for 30 minutesconfig.quietMode.allowedRoles(any / moderator / admin / specific role IDs)Commands
@bot quiet@bot quiet for 1 hour@bot quiet 45m@bot unquiet/resume@bot statusEvents (
src/modules/events.js)Config (
config.json){ "quietMode": { "enabled": false, "allowedRoles": ["moderator"], "defaultDurationMinutes": 30, "maxDurationMinutes": 1440 } }Dashboard (
src/api/utils/configAllowlist.js)Added
quietModetoSAFE_CONFIG_KEYSso the web dashboard can edit it.Files Changed
src/modules/quietMode.js— new module (345 lines)src/modules/events.js— integrate quiet mode checksconfig.json— add quietMode config section (disabled by default)src/api/utils/configAllowlist.js— add quietMode to allowlisttests/modules/quietMode.test.js— 41 testsTesting
Notes