# Spike Discord-Gitea bridge bot. When someone opens a PR or leaves a comment on [git.nose.space](https://git.nose.space), Spike posts it to a Discord channel. When someone replies in the Discord thread, Spike posts it back to Gitea. ## Setup 1. Install [bun](https://bun.sh) and clone this repo. 2. `bun install` from the repo root. 3. `cp .env.example .env` and fill in the values (see below). 4. `bun run subdomain:dev` to start the server. 5. Visit `localhost:3000/discord/auth` to authorize the bot to your Discord server. 6. Use [Tailscale Funnel](https://tailscale.com/kb/1223/funnel) or ngrok to expose your local server to the internet. 7. Add a Gitea webhook (see below). ### Gitea webhook Spike needs a webhook so Gitea sends PR and comment events to it. You have two options: **System webhook (covers all repos)** — Requires Gitea admin access. Go to **Site Administration > System Webhooks > Add Webhook > Gitea**. Set the target URL to `https://spike.theworkshop.cc/gitea/webhook`. Under "Trigger On", select **Custom Events** and enable Pull Request, Issue Comment, and Pull Request Comment. This fires for every repo on the instance. **Org/repo webhook (covers one org or repo)** — Go to the org or repo settings, then **Webhooks > Add Webhook > Gitea**. Same target URL and event configuration as above. Only fires for that org or repo. ### Environment variables Copy `.env.example` and fill in: | Variable | What it is | |---|---| | `DISCORD_TOKEN` | Bot token from [Discord Developer Portal](https://discord.com/developers/applications) | | `DISCORD_CLIENT_ID` | Application ID from the same portal | | `GITEA_API_TOKEN` | Personal access token from git.nose.space (Settings > Applications) | | `PORT` | Server port (default 3000) | | `NODE_ENV` | Set to `production` for prod, leave blank for dev | | `DATA_DIR` | Where to store the SQLite DB in prod | For running integration tests, you also need `TEST_GITEA_API_TOKEN_COREY`, `TEST_GITEA_API_TOKEN_SPIKE`, `TEST_REPO_COREY`, and `TEST_REPO_SPIKE` — see `.env.example` for defaults. ### Username mappings Spike converts between Gitea and Discord usernames so @mentions work across platforms. These mappings live in `src/config.ts` under `giteaToDiscordUserMappings`. Add your Gitea username → Discord username pair there. ## Architecture ``` src/ ├── server.tsx — HTTP server (webhook endpoint, error log) ├── config.ts — Dev/prod config (DB paths, channel IDs, username mappings) ├── gitea/ — Pure Gitea API client and types ├── discord/ — Discord bot client, events, slash commands └── bridge/ — Wiring between Gitea and Discord (webhook handler, DB, helpers) ``` Dependencies flow one way: `bridge/ → gitea/` and `bridge/ → discord/`. The gitea and discord modules don't know about each other — bridge is the only thing that connects them. ### How the code is organized Each directory with an `index.ts` is a standalone lib. The `index.ts` is a barrel — it re-exports the lib's public API. Everything else inside the directory is internal. **The rule:** external code imports from the barrel only. Never reach into a lib's internal files. ```ts // Good import { handleGiteaWebhook } from "./bridge" // Bad — reaches into internals import { handleGiteaWebhook } from "./bridge/webhook-handler" ``` Each lib has a `README.md` that documents its barrel exports. This is the contract — if you want to know what a lib does, read its README. You don't need to read every internal file. This matters for AI-assisted development: when Claude works on code that *uses* a lib, it only needs the README to understand the API. When it works on code *inside* a lib, it reads the internals. This keeps context small and focused. ### The three libs **[gitea/](src/gitea/README.md)** — Pure Gitea API. Fetches PRs and review comments, converts usernames, formats thread names. No side effects, no Discord, no database. Unit-testable in ~15ms. **[discord/](src/discord/README.md)** — Discord bot setup. Creates and logs in the client, registers slash commands, listens for messages. When someone types in a PR thread, it hands off to bridge to relay the message to Gitea. **[bridge/](src/bridge/README.md)** — The glue. Receives Gitea webhooks and creates Discord threads/messages. Receives Discord messages and posts Gitea comments. Owns the SQLite database that maps Gitea PR IDs ↔ Discord thread IDs and Gitea comment IDs ↔ Discord message IDs. ### Data flow ``` Gitea webhook POST → server.tsx → bridge/handleGiteaWebhook → Discord thread/message Discord message → discord/events → bridge/createPRComment → Gitea comment ``` ## Running - `bun run subdomain:dev` — Start with hot reload - `bun run subdomain:start` — Production mode - `bun test` — Run integration tests (requires Tailscale funnel + test env vars) ## Tests Unit tests for pure functions (username conversion, thread naming) run in ~15ms with no external dependencies. Integration tests for webhooks require a live Gitea instance and Tailscale funnel. They open real PRs, post real comments, and verify the correct webhooks arrive. See `src/gitea/test/` for details.