feat: add gitea/forgejo support

This commit is contained in:
RhenCloud 2026-08-11 22:01:04 +08:00
parent ace036c209
commit aec0d1a257
43 changed files with 683 additions and 130 deletions

View file

@ -6,13 +6,14 @@ WebHooker requires several secrets to function. For local development, store the
### Required Secrets
| Variable | Description |
| ----------------------- | ------------------------------------------------------------------ |
| `GITHUB_WEBHOOK_SECRET` | Webhook secret from your GitHub App settings |
| `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 |
| Variable | Description |
| ----------------------- | ------------------------------------------------------------------------ |
| `GITHUB_WEBHOOK_SECRET` | Webhook secret from your GitHub App settings |
| `GITEA_WEBHOOK_SECRET` | Webhook secret from your Gitea instance (only to receive Gitea webhooks) |
| `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
@ -30,6 +31,17 @@ WebHooker requires several secrets to function. For local development, store the
| `BASE_URL` | Public URL for OAuth callbacks | `http://localhost:8787` |
| `ADMIN_USER_IDS` | Comma-separated GitHub user IDs (or logins) allowed to access the Web UI | Disabled |
## Webhook Providers
WebHooker ingests webhooks from multiple forges through the same `POST /webhook` endpoint; the provider is auto-detected from the request headers, so point every forge's webhook at `{BASE_URL}/webhook`.
| Provider | Event header | Signature header | Signature format | Secret |
| -------- | ---------------- | --------------------- | -------------------------- | ----------------------- |
| GitHub | `X-GitHub-Event` | `X-Hub-Signature-256` | `sha256=<hex>` HMAC-SHA256 | `GITHUB_WEBHOOK_SECRET` |
| Gitea | `X-Gitea-Event` | `X-Gitea-Signature` | plain hex HMAC-SHA256 | `GITEA_WEBHOOK_SECRET` |
Gitea payloads are normalized to the same internal shape as GitHub events, so routes, filters, and the 28 formatters work unchanged. Unknown or unmapped Gitea events fall back to the generic formatter. Repository/commit/user links are derived from the payload's `repository.html_url`, so they point at your Gitea instance.
## Web UI
WebHooker ships with a built-in config console at `/admin` for managing routes in the browser. It is protected by GitHub OAuth plus an admin whitelist.
@ -156,17 +168,19 @@ Routes belong to groups. Groups scope admin access and can restrict which events
"id": "backend-team",
"name": "Backend Team",
"adminIds": ["rhencloud"],
"owners": ["myorg"]
"owners": ["myorg"],
"providers": ["github", "gitea"]
}
```
| Field | Type | Required | Description |
| ---------- | -------- | -------- | ---------------------------------------------------------------------- |
| `id` | string | Yes | Lowercase id (`a-z0-9`, `-`); referenced by each route's `groupId` |
| `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`) |
| Field | Type | Required | Description |
| ----------- | -------- | -------- | ------------------------------------------------------------------------- |
| `id` | string | Yes | Lowercase id (`a-z0-9`, `-`); referenced by each route's `groupId` |
| `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 |
| `providers` | string[] | No | Source platforms allowed into this group (`github`, `gitea`); empty = all |
| `emoji` | boolean | No | Whether to include emoji in this group's messages (default `true`) |
### Access Model
@ -174,6 +188,7 @@ Routes belong to groups. Groups scope admin access and can restrict which events
- **Group admins** (`adminIds`) only see and edit the groups they manage; submitting a route outside their groups returns `403`.
- Group admin endpoints operate on a single group at a time via `/admin/api/groups/:id/routes`; `groupId` is forced from the path parameter.
- The `owners` list restricts which event actors (sender logins) the group's routes will dispatch at all.
- The `providers` list restricts which forge's events (`github`, `gitea`) the group's routes will dispatch. This lets you keep GitHub and Gitea groups separate even when org/user names collide.
## Filter Types

View file

@ -99,6 +99,14 @@ Your worker is now live at `https://webhooker.<your-subdomain>.workers.dev`.
2. Set **Webhook URL** to `https://webhooker.<your-subdomain>.workers.dev/webhook`
3. Set **Webhook secret** to match `GITHUB_WEBHOOK_SECRET`
### 6. (Optional) Configure Gitea Webhook
1. In your Gitea repo, go to **Settings → Webhooks → Add Webhook → Gitea**
2. Set **Target URL** to `https://webhooker.<your-subdomain>.workers.dev/webhook`
3. Set **HTTP Method** to `POST` and **Content Type** to `application/json`
4. Set **Secret** to match `GITEA_WEBHOOK_SECRET`
5. Choose the events to trigger (push, issues, pull requests, releases, ...)
## GitHub App Setup
### 1. Create App

View file

@ -1,12 +1,12 @@
# Introduction
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.
WebHooker is a GitHub/Gitea webhook dispatcher built on Cloudflare Workers. It receives webhook events from supported forges (GitHub, Gitea — more can be added via `src/providers/`), 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) / Telegram (Bot API)
GitHub / Gitea Webhook → Cloudflare Worker (Hono)
├── POST /webhook → detect provider → 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
@ -27,10 +27,10 @@ GitHub Webhook → Cloudflare Worker (Hono)
### Data Flow
1. GitHub sends a webhook to `POST /webhook`
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
1. A forge (GitHub or Gitea) sends a webhook to `POST /webhook`
2. Worker detects the provider from its headers (`X-GitHub-Event` / `X-Gitea-Event`) and verifies the provider-specific HMAC-SHA256 signature
3. Worker deduplicates by the delivery id (KV, short TTL) to drop repeat deliveries
4. Worker parses the event type and normalizes the payload to a GitHub-shaped event
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