initial commit

This commit is contained in:
RhenCloud 2026-07-24 21:48:50 +08:00
commit 512d4b01d5
No known key found for this signature in database
GPG key ID: A574A617378C4E0B
55 changed files with 6430 additions and 0 deletions

115
docs/guide/configuration.md Normal file
View file

@ -0,0 +1,115 @@
# Configuration
## Secrets
WebHooker requires several secrets to function. For local development, store them in `.dev.vars`. For production, use Cloudflare Worker Secrets.
### Required Secrets
| 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 |
| `DISCORD_CHANNEL_ID` | Default Discord channel ID for messages |
### Optional Secrets
| Variable | Description | Default |
| ---------- | ------------------------------ | ----------------------- |
| `BASE_URL` | Public URL for OAuth callbacks | `http://localhost:8787` |
## Routes
Routes define which events get forwarded to which Discord channels. They are stored in Cloudflare KV under the key `config:routes` as a JSON array.
On first boot, 7 default routes are used if no KV config exists.
### Route Schema
```json
{
"id": "unique-route-id",
"name": "Human-readable name",
"enabled": true,
"filters": [
{ "type": "event", "match": "push" },
{ "type": "repo", "match": "org/repo", "exclude": false }
],
"target": {
"channelId": "DISCORD_CHANNEL_ID",
"threadId": "OPTIONAL_THREAD_ID"
}
}
```
### Default Routes
| ID | Event(s) | Description |
| ----------------- | ------------------ | -------------------------------- |
| `all-push` | `push` | All push events |
| `pull-requests` | `pull_request` | All PR activity |
| `issues` | `issues` | Issue open/close/edit |
| `issue-comments` | `issue_comment` | Issue and PR comments |
| `workflow-runs` | `workflow_run` | CI/CD workflow completions |
| `releases` | `release` | Release publish/edit |
| `branch-activity` | `create`, `delete` | Branch/tag creation and deletion |
### Custom Route Example
```json
[
{
"id": "backend-prs",
"name": "Backend PRs",
"enabled": true,
"filters": [
{ "type": "repo", "match": "myorg/backend" },
{ "type": "event", "match": "pull_request" },
{ "type": "actor", "match": "[bot]", "exclude": true }
],
"target": {
"channelId": "1234567890",
"threadId": "9876543210"
}
}
]
```
## Filter Types
| Type | Matches | Example |
| --------- | -------------------- | -------------------------------- |
| `event` | GitHub event name | `push`, `pull_request`, `issues` |
| `repo` | Repository full name | `org/repo` |
| `actor` | Sender login | `username`, `[bot]` |
| `action` | Event action | `opened`, `closed`, `published` |
| `branch` | Branch name | `main`, `feature/*` |
| `keyword` | Text in payload body | `deploy`, `/fix\s+\d+/` (regex) |
### Filter Behavior
- All filters in a route must match for the route to trigger (AND logic)
- Set `"exclude": true` on any filter to invert it (NOT logic)
- `keyword` filter supports regex patterns — falls back to substring match if regex is invalid
- `branch` filter works for push, pull_request, create/delete, workflow_run, and code_scanning_alert events
### Match Values
Filters accept either a single string or an array of strings:
```json
{ "type": "event", "match": "push" }
{ "type": "event", "match": ["push", "pull_request"] }
```
## KV Storage Layout
| Key Pattern | Value | TTL |
| ---------------- | ---------------------------- | ------------ |
| `config:routes` | JSON array of routes | Permanent |
| `token:{userId}` | `{ accessToken, expiresAt }` | Until expiry |
| `state:{hex}` | `{ userId, createdAt }` | 600 seconds |

104
docs/guide/deployment.md Normal file
View file

@ -0,0 +1,104 @@
# Deployment
## Cloudflare Setup
### 1. Create KV Namespace
```bash
npx wrangler kv namespace create KV
```
This outputs a namespace ID. Update `wrangler.jsonc` with the ID:
```jsonc
{
"kv_namespaces": [
{
"binding": "KV",
"id": "your-namespace-id",
},
],
}
```
### 2. Set Secrets
```bash
npx wrangler secret put GITHUB_WEBHOOK_SECRET
npx wrangler secret put GITHUB_APP_ID
npx wrangler secret put GITHUB_PRIVATE_KEY
npx wrangler secret put GITHUB_CLIENT_ID
npx wrangler secret put GITHUB_CLIENT_SECRET
npx wrangler secret put DISCORD_TOKEN
npx wrangler secret put DISCORD_CHANNEL_ID
```
### 3. Deploy
```bash
npx wrangler deploy
```
Your worker is now live at `https://webhooker.<your-subdomain>.workers.dev`.
### 4. Configure GitHub Webhook
1. Go to your GitHub App settings
2. Set **Webhook URL** to `https://webhooker.<your-subdomain>.workers.dev/webhook`
3. Set **Webhook secret** to match `GITHUB_WEBHOOK_SECRET`
## GitHub App Setup
### 1. Create App
1. Go to <https://github.com/settings/apps/new>
2. Fill in:
- **GitHub App name**: `WebHooker` (or your choice)
- **Homepage URL**: your domain
- **Webhook URL**: `https://your-domain/webhook`
- **Webhook secret**: generate and copy to `GITHUB_WEBHOOK_SECRET`
3. Set permissions:
- **Repository permissions**: Contents (read), Issues (write), Pull requests (write), Metadata (read)
- **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
5. Generate private key → save contents to `GITHUB_PRIVATE_KEY` env var
### 2. Install App
1. After creation, go to the App settings page
2. Click "Install App" → select org/user
3. Choose repositories to monitor
### 3. Configure OAuth
1. Go to App → OAuth settings
2. Set **Callback URL**: `https://your-domain/auth/github/callback`
3. Copy Client ID and Client Secret to env
## Discord Bot Setup
1. Go to <https://discord.com/developers/applications>
2. Create a new application → go to Bot section
3. Copy the bot token to `DISCORD_TOKEN`
4. Invite the bot to your server with `bot` scope and `Send Messages` permission
5. Copy the target channel ID to `DISCORD_CHANNEL_ID`
## Custom Domain (Optional)
To use a custom domain instead of `*.workers.dev`:
1. Go to your Cloudflare Worker settings
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 Durable Objects and KV. Use Cloudflare deployment for full functionality.

View file

@ -0,0 +1,72 @@
# Getting Started
## Prerequisites
- [Node.js](https://nodejs.org/) 18+
- A [Cloudflare account](https://dash.cloudflare.com/) (free tier works)
- A [GitHub App](https://github.com/settings/apps/new) (see [GitHub App Setup](/guide/deployment#github-app-setup))
- A Discord bot token (see [Discord Bot Setup](/guide/deployment#discord-bot-setup))
## Installation
```bash
git clone https://github.com/ReCloudStudio/WebHooker.git
cd WebHooker
npm install
```
## Local Development
### 1. Configure Secrets
Copy the example env file and fill in your secrets:
```bash
cp .env.example .dev.vars
```
Edit `.dev.vars` with your actual values:
```bash
GITHUB_WEBHOOK_SECRET=your-webhook-secret
GITHUB_APP_ID=your-app-id
GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret
DISCORD_TOKEN=your-bot-token
DISCORD_CHANNEL_ID=your-channel-id
BASE_URL=http://localhost:8787
```
::: warning
`.dev.vars` is gitignored and contains secrets. Never commit it.
:::
### 2. Start Dev Server
```bash
npm run dev
```
This starts a local Miniflare environment at `http://localhost:8787`.
### 3. Verify
```bash
curl http://localhost:8787/health
# → {"status":"ok"}
```
## Available Scripts
| Script | Description |
| ---------------------- | --------------------------------- |
| `npm run dev` | Start local dev server (wrangler) |
| `npm run deploy` | Deploy to Cloudflare |
| `npm run typecheck` | TypeScript type checking |
| `npm run lint` | ESLint |
| `npm run lint:md` | Markdownlint |
| `npm run format` | Format with Prettier |
| `npm run format:check` | Check Prettier formatting |
| `npm run docs:dev` | Start docs dev server |
| `npm run docs:build` | Build docs site |

View file

@ -0,0 +1,44 @@
# 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 routes messages to Discord channels or threads via a Durable Object-maintained Gateway connection.
## Architecture
```text
GitHub Webhook → Cloudflare Worker (Hono)
├── POST /webhook → verify → filter → format → DO (Discord Gateway) → Discord
├── GET /auth/github → OAuth flow
├── POST /api/* → user actions (Bearer token auth)
└── GET /health → status check
```
### Components
| Component | Role |
| ----------------------------------- | --------------------------------------------------------------------------------------------- |
| **Cloudflare Worker** | HTTP ingress, signature verification, event parsing, route matching |
| **Durable Object (DiscordGateway)** | Persistent WebSocket to Discord Gateway, channel cache, message dispatch with retry |
| **KV** | Token storage (`token:{userId}`), OAuth state (`state:{hex}`), route config (`config:routes`) |
### Data Flow
1. GitHub sends a webhook to `POST /webhook`
2. Worker verifies the HMAC-SHA256 signature
3. Worker parses the event type and payload
4. Routes are evaluated against filters (event, repo, actor, action, branch, keyword)
5. Matching routes trigger formatter functions that produce Discord embeds
6. Messages are dispatched to the Durable Object, which maintains the Gateway connection
7. DO sends messages to Discord via REST API with rate-limit retry
## Tech Stack
- **Runtime**: Cloudflare Workers
- **HTTP Framework**: Hono
- **Discord Gateway**: Durable Object (persistent WebSocket + channel cache)
- **Storage**: Cloudflare KV
- **Auth**: Web Crypto API (HMAC-SHA256), jose (JWT), octokit (GitHub API)
- **Language**: TypeScript
## License
MIT