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

View file

@ -1,7 +1,7 @@
# GitHub # GitHub
GITHUB_WEBHOOK_SECRET=your-webhook-secret GITHUB_WEBHOOK_SECRET=your-webhook-secret
GITHUB_APP_ID=your-app-id GITHUB_APP_ID=your-app-id
GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----" GITHUB_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
GITHUB_CLIENT_ID=your-client-id GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret GITHUB_CLIENT_SECRET=your-client-secret
@ -9,15 +9,19 @@ GITHUB_CLIENT_SECRET=your-client-secret
DISCORD_TOKEN=your-bot-token DISCORD_TOKEN=your-bot-token
DISCORD_PUBLIC_KEY=your-public-key DISCORD_PUBLIC_KEY=your-public-key
DISCORD_APPLICATION_ID=your-application-id DISCORD_APPLICATION_ID=your-application-id
DISCORD_CHANNEL_ID=your-channel-id
# Telegram # Telegram
TELEGRAM_TOKEN=your-bot-token TELEGRAM_TOKEN=your-bot-token
TELEGRAM_WEBHOOK_SECRET=your-webhook-secret TELEGRAM_WEBHOOK_SECRET=your-webhook-secret
# TELEGRAM_RICH_HEADER_HOST=https://your-domain
# Server # Admin
PORT=3000 ADMIN_USER_IDS=your-github-id,your-github-login
BASE_URL=http://localhost:3000
# Config # Server / public URL (OAuth callbacks + Telegram webhook sync)
CONFIG_PATH=./config.yaml BASE_URL=http://localhost:8787
# Optional
# DOCS_URL=https://your-docs-site
# GITHUB_REPO_URL=https://github.com/ReCloudStudio/WebHooker
# LEGAL_CONTACT=contact@example.com

View file

@ -2,18 +2,18 @@
## Project Purpose ## Project Purpose
Cloudflare Worker that receives GitHub webhooks and dispatches processed events to Discord channels/threads, and receives Discord interactions (slash commands, buttons, modals) via the Interactions Endpoint. Cloudflare Worker that receives GitHub webhooks and dispatches processed events to Discord channels/threads and Telegram chats/topics, and receives Discord interactions (slash commands, buttons, modals) via the Interactions Endpoint plus Telegram bot `/gh` commands via the Telegram webhook.
Core pipeline: GitHub Webhook → Worker (verify + filter + format) → Discord (REST) Core pipeline: GitHub Webhook → Worker (verify + filter + format) → Discord (REST) / Telegram (Bot API)
## Key Decisions ## Key Decisions
- Runtime: Cloudflare Workers - Runtime: Cloudflare Workers
- HTTP framework: Hono - HTTP framework: Hono
- Discord interactions: HTTPS Interactions Endpoint (`POST /discord/interactions`, Ed25519-signed) — no Discord Gateway / Durable Object; bot stays offline, messages always sent via REST - Discord interactions: HTTPS Interactions Endpoint (`POST /discord/interactions`, Ed25519-signed) — no Discord Gateway / Durable Object; bot stays offline, messages always sent via REST
- Storage: Cloudflare KV (tokens, OAuth state, route config, admin sessions, delivery dedup) + D1 (send logs, discord-link mapping) - Storage: Cloudflare KV (tokens, OAuth state, route config `config:routes`, group config `config:groups`, admin sessions, delivery dedup, message-update tracking `msg:*`, i18n overrides `i18n:*`) + D1 (`send_logs`, `discord_links`, `telegram_links`)
- Signature verification: Web Crypto API (HMAC-SHA256 for GitHub, Ed25519 for Discord) - Signature verification: Web Crypto API (HMAC-SHA256 for GitHub, Ed25519 for Discord, timing-safe secret-token compare for Telegram)
- GitHub OAuth: octokit + jose (JWT) - GitHub OAuth: octokit (token is stored hashed for reverse lookup; jose is a dependency but JWT issuance is not used)
- Admin WebUI: `/admin` config console, OAuth-session protected via `ADMIN_USER_IDS` whitelist - Admin WebUI: `/admin` config console, OAuth-session protected via `ADMIN_USER_IDS` whitelist
- Local dev: wrangler + Miniflare - Local dev: wrangler + Miniflare
@ -21,54 +21,57 @@ Core pipeline: GitHub Webhook → Worker (verify + filter + format) → Discord
```text ```text
src/ src/
├── index.ts # CF Workers entry (fetch + scheduled), scheduled = command sync ├── index.ts # CF Workers entry (fetch + scheduled), scheduled = Discord command sync + Telegram webhook sync
├── types.ts # Env, Config, Route, Filter, WebhookEvent, NeutralMessage ├── types.ts # Env, Config, Route, Filter, Group, WebhookEvent, NeutralMessage
├── config.ts # loadRoutes/saveRoutes (KV config:routes, cache w/ 60s TTL), loadConfig from env ├── config.ts # loadRoutes/saveRoutes (KV config:routes, cache w/ 60s TTL), loadConfig from env
├── server.ts # Hono app: /health, /webhook, /discord/interactions, mounts /auth, /admin + / ├── server.ts # Hono app: /health, /webhook, /discord/interactions, /telegram/webhook, mounts /auth, /admin + /
├── core/ ├── core/
│ └── dispatch.ts # Platform-neutral dispatch: match routes → formatEvent → driver.send (recordSend + group filter) │ └── dispatch.ts # Platform-neutral dispatch: match routes → formatEvent → driver.send/edit (recordSend + group filter)
├── events/ # GitHub webhook pipeline (was webhook.ts) ├── events/ # GitHub webhook pipeline (legacy src/webhook.ts is dead code — do not import it)
│ ├── verify.ts # HMAC signature verify (Web Crypto, timing-safe) │ ├── verify.ts # HMAC signature verify (Web Crypto, timing-safe)
│ ├── parse.ts # parseEvent (headers + body → WebhookEvent) │ ├── parse.ts # parseEvent (headers + body → WebhookEvent)
│ └── match.ts # matchRoute, eventOwners, extractBranch, keyword regex filtering │ └── match.ts # matchRoute, eventOwners, extractBranch, keyword regex filtering
├── formatters/ # Platform-neutral message formatters (was formatter.ts) ├── formatters/ # Platform-neutral message formatters (was formatter.ts)
│ ├── index.ts # formatEvent: 24-event switch → NeutralMessage + re-exports │ ├── index.ts # formatEvent: 28-event switch → NeutralMessage + re-exports
│ ├── colors.ts # GITHUB_COLORS + WORKFLOW_CONCLUSION_EMOJI │ ├── colors.ts # GITHUB_COLORS + WORKFLOW_CONCLUSION_EMOJI
│ ├── helpers.ts # emojiPrefix, T, buildMessage │ ├── helpers.ts # emojiPrefix, T, buildMessage
│ └── *.ts # push, pull-request, issues, comments, workflow, release, create, │ └── *.ts # push, pull-request, issues, comments, workflow, release, create,
│ # repo, check, review, commit-comment, deployment, member, label, │ # repo, check, review, commit-comment, deployment, member, label,
│ # milestone, discussion, repository, security, generic │ # milestone, discussion, repository, security, generic, ping
├── drivers/ # Platform drivers (pluggable push targets) ├── drivers/ # Platform drivers (pluggable push targets)
│ ├── types.ts # PlatformDriver interface + SendResult │ ├── types.ts # PlatformDriver interface + SendResult (send + edit)
│ ├── index.ts # getDriver() registry (discord default + telegram) │ ├── index.ts # getDriver() registry (discord default + telegram)
│ ├── discord/ │ ├── discord/
│ │ ├── index.ts # DiscordDriver: send → renderNeutralMessage + rest.sendMessage │ │ ├── index.ts # DiscordDriver: send/edit → renderNeutralMessage + rest.sendMessage/editMessage
│ │ ├── render.ts # renderNeutralMessage: NeutralMessage → Discord FormattedMessage │ │ ├── render.ts # renderNeutralMessage: NeutralMessage → Discord FormattedMessage
│ │ ├── rest.ts # Discord REST sendMessage with retry + rate-limit handling │ │ ├── rest.ts # Discord REST sendMessage/editMessage with retry + rate-limit handling
│ │ ├── interactions.ts # Ed25519 verify + interaction handlers (/gh, buttons, modals) │ │ ├── interactions.ts # Ed25519 verify + interaction handlers (/gh, buttons, modals)
│ │ └── commands.ts # APP_COMMANDS + registerGlobalCommands/syncGuildCommands/syncCommands │ │ └── commands.ts # APP_COMMANDS + registerGlobalCommands/syncGuildCommands/syncCommands
│ └── telegram/ │ └── telegram/
│ ├── index.ts # TelegramDriver: send → renderNeutralMessage + rest.sendMessage │ ├── index.ts # TelegramDriver: send/edit → renderNeutralMessage + rest.sendMessage (avatar rich-header card)
│ ├── render.ts # renderNeutralMessage: NeutralMessage → Telegram HTML (parse_mode HTML) │ ├── render.ts # renderNeutralMessage: NeutralMessage → Telegram HTML (parse_mode HTML)
│ ├── rest.ts # Telegram Bot API sendMessage (chat_id + message_thread_id), retry │ ├── rest.ts # Telegram Bot API sendMessage/sendPhoto/editMessage* (chat_id + message_thread_id), retry
│ ├── updates.ts # POST /telegram/webhook: secret-token verify + handleTelegramUpdate │ ├── updates.ts # POST /telegram/webhook: secret-token verify + handleTelegramUpdate
│ └── commands.ts # Telegram /gh login|logout|comment|merge|close + reply-message parsing + syncTelegramWebhook │ └── commands.ts # Telegram /gh login|logout|comment|merge|close + reply-message parsing + syncTelegramWebhook
├── github/ ├── github/
│ ├── oauth.ts # OAuth URL, callback token exchange, getUserOctokit, comment/merge/close actions │ ├── oauth.ts # OAuth URL, callback token exchange, getUserOctokit, comment/getComment/editComment/deleteComment/merge/close actions
│ └── store.ts # KV token CRUD + D1 discord-link/telegram-link mapping (was token-store.ts) │ └── store.ts # KV token CRUD + D1 discord-link/telegram-link mapping (was token-store.ts)
├── web/ # HTTP UI/API routes ├── web/ # HTTP UI/API routes
│ ├── oauth-routes.ts # GET /auth/github, callback (admin session / discord-link), DELETE /token/:userId │ ├── oauth-routes.ts # GET /auth/github, callback (admin session / discord-link / telegram-link), DELETE /token/:userId
│ ├── action-routes.ts # POST /api/comment|merge|react (Bearer token auth via KV lookup) │ ├── action-routes.ts # POST /api/comment|merge|close|react (Bearer token auth via KV lookup)
│ ├── admin-routes.ts # /admin UI + GET/PUT /admin/api/routes (session + ADMIN_USER_IDS auth, validation) │ ├── admin-routes.ts # /admin UI + GET/PUT /admin/api/routes|groups|me|logs (session + scope auth, validation)
│ ├── session.ts # Session CRUD (KV session:{id}), isAdminUser, cookie helpers │ ├── session.ts # Session CRUD (KV session:{id}), isAdminUser, cookie helpers
│ ├── groups.ts # Group CRUD (config:groups), resolveScope, hasAnyAccess │ ├── groups.ts # Group CRUD (config:groups), resolveScope, hasAnyAccess, groupAcceptsOwners
│ ├── home-routes.ts # home page │ ├── home-routes.ts # landing page (zh/en)
│ └── legal-routes.ts # legal / privacy / terms pages │ ├── legal-routes.ts # /terms + /privacy pages (zh/en)
│ └── richheader-routes.ts # GET /api/richheader: Open Graph page for Telegram avatar link-preview card
└── lib/ # shared infra └── lib/ # shared infra
├── i18n.ts # loadTranslations, t() with param interpolation ├── i18n.ts # loadTranslations (KV i18n:{lang} overrides), t() with param interpolation
├── send-log.ts # SendRecord, recordSend/getSendLog (D1 send_logs) ├── send-log.ts # SendRecord, recordSend/getSendLog/getSendLogById (D1 send_logs)
├── log.ts # JSON console logger (info/warn/error/fatal) ├── log.ts # JSON console logger (info/warn/error/fatal)
└── locales/ # en.ts, zh.ts translation dictionaries └── locales/ # en.ts, zh.ts translation dictionaries
src/__tests__/ # bun test unit tests (webhook, formatter, discord, telegram, admin, send-log, token-store)
``` ```
## Responsibilities ## Responsibilities
@ -77,8 +80,11 @@ src/
- Verify Discord interactions (Web Crypto Ed25519, X-Signature-Ed25519 over timestamp + body) - Verify Discord interactions (Web Crypto Ed25519, X-Signature-Ed25519 over timestamp + body)
- Verify Telegram webhook calls (X-Telegram-Bot-Api-Secret-Token when configured) - Verify Telegram webhook calls (X-Telegram-Bot-Api-Secret-Token when configured)
- Filter events by: event type, repo name, actor, action, branch, keyword (regex supported) - Filter events by: event type, repo name, actor, action, branch, keyword (regex supported)
- Format 23+ event types as Discord embeds - Filter routes by group owner restriction (`Group.owners`) and skip fallback routes whenever a regular route matched
- Format 28 event types as platform-neutral messages (Discord embeds + Telegram HTML)
- Route messages to Discord channels/threads and Telegram chats/topics via REST - Route messages to Discord channels/threads and Telegram chats/topics via REST
- Edit already-sent messages in place for `workflow_run` progress (stable `updateKey`, KV `msg:*` tracking)
- Record every dispatch attempt to D1 `send_logs` (route id, event, target, ok/error, duration, error code)
- Serve `/gh` slash commands + message context-menu commands + PR merge/close buttons + comment modals - Serve `/gh` slash commands + message context-menu commands + PR merge/close buttons + comment modals
- Serve Telegram `/gh` commands (login/logout/comment/merge/close) via reply-message parsing - Serve Telegram `/gh` commands (login/logout/comment/merge/close) via reply-message parsing
- Sync application commands from the scheduled trigger (global ~1h propagation + per-guild instant) - Sync application commands from the scheduled trigger (global ~1h propagation + per-guild instant)
@ -104,8 +110,21 @@ src/
npx wrangler dev # Local dev (Miniflare) npx wrangler dev # Local dev (Miniflare)
npm run typecheck # Type checking npm run typecheck # Type checking
npm run lint # ESLint npm run lint # ESLint
npm test # Unit tests (bun test, under src/__tests__)
``` ```
## Documentation
Keep every functional change in sync with the docs. After implementing a feature, fix,
or refactor, update all of the following that are affected:
- `AGENTS.md` (this file) — architecture tree, responsibilities, key decisions, config
- `README.md` / `README.zh.md` — features, setup, configuration, supported events
- `docs/` (VitePress) — both `docs/` (English) and `docs/zh/` (Chinese) mirrors
- `config.example.yaml` / `.env.example` — example config/secret files
Rule: no functional change ships without its documentation; docs and code must not drift.
## Configuration ## Configuration
- **Local dev**: `.dev.vars` (wrangler reads this for env bindings) - **Local dev**: `.dev.vars` (wrangler reads this for env bindings)
@ -127,9 +146,17 @@ npx wrangler kv namespace create KV
npx wrangler d1 create webhooker npx wrangler d1 create webhooker
# Update wrangler.jsonc d1_databases with the database ID # 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/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
npx wrangler deploy npx wrangler deploy
``` ```
Full list of secrets used: `GITHUB_WEBHOOK_SECRET`, `GITHUB_APP_ID`, `GITHUB_PRIVATE_KEY`
(PKCS#8 PEM), `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `DISCORD_TOKEN`,
`DISCORD_PUBLIC_KEY`, `TELEGRAM_TOKEN`, `TELEGRAM_WEBHOOK_SECRET`, `ADMIN_USER_IDS`,
plus optional `BASE_URL`, `DISCORD_APPLICATION_ID`, `TELEGRAM_RICH_HEADER_HOST`,
`DOCS_URL`, `GITHUB_REPO_URL`, `LEGAL_CONTACT`. See `.env.example` and `docs/guide/configuration.md`.
## Notes ## Notes
- Commands sync from the scheduled trigger (`*/5 * * * *`): registered per-guild for instant availability and globally (24h dedup, ~1h propagation). - Commands sync from the scheduled trigger (`*/5 * * * *`): registered per-guild for instant availability and globally (24h dedup, ~1h propagation).

198
README.md
View file

@ -1,34 +1,40 @@
# WebHooker # 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 ## 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) - HMAC-SHA256 signature verification (Web Crypto API)
- Filter by event type, repo, actor, action, branch (incl. PR), keyword (supports regex) - Filter by event type, repo, actor, action, branch, keyword (supports regex)
- Rich Discord embeds with color coding, author avatars, fields, and timestamps - Rich messages with color coding, author avatars, fields, and timestamps — rendered as Discord embeds and Telegram HTML
- Route to channels or threads - Route to Discord channels/threads and Telegram chats/topics (multi-target routes)
- GitHub App OAuth for user actions (comment, merge, react) - `workflow_run` progress is edited **in place** (single message updated as the workflow advances) on both platforms
- **Web UI config console** (`/admin`) — manage routes with GitHub OAuth + admin whitelist - 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 - **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) - Graceful degradation (webhook-only mode if Discord unavailable)
## Architecture ## Architecture
```text ```text
GitHub Webhook → Cloudflare Worker (Hono) 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 /discord/interactions → verify (Ed25519) → handle command/button/modal
├── POST /telegram/webhook → verify (secret token) → handle /gh commands
├── GET /auth/github → OAuth flow ├── GET /auth/github → OAuth flow
├── GET /api/richheader → Telegram avatar link-preview card
├── POST /api/* → user actions (Bearer token auth) ├── POST /api/* → user actions (Bearer token auth)
├── /admin → routes, groups & send logs Web UI
└── GET /health → status check └── 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 - **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 ## Quick Start
@ -42,22 +48,28 @@ npx wrangler dev # Start local dev server
### Secrets (`.dev.vars` for local, Worker Secrets for production) ### Secrets (`.dev.vars` for local, Worker Secrets for production)
| Variable | Description | | Variable | Description |
| ------------------------ | ---------------------------------------------------------------------------------------------- | | --------------------------- | ---------------------------------------------------------------------------------------------- |
| `GITHUB_WEBHOOK_SECRET` | Webhook secret from GitHub | | `GITHUB_WEBHOOK_SECRET` | Webhook secret from GitHub |
| `GITHUB_APP_ID` | GitHub App ID | | `GITHUB_APP_ID` | GitHub App ID (not currently used by the code; kept for compatibility) |
| `GITHUB_PRIVATE_KEY` | App private key (PEM) | | `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_ID` | OAuth client ID |
| `GITHUB_CLIENT_SECRET` | OAuth client secret | | `GITHUB_CLIENT_SECRET` | OAuth client secret |
| `DISCORD_TOKEN` | Bot token | | `DISCORD_TOKEN` | Bot token |
| `DISCORD_PUBLIC_KEY` | Discord application public key (from the Developer Portal) — required for interactions | | `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) | | `DISCORD_APPLICATION_ID` | Discord application id (optional; auto-resolved via `GET /oauth2/applications/@me` if omitted) |
| `BASE_URL` | Public URL for OAuth callbacks | | `TELEGRAM_TOKEN` | Telegram bot token (from BotFather) — required for Telegram routes |
| `ADMIN_USER_IDS` | Comma-separated GitHub user IDs (or logins) allowed to access `/admin` | | `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
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 ```json
[ [
@ -65,28 +77,27 @@ Routes are stored in KV (`config:routes` as JSON). There are **no default routes
"id": "all-push", "id": "all-push",
"name": "Push Events", "name": "Push Events",
"enabled": true, "enabled": true,
"groupId": "default",
"filters": [{ "type": "event", "match": "push" }], "filters": [{ "type": "event", "match": "push" }],
"target": { "platform": "discord", "channelId": "CHANNEL_ID" } "targets": [
}, { "platform": "discord", "channelId": "CHANNEL_ID" },
{ { "platform": "telegram", "chatId": "-1001234567890" }
"id": "telegram-issues", ]
"name": "Issues to Telegram",
"enabled": true,
"filters": [{ "type": "event", "match": "issues" }],
"target": { "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`) ### 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`. 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. 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`. Sign out at `/admin/logout`.
@ -94,14 +105,14 @@ See `config.example.yaml` for full syntax examples.
### Filter Types ### Filter Types
| Type | Matches | Notes | | Type | Matches | Notes |
| --------- | -------------------------------------- | -------------------------------------------------------------------- | | --------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `event` | `push`, `pull_request`, `issues`, etc. | GitHub event name | | `event` | `push`, `pull_request`, `issues`, etc. | GitHub event name |
| `repo` | `org/repo` full name | | | `repo` | `org/repo` full name | |
| `actor` | Sender login | | | `actor` | Sender login | |
| `action` | `opened`, `closed`, `published`, etc. | | | `action` | `opened`, `closed`, `published`, etc. | |
| `branch` | Branch name | Works for push, PR, create/delete, workflow_run, code_scanning_alert | | `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 | | `keyword` | Text in payload body | Supports regex patterns; falls back to substring match |
Set `exclude: true` to invert any filter. Set `exclude: true` to invert any filter.
@ -114,13 +125,14 @@ Set `exclude: true` to invert any filter.
### OAuth ### OAuth
- `GET /auth/github` — Start GitHub OAuth flow (redirects to GitHub) - `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 - `DELETE /auth/token/:userId` — Revoke user token
### Actions (require `Authorization: Bearer <token>` header) ### Actions (require `Authorization: Bearer <token>` header)
- `POST /api/comment` — Create issue comment - `POST /api/comment` — Create issue comment
- `POST /api/merge` — Merge pull request - `POST /api/merge` — Merge pull request
- `POST /api/close` — Close pull request
- `POST /api/react` — Add reaction to issue - `POST /api/react` — Add reaction to issue
### Admin (require admin OAuth session) ### Admin (require admin OAuth session)
@ -130,6 +142,13 @@ Set `exclude: true` to invert any filter.
- `GET /admin/logout` — Sign out - `GET /admin/logout` — Sign out
- `GET /admin/api/routes` — List routes - `GET /admin/api/routes` — List routes
- `PUT /admin/api/routes` — Replace 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 ## GitHub App Setup
@ -142,11 +161,11 @@ Set `exclude: true` to invert any filter.
- **Webhook URL**: `https://your-domain/webhook` - **Webhook URL**: `https://your-domain/webhook`
- **Webhook secret**: generate and copy to `GITHUB_WEBHOOK_SECRET` - **Webhook secret**: generate and copy to `GITHUB_WEBHOOK_SECRET`
3. Set permissions: 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 - **Organization permissions**: Members (read) — if needed
4. Subscribe to events: 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 - 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 → save contents to `GITHUB_PRIVATE_KEY` env var 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 ### 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 | | OAuth | `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` and `BASE_URL` configured |
| User linked | Each user runs `/gh login` first | | 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 ## Deployment
```bash ```bash
# Set secrets in Cloudflare # Set secrets in Cloudflare
npx wrangler secret put GITHUB_WEBHOOK_SECRET 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_ID
npx wrangler secret put GITHUB_CLIENT_SECRET npx wrangler secret put GITHUB_CLIENT_SECRET
npx wrangler secret put DISCORD_TOKEN npx wrangler secret put DISCORD_TOKEN
npx wrangler secret put DISCORD_PUBLIC_KEY 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 # Create KV namespace
npx wrangler kv namespace create KV npx wrangler kv namespace create KV
# Update wrangler.jsonc with the KV namespace ID # 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 # Deploy
npx wrangler deploy npx wrangler deploy
``` ```
@ -253,34 +293,42 @@ npx wrangler deploy
npx wrangler dev # Local dev server (Miniflare) npx wrangler dev # Local dev server (Miniflare)
npm run typecheck # Type checking npm run typecheck # Type checking
npm run lint # ESLint npm run lint # ESLint
npm test # Unit tests (bun test)
``` ```
## Supported Events ## Supported Events
| Event | Formatter | | Event | Formatter |
| ----------------------------- | ------------------------------------------------ | | ----------------------------- | ------------------------------------------------------- |
| `push` | Commit list, branch, author | | `push` | Commit list, branch, author |
| `pull_request` | PR title, branch, diff stats | | `pull_request` | PR title, branch, diff stats |
| `issues` | Issue title, labels, assignees | | `issues` | Issue title, labels, assignees |
| `issue_comment` | Comment body, issue reference | | `issue_comment` | Comment body, issue reference |
| `workflow_run` | Workflow status, conclusion, duration | | `workflow_run` | Workflow status, conclusion, duration (edited in place) |
| `release` | Tag, body, assets | | `workflow_job` | Job name, status, conclusion |
| `create` / `delete` | Branch/tag creation/deletion | | `status` | Commit status, context, state |
| `star` | Star count, repository | | `deployment` | Environment, ref, task |
| `fork` | Fork source → target | | `deployment_status` | Environment, status, commit ref |
| `check_run` | Status, conclusion, details URL | | `check_run` | Status, conclusion, details URL |
| `pull_request_review` | Review state, body preview | | `check_suite` | Suite conclusion, head branch, commit |
| `pull_request_review_comment` | Inline code comment, file path, line | | `ping` | Webhook confirmation |
| `commit_comment` | Commit SHA, comment body | | `release` | Tag, body, assets |
| `deployment_status` | Environment, status, commit ref | | `create` / `delete` | Branch/tag creation/deletion |
| `member` | Collaborator add/remove | | `star` | Star count, repository |
| `label` | Label name, color, description | | `fork` | Fork source → target |
| `milestone` | Progress bar, open/closed counts, due date | | `pull_request_review` | Review state, body preview |
| `discussion` | Discussion title, category, action | | `pull_request_review_comment` | Inline code comment, file path, line |
| `discussion_comment` | Comment body, discussion reference | | `commit_comment` | Commit SHA, comment body |
| `repository` | Repo rename/transfer details | | `member` | Collaborator add/remove |
| `code_scanning_alert` | Severity, rule ID, file path | | `label` | Label name, color, description |
| `dependabot_alert` | Severity, package, vulnerable range, fix version | | `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 ## License

View file

@ -1,34 +1,40 @@
# WebHooker # WebHooker
GitHub webhook → Discord 分发服务。通过 Cloudflare Workers 接收 webhook 事件,应用过滤器,将格式化消息路由到 Discord 频道或子区 GitHub webhook → Discord / Telegram 分发服务。通过 Cloudflare Workers 接收 webhook 事件,应用过滤器,将格式化消息路由到 Discord 频道/子区与 Telegram 群组/话题
## 功能特性 ## 功能特性
- **23 种事件格式化** — 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+ 通用回退) - **28 种事件格式化** — 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+ 通用回退)
- HMAC-SHA256 签名验证Web Crypto API - HMAC-SHA256 签名验证Web Crypto API
- 按事件类型、仓库、操作人、操作、分支(含 PR、关键词支持正则过滤 - 按事件类型、仓库、操作人、操作、分支、关键词(支持正则)过滤
- 富 Discord embed颜色编码、作者头像、字段、时间戳 - 富消息:颜色编码、作者头像、字段、时间戳——渲染为 Discord embed 与 Telegram HTML
- 路由到频道或子区 - 路由到 Discord 频道/子区与 Telegram 群组/话题(一条路由可多目标)
- GitHub App OAuth 用户授权(评论、合并、反应) - `workflow_run` 进度**原地编辑**同一条消息(工作流推进时更新),两个平台均支持
- **Web 配置控制台**`/admin`)— 通过 GitHub OAuth + 管理员白名单管理路由 - GitHub OAuth 用户授权(评论、编辑评论、删除评论、合并、关闭、反应)
- **Web 配置控制台**`/admin`)— 通过 GitHub OAuth + 管理员白名单管理路由与分组、查看发送日志
- **Discord Interactions Endpoint**Ed25519 验签)支持 `/gh` 斜杠命令、消息右键菜单命令、PR 合并/关闭按钮与评论 modal - **Discord Interactions Endpoint**Ed25519 验签)支持 `/gh` 斜杠命令、消息右键菜单命令、PR 合并/关闭按钮与评论 modal
- Cloudflare KV 存储 token/状态/配置 - **Telegram `/gh` 命令**login/logout/comment/merge/close通过 Telegram webhook 接收,头像以链接预览卡片呈现
- Cloudflare KV 存储 token/状态/配置/会话 + D1 存储发送日志与平台账号绑定
- 优雅降级Discord 不可用时仅 webhook 模式) - 优雅降级Discord 不可用时仅 webhook 模式)
## 架构 ## 架构
```text ```text
GitHub Webhook → Cloudflare Worker (Hono) GitHub Webhook → Cloudflare Worker (Hono)
├── POST /webhook → 验证 → 过滤 → 格式化 → Discord (REST) ├── POST /webhook → 验证 → 去重 → 过滤 → 格式化 → Discord (REST) / Telegram (Bot API)
├── POST /discord/interactions → 验证 (Ed25519) → 处理命令/按钮/modal ├── POST /discord/interactions → 验证 (Ed25519) → 处理命令/按钮/modal
├── POST /telegram/webhook → 验证 (secret token) → 处理 /gh 命令
├── GET /auth/github → OAuth 流程 ├── GET /auth/github → OAuth 流程
├── GET /api/richheader → Telegram 头像链接预览卡片
├── POST /api/* → 用户操作Bearer token 鉴权) ├── POST /api/* → 用户操作Bearer token 鉴权)
├── /admin → 路由、分组与发送日志 Web UI
└── GET /health → 健康检查 └── GET /health → 健康检查
``` ```
- **Cloudflare Worker** — HTTP 入口、签名验证、路由分发 - **Cloudflare Worker** — HTTP 入口、签名验证、路由分发
- **Interactions Endpoint** — HTTPS 回调(无 Discord Gateway 连接、无 Durable Objectbot 保持离线,命令通过 API 注册 - **Interactions Endpoint** — HTTPS 回调(无 Discord Gateway 连接、无 Durable Objectbot 保持离线,命令通过 API 注册
- **KV** — Token 存储(`token:{userId}`、OAuth state`state:{hex}`)、路由配置(`config:routes` - **KV** — Token 存储(`token:{userId}`、OAuth state`state:{hex}`)、路由配置(`config:routes`)、分组配置(`config:groups`)、管理员会话(`session:{id}`)、投递去重(`delivery:{id}`)、消息更新追踪(`msg:*`
- **D1** — 发送日志(`send_logs`、Discord↔GitHub 绑定(`discord_links`、Telegram↔GitHub 绑定(`telegram_links`
## 快速开始 ## 快速开始
@ -42,22 +48,28 @@ npx wrangler dev # 启动本地开发服务器
### 密钥(本地用 `.dev.vars`,生产用 Worker Secrets ### 密钥(本地用 `.dev.vars`,生产用 Worker Secrets
| 变量 | 说明 | | 变量 | 说明 |
| ------------------------ | --------------------------------------------------------------------------- | | --------------------------- | --------------------------------------------------------------------------- |
| `GITHUB_WEBHOOK_SECRET` | GitHub webhook 密钥 | | `GITHUB_WEBHOOK_SECRET` | GitHub webhook 密钥 |
| `GITHUB_APP_ID` | GitHub App ID | | `GITHUB_APP_ID` | GitHub App ID当前代码未使用为兼容保留 |
| `GITHUB_PRIVATE_KEY` | App 私钥PEM | | `GITHUB_PRIVATE_KEY` | App 私钥PKCS#8 PEM当前代码未使用为兼容保留 |
| `GITHUB_CLIENT_ID` | OAuth Client ID | | `GITHUB_CLIENT_ID` | OAuth Client ID |
| `GITHUB_CLIENT_SECRET` | OAuth Client Secret | | `GITHUB_CLIENT_SECRET` | OAuth Client Secret |
| `DISCORD_TOKEN` | 机器人 token | | `DISCORD_TOKEN` | 机器人 token |
| `DISCORD_PUBLIC_KEY` | Discord 应用的公钥(开发者门户获取)—— 交互功能必需 | | `DISCORD_PUBLIC_KEY` | Discord 应用的公钥(开发者门户获取)—— 交互功能必需 |
| `DISCORD_APPLICATION_ID` | Discord 应用 ID可选省略时通过 `GET /oauth2/applications/@me` 自动获取) | | `DISCORD_APPLICATION_ID` | Discord 应用 ID可选省略时通过 `GET /oauth2/applications/@me` 自动获取) |
| `BASE_URL` | 公网地址(用于 OAuth 回调) | | `TELEGRAM_TOKEN` | Telegram Bot TokenBotFather 获取)—— Telegram 路由必需 |
| `ADMIN_USER_IDS` | 允许访问 `/admin` 的 GitHub 用户 ID或登录名逗号分隔 | | `TELEGRAM_WEBHOOK_SECRET` | 可选;`POST /telegram/webhook` 的验签密钥 |
| `TELEGRAM_RICH_HEADER_HOST` | 可选;覆盖内置 `GET /api/richheader` 的 Telegram 头像卡片地址 |
| `BASE_URL` | 公网地址(用于 OAuth 回调与 Telegram webhook 同步) |
| `ADMIN_USER_IDS` | 允许访问 `/admin` 的 GitHub 用户 ID或登录名逗号分隔 |
| `DOCS_URL` | 可选;落地页使用的文档站点 URL |
| `GITHUB_REPO_URL` | 可选;落地页使用的 GitHub 仓库 URL |
| `LEGAL_CONTACT` | 可选;`/terms``/privacy` 页面展示的联系方式 |
### 路由配置 ### 路由配置
路由存储在 KV`config:routes`JSON 格式)。**没有默认路由**——每条路由(包括目标)都必须显式定义,可通过 Web 控制台(`/admin`)或直接向 KV 存储 JSON 数组: 路由存储在 KV`config:routes`JSON 格式)。**没有默认路由**——每条路由(包括目标)都必须显式定义,可通过 Web 控制台(`/admin`)或直接向 KV 存储 JSON 数组。一条路由可携带多个 `targets`,因此一个规则可以同时转发到多个频道
```json ```json
[ [
@ -65,28 +77,27 @@ npx wrangler dev # 启动本地开发服务器
"id": "all-push", "id": "all-push",
"name": "Push 事件", "name": "Push 事件",
"enabled": true, "enabled": true,
"groupId": "default",
"filters": [{ "type": "event", "match": "push" }], "filters": [{ "type": "event", "match": "push" }],
"target": { "platform": "discord", "channelId": "频道ID" } "targets": [
}, { "platform": "discord", "channelId": "频道ID" },
{ { "platform": "telegram", "chatId": "-1001234567890" }
"id": "telegram-issues", ]
"name": "Issue 推送 Telegram",
"enabled": true,
"filters": [{ "type": "event", "match": "issues" }],
"target": { "platform": "telegram", "chatId": "-1001234567890" }
} }
] ]
``` ```
`target.platform` 选择推送目标:`discord`(默认)或 `telegram`。Discord 路由需 `target.channelId`(可选 `threadId` 指向子区Telegram 路由需 `target.chatId`(群组聊天 ID可选 `topicId` 指向话题)。不存在默认频道回退。 `target.platform` 选择推送目标:`discord`(默认)或 `telegram`。Discord 目标需 `target.channelId`(可选 `threadId` 指向子区Telegram 目标需 `target.chatId`(可选 `topicId` 指向话题)。旧的单数 `target` 字段仍会被自动迁移。不存在默认频道回退。
路由隶属于**分组**KV `config:groups`),分组用于限定管理权限,并可限制哪些组织/用户的事件流入。完整模式见 `config.example.yaml``docs/zh/guide/configuration.md`
### Web 控制台(`/admin` ### Web 控制台(`/admin`
内置的配置控制台让你在浏览器中管理路由(新增 / 编辑 / 删除 / 开关),无需操作 KV 内置的配置控制台让你在浏览器中管理路由与分组(新增 / 编辑 / 删除 / 开关 / 排序),并查看发送日志——无需操作 KV
1. 设置 `ADMIN_USER_IDS` 为允许管理控制台的 GitHub 用户 ID或登录名例如 `ADMIN_USER_IDS=12345,RhenCloud` 1. 设置 `ADMIN_USER_IDS` 为允许管理控制台的 GitHub 用户 ID或登录名例如 `ADMIN_USER_IDS=12345,RhenCloud`
2. 访问 `/admin` 并用 GitHub 登录,仅白名单内用户可进入。 2. 访问 `/admin` 并用 GitHub 登录,仅白名单内用户可进入。
3. 修改会立即写入 KV `config:routes`webhook 管线随即生效。 3. 修改会立即写入 KVwebhook 管线随即生效。
`/admin/logout` 退出登录。 `/admin/logout` 退出登录。
@ -94,14 +105,14 @@ npx wrangler dev # 启动本地开发服务器
### 过滤器类型 ### 过滤器类型
| 类型 | 匹配内容 | 备注 | | 类型 | 匹配内容 | 备注 |
| --------- | ----------------------------------- | --------------------------------------------------------------- | | --------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `event` | `push``pull_request``issues` 等 | GitHub 事件名 | | `event` | `push``pull_request``issues` 等 | GitHub 事件名 |
| `repo` | `org/repo` 全名 | | | `repo` | `org/repo` 全名 | |
| `actor` | 发送者登录名 | | | `actor` | 发送者登录名 | |
| `action` | `opened``closed``published` 等 | | | `action` | `opened``closed``published` 等 | |
| `branch` | 分支名 | 支持 push、PR、create/delete、workflow_run、code_scanning_alert | | `branch` | 分支名 | 支持 push、PR/review、create/delete、workflow_run、workflow_job、check_suite、deployment、code_scanning_alert |
| `keyword` | payload 中的文本 | 支持正则表达式;无效正则回退为子串匹配 | | `keyword` | payload 中的文本 | 支持正则表达式;无效正则回退为子串匹配 |
设置 `exclude: true` 可取反过滤器。 设置 `exclude: true` 可取反过滤器。
@ -114,13 +125,14 @@ npx wrangler dev # 启动本地开发服务器
### OAuth ### OAuth
- `GET /auth/github` — 发起 GitHub OAuth 授权(重定向到 GitHub - `GET /auth/github` — 发起 GitHub OAuth 授权(重定向到 GitHub
- `GET /auth/github/callback` — OAuth 回调(交换 code 为 token - `GET /auth/github/callback` — OAuth 回调(交换 code 为 token;管理员会话 / Discord 绑定 / Telegram 绑定
- `DELETE /auth/token/:userId` — 撤销用户 token - `DELETE /auth/token/:userId` — 撤销用户 token
### 操作接口(需要 `Authorization: Bearer <token>` 头) ### 操作接口(需要 `Authorization: Bearer <token>` 头)
- `POST /api/comment` — 创建 issue 评论 - `POST /api/comment` — 创建 issue 评论
- `POST /api/merge` — 合并 PR - `POST /api/merge` — 合并 PR
- `POST /api/close` — 关闭 PR
- `POST /api/react` — 添加 issue 反应 - `POST /api/react` — 添加 issue 反应
### 管理接口(需要管理员 OAuth 会话) ### 管理接口(需要管理员 OAuth 会话)
@ -130,6 +142,13 @@ npx wrangler dev # 启动本地开发服务器
- `GET /admin/logout` — 退出登录 - `GET /admin/logout` — 退出登录
- `GET /admin/api/routes` — 列出路由 - `GET /admin/api/routes` — 列出路由
- `PUT /admin/api/routes` — 替换路由 - `PUT /admin/api/routes` — 替换路由
- `GET /admin/api/groups` — 列出分组(按权限过滤)
- `PUT /admin/api/groups` — 替换分组(仅超级管理员)
- `GET /admin/api/groups/:groupId/routes` — 列出某分组的路由
- `PUT /admin/api/groups/:groupId/routes` — 替换某分组的路由
- `GET /admin/api/me` — 当前会话 / 权限范围
- `GET /admin/api/logs` — 发送日志(按权限过滤)
- `GET /admin/api/logs/:id` — 单条发送日志
## GitHub App 配置教程 ## GitHub App 配置教程
@ -142,10 +161,10 @@ npx wrangler dev # 启动本地开发服务器
- **Webhook URL**`https://your-domain/webhook` - **Webhook URL**`https://your-domain/webhook`
- **Webhook secret**:生成并复制到 `GITHUB_WEBHOOK_SECRET` - **Webhook secret**:生成并复制到 `GITHUB_WEBHOOK_SECRET`
3. 设置权限: 3. 设置权限:
- **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) — 如需要 - **Organization permissions**Members (read) — 如需要
4. 订阅事件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 4. 订阅事件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. 生成私钥 → 将内容保存到 `GITHUB_PRIVATE_KEY` 环境变量 5. 生成私钥 `GITHUB_PRIVATE_KEY` 当前未被代码使用OAuth 流程只用到 Client ID/Secret因此为可选若日后启用 GitHub App 认证可再配置。
### 2. 安装 App ### 2. 安装 App
@ -224,24 +243,45 @@ bot 通过定时任务(每 5 分钟)同步注册原生的**斜杠命令**与
| OAuth | 已配置 `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET``BASE_URL` | | OAuth | 已配置 `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET``BASE_URL` |
| 用户绑定 | 每个用户先执行 `/gh login` | | 用户绑定 | 每个用户先执行 `/gh login` |
## Telegram 机器人配置
1. 用 [@BotFather](https://t.me/BotFather) 创建机器人,将 Token 复制到 `TELEGRAM_TOKEN`
2. (可选)设置 `TELEGRAM_WEBHOOK_SECRET`webhook 注册时会作为 `secret_token` 传给 Telegram`POST /telegram/webhook` 使用时间安全比较校验。
3. Worker 会在定时任务中自动同步 webhook`setWebhook` 指向 `{BASE_URL}/telegram/webhook`),因此无需手动调用 `setWebhook`——只需确保 `BASE_URL` 已设置。
4. 将机器人加入群组(或启用话题),在路由配置中用 `chatId` / `topicId` 指定目标。
在 Telegram 中,`/gh` 命令通过在通知消息上**回复**来使用:
- `/gh login` — 绑定你的 GitHub 账号(返回 OAuth 链接)
- `/gh logout` — 解除绑定
- `/gh comment <内容>` — 回复一条 issue/PR 通知,以本人身份评论
- `/gh merge` / `/gh close` — 回复一条 PR 通知,合并/关闭该 PR
头像使用内置 `GET /api/richheader` 渲染为链接预览卡片(可用 `TELEGRAM_RICH_HEADER_HOST` 覆盖)。
## 部署 ## 部署
```bash ```bash
# 在 Cloudflare 设置密钥 # 在 Cloudflare 设置密钥
npx wrangler secret put GITHUB_WEBHOOK_SECRET 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_ID
npx wrangler secret put GITHUB_CLIENT_SECRET npx wrangler secret put GITHUB_CLIENT_SECRET
npx wrangler secret put DISCORD_TOKEN npx wrangler secret put DISCORD_TOKEN
npx wrangler secret put DISCORD_PUBLIC_KEY 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
# 创建 KV 命名空间 # 创建 KV 命名空间
npx wrangler kv namespace create KV npx wrangler kv namespace create KV
# 更新 wrangler.jsonc 中的 KV namespace ID # 更新 wrangler.jsonc 中的 KV namespace ID
# 创建 D1 数据库并执行迁移
npx wrangler d1 create webhooker
# 更新 wrangler.jsonc d1_databases 中的数据库 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
# 部署 # 部署
npx wrangler deploy npx wrangler deploy
``` ```
@ -252,6 +292,7 @@ npx wrangler deploy
npx wrangler dev # 本地开发服务器Miniflare npx wrangler dev # 本地开发服务器Miniflare
npm run typecheck # 类型检查 npm run typecheck # 类型检查
npm run lint # ESLint npm run lint # ESLint
npm test # 单元测试bun test
``` ```
## 支持的事件 ## 支持的事件
@ -262,16 +303,21 @@ npm run lint # ESLint
| `pull_request` | PR 标题、分支、差异统计 | | `pull_request` | PR 标题、分支、差异统计 |
| `issues` | Issue 标题、标签、指派人 | | `issues` | Issue 标题、标签、指派人 |
| `issue_comment` | 评论内容、Issue 引用 | | `issue_comment` | 评论内容、Issue 引用 |
| `workflow_run` | 工作流状态、结论、耗时 | | `workflow_run` | 工作流状态、结论、耗时(原地更新) |
| `workflow_job` | 作业名、状态、结论 |
| `status` | 提交状态、上下文、状态值 |
| `deployment` | 环境、引用、任务 |
| `deployment_status` | 环境、状态、commit ref |
| `check_run` | 状态、结论、详情链接 |
| `check_suite` | 套件结论、head 分支、提交 |
| `ping` | Webhook 确认 |
| `release` | Tag、内容、资产 | | `release` | Tag、内容、资产 |
| `create` / `delete` | 分支/tag 创建或删除 | | `create` / `delete` | 分支/tag 创建或删除 |
| `star` | Star 数量、仓库 | | `star` | Star 数量、仓库 |
| `fork` | Fork 来源 → 目标 | | `fork` | Fork 来源 → 目标 |
| `check_run` | 状态、结论、详情链接 |
| `pull_request_review` | 审查状态、内容预览 | | `pull_request_review` | 审查状态、内容预览 |
| `pull_request_review_comment` | 行内代码评论、文件路径、行号 | | `pull_request_review_comment` | 行内代码评论、文件路径、行号 |
| `commit_comment` | Commit SHA、评论内容 | | `commit_comment` | Commit SHA、评论内容 |
| `deployment_status` | 环境、状态、commit ref |
| `member` | 协作者添加/移除 | | `member` | 协作者添加/移除 |
| `label` | 标签名、颜色、描述 | | `label` | 标签名、颜色、描述 |
| `milestone` | 进度条、open/closed 计数、截止日期 | | `milestone` | 进度条、open/closed 计数、截止日期 |
@ -281,6 +327,8 @@ npm run lint # ESLint
| `code_scanning_alert` | 严重程度、规则 ID、文件路径 | | `code_scanning_alert` | 严重程度、规则 ID、文件路径 |
| `dependabot_alert` | 严重程度、包名、受影响范围、修复版本 | | `dependabot_alert` | 严重程度、包名、受影响范围、修复版本 |
任何其他事件类型回退到通用格式化器(事件类型、操作、操作人、仓库、原始载荷)。
## 许可证 ## 许可证
MIT MIT

View file

@ -1,34 +1,63 @@
# Routes # Routes
# Each route defines a filter chain and target Discord channel # Each route defines a filter chain and one or more target destinations
# (Discord channel/thread and/or Telegram chat/topic). See docs/guide/configuration.md.
# Groups scope admin access and can restrict which org/user events flow in.
groups:
- id: default
name: "Default"
adminIds: ["your-github-id"]
# owners: ["myorg"] # restrict events to this org/user (optional)
# emoji: true # include emoji in messages (default true)
routes: routes:
- id: all-push - id: all-push
name: "All Push Events" name: "All Push Events"
enabled: true enabled: true
groupId: default
filters: filters:
- type: event - type: event
match: push match: push
target: targets:
channelId: "CHANNEL_ID_HERE" - platform: discord
channelId: "CHANNEL_ID_HERE"
# - id: specific-repo # - id: specific-repo
# name: "Specific Repo" # name: "Specific Repo"
# enabled: true # enabled: true
# groupId: default
# filters: # filters:
# - type: repo # - type: repo
# match: "org/repo" # match: "org/repo"
# - type: event # - type: event
# match: [pull_request, issues] # match: [pull_request, issues]
# target: # targets:
# channelId: "CHANNEL_ID_HERE" # - platform: discord
# threadId: "THREAD_ID_HERE" # channelId: "CHANNEL_ID_HERE"
# threadId: "THREAD_ID_HERE"
# - platform: telegram
# chatId: "-1001234567890"
# topicId: "9876543210"
# - id: exclude-bot # - id: exclude-bot
# name: "Exclude Bot Actions" # name: "Exclude Bot Actions"
# enabled: true # enabled: true
# groupId: default
# filters: # filters:
# - type: actor # - type: actor
# match: "[bot]" # match: "[bot]"
# exclude: true # exclude: true
# target: # targets:
# channelId: "CHANNEL_ID_HERE" # - platform: discord
# channelId: "CHANNEL_ID_HERE"
# Fallback routes only fire when no other (non-fallback) route matched.
# - id: fallback-all
# name: "Fallback: everything else"
# enabled: true
# groupId: default
# fallback: true
# filters: []
# targets:
# - platform: telegram
# chatId: "-1001234567890"

View file

@ -47,6 +47,7 @@ GitHub redirects here after authorization. Exchanges the code for an access toke
- **Browser flow** (`Accept: text/html`): sets an admin session cookie, then redirects to the `redirect` target. Users without admin access are redirected to `/admin?error=forbidden`. - **Browser flow** (`Accept: text/html`): sets an admin session cookie, then redirects to the `redirect` target. Users without admin access are redirected to `/admin?error=forbidden`.
- **JSON flow**: returns `{ "userId": "...", "login": "...", "redirectTo": "..." }`. - **JSON flow**: returns `{ "userId": "...", "login": "...", "redirectTo": "..." }`.
- **Discord link flow** (started with a pending `discordUserId`): links the Discord user to this GitHub account, returning `{ "ok": true, "discordUserId": "...", "login": "..." }` — or a success page in the browser. - **Discord link flow** (started with a pending `discordUserId`): links the Discord user to this GitHub account, returning `{ "ok": true, "discordUserId": "...", "login": "..." }` — or a success page in the browser.
- **Telegram link flow** (started with a pending `telegramUserId`): links the Telegram user to this GitHub account, returns `{ "ok": true, "telegramUserId": "...", "login": "..." }`, and sends a confirmation message to the pending `telegramChatId`.
### Revoke Token ### Revoke Token
@ -77,7 +78,7 @@ Tokens are stored in KV with key pattern `token:{userId}`:
} }
``` ```
`expiresAt` is a Unix timestamp in milliseconds. KV entries expire at 90% of the token's lifetime (minimum 60 seconds). A reverse index `token-reverse:{sha256 of token}` maps the access token back to its user id so Bearer-authenticated endpoints can resolve the caller. Discord users linked to a GitHub account are stored in the D1 `discord_links` table. `expiresAt` is a Unix timestamp in milliseconds. KV entries expire at 90% of the token's lifetime (minimum 60 seconds). A reverse index `token-reverse:{sha256 of token}` maps the access token back to its user id so Bearer-authenticated endpoints can resolve the caller. Discord users linked to a GitHub account are stored in the D1 `discord_links` table; Telegram users in the D1 `telegram_links` table.
## Using Tokens ## Using Tokens

View file

@ -33,6 +33,7 @@ https://your-worker.workers.dev
| `PUT` | `/admin/api/groups/:id/routes` | Admin session | Replace a group's routes | | `PUT` | `/admin/api/groups/:id/routes` | Admin session | Replace a group's routes |
| `GET` | `/admin/api/me` | Admin session | Current session info | | `GET` | `/admin/api/me` | Admin session | Current session info |
| `GET` | `/admin/api/logs` | Admin session | Send logs (scoped) | | `GET` | `/admin/api/logs` | Admin session | Send logs (scoped) |
| `GET` | `/admin/api/logs/:id` | Admin session | Single send-log entry (scoped) |
## Admin Console ## Admin Console
@ -40,7 +41,7 @@ See [Configuration → Web UI](../guide/configuration.md#web-ui) for setup. Admi
- `GET /admin` — Serves the config console HTML - `GET /admin` — Serves the config console HTML
- `GET /admin/api/routes` — Returns `{ "routes": Route[] }` - `GET /admin/api/routes` — Returns `{ "routes": Route[] }`
- `PUT /admin/api/routes` — Body `{ "routes": Route[] }`; validates each route (id pattern, unique id, name, enabled, `groupId`, filters — empty only allowed for `fallback` routes — and platform-aware target: `target.channelId` for Discord, `target.chatId` for Telegram) and persists to KV `config:routes`. Returns `200 { ok, count }` or `400 { error }` / `401 { error }`. - `PUT /admin/api/routes` — Body `{ "routes": Route[] }`; validates each route (id pattern, unique id, name, enabled, `groupId`, filters — empty only allowed for `fallback` routes — and platform-aware targets: `target.channelId` for Discord, `target.chatId` for Telegram) and persists to KV `config:routes`. Returns `200 { ok, count }` or `400 { error }` / `401 { error }` / `403 { error }`.
## Health Check ## Health Check
@ -66,11 +67,11 @@ Accepts GitHub webhook payloads. Requires valid `X-Hub-Signature-256` header.
**Headers:** **Headers:**
| Header | Required | Description | | Header | Required | Description |
| --------------------- | -------- | --------------------- | | --------------------- | -------- | ------------------------------------------------ |
| `X-Hub-Signature-256` | Yes | HMAC-SHA256 signature | | `X-Hub-Signature-256` | Yes | HMAC-SHA256 signature |
| `X-GitHub-Event` | Yes | Event type name | | `X-GitHub-Event` | Yes | Event type name |
| `X-GitHub-Delivery` | Yes | Unique delivery ID | | `X-GitHub-Delivery` | No | Unique delivery ID (used for dedup when present) |
**Request Body:** GitHub webhook JSON payload (max 1MB). **Request Body:** GitHub webhook JSON payload (max 1MB).
@ -82,6 +83,8 @@ Accepts GitHub webhook payloads. Requires valid `X-Hub-Signature-256` header.
} }
``` ```
When `X-GitHub-Delivery` is present and the same delivery was already processed within the last 5 minutes, the worker responds `200 { "ok": true, "duplicate": true }` without re-dispatching.
**Error Responses:** **Error Responses:**
| Status | Body | Cause | | Status | Body | Cause |

View file

@ -14,23 +14,25 @@ npm run dev # Start local dev server
```text ```text
src/ src/
├── index.ts # CF Workers entry (fetch + scheduled), scheduled = command sync ├── index.ts # CF Workers entry (fetch + scheduled), scheduled = Discord command sync + Telegram webhook sync
├── types.ts # Env, Config, Route, Filter, WebhookEvent, NeutralMessage ├── types.ts # Env, Config, Route, Filter, Group, WebhookEvent, NeutralMessage
├── config.ts # Loads routes from KV (returns [] if unset), builds Config from env ├── config.ts # Loads routes from KV (returns [] if unset), builds Config from env
├── server.ts # Hono app: /health, /webhook, /discord/interactions, mounts /auth, /admin + / ├── server.ts # Hono app: /health, /webhook, /discord/interactions, /telegram/webhook, mounts /auth, /admin + /
├── core/ ├── core/
│ └── dispatch.ts # Platform-neutral dispatch: match routes → formatEvent → getDriver().send │ └── dispatch.ts # Platform-neutral dispatch: match routes → formatEvent → getDriver().send/edit
├── events/ # GitHub webhook pipeline ├── events/ # GitHub webhook pipeline (legacy src/webhook.ts is dead code)
│ ├── verify.ts # HMAC signature verification (Web Crypto, timing-safe) │ ├── verify.ts # HMAC signature verification (Web Crypto, timing-safe)
│ ├── parse.ts # parseEvent (headers + body → WebhookEvent) │ ├── parse.ts # parseEvent (headers + body → WebhookEvent)
│ └── match.ts # matchRoute, eventOwners, extractBranch, keyword filtering │ └── match.ts # matchRoute, eventOwners, extractBranch, keyword filtering
├── formatters/ # Platform-neutral formatters (produce NeutralMessage) ├── formatters/ # Platform-neutral formatters (produce NeutralMessage)
│ ├── index.ts # formatEvent: 24-event switch → NeutralMessage + re-exports │ ├── index.ts # formatEvent: 28-event switch → NeutralMessage + re-exports
│ ├── colors.ts # GITHUB_COLORS + WORKFLOW_CONCLUSION_EMOJI │ ├── colors.ts # GITHUB_COLORS + WORKFLOW_CONCLUSION_EMOJI
│ ├── helpers.ts # emojiPrefix, T, buildMessage │ ├── helpers.ts # emojiPrefix, T, buildMessage
│ └── *.ts # push, pull-request, issues, comments, workflow, release, repo, ... │ └── *.ts # push, pull-request, issues, comments, workflow, release, create, repo,
│ # check, review, commit-comment, deployment, member, label, milestone,
│ # discussion, repository, security, generic, ping
├── drivers/ # Platform drivers (pluggable push targets) ├── drivers/ # Platform drivers (pluggable push targets)
│ ├── types.ts # PlatformDriver interface + SendResult │ ├── types.ts # PlatformDriver interface + SendResult (send + edit)
│ ├── index.ts # getDriver() registry (discord + telegram) │ ├── index.ts # getDriver() registry (discord + telegram)
│ ├── discord/ # index.ts (driver), render.ts (NeutralMessage → embed), │ ├── discord/ # index.ts (driver), render.ts (NeutralMessage → embed),
│ │ # rest.ts, interactions.ts, commands.ts │ │ # rest.ts, interactions.ts, commands.ts
@ -38,21 +40,24 @@ src/
│ # rest.ts (chat_id + message_thread_id), updates.ts (webhook verify), │ # rest.ts (chat_id + message_thread_id), updates.ts (webhook verify),
│ # commands.ts (/gh login|logout|comment|merge|close + reply parsing) │ # commands.ts (/gh login|logout|comment|merge|close + reply parsing)
├── github/ # GitHub OAuth + as-user actions ├── github/ # GitHub OAuth + as-user actions
│ ├── oauth.ts # OAuth URL, callback token exchange, getUserOctokit, actions │ ├── oauth.ts # OAuth URL, callback token exchange, getUserOctokit, comment/merge/close actions
│ └── store.ts # KV token CRUD + D1 discord-link/telegram-link mapping │ └── store.ts # KV token CRUD + D1 discord-link/telegram-link mapping
├── web/ # HTTP UI/API routes ├── web/ # HTTP UI/API routes
│ ├── oauth-routes.ts # GET /auth/github, callback, DELETE /token/:userId (KV state) │ ├── oauth-routes.ts # GET /auth/github, callback (admin session / discord-link / telegram-link), DELETE /token/:userId
│ ├── action-routes.ts # POST /api/comment|merge|close|react (Bearer token auth via KV lookup) │ ├── action-routes.ts # POST /api/comment|merge|close|react (Bearer token auth via KV lookup)
│ ├── admin-routes.ts # /admin API: routes, groups, me, logs (session + scope auth) │ ├── admin-routes.ts # /admin API: routes, groups, me, logs (session + scope auth)
│ ├── session.ts # Admin session CRUD (KV session:{id}), cookie helpers │ ├── session.ts # Admin session CRUD (KV session:{id}), cookie helpers
│ ├── groups.ts # Group loading, group-admin access scoping │ ├── groups.ts # Group loading, group-admin access scoping
│ ├── home-routes.ts # Landing page routes │ ├── home-routes.ts # Landing page routes
│ └── legal-routes.ts # Legal page routes │ ├── legal-routes.ts # Legal page routes
│ └── richheader-routes.ts # GET /api/richheader (Telegram avatar card)
└── lib/ # Shared infrastructure └── lib/ # Shared infrastructure
├── i18n.ts # Message language overrides (en/zh) ├── i18n.ts # Message language overrides (en/zh)
├── send-log.ts # Send logging (D1 send_logs) ├── send-log.ts # Send logging (D1 send_logs)
├── log.ts # JSON console logger (info/warn/error/fatal) ├── log.ts # JSON console logger (info/warn/error/fatal)
└── locales/ # en.ts, zh.ts translation dictionaries └── locales/ # en.ts, zh.ts translation dictionaries
src/__tests__/ # Unit tests (bun test)
``` ```
## Scripts ## Scripts
@ -93,16 +98,17 @@ curl http://localhost:8787/health
## Adding a New Event Formatter ## Adding a New Event Formatter
1. Add the event type to `GITHUB_COLORS` in `src/formatters/colors.ts` (if new color needed) 1. Add the event type to `GITHUB_COLORS` in `src/formatters/colors.ts` (if new color needed)
2. Add action labels to `ACTION_LABELS` (if new actions) 2. Add action labels to the locale dictionaries in `src/lib/locales/en.ts` and `src/lib/locales/zh.ts` (if new actions)
3. Create a `formatEventType` function in `src/formatters/` 3. Create a `formatEventType` function in `src/formatters/`
4. Add the case to the `formatEvent` switch statement in `src/formatters/index.ts` 4. Add the case to the `formatEvent` switch statement in `src/formatters/index.ts`
5. Update `extractBranch` in `src/events/match.ts` if the event has branch info 5. Update `extractBranch` in `src/events/match.ts` if the event has branch info
6. Add the event to the documentation in `docs/events/supported.md` 6. Add the event to the documentation in `docs/events/supported.md` and `docs/zh/events/supported.md`
7. Subscribe to the event in your GitHub App settings 7. Add the event to the README (`README.md` and `README.zh.md`) event tables and the GitHub App event subscription list
8. Subscribe to the event in your GitHub App settings
## Pull Request Guidelines ## Pull Request Guidelines
- Keep changes focused and atomic - Keep changes focused and atomic
- Include type annotations for all function returns - Include type annotations for all function returns
- Run `npm run typecheck && npm run lint && npm run format:check` before submitting - Run `npm run typecheck && npm run lint && npm run format:check` before submitting
- Update documentation if adding features - Update documentation if adding features (see the checklist in `AGENTS.md` → Documentation): README (`README.md` / `README.zh.md`), VitePress docs (`docs/` and `docs/zh/`), and example config files

View file

@ -1,6 +1,6 @@
# Supported Events # Supported Events
WebHooker supports 23 GitHub webhook event types, each with a dedicated formatter that produces rich Discord embeds. Unsupported events fall through to a generic formatter. WebHooker supports 28 GitHub webhook event types, each with a dedicated formatter that produces rich Discord embeds and Telegram HTML messages. Unsupported events fall through to a generic formatter.
## Events Table ## Events Table
@ -11,16 +11,21 @@ WebHooker supports 23 GitHub webhook event types, each with a dedicated formatte
| `issues` | Issue opened/closed/edited | Issue title, labels, assignees | | `issues` | Issue opened/closed/edited | Issue title, labels, assignees |
| `issue_comment` | Comment on issue or PR | Comment body, issue reference | | `issue_comment` | Comment on issue or PR | Comment body, issue reference |
| `workflow_run` | CI/CD workflow phase updated | Workflow status, conclusion, duration; phases update a single message in place | | `workflow_run` | CI/CD workflow phase updated | Workflow status, conclusion, duration; phases update a single message in place |
| `workflow_job` | CI job phase updated | Job name, status, conclusion, workflow |
| `status` | Commit status updated | Commit status, context, state, commit link |
| `deployment` | Deployment created | Environment, ref, task |
| `deployment_status` | Deployment status updated | Environment, status, commit ref |
| `check_run` | Check run completed | Status, conclusion, details URL |
| `check_suite` | Check suite completed | Suite conclusion, head branch, commit link |
| `ping` | Webhook confirmation | Webhook confirmation, event types subscribed |
| `release` | Release published/edited | Tag, body, assets, pre-release flag | | `release` | Release published/edited | Tag, body, assets, pre-release flag |
| `create` | Branch or tag created | Ref name, ref type | | `create` | Branch or tag created | Ref name, ref type |
| `delete` | Branch or tag deleted | Ref name, ref type | | `delete` | Branch or tag deleted | Ref name, ref type |
| `star` | Repository starred/unstarred | Star count, action | | `star` | Repository starred/unstarred | Star count, action |
| `fork` | Repository forked | Source → target fork | | `fork` | Repository forked | Source → target fork |
| `check_run` | Check run completed | Status, conclusion, details URL |
| `pull_request_review` | PR review submitted | Review state (approved/changes/commented), body | | `pull_request_review` | PR review submitted | Review state (approved/changes/commented), body |
| `pull_request_review_comment` | Inline code review comment | File path, line number, comment body | | `pull_request_review_comment` | Inline code review comment | File path, line number, comment body |
| `commit_comment` | Comment on a commit | Commit SHA, comment body | | `commit_comment` | Comment on a commit | Commit SHA, comment body |
| `deployment_status` | Deployment status updated | Environment, status, commit ref |
| `member` | Collaborator added/removed | Member login, action | | `member` | Collaborator added/removed | Member login, action |
| `label` | Label created/edited/deleted | Label name, color, description | | `label` | Label created/edited/deleted | Label name, color, description |
| `milestone` | Milestone opened/closed | Progress bar, issue counts, due date | | `milestone` | Milestone opened/closed | Progress bar, issue counts, due date |
@ -32,18 +37,17 @@ WebHooker supports 23 GitHub webhook event types, each with a dedicated formatte
## Color Coding ## Color Coding
Each event type uses a distinct color in the Discord embed: Each event type uses a distinct color in the Discord embed (from `src/formatters/colors.ts`):
| Color | Events | | Color | Events |
| ------------------ | -------------------------------------------------------------------- | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Green (`#2ea44f`) | push, issue opened, PR opened, release published, star, member added | | Green (`#2da44e`) | push, PR opened / ready for review, issue opened, workflow success, release published, check success, review approved, deployment success, member added, milestone closed, discussion answered |
| Red (`#d73a49`) | issue closed, PR closed, deployment failure, dependabot critical | | Red (`#f85149`) | PR closed, issue closed, workflow failure, release deleted, delete, check failure, review changes requested, deployment failure, member removed, code scanning / dependabot critical & high |
| Purple (`#7057ff`) | PR merged, discussion created | | Purple (`#8957e5`) | PR merged, label, discussion |
| Blue (`#0366d6`) | PR review commented, issue comment, workflow run | | Blue (`#1f6feb`) | PR (other actions), issue reopened, fork, milestone opened |
| Yellow (`#dbab09`) | PR review changes requested, deployment pending | | Yellow (`#d29922`) | workflow (queued/running/other), release prerelease, star, check (other), deployment pending, code scanning / dependabot medium |
| Teal (`#00897b`) | check run, code scanning | | Gray (`#6e7681`) | issue comment, commit comment, discussion comment |
| Orange (`#e67e22`) | label, milestone | | Gray (`#8b949e`) | review commented, repository, code scanning / dependabot low, default |
| Gray (`#6a737d`) | delete, repository, member removed |
## Generic Fallback ## Generic Fallback
@ -63,11 +67,11 @@ Any event type without a dedicated formatter falls through to the generic format
See the [Filter Tutorial](../guide/filters) for a hands-on guide with worked examples. See the [Filter Tutorial](../guide/filters) for a hands-on guide with worked examples.
| Filter | Works With | | Filter | Works With |
| --------- | ----------------------------------------------------------------------------------------------------------------------- | | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event` | All events | | `event` | All events |
| `repo` | All events | | `repo` | All events |
| `actor` | All events | | `actor` | All events |
| `action` | Events with `action` field in payload | | `action` | Events with `action` field in payload |
| `branch` | push, pull_request, pull_request_review, pull_request_review_comment, create, delete, workflow_run, code_scanning_alert | | `branch` | push, pull_request, pull_request_review, pull_request_review_comment, create, delete, workflow_run, workflow_job, check_suite, deployment, code_scanning_alert |
| `keyword` | All events (searches full payload body) | | `keyword` | All events (searches full payload body) |

View file

@ -9,13 +9,16 @@ WebHooker requires several secrets to function. For local development, store the
| Variable | Description | | Variable | Description |
| ----------------------- | ------------------------------------------------------------------ | | ----------------------- | ------------------------------------------------------------------ |
| `GITHUB_WEBHOOK_SECRET` | Webhook secret from your GitHub App settings | | `GITHUB_WEBHOOK_SECRET` | Webhook secret from your GitHub App settings |
| `GITHUB_APP_ID` | Numeric ID of your GitHub App |
| `GITHUB_PRIVATE_KEY` | App private key (PEM format, with `\n` escapes) |
| `GITHUB_CLIENT_ID` | OAuth client ID from App settings | | `GITHUB_CLIENT_ID` | OAuth client ID from App settings |
| `GITHUB_CLIENT_SECRET` | OAuth client secret from App settings | | `GITHUB_CLIENT_SECRET` | OAuth client secret from App settings |
| `DISCORD_TOKEN` | Discord bot token | | `DISCORD_TOKEN` | Discord bot token |
| `TELEGRAM_TOKEN` | Telegram bot token (from BotFather) — required for Telegram routes | | `TELEGRAM_TOKEN` | Telegram bot token (from BotFather) — required for Telegram routes |
> [!NOTE]
> `GITHUB_APP_ID` and `GITHUB_PRIVATE_KEY` are not currently used by the code — the
> OAuth flow only needs `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`. They are kept
> in the schema for compatibility in case GitHub App authentication is added later.
### Optional Secrets ### Optional Secrets
| Variable | Description | Default | | Variable | Description | Default |
@ -52,6 +55,7 @@ WebHooker ships with a built-in config console at `/admin` for managing routes i
| `GET /admin/api/groups/:id/routes` | List a group's routes | | `GET /admin/api/groups/:id/routes` | List a group's routes |
| `PUT /admin/api/groups/:id/routes` | Replace a group's routes | | `PUT /admin/api/groups/:id/routes` | Replace a group's routes |
| `GET /admin/api/logs` | Send logs (scoped to accessible routes) | | `GET /admin/api/logs` | Send logs (scoped to accessible routes) |
| `GET /admin/api/logs/:id` | Single send-log entry (scoped) |
The console lets you add, edit, delete, and toggle routes. Saved routes are written to KV `config:routes` immediately and the config cache is invalidated so the webhook pipeline picks them up on the next run. The console lets you add, edit, delete, and toggle routes. Saved routes are written to KV `config:routes` immediately and the config cache is invalidated so the webhook pipeline picks them up on the next run.
@ -140,6 +144,7 @@ Routes belong to groups. Groups scope admin access and can restrict which events
| `name` | string | Yes | Human-readable group name | | `name` | string | Yes | Human-readable group name |
| `adminIds` | string[] | Yes | GitHub user IDs or logins who may manage this group's routes | | `adminIds` | string[] | Yes | GitHub user IDs or logins who may manage this group's routes |
| `owners` | string[] | No | Org/user logins whose events are accepted into this group; empty = all | | `owners` | string[] | No | Org/user logins whose events are accepted into this group; empty = all |
| `emoji` | boolean | No | Whether to include emoji in this group's messages (default `true`) |
### Access Model ### Access Model
@ -167,7 +172,7 @@ See the [Filter Tutorial](./filters) for a hands-on guide with worked examples.
- Set `"exclude": true` on any filter to invert it (NOT logic) - Set `"exclude": true` on any filter to invert it (NOT logic)
- Non-keyword filters are **exact, case-insensitive matches** — no wildcards (`repo: "org/*"` does not match anything) - Non-keyword filters are **exact, case-insensitive matches** — no wildcards (`repo: "org/*"` does not match anything)
- `keyword` filter supports regex patterns — falls back to substring match if regex is invalid or longer than 200 characters - `keyword` filter supports regex patterns — falls back to substring match if regex is invalid or longer than 200 characters
- `branch` filter works for push, pull_request, pull_request_review, pull_request_review_comment, create/delete, workflow_run, and code_scanning_alert events - `branch` filter works for push, pull_request, pull_request_review, pull_request_review_comment, create/delete, workflow_run, workflow_job, check_suite, deployment, and code_scanning_alert events
### Match Values ### Match Values
@ -180,17 +185,27 @@ Filters accept either a single string or an array of strings:
## KV Storage Layout ## KV Storage Layout
| Key Pattern | Value | TTL | | Key Pattern | Value | TTL |
| ------------------------ | --------------------------------------------------- | ------------------ | | ------------------------------ | ----------------------------------------------------------------------------- | ------------------ |
| `config:routes` | JSON array of routes | Permanent | | `config:routes` | JSON array of routes | Permanent |
| `config:groups` | JSON array of groups | Permanent | | `config:groups` | JSON array of groups | Permanent |
| `session:{id}` | Admin session `{ userId, login }` | 7 days | | `session:{id}` | Admin session `{ userId, login }` | 7 days |
| `token:{userId}` | `{ userId, accessToken, expiresAt, refreshToken? }` | 0.9 × token expiry | | `token:{userId}` | `{ userId, accessToken, expiresAt, refreshToken? }` | 0.9 × token expiry |
| `token-reverse:{sha256}` | User id for reverse lookup by token | 0.9 × token expiry | | `token-reverse:{sha256}` | User id for reverse lookup by token | 0.9 × token expiry |
| `discord-link:{userId}` | GitHub user id linked to a Discord user | Permanent | | `state:{hex}` | `{ redirectTo, expiresAt, discordUserId?, telegramUserId?, telegramChatId? }` | 600 seconds |
| `state:{hex}` | `{ redirectTo, expiresAt, discordUserId? }` | 600 seconds | | `delivery:{id}` | Webhook delivery id (dedup marker) | 300 seconds |
| `delivery:{id}` | Webhook delivery id (dedup marker) | 300 seconds | | `msg:{routeId}:{key}:{target}` | Message id tracking for in-place updates (e.g. `workflow_run`) | 7 days |
| `cmd:guild:{id}` | Guild id whose commands were registered (dedup) | Permanent | | `cmd:guild:{id}` | Guild id whose commands were registered (dedup) | Permanent |
| `cmd:registered:global` | Global command registration marker (dedup) | 1 day | | `cmd:registered:global` | Global command registration marker (dedup) | 1 day |
| `config:discord-app-id` | Cached Discord application id | Permanent | | `config:discord-app-id` | Cached Discord application id | Permanent |
| `logs:send:{ts}-{hex}` | Send record | 1 hour | | `i18n:{lang}` | Translation overrides merged on top of English | Permanent |
## D1 Storage Layout
The D1 database (`DB` binding, database `webhooker`) holds three tables:
| Table | Purpose |
| ---------------- | ---------------------------------------------------------------------------------------------- |
| `send_logs` | One row per dispatch attempt (route id, event, target, ok/error, duration, error code, detail) |
| `discord_links` | Maps `discord_user_id``github_user_id` for `/gh` Discord commands |
| `telegram_links` | Maps `telegram_user_id``github_user_id` for `/gh` Telegram commands |

View file

@ -25,8 +25,6 @@ This outputs a namespace ID. Update `wrangler.jsonc` with the ID:
```bash ```bash
npx wrangler secret put GITHUB_WEBHOOK_SECRET npx wrangler secret put GITHUB_WEBHOOK_SECRET
npx wrangler secret put GITHUB_APP_ID
npx wrangler secret put GITHUB_PRIVATE_KEY # PKCS#8 PEM (BEGIN PRIVATE KEY)
npx wrangler secret put GITHUB_CLIENT_ID npx wrangler secret put GITHUB_CLIENT_ID
npx wrangler secret put GITHUB_CLIENT_SECRET npx wrangler secret put GITHUB_CLIENT_SECRET
npx wrangler secret put DISCORD_TOKEN npx wrangler secret put DISCORD_TOKEN
@ -39,15 +37,10 @@ npx wrangler secret put ADMIN_USER_IDS # comma-separated GitHub IDs/logins
There is no global channel secret. Each route in the [Web UI](/guide/configuration#web-ui) declares its own target channel (and optional thread), so `DISCORD_CHANNEL_ID` is not needed. There is no global channel secret. Each route in the [Web UI](/guide/configuration#web-ui) declares its own target channel (and optional thread), so `DISCORD_CHANNEL_ID` is not needed.
::: :::
::: warning GitHub App private key must be PKCS#8 ::: tip GitHub App ID / private key are unused
GitHub issues private keys in PKCS#1 format (`BEGIN RSA PRIVATE KEY`). Cloudflare Workers' JWT signing requires PKCS#8. Convert first: `GITHUB_APP_ID` and `GITHUB_PRIVATE_KEY` are not currently used by the code — the
OAuth flow only needs `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`. You do not need to
```bash set them (no PKCS#8 conversion required).
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt \
-in your-app.private-key.pem -out gh_pk_pkcs8.pem
```
Then upload `gh_pk_pkcs8.pem` as `GITHUB_PRIVATE_KEY`.
::: :::
Discord interactions arrive via the HTTPS Interactions Endpoint, so set `DISCORD_PUBLIC_KEY` and point the **Interactions Endpoint URL** at `https://your-domain/discord/interactions`. See [Interactions Endpoint](#interactions-endpoint) below. Discord interactions arrive via the HTTPS Interactions Endpoint, so set `DISCORD_PUBLIC_KEY` and point the **Interactions Endpoint URL** at `https://your-domain/discord/interactions`. See [Interactions Endpoint](#interactions-endpoint) below.
@ -79,8 +72,8 @@ Your worker is now live at `https://webhooker.<your-subdomain>.workers.dev`.
3. Set permissions: 3. Set permissions:
- **Repository permissions**: Contents (read), Issues (write), Pull requests (write), Metadata (read), Checks (read), Deployments (read), Discussions (read), Code scanning alerts (read), Dependabot alerts (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 - **Organization permissions**: Members (read) — if needed
4. Subscribe to events (all 23 supported): 4. Subscribe to events (all 28 supported):
- 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 - 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 → save contents to `GITHUB_PRIVATE_KEY` env var 5. Generate private key → save contents to `GITHUB_PRIVATE_KEY` env var
### 2. Install App ### 2. Install App
@ -120,6 +113,22 @@ The `/gh` slash command and the `GitHub: 添加/编辑/删除评论` message com
Users run `/gh login` to link their GitHub account and can then comment on issues/PRs as themselves. See the [README](https://github.com/ReCloudStudio/WebHooker#bot-commands-comment-on-github-as-yourself) for the full command reference. Users run `/gh login` to link their GitHub account and can then comment on issues/PRs as themselves. See the [README](https://github.com/ReCloudStudio/WebHooker#bot-commands-comment-on-github-as-yourself) for the full command reference.
## 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 `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`).
## Custom Domain (Optional) ## Custom Domain (Optional)
To use a custom domain instead of `*.workers.dev`: To use a custom domain instead of `*.workers.dev`:
@ -128,13 +137,5 @@ To use a custom domain instead of `*.workers.dev`:
2. Add a custom domain or route 2. Add a custom domain or route
3. Update `BASE_URL` to match 3. Update `BASE_URL` to match
## Docker > [!NOTE]
> This project is a Cloudflare Worker. It requires the KV and D1 bindings declared in `wrangler.jsonc`, so it cannot run as a standalone Node/container process.
A Dockerfile is provided for containerized deployments (e.g., behind a reverse proxy):
```bash
docker build -t webhooker .
docker run -p 8787:8787 --env-file .env webhooker
```
Note: Docker mode runs without KV and other Cloudflare storage. Use Cloudflare deployment for full functionality.

View file

@ -90,6 +90,9 @@ Matches the branch involved in the event. What counts as "the branch" depends on
| `pull_request` (and review) | The pull request's **head** (source) branch | | `pull_request` (and review) | The pull request's **head** (source) branch |
| `create` / `delete` | The created/deleted branch or tag | | `create` / `delete` | The created/deleted branch or tag |
| `workflow_run` | The `head_branch` the workflow ran on | | `workflow_run` | The `head_branch` the workflow ran on |
| `workflow_job` | The `head_branch` the job ran on |
| `check_suite` | The `head_branch` of the check suite |
| `deployment` | The deployment ref (strips `refs/heads/`) |
| `code_scanning_alert` | The branch the alert belongs to | | `code_scanning_alert` | The branch the alert belongs to |
```json ```json

View file

@ -29,8 +29,6 @@ Edit `.dev.vars` with your actual values:
```bash ```bash
GITHUB_WEBHOOK_SECRET=your-webhook-secret GITHUB_WEBHOOK_SECRET=your-webhook-secret
GITHUB_APP_ID=your-app-id
GITHUB_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
GITHUB_CLIENT_ID=your-client-id GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret GITHUB_CLIENT_SECRET=your-client-secret
DISCORD_TOKEN=your-bot-token DISCORD_TOKEN=your-bot-token
@ -40,7 +38,7 @@ BASE_URL=http://localhost:8787
``` ```
::: tip ::: tip
`GITHUB_PRIVATE_KEY` must be in **PKCS#8** format (`BEGIN PRIVATE KEY`). Convert a GitHub-issued PKCS#1 key with `openssl pkcs8 -nocrypt -in app.pem -out pkcs8.pem`. Target channels are set per route in the Web UI, so no `DISCORD_CHANNEL_ID` is needed. To enable `/gh` commands locally, copy the **Public Key** from the Developer Portal into `DISCORD_PUBLIC_KEY` and set the Interactions Endpoint URL to `http://localhost:8787/discord/interactions`. `GITHUB_APP_ID` / `GITHUB_PRIVATE_KEY` are not used by the code (the OAuth flow only needs the client ID/secret), so you can omit them. Target channels are set per route in the Web UI, so no `DISCORD_CHANNEL_ID` is needed. To enable `/gh` commands locally, copy the **Public Key** from the Developer Portal into `DISCORD_PUBLIC_KEY` and set the Interactions Endpoint URL to `http://localhost:8787/discord/interactions`.
::: :::
::: warning ::: warning

View file

@ -1,27 +1,29 @@
# Introduction # Introduction
WebHooker is a GitHub webhook dispatcher built on Cloudflare Workers. It receives GitHub webhook events, applies configurable filters, formats them into rich Discord embeds, and delivers them to Discord channels or threads through the Discord REST API. In-Discord `/gh` interactions arrive via an HTTPS Interactions Endpoint (Ed25519-verified). Routes are managed through a built-in Web UI. WebHooker is a GitHub webhook dispatcher built on Cloudflare Workers. It receives GitHub webhook events, applies configurable filters, formats them into rich messages, and delivers them to Discord channels/threads (embeds) and Telegram chats/topics (HTML) via their REST APIs. In-Discord `/gh` interactions arrive via an HTTPS Interactions Endpoint (Ed25519-verified); Telegram `/gh` commands arrive via the Telegram webhook. Routes and groups are managed through a built-in Web UI.
## Architecture ## Architecture
```text ```text
GitHub Webhook → Cloudflare Worker (Hono) GitHub Webhook → Cloudflare Worker (Hono)
├── POST /webhook → verify → dedup → filter → format → Discord (REST API) ├── POST /webhook → verify → dedup → filter → format → Discord (REST) / Telegram (Bot API)
├── POST /discord/interactions → verify (Ed25519) → handle /gh slash & context commands
├── POST /telegram/webhook → verify (secret token) → handle /gh commands
├── GET /auth/github → OAuth flow ├── GET /auth/github → OAuth flow
├── GET /api/richheader → Telegram avatar link-preview card
├── POST /api/* → user actions (Bearer token auth) ├── POST /api/* → user actions (Bearer token auth)
├── /admin → routes & send-log Web UI (admin session) ├── /admin → routes, groups & send-log Web UI (admin session)
└── GET /health → status check └── GET /health → status check
POST /discord/interactions → verify (Ed25519) → handle /gh slash & context commands
``` ```
### Components ### Components
| Component | Role | | Component | Role |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Cloudflare Worker** | HTTP ingress, signature verification, delivery dedup, event parsing, route matching, REST send | | **Cloudflare Worker** | HTTP ingress, signature verification, delivery dedup, event parsing, route matching, platform dispatch |
| **Interactions Endpoint** | Verifies Ed25519 signatures and handles `/gh` interactions (slash commands, context-menu commands, buttons, modals) | | **Interactions Endpoint** | Verifies Ed25519 signatures and handles `/gh` interactions (slash commands, context-menu commands, buttons, modals) |
| **KV** | Token storage (`token:{userId}`), OAuth state (`state:{hex}`), route config (`config:routes`), send logs, delivery dedup | | **KV** | Token storage (`token:{userId}`), OAuth state (`state:{hex}`), route config (`config:routes`), group config (`config:groups`), admin sessions, delivery dedup, message-update tracking (`msg:*`) |
| **D1** | Send logs (`send_logs`), Discord↔GitHub links (`discord_links`), Telegram↔GitHub links (`telegram_links`) |
### Data Flow ### Data Flow
@ -29,18 +31,19 @@ POST /discord/interactions → verify (Ed25519) → handle /gh slash & context c
2. Worker verifies the HMAC-SHA256 signature 2. Worker verifies the HMAC-SHA256 signature
3. Worker deduplicates by `X-GitHub-Delivery` (KV, short TTL) to drop repeat deliveries 3. Worker deduplicates by `X-GitHub-Delivery` (KV, short TTL) to drop repeat deliveries
4. Worker parses the event type and payload 4. Worker parses the event type and payload
5. Routes are evaluated against filters (event, repo, actor, action, branch, keyword) 5. Routes are evaluated against filters (event, repo, actor, action, branch, keyword) and group owner restrictions
6. Matching routes trigger formatter functions that produce Discord embeds 6. Matching routes trigger formatter functions that produce platform-neutral messages
7. Each message is sent to its route's target channel/thread via the Discord REST API with rate-limit retry, and the result is recorded in the send log 7. Each message is sent to its route's target(s) via the Discord or Telegram REST API with rate-limit retry; `workflow_run` progress is edited in place. Every attempt is recorded in the D1 send log
## Tech Stack ## Tech Stack
- **Runtime**: Cloudflare Workers - **Runtime**: Cloudflare Workers
- **HTTP Framework**: Hono - **HTTP Framework**: Hono
- **Discord delivery**: Discord REST API (interactions via an Ed25519-verified HTTPS Interactions Endpoint) - **Discord delivery**: Discord REST API (interactions via an Ed25519-verified HTTPS Interactions Endpoint)
- **Telegram delivery**: Telegram Bot API (webhook with optional secret-token verification)
- **Web UI**: Nuxt 3 static SPA served from Worker assets - **Web UI**: Nuxt 3 static SPA served from Worker assets
- **Storage**: Cloudflare KV - **Storage**: Cloudflare KV + D1
- **Auth**: Web Crypto API (HMAC-SHA256), jose (JWT), octokit (GitHub API) - **Auth**: Web Crypto API (HMAC-SHA256, Ed25519), octokit (GitHub API), jose (dependency)
- **Language**: TypeScript - **Language**: TypeScript
## License ## License

View file

@ -4,7 +4,7 @@ layout: home
hero: hero:
name: WebHooker name: WebHooker
text: GitHub Webhook → Discord text: GitHub Webhook → Discord
tagline: Receive GitHub events via Cloudflare Workers, apply filters, and route formatted messages to Discord channels or threads. tagline: Receive GitHub events via Cloudflare Workers, apply filters, and route formatted messages to Discord channels/threads and Telegram chats/topics.
actions: actions:
- theme: brand - theme: brand
text: Get Started text: Get Started
@ -14,16 +14,16 @@ hero:
link: https://github.com/ReCloudStudio/WebHooker link: https://github.com/ReCloudStudio/WebHooker
features: features:
- title: 23 Event Formatters - title: 28 Event Formatters
details: Rich Discord embeds for push, pull_request, issues, release, workflow_run, and 18 more event types with color-coded output. details: Rich Discord embeds and Telegram HTML for push, pull_request, issues, release, workflow_run, and 23 more event types with color-coded output.
- title: Flexible Filtering - title: Flexible Filtering
details: Filter by event type, repo, actor, action, branch (including PRs), and keyword (with regex support). Exclude patterns with a flag. details: Filter by event type, repo, actor, action, branch (including PRs), and keyword (with regex support). Exclude patterns with a flag.
- title: Cloudflare Workers - title: Cloudflare Workers
details: Runs on Cloudflare's edge network. Sends via the Discord REST API, with an Ed25519-verified Interactions Endpoint for `/gh` slash commands and buttons. details: Runs on Cloudflare's edge network. Sends via the Discord REST API and Telegram Bot API, with an Ed25519-verified Interactions Endpoint for `/gh` slash commands and buttons.
- title: Web UI & Slash Commands - title: Web UI, Groups & Commands
details: "Manage routes and view send logs from a built-in admin console. Link your GitHub account and comment on issues/PRs as yourself via /gh commands." details: "Manage routes, groups and send logs from a built-in admin console. Link your GitHub account and comment on issues/PRs as yourself via /gh commands on Discord or Telegram."
- title: Signature Verification - title: Signature Verification
details: HMAC-SHA256 webhook signature verification and Ed25519 interaction signature verification using the Web Crypto API with timing-safe comparison. details: HMAC-SHA256 webhook signature verification and Ed25519 interaction signature verification using the Web Crypto API with timing-safe comparison.
- title: Graceful Degradation - title: In-Place Updates
details: Runs in webhook-only mode if Discord token is unavailable. Health endpoint for monitoring. details: workflow_run progress is edited in place on a single message as the run advances, on both Discord and Telegram.
--- ---

View file

@ -47,6 +47,7 @@ GitHub 授权后重定向到此地址。将 code 交换为访问令牌并存储
- **浏览器流程**`Accept: text/html`):设置管理员会话 Cookie然后重定向到 `redirect` 目标;无管理权限的用户被重定向到 `/admin?error=forbidden` - **浏览器流程**`Accept: text/html`):设置管理员会话 Cookie然后重定向到 `redirect` 目标;无管理权限的用户被重定向到 `/admin?error=forbidden`
- **JSON 流程**:返回 `{ "userId": "...", "login": "...", "redirectTo": "..." }` - **JSON 流程**:返回 `{ "userId": "...", "login": "...", "redirectTo": "..." }`
- **Discord 绑定流程**(以未决的 `discordUserId` 启动时):将 Discord 用户绑定到此 GitHub 账号,返回 `{ "ok": true, "discordUserId": "...", "login": "..." }`——浏览器中则显示成功页面。 - **Discord 绑定流程**(以未决的 `discordUserId` 启动时):将 Discord 用户绑定到此 GitHub 账号,返回 `{ "ok": true, "discordUserId": "...", "login": "..." }`——浏览器中则显示成功页面。
- **Telegram 绑定流程**(以未决的 `telegramUserId` 启动时):将 Telegram 用户绑定到此 GitHub 账号,返回 `{ "ok": true, "telegramUserId": "...", "login": "..." }`,并向未决的 `telegramChatId` 发送确认消息。
### 撤销 Token ### 撤销 Token
@ -77,7 +78,7 @@ Token 以键模式 `token:{userId}` 存储在 KV 中:
} }
``` ```
`expiresAt` 是毫秒级 Unix 时间戳。KV 条目在 Token 有效期的 90% 时过期(至少 60 秒)。反向索引 `token-reverse:{sha256 of token}` 将访问令牌映射回用户 id使 Bearer 鉴权的端点能解析调用者。与 GitHub 账号绑定的 Discord 用户存储在 D1 的 `discord_links` 表中。 `expiresAt` 是毫秒级 Unix 时间戳。KV 条目在 Token 有效期的 90% 时过期(至少 60 秒)。反向索引 `token-reverse:{sha256 of token}` 将访问令牌映射回用户 id使 Bearer 鉴权的端点能解析调用者。与 GitHub 账号绑定的 Discord 用户存储在 D1 的 `discord_links` 表中Telegram 用户存储在 D1 的 `telegram_links` 表中。
## 使用 Token ## 使用 Token

View file

@ -33,6 +33,7 @@ https://your-worker.workers.dev
| `PUT` | `/admin/api/groups/:id/routes` | 管理员会话 | 替换某分组的路由 | | `PUT` | `/admin/api/groups/:id/routes` | 管理员会话 | 替换某分组的路由 |
| `GET` | `/admin/api/me` | 管理员会话 | 当前会话信息 | | `GET` | `/admin/api/me` | 管理员会话 | 当前会话信息 |
| `GET` | `/admin/api/logs` | 管理员会话 | 发送日志(按权限过滤) | | `GET` | `/admin/api/logs` | 管理员会话 | 发送日志(按权限过滤) |
| `GET` | `/admin/api/logs/:id` | 管理员会话 | 单条发送日志(按权限过滤) |
## 管理控制台 ## 管理控制台
@ -40,7 +41,7 @@ https://your-worker.workers.dev
- `GET /admin` — 提供配置控制台 HTML - `GET /admin` — 提供配置控制台 HTML
- `GET /admin/api/routes` — 返回 `{ "routes": Route[] }` - `GET /admin/api/routes` — 返回 `{ "routes": Route[] }`
- `PUT /admin/api/routes` — 请求体为 `{ "routes": Route[] }`校验每条路由id 格式、唯一 id、name、enabled、groupId、过滤器、平台感知的 targetDiscord 需 `target.channelId`Telegram 需 `target.chatId`)并持久化到 KV `config:routes`。返回 `200 { ok, count }``400 { error }` / `401 { error }`。 - `PUT /admin/api/routes` — 请求体为 `{ "routes": Route[] }`校验每条路由id 格式、唯一 id、name、enabled、groupId、过滤器、平台感知的 targetsDiscord 需 `target.channelId`Telegram 需 `target.chatId`)并持久化到 KV `config:routes`。返回 `200 { ok, count }``400 { error }` / `401 { error }` / `403 { error }`。
## 健康检查 ## 健康检查
@ -66,11 +67,11 @@ POST /webhook
**请求头:** **请求头:**
| 头部 | 必需 | 说明 | | 头部 | 必需 | 说明 |
| --------------------- | ---- | ---------------- | | --------------------- | ---- | ----------------------------- |
| `X-Hub-Signature-256` | 是 | HMAC-SHA256 签名 | | `X-Hub-Signature-256` | 是 | HMAC-SHA256 签名 |
| `X-GitHub-Event` | 是 | 事件类型名称 | | `X-GitHub-Event` | 是 | 事件类型名称 |
| `X-GitHub-Delivery` | 是 | 唯一投递 ID | | `X-GitHub-Delivery` | 否 | 唯一投递 ID存在时用于去重 |
**请求体:** GitHub webhook JSON 载荷(最大 1MB **请求体:** GitHub webhook JSON 载荷(最大 1MB
@ -82,6 +83,8 @@ POST /webhook
} }
``` ```
`X-GitHub-Delivery` 存在且同一投递在最近 5 分钟内已被处理时Worker 返回 `200 { "ok": true, "duplicate": true }`,不再重复分发。
**错误响应:** **错误响应:**
| 状态码 | 响应体 | 原因 | | 状态码 | 响应体 | 原因 |

View file

@ -14,23 +14,25 @@ npm run dev # 启动本地开发服务器
```text ```text
src/ src/
├── index.ts # CF Workers 入口 (fetch + scheduled)scheduled = 命令同步 ├── index.ts # CF Workers 入口 (fetch + scheduled)scheduled = Discord 命令同步 + Telegram webhook 同步
├── types.ts # Env、Config、Route、Filter、WebhookEvent、NeutralMessage ├── types.ts # Env、Config、Route、Filter、Group、WebhookEvent、NeutralMessage
├── config.ts # 从 KV 加载路由(未设置时返回 []),从 env 构建 Config ├── config.ts # 从 KV 加载路由(未设置时返回 []),从 env 构建 Config
├── server.ts # Hono 应用: /health、/webhook、/discord/interactions挂载 /auth、/admin + / ├── server.ts # Hono 应用: /health、/webhook、/discord/interactions、/telegram/webhook,挂载 /auth、/admin + /
├── core/ ├── core/
│ └── dispatch.ts # 平台中立分发:匹配路由 → formatEvent → getDriver().send │ └── dispatch.ts # 平台中立分发:匹配路由 → formatEvent → getDriver().send/edit
├── events/ # GitHub webhook 事件流水线 ├── events/ # GitHub webhook 事件流水线(旧 src/webhook.ts 为死代码)
│ ├── verify.ts # HMAC 签名验证 (Web Crypto时间安全) │ ├── verify.ts # HMAC 签名验证 (Web Crypto时间安全)
│ ├── parse.ts # parseEvent (headers + body → WebhookEvent) │ ├── parse.ts # parseEvent (headers + body → WebhookEvent)
│ └── match.ts # matchRoute、eventOwners、extractBranch、关键词过滤 │ └── match.ts # matchRoute、eventOwners、extractBranch、关键词过滤
├── formatters/ # 平台中立格式化器(产出 NeutralMessage ├── formatters/ # 平台中立格式化器(产出 NeutralMessage
│ ├── index.ts # formatEvent24 事件 switch → NeutralMessage + re-export │ ├── index.ts # formatEvent28 事件 switch → NeutralMessage + re-export
│ ├── colors.ts # GITHUB_COLORS + WORKFLOW_CONCLUSION_EMOJI │ ├── colors.ts # GITHUB_COLORS + WORKFLOW_CONCLUSION_EMOJI
│ ├── helpers.ts # emojiPrefix、T、buildMessage │ ├── helpers.ts # emojiPrefix、T、buildMessage
│ └── *.ts # push、pull-request、issues、comments、workflow、release、repo 等 │ └── *.ts # push、pull-request、issues、comments、workflow、release、create、repo、
│ # check、review、commit-comment、deployment、member、label、milestone、
│ # discussion、repository、security、generic、ping
├── drivers/ # 平台驱动(可插拔推送目标) ├── drivers/ # 平台驱动(可插拔推送目标)
│ ├── types.ts # PlatformDriver 接口 + SendResult │ ├── types.ts # PlatformDriver 接口 + SendResultsend + edit
│ ├── index.ts # getDriver() 注册表discord + telegram │ ├── index.ts # getDriver() 注册表discord + telegram
│ ├── discord/ # index.ts (驱动)、render.ts (NeutralMessage → embed)、 │ ├── discord/ # index.ts (驱动)、render.ts (NeutralMessage → embed)、
│ │ # rest.ts、interactions.ts、commands.ts │ │ # rest.ts、interactions.ts、commands.ts
@ -38,21 +40,24 @@ src/
│ # rest.ts (chat_id + message_thread_id)、updates.ts (webhook 验签)、 │ # rest.ts (chat_id + message_thread_id)、updates.ts (webhook 验签)、
│ # commands.ts (/gh login|logout|comment|merge|close + 引用消息解析) │ # commands.ts (/gh login|logout|comment|merge|close + 引用消息解析)
├── github/ # GitHub OAuth + 以用户身份操作 ├── github/ # GitHub OAuth + 以用户身份操作
│ ├── oauth.ts # OAuth URL、回调 Token 交换、getUserOctokit、操作 │ ├── oauth.ts # OAuth URL、回调 Token 交换、getUserOctokit、评论/合并/关闭操作
│ └── store.ts # KV Token CRUD + D1 discord-link/telegram-link 映射 │ └── store.ts # KV Token CRUD + D1 discord-link/telegram-link 映射
├── web/ # HTTP UI/API 路由 ├── web/ # HTTP UI/API 路由
│ ├── oauth-routes.ts # GET /auth/github、回调、DELETE /token/:userId (KV 状态) │ ├── oauth-routes.ts # GET /auth/github、回调(管理员会话 / Discord 绑定 / Telegram 绑定)、DELETE /token/:userId
│ ├── action-routes.ts # POST /api/comment|merge|close|react (通过 KV 查找进行 Bearer Token 鉴权) │ ├── action-routes.ts # POST /api/comment|merge|close|react (通过 KV 查找进行 Bearer Token 鉴权)
│ ├── admin-routes.ts # /admin API路由、分组、me、日志会话 + 权限范围鉴权) │ ├── admin-routes.ts # /admin API路由、分组、me、日志会话 + 权限范围鉴权)
│ ├── session.ts # 管理员会话 CRUD (KV session:{id})、Cookie 辅助函数 │ ├── session.ts # 管理员会话 CRUD (KV session:{id})、Cookie 辅助函数
│ ├── groups.ts # 分组加载、分组管理员权限范围 │ ├── groups.ts # 分组加载、分组管理员权限范围
│ ├── home-routes.ts # 落地页路由 │ ├── home-routes.ts # 落地页路由
│ └── legal-routes.ts # 法律页面路由 │ ├── legal-routes.ts # 法律页面路由
│ └── richheader-routes.ts # GET /api/richheaderTelegram 头像卡片)
└── lib/ # 共享基础设施 └── lib/ # 共享基础设施
├── i18n.ts # 消息语言覆盖 (en/zh) ├── i18n.ts # 消息语言覆盖 (en/zh)
├── send-log.ts # 发送日志 (D1 send_logs) ├── send-log.ts # 发送日志 (D1 send_logs)
├── log.ts # JSON 控制台日志 (info/warn/error/fatal) ├── log.ts # JSON 控制台日志 (info/warn/error/fatal)
└── locales/ # en.ts、zh.ts 翻译字典 └── locales/ # en.ts、zh.ts 翻译字典
src/__tests__/ # 单元测试 (bun test)
``` ```
## 脚本 ## 脚本
@ -93,16 +98,17 @@ curl http://localhost:8787/health
## 添加新事件格式化器 ## 添加新事件格式化器
1. 将事件类型添加到 `src/formatters/colors.ts` 中的 `GITHUB_COLORS`(如果需要新颜色) 1. 将事件类型添加到 `src/formatters/colors.ts` 中的 `GITHUB_COLORS`(如果需要新颜色)
2. 将操作标签添加到 `ACTION_LABELS`(如果有新操作) 2. 将操作标签添加到 `src/lib/locales/en.ts` 与 `src/lib/locales/zh.ts` 的翻译字典(如果有新操作)
3. 在 `src/formatters/` 中创建 `formatEventType` 函数 3. 在 `src/formatters/` 中创建 `formatEventType` 函数
4. 将 case 添加到 `src/formatters/index.ts` 中的 `formatEvent` switch 语句 4. 将 case 添加到 `src/formatters/index.ts` 中的 `formatEvent` switch 语句
5. 如果事件包含分支信息,更新 `src/events/match.ts` 中的 `extractBranch` 5. 如果事件包含分支信息,更新 `src/events/match.ts` 中的 `extractBranch`
6. 将事件添加到 `docs/events/supported.md` 文档中 6. 将事件添加到 `docs/events/supported.md``docs/zh/events/supported.md` 文档中
7. 在 GitHub App 设置中订阅该事件 7. 将事件添加到 README`README.md``README.zh.md`)的事件表与 GitHub App 事件订阅列表中
8. 在 GitHub App 设置中订阅该事件
## 拉取请求指南 ## 拉取请求指南
- 保持变更聚焦且原子化 - 保持变更聚焦且原子化
- 为所有函数返回值包含类型注解 - 为所有函数返回值包含类型注解
- 提交前运行 `npm run typecheck && npm run lint && npm run format:check` - 提交前运行 `npm run typecheck && npm run lint && npm run format:check`
- 添加功能时更新文档 - 添加功能时更新文档(见 `AGENTS.md` → Documentation 的清单README`README.md` / `README.zh.md`、VitePress 文档(`docs/``docs/zh/`)以及示例配置文件

View file

@ -1,6 +1,6 @@
# 支持的事件 # 支持的事件
WebHooker 支持 23 种 GitHub webhook 事件类型,每种都有专用的格式化器,生成丰富的 Discord 嵌入消息。不支持的事件会回退到通用格式化器。 WebHooker 支持 28 种 GitHub webhook 事件类型,每种都有专用的格式化器,生成丰富的 Discord 嵌入消息与 Telegram HTML 消息。不支持的事件会回退到通用格式化器。
## 事件表 ## 事件表
@ -11,16 +11,21 @@ WebHooker 支持 23 种 GitHub webhook 事件类型,每种都有专用的格
| `issues` | 议题打开/关闭/编辑 | 议题标题、标签、指派人 | | `issues` | 议题打开/关闭/编辑 | 议题标题、标签、指派人 |
| `issue_comment` | 议题或 PR 的评论 | 评论内容、议题引用 | | `issue_comment` | 议题或 PR 的评论 | 评论内容、议题引用 |
| `workflow_run` | CI/CD 工作流阶段更新 | 工作流状态、结论、耗时;各阶段原地更新同一条消息 | | `workflow_run` | CI/CD 工作流阶段更新 | 工作流状态、结论、耗时;各阶段原地更新同一条消息 |
| `workflow_job` | CI 作业阶段更新 | 作业名、状态、结论、工作流 |
| `status` | 提交状态更新 | 提交状态、上下文、状态值、提交链接 |
| `deployment` | 部署已创建 | 环境、引用、任务 |
| `deployment_status` | 部署状态更新 | 环境、状态、提交引用 |
| `check_run` | 检查运行完成 | 状态、结论、详情 URL |
| `check_suite` | 检查套件完成 | 套件结论、head 分支、提交链接 |
| `ping` | Webhook 确认 | Webhook 确认、已订阅的事件类型 |
| `release` | 发布创建/编辑 | 标签、内容、附件、预发布标记 | | `release` | 发布创建/编辑 | 标签、内容、附件、预发布标记 |
| `create` | 分支或标签已创建 | 引用名称、引用类型 | | `create` | 分支或标签已创建 | 引用名称、引用类型 |
| `delete` | 分支或标签已删除 | 引用名称、引用类型 | | `delete` | 分支或标签已删除 | 引用名称、引用类型 |
| `star` | 仓库加星/取消星标 | 星标数、操作 | | `star` | 仓库加星/取消星标 | 星标数、操作 |
| `fork` | 仓库已复刻 | 源 → 目标复刻 | | `fork` | 仓库已复刻 | 源 → 目标复刻 |
| `check_run` | 检查运行完成 | 状态、结论、详情 URL |
| `pull_request_review` | PR 审查已提交 | 审查状态(已批准/需修改/已评论)、正文 | | `pull_request_review` | PR 审查已提交 | 审查状态(已批准/需修改/已评论)、正文 |
| `pull_request_review_comment` | 行内代码审查评论 | 文件路径、行号、评论内容 | | `pull_request_review_comment` | 行内代码审查评论 | 文件路径、行号、评论内容 |
| `commit_comment` | 提交的评论 | 提交 SHA、评论内容 | | `commit_comment` | 提交的评论 | 提交 SHA、评论内容 |
| `deployment_status` | 部署状态更新 | 环境、状态、提交引用 |
| `member` | 协作者添加/移除 | 成员登录名、操作 | | `member` | 协作者添加/移除 | 成员登录名、操作 |
| `label` | 标签创建/编辑/删除 | 标签名称、颜色、描述 | | `label` | 标签创建/编辑/删除 | 标签名称、颜色、描述 |
| `milestone` | 里程碑打开/关闭 | 进度条、议题计数、截止日期 | | `milestone` | 里程碑打开/关闭 | 进度条、议题计数、截止日期 |
@ -32,18 +37,17 @@ WebHooker 支持 23 种 GitHub webhook 事件类型,每种都有专用的格
## 颜色编码 ## 颜色编码
每种事件类型在 Discord 嵌入中使用不同的颜色: 每种事件类型在 Discord 嵌入中使用不同的颜色(来自 `src/formatters/colors.ts`
| 颜色 | 事件 | | 颜色 | 事件 |
| ---------------- | ---------------------------------------------------------- | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| 绿色 (`#2ea44f`) | push、issue 打开、PR 打开、release 发布、star、member 添加 | | 绿色 (`#2da44e`) | push、PR 打开/可审查、issue 打开、工作流成功、发布已发布、检查成功、审查已批准、部署成功、成员添加、里程碑关闭、讨论已回答 |
| 红色 (`#d73a49`) | issue 关闭、PR 关闭、deployment 失败、dependabot 严重 | | 红色 (`#f85149`) | PR 关闭、issue 关闭、工作流失败、发布已删除、delete、检查失败、审查请求修改、部署失败、成员移除、代码扫描/Dependabot 严重与高危 |
| 紫色 (`#7057ff`) | PR 合并、discussion 创建 | | 紫色 (`#8957e5`) | PR 合并、label、discussion |
| 蓝色 (`#0366d6`) | PR review 评论、issue 评论、workflow run | | 蓝色 (`#1f6feb`) | PR其他操作、issue 重新打开、fork、里程碑打开 |
| 黄色 (`#dbab09`) | PR review 请求修改、deployment 待定 | | 黄色 (`#d29922`) | 工作流(排队/运行中/其他)、预发布 release、star、检查其他、部署待定、代码扫描/Dependabot 中危 |
| 青色 (`#00897b`) | check run、code scanning | | 灰色 (`#6e7681`) | issue 评论、commit 评论、讨论评论 |
| 橙色 (`#e67e22`) | label、milestone | | 灰色 (`#8b949e`) | 审查评论、repository、代码扫描/Dependabot 低危、默认 |
| 灰色 (`#6a737d`) | delete、repository、member 移除 |
## 通用回退 ## 通用回退
@ -63,11 +67,11 @@ WebHooker 支持 23 种 GitHub webhook 事件类型,每种都有专用的格
实操指南见[过滤器教程](../guide/filters),包含完整示例。 实操指南见[过滤器教程](../guide/filters),包含完整示例。
| 过滤器 | 适用事件 | | 过滤器 | 适用事件 |
| --------- | ----------------------------------------------------------------------------------------------------------------------- | | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event` | 所有事件 | | `event` | 所有事件 |
| `repo` | 所有事件 | | `repo` | 所有事件 |
| `actor` | 所有事件 | | `actor` | 所有事件 |
| `action` | 载荷中包含 `action` 字段的事件 | | `action` | 载荷中包含 `action` 字段的事件 |
| `branch` | push、pull_request、pull_request_review、pull_request_review_comment、create、delete、workflow_run、code_scanning_alert | | `branch` | push、pull_request、pull_request_review、pull_request_review_comment、create、delete、workflow_run、workflow_job、check_suite、deployment、code_scanning_alert |
| `keyword` | 所有事件(搜索完整载荷正文) | | `keyword` | 所有事件(搜索完整载荷正文) |

View file

@ -9,13 +9,16 @@ WebHooker 需要多个密钥才能运行。本地开发时存储在 `.dev.vars`
| 变量 | 说明 | | 变量 | 说明 |
| ----------------------- | -------------------------------------------------------- | | ----------------------- | -------------------------------------------------------- |
| `GITHUB_WEBHOOK_SECRET` | GitHub App 设置中的 Webhook 密钥 | | `GITHUB_WEBHOOK_SECRET` | GitHub App 设置中的 Webhook 密钥 |
| `GITHUB_APP_ID` | GitHub App 的数字 ID |
| `GITHUB_PRIVATE_KEY` | App 私钥PEM 格式,用 `\n` 转义) |
| `GITHUB_CLIENT_ID` | App 设置中的 OAuth 客户端 ID | | `GITHUB_CLIENT_ID` | App 设置中的 OAuth 客户端 ID |
| `GITHUB_CLIENT_SECRET` | App 设置中的 OAuth 客户端密钥 | | `GITHUB_CLIENT_SECRET` | App 设置中的 OAuth 客户端密钥 |
| `DISCORD_TOKEN` | Discord Bot Token | | `DISCORD_TOKEN` | Discord Bot Token |
| `TELEGRAM_TOKEN` | Telegram Bot TokenBotFather 获取)—— Telegram 路由必需 | | `TELEGRAM_TOKEN` | Telegram Bot TokenBotFather 获取)—— Telegram 路由必需 |
> [!NOTE]
> `GITHUB_APP_ID``GITHUB_PRIVATE_KEY` 当前未被代码使用——OAuth 流程只需要
> `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`。为兼容性保留在模式中,以备日后启用
> GitHub App 认证。
### 可选密钥 ### 可选密钥
| 变量 | 说明 | 默认值 | | 变量 | 说明 | 默认值 |
@ -52,6 +55,7 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
| `GET /admin/api/groups/:id/routes` | 列出某分组的路由 | | `GET /admin/api/groups/:id/routes` | 列出某分组的路由 |
| `PUT /admin/api/groups/:id/routes` | 替换某分组的路由 | | `PUT /admin/api/groups/:id/routes` | 替换某分组的路由 |
| `GET /admin/api/logs` | 发送日志(按可访问路由过滤) | | `GET /admin/api/logs` | 发送日志(按可访问路由过滤) |
| `GET /admin/api/logs/:id` | 单条发送日志(按权限过滤) |
控制台支持新增、编辑、删除和开关路由。保存后立即写入 KV `config:routes` 并使配置缓存失效,下一次 webhook 处理即会生效。 控制台支持新增、编辑、删除和开关路由。保存后立即写入 KV `config:routes` 并使配置缓存失效,下一次 webhook 处理即会生效。
@ -140,6 +144,7 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
| `name` | string | 是 | 可读的分组名称 | | `name` | string | 是 | 可读的分组名称 |
| `adminIds` | string[] | 是 | 可管理该分组路由的 GitHub 用户 ID 或登录名 | | `adminIds` | string[] | 是 | 可管理该分组路由的 GitHub 用户 ID 或登录名 |
| `owners` | string[] | 否 | 允许事件进入该分组的组织/用户登录名;为空表示不限制 | | `owners` | string[] | 否 | 允许事件进入该分组的组织/用户登录名;为空表示不限制 |
| `emoji` | boolean | 否 | 是否在该分组消息中显示 emoji默认 `true` |
### 权限模型 ### 权限模型
@ -167,7 +172,7 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
- 在任何过滤器上设置 `"exclude": true` 可反转匹配逻辑NOT 逻辑) - 在任何过滤器上设置 `"exclude": true` 可反转匹配逻辑NOT 逻辑)
- 非 keyword 过滤器为**精确、不区分大小写**的匹配——不支持通配符(`repo: "org/*"` 不会匹配任何内容) - 非 keyword 过滤器为**精确、不区分大小写**的匹配——不支持通配符(`repo: "org/*"` 不会匹配任何内容)
- `keyword` 过滤器支持正则表达式——正则有误或超过 200 个字符时回退到子串匹配 - `keyword` 过滤器支持正则表达式——正则有误或超过 200 个字符时回退到子串匹配
- `branch` 过滤器适用于 push、pull_request、pull_request_review、pull_request_review_comment、create/delete、workflow_run 和 code_scanning_alert 事件 - `branch` 过滤器适用于 push、pull_request、pull_request_review、pull_request_review_comment、create/delete、workflow_run、workflow_job、check_suite、deployment 和 code_scanning_alert 事件
### 匹配值 ### 匹配值
@ -180,17 +185,27 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
## KV 存储布局 ## KV 存储布局
| 键模式 | 值 | TTL | | 键模式 | 值 | TTL |
| ------------------------ | --------------------------------------------------- | ------------------ | | ------------------------------ | ----------------------------------------------------------------------------- | ------------------ |
| `config:routes` | JSON 路由数组 | 永久 | | `config:routes` | JSON 路由数组 | 永久 |
| `config:groups` | JSON 分组数组 | 永久 | | `config:groups` | JSON 分组数组 | 永久 |
| `session:{id}` | 管理员会话 `{ userId, login }` | 7 天 | | `session:{id}` | 管理员会话 `{ userId, login }` | 7 天 |
| `token:{userId}` | `{ userId, accessToken, expiresAt, refreshToken? }` | 0.9 × Token 有效期 | | `token:{userId}` | `{ userId, accessToken, expiresAt, refreshToken? }` | 0.9 × Token 有效期 |
| `token-reverse:{sha256}` | 用于按 Token 反查的用户 id | 0.9 × Token 有效期 | | `token-reverse:{sha256}` | 用于按 Token 反查的用户 id | 0.9 × Token 有效期 |
| `discord-link:{userId}` | 与 Discord 用户绑定的 GitHub 用户 id | 永久 | | `state:{hex}` | `{ redirectTo, expiresAt, discordUserId?, telegramUserId?, telegramChatId? }` | 600 秒 |
| `state:{hex}` | `{ redirectTo, expiresAt, discordUserId? }` | 600 秒 | | `delivery:{id}` | Webhook 投递 id去重标记 | 300 秒 |
| `delivery:{id}` | Webhook 投递 id去重标记 | 300 秒 | | `msg:{routeId}:{key}:{target}` | 原地更新用消息 id 追踪(如 `workflow_run` | 7 天 |
| `logs:send:{ts}-{hex}` | 发送记录 | 1 小时 | | `cmd:guild:{id}` | 已注册命令的服务器 id去重标记 | 永久 |
| `cmd:guild:{id}` | 已注册命令的服务器 id去重标记 | 永久 | | `cmd:registered:global` | 全局命令已注册标记24h 去重) | 1 天 |
| `cmd:registered:global` | 全局命令已注册标记24h 去重) | 1 天 | | `config:discord-app-id` | Discord 应用 id 缓存 | 永久 |
| `config:discord-app-id` | Discord 应用 id 缓存 | 永久 | | `i18n:{lang}` | 翻译覆盖,合并到英文之上 | 永久 |
## D1 存储布局
D1 数据库(`DB` 绑定,数据库 `webhooker`)包含三张表:
| 表 | 用途 |
| ---------------- | ---------------------------------------------------------------------- |
| `send_logs` | 每次分发尝试一行(路由 id、事件、目标、成功/失败、耗时、错误码、详情) |
| `discord_links` | 映射 `discord_user_id``github_user_id`,用于 Discord `/gh` 命令 |
| `telegram_links` | 映射 `telegram_user_id``github_user_id`,用于 Telegram `/gh` 命令 |

View file

@ -25,8 +25,6 @@ npx wrangler kv namespace create KV
```bash ```bash
npx wrangler secret put GITHUB_WEBHOOK_SECRET npx wrangler secret put GITHUB_WEBHOOK_SECRET
npx wrangler secret put GITHUB_APP_ID
npx wrangler secret put GITHUB_PRIVATE_KEY # PKCS#8 PEMBEGIN PRIVATE KEY
npx wrangler secret put GITHUB_CLIENT_ID npx wrangler secret put GITHUB_CLIENT_ID
npx wrangler secret put GITHUB_CLIENT_SECRET npx wrangler secret put GITHUB_CLIENT_SECRET
npx wrangler secret put DISCORD_TOKEN npx wrangler secret put DISCORD_TOKEN
@ -39,15 +37,9 @@ npx wrangler secret put ADMIN_USER_IDS # 逗号分隔的 GitHub ID/登录
不存在全局频道密钥。每条路由在 [Web 控制台](/zh/guide/configuration#web-控制台) 中声明各自的目标频道(及可选的子区/thread因此不需要 `DISCORD_CHANNEL_ID` 不存在全局频道密钥。每条路由在 [Web 控制台](/zh/guide/configuration#web-控制台) 中声明各自的目标频道(及可选的子区/thread因此不需要 `DISCORD_CHANNEL_ID`
::: :::
::: warning GitHub App 私钥必须是 PKCS#8 ::: tip GitHub App ID / 私钥未使用
GitHub 下发的私钥为 PKCS#1 格式(`BEGIN RSA PRIVATE KEY`。Cloudflare Workers 的 JWT 签名要求 PKCS#8,需先转换: `GITHUB_APP_ID``GITHUB_PRIVATE_KEY` 当前未被代码使用——OAuth 流程只需要
`GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`。无需设置(也无需进行 PKCS#8 转换)。
```bash
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt \
-in your-app.private-key.pem -out gh_pk_pkcs8.pem
```
然后将 `gh_pk_pkcs8.pem` 作为 `GITHUB_PRIVATE_KEY` 上传。
::: :::
Discord 交互通过 HTTPS Interactions Endpoint 送达,需要设置 `DISCORD_PUBLIC_KEY` 并把 **Interactions Endpoint URL** 指向 `https://your-domain/discord/interactions`。参见下方 [Interactions Endpoint](#interactions-endpoint)。 Discord 交互通过 HTTPS Interactions Endpoint 送达,需要设置 `DISCORD_PUBLIC_KEY` 并把 **Interactions Endpoint URL** 指向 `https://your-domain/discord/interactions`。参见下方 [Interactions Endpoint](#interactions-endpoint)。
@ -79,8 +71,8 @@ Worker 现在可通过 `https://webhooker.<your-subdomain>.workers.dev` 访问
3. 设置权限: 3. 设置权限:
- **Repository permissions**: Contents (read)、Issues (write)、Pull requests (write)、Metadata (read)、Checks (read)、Deployments (read)、Discussions (read)、Code scanning alerts (read)、Dependabot alerts (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) —— 如果需要 - **Organization permissions**: Members (read) —— 如果需要
4. 订阅事件(全部 23 种支持的事件): 4. 订阅事件(全部 28 种支持的事件):
- 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 - 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. 生成私钥 → 将内容保存到 `GITHUB_PRIVATE_KEY` 环境变量 5. 生成私钥 → 将内容保存到 `GITHUB_PRIVATE_KEY` 环境变量
### 2. 安装 App ### 2. 安装 App
@ -120,6 +112,22 @@ Worker 现在可通过 `https://webhooker.<your-subdomain>.workers.dev` 访问
用户运行 `/gh login` 绑定自己的 GitHub 账号,即可以本人身份评论 issue/PR。完整命令说明见 [README](https://github.com/ReCloudStudio/WebHooker#bot-commands-comment-on-github-as-yourself)。 用户运行 `/gh login` 绑定自己的 GitHub 账号,即可以本人身份评论 issue/PR。完整命令说明见 [README](https://github.com/ReCloudStudio/WebHooker#bot-commands-comment-on-github-as-yourself)。
## Telegram 机器人配置
1. 用 [@BotFather](https://t.me/BotFather) 创建机器人,将 Token 复制到 `TELEGRAM_TOKEN`
2. (可选)设置 `TELEGRAM_WEBHOOK_SECRET`webhook 注册时会作为 `secret_token` 传给 Telegram`POST /telegram/webhook` 使用时间安全比较校验。
3. Worker 会在定时任务中自动同步 webhook`setWebhook` 指向 `{BASE_URL}/telegram/webhook`),因此无需手动调用 `setWebhook`——只需确保 `BASE_URL` 已设置。
4. 将机器人加入群组(或启用话题),在路由配置中用 `chatId` / `topicId` 指定目标。
在 Telegram 中,`/gh` 命令通过在通知消息上**回复**来使用:
- `/gh login` — 绑定你的 GitHub 账号(返回 OAuth 链接)
- `/gh logout` — 解除绑定
- `/gh comment <内容>` — 回复一条 issue/PR 通知,以本人身份评论
- `/gh merge` / `/gh close` — 回复一条 PR 通知,合并/关闭该 PR
头像使用内置 `GET /api/richheader` 渲染为链接预览卡片(可用 `TELEGRAM_RICH_HEADER_HOST` 覆盖)。
## 自定义域名(可选) ## 自定义域名(可选)
要使用自定义域名替代 `*.workers.dev` 要使用自定义域名替代 `*.workers.dev`
@ -128,13 +136,5 @@ Worker 现在可通过 `https://webhooker.<your-subdomain>.workers.dev` 访问
2. 添加自定义域名或路由 2. 添加自定义域名或路由
3. 更新 `BASE_URL` 以匹配 3. 更新 `BASE_URL` 以匹配
## Docker > [!NOTE]
> 本项目是一个 Cloudflare Worker依赖 `wrangler.jsonc` 中声明的 KV 与 D1 绑定,无法作为独立的 Node/容器进程运行。
提供 Dockerfile 用于容器化部署(例如在反向代理后面):
```bash
docker build -t webhooker .
docker run -p 8787:8787 --env-file .env webhooker
```
注意Docker 模式下不包含 KV 等 Cloudflare 存储。完整功能请使用 Cloudflare 部署。

View file

@ -84,13 +84,16 @@
匹配事件涉及的分支。何种字段算作「分支」取决于事件类型: 匹配事件涉及的分支。何种字段算作「分支」取决于事件类型:
| 事件 | 提取的分支 | | 事件 | 提取的分支 |
| --------------------------- | ------------------------------ | | --------------------------- | ----------------------------------- |
| `push` | 推送到的目标分支 | | `push` | 推送到的目标分支 |
| `pull_request`(及 review | 拉取请求的 **head**(源)分支 | | `pull_request`(及 review | 拉取请求的 **head**(源)分支 |
| `create` / `delete` | 创建/删除的分支或标签 | | `create` / `delete` | 创建/删除的分支或标签 |
| `workflow_run` | 工作流运行所在的 `head_branch` | | `workflow_run` | 工作流运行所在的 `head_branch` |
| `code_scanning_alert` | 告警所属的分支 | | `workflow_job` | 作业运行所在的 `head_branch` |
| `check_suite` | 检查套件的 `head_branch` |
| `deployment` | 部署引用(去除 `refs/heads/` 前缀) |
| `code_scanning_alert` | 告警所属的分支 |
```json ```json
{ {

View file

@ -29,8 +29,6 @@ cp .env.example .dev.vars
```bash ```bash
GITHUB_WEBHOOK_SECRET=your-webhook-secret GITHUB_WEBHOOK_SECRET=your-webhook-secret
GITHUB_APP_ID=your-app-id
GITHUB_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
GITHUB_CLIENT_ID=your-client-id GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret GITHUB_CLIENT_SECRET=your-client-secret
DISCORD_TOKEN=your-bot-token DISCORD_TOKEN=your-bot-token
@ -40,7 +38,7 @@ BASE_URL=http://localhost:8787
``` ```
::: tip ::: tip
`GITHUB_PRIVATE_KEY` 必须是 **PKCS#8** 格式(`BEGIN PRIVATE KEY`)。用 `openssl pkcs8 -nocrypt -in app.pem -out pkcs8.pem` 转换 GitHub 下发的 PKCS#1 私钥。目标频道在 Web UI 中按路由设置,因此不需要 `DISCORD_CHANNEL_ID`。若要在本地启用 `/gh` 命令,请在开发者门户复制 **Public Key** 填入 `DISCORD_PUBLIC_KEY`,并把 Interactions Endpoint URL 设为 `http://localhost:8787/discord/interactions` `GITHUB_APP_ID` / `GITHUB_PRIVATE_KEY` 未被代码使用OAuth 流程只需要 Client ID/Secret可省略。目标频道在 Web UI 中按路由设置,因此不需要 `DISCORD_CHANNEL_ID`。若要在本地启用 `/gh` 命令,请在开发者门户复制 **Public Key** 填入 `DISCORD_PUBLIC_KEY`,并把 Interactions Endpoint URL 设为 `http://localhost:8787/discord/interactions`
::: :::
::: warning ::: warning

View file

@ -1,27 +1,29 @@
# 简介 # 简介
WebHooker 是一个基于 Cloudflare Workers 构建的 GitHub webhook 调度器。它接收 GitHub webhook 事件,应用可配置的过滤器,将事件格式化为丰富的 Discord 嵌入消息,并通过 Discord REST API 投递到 Discord 频道或帖子。Discord 内的 `/gh` 交互通过 HTTPS Interactions EndpointEd25519 验签)送达。路由通过内置的 Web UI 管理。 WebHooker 是一个基于 Cloudflare Workers 构建的 GitHub webhook 调度器。它接收 GitHub webhook 事件,应用可配置的过滤器,将事件格式化为富消息,并通过各自 REST API 投递到 Discord 频道/子区embed与 Telegram 群组/话题HTML。Discord 内的 `/gh` 交互通过 HTTPS Interactions EndpointEd25519 验签送达Telegram 的 `/gh` 命令通过 Telegram webhook 送达。路由与分组通过内置的 Web UI 管理。
## 架构 ## 架构
```text ```text
GitHub Webhook → Cloudflare Worker (Hono) GitHub Webhook → Cloudflare Worker (Hono)
├── POST /webhook → 验证 → 去重 → 过滤 → 格式化 → Discord (REST API) ├── POST /webhook → 验证 → 去重 → 过滤 → 格式化 → Discord (REST) / Telegram (Bot API)
├── POST /discord/interactions → 验证 (Ed25519) → 处理 /gh 斜杠与右键命令
├── POST /telegram/webhook → 验证 (secret token) → 处理 /gh 命令
├── GET /auth/github → OAuth 流程 ├── GET /auth/github → OAuth 流程
├── GET /api/richheader → Telegram 头像链接预览卡片
├── POST /api/* → 用户操作 (Bearer Token 鉴权) ├── POST /api/* → 用户操作 (Bearer Token 鉴权)
├── /admin → 路由与发送日志 Web UI管理员会话 ├── /admin → 路由、分组与发送日志 Web UI管理员会话
└── GET /health → 健康检查 └── GET /health → 健康检查
POST /discord/interactions → 验证 (Ed25519) → 处理 /gh 斜杠与右键命令
``` ```
### 组件 ### 组件
| 组件 | 职责 | | 组件 | 职责 |
| ------------------------- | --------------------------------------------------------------------------------------------------------- | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Cloudflare Worker** | HTTP 入口、签名验证、投递去重、事件解析、路由匹配、REST 发送 | | **Cloudflare Worker** | HTTP 入口、签名验证、投递去重、事件解析、路由匹配、平台分发 |
| **Interactions Endpoint** | 验证 Ed25519 签名并处理 `/gh` 交互斜杠命令、右键菜单、按钮、modal | | **Interactions Endpoint** | 验证 Ed25519 签名并处理 `/gh` 交互斜杠命令、右键菜单、按钮、modal |
| **KV** | Token 存储 (`token:{userId}`)、OAuth 状态 (`state:{hex}`)、路由配置 (`config:routes`)、发送日志、投递去重 | | **KV** | Token 存储 (`token:{userId}`)、OAuth 状态 (`state:{hex}`)、路由配置 (`config:routes`)、分组配置 (`config:groups`)、管理员会话、投递去重、消息更新追踪 (`msg:*`) |
| **D1** | 发送日志 (`send_logs`)、Discord↔GitHub 绑定 (`discord_links`)、Telegram↔GitHub 绑定 (`telegram_links`) |
### 数据流 ### 数据流
@ -29,18 +31,19 @@ POST /discord/interactions → 验证 (Ed25519) → 处理 /gh 斜杠与右键
2. Worker 验证 HMAC-SHA256 签名 2. Worker 验证 HMAC-SHA256 签名
3. Worker 按 `X-GitHub-Delivery` 去重KV短 TTL丢弃重复投递 3. Worker 按 `X-GitHub-Delivery` 去重KV短 TTL丢弃重复投递
4. Worker 解析事件类型和载荷 4. Worker 解析事件类型和载荷
5. 根据过滤器评估路由event、repo、actor、action、branch、keyword 5. 根据过滤器event、repo、actor、action、branch、keyword与分组所有者限制评估路由
6. 匹配的路由触发格式化器函数生成 Discord 嵌入消息 6. 匹配的路由触发格式化器函数生成平台中立消息
7. 每条消息通过 Discord REST API 发送到对应路由的目标频道/帖子,并处理速率限制重试,结果记录到发送日志 7. 每条消息通过 Discord 或 Telegram REST API 发送到对应路由的目标,并处理速率限制重试;`workflow_run` 进度原地更新。每次尝试都记录到 D1 发送日志
## 技术栈 ## 技术栈
- **运行时**: Cloudflare Workers - **运行时**: Cloudflare Workers
- **HTTP 框架**: Hono - **HTTP 框架**: Hono
- **Discord 投递**: Discord REST API交互通过 Ed25519 验签的 HTTPS Interactions Endpoint - **Discord 投递**: Discord REST API交互通过 Ed25519 验签的 HTTPS Interactions Endpoint
- **Telegram 投递**: Telegram Bot APIwebhook 带可选 secret-token 校验)
- **Web UI**: Nuxt 3 静态 SPA由 Worker 资源托管 - **Web UI**: Nuxt 3 静态 SPA由 Worker 资源托管
- **存储**: Cloudflare KV - **存储**: Cloudflare KV + D1
- **鉴权**: Web Crypto API (HMAC-SHA256)、jose (JWT)、octokit (GitHub API) - **鉴权**: Web Crypto API (HMAC-SHA256、Ed25519)、octokit (GitHub API)、jose依赖
- **语言**: TypeScript - **语言**: TypeScript
## 许可证 ## 许可证

View file

@ -4,7 +4,7 @@ layout: home
hero: hero:
name: WebHooker name: WebHooker
text: GitHub Webhook → Discord text: GitHub Webhook → Discord
tagline: 通过 Cloudflare Workers 接收 GitHub 事件,应用过滤器,将格式化消息路由到 Discord 频道或帖子 tagline: 通过 Cloudflare Workers 接收 GitHub 事件,应用过滤器,将格式化消息路由到 Discord 频道/子区与 Telegram 群组/话题
actions: actions:
- theme: brand - theme: brand
text: 快速开始 text: 快速开始
@ -14,16 +14,16 @@ hero:
link: https://github.com/ReCloudStudio/WebHooker link: https://github.com/ReCloudStudio/WebHooker
features: features:
- title: 23 种事件格式化器 - title: 28 种事件格式化器
details: 为 push、pull_request、issues、release、workflow_run 及其他 18 种事件类型提供丰富的 Discord 嵌入消息,支持颜色编码输出。 details: 为 push、pull_request、issues、release、workflow_run 及其他 23 种事件类型提供丰富的 Discord 嵌入与 Telegram HTML 消息,支持颜色编码输出。
- title: 灵活的过滤器 - title: 灵活的过滤器
details: 支持按事件类型、仓库、参与者、操作、分支(含 PR和关键字支持正则过滤。支持排除模式。 details: 支持按事件类型、仓库、参与者、操作、分支(含 PR和关键字支持正则过滤。支持排除模式。
- title: Cloudflare Workers - title: Cloudflare Workers
details: 运行在 Cloudflare 边缘网络上。通过 Discord REST API 发送消息,并通过 Ed25519 验签的 Interactions Endpoint 支持 `/gh` 命令。 details: 运行在 Cloudflare 边缘网络上。通过 Discord REST API 与 Telegram Bot API 发送消息,并通过 Ed25519 验签的 Interactions Endpoint 支持 `/gh` 命令。
- title: Web UI 与斜杠命令 - title: Web UI、分组与命令
details: "在内置管理控制台中管理路由、查看发送日志。绑定你的 GitHub 账号,通过 /gh 命令以本人身份评论 issue/PR。" details: "在内置管理控制台中管理路由、分组与发送日志。绑定你的 GitHub 账号,通过 /gh 命令以本人身份评论 issue/PRDiscord 或 Telegram。"
- title: 签名验证 - title: 签名验证
details: 使用 Web Crypto API 进行 HMAC-SHA256 webhook 签名验证与 Ed25519 交互签名验证,支持时间安全比较。 details: 使用 Web Crypto API 进行 HMAC-SHA256 webhook 签名验证与 Ed25519 交互签名验证,支持时间安全比较。
- title: 优雅降级 - title: 原地更新
details: 当 Discord Token 不可用时以 webhook-only 模式运行。提供健康检查端点用于监控 details: workflow_run 进度在运行推进时于同一条消息上原地更新Discord 与 Telegram 均支持
--- ---