mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
initial commit
This commit is contained in:
commit
512d4b01d5
55 changed files with 6430 additions and 0 deletions
130
docs/.vitepress/config.ts
Normal file
130
docs/.vitepress/config.ts
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
import { defineConfig } from "vitepress";
|
||||
|
||||
const github = "https://github.com/ReCloudStudio/WebHooker";
|
||||
|
||||
export default defineConfig({
|
||||
title: "WebHooker",
|
||||
description: "GitHub webhook to Discord dispatcher on Cloudflare Workers",
|
||||
base: "/",
|
||||
|
||||
head: [["link", { rel: "icon", type: "image/svg+xml", href: "/logo.svg" }]],
|
||||
|
||||
locales: {
|
||||
root: {
|
||||
label: "English",
|
||||
lang: "en",
|
||||
themeConfig: {
|
||||
nav: [
|
||||
{ text: "Guide", link: "/guide/introduction" },
|
||||
{ text: "API", link: "/api/overview" },
|
||||
{ text: "Events", link: "/events/supported" },
|
||||
{
|
||||
text: "Links",
|
||||
items: [
|
||||
{ text: "GitHub", link: github },
|
||||
{ text: "Changelog", link: `${github}/releases` },
|
||||
],
|
||||
},
|
||||
],
|
||||
sidebar: {
|
||||
"/guide/": [
|
||||
{
|
||||
text: "Guide",
|
||||
items: [
|
||||
{ text: "Introduction", link: "/guide/introduction" },
|
||||
{ text: "Getting Started", link: "/guide/getting-started" },
|
||||
{ text: "Configuration", link: "/guide/configuration" },
|
||||
{ text: "Deployment", link: "/guide/deployment" },
|
||||
],
|
||||
},
|
||||
],
|
||||
"/api/": [
|
||||
{
|
||||
text: "API Reference",
|
||||
items: [
|
||||
{ text: "Overview", link: "/api/overview" },
|
||||
{ text: "OAuth", link: "/api/oauth" },
|
||||
{ text: "Actions", link: "/api/actions" },
|
||||
],
|
||||
},
|
||||
],
|
||||
"/events/": [
|
||||
{
|
||||
text: "Events",
|
||||
items: [{ text: "Supported Events", link: "/events/supported" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
message: "Released under the MIT License.",
|
||||
copyright: "Copyright 2025 ReCloudStudio",
|
||||
},
|
||||
editLink: {
|
||||
pattern: `${github}/edit/main/docs/:path`,
|
||||
text: "Edit this page on GitHub",
|
||||
},
|
||||
},
|
||||
},
|
||||
zh: {
|
||||
label: "简体中文",
|
||||
lang: "zh-CN",
|
||||
themeConfig: {
|
||||
nav: [
|
||||
{ text: "指南", link: "/zh/guide/introduction" },
|
||||
{ text: "API", link: "/zh/api/overview" },
|
||||
{ text: "事件", link: "/zh/events/supported" },
|
||||
{
|
||||
text: "链接",
|
||||
items: [
|
||||
{ text: "GitHub", link: github },
|
||||
{ text: "更新日志", link: `${github}/releases` },
|
||||
],
|
||||
},
|
||||
],
|
||||
sidebar: {
|
||||
"/zh/guide/": [
|
||||
{
|
||||
text: "指南",
|
||||
items: [
|
||||
{ text: "简介", link: "/zh/guide/introduction" },
|
||||
{ text: "快速开始", link: "/zh/guide/getting-started" },
|
||||
{ text: "配置", link: "/zh/guide/configuration" },
|
||||
{ text: "部署", link: "/zh/guide/deployment" },
|
||||
],
|
||||
},
|
||||
],
|
||||
"/zh/api/": [
|
||||
{
|
||||
text: "API 参考",
|
||||
items: [
|
||||
{ text: "概览", link: "/zh/api/overview" },
|
||||
{ text: "OAuth", link: "/zh/api/oauth" },
|
||||
{ text: "用户操作", link: "/zh/api/actions" },
|
||||
],
|
||||
},
|
||||
],
|
||||
"/zh/events/": [
|
||||
{
|
||||
text: "事件",
|
||||
items: [{ text: "支持的事件", link: "/zh/events/supported" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
message: "基于 MIT 许可发布。",
|
||||
copyright: "Copyright 2025 ReCloudStudio",
|
||||
},
|
||||
editLink: {
|
||||
pattern: `${github}/edit/main/docs/:path`,
|
||||
text: "在 GitHub 上编辑此页面",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
themeConfig: {
|
||||
logo: "/logo.svg",
|
||||
socialLinks: [{ icon: "github", link: github }],
|
||||
search: { provider: "local" },
|
||||
},
|
||||
});
|
||||
111
docs/api/actions.md
Normal file
111
docs/api/actions.md
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
# Actions
|
||||
|
||||
User action endpoints allow commenting on issues, merging PRs, and adding reactions. All action endpoints require a valid Bearer token from the OAuth flow.
|
||||
|
||||
## Authentication
|
||||
|
||||
All action endpoints require the `Authorization` header:
|
||||
|
||||
```
|
||||
Authorization: Bearer <github-access-token>
|
||||
```
|
||||
|
||||
If the token is missing or invalid, the endpoint returns `401`.
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Comment on Issue
|
||||
|
||||
```
|
||||
POST /api/comment
|
||||
```
|
||||
|
||||
Creates a comment on an issue or pull request.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"owner": "org",
|
||||
"repo": "repo",
|
||||
"issueNumber": 42,
|
||||
"body": "Comment text here"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ------------- | ------ | -------- | --------------------------------- |
|
||||
| `owner` | string | Yes | Repository owner |
|
||||
| `repo` | string | Yes | Repository name |
|
||||
| `issueNumber` | number | Yes | Issue or PR number |
|
||||
| `body` | string | Yes | Comment body (Markdown supported) |
|
||||
|
||||
**Response:** `200` with GitHub API response.
|
||||
|
||||
### Merge Pull Request
|
||||
|
||||
```
|
||||
POST /api/merge
|
||||
```
|
||||
|
||||
Merges a pull request.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"owner": "org",
|
||||
"repo": "repo",
|
||||
"pullNumber": 42,
|
||||
"mergeMethod": "squash"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ------------- | ------ | -------- | ------------------------------------------------- |
|
||||
| `owner` | string | Yes | Repository owner |
|
||||
| `repo` | string | Yes | Repository name |
|
||||
| `pullNumber` | number | Yes | Pull request number |
|
||||
| `mergeMethod` | string | No | `merge`, `squash`, or `rebase` (default: `merge`) |
|
||||
|
||||
**Response:** `200` with GitHub merge response.
|
||||
|
||||
### Add Reaction
|
||||
|
||||
```
|
||||
POST /api/react
|
||||
```
|
||||
|
||||
Adds an emoji reaction to an issue or comment.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"owner": "org",
|
||||
"repo": "repo",
|
||||
"issueNumber": 42,
|
||||
"content": "rocket"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ------------- | ------ | -------- | ---------------------------- |
|
||||
| `owner` | string | Yes | Repository owner |
|
||||
| `repo` | string | Yes | Repository name |
|
||||
| `issueNumber` | number | Yes | Issue, PR, or comment number |
|
||||
| `content` | string | Yes | Reaction type (see below) |
|
||||
|
||||
**Reaction Types:**
|
||||
|
||||
`+1`, `-1`, `laugh`, `confused`, `heart`, `hooray`, `rocket`, `eyes`
|
||||
|
||||
**Response:** `200` with GitHub reaction response.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Status | Body | Cause |
|
||||
| ------ | --------------------------- | ------------------------------- |
|
||||
| `401` | `{"error": "Unauthorized"}` | Missing or invalid Bearer token |
|
||||
| `400` | `{"error": "..."}` | Invalid request body |
|
||||
| `500` | `{"error": "..."}` | GitHub API error |
|
||||
85
docs/api/oauth.md
Normal file
85
docs/api/oauth.md
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# OAuth
|
||||
|
||||
WebHooker implements GitHub OAuth2 to enable user-initiated actions (comment, merge, react).
|
||||
|
||||
## Flow
|
||||
|
||||
```text
|
||||
User → GET /auth/github → Redirect to GitHub → Authorize →
|
||||
→ GET /auth/github/callback → Exchange code for token → Store in KV
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Start OAuth
|
||||
|
||||
```
|
||||
GET /auth/github
|
||||
```
|
||||
|
||||
Redirects the user to GitHub's authorization page.
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Parameter | Description |
|
||||
| --------- | ---------------------------------- |
|
||||
| `userId` | Your application's user identifier |
|
||||
|
||||
**Response:** `302` redirect to GitHub OAuth authorize URL.
|
||||
|
||||
### OAuth Callback
|
||||
|
||||
```
|
||||
GET /auth/github/callback
|
||||
```
|
||||
|
||||
GitHub redirects here after authorization. Exchanges the code for an access token and stores it in KV.
|
||||
|
||||
**Query Parameters (from GitHub):**
|
||||
|
||||
| Parameter | Description |
|
||||
| --------- | ----------------------------------- |
|
||||
| `code` | Authorization code |
|
||||
| `state` | State parameter for CSRF protection |
|
||||
|
||||
**Response:** Redirects to your `BASE_URL` with a success/error indicator.
|
||||
|
||||
### Revoke Token
|
||||
|
||||
```
|
||||
DELETE /auth/token/:userId
|
||||
```
|
||||
|
||||
Removes the stored OAuth token for a user.
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
## Token Storage
|
||||
|
||||
Tokens are stored in KV with key pattern `token:{userId}`:
|
||||
|
||||
```json
|
||||
{
|
||||
"accessToken": "gho_...",
|
||||
"expiresAt": "2025-01-01T00:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
Tokens are automatically expired based on the `expiresAt` timestamp.
|
||||
|
||||
## Using Tokens
|
||||
|
||||
After OAuth, include the access token in the `Authorization` header for action API calls:
|
||||
|
||||
```bash
|
||||
curl -X POST https://your-worker/api/comment \
|
||||
-H "Authorization: Bearer gho_..." \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"owner": "org", "repo": "repo", "issueNumber": 1, "body": "Hello!"}'
|
||||
```
|
||||
80
docs/api/overview.md
Normal file
80
docs/api/overview.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# API Overview
|
||||
|
||||
WebHooker exposes an HTTP API via Hono on Cloudflare Workers.
|
||||
|
||||
## Base URL
|
||||
|
||||
```
|
||||
https://your-worker.workers.dev
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
| -------- | ----------------------- | -------------- | ------------------------ |
|
||||
| `GET` | `/health` | None | Health check |
|
||||
| `POST` | `/webhook` | HMAC signature | GitHub webhook ingestion |
|
||||
| `GET` | `/auth/github` | None | Start GitHub OAuth flow |
|
||||
| `GET` | `/auth/github/callback` | None | OAuth callback |
|
||||
| `DELETE` | `/auth/token/:userId` | None | Revoke user token |
|
||||
| `POST` | `/api/comment` | Bearer token | Create issue comment |
|
||||
| `POST` | `/api/merge` | Bearer token | Merge pull request |
|
||||
| `POST` | `/api/react` | Bearer token | Add reaction to issue |
|
||||
|
||||
## Health Check
|
||||
|
||||
```
|
||||
GET /health
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok"
|
||||
}
|
||||
```
|
||||
|
||||
## Webhook Ingestion
|
||||
|
||||
```
|
||||
POST /webhook
|
||||
```
|
||||
|
||||
Accepts GitHub webhook payloads. Requires valid `X-Hub-Signature-256` header.
|
||||
|
||||
**Headers:**
|
||||
|
||||
| Header | Required | Description |
|
||||
| --------------------- | -------- | --------------------- |
|
||||
| `X-Hub-Signature-256` | Yes | HMAC-SHA256 signature |
|
||||
| `X-GitHub-Event` | Yes | Event type name |
|
||||
| `X-GitHub-Delivery` | Yes | Unique delivery ID |
|
||||
|
||||
**Request Body:** GitHub webhook JSON payload (max 1MB).
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
|
||||
| Status | Body | Cause |
|
||||
| ------ | -------------------------------- | -------------------------------------- |
|
||||
| `401` | `{"error": "Invalid signature"}` | Signature verification failed |
|
||||
| `400` | `{"error": "Invalid event"}` | Missing event header or malformed body |
|
||||
| `413` | `{"error": "Request too large"}` | Body exceeds 1MB limit |
|
||||
|
||||
## Error Format
|
||||
|
||||
All error responses follow the format:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Description of the error"
|
||||
}
|
||||
```
|
||||
81
docs/contributing.md
Normal file
81
docs/contributing.md
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# Contributing
|
||||
|
||||
## Development Setup
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ReCloudStudio/WebHooker.git
|
||||
cd WebHooker
|
||||
npm install
|
||||
cp .env.example .dev.vars # Fill in secrets
|
||||
npm run dev # Start local dev server
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```text
|
||||
src/
|
||||
├── index.ts # CF Workers entry (fetch + scheduled), exports DiscordGateway DO
|
||||
├── types.ts # Env, Config, Route, Filter, WebhookEvent, FormattedMessage
|
||||
├── config.ts # Loads routes from KV (fallback to 7 defaults), builds Config from env
|
||||
├── server.ts # Hono app: /health, /webhook, mounts /auth + /
|
||||
├── webhook.ts # HMAC verify (Web Crypto), parseEvent, extractBranch, matchRoute
|
||||
├── discord.ts # Dispatch via DO RPC, initGateway (scheduled)
|
||||
├── discord-gateway.ts # Durable Object: Discord Gateway WS, heartbeat, channel cache, send
|
||||
├── formatter.ts # 23 event formatters + generic fallback
|
||||
├── github-oauth.ts # OAuth URL, callback token exchange, getUserOctokit
|
||||
├── oauth-routes.ts # GET /auth/github, callback, DELETE /token/:userId (KV state)
|
||||
├── action-routes.ts # POST /api/comment|merge|react (Bearer token auth via KV lookup)
|
||||
├── token-store.ts # KV-based token CRUD with findUserIdByToken reverse lookup
|
||||
└── log.ts # JSON console logger (info/warn/error/fatal)
|
||||
```
|
||||
|
||||
## Scripts
|
||||
|
||||
| Command | Description |
|
||||
| ---------------------- | ------------------------------- |
|
||||
| `npm run dev` | Start wrangler dev server |
|
||||
| `npm run typecheck` | TypeScript type checking |
|
||||
| `npm run lint` | ESLint (TypeScript) |
|
||||
| `npm run lint:md` | Markdownlint (Markdown) |
|
||||
| `npm run format` | Format all files with Prettier |
|
||||
| `npm run format:check` | Check Prettier formatting |
|
||||
| `npm run docs:dev` | Start VitePress docs dev server |
|
||||
| `npm run docs:build` | Build docs site |
|
||||
|
||||
## Code Style
|
||||
|
||||
- **TypeScript** with strict mode
|
||||
- **Double quotes** for strings
|
||||
- **Semicolons** required
|
||||
- **Trailing commas** in all positions
|
||||
- **100 char** print width
|
||||
- **ESLint** with `@typescript-eslint` recommended rules
|
||||
- **Prettier** for formatting
|
||||
- **Markdownlint** for markdown files
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Functional tests (requires wrangler dev running)
|
||||
bash /tmp/test-webhooker.sh
|
||||
|
||||
# Or manually
|
||||
curl http://localhost:8787/health
|
||||
```
|
||||
|
||||
## Adding a New Event Formatter
|
||||
|
||||
1. Add the event type to `GITHUB_COLORS` in `formatter.ts` (if new color needed)
|
||||
2. Add action labels to `ACTION_LABELS` (if new actions)
|
||||
3. Create a `formatEventType` function in `formatter.ts`
|
||||
4. Add the case to the `formatEvent` switch statement
|
||||
5. Update `extractBranch` in `webhook.ts` if the event has branch info
|
||||
6. Add the event to the documentation in `docs/events/supported.md`
|
||||
7. Subscribe to the event in your GitHub App settings
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
- Keep changes focused and atomic
|
||||
- Include type annotations for all function returns
|
||||
- Run `npm run typecheck && npm run lint && npm run format:check` before submitting
|
||||
- Update documentation if adding features
|
||||
67
docs/events/supported.md
Normal file
67
docs/events/supported.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# 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.
|
||||
|
||||
## Events Table
|
||||
|
||||
| Event | Description | Embed Highlights |
|
||||
| ----------------------------- | ------------------------------ | ------------------------------------------------ |
|
||||
| `push` | Code pushed to a branch | Commit list, branch, author, diff stats |
|
||||
| `pull_request` | PR opened/closed/merged/edited | PR title, branch, diff stats, labels |
|
||||
| `issues` | Issue opened/closed/edited | Issue title, labels, assignees |
|
||||
| `issue_comment` | Comment on issue or PR | Comment body, issue reference |
|
||||
| `workflow_run` | CI/CD workflow completed | Workflow status, conclusion, duration |
|
||||
| `release` | Release published/edited | Tag, body, assets, pre-release flag |
|
||||
| `create` | Branch or tag created | Ref name, ref type |
|
||||
| `delete` | Branch or tag deleted | Ref name, ref type |
|
||||
| `star` | Repository starred/unstarred | Star count, action |
|
||||
| `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_comment` | Inline code review comment | File path, line number, 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 |
|
||||
| `label` | Label created/edited/deleted | Label name, color, description |
|
||||
| `milestone` | Milestone opened/closed | Progress bar, issue counts, due date |
|
||||
| `discussion` | Discussion created/answered | Title, category, action |
|
||||
| `discussion_comment` | Comment on discussion | Comment body, discussion reference |
|
||||
| `repository` | Repo renamed/transferred | Old → new name, changes |
|
||||
| `code_scanning_alert` | Code scanning alert | Severity, rule ID, file path |
|
||||
| `dependabot_alert` | Dependabot alert | Severity, package, vulnerable range, fix version |
|
||||
|
||||
## Color Coding
|
||||
|
||||
Each event type uses a distinct color in the Discord embed:
|
||||
|
||||
| Color | Events |
|
||||
| ------------------ | -------------------------------------------------------------------- |
|
||||
| Green (`#2ea44f`) | push, issue opened, PR opened, release published, star, member added |
|
||||
| Red (`#d73a49`) | issue closed, PR closed, deployment failure, dependabot critical |
|
||||
| Purple (`#7057ff`) | PR merged, discussion created |
|
||||
| Blue (`#0366d6`) | PR review commented, issue comment, workflow run |
|
||||
| Yellow (`#dbab09`) | PR review changes requested, deployment pending |
|
||||
| Teal (`#00897b`) | check run, code scanning |
|
||||
| Orange (`#e67e22`) | label, milestone |
|
||||
| Gray (`#6a737d`) | delete, repository, member removed |
|
||||
|
||||
## Generic Fallback
|
||||
|
||||
Any event type without a dedicated formatter falls through to the generic formatter, which produces a basic embed with:
|
||||
|
||||
- Event type as title
|
||||
- Action (if available)
|
||||
- Actor login
|
||||
- Repository name
|
||||
- Raw payload as code block (truncated to 1000 chars)
|
||||
|
||||
## Filter Compatibility
|
||||
|
||||
| Filter | Works With |
|
||||
| --------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `event` | All events |
|
||||
| `repo` | All events |
|
||||
| `actor` | All events |
|
||||
| `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 |
|
||||
| `keyword` | All events (searches full payload body) |
|
||||
115
docs/guide/configuration.md
Normal file
115
docs/guide/configuration.md
Normal 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
104
docs/guide/deployment.md
Normal 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.
|
||||
72
docs/guide/getting-started.md
Normal file
72
docs/guide/getting-started.md
Normal 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 |
|
||||
44
docs/guide/introduction.md
Normal file
44
docs/guide/introduction.md
Normal 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
|
||||
29
docs/index.md
Normal file
29
docs/index.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: WebHooker
|
||||
text: GitHub Webhook → Discord
|
||||
tagline: Receive GitHub events via Cloudflare Workers, apply filters, and route formatted messages to Discord channels or threads.
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Get Started
|
||||
link: /guide/getting-started
|
||||
- theme: alt
|
||||
text: View on GitHub
|
||||
link: https://github.com/ReCloudStudio/WebHooker
|
||||
|
||||
features:
|
||||
- title: 23 Event Formatters
|
||||
details: Rich Discord embeds for push, pull_request, issues, release, workflow_run, and 18 more event types with color-coded output.
|
||||
- title: Flexible Filtering
|
||||
details: Filter by event type, repo, actor, action, branch (including PRs), and keyword (with regex support). Exclude patterns with a flag.
|
||||
- title: Cloudflare Workers
|
||||
details: Runs on Cloudflare's edge network with Durable Objects for persistent Discord Gateway connections and KV for storage.
|
||||
- title: OAuth & User Actions
|
||||
details: "GitHub App OAuth flow enables user-initiated actions: comment on issues, merge PRs, add reactions — all via Bearer token auth."
|
||||
- title: Signature Verification
|
||||
details: HMAC-SHA256 webhook signature verification using the Web Crypto API with timing-safe comparison.
|
||||
- title: Graceful Degradation
|
||||
details: Runs in webhook-only mode if Discord token is unavailable. Health endpoint for monitoring.
|
||||
---
|
||||
111
docs/zh/api/actions.md
Normal file
111
docs/zh/api/actions.md
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
# 用户操作
|
||||
|
||||
用户操作端点支持评论议题、合并 PR 和添加反应。所有操作端点都需要来自 OAuth 流程的有效 Bearer Token。
|
||||
|
||||
## 鉴权
|
||||
|
||||
所有操作端点都需要 `Authorization` 头:
|
||||
|
||||
```
|
||||
Authorization: Bearer <github-access-token>
|
||||
```
|
||||
|
||||
如果 Token 缺失或无效,端点返回 `401`。
|
||||
|
||||
## 端点
|
||||
|
||||
### 评论议题
|
||||
|
||||
```
|
||||
POST /api/comment
|
||||
```
|
||||
|
||||
在议题或拉取请求上创建评论。
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"owner": "org",
|
||||
"repo": "repo",
|
||||
"issueNumber": 42,
|
||||
"body": "评论内容"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必需 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `owner` | string | 是 | 仓库所有者 |
|
||||
| `repo` | string | 是 | 仓库名称 |
|
||||
| `issueNumber` | number | 是 | 议题或 PR 编号 |
|
||||
| `body` | string | 是 | 评论内容(支持 Markdown) |
|
||||
|
||||
**响应:** `200` 与 GitHub API 响应。
|
||||
|
||||
### 合并拉取请求
|
||||
|
||||
```
|
||||
POST /api/merge
|
||||
```
|
||||
|
||||
合并拉取请求。
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"owner": "org",
|
||||
"repo": "repo",
|
||||
"pullNumber": 42,
|
||||
"mergeMethod": "squash"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必需 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `owner` | string | 是 | 仓库所有者 |
|
||||
| `repo` | string | 是 | 仓库名称 |
|
||||
| `pullNumber` | number | 是 | 拉取请求编号 |
|
||||
| `mergeMethod` | string | 否 | `merge`、`squash` 或 `rebase`(默认:`merge`) |
|
||||
|
||||
**响应:** `200` 与 GitHub 合并响应。
|
||||
|
||||
### 添加反应
|
||||
|
||||
```
|
||||
POST /api/react
|
||||
```
|
||||
|
||||
为议题或评论添加表情反应。
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"owner": "org",
|
||||
"repo": "repo",
|
||||
"issueNumber": 42,
|
||||
"content": "rocket"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必需 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `owner` | string | 是 | 仓库所有者 |
|
||||
| `repo` | string | 是 | 仓库名称 |
|
||||
| `issueNumber` | number | 是 | 议题、PR 或评论编号 |
|
||||
| `content` | string | 是 | 反应类型(见下方) |
|
||||
|
||||
**反应类型:**
|
||||
|
||||
`+1`、`-1`、`laugh`、`confused`、`heart`、`hooray`、`rocket`、`eyes`
|
||||
|
||||
**响应:** `200` 与 GitHub 反应响应。
|
||||
|
||||
## 错误响应
|
||||
|
||||
| 状态码 | 响应体 | 原因 |
|
||||
| --- | --- | --- |
|
||||
| `401` | `{"error": "Unauthorized"}` | 缺少或无效的 Bearer Token |
|
||||
| `400` | `{"error": "..."}` | 无效的请求体 |
|
||||
| `500` | `{"error": "..."}` | GitHub API 错误 |
|
||||
85
docs/zh/api/oauth.md
Normal file
85
docs/zh/api/oauth.md
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# OAuth
|
||||
|
||||
WebHooker 实现 GitHub OAuth2 以支持用户发起的操作(评论、合并、反应)。
|
||||
|
||||
## 流程
|
||||
|
||||
```text
|
||||
用户 → GET /auth/github → 重定向到 GitHub → 授权 →
|
||||
→ GET /auth/github/callback → 交换 code 获取 Token → 存储到 KV
|
||||
```
|
||||
|
||||
## 端点
|
||||
|
||||
### 启动 OAuth
|
||||
|
||||
```
|
||||
GET /auth/github
|
||||
```
|
||||
|
||||
将用户重定向到 GitHub 的授权页面。
|
||||
|
||||
**查询参数:**
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `userId` | 你的应用用户标识符 |
|
||||
|
||||
**响应:** `302` 重定向到 GitHub OAuth 授权 URL。
|
||||
|
||||
### OAuth 回调
|
||||
|
||||
```
|
||||
GET /auth/github/callback
|
||||
```
|
||||
|
||||
GitHub 授权后重定向到此地址。将 code 交换为访问令牌并存储到 KV。
|
||||
|
||||
**查询参数(来自 GitHub):**
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `code` | 授权码 |
|
||||
| `state` | CSRF 保护的状态参数 |
|
||||
|
||||
**响应:** 重定向到你的 `BASE_URL`,附带成功/失败指示。
|
||||
|
||||
### 撤销 Token
|
||||
|
||||
```
|
||||
DELETE /auth/token/:userId
|
||||
```
|
||||
|
||||
删除用户存储的 OAuth Token。
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
## Token 存储
|
||||
|
||||
Token 以键模式 `token:{userId}` 存储在 KV 中:
|
||||
|
||||
```json
|
||||
{
|
||||
"accessToken": "gho_...",
|
||||
"expiresAt": "2025-01-01T00:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
Token 会根据 `expiresAt` 时间戳自动过期。
|
||||
|
||||
## 使用 Token
|
||||
|
||||
OAuth 完成后,在操作 API 调用的 `Authorization` 头中包含访问令牌:
|
||||
|
||||
```bash
|
||||
curl -X POST https://your-worker/api/comment \
|
||||
-H "Authorization: Bearer gho_..." \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"owner": "org", "repo": "repo", "issueNumber": 1, "body": "你好!"}'
|
||||
```
|
||||
80
docs/zh/api/overview.md
Normal file
80
docs/zh/api/overview.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# API 概览
|
||||
|
||||
WebHooker 通过 Hono 在 Cloudflare Workers 上提供 HTTP API。
|
||||
|
||||
## 基础 URL
|
||||
|
||||
```
|
||||
https://your-worker.workers.dev
|
||||
```
|
||||
|
||||
## 端点
|
||||
|
||||
| 方法 | 路径 | 鉴权 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `GET` | `/health` | 无 | 健康检查 |
|
||||
| `POST` | `/webhook` | HMAC 签名 | GitHub webhook 接入 |
|
||||
| `GET` | `/auth/github` | 无 | 启动 GitHub OAuth 流程 |
|
||||
| `GET` | `/auth/github/callback` | 无 | OAuth 回调 |
|
||||
| `DELETE` | `/auth/token/:userId` | 无 | 撤销用户 Token |
|
||||
| `POST` | `/api/comment` | Bearer Token | 创建议题评论 |
|
||||
| `POST` | `/api/merge` | Bearer Token | 合并拉取请求 |
|
||||
| `POST` | `/api/react` | Bearer Token | 添加议题反应 |
|
||||
|
||||
## 健康检查
|
||||
|
||||
```
|
||||
GET /health
|
||||
```
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok"
|
||||
}
|
||||
```
|
||||
|
||||
## Webhook 接入
|
||||
|
||||
```
|
||||
POST /webhook
|
||||
```
|
||||
|
||||
接受 GitHub webhook 载荷。需要有效的 `X-Hub-Signature-256` 头部。
|
||||
|
||||
**请求头:**
|
||||
|
||||
| 头部 | 必需 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `X-Hub-Signature-256` | 是 | HMAC-SHA256 签名 |
|
||||
| `X-GitHub-Event` | 是 | 事件类型名称 |
|
||||
| `X-GitHub-Delivery` | 是 | 唯一投递 ID |
|
||||
|
||||
**请求体:** GitHub webhook JSON 载荷(最大 1MB)。
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应:**
|
||||
|
||||
| 状态码 | 响应体 | 原因 |
|
||||
| --- | --- | --- |
|
||||
| `401` | `{"error": "Invalid signature"}` | 签名验证失败 |
|
||||
| `400` | `{"error": "Invalid event"}` | 缺少事件头或格式错误的请求体 |
|
||||
| `413` | `{"error": "Request too large"}` | 请求体超过 1MB 限制 |
|
||||
|
||||
## 错误格式
|
||||
|
||||
所有错误响应都遵循以下格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "错误的说明"
|
||||
}
|
||||
```
|
||||
81
docs/zh/contributing.md
Normal file
81
docs/zh/contributing.md
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# 贡献
|
||||
|
||||
## 开发环境设置
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ReCloudStudio/WebHooker.git
|
||||
cd WebHooker
|
||||
npm install
|
||||
cp .env.example .dev.vars # 填入密钥
|
||||
npm run dev # 启动本地开发服务器
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```text
|
||||
src/
|
||||
├── index.ts # CF Workers 入口 (fetch + scheduled),导出 DiscordGateway DO
|
||||
├── types.ts # Env、Config、Route、Filter、WebhookEvent、FormattedMessage
|
||||
├── config.ts # 从 KV 加载路由(回退到 7 条默认),从 env 构建 Config
|
||||
├── server.ts # Hono 应用: /health、/webhook,挂载 /auth + /
|
||||
├── webhook.ts # HMAC 验证 (Web Crypto)、parseEvent、extractBranch、matchRoute
|
||||
├── discord.ts # 通过 DO RPC 分发、initGateway (scheduled)
|
||||
├── discord-gateway.ts # Durable Object: Discord Gateway WS、心跳、频道缓存、发送
|
||||
├── formatter.ts # 23 种事件格式化器 + 通用回退
|
||||
├── github-oauth.ts # OAuth URL、回调 Token 交换、getUserOctokit
|
||||
├── oauth-routes.ts # GET /auth/github、回调、DELETE /token/:userId (KV 状态)
|
||||
├── action-routes.ts # POST /api/comment|merge|react (通过 KV 查找进行 Bearer Token 鉴权)
|
||||
├── token-store.ts # 基于 KV 的 Token CRUD,带 findUserIdByToken 反向查找
|
||||
└── log.ts # JSON 控制台日志 (info/warn/error/fatal)
|
||||
```
|
||||
|
||||
## 脚本
|
||||
|
||||
| 命令 | 说明 |
|
||||
| --- | --- |
|
||||
| `npm run dev` | 启动 wrangler dev 服务器 |
|
||||
| `npm run typecheck` | TypeScript 类型检查 |
|
||||
| `npm run lint` | ESLint (TypeScript) |
|
||||
| `npm run lint:md` | Markdownlint (Markdown) |
|
||||
| `npm run format` | 使用 Prettier 格式化所有文件 |
|
||||
| `npm run format:check` | 检查 Prettier 格式 |
|
||||
| `npm run docs:dev` | 启动 VitePress 文档开发服务器 |
|
||||
| `npm run docs:build` | 构建文档站点 |
|
||||
|
||||
## 代码风格
|
||||
|
||||
- **TypeScript** 严格模式
|
||||
- **双引号** 字符串
|
||||
- **分号** 必需
|
||||
- **尾逗号** 所有位置
|
||||
- **100 字符** 打印宽度
|
||||
- **ESLint** 使用 `@typescript-eslint` 推荐规则
|
||||
- **Prettier** 格式化
|
||||
- **Markdownlint** 用于 Markdown 文件
|
||||
|
||||
## 测试
|
||||
|
||||
```bash
|
||||
# 功能测试(需要正在运行的 wrangler dev)
|
||||
bash /tmp/test-webhooker.sh
|
||||
|
||||
# 或手动
|
||||
curl http://localhost:8787/health
|
||||
```
|
||||
|
||||
## 添加新事件格式化器
|
||||
|
||||
1. 将事件类型添加到 `formatter.ts` 中的 `GITHUB_COLORS`(如果需要新颜色)
|
||||
2. 将操作标签添加到 `ACTION_LABELS`(如果有新操作)
|
||||
3. 在 `formatter.ts` 中创建 `formatEventType` 函数
|
||||
4. 将 case 添加到 `formatEvent` switch 语句
|
||||
5. 如果事件包含分支信息,更新 `webhook.ts` 中的 `extractBranch`
|
||||
6. 将事件添加到 `docs/events/supported.md` 文档中
|
||||
7. 在 GitHub App 设置中订阅该事件
|
||||
|
||||
## 拉取请求指南
|
||||
|
||||
- 保持变更聚焦且原子化
|
||||
- 为所有函数返回值包含类型注解
|
||||
- 提交前运行 `npm run typecheck && npm run lint && npm run format:check`
|
||||
- 添加功能时更新文档
|
||||
67
docs/zh/events/supported.md
Normal file
67
docs/zh/events/supported.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# 支持的事件
|
||||
|
||||
WebHooker 支持 23 种 GitHub webhook 事件类型,每种都有专用的格式化器,生成丰富的 Discord 嵌入消息。不支持的事件会回退到通用格式化器。
|
||||
|
||||
## 事件表
|
||||
|
||||
| 事件 | 说明 | 嵌入亮点 |
|
||||
| --- | --- | --- |
|
||||
| `push` | 代码推送到分支 | 提交列表、分支、作者、差异统计 |
|
||||
| `pull_request` | PR 打开/关闭/合并/编辑 | PR 标题、分支、差异统计、标签 |
|
||||
| `issues` | 议题打开/关闭/编辑 | 议题标题、标签、指派人 |
|
||||
| `issue_comment` | 议题或 PR 的评论 | 评论内容、议题引用 |
|
||||
| `workflow_run` | CI/CD 工作流完成 | 工作流状态、结论、耗时 |
|
||||
| `release` | 发布创建/编辑 | 标签、内容、附件、预发布标记 |
|
||||
| `create` | 分支或标签已创建 | 引用名称、引用类型 |
|
||||
| `delete` | 分支或标签已删除 | 引用名称、引用类型 |
|
||||
| `star` | 仓库加星/取消星标 | 星标数、操作 |
|
||||
| `fork` | 仓库已复刻 | 源 → 目标复刻 |
|
||||
| `check_run` | 检查运行完成 | 状态、结论、详情 URL |
|
||||
| `pull_request_review` | PR 审查已提交 | 审查状态(已批准/需修改/已评论)、正文 |
|
||||
| `pull_request_review_comment` | 行内代码审查评论 | 文件路径、行号、评论内容 |
|
||||
| `commit_comment` | 提交的评论 | 提交 SHA、评论内容 |
|
||||
| `deployment_status` | 部署状态更新 | 环境、状态、提交引用 |
|
||||
| `member` | 协作者添加/移除 | 成员登录名、操作 |
|
||||
| `label` | 标签创建/编辑/删除 | 标签名称、颜色、描述 |
|
||||
| `milestone` | 里程碑打开/关闭 | 进度条、议题计数、截止日期 |
|
||||
| `discussion` | 讨论创建/回答 | 标题、分类、操作 |
|
||||
| `discussion_comment` | 讨论的评论 | 评论内容、讨论引用 |
|
||||
| `repository` | 仓库重命名/转移 | 旧 → 新名称、变更 |
|
||||
| `code_scanning_alert` | 代码扫描告警 | 严重程度、规则 ID、文件路径 |
|
||||
| `dependabot_alert` | Dependabot 告警 | 严重程度、包、受影响版本、修复版本 |
|
||||
|
||||
## 颜色编码
|
||||
|
||||
每种事件类型在 Discord 嵌入中使用不同的颜色:
|
||||
|
||||
| 颜色 | 事件 |
|
||||
| --- | --- |
|
||||
| 绿色 (`#2ea44f`) | push、issue 打开、PR 打开、release 发布、star、member 添加 |
|
||||
| 红色 (`#d73a49`) | issue 关闭、PR 关闭、deployment 失败、dependabot 严重 |
|
||||
| 紫色 (`#7057ff`) | PR 合并、discussion 创建 |
|
||||
| 蓝色 (`#0366d6`) | PR review 评论、issue 评论、workflow run |
|
||||
| 黄色 (`#dbab09`) | PR review 请求修改、deployment 待定 |
|
||||
| 青色 (`#00897b`) | check run、code scanning |
|
||||
| 橙色 (`#e67e22`) | label、milestone |
|
||||
| 灰色 (`#6a737d`) | delete、repository、member 移除 |
|
||||
|
||||
## 通用回退
|
||||
|
||||
没有专用格式化器的事件类型会回退到通用格式化器,生成包含以下内容的基础嵌入:
|
||||
|
||||
- 事件类型作为标题
|
||||
- 操作(如果可用)
|
||||
- 发送者登录名
|
||||
- 仓库名称
|
||||
- 原始载荷作为代码块(截断到 1000 字符)
|
||||
|
||||
## 过滤器兼容性
|
||||
|
||||
| 过滤器 | 适用事件 |
|
||||
| --- | --- |
|
||||
| `event` | 所有事件 |
|
||||
| `repo` | 所有事件 |
|
||||
| `actor` | 所有事件 |
|
||||
| `action` | 载荷中包含 `action` 字段的事件 |
|
||||
| `branch` | push、pull_request、pull_request_review、pull_request_review_comment、create、delete、workflow_run、code_scanning_alert |
|
||||
| `keyword` | 所有事件(搜索完整载荷正文) |
|
||||
115
docs/zh/guide/configuration.md
Normal file
115
docs/zh/guide/configuration.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# 配置
|
||||
|
||||
## 密钥
|
||||
|
||||
WebHooker 需要多个密钥才能运行。本地开发时存储在 `.dev.vars` 中,生产环境使用 Cloudflare Worker Secrets。
|
||||
|
||||
### 必需密钥
|
||||
|
||||
| 变量 | 说明 |
|
||||
| --- | --- |
|
||||
| `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_SECRET` | App 设置中的 OAuth 客户端密钥 |
|
||||
| `DISCORD_TOKEN` | Discord Bot Token |
|
||||
| `DISCORD_CHANNEL_ID` | 消息发送的默认 Discord 频道 ID |
|
||||
|
||||
### 可选密钥
|
||||
|
||||
| 变量 | 说明 | 默认值 |
|
||||
| --- | --- | --- |
|
||||
| `BASE_URL` | OAuth 回调的公开 URL | `http://localhost:8787` |
|
||||
|
||||
## 路由
|
||||
|
||||
路由定义了哪些事件被转发到哪些 Discord 频道。它们以 JSON 数组形式存储在 Cloudflare KV 中,键为 `config:routes`。
|
||||
|
||||
首次启动时,如果 KV 中没有配置,则使用 7 条默认路由。
|
||||
|
||||
### 路由模式
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "unique-route-id",
|
||||
"name": "可读名称",
|
||||
"enabled": true,
|
||||
"filters": [
|
||||
{ "type": "event", "match": "push" },
|
||||
{ "type": "repo", "match": "org/repo", "exclude": false }
|
||||
],
|
||||
"target": {
|
||||
"channelId": "DISCORD_CHANNEL_ID",
|
||||
"threadId": "OPTIONAL_THREAD_ID"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 默认路由
|
||||
|
||||
| ID | 事件 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `all-push` | `push` | 所有推送事件 |
|
||||
| `pull-requests` | `pull_request` | 所有 PR 活动 |
|
||||
| `issues` | `issues` | 议题打开/关闭/编辑 |
|
||||
| `issue-comments` | `issue_comment` | 议题和 PR 评论 |
|
||||
| `workflow-runs` | `workflow_run` | CI/CD 工作流完成 |
|
||||
| `releases` | `release` | 发布创建/编辑 |
|
||||
| `branch-activity` | `create`, `delete` | 分支/标签创建和删除 |
|
||||
|
||||
### 自定义路由示例
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "backend-prs",
|
||||
"name": "后端 PR",
|
||||
"enabled": true,
|
||||
"filters": [
|
||||
{ "type": "repo", "match": "myorg/backend" },
|
||||
{ "type": "event", "match": "pull_request" },
|
||||
{ "type": "actor", "match": "[bot]", "exclude": true }
|
||||
],
|
||||
"target": {
|
||||
"channelId": "1234567890",
|
||||
"threadId": "9876543210"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 过滤器类型
|
||||
|
||||
| 类型 | 匹配对象 | 示例 |
|
||||
| --- | --- | --- |
|
||||
| `event` | GitHub 事件名称 | `push`, `pull_request`, `issues` |
|
||||
| `repo` | 仓库全名 | `org/repo` |
|
||||
| `actor` | 发送者登录名 | `username`, `[bot]` |
|
||||
| `action` | 事件操作 | `opened`, `closed`, `published` |
|
||||
| `branch` | 分支名称 | `main`, `feature/*` |
|
||||
| `keyword` | 载荷正文中的文本 | `deploy`, `/fix\s+\d+/` (正则) |
|
||||
|
||||
### 过滤器行为
|
||||
|
||||
- 路由中的所有过滤器必须都匹配才触发路由(AND 逻辑)
|
||||
- 在任何过滤器上设置 `"exclude": true` 可反转匹配逻辑(NOT 逻辑)
|
||||
- `keyword` 过滤器支持正则表达式——如果正则有误,回退到子串匹配
|
||||
- `branch` 过滤器适用于 push、pull_request、create/delete、workflow_run 和 code_scanning_alert 事件
|
||||
|
||||
### 匹配值
|
||||
|
||||
过滤器接受单个字符串或字符串数组:
|
||||
|
||||
```json
|
||||
{ "type": "event", "match": "push" }
|
||||
{ "type": "event", "match": ["push", "pull_request"] }
|
||||
```
|
||||
|
||||
## KV 存储布局
|
||||
|
||||
| 键模式 | 值 | TTL |
|
||||
| --- | --- | --- |
|
||||
| `config:routes` | JSON 路由数组 | 永久 |
|
||||
| `token:{userId}` | `{ accessToken, expiresAt }` | 至过期 |
|
||||
| `state:{hex}` | `{ userId, createdAt }` | 600 秒 |
|
||||
104
docs/zh/guide/deployment.md
Normal file
104
docs/zh/guide/deployment.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# 部署
|
||||
|
||||
## Cloudflare 设置
|
||||
|
||||
### 1. 创建 KV 命名空间
|
||||
|
||||
```bash
|
||||
npx wrangler kv namespace create KV
|
||||
```
|
||||
|
||||
这会输出一个命名空间 ID。更新 `wrangler.jsonc`,填入 ID:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"kv_namespaces": [
|
||||
{
|
||||
"binding": "KV",
|
||||
"id": "your-namespace-id",
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 设置密钥
|
||||
|
||||
```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. 部署
|
||||
|
||||
```bash
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
Worker 现在可通过 `https://webhooker.<your-subdomain>.workers.dev` 访问。
|
||||
|
||||
### 4. 配置 GitHub Webhook
|
||||
|
||||
1. 进入 GitHub App 设置页面
|
||||
2. 设置 **Webhook URL** 为 `https://webhooker.<your-subdomain>.workers.dev/webhook`
|
||||
3. 设置 **Webhook secret** 与 `GITHUB_WEBHOOK_SECRET` 一致
|
||||
|
||||
## GitHub App 设置
|
||||
|
||||
### 1. 创建 App
|
||||
|
||||
1. 打开 <https://github.com/settings/apps/new>
|
||||
2. 填写:
|
||||
- **GitHub App name**: `WebHooker`(或自定义名称)
|
||||
- **Homepage URL**: 你的域名
|
||||
- **Webhook URL**: `https://your-domain/webhook`
|
||||
- **Webhook secret**: 生成并复制到 `GITHUB_WEBHOOK_SECRET`
|
||||
3. 设置权限:
|
||||
- **Repository permissions**: Contents (read)、Issues (write)、Pull requests (write)、Metadata (read)
|
||||
- **Organization permissions**: Members (read)——如果需要
|
||||
4. 订阅事件(全部 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
|
||||
5. 生成私钥 → 将内容保存到 `GITHUB_PRIVATE_KEY` 环境变量
|
||||
|
||||
### 2. 安装 App
|
||||
|
||||
1. 创建后,进入 App 设置页面
|
||||
2. 点击 "Install App" → 选择组织/用户
|
||||
3. 选择要监控的仓库
|
||||
|
||||
### 3. 配置 OAuth
|
||||
|
||||
1. 进入 App → OAuth 设置
|
||||
2. 设置 **Callback URL**: `https://your-domain/auth/github/callback`
|
||||
3. 将 Client ID 和 Client Secret 复制到环境变量
|
||||
|
||||
## Discord Bot 设置
|
||||
|
||||
1. 打开 <https://discord.com/developers/applications>
|
||||
2. 创建新应用 → 进入 Bot 部分
|
||||
3. 将 Bot Token 复制到 `DISCORD_TOKEN`
|
||||
4. 使用 `bot` 权限范围邀请 Bot 到你的服务器,并勾选 `Send Messages` 权限
|
||||
5. 将目标频道 ID 复制到 `DISCORD_CHANNEL_ID`
|
||||
|
||||
## 自定义域名(可选)
|
||||
|
||||
要使用自定义域名替代 `*.workers.dev`:
|
||||
|
||||
1. 进入 Cloudflare Worker 设置
|
||||
2. 添加自定义域名或路由
|
||||
3. 更新 `BASE_URL` 以匹配
|
||||
|
||||
## Docker
|
||||
|
||||
提供 Dockerfile 用于容器化部署(例如在反向代理后面):
|
||||
|
||||
```bash
|
||||
docker build -t webhooker .
|
||||
docker run -p 8787:8787 --env-file .env webhooker
|
||||
```
|
||||
|
||||
注意:Docker 模式下不包含 Durable Objects 和 KV。完整功能请使用 Cloudflare 部署。
|
||||
72
docs/zh/guide/getting-started.md
Normal file
72
docs/zh/guide/getting-started.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# 快速开始
|
||||
|
||||
## 前置要求
|
||||
|
||||
- [Node.js](https://nodejs.org/) 18+
|
||||
- [Cloudflare 账号](https://dash.cloudflare.com/)(免费套餐即可)
|
||||
- [GitHub App](https://github.com/settings/apps/new)(参见 [GitHub App 设置](/zh/guide/deployment#github-app-设置))
|
||||
- Discord Bot Token(参见 [Discord Bot 设置](/zh/guide/deployment#discord-bot-设置))
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ReCloudStudio/WebHooker.git
|
||||
cd WebHooker
|
||||
npm install
|
||||
```
|
||||
|
||||
## 本地开发
|
||||
|
||||
### 1. 配置密钥
|
||||
|
||||
复制示例环境变量文件并填入你的密钥:
|
||||
|
||||
```bash
|
||||
cp .env.example .dev.vars
|
||||
```
|
||||
|
||||
编辑 `.dev.vars`,填入实际值:
|
||||
|
||||
```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` 已被 gitignore,包含敏感信息,请勿提交。
|
||||
:::
|
||||
|
||||
### 2. 启动开发服务器
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
这将在 `http://localhost:8787` 启动本地 Miniflare 环境。
|
||||
|
||||
### 3. 验证
|
||||
|
||||
```bash
|
||||
curl http://localhost:8787/health
|
||||
# → {"status":"ok"}
|
||||
```
|
||||
|
||||
## 可用脚本
|
||||
|
||||
| 脚本 | 说明 |
|
||||
| --- | --- |
|
||||
| `npm run dev` | 启动本地开发服务器 (wrangler) |
|
||||
| `npm run deploy` | 部署到 Cloudflare |
|
||||
| `npm run typecheck` | TypeScript 类型检查 |
|
||||
| `npm run lint` | ESLint |
|
||||
| `npm run lint:md` | Markdownlint |
|
||||
| `npm run format` | 使用 Prettier 格式化 |
|
||||
| `npm run format:check` | 检查 Prettier 格式 |
|
||||
| `npm run docs:dev` | 启动文档开发服务器 |
|
||||
| `npm run docs:build` | 构建文档站点 |
|
||||
44
docs/zh/guide/introduction.md
Normal file
44
docs/zh/guide/introduction.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# 简介
|
||||
|
||||
WebHooker 是一个基于 Cloudflare Workers 构建的 GitHub webhook 调度器。它接收 GitHub webhook 事件,应用可配置的过滤器,将事件格式化为丰富的 Discord 嵌入消息,并通过 Durable Object 维护的 Gateway 连接将消息路由到 Discord 频道或帖子。
|
||||
|
||||
## 架构
|
||||
|
||||
```text
|
||||
GitHub Webhook → Cloudflare Worker (Hono)
|
||||
├── POST /webhook → 验证 → 过滤 → 格式化 → DO (Discord Gateway) → Discord
|
||||
├── GET /auth/github → OAuth 流程
|
||||
├── POST /api/* → 用户操作 (Bearer Token 鉴权)
|
||||
└── GET /health → 健康检查
|
||||
```
|
||||
|
||||
### 组件
|
||||
|
||||
| 组件 | 职责 |
|
||||
| --- | --- |
|
||||
| **Cloudflare Worker** | HTTP 入口、签名验证、事件解析、路由匹配 |
|
||||
| **Durable Object (DiscordGateway)** | 持久 WebSocket 连接 Discord Gateway、频道缓存、带重试的消息分发 |
|
||||
| **KV** | Token 存储 (`token:{userId}`)、OAuth 状态 (`state:{hex}`)、路由配置 (`config:routes`) |
|
||||
|
||||
### 数据流
|
||||
|
||||
1. GitHub 发送 webhook 到 `POST /webhook`
|
||||
2. Worker 验证 HMAC-SHA256 签名
|
||||
3. Worker 解析事件类型和载荷
|
||||
4. 根据过滤器评估路由(event、repo、actor、action、branch、keyword)
|
||||
5. 匹配的路由触发格式化器函数生成 Discord 嵌入消息
|
||||
6. 消息被分发到 Durable Object,由其维护 Gateway 连接
|
||||
7. DO 通过 REST API 将消息发送到 Discord,并处理速率限制重试
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **运行时**: Cloudflare Workers
|
||||
- **HTTP 框架**: Hono
|
||||
- **Discord Gateway**: Durable Object (持久 WebSocket + 频道缓存)
|
||||
- **存储**: Cloudflare KV
|
||||
- **鉴权**: Web Crypto API (HMAC-SHA256)、jose (JWT)、octokit (GitHub API)
|
||||
- **语言**: TypeScript
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT
|
||||
29
docs/zh/index.md
Normal file
29
docs/zh/index.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: WebHooker
|
||||
text: GitHub Webhook → Discord
|
||||
tagline: 通过 Cloudflare Workers 接收 GitHub 事件,应用过滤器,将格式化消息路由到 Discord 频道或帖子。
|
||||
actions:
|
||||
- theme: brand
|
||||
text: 快速开始
|
||||
link: /zh/guide/getting-started
|
||||
- theme: alt
|
||||
text: 在 GitHub 上查看
|
||||
link: https://github.com/ReCloudStudio/WebHooker
|
||||
|
||||
features:
|
||||
- title: 23 种事件格式化器
|
||||
details: 为 push、pull_request、issues、release、workflow_run 及其他 18 种事件类型提供丰富的 Discord 嵌入消息,支持颜色编码输出。
|
||||
- title: 灵活的过滤器
|
||||
details: 支持按事件类型、仓库、参与者、操作、分支(含 PR)和关键字(支持正则)过滤。支持排除模式。
|
||||
- title: Cloudflare Workers
|
||||
details: 运行在 Cloudflare 边缘网络上,使用 Durable Objects 维持持久的 Discord Gateway 连接,使用 KV 进行存储。
|
||||
- title: OAuth 与用户操作
|
||||
details: "GitHub App OAuth 流程支持用户发起操作:评论议题、合并 PR、添加反应 — 全部通过 Bearer Token 鉴权。"
|
||||
- title: 签名验证
|
||||
details: 使用 Web Crypto API 进行 HMAC-SHA256 webhook 签名验证,支持时间安全比较。
|
||||
- title: 优雅降级
|
||||
details: 当 Discord Token 不可用时以 webhook-only 模式运行。提供健康检查端点用于监控。
|
||||
---
|
||||
Loading…
Add table
Add a link
Reference in a new issue