Skip to content

feat(build): DM the user on Discord when a triggered build finishes#105

Open
barnabasbusa wants to merge 3 commits into
masterfrom
bbusa/discord-dm-on-build
Open

feat(build): DM the user on Discord when a triggered build finishes#105
barnabasbusa wants to merge 3 commits into
masterfrom
bbusa/discord-dm-on-build

Conversation

@barnabasbusa
Copy link
Copy Markdown

@barnabasbusa barnabasbusa commented May 13, 2026

Summary

Adds end-to-end Discord DM notifications for `panda build` so you get a message when the dispatched workflow finishes — no need to keep a terminal open. After #106 was folded in, this PR also includes the `panda init` setup prompt so the username is captured once and reused forever.

What's in here

`panda build`

  • `--discord-dm ` — Discord username (`@barnabasbusa`) or numeric user ID to DM on completion.
  • `--no-discord-dm` — disable for this run, even when a default is configured.
  • First successful `--discord-dm` invocation auto-persists the value to `config.user.yaml` under `notifications.discord.username`, so later runs notify without re-passing the flag.

`panda init`

  • Interactive: prompts `Discord username:` after the auth step on a TTY. Blank skips.
  • Non-interactive: `--discord-username ` sets it directly; `--skip-discord` silences the prompt entirely. Skipped automatically when stdin is not a TTY (CI-safe).
  • Idempotent: prints `already configured for …` if a value is already saved.

Proxy

  • New optional `discord:` config block (`bot_token` + `guild_id`) on the proxy. When present, the GitHub handler spawns a watcher goroutine after a successful workflow dispatch that polls the run and DMs the resolved user on completion.
  • Discord client uses raw REST against `discord.com/api/v10` (no websocket/gateway dependency). Resolution accepts either a numeric Discord ID or a username (looked up via `GET /guilds/{id}/members/search`).

Architecture

  • CLI (`pkg/cli/build.go`, `pkg/cli/init.go`): flag + prompt plumbing; both flows write via `config.SaveUserConfig` so they produce identical YAML.
  • Local server (`pkg/server/api.go`): forwards the `notify` block to the proxy's `/github/actions/trigger`.
  • Proxy GitHub handler (`pkg/proxy/handlers/github.go`): on dispatch + run discovery, if Discord is configured and a target was requested, spawns the watcher.
  • Discord client (`pkg/proxy/handlers/discord.go`): bot-token authenticated REST; member search → `POST /users/@me/channels` → `POST /channels/{id}/messages`.

Pairs with

  • ethpandaops/platform#288 — wires the panda-pulse Discord bot token + guild IDs into `panda-proxy/values.yaml` (reusing the same SOPS entry as the GitHub PAT).

Tradeoffs / known limitations

  • Watcher is in-memory. A proxy pod restart drops pending DMs. Acceptable for now (single replica; builds typically <30 min). A Redis-backed queue is a follow-up — the redis is already deployed in the same namespace.
  • `@username` resolution requires `guild_id`. Numeric Discord user IDs always work; usernames only resolve in guilds the bot is in.
  • Discord auth at the proxy. Intentionally avoids per-user OAuth/Discord bindings to keep the change small. A future PR could add the OIDC ↔ Discord binding flow we sketched earlier.

Test plan

  • Local: `go build ./...` clean, `go test ./pkg/cli/... ./pkg/config/... ./pkg/proxy/... ./pkg/serverapi/...` green.
  • Deploy a `proxy-` image containing this commit via the matching platform PR.
  • Fresh `panda init` on a TTY: prompts after auth, accepts `barnabasbusa`, writes to `config.user.yaml`.
  • `panda init --discord-username '@bbusa' --skip-auth --skip-docker --skip-start --force`: no prompt, value persisted.
  • `panda init --skip-discord --skip-auth --skip-docker --skip-start --force`: no prompt, no save.
  • `panda init` with stdin not a TTY (`</dev/null`): no prompt, no error.
  • `panda build geth --discord-dm @barnabasbusa` → ~5–10 min later a DM arrives in Discord with the run conclusion + URL.
  • Re-run `panda build geth` (no flag) → DM still arrives (default persisted on first use).
  • `panda build geth --no-discord-dm` → trigger succeeds, no DM.

Adds a --discord-dm flag to `panda build` that the proxy uses to send a
Discord DM (via the panda-pulse bot token, plumbed through proxy config)
when the workflow run reaches a terminal state.

- CLI: --discord-dm <user> / --no-discord-dm. The first time --discord-dm
  is supplied and no default is configured, the username is persisted to
  config.user.yaml under notifications.discord.username so subsequent
  builds notify without re-passing the flag.
- Server: forwards Notify spec to the proxy's /github/actions/trigger.
- Proxy: optional `discord:` config with bot_token + guild_id. When set,
  the GitHub handler spawns a watcher goroutine after a successful
  dispatch that polls the run and DMs the resolved user on completion.
  Resolution accepts either a numeric Discord ID or a username (looked
  up via guild member search).
- Discord client uses raw REST against discord.com/api/v10 to avoid a
  websocket/gateway dependency.
Adds an optional Discord username step to `panda init` so build-completion
DMs work out of the box without ever passing --discord-dm. The value lands
in config.user.yaml under notifications.discord.username, which `panda
build` already reads as its default target.

- Interactive: prompts on a TTY after auth, unless --skip-discord is set or
  a username is already configured.
- Non-interactive: --discord-username <value> sets it directly; --skip-discord
  silences the prompt entirely. Stdin not a TTY ⇒ no prompt, no failure.
- Persists via the same config.SaveUserConfig path used by `panda build`'s
  auto-save behavior, so the flows produce identical YAML.
feat(init): prompt for + persist a Discord username during setup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant