mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
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:
parent
68cda9f178
commit
afe19795b1
25 changed files with 621 additions and 398 deletions
|
|
@ -9,13 +9,16 @@ WebHooker requires several secrets to function. For local development, store the
|
|||
| Variable | Description |
|
||||
| ----------------------- | ------------------------------------------------------------------ |
|
||||
| `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_SECRET` | OAuth client secret from App settings |
|
||||
| `DISCORD_TOKEN` | Discord bot token |
|
||||
| `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
|
||||
|
||||
| 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 |
|
||||
| `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/: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.
|
||||
|
||||
|
|
@ -140,6 +144,7 @@ Routes belong to groups. Groups scope admin access and can restrict which events
|
|||
| `name` | string | Yes | Human-readable group name |
|
||||
| `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 |
|
||||
| `emoji` | boolean | No | Whether to include emoji in this group's messages (default `true`) |
|
||||
|
||||
### 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)
|
||||
- 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
|
||||
- `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
|
||||
|
||||
|
|
@ -180,17 +185,27 @@ Filters accept either a single string or an array of strings:
|
|||
|
||||
## KV Storage Layout
|
||||
|
||||
| Key Pattern | Value | TTL |
|
||||
| ------------------------ | --------------------------------------------------- | ------------------ |
|
||||
| `config:routes` | JSON array of routes | Permanent |
|
||||
| `config:groups` | JSON array of groups | Permanent |
|
||||
| `session:{id}` | Admin session `{ userId, login }` | 7 days |
|
||||
| `token:{userId}` | `{ userId, accessToken, expiresAt, refreshToken? }` | 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? }` | 600 seconds |
|
||||
| `delivery:{id}` | Webhook delivery id (dedup marker) | 300 seconds |
|
||||
| `cmd:guild:{id}` | Guild id whose commands were registered (dedup) | Permanent |
|
||||
| `cmd:registered:global` | Global command registration marker (dedup) | 1 day |
|
||||
| `config:discord-app-id` | Cached Discord application id | Permanent |
|
||||
| `logs:send:{ts}-{hex}` | Send record | 1 hour |
|
||||
| Key Pattern | Value | TTL |
|
||||
| ------------------------------ | ----------------------------------------------------------------------------- | ------------------ |
|
||||
| `config:routes` | JSON array of routes | Permanent |
|
||||
| `config:groups` | JSON array of groups | Permanent |
|
||||
| `session:{id}` | Admin session `{ userId, login }` | 7 days |
|
||||
| `token:{userId}` | `{ userId, accessToken, expiresAt, refreshToken? }` | 0.9 × token expiry |
|
||||
| `token-reverse:{sha256}` | User id for reverse lookup by token | 0.9 × token expiry |
|
||||
| `state:{hex}` | `{ redirectTo, expiresAt, discordUserId?, telegramUserId?, telegramChatId? }` | 600 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:registered:global` | Global command registration marker (dedup) | 1 day |
|
||||
| `config:discord-app-id` | Cached Discord application id | Permanent |
|
||||
| `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 |
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ This outputs a namespace ID. Update `wrangler.jsonc` with the ID:
|
|||
|
||||
```bash
|
||||
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_SECRET
|
||||
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.
|
||||
:::
|
||||
|
||||
::: warning GitHub App private key must be PKCS#8
|
||||
GitHub issues private keys in PKCS#1 format (`BEGIN RSA PRIVATE KEY`). Cloudflare Workers' JWT signing requires PKCS#8. Convert first:
|
||||
|
||||
```bash
|
||||
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`.
|
||||
::: tip GitHub App ID / private key are unused
|
||||
`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
|
||||
set them (no PKCS#8 conversion required).
|
||||
:::
|
||||
|
||||
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:
|
||||
- **Repository permissions**: Contents (read), Issues (write), Pull requests (write), Metadata (read), Checks (read), Deployments (read), Discussions (read), Code scanning alerts (read), Dependabot alerts (read)
|
||||
- **Organization permissions**: Members (read) — if needed
|
||||
4. Subscribe to events (all 23 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
|
||||
4. Subscribe to events (all 28 supported):
|
||||
- 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
|
||||
|
||||
### 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.
|
||||
|
||||
## 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)
|
||||
|
||||
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
|
||||
3. Update `BASE_URL` to match
|
||||
|
||||
## Docker
|
||||
|
||||
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.
|
||||
> [!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.
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
| `create` / `delete` | The created/deleted branch or tag |
|
||||
| `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 |
|
||||
|
||||
```json
|
||||
|
|
|
|||
|
|
@ -29,8 +29,6 @@ Edit `.dev.vars` with your actual values:
|
|||
|
||||
```bash
|
||||
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_SECRET=your-client-secret
|
||||
DISCORD_TOKEN=your-bot-token
|
||||
|
|
@ -40,7 +38,7 @@ BASE_URL=http://localhost:8787
|
|||
```
|
||||
|
||||
::: 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
|
||||
|
|
|
|||
|
|
@ -1,27 +1,29 @@
|
|||
# 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
|
||||
|
||||
```text
|
||||
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 /api/richheader → Telegram avatar link-preview card
|
||||
├── POST /api/* → user actions (Bearer token auth)
|
||||
├── /admin → routes & send-log Web UI (admin session)
|
||||
└── GET /health → status check
|
||||
|
||||
POST /discord/interactions → verify (Ed25519) → handle /gh slash & context commands
|
||||
├── /admin → routes, groups & send-log Web UI (admin session)
|
||||
└── GET /health → status check
|
||||
```
|
||||
|
||||
### Components
|
||||
|
||||
| Component | Role |
|
||||
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Cloudflare Worker** | HTTP ingress, signature verification, delivery dedup, event parsing, route matching, REST send |
|
||||
| **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 |
|
||||
| Component | Role |
|
||||
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **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) |
|
||||
| **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
|
||||
|
||||
|
|
@ -29,18 +31,19 @@ POST /discord/interactions → verify (Ed25519) → handle /gh slash & context c
|
|||
2. Worker verifies the HMAC-SHA256 signature
|
||||
3. Worker deduplicates by `X-GitHub-Delivery` (KV, short TTL) to drop repeat deliveries
|
||||
4. Worker parses the event type and payload
|
||||
5. Routes are evaluated against filters (event, repo, actor, action, branch, keyword)
|
||||
6. Matching routes trigger formatter functions that produce Discord embeds
|
||||
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
|
||||
5. Routes are evaluated against filters (event, repo, actor, action, branch, keyword) and group owner restrictions
|
||||
6. Matching routes trigger formatter functions that produce platform-neutral messages
|
||||
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
|
||||
|
||||
- **Runtime**: Cloudflare Workers
|
||||
- **HTTP Framework**: Hono
|
||||
- **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
|
||||
- **Storage**: Cloudflare KV
|
||||
- **Auth**: Web Crypto API (HMAC-SHA256), jose (JWT), octokit (GitHub API)
|
||||
- **Storage**: Cloudflare KV + D1
|
||||
- **Auth**: Web Crypto API (HMAC-SHA256, Ed25519), octokit (GitHub API), jose (dependency)
|
||||
- **Language**: TypeScript
|
||||
|
||||
## License
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue