5.9 KiB
Discord Bot — Product Context
Status: implemented and shipping. The bot runs inside the production Docker image and activates per-environment when DISCORD_TOKEN is set (prod only) — see ../../DEPLOYMENT.md §5. Keep this file updated as decisions are made — it is the canonical reference for what the bot must do.
Planned location: src/bot/ (same repo as the web app — the bot runs as a standalone long-running process importing @payload-config, following the existing src/scripts/ / src/tools/seed/ pattern).
1. Discord sign-up and account linking
Allow people in the Discord server to sign up for the site and link their Discord account — without OAuth / without logging into Discord.
- The bot checks the Discord username of the person trying to sign up.
- If the username is acceptable, the bot uses it when creating the user (i.e., the new Payload
usersdoc getsdiscordUsername= their Discord username). - This is the "linking" step: the Discord account and the site account are tied together by the matching username.
2. Recurring operations calendar + attendance (two-way sync)
Post recurring calendar events for operations, with a Yes / No / Tentative sign-up system for tracking attendance.
- When players mark their attendance in Discord, the corresponding mission attendance entry on the website is updated to show the same data.
- When a user changes their attendance on the website, the Discord calendar embed updates to reflect the change — names move between / are removed from the Yes / No / Tentative lists.
In short: Discord embed ↔ website attendance data stay in sync in both directions.
3. Announcements and app-event notifications
Post announcements and selected app events to Discord.
- Event examples: someone offers to buy a player's market listing; a shipment completes; a deposit to a bank account; etc.
- Notification should be optional, per user preference — "The options for these could live in the user's preferences."
Integration points (verified against the current codebase, 2026-08-11)
- Users collection (
src/collections/users/Users.ts): hasdiscordUsername(text, required, unique) +preferencesgroup (notifications.mutedAll/mutedTypes,display.showCallsign). No Discord snowflake/ID field yet. - Existing standalone-process pattern:
src/scripts/(game-tick, market-tick bins) andsrc/tools/seed/(backfillProfiles.ts) all doimport config from "@payload-config"→getPayload({ config }). The bot reuses this + the@/aliases andsrc/payload-types.ts. - Notification types (
src/lib/notifications/notificationTypes.ts):market:offer,market:counter,market:accept,market:reject,market:withdrawn,market:closed,market:sold,market:expired,account:discord-request.notifyUser()(fire-and-forget in-app notifications) is the existing hook point the bot could bridge into. - Missions collection (
src/collections/intelligence/Missions.ts): the operations model. Hasclassification.startDateTime(date+time picker, required, defaults to next Saturday 20:00) andclassification.estimatedDuration(minutes, default 240). AlsooperationType(main/side/external),ownershipAndStatus.status(Concept/Planning/Ready/Scheduled/Active/Completed/Cancelled) +visibility,missionRoles, campaign link, briefing/objectives, server details. No attendance and no recurrence fields exist. - Event log:
emitGameEvent()— the bot can log its own actions. - Reusable libs the bot can call directly: banking (
applyTransaction,ensurePersonalAccount), market (buyListing, negotiations), notifications, storage rules.
Locked decisions (2026-08-11)
- Sign-up includes
steamId— the player provides it as part of/signup(it is required on the Users collection). - The operations model is the Missions collection — no new operations collection. Any scheduling/attendance work builds on
missions. - Attendance is per-occurrence. Each mission has one
startDateTime, so an occurrence == a mission; attendance records are per (mission, user). - App → Discord bridge: the bot polls
user-notificationswith direct DB access and mirrors new notifications to Discord DMs for users who opted in. No webhook plumbing; existingnotifyUser()call sites stay untouched.
Gaps the bot work will need to fill
- No attendance model exists (verified: zero matches for attendance/occurrence/recurring in
src/). The website-side attendance model must be built as part of this feature. Lean: a new collection (e.g.,mission-attendances:mission→ missions,user→ users,responseyes/no/tentative), consistent with the app's LedgerEntries precedent. - No Discord snowflake ID on users.
discordUsernameis a username string ("Z8MB1E"), not a Discord user ID. DMs / role-gating / reliable linking need the snowflake (add adiscordIdfield +/linkcommand or guild-join mapping). - No Discord notification preferences. Current prefs mute in-app notifications only; Discord-notification toggles would extend the
preferencesgroup.
Open design questions
- Sign-up flow: what does the player type in Discord (username + password + display name + steam ID?), and how is the initial password handled? Lean:
/signupin a DM; bot creates the user with a generated temp password, DMs it, player changes it on first login. - Recurrence type: same weekly slot? How far ahead are events posted? (Missions are one-shot docs — is a new mission created per operation night?)
- Attendance model details: unique (mission, user) enforced in code; how web changes reach the bot (the chosen bridge is the
user-notificationspoll — attendance changes may need their own notification type so the bot reconciles the embed). - Announcements: which channels, who can trigger (Discord roles → app roles mapping), and how app → Discord pushes work.