# Bellhop Go Rewrite — Session Plan Rewriting Bellhop from a Python FastAPI web portal into a Go-based Matrix command bot, modeled after [Pete](../pete). Users issue commands like `!movie dune` in allowlisted Matrix rooms; the bot performs the top-hit search against Radarr/Sonarr/Lidarr and adds the result. ## Design decisions (locked in) - **Command UX:** top-hit auto-add (no numbered picker, no reaction selector). - **Authz:** any member of an allowlisted room ID can issue commands. No per-user list. - **Old Python code:** deleted, clean-slate Go rewrite. - **Module layout:** mirrors Pete (`main.go` + `internal//`). - **Config:** YAML with `${ENV_VAR}` expansion (Pete-style). - **Matrix client:** mautrix-go with cryptohelper for E2EE support. ## Session breakdown - [x] **Session 1 — Foundation** - Delete Python (`app/`, `requirements.txt`, `Dockerfile`, `.env.example`) - Reset `.gitignore` / `.dockerignore` for Go - `go.mod` (mautrix + yaml) - `internal/config/config.go` — YAML loader: matrix creds, `allowed_rooms`, `services.{radarr,sonarr,lidarr}` - [x] **Session 2 — Matrix client** (`internal/matrix/`) - Adapt Pete's `client.go`: login + device persistence + cryptohelper - Sync loop, auto-join on invite - Message handler scoped to `allowed_rooms` (not channel-name map like Pete) - Threaded reply helper for command responses - Drop Pete-specific bits: image upload, reaction handler, story formatting - [x] **Session 3 — *arr clients** (`internal/arr/`) - One file per service or a single generic client (TBD in session) - `Search(term) → []Result` and `Add(result) → error` - Radarr: `/api/v3/movie/lookup` + POST `/api/v3/movie` - Sonarr: `/api/v3/series/lookup` + POST `/api/v3/series` - Lidarr: `/api/v1/artist/lookup` + POST `/api/v1/artist` - All adds: monitored=true, search-on-add=true, quality profile + root folder from config - Unit tests with httptest - [x] **Session 4 — Command dispatch + main** (`internal/bot/`, `main.go`) - Parse ` ` (default prefix `!`) - Commands: `movie`, `tv`, `music`, `help` - On match: search → take `[0]` → add → reply with title/year (or error) - "Service not configured" reply if the corresponding *arr block is absent - `main.go`: load config, init matrix, wire handler, SIGINT/SIGTERM shutdown - [x] **Session 5 — Ship polish** - Multi-stage Dockerfile (`golang:1.25-alpine` → `alpine:3.21`, build with `-tags goolm`) - `config.example.yaml` - Rewrite `README.md`: command UX, install, config reference, Docker - `go build ./...` + `go vet ./...` clean ## Out of scope (for now) - Reaction-based result picker - Per-user allowlist or power-level gating - Persistent request audit log (the Matrix room itself is the audit trail since requests are in-channel) - Removal/cancellation commands - Search-only mode (browse without adding)