Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

Discord bot for the [Fleetyards](https://fleetyards.net) community server.

## Scope

**Single-tenant.** This bot is built for the Fleetyards Discord only. The Discord application has `bot_public: false`, so it cannot be added to other servers, and the config hardcodes Fleetyards-specific guild/channel/message/role IDs anyway.

Anything we want to make available to other Star Citizen servers (slash-command ship lookups, hangar lookups, release announcements via webhook, etc.) belongs in a separate, public, multi-tenant bot — not yet started. yards-bot's scope is verification, role management, and other Fleetyards-only mod/admin work.

## Status

v0.1 — scaffold only. See [`docs/exec-plans/v1.md`](docs/exec-plans/v1.md) for the v1 design and feature scope.
Expand Down Expand Up @@ -36,7 +42,7 @@ pnpm dev

The bot needs `View Channel`, `Read Message History`, and `Manage Roles` on the target guild. Its top role must sit **above** any role it grants — Discord silently rejects role changes otherwise.

The `Server Members Intent` (privileged) must be enabled in the [Developer Portal](https://discord.com/developers/applications) under the bot's settings.
No privileged intents are required. v1 uses `Guilds` + `GuildMessageReactions` and fetches members on demand via REST, so the `Server Members Intent` does not need to be enabled.

## Deployment

Expand Down
35 changes: 20 additions & 15 deletions docs/exec-plans/v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@

Replace the manual reaction-sweep with a real-time Discord bot. No database, no Rails integration. React-with-emoji grants a role; un-reacting revokes it. A periodic backfill sweep heals anything the gateway missed.

## Scope

**Single-tenant.** yards-bot only runs against the Fleetyards Discord. The Discord application is private (`bot_public: false`) and the config hardcodes Fleetyards-specific IDs. Anything we'd want to expose to other Star Citizen servers (slash-command lookups, release announcements, etc.) belongs in a **separate, future, public bot** — not in this repo.

## Out of scope for v1

Account linking with the Fleetyards Rails app, slash commands, audit-to-channel, captcha, welcome DMs, role sync with fleets/supporters. Tracked separately for v2+.
Account linking with the Fleetyards Rails app, slash commands, audit-to-channel, captcha, welcome DMs, role sync with fleets/supporters. Multi-tenant operation. Tracked separately for v2+ (or, for the multi-tenant pieces, the future public bot).

## Stack

Expand Down Expand Up @@ -73,8 +77,8 @@ sweep:

## Discord intents & partials

- Intents: `Guilds`, `GuildMembers` *(privileged — toggle in dev portal)*, `GuildMessageReactions`
- Partials: `Message`, `Channel`, `Reaction` — required so the bot receives reaction events on messages older than its uptime
- Intents: `Guilds`, `GuildMessageReactions`. No privileged intents required — members are fetched on demand via REST instead of cached via the `GuildMembers` intent.
- Partials: `Message`, `Channel`, `Reaction`, `User`. The first three let the bot receive reaction events on messages older than its uptime; `User` is required for `MESSAGE_REACTION_REMOVE` on uncached users, because the gateway payload only ships `user_id` (not a member object like the add event does), and without it discord.js silently drops the event.

## Required bot permissions in the guild

Expand Down Expand Up @@ -130,20 +134,21 @@ sweep:

## Implementation order

1. Repo scaffold + tsconfig + biome + CI — **done**.
2. Config loader with zod.
3. discord.js client + READY logging.
4. Sweep (TS port of the original bash script) with unit tests.
5. `reactionAdd` + `reactionRemove` handlers.
6. Health endpoint.
7. Dockerfile finalization + local docker-compose.
8. Kamal deploy config.
9. AppSignal wiring.
10. README updates with the role-hierarchy gotcha + bot invite URL.
1. Repo scaffold + tsconfig + biome + CI — **done** (PR #0, initial scaffold).
2. Config loader with zod — **done** (PR #1).
3. discord.js client + READY logging — **done** (PR #2).
4. Sweep (TS port of the original bash script) with unit tests, run on startup — **done** (PR #3).
5. `reactionAdd` + `reactionRemove` handlers — **done** (PR #4).
6. Cron-scheduled sweep on top of the on-startup sweep.
7. Health endpoint.
8. Dockerfile finalization + local docker-compose.
9. Kamal deploy config.
10. AppSignal wiring.
11. README polish (role-hierarchy gotcha, bot invite URL).

## Risks

- **Role hierarchy misconfiguration** is the most common silent failure for this kind of bot. Sweep should explicitly probe and log on startup.
- **Privileged `GuildMembers` intent** must be toggled in the Developer Portal; if missing, members can't be resolved on reaction events.
- **Single-instance constraint.** Kamal config must not scale horizontally.
- **Partials.User pitfall:** `MESSAGE_REACTION_REMOVE` is silently dropped by discord.js if the user isn't cached and `Partials.User` isn't enabled (the gateway payload only carries `user_id`). We do enable it; don't remove it from `src/client.ts` without testing the remove path live.
- **Single-instance constraint.** Kamal config must not scale horizontally — duplicate gateway connections would produce duplicate role grants.
- **Stateless trade-off**: no in-bot audit trail beyond logs. Acceptable in v1; if richer audit is needed later, the cheapest add is a Discord audit channel before reaching for a DB.
Loading