Skip to content

Feature Roadmap

Jason Tucker edited this page May 6, 2026 · 7 revisions

Feature Roadmap

A living document describing what's planned, why, and the concrete steps to ship it. Each feature also has a card on the Bot Development project board with a matching breakdown — bring those into "In Progress" when you start, and use the implementation steps below as the punch list.


Status legend

  • Shipped — running in production, documented, and wired into /help
  • 🟡 Infrastructure ready — schema and/or /sudo → Settings toggle exists, but the user-facing behavior hasn't been built
  • 🔵 Designed — concept agreed, scope written, no infrastructure yet
  • 💭 Idea — bullet on a napkin; needs more thought before estimating

✅ Recently shipped

The 0.7.x and 0.8.x lines covered most of the original Phase 5/6 backlog:

  • Auto voice channels with persistent control panel — hubs rename in place, replacement hub spawns, attached private text channel, sticky 📋 Open Panel button, reconciler on startup
  • Voice control panel buttons — Rename, Lock/Unlock, Hosts (single panel listing all members with rank emojis), Templates (Auto / Counter / Comp 5-stack / Tryhard / Chill), Claim, Delete
  • Random tech default channel namesSloppy Ethernet style fallback when no rich-presence game is active
  • /report flow — modal → owner DM with Approve/Reject (notify or silent) → GitHub issue
  • /sudo → Settings panel — runtime config for sudo users, channels, voice cleanup delay, feature flags. Backed by bot_settings (key/value overrides) and sudo_users (members granted sudo at runtime). See Slash Commands for the full sub-panel reference.

🔵 In progress / next up

Not features — operational items that unblock everything else.

  • Verify AUTO_VOICE_CATEGORY_ID and HUB_CHANNEL_IDS in .env are correct for the ITSRI guild — see issue #3
  • Enable Presence Intent in Discord Developer Portal — issue #4 (resolved)
  • GitHub Actions auto-deploy on push to main — done end-to-end

🟡 Auto-thread on clips channel — Phase 10a

What it does. Whenever a non-bot, non-system message is posted in the configured clips channel, SquishyBot creates a public thread on that message named after the author and a slice of the message content. Threads inherit the channel's default auto-archive duration (configurable later). The bot ignores its own messages, system messages, and any message in a thread (so we don't recursively thread inside threads).

Why now. Clips are easier to discuss when each one has its own thread; otherwise channel chat fragments across video posts.

Infrastructure already in place.

  • channel.clips setting (override-able via /sudo → Settings → Channels)
  • feature.clips_auto_thread boolean toggle (already on the Features sub-panel) — currently has no effect because there's no listener
  • CLIPS_CHANNEL_ID env fallback already documented in .env.example

Missing — implementation steps.

  1. src/services/autoThread.ts — pure function shouldAutoThread(message, channelId, featureKey) that returns false on bot/system/in-thread messages and when the feature flag is off.
  2. src/bot/events/messageCreate.ts — new event handler. For each feature.<x>_auto_thread flag that's on, look up the configured channel via getSetting('channel.<x>') ?? env.<X>_CHANNEL_ID. If the message is in that channel, call message.startThread({ name, autoArchiveDuration }).
  3. Thread name generator${authorDisplayName} — ${firstNonEmptyLine.slice(0, 80)} with a fallback of just ${authorDisplayName} clip when the message is media-only. Trim trailing whitespace. Discord limits thread names to 100 chars.
  4. Register handler in src/index.ts alongside the other event registrations.
  5. Rate-limit guard — Discord limits thread creation to 50/10min per channel. Catch DiscordAPIError 429 / RATE_LIMITED and log via logger; do not retry.
  6. MessageContent intent — verify it's listed in client.ts intents. Without it, message.content arrives empty. (MessageContent is a privileged intent; enable in the Dev Portal too.)
  7. Tests / dry run — flip the flag in dev guild, post a test clip, confirm thread + name; flip off, confirm no thread.
  8. Docs/help voice section (mention auto-threading), wiki Slash Commands under a new "Automatic events" section, CHANGELOG.

Acceptance criteria.

  • Toggle feature.clips_auto_thread on → next message in channel.clips gets a thread within ~1s.
  • Toggle off → no new threads.
  • Bot's own posts don't get threaded (no recursion if the bot ever posts there).
  • Replies inside an existing thread don't trigger nesting.

🟡 Auto-thread on food channel — Phase 10b

Identical to clips, just for the food channel. Implementation is shared — once the clips path lands, this is one extra branch in messageCreate keyed off feature.food_auto_thread + channel.food.

Steps.

  1. Land the clips feature first (above).
  2. Add food to the channel-flag pair list inside the messageCreate handler.
  3. Confirm channel.food setting + feature.food_auto_thread flag work end-to-end.
  4. CHANGELOG + wiki.

🟡 Birthday pings — Phase 10c

What it does. Once a day at a configured time, the bot reads user_profiles.birthday_month / birthday_day, finds members whose birthday is today (in their timezone, if set), and posts a celebratory message in the configured birthday channel. Birthdays are month/day only — no year stored or shown unless the user explicitly opts in to age display.

Infrastructure already in place.

  • user_profiles schema has birthday_month and birthday_day integer columns
  • channel.birthday setting exists
  • BIRTHDAY_CHANNEL_ID env fallback is documented

Missing — implementation steps.

Step 1 — User-facing input UI.

  1. Add a birthday section to /squishy (or a dedicated /birthday command if cleaner) with a modal: month (number 1–12), day (number 1–31), optional timezone (string). Validate via Zod; reject Feb 30 etc.
  2. Add birthday_year_visible boolean column to user_profiles (drizzle-kit push) for opt-in age display later.
  3. Persist via the userProfiles schema. Update the User Profiles placeholder in /sudo → Settings to show the current count of users with birthdays set.

Step 2 — Daily scheduler. 4. New service src/services/birthdayScheduler.ts. On clientReady, schedule a setInterval-style job that fires once per minute and checks the wall-clock time against a configurable target (default 09:00 server time). Use a "last-run-date" file or DB row (bot_settings key birthday.last_run_date) to prevent double-firing on restart. 5. Query: SELECT user_id FROM user_profiles WHERE birthday_month = $1 AND birthday_day = $2 AND guild_id = $3. 6. For each match, format a message: 🎂 Happy birthday, <@${userId}>! 🎉 with random flavor lines (small array of variants). 7. Post to getSetting('channel.birthday') ?? env.BIRTHDAY_CHANNEL_ID.

Step 3 — Edge cases & opt-out. 8. Opt-out flag — add birthday_pings_enabled (default true) to user_profiles. Skip pings for users who opted out. 9. February 29 — if today is Feb 29 and the user's birthday is Feb 29, post normally; if today is Feb 28 in non-leap years and the user is Feb 29, decide whether to post early (default: yes, with a note). 10. Members who left the guild — skip; don't ping IDs that no longer resolve.

Step 4 — Docs. 11. README "Planned Features" → "Features" + add a row to the slash command table for the new birthday command. 12. Wiki Slash-Commands page + Database-Schema (birthday_pings_enabled, birthday_year_visible columns). 13. CHANGELOG.

Acceptance criteria.

  • A user can set their birthday via Discord without admin help.
  • On the day of their birthday, a single ping fires in the birthday channel.
  • A user who opted out gets no ping even on their birthday.
  • Bot restart on the day of someone's birthday doesn't double-ping (idempotency check).

🟡 User profile editor — Phase 11

What it does. A sudo-facing editor (under /sudo → Settings → User Profiles) plus a self-service flow (under /squishy) for users to manage their stored profile fields: display name, real name (sudo-only), staff category/department/tier, leadership title, birthday, opt-out toggles.

Infrastructure already in place.

  • user_profiles schema already has all the fields (real_name, display_name, birthday_*, staff_category, department, tier, leadership_title)
  • /sudo → Settings → User Profiles placeholder panel (read-only count) is wired

Missing — implementation steps.

  1. Self-service /profile command — modal-based editor for display_name and birthday_*. Other fields are sudo-only.
  2. Sudo browse UI — replace the placeholder panel with: paginated user search (by Discord display name or stored display name), pick a member → form view → editable rows (channel/role pickers where appropriate, modals for free text).
  3. Auto-create profile rows on first interaction — when /squishy, /profile, or any flow first touches a user, upsert a user_profiles row keyed by (guild_id, user_id).
  4. Schema additionsbirthday_pings_enabled, birthday_year_visible (covered in birthday pings task above; do whichever ships first).
  5. Cross-feature usage — pre-fill display_name into /staff request modal default, and use display_name in birthday ping copy when available.
  6. Audit logging — every sudo edit writes to a new audit_logs table mirroring otterbot's pattern (or reuses if it gets factored out).

Acceptance criteria.

  • A user can update their own display name and birthday without sudo.
  • Sudo can view + edit any field on any user, with an audit trail.
  • The User Profiles count on the Settings panel reflects reality.

🟡 Game role + channel management — Phase 12

The largest planned feature. Three coordinated pieces: definitions, opt-in, and pings.

What it does. Members opt into game-specific Discord roles. Each game has an associated channel (or category), an optional separate "ping role" used only for LFG pings, and visibility/archive toggles. /play <game> lets opted-in members ping the ping-role for a session, with metadata like party size and time.

Infrastructure already in place.

  • games schema: name, role_id, channel_id, category_id, ping_role_id, is_archived, is_visible, sort_order, aliases
  • user_game_prefs schema: per-user opt-in for view + ping
  • /sudo → Settings → Games placeholder panel (read-only count)

Missing — implementation steps.

Step 1 — Definitions (sudo-side).

  1. Replace the Games placeholder panel with a list view + add/edit/archive flow. Adding a game: name, optional aliases, role picker (or "create new role" button), channel picker, ping-role picker (optional, defaults to view role), category picker, sort order.
  2. CRUD operations + audit log entries for adds/removes/archive.

Step 2 — Member opt-in. 3. New /games command — opens a paginated select with all is_visible=true && is_archived=false games. Per-game: a row of two buttons, "View access" and "LFG pings", that toggle user_game_prefs.view_enabled and .ping_enabled. Toggling view assigns/removes the view role on Discord; toggling ping does the same for the ping role. 4. Hide archived games from the list.

Step 3 — /play LFG pings. 5. New /play <game> command with options: party_size? (int 1–32), when? (string), platform?, rank?, message? (free text 200 chars). 6. Resolve game via name or aliases (case-insensitive). 7. Post in the game's channel; ping ping_role_id. Format: ${pingRoleMention} **LFG: ${game}** — host ${authorMention}, ${partySize} needed, ${when}\n${message}. 8. Rate limiting — store last /play time per (guild_id, user_id, game_id) in a new play_pings table; reject if < 30 minutes since last. Sudo can override. 9. Anti-abuse — never @everyone or @here regardless of options. Sanitize message to strip role/user mentions outside of the host's allowed set.

Step 4 — Polish. 10. /games list shows current member count per game (pull live from the role). 11. Sort order respected in /games list. 12. Migrating archived games — keep the role but stop showing in /games.

Step 5 — Docs + project hygiene. 13. README, /help, wiki Slash-Commands + Database-Schema, CHANGELOG. 14. Move all sub-tasks above into individual project items if scope creeps; otherwise keep under one card with checkboxes.

Acceptance criteria.

  • Sudo can add a new game in under a minute via /sudo → Settings → Games.
  • A regular member can opt into View + Pings for two games via /games and see the new roles immediately.
  • /play valorant party_size:5 when:9pm pings the right role in the right channel and is rate-limited.
  • Archived games disappear from /games but existing role memberships are preserved.

💭 Ideas backlog

Light sketches — bring one to "Designed" status before it goes on the project board.

  • Voice channel "party" / LFG mode — members in an auto-channel can mark themselves LFG; the channel name appends (LFG x/y) and a button lets others join.
  • Auto-name cycling — if the rich-presence game changes mid-session, the auto-channel name updates to match (with rate-limit guards — Discord limits channel renames to 2/10min).
  • Voice channel stats — per-channel/-hub time totals, peak concurrent counts, top creators per month. Read-only sudo dashboard.
  • OC-style stock widget for other businesses — port otterbot's oc_stock pattern if a non-MKE business in the server wants the same kind of board.
  • External webhook intakePOST /webhook/<token> endpoint that posts to a configured channel (e.g. for GitHub Actions completion notices, Grafana alerts). Would require a small HTTP server alongside the Discord client.
  • Configurable thread archive duration — extend the auto-thread feature to expose 60/1440/4320/10080 minutes per channel.

How to use this roadmap

  1. When you start working on a feature, move its project board card to In Progress.
  2. Add a corresponding ## [Unreleased] entry in CHANGELOG.md with the same name.
  3. Treat the implementation steps above as a checklist — tick them off in commit messages or PR description.
  4. When the feature ships, move the card to Done, mark the row above ✅ Shipped, and prune steps from this page (keep the headline + 1-line summary so the history is readable).

Clone this wiki locally