docs: sync documentation with current codebase

- Update event formatter count 23 -> 28 (add ping, workflow_job, status, deployment, check_suite)
- Document Telegram support end-to-end (routes, /gh commands, richheader, secrets)
- Fix route schema to use targets array and group fields (owners, emoji)
- Correct KV/D1 storage layout (msg:*, i18n:*, D1 links/send_logs)
- Note GITHUB_APP_ID/GITHUB_PRIVATE_KEY are unused; drop legacy DISCORD_CHANNEL_ID/PORT/CONFIG_PATH
- Remove stale Docker deployment section
- Update color table, branch filter compatibility, admin API endpoints
- AGENTS.md: add Documentation section requiring doc updates after functional changes
This commit is contained in:
wyf9 2026-08-05 17:22:15 +08:00
parent 68cda9f178
commit afe19795b1
No known key found for this signature in database
GPG key ID: B126966081BFDBE4
25 changed files with 621 additions and 398 deletions

198
README.md
View file

@ -1,34 +1,40 @@
# WebHooker
GitHub webhook → Discord dispatcher. Receives webhook events via Cloudflare Workers, applies filters, and routes formatted messages to Discord channels or threads.
GitHub webhook → Discord / Telegram dispatcher. Receives webhook events via Cloudflare Workers, applies filters, and routes formatted messages to Discord channels/threads and Telegram chats/topics.
## Features
- **23 event formatters** — push, pull_request, issues, issue_comment, workflow_run, release, create, delete, star, fork, check_run, pull_request_review, pull_request_review_comment, commit_comment, deployment_status, member, label, milestone, discussion, discussion_comment, repository, code_scanning_alert, dependabot_alert (+ generic fallback)
- **28 event formatters** — push, pull_request, issues, issue_comment, workflow_run, workflow_job, status, deployment, deployment_status, check_run, check_suite, ping, release, create, delete, star, fork, pull_request_review, pull_request_review_comment, commit_comment, member, label, milestone, discussion, discussion_comment, repository, code_scanning_alert, dependabot_alert (+ generic fallback)
- HMAC-SHA256 signature verification (Web Crypto API)
- Filter by event type, repo, actor, action, branch (incl. PR), keyword (supports regex)
- Rich Discord embeds with color coding, author avatars, fields, and timestamps
- Route to channels or threads
- GitHub App OAuth for user actions (comment, merge, react)
- **Web UI config console** (`/admin`) — manage routes with GitHub OAuth + admin whitelist
- Filter by event type, repo, actor, action, branch, keyword (supports regex)
- Rich messages with color coding, author avatars, fields, and timestamps — rendered as Discord embeds and Telegram HTML
- Route to Discord channels/threads and Telegram chats/topics (multi-target routes)
- `workflow_run` progress is edited **in place** (single message updated as the workflow advances) on both platforms
- GitHub OAuth for user actions (comment, edit comment, delete comment, merge, close, react)
- **Web UI config console** (`/admin`) — manage routes and groups with GitHub OAuth + admin whitelist, view send logs
- **Discord Interactions Endpoint** (Ed25519-verified) for `/gh` slash commands, message context-menu commands, PR merge/close buttons, and comment modals
- Cloudflare KV for token/state/config storage
- **Telegram `/gh` commands** (login/logout/comment/merge/close) via the Telegram webhook, with avatar link-preview cards
- Cloudflare KV for token/state/config/session storage + D1 for send logs and platform account links
- Graceful degradation (webhook-only mode if Discord unavailable)
## Architecture
```text
GitHub Webhook → Cloudflare Worker (Hono)
├── POST /webhook → verify → filter → format → Discord (REST)
├── POST /webhook → verify → dedup → filter → format → Discord (REST) / Telegram (Bot API)
├── POST /discord/interactions → verify (Ed25519) → handle command/button/modal
├── POST /telegram/webhook → verify (secret token) → handle /gh commands
├── GET /auth/github → OAuth flow
├── GET /api/richheader → Telegram avatar link-preview card
├── POST /api/* → user actions (Bearer token auth)
├── /admin → routes, groups & send logs Web UI
└── GET /health → status check
```
- **Cloudflare Worker** — HTTP ingress, signature verification, routing, Discord REST dispatch
- **Cloudflare Worker** — HTTP ingress, signature verification, routing, platform dispatch
- **Interactions Endpoint** — HTTPS callback (no Discord Gateway connection, no Durable Object); the bot stays offline and commands are registered via the API
- **KV** — Token storage (`token:{userId}`), OAuth state (`state:{hex}`), route config (`config:routes`)
- **KV** — token storage (`token:{userId}`), OAuth state (`state:{hex}`), route config (`config:routes`), group config (`config:groups`), admin sessions (`session:{id}`), delivery dedup (`delivery:{id}`), message-update tracking (`msg:*`)
- **D1** — send logs (`send_logs`), Discord↔GitHub links (`discord_links`), Telegram↔GitHub links (`telegram_links`)
## Quick Start
@ -42,22 +48,28 @@ npx wrangler dev # Start local dev server
### Secrets (`.dev.vars` for local, Worker Secrets for production)
| Variable | Description |
| ------------------------ | ---------------------------------------------------------------------------------------------- |
| `GITHUB_WEBHOOK_SECRET` | Webhook secret from GitHub |
| `GITHUB_APP_ID` | GitHub App ID |
| `GITHUB_PRIVATE_KEY` | App private key (PEM) |
| `GITHUB_CLIENT_ID` | OAuth client ID |
| `GITHUB_CLIENT_SECRET` | OAuth client secret |
| `DISCORD_TOKEN` | Bot token |
| `DISCORD_PUBLIC_KEY` | Discord application public key (from the Developer Portal) — required for interactions |
| `DISCORD_APPLICATION_ID` | Discord application id (optional; auto-resolved via `GET /oauth2/applications/@me` if omitted) |
| `BASE_URL` | Public URL for OAuth callbacks |
| `ADMIN_USER_IDS` | Comma-separated GitHub user IDs (or logins) allowed to access `/admin` |
| Variable | Description |
| --------------------------- | ---------------------------------------------------------------------------------------------- |
| `GITHUB_WEBHOOK_SECRET` | Webhook secret from GitHub |
| `GITHUB_APP_ID` | GitHub App ID (not currently used by the code; kept for compatibility) |
| `GITHUB_PRIVATE_KEY` | App private key (PKCS#8 PEM; not currently used by the code; kept for compatibility) |
| `GITHUB_CLIENT_ID` | OAuth client ID |
| `GITHUB_CLIENT_SECRET` | OAuth client secret |
| `DISCORD_TOKEN` | Bot token |
| `DISCORD_PUBLIC_KEY` | Discord application public key (from the Developer Portal) — required for interactions |
| `DISCORD_APPLICATION_ID` | Discord application id (optional; auto-resolved via `GET /oauth2/applications/@me` if omitted) |
| `TELEGRAM_TOKEN` | Telegram bot token (from BotFather) — required for Telegram routes |
| `TELEGRAM_WEBHOOK_SECRET` | Optional secret token for `POST /telegram/webhook` verification |
| `TELEGRAM_RICH_HEADER_HOST` | Optional base URL overriding the built-in `GET /api/richheader` for Telegram avatar cards |
| `BASE_URL` | Public URL for OAuth callbacks and the Telegram webhook sync |
| `ADMIN_USER_IDS` | Comma-separated GitHub user IDs (or logins) allowed to access `/admin` |
| `DOCS_URL` | Optional docs site URL used by the landing page |
| `GITHUB_REPO_URL` | Optional GitHub repo URL used by the landing page |
| `LEGAL_CONTACT` | Optional contact shown on `/terms` and `/privacy` |
### Routes
Routes are stored in KV (`config:routes` as JSON). There are **no default routes** — every route (including its target) must be defined explicitly, either via the Web UI (`/admin`) or by storing a JSON array in KV:
Routes are stored in KV (`config:routes` as JSON). There are **no default routes** — every route (including its target) must be defined explicitly, either via the Web UI (`/admin`) or by storing a JSON array in KV. A route may carry multiple `targets`, so one rule can forward to several channels at once:
```json
[
@ -65,28 +77,27 @@ Routes are stored in KV (`config:routes` as JSON). There are **no default routes
"id": "all-push",
"name": "Push Events",
"enabled": true,
"groupId": "default",
"filters": [{ "type": "event", "match": "push" }],
"target": { "platform": "discord", "channelId": "CHANNEL_ID" }
},
{
"id": "telegram-issues",
"name": "Issues to Telegram",
"enabled": true,
"filters": [{ "type": "event", "match": "issues" }],
"target": { "platform": "telegram", "chatId": "-1001234567890" }
"targets": [
{ "platform": "discord", "channelId": "CHANNEL_ID" },
{ "platform": "telegram", "chatId": "-1001234567890" }
]
}
]
```
`target.platform` selects the push target: `discord` (default) or `telegram`. Discord routes require `target.channelId` (optional `threadId` for a thread); Telegram routes require `target.chatId` (the group chat id, optional `topicId` for a topic). There is no fallback to a default channel.
`target.platform` selects the push target: `discord` (default) or `telegram`. Discord targets require `target.channelId` (optional `threadId` for a thread); Telegram targets require `target.chatId` (optional `topicId` for a topic). The legacy singular `target` field is still migrated automatically. There is no fallback to a default channel.
Routes belong to **groups** (KV `config:groups`) that scope admin access and can restrict which org/user events flow in. See `config.example.yaml` and `docs/guide/configuration.md` for the full schema.
### Web UI (`/admin`)
The built-in config console lets you manage routes in the browser (add / edit / delete / toggle / reorder), no KV access needed:
The built-in config console lets you manage routes and groups in the browser (add / edit / delete / toggle / reorder), and inspect send logs — no KV access needed:
1. Set `ADMIN_USER_IDS` to the GitHub user IDs (or logins) allowed to manage the console, e.g. `ADMIN_USER_IDS=12345,RhenCloud`.
2. Visit `/admin` and sign in with GitHub. Only users in the whitelist get access.
3. Changes are written to KV `config:routes` immediately and picked up by the webhook pipeline.
3. Changes are written to KV immediately and picked up by the webhook pipeline.
Sign out at `/admin/logout`.
@ -94,14 +105,14 @@ See `config.example.yaml` for full syntax examples.
### Filter Types
| Type | Matches | Notes |
| --------- | -------------------------------------- | -------------------------------------------------------------------- |
| `event` | `push`, `pull_request`, `issues`, etc. | GitHub event name |
| `repo` | `org/repo` full name | |
| `actor` | Sender login | |
| `action` | `opened`, `closed`, `published`, etc. | |
| `branch` | Branch name | Works for push, PR, create/delete, workflow_run, code_scanning_alert |
| `keyword` | Text in payload body | Supports regex patterns; falls back to substring match |
| Type | Matches | Notes |
| --------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `event` | `push`, `pull_request`, `issues`, etc. | GitHub event name |
| `repo` | `org/repo` full name | |
| `actor` | Sender login | |
| `action` | `opened`, `closed`, `published`, etc. | |
| `branch` | Branch name | Works for push, PR/review, create/delete, workflow_run, workflow_job, check_suite, deployment, code_scanning_alert |
| `keyword` | Text in payload body | Supports regex patterns; falls back to substring match |
Set `exclude: true` to invert any filter.
@ -114,13 +125,14 @@ Set `exclude: true` to invert any filter.
### OAuth
- `GET /auth/github` — Start GitHub OAuth flow (redirects to GitHub)
- `GET /auth/github/callback` — OAuth callback (exchanges code for token)
- `GET /auth/github/callback` — OAuth callback (exchanges code for token; admin session / Discord link / Telegram link)
- `DELETE /auth/token/:userId` — Revoke user token
### Actions (require `Authorization: Bearer <token>` header)
- `POST /api/comment` — Create issue comment
- `POST /api/merge` — Merge pull request
- `POST /api/close` — Close pull request
- `POST /api/react` — Add reaction to issue
### Admin (require admin OAuth session)
@ -130,6 +142,13 @@ Set `exclude: true` to invert any filter.
- `GET /admin/logout` — Sign out
- `GET /admin/api/routes` — List routes
- `PUT /admin/api/routes` — Replace routes
- `GET /admin/api/groups` — List groups (scoped)
- `PUT /admin/api/groups` — Replace groups (super admin only)
- `GET /admin/api/groups/:groupId/routes` — List a group's routes
- `PUT /admin/api/groups/:groupId/routes` — Replace a group's routes
- `GET /admin/api/me` — Current session / scope
- `GET /admin/api/logs` — Send logs (scoped)
- `GET /admin/api/logs/:id` — Single send-log entry
## GitHub App Setup
@ -142,11 +161,11 @@ Set `exclude: true` to invert any filter.
- **Webhook URL**: `https://your-domain/webhook`
- **Webhook secret**: generate and copy to `GITHUB_WEBHOOK_SECRET`
3. Set permissions:
- **Repository permissions**: Contents (read), Issues (write), Pull requests (write), Metadata (read)
- **Repository permissions**: Contents (read), Issues (write), Pull requests (write), Metadata (read), Checks (read), Deployments (read), Discussions (read), Code scanning alerts (read), Dependabot alerts (read)
- **Organization permissions**: Members (read) — if needed
4. Subscribe to events:
- Push, Pull request, Issues, Issue comment, Workflow run, Release, Create, Delete, Star, Fork, Check run, Pull request review, Pull request review comment, Commit comment, Deployment status, Member, Label, Milestone, Discussion, Discussion comment, Repository, Code scanning alert, Dependabot alert
5. Generate private key → save contents to `GITHUB_PRIVATE_KEY` env var
- Push, Pull request, Issues, Issue comment, Workflow run, Workflow job, Status, Deployment, Deployment status, Ping, Release, Create, Delete, Star, Fork, Check run, Check suite, Pull request review, Pull request review comment, Commit comment, Member, Label, Milestone, Discussion, Discussion comment, Repository, Code scanning alert, Dependabot alert
5. Generate private key `GITHUB_PRIVATE_KEY` is currently unused by the code (only client ID/secret power the OAuth flow), so it is optional; store it if you later enable GitHub App authentication.
### 2. Install App
@ -225,24 +244,45 @@ The bot registers native **slash** and **message context-menu** commands, synced
| OAuth | `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` and `BASE_URL` configured |
| User linked | Each user runs `/gh login` first |
## Telegram Bot Setup
1. Create a bot with [@BotFather](https://t.me/BotFather) and copy its token to `TELEGRAM_TOKEN`.
2. (Optional) Set `TELEGRAM_WEBHOOK_SECRET`; the webhook registration passes it to Telegram as the `secret_token`, and `POST /telegram/webhook` verifies it with a timing-safe compare.
3. The worker syncs the webhook from the scheduled trigger (`setWebhook` to `{BASE_URL}/telegram/webhook`), so no manual `setWebhook` call is needed — just make sure `BASE_URL` is set.
4. Add the bot to a group (or enable topics) and route events to `chatId` / `topicId` in the route config.
In Telegram, `/gh` commands work by replying to a notification message:
- `/gh login` — link your GitHub account (returns an OAuth link)
- `/gh logout` — unlink
- `/gh comment <text>` — reply to an issue/PR notification to comment as yourself
- `/gh merge` / `/gh close` — reply to a PR notification to merge/close it
Avatars are rendered as a link-preview card using the built-in `GET /api/richheader` (overridable with `TELEGRAM_RICH_HEADER_HOST`).
## Deployment
```bash
# Set secrets in Cloudflare
npx wrangler secret put GITHUB_WEBHOOK_SECRET
npx wrangler secret put GITHUB_APP_ID
npx wrangler secret put GITHUB_PRIVATE_KEY
npx wrangler secret put GITHUB_CLIENT_ID
npx wrangler secret put GITHUB_CLIENT_SECRET
npx wrangler secret put DISCORD_TOKEN
npx wrangler secret put DISCORD_PUBLIC_KEY
npx wrangler secret put DISCORD_CHANNEL_ID
npx wrangler secret put TELEGRAM_TOKEN
npx wrangler secret put ADMIN_USER_IDS
# Create KV namespace
npx wrangler kv namespace create KV
# Update wrangler.jsonc with the KV namespace ID
# Create D1 database and run migrations
npx wrangler d1 create webhooker
# Update wrangler.jsonc d1_databases with the database ID
npx wrangler d1 execute webhooker --remote --file ./migrations/0001_init.sql
npx wrangler d1 execute webhooker --remote --file ./migrations/0002_log_detail.sql
npx wrangler d1 execute webhooker --remote --file ./migrations/0003_telegram_links.sql
# Deploy
npx wrangler deploy
```
@ -253,34 +293,42 @@ npx wrangler deploy
npx wrangler dev # Local dev server (Miniflare)
npm run typecheck # Type checking
npm run lint # ESLint
npm test # Unit tests (bun test)
```
## Supported Events
| Event | Formatter |
| ----------------------------- | ------------------------------------------------ |
| `push` | Commit list, branch, author |
| `pull_request` | PR title, branch, diff stats |
| `issues` | Issue title, labels, assignees |
| `issue_comment` | Comment body, issue reference |
| `workflow_run` | Workflow status, conclusion, duration |
| `release` | Tag, body, assets |
| `create` / `delete` | Branch/tag creation/deletion |
| `star` | Star count, repository |
| `fork` | Fork source → target |
| `check_run` | Status, conclusion, details URL |
| `pull_request_review` | Review state, body preview |
| `pull_request_review_comment` | Inline code comment, file path, line |
| `commit_comment` | Commit SHA, comment body |
| `deployment_status` | Environment, status, commit ref |
| `member` | Collaborator add/remove |
| `label` | Label name, color, description |
| `milestone` | Progress bar, open/closed counts, due date |
| `discussion` | Discussion title, category, action |
| `discussion_comment` | Comment body, discussion reference |
| `repository` | Repo rename/transfer details |
| `code_scanning_alert` | Severity, rule ID, file path |
| `dependabot_alert` | Severity, package, vulnerable range, fix version |
| Event | Formatter |
| ----------------------------- | ------------------------------------------------------- |
| `push` | Commit list, branch, author |
| `pull_request` | PR title, branch, diff stats |
| `issues` | Issue title, labels, assignees |
| `issue_comment` | Comment body, issue reference |
| `workflow_run` | Workflow status, conclusion, duration (edited in place) |
| `workflow_job` | Job name, status, conclusion |
| `status` | Commit status, context, state |
| `deployment` | Environment, ref, task |
| `deployment_status` | Environment, status, commit ref |
| `check_run` | Status, conclusion, details URL |
| `check_suite` | Suite conclusion, head branch, commit |
| `ping` | Webhook confirmation |
| `release` | Tag, body, assets |
| `create` / `delete` | Branch/tag creation/deletion |
| `star` | Star count, repository |
| `fork` | Fork source → target |
| `pull_request_review` | Review state, body preview |
| `pull_request_review_comment` | Inline code comment, file path, line |
| `commit_comment` | Commit SHA, comment body |
| `member` | Collaborator add/remove |
| `label` | Label name, color, description |
| `milestone` | Progress bar, open/closed counts, due date |
| `discussion` | Discussion title, category, action |
| `discussion_comment` | Comment body, discussion reference |
| `repository` | Repo rename/transfer details |
| `code_scanning_alert` | Severity, rule ID, file path |
| `dependabot_alert` | Severity, package, vulnerable range, fix version |
Any other event type falls back to the generic formatter (event type, action, actor, repo, raw payload).
## License