docs: fix factual errors, fill coverage gaps and align zh mirror

This commit is contained in:
RhenCloud 2026-08-14 06:17:13 +08:00
parent 41ad1a036b
commit db49e1f01c
No known key found for this signature in database
GPG key ID: A574A617378C4E0B
35 changed files with 912 additions and 316 deletions

View file

@ -64,7 +64,7 @@ server/ # Nitro server
│ │ └── parse.ts # parse + normalize Gitea payloads to GitHub shape
│ └── custom/ # X-WebHooker-Signature (sha256= HMAC) + arbitrary JSON → `custom` events
├── formatters/ # Platform-neutral message formatters (was formatter.ts)
│ ├── index.ts # formatEvent: 29-event switch → NeutralMessage + re-exports
│ ├── index.ts # formatEvent: 28-event switch + custom → NeutralMessage + re-exports
│ ├── colors.ts # GITHUB_COLORS + WORKFLOW_CONCLUSION_EMOJI
│ ├── helpers.ts # emojiPrefix, T, buildMessage, commitLink/branchLink/tagLink
│ └── *.ts # push, pull-request, issues, comments, workflow, release, create,
@ -123,7 +123,7 @@ tests/ # bun test unit tests (webhook, formatter, discord, tel
- Issue single-use 7-day group invite links (`invite:{token}`); accepting joins as admin/viewer (never owner); `ALLOW_SELF_SIGNUP=1` creates a deterministic personal group (`u-{userId}`) on first login
- Record every admin operation (login/logout, group/route/member/invite changes) to D1 `audit_logs`; the scheduled trigger prunes entries past `AUDIT_RETENTION_DAYS`
- Mention Discord roles on route trigger: route-level `discordRoleIds` are rendered as `<@&id>` into the Discord message `content` (Telegram targets ignore the field)
- Format 29 event types as platform-neutral messages (Discord embeds + Telegram HTML)
- Format 28 GitHub/Gitea event types plus `custom` webhooks as platform-neutral messages (Discord embeds + Telegram HTML)
- Route messages to Discord channels/threads and Telegram chats/topics via REST
- Edit already-sent messages in place for `workflow_run` / `check_run` progress (stable `updateKey`, KV `msg:*` tracking)
- Record every dispatch attempt to D1 `send_logs` (route id, event, target, ok/error, duration, error code)
@ -215,7 +215,7 @@ Full list of secrets used: `GITHUB_WEBHOOK_SECRET`, `GITEA_WEBHOOK_SECRET`,
(PKCS#8 PEM), `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `DISCORD_TOKEN`,
`DISCORD_PUBLIC_KEY`, `TELEGRAM_TOKEN`, `TELEGRAM_WEBHOOK_SECRET`, `ADMIN_USER_IDS`,
plus optional `BASE_URL`, `DISCORD_APPLICATION_ID`, `TELEGRAM_RICH_HEADER_HOST`,
`DOCS_URL`, `GITHUB_REPO_URL`, `LEGAL_CONTACT`. See `.env.example` and `docs/guide/configuration.md`.
`NUXT_PUBLIC_DOCS_URL`, `NUXT_PUBLIC_REPO_URL`, `NUXT_PUBLIC_LEGAL_CONTACT`. See `.env.example` and `docs/guide/configuration.md`.
## Notes

View file

@ -54,11 +54,11 @@ bunx wrangler dev # Start local dev server
### Secrets (`.dev.vars` for local, Worker Secrets for production)
| Variable | Description |
| --------------------------- | ---------------------------------------------------------------------------------------------- |
|-----------------------------|------------------------------------------------------------------------------------------------|
| `GITHUB_WEBHOOK_SECRET` | Webhook secret from GitHub |
| `GITEA_WEBHOOK_SECRET` | Webhook secret from Gitea (required only to receive Gitea webhooks) |
| `GITHUB_APP_ID` | GitHub App ID (not currently used by the code; kept for compatibility) |
| `GITHUB_PRIVATE_KEY` | App private key (PKCS#8 PEM; not currently used by the code; kept for compatibility) |
| `GITHUB_APP_ID` | GitHub App ID (used by the App install flow to resolve the installing account) |
| `GITHUB_PRIVATE_KEY` | App private key (PKCS#8 PEM; used by the App install flow; optional) |
| `GITHUB_CLIENT_ID` | OAuth client ID |
| `GITHUB_CLIENT_SECRET` | OAuth client secret |
| `DISCORD_TOKEN` | Bot token |
@ -71,9 +71,9 @@ bunx wrangler dev # Start local dev server
| `ADMIN_USER_IDS` | Comma-separated GitHub user IDs (or logins) allowed to access `/admin` |
| `ALLOW_SELF_SIGNUP` | `1` to give access-less GitHub users a personal group on first login (default off) |
| `AUDIT_RETENTION_DAYS` | Audit-log retention in days for the scheduled cleanup (default 90) |
| `DOCS_URL` | Optional docs site URL used by the landing page |
| `GITHUB_REPO_URL` | Optional GitHub repo URL used by the landing page |
| `LEGAL_CONTACT` | Optional contact shown on `/terms` and `/privacy` |
| `NUXT_PUBLIC_DOCS_URL` | Optional docs site URL used by the landing page |
| `NUXT_PUBLIC_REPO_URL` | Optional GitHub repo URL used by the landing page |
| `NUXT_PUBLIC_LEGAL_CONTACT` | Optional contact shown on `/terms` and `/privacy` |
### Routes
@ -131,7 +131,7 @@ See `config.example.yaml` for full syntax examples.
### Filter Types
| Type | Matches | Notes |
| --------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|-----------|----------------------------------------|--------------------------------------------------------------------------------------------------------------------|
| `event` | `push`, `pull_request`, `issues`, etc. | GitHub event name |
| `repo` | `org/repo` full name | |
| `actor` | Sender login | |
@ -195,7 +195,7 @@ Set `exclude: true` to invert any filter. See the [Filter Tutorial](https://webh
- **Organization permissions**: Members (read) — if needed
4. Subscribe to events:
- 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 — `GITHUB_PRIVATE_KEY` is currently unused by the code (only client ID/secret power the OAuth flow), so it is optional; store it if you later enable GitHub App authentication.
5. Generate private key — optional; `GITHUB_APP_ID` + `GITHUB_PRIVATE_KEY` are only used by the [App install flow](/guide/deployment#github-app-setup) to resolve the installing account's login on the post-install page.
### 2. Install App
@ -218,7 +218,7 @@ Create a bot at <https://discord.com/developers/applications>, copy its token to
Add the bot to your server with the `bot` scope and the following permissions:
| Permission | Value | Why |
| ------------------------ | -------------- | ----------------------------------------------- |
|--------------------------|----------------|-------------------------------------------------|
| View Channels | `1024` | See the target channel to post messages |
| Send Messages | `2048` | Send embeds/messages to channels |
| Send Messages in Threads | `274877906944` | Send to threads when a route targets `threadId` |
@ -239,40 +239,14 @@ The bot never connects to the Discord Gateway, so it shows as **offline** — me
### Bot Commands (comment on GitHub as yourself)
The bot registers native **slash** and **message context-menu** commands, synced by the scheduled trigger (every 5 minutes): per-guild for instant availability, and globally (24h dedup, ~1h propagation). Comments are posted using **your own** linked GitHub account (OAuth), and permission is delegated to GitHub — if GitHub rejects the action (e.g. editing someone else's comment) the bot tells you so. All replies are ephemeral (only you see them).
**1. Link your account** (once):
The bot registers native **slash** and **message context-menu** commands, synced by the scheduled trigger (every 5 minutes): per-guild for instant availability, and globally (24h dedup, ~1h propagation). After `/gh login` you can comment on issues/PRs as yourself, edit/delete your comments, and merge/close PRs via buttons — all replies are ephemeral and GitHub enforces permission.
```
/gh login → returns an ephemeral link to authorize your GitHub account
/gh logout → unlink your GitHub account
/gh login /gh logout
/gh comment add|edit|del link:<url> (or right-click a notification → Apps → GitHub: 添加/编辑/删除评论)
```
**2. Add / edit / delete a comment** — two equivalent ways:
- **Right-click a notification** (recommended): right-click a bot-issued issue / PR / comment notification → **Apps****GitHub: 添加评论 / 编辑评论 / 删除评论**. The target is auto-extracted from the notification embed; no link needed.
- **Slash command with a link**:
```
/gh comment add link:<issue or PR url> e.g. https://github.com/owner/repo/issues/123
/gh comment edit link:<comment url> url must contain #issuecomment-<id>
/gh comment del link:<comment url> url must contain #issuecomment-<id>
```
For `edit` / `del`, copy the specific comment link on GitHub (comment ⋯ menu → **Copy link**). `add` / `edit` open a modal to enter/adjust the comment body (prefilled for edit).
**3. Merge / close a PR** — notifications for open PRs include **合并 / 关闭** (merge/close) buttons:
- Clicking a button merges (squash) or closes the PR as **your linked** GitHub account; GitHub enforces permission. On success the buttons are removed from the notification and the result is shown in an ephemeral reply.
**Requirements:**
| Item | How |
| ------------ | --------------------------------------------------------------------- |
| Public key | `DISCORD_PUBLIC_KEY` set + Interactions Endpoint URL configured |
| Invite scope | Bot invited with `applications.commands` (see invite URL above) |
| OAuth | `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` and `BASE_URL` configured |
| User linked | Each user runs `/gh login` first |
See the full reference in the [Bot Commands guide](https://webhooker.docs.worldexecute.me/guide/commands).
## Telegram Bot Setup
@ -281,12 +255,7 @@ The bot registers native **slash** and **message context-menu** commands, synced
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
In Telegram, `/gh` commands work by **replying to a notification message**: `/gh login`, `/gh logout`, `/gh comment <text>`, `/gh merge`, `/gh close`. See the [Bot Commands guide](https://webhooker.docs.worldexecute.me/guide/commands).
Avatars are rendered as a link-preview card using the built-in `GET /api/richheader` (overridable with `TELEGRAM_RICH_HEADER_HOST`).
@ -327,7 +296,7 @@ bun test # Unit tests
## Supported Events
| Event | Formatter |
| ----------------------------- | ------------------------------------------------------- |
|-------------------------------|---------------------------------------------------------|
| `push` | Commit list, branch, author |
| `pull_request` | PR title, branch, diff stats |
| `issues` | Issue title, labels, assignees |

View file

@ -54,11 +54,11 @@ bunx wrangler dev # 启动本地开发服务器
### 密钥(本地用 `.dev.vars`,生产用 Worker Secrets
| 变量 | 说明 |
| --------------------------- | --------------------------------------------------------------------------- |
|-----------------------------|-----------------------------------------------------------------------------|
| `GITHUB_WEBHOOK_SECRET` | GitHub webhook 密钥 |
| `GITEA_WEBHOOK_SECRET` | Gitea webhook 密钥(仅接收 Gitea webhook 时需要) |
| `GITHUB_APP_ID` | GitHub App ID当前代码未使用,为兼容保留) |
| `GITHUB_PRIVATE_KEY` | App 私钥PKCS#8 PEM当前代码未使用,为兼容保留) |
| `GITHUB_APP_ID` | GitHub App ID用于 App 安装流程解析安装所属账号) |
| `GITHUB_PRIVATE_KEY` | App 私钥PKCS#8 PEM用于 App 安装流程,可选) |
| `GITHUB_CLIENT_ID` | OAuth Client ID |
| `GITHUB_CLIENT_SECRET` | OAuth Client Secret |
| `DISCORD_TOKEN` | 机器人 token |
@ -71,9 +71,9 @@ bunx wrangler dev # 启动本地开发服务器
| `ADMIN_USER_IDS` | 允许访问 `/admin` 的 GitHub 用户 ID或登录名逗号分隔 |
| `ALLOW_SELF_SIGNUP` | 设为 `1` 时,无权限的 GitHub 用户首次登录自动获得个人分组(默认关闭) |
| `AUDIT_RETENTION_DAYS` | 定时清理时审计日志的保留天数(默认 90 |
| `DOCS_URL` | 可选;落地页使用的文档站点 URL |
| `GITHUB_REPO_URL` | 可选;落地页使用的 GitHub 仓库 URL |
| `LEGAL_CONTACT` | 可选;`/terms``/privacy` 页面展示的联系方式 |
| `NUXT_PUBLIC_DOCS_URL` | 可选;落地页使用的文档站点 URL |
| `NUXT_PUBLIC_REPO_URL` | 可选;落地页使用的 GitHub 仓库 URL |
| `NUXT_PUBLIC_LEGAL_CONTACT` | 可选;`/terms``/privacy` 页面展示的联系方式 |
### 路由配置
@ -131,7 +131,7 @@ bunx wrangler dev # 启动本地开发服务器
### 过滤器类型
| 类型 | 匹配内容 | 备注 |
| --------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|-----------|-------------------------------------|---------------------------------------------------------------------------------------------------------------|
| `event` | `push``pull_request``issues` 等 | GitHub 事件名 |
| `repo` | `org/repo` 全名 | |
| `actor` | 发送者登录名 | |
@ -194,7 +194,7 @@ bunx wrangler dev # 启动本地开发服务器
- **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) — 如需要
4. 订阅事件Push、Pull request、Issues、Issue comment、Workflow run、Workflow job、Status、Deployment、Deployment status、Ping、Release、Create、Delete、Star、Fork、Check run、Check suite、Pull request review、Pull request review comment、Commit comment、Member、Label、Milestone、Discussion、Discussion comment、Repository、Code scanning alert、Dependabot alert
5. 生成私钥 — `GITHUB_PRIVATE_KEY` 当前未被代码使用OAuth 流程只用到 Client ID/Secret因此为可选若日后启用 GitHub App 认证可再配置
5. 生成私钥 — 可选;`GITHUB_APP_ID` + `GITHUB_PRIVATE_KEY` 仅用于 [App 安装流程](/zh/guide/deployment#github-app-设置)在安装后页面解析安装所属账号的登录名
### 2. 安装 App
@ -217,7 +217,7 @@ bunx wrangler dev # 启动本地开发服务器
使用 `bot` scope 将机器人加入服务器,需要以下权限:
| 权限 | 数值 | 用途 |
| ------------------------------------------- | -------------- | ---------------------------------------- |
|---------------------------------------------|----------------|------------------------------------------|
| 查看频道 (View Channels) | `1024` | 查看目标频道以发送消息 |
| 发送消息 (Send Messages) | `2048` | 向频道发送 embed/消息 |
| 在线程中发送消息 (Send Messages in Threads) | `274877906944` | 当路由配置了 `threadId` 时向线程发送消息 |
@ -238,40 +238,14 @@ bot 从不连接 Discord Gateway因此显示为**离线**——消息推送
### Bot 指令(以本人身份评论 GitHub
bot 通过定时任务(每 5 分钟)同步注册原生的**斜杠命令**与**消息右键菜单命令**按服务器注册以获得即时可用性并全局注册24h 去重,约 1 小时传播)。评论以**你本人**绑定的 GitHub 账号OAuth发出权限交由 GitHub 判定——若 GitHub 拒绝例如去修改他人评论bot 会提示你无权限。所有回复均为 ephemeral仅你可见
**1. 绑定账号**(一次即可):
bot 通过定时任务(每 5 分钟)同步注册原生的**斜杠命令**与**消息右键菜单命令**按服务器注册以获得即时可用性并全局注册24h 去重,约 1 小时传播)。执行 `/gh login` 后即可评论 issue/PR、编辑/删除自己的评论,并通过按钮合并/关闭 PR——所有回复均为临时消息仅你可见权限交由 GitHub 判定。
```
/gh login → 返回一个 ephemeral 授权链接,用于绑定你的 GitHub 账号
/gh logout → 解除绑定
/gh login /gh logout
/gh comment add|edit|del link:<链接> (或右键通知 → 应用 → GitHub: 添加/编辑/删除评论)
```
**2. 添加 / 编辑 / 删除评论** —— 两种等价方式:
- **右键点击通知**(推荐):右键一条 bot 推送的 issue / PR / 评论通知 → **应用Apps****GitHub: 添加评论 / 编辑评论 / 删除评论**。目标会从通知 embed 中自动提取,无需粘贴链接。
- **斜杠命令 + 链接**
```
/gh comment add link:<issue PR 链接> 例如 https://github.com/owner/repo/issues/123
/gh comment edit link:<评论链接> 链接需包含 #issuecomment-<id>
/gh comment del link:<评论链接> 链接需包含 #issuecomment-<id>
```
`edit` / `del` 需要具体的评论链接(在 GitHub 上:评论 ⋯ 菜单 → **Copy link**)。`add` / `edit` 会弹出 modal 让你输入 / 修改评论内容(编辑时预填原文)。
**3. 合并 / 关闭 PR** —— 打开状态的 PR 通知会附带 **合并 / 关闭** 按钮:
- 点击按钮后以**你绑定**的 GitHub 账号执行合并squash或关闭操作权限交由 GitHub 判定。操作成功后通知上的按钮会被移除,结果以 ephemeral 回复显示。
**要求:**
| 项目 | 说明 |
| ---------- | ---------------------------------------------------------------- |
| Public Key | 已配置 `DISCORD_PUBLIC_KEY` 且已设置 Interactions Endpoint URL |
| 邀请 scope | 邀请时带上 `applications.commands`(见上方邀请链接) |
| OAuth | 已配置 `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET``BASE_URL` |
| 用户绑定 | 每个用户先执行 `/gh login` |
完整参考见[机器人命令指南](https://webhooker.docs.worldexecute.me/zh/guide/commands)。
## Telegram 机器人配置
@ -280,12 +254,7 @@ bot 通过定时任务(每 5 分钟)同步注册原生的**斜杠命令**与
3. Worker 会在定时任务中自动同步 webhook`setWebhook` 指向 `{BASE_URL}/telegram/webhook`),因此无需手动调用 `setWebhook`——只需确保 `BASE_URL` 已设置。
4. 将机器人加入群组(或启用话题),在路由配置中用 `chatId` / `topicId` 指定目标。
在 Telegram 中,`/gh` 命令通过在通知消息上**回复**来使用:
- `/gh login` — 绑定你的 GitHub 账号(返回 OAuth 链接)
- `/gh logout` — 解除绑定
- `/gh comment <内容>` — 回复一条 issue/PR 通知,以本人身份评论
- `/gh merge` / `/gh close` — 回复一条 PR 通知,合并/关闭该 PR
在 Telegram 中,`/gh` 命令通过在通知消息上**回复**来使用:`/gh login``/gh logout``/gh comment <文本>``/gh merge``/gh close`。见[机器人命令指南](https://webhooker.docs.worldexecute.me/zh/guide/commands)。
头像使用内置 `GET /api/richheader` 渲染为链接预览卡片(可用 `TELEGRAM_RICH_HEADER_HOST` 覆盖)。
@ -326,7 +295,7 @@ bun test # 单元测试
## 支持的事件
| 事件 | 格式化内容 |
| ----------------------------- | ------------------------------------ |
|-------------------------------|--------------------------------------|
| `push` | 提交列表、分支、作者 |
| `pull_request` | PR 标题、分支、差异统计 |
| `issues` | Issue 标题、标签、指派人 |

174
docs-plan.md Normal file
View file

@ -0,0 +1,174 @@
# WebHooker 文档评估与优化计划
> 生成日期2026-08-13
> 范围:`docs/`VitePressen+zh`README.md` / `README.zh.md``AGENTS.md``config.example.yaml``.env.example`
---
## 一、现状总览
| 文档 | 规模 | 状态 |
|---|---|---|
| `docs/`VitePressen+zh 镜像) | 11 页 ×2约 75KB | 结构完整但存在事实错误、覆盖缺失、信息架构混乱 |
| `README.md` / `README.zh.md` | 363 行 | 与 docs 大量重复secrets、GitHub App 设置、部署),已出现漂移 |
| `AGENTS.md` | 222 行 | 基本同步,个别过时 |
| `config.example.yaml` / `.env.example` | — | 良好,但 README/AGENTS 中 `DOCS_URL` 等变量名与代码不符(`.env.example` 正确) |
---
## 二、事实错误(需修复)
1. **`GITHUB_APP_ID` / `GITHUB_PRIVATE_KEY` "未被代码使用"说法过时**
- `server/lib/github/oauth.ts:27-69` 已用它们生成 App JWT 查询安装账号install 绑定流程)
- 过时位置configuration.md:18-21、README.md:60-61/198、deployment.md:40-44、getting-started.md:40-42en/zh 共 8 处)
- **deployment.md 内部自相矛盾**40-44 行说"无需设置"125-127 行又要求生成私钥保存到 `GITHUB_PRIVATE_KEY`
2. **`DOCS_URL` / `GITHUB_REPO_URL` / `LEGAL_CONTACT` 变量名错误**
- README.md:74-76 与 AGENTS.md:218 使用旧名
- 代码实际读取 `NUXT_PUBLIC_DOCS_URL` / `NUXT_PUBLIC_REPO_URL` / `NUXT_PUBLIC_LEGAL_CONTACT`nuxt.config.ts + runtimeConfig`.env.example` 正确)
3. **`DELETE /auth/token/:userId` 鉴权标注 "None"**api/overview.md:23
- 实际要求 admin sessionoauth.ts:371-377无 session 返回 401
4. **Generic fallback 描述过时**events/supported.md:60en/zh
- 声称"原始 payload 代码块(截断 1000 字符)"
- 实际 `server/lib/formatters/generic.ts` 只输出 title + color + author无 payload 代码块
5. **事件数量口径不一**
- "28"docs/index、configuration、deployment、READMEvs "29"contributing.md、AGENTS.md:126
- 实际 `server/lib/formatters/index.ts` switch 有 29 个 case`custom`
- 建议统一为:"28 种 GitHub/Gitea 事件 + `custom`"
6. **events/supported.md 事件表缺 `custom` 行**en/zh 都缺)
7. **`src/formatters/colors.ts` 旧路径残留**events/supported.md:40en/zh应为 `server/lib/formatters/colors.ts`
8. **api/overview.md 端点表缺 9 个端点**
- `POST/GET /admin/api/groups/:id/invites``DELETE /admin/api/invites/:token`
- `GET /admin/api/audit``GET /admin/api/groups/:id/webhook``POST .../webhook/regenerate``DELETE .../webhook`
- `GET /admin/logout``GET /admin/invite?token=`
- configuration.md 里反而齐全——两处清单已漂移
9. **providers 校验接受不存在的 `gitlab` 值**admin.ts:288
- 错误信息为 `"github" | "gitea" | "gitlab"`,但无 gitlab provider 实现
- 需决定:删掉(与文档 github/gitea 对齐)或保留(为未来扩展)
---
## 三、覆盖缺失
1. **KV 布局表缺 `tenant:{groupId}`**configuration.md:326-343en/zh 都缺)
- 租户态 dedup key `delivery:{groupId}:{id}` 只散见正文
2. **`NUXT_PUBLIC_DOCS_URL` / `NUXT_PUBLIC_REPO_URL` / `NUXT_PUBLIC_LEGAL_CONTACT` 未收录**
- configuration.md Secrets 表en/zh都未记录这三个变量
3. **bot 命令文档碎片化**
- Discord `/gh` 命令只在 READMEBot Commands 段落)
- Telegram `/gh` 命令只在 deployment.md
- 无独立页面sidebar 无入口
4. **无独立页面/章节**
- 调度任务cron `*/5`discord-sync / telegram-sync / audit-prune
- i18n 消息语言与 `i18n:*` KV 覆盖机制
- 消息格式规范(只在 AGENTS.md属开发内部文档
- 发送日志send_logs字段与错误码说明
- FAQ / 故障排查
5. **次要缺失**
- 路由上限 200 / 分组上限 100admin.ts:76,257
- `X-Gitea-Delivery` 头参与去重providers/gitea/parse.ts:60
- install 选择页需要登录 sessionoauth.ts:153-158
- logTarget 摘要消息只列前 10 条 route×target + "+N"dispatch.ts:88-100
---
## 四、结构问题("杂乱"的主因)
1. **configuration.md 是 356 行巨型文档**
- 密钥、提供方、Web UI、端点、自定义 webhook、租户隔离、路由、分组、角色、过滤器、KV/D1 布局全挤一页
2. **API 参考混乱**
- admin API 同时在 configuration.mdWeb UI→端点表、api/overview.md、README 出现三份,已开始漂移
3. **sidebar 信息架构**
- 只有 Guide / API / Events 三类
- `docs/contributing.md` 不在 sidebar孤儿页
- 无 Bot 命令、无 FAQ、无故障排查入口
4. **README 与 docs 严重重复**
- secrets 表、GitHub App 设置、Discord/Telegram bot 设置、部署步骤在 README 和 docs/deployment.md 各写一遍
5. **docs 配置引用不存在的 `logo.svg`**
- `docs/.vitepress/config.ts` 引用 `/logo.svg`favicon + 主题 logo文件不存在404
6. **footer copyright 仍写 2025**(当前 2026
---
## 五、中英一致性zh 滞后)
| 严重度 | 文件 | 差异 |
|---|---|---|
| 高 | guide/introduction.md | zh 技术栈仍是"Nux3 静态 SPA"en 已为"Nuxt 4 (Vue 3 + Tailwind CSS v3)" |
| 高 | guide/getting-started.md | zh 脚本表缺 `bun run build``bun test` 两行;`bun run dev` 描述不一致wrangler vs Nuxt HMR |
| 中 | api/overview.md | zh 漏"or manage a group"准入条件;漏"空过滤器仅 fallback 路由允许" |
| 低 | guide/configuration.md | 可选密钥表行序不同keyword 示例 zh 多 `*release-*`"manage everything" 译作"管理路由" |
| 低 | guide/filters.md | zh 一处"`/` 包裹"应为"`//` 包裹"(同文件其他处正确) |
| 低 | index.md | 2 处 feature 描述中文略精简(未列签名头部、未列 slash commands and buttons |
完全一致的文件对guide/deployment.md、api/actions.md、api/oauth.md、events/supported.md、contributing.md。
---
## 六、建议的重构方案
### A 阶段:事实修正(低风险,必做)
- 修复"二"中全部 9 项 + "三"的 KV/变量 2 项
- 修复中英"高/中"级差异
- logo.svg补文件或从 config 移除引用、footer 年份
- 统一事件数量口径28 + custom
### B 阶段:信息架构重构
建议新结构:
```text
指南 Guide
Introduction / Getting Started / Deployment保留现状
核心概念(从 configuration.md 拆出):
Routes & Targets
Groups & Access Control角色/邀请/自助注册)
Webhook Ingress & Tenancy全局/分组端点、custom、App 隔离)
消息与命令(新):
Discord /gh Commands合并 README 的 Bot Commands 段落)
Telegram /gh Commands
Message Format & i18n新页标题规范、emoji 开关、语言覆盖)
运维(新):
Scheduled Tasks
Storage LayoutKV / D1 布局)
Send Logs & Audit Logs
FAQ & 故障排查
参考 Reference
API拆分 Public API 与 Admin API与 configuration 去重)
Supported Events补 custom 行、修 generic 描述)
配置(单一权威来源)→ configuration.md 瘦身为"完整参考"
README → 精简为 features + quick start + 命令摘要 + 指向 docs 的链接
```
### C 阶段README 瘦身
- README 保留简介、features、quick start、/gh 命令摘要、部署速览、License
- 移除与 docs 重复的完整表格secrets、GitHub App 设置、bot 设置细节),改为链接指向 docs
---
## 七、待用户确认的决策点
1. **范围**:只做 A还是 A+B重构结构或 A+B+C含 README 瘦身)?
2. **configuration.md**:按上述拆分成多个页面(改动大、导航清晰),还是保留单页只做内容修正?
3. **`gitlab` 校验值**:代码放行但无实现——删掉(与文档对齐)还是保留(为未来扩展)?
4. **新页面**Bot 命令页、i18n/消息格式页、FAQ 页是否都需要?

View file

@ -35,7 +35,23 @@ export default defineConfig({
{ text: "Getting Started", link: "/guide/getting-started" },
{ text: "Configuration", link: "/guide/configuration" },
{ text: "Filter Tutorial", link: "/guide/filters" },
],
},
{
text: "Messaging",
items: [
{ text: "Bot Commands", link: "/guide/commands" },
{ text: "Message Format", link: "/guide/message-format" },
{ text: "Message Language (i18n)", link: "/guide/i18n" },
],
},
{
text: "Operations",
items: [
{ text: "Logs", link: "/guide/logs" },
{ text: "Scheduled Tasks", link: "/guide/tasks" },
{ text: "Deployment", link: "/guide/deployment" },
{ text: "FAQ", link: "/guide/faq" },
],
},
],
@ -55,10 +71,16 @@ export default defineConfig({
items: [{ text: "Supported Events", link: "/events/supported" }],
},
],
"/contributing": [
{
text: "Development",
items: [{ text: "Contributing", link: "/contributing" }],
},
],
},
footer: {
message: "Released under the MIT License.",
copyright: "Copyright 2025 ReCloudStudio",
copyright: "Copyright 2026 ReCloudStudio",
},
editLink: {
pattern: `${github}/edit/main/docs/:path`,
@ -91,7 +113,23 @@ export default defineConfig({
{ text: "快速开始", link: "/zh/guide/getting-started" },
{ text: "配置", link: "/zh/guide/configuration" },
{ text: "过滤器教程", link: "/zh/guide/filters" },
],
},
{
text: "消息",
items: [
{ text: "机器人命令", link: "/zh/guide/commands" },
{ text: "消息格式", link: "/zh/guide/message-format" },
{ text: "消息语言 (i18n)", link: "/zh/guide/i18n" },
],
},
{
text: "运维",
items: [
{ text: "日志", link: "/zh/guide/logs" },
{ text: "定时任务", link: "/zh/guide/tasks" },
{ text: "部署", link: "/zh/guide/deployment" },
{ text: "常见问题", link: "/zh/guide/faq" },
],
},
],
@ -111,10 +149,16 @@ export default defineConfig({
items: [{ text: "支持的事件", link: "/zh/events/supported" }],
},
],
"/zh/contributing": [
{
text: "开发",
items: [{ text: "贡献指南", link: "/zh/contributing" }],
},
],
},
footer: {
message: "基于 MIT 许可发布。",
copyright: "Copyright 2025 ReCloudStudio",
copyright: "Copyright 2026 ReCloudStudio",
},
editLink: {
pattern: `${github}/edit/main/docs/:path`,

View file

@ -10,32 +10,44 @@ https://your-worker.workers.dev
## Endpoints
| Method | Path | Auth | Description |
| -------- | ------------------------------ | ----------------- | ------------------------------------------------------------------ |
| `GET` | `/health` | None | Health check |
| `POST` | `/webhook` | HMAC signature | GitHub / Gitea / custom webhook ingestion (provider auto-detected) |
| `POST` | `/webhook/:groupId` | Per-group secret | Per-group webhook ingress (only that group's routes fire) |
| `POST` | `/discord/interactions` | Ed25519 signature | Discord interactions (slash commands, buttons, modals) |
| `POST` | `/telegram/webhook` | Secret token | Telegram updates (bot `/gh` commands) |
| `GET` | `/api/richheader` | None | Open Graph page for the Telegram avatar link-preview card |
| `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/close` | Bearer token | Close pull request |
| `POST` | `/api/react` | Bearer token | Add reaction to issue |
| `GET` | `/admin` | Admin session | Config console UI |
| `GET` | `/admin/api/routes` | Admin session | List routes |
| `PUT` | `/admin/api/routes` | Admin session | Replace routes |
| `GET` | `/admin/api/groups` | Admin session | List groups (scoped) |
| `PUT` | `/admin/api/groups` | Admin session | Replace groups (super) |
| `GET` | `/admin/api/groups/:id/routes` | Admin session | List a group's routes |
| `PUT` | `/admin/api/groups/:id/routes` | Admin session | Replace a group's routes |
| `PUT` | `/admin/api/groups/:id/rename` | Admin session | Rename a group (owner); routes/secret/invites follow |
| `GET` | `/admin/api/me` | Admin session | Current session info |
| `GET` | `/admin/api/logs` | Admin session | Send logs (scoped) |
| `GET` | `/admin/api/logs/:id` | Admin session | Single send-log entry (scoped) |
| Method | Path | Auth | Description |
|----------|--------------------------------------------|-------------------|--------------------------------------------------------------------|
| `GET` | `/health` | None | Health check |
| `POST` | `/webhook` | HMAC signature | GitHub / Gitea / custom webhook ingestion (provider auto-detected) |
| `POST` | `/webhook/:groupId` | Per-group secret | Per-group webhook ingress (only that group's routes fire) |
| `POST` | `/discord/interactions` | Ed25519 signature | Discord interactions (slash commands, buttons, modals) |
| `POST` | `/telegram/webhook` | Secret token | Telegram updates (bot `/gh` commands) |
| `GET` | `/api/richheader` | None | Open Graph page for the Telegram avatar link-preview card |
| `GET` | `/auth/github` | None | Start GitHub OAuth flow |
| `GET` | `/auth/github/callback` | None | OAuth callback |
| `GET` | `/auth/github/install` | Admin session | Post-install choice page: bind the installation to a group |
| `POST` | `/auth/github/install/bind` | Admin session | Provision the chosen installation binding |
| `DELETE` | `/auth/token/:userId` | Admin session | Revoke user token |
| `POST` | `/api/comment` | Bearer token | Create issue comment |
| `POST` | `/api/merge` | Bearer token | Merge pull request |
| `POST` | `/api/close` | Bearer token | Close pull request |
| `POST` | `/api/react` | Bearer token | Add reaction to issue |
| `GET` | `/admin` | Admin session | Config console UI |
| `GET` | `/admin/login` | None | Start admin sign-in (GitHub OAuth) |
| `GET` | `/admin/logout` | Admin session | Sign out and destroy the session |
| `GET` | `/admin/invite` | Admin session | Accept a group invite (browser page, `?token=…`) |
| `GET` | `/admin/api/me` | Admin session | Current session, scope, groups, and roles |
| `GET` | `/admin/api/routes` | Admin session | List routes (scoped to access) |
| `PUT` | `/admin/api/routes` | Admin session | Replace routes (owner/admin per group) |
| `GET` | `/admin/api/groups` | Admin session | List groups + the signed-in user's role in each |
| `PUT` | `/admin/api/groups` | Admin session | Replace groups (super: all; owner: own only) |
| `GET` | `/admin/api/groups/:id/routes` | Admin session | List a group's routes |
| `PUT` | `/admin/api/groups/:id/routes` | Admin session | Replace a group's routes (owner/admin) |
| `PUT` | `/admin/api/groups/:id/rename` | Admin session | Rename a group (owner); routes/secret/invites follow |
| `GET` | `/admin/api/groups/:id/invites` | Admin session | List pending invites (owner) |
| `POST` | `/admin/api/groups/:id/invites` | Admin session | Create an invite link (owner) |
| `DELETE` | `/admin/api/invites/:token` | Admin session | Revoke an invite (owner) |
| `GET` | `/admin/api/groups/:id/webhook` | Admin session | Group webhook endpoint info (owner) |
| `POST` | `/admin/api/groups/:id/webhook/regenerate` | Admin session | Generate/regenerate the group webhook secret (owner) |
| `DELETE` | `/admin/api/groups/:id/webhook` | Admin session | Disable the group webhook ingress (owner) |
| `GET` | `/admin/api/logs` | Admin session | Send logs (scoped) |
| `GET` | `/admin/api/logs/:id` | Admin session | Single send-log entry (scoped) |
| `GET` | `/admin/api/audit` | Admin session | Audit log (scoped to accessible groups) |
## Admin Console
@ -70,7 +82,7 @@ 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` | No | Unique delivery ID (used for dedup when present) |
@ -90,7 +102,7 @@ When `X-GitHub-Delivery` is present and the same delivery was already processed
**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 |
@ -110,7 +122,7 @@ Any JSON payload signed with `X-WebHooker-Signature: sha256=<hex>` (HMAC-SHA256
The primary flow is the App's **Setup URL** — set it to `{BASE_URL}/auth/github/install`. After a user installs the App, the browser lands on:
| Method | Path | Description |
| ------ | --------------------------- | ---------------------------------------------------------------------------------------------- |
|--------|-----------------------------|------------------------------------------------------------------------------------------------|
| `GET` | `/auth/github/install` | Choice page: bind the installation to a new group or an existing group the signed-in user owns |
| `POST` | `/auth/github/install/bind` | Provisions the binding (owner role re-checked) and redirects to `/admin?install=ok` |

View file

@ -40,7 +40,7 @@ server/ # Nitro server (H3 handlers in server/routes/)
│ ├── github/ # X-GitHub-Event + X-Hub-Signature-256
│ └── gitea/ # X-Gitea-Event + X-Gitea-Signature (normalized payloads)
├── formatters/ # Platform-neutral formatters (produce NeutralMessage)
│ ├── index.ts # formatEvent: 29-event switch → NeutralMessage + re-exports
│ ├── index.ts # formatEvent: 28-event switch + custom → NeutralMessage + re-exports
│ ├── colors.ts # GITHUB_COLORS + WORKFLOW_CONCLUSION_EMOJI
│ ├── helpers.ts # emojiPrefix, T, buildMessage, commitLink/branchLink/tagLink
│ └── *.ts # push, pull-request, issues, comments, workflow, release, create, repo,

View file

@ -1,46 +1,47 @@
# Supported Events
WebHooker supports 28 GitHub webhook event types, each with a dedicated formatter that produces rich Discord embeds and Telegram HTML messages. Unsupported events fall through to a generic formatter.
WebHooker supports 28 GitHub webhook event types, each with a dedicated formatter that produces rich Discord embeds and Telegram HTML messages — plus `custom` events from signed custom JSON webhooks. 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 phase updated | Workflow status, conclusion, duration; phases update a single message in place |
| `workflow_job` | CI job phase updated | Job name, status, conclusion, workflow |
| `status` | Commit status updated | Commit status, context, state, commit link |
| `deployment` | Deployment created | Environment, ref, task |
| `deployment_status` | Deployment status updated | Environment, status, commit ref |
| `check_run` | Check run phase updated | Status, conclusion, details URL; phases update a single message in place |
| `check_suite` | Check suite completed | Suite conclusion, head branch, commit link |
| `ping` | Webhook confirmation | Webhook confirmation, event types subscribed |
| `release` | Release published/edited | Tag, body, assets, pre-release flag |
| `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 |
| `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 |
| `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 |
| 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 phase updated | Workflow status, conclusion, duration; phases update a single message in place |
| `workflow_job` | CI job phase updated | Job name, status, conclusion, workflow |
| `status` | Commit status updated | Commit status, context, state, commit link |
| `deployment` | Deployment created | Environment, ref, task |
| `deployment_status` | Deployment status updated | Environment, status, commit ref |
| `check_run` | Check run phase updated | Status, conclusion, details URL; phases update a single message in place |
| `check_suite` | Check suite completed | Suite conclusion, head branch, commit link |
| `ping` | Webhook confirmation | Webhook confirmation, event types subscribed |
| `release` | Release published/edited | Tag, body, assets, pre-release flag |
| `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 |
| `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 |
| `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 |
| `custom` | Signed custom JSON webhook | Arbitrary title/description/color/url/author/fields (see [Custom webhooks](../guide/configuration.md#custom-webhooks)) |
## Color Coding
Each event type uses a distinct color in the Discord embed (from `src/formatters/colors.ts`):
Each event type uses a distinct color in the Discord embed (from `server/lib/formatters/colors.ts`):
| Color | Events |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Green (`#2da44e`) | push, PR opened / ready for review, issue opened, workflow success, release published, check success, review approved, deployment success, member added, milestone closed, discussion answered |
| Red (`#f85149`) | PR closed, issue closed, workflow failure, release deleted, delete, check failure, review changes requested, deployment failure, member removed, code scanning / dependabot critical & high |
| Purple (`#8957e5`) | PR merged, label, discussion |
@ -53,11 +54,9 @@ Each event type uses a distinct color in the Discord embed (from `src/formatters
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)
- Event type as title (plus the action when available)
- Actor login (author line)
- Repository name in the footer
## In-Place Message Updates
@ -68,7 +67,7 @@ Any event type without a dedicated formatter falls through to the generic format
See the [Filter Tutorial](../guide/filters) for a hands-on guide with worked examples.
| Filter | Works With |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|-----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `event` | All events |
| `repo` | All events |
| `actor` | All events |

62
docs/guide/commands.md Normal file
View file

@ -0,0 +1,62 @@
# Bot Commands
After [linking your GitHub account](#linking-your-account), you can act on GitHub **as yourself** from both Discord and Telegram — comments are posted with your own OAuth token and GitHub enforces permission. If GitHub rejects an action (e.g. editing someone else's comment) the bot tells you so.
## Linking Your Account
You must link your GitHub account once before using any command:
| Platform | Command | Effect |
|----------|-----------------------------------|------------------------------------------------------------------|
| Discord | `/gh login` | Returns an ephemeral OAuth link to authorize your GitHub account |
| Discord | `/gh logout` | Unlinks your GitHub account |
| Telegram | `/gh login` (reply to a message) | Returns an OAuth link |
| Telegram | `/gh logout` (reply to a message) | Unlinks your GitHub account |
The link is stored server-side (KV) and mapped to your Discord/Telegram user id in D1.
## Discord
Discord commands are **slash commands** and **message context-menu commands**, synced by the scheduled trigger (every 5 minutes): per-guild for instant availability, and globally (24h dedup, ~1h propagation). All replies are ephemeral (only you see them).
### Comment on an issue / PR
Two equivalent ways:
- **Right-click a notification** (recommended): right-click a bot-issued issue / PR / comment notification → **Apps****GitHub: 添加评论 / 编辑评论 / 删除评论**. The target is auto-extracted from the notification embed; no link needed.
- **Slash command with a link**:
```
/gh comment add link:<issue or PR url> e.g. https://github.com/owner/repo/issues/123
/gh comment edit link:<comment url> url must contain #issuecomment-<id>
/gh comment del link:<comment url> url must contain #issuecomment-<id>
```
For `edit` / `del`, copy the specific comment link on GitHub (comment ⋯ menu → **Copy link**). `add` / `edit` open a modal to enter/adjust the comment body (prefilled for edit).
### Merge / close a PR
Notifications for open PRs include **合并 / 关闭** (merge/close) buttons:
- Clicking a button merges (squash) or closes the PR as your linked GitHub account; GitHub enforces permission.
- On success the buttons are removed from the notification and the result is shown in an ephemeral reply.
### Requirements
| Item | How |
|--------------|----------------------------------------------------------------------------------------------------|
| Public key | `DISCORD_PUBLIC_KEY` set + Interactions Endpoint URL configured |
| Invite scope | Bot invited with `applications.commands` (see [Discord Bot Setup](./deployment#discord-bot-setup)) |
| OAuth | `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` and `BASE_URL` configured |
| User linked | Each user runs `/gh login` first |
## Telegram
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
The target issue/PR is parsed from the message you reply to (the notification embed links). Commands arrive via the Telegram webhook (`POST /telegram/webhook`, optionally verified with `TELEGRAM_WEBHOOK_SECRET`).

View file

@ -7,7 +7,7 @@ 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 |
| `GITEA_WEBHOOK_SECRET` | Webhook secret from your Gitea instance (only to receive Gitea webhooks) |
| `GITHUB_CLIENT_ID` | OAuth client ID from App settings |
@ -16,14 +16,16 @@ WebHooker requires several secrets to function. For local development, store the
| `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.
> `GITHUB_APP_ID` and `GITHUB_PRIVATE_KEY` (PKCS#8 PEM) are used by the GitHub App
> **install flow** (`/auth/github/install`) to resolve the installing account's login
> via an App JWT. They are optional — when unset, the install page still works but
> shows an anonymous `inst-{installationId}` group without the account name. The
> OAuth flow itself only needs `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`.
### Optional Secrets
| Variable | Description | Default |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------|-----------------------------------|
| `DISCORD_PUBLIC_KEY` | Discord application public key (Developer Portal) — required for interactions | Unset → interactions return `401` |
| `DISCORD_APPLICATION_ID` | Discord application id; auto-resolved when omitted | Auto-resolved |
| `TELEGRAM_WEBHOOK_SECRET` | Secret token for `POST /telegram/webhook` verification (X-Telegram-Bot-Api-Secret-Token) | Disabled (no verification) |
@ -32,16 +34,21 @@ WebHooker requires several secrets to function. For local development, store the
| `ADMIN_USER_IDS` | Comma-separated GitHub user IDs (or logins) allowed to access the Web UI | Disabled |
| `ALLOW_SELF_SIGNUP` | When enabled (`1`/`true`), GitHub users without any group access get a personal group on first login instead of `403` | Disabled |
| `AUDIT_RETENTION_DAYS` | Audit-log retention in days for the scheduled cleanup | `90` |
| `NUXT_PUBLIC_DOCS_URL` | Docs site URL used by the landing page (client-side runtime config) | Landing page defaults |
| `NUXT_PUBLIC_REPO_URL` | GitHub repo URL used by the landing page | Landing page defaults |
| `NUXT_PUBLIC_LEGAL_CONTACT` | Contact shown on `/terms` and `/privacy` | Unset → placeholder text |
## 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` |
Delivery-id dedup uses `X-GitHub-Delivery` (GitHub) or `X-Gitea-Delivery` (Gitea) when present.
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
@ -59,7 +66,7 @@ WebHooker ships with a built-in config console at `/admin` for managing routes i
The console is served as an SPA at `/admin`; its tabs are deep-linkable via the URL path (`/admin/groups`, `/admin/logs`, `/admin/audit`). URLs outside `/admin` that do not match an endpoint below return a plain `404` instead of the console.
| Endpoint | Description |
| ----------------------------------------------- | ----------------------------------------------------------------- |
|-------------------------------------------------|-------------------------------------------------------------------|
| `GET /admin` | Config console UI |
| `GET /admin/login` | Start GitHub OAuth sign-in |
| `GET /admin/logout` | Destroy session |
@ -82,7 +89,7 @@ The console is served as an SPA at `/admin`; its tabs are deep-linkable via the
| `POST /admin/api/groups/:id/webhook/regenerate` | Generate/regenerate the group webhook secret (owner) |
| `DELETE /admin/api/groups/:id/webhook` | Disable the group webhook ingress (owner) |
The console lets you add, edit, delete, and toggle routes. Saved routes are written to KV `config:routes` immediately and the config cache is invalidated so the webhook pipeline picks them up on the next run.
The console lets you add, edit, delete, and toggle routes. Saved routes are written to KV `config:routes` immediately and the config cache is invalidated so the webhook pipeline picks them up on the next run. Limits: at most **200 routes** and **100 groups** per instance.
## Webhook Endpoints
@ -124,7 +131,7 @@ Payload schema:
```
| Field | Type | Description |
| ------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
|---------------|----------|------------------------------------------------------------------------------------------------------------------|
| `title` | string | Message title (falls back to "Custom message") |
| `description` | string | Optional message body |
| `color` | string | Optional embed color: a word (`red`, `green`, `yellow`, `blue`, `purple`, `orange`, `cyan`, `gray`) or `#rrggbb` |
@ -139,7 +146,7 @@ Payload schema:
When the GitHub App is installed, its events arrive at the global endpoint for **every** installation. To keep tenants apart, bind each group to the installation id that should feed it: `"installationId": 12345678`. The id is visible in the App's installation webhook payload (`installation.id`) or on the GitHub App installation page URL. Events from any other installation are rejected for that group even if its `owners` list is empty. Groups without `installationId` keep the legacy behavior (`owners` filtering).
Binding is **auto-configured** — the GitHub App's _Setup URL_ should point to `{BASE_URL}/auth/github/install`. Right after a user installs the App, the browser lands there and they choose where the installation binds: a **new group** (`inst-{installationId}`, default) or any **existing group they own** (owner role checked again on submit; `POST /auth/github/install/bind` performs the provisioning). No manual id entry is needed. As a fallback (e.g. when the Setup URL is not configured), the `installation.created` webhook event creates/binds the group automatically — existing groups whose `owners` match the installing account are bound, otherwise a dedicated `inst-{installationId}` group is created. Then just add routes/members in the console.
Binding is **auto-configured** — the GitHub App's _Setup URL_ should point to `{BASE_URL}/auth/github/install`. Right after a user installs the App, the browser lands there (the page requires a signed-in admin session — not signed in users are redirected through the OAuth flow first) and they choose where the installation binds: a **new group** (`inst-{installationId}`, default) or any **existing group they own** (owner role checked again on submit; `POST /auth/github/install/bind` performs the provisioning). No manual id entry is needed. As a fallback (e.g. when the Setup URL is not configured), the `installation.created` webhook event creates/binds the group automatically — existing groups whose `owners` match the installing account are bound, otherwise a dedicated `inst-{installationId}` group is created. Then just add routes/members in the console.
## Routes
@ -195,7 +202,7 @@ You can add role ids in the admin console under _Discord role mentions_.
Other route fields:
| Field | Type | Required | Description |
| ---------------- | -------- | -------- | ----------------------------------------------------------------------------------------------- |
|------------------|----------|----------|-------------------------------------------------------------------------------------------------|
| `groupId` | string | Yes | Id of the [group](#groups) this route belongs to |
| `fallback` | boolean | No | When `true`, fires only if no non-fallback route matched the event; its own filters are ignored |
| `stop` | boolean | No | When `true` and this route matches, no further routes are evaluated for this event |
@ -249,7 +256,7 @@ Routes belong to groups. Groups scope admin access and can restrict which events
```
| Field | Type | Required | Description |
| ---------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|------------------|----------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | string | Yes | Lowercase id (`a-z0-9`, `-`); referenced by each route's `groupId`. Editable: renaming a group re-points its routes, per-group webhook secret and pending invites |
| `name` | string | Yes | Human-readable group name |
| `members` | object[] | No | `{ login, role }` entries; role is `owner`, `admin`, or `viewer` |
@ -266,7 +273,7 @@ Routes belong to groups. Groups scope admin access and can restrict which events
Every group member has one of three roles. Super admins (`ADMIN_USER_IDS`) always bypass them.
| Role | View routes/logs | Edit routes | Manage members & invites | Edit group settings |
| -------- | ---------------- | ----------- | ------------------------ | ------------------- |
|----------|------------------|-------------|--------------------------|---------------------|
| `owner` | ✓ | ✓ | ✓ | ✓ (except `owners`) |
| `admin` | ✓ | ✓ | ✗ | ✗ |
| `viewer` | ✓ (read-only) | ✗ | ✗ | ✗ |
@ -282,7 +289,7 @@ Every group member has one of three roles. Super admins (`ADMIN_USER_IDS`) alway
### Webhook Log Channel
A group may set `logTarget` to a Discord channel/thread or Telegram chat/topic. Whenever the group's routes dispatch a webhook, a single summary message is sent there: the event type/action, the repo, the delivery id, and one line per route×target with an ✅/❌ outcome (including the error for failed sends). The message is green when every dispatch succeeded and red when any failed. The summary uses the group's message language. Log messages are sent best-effort and are not themselves recorded in the D1 send log.
A group may set `logTarget` to a Discord channel/thread or Telegram chat/topic. Whenever the group's routes dispatch a webhook, a single summary message is sent there: the event type/action, the repo, the delivery id, and one line per route×target with an ✅/❌ outcome (including the error for failed sends; at most the first 10 lines are listed, the rest is summarized as `+N`). The message is green when every dispatch succeeded and red when any failed. The summary uses the group's message language. Log messages are sent best-effort and are not themselves recorded in the D1 send log.
### Invites
@ -297,7 +304,7 @@ With `ALLOW_SELF_SIGNUP=1`, a GitHub user who has no group access gets a persona
See the [Filter Tutorial](./filters) for a hands-on guide with worked examples.
| Type | Matches | Example |
| --------- | -------------------- | ---------------------------------- |
|-----------|----------------------|------------------------------------|
| `event` | GitHub event name | `push`, `pull_*`, `pull_request` |
| `repo` | Repository full name | `org/repo`, `org/*` |
| `actor` | Sender login | `username`, `[bot]`, `*[bot]` |
@ -326,7 +333,7 @@ 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 |
@ -336,6 +343,8 @@ Filters accept either a single string or an array of strings:
| `invite:{token}` | `{ groupId, role, expiresAt, createdBy, note? }` | 7 days |
| `invite:group:{id}` | Token index per group (keeps invite listing consistent) | Permanent |
| `delivery:{id}` | Webhook delivery id (dedup marker) | 300 seconds |
| `delivery:{groupId}:{id}` | Tenant-scoped delivery dedup for the per-group webhook ingress | 300 seconds |
| `tenant:{groupId}` | Per-group webhook secret (64-char hex, generated from the console) | Permanent |
| `msg:{routeId}:{key}:{target}` | Message id tracking for in-place updates (e.g. `workflow_run` / `check_run`) | 7 days |
| `cmd:guild:{id}` | Guild id whose commands were registered (dedup) | Permanent |
| `cmd:registered:global` | Global command registration marker (dedup) | 1 day |
@ -347,7 +356,7 @@ Filters accept either a single string or an array of strings:
The D1 database (`DB` binding, database `webhooker`) holds four tables:
| Table | Purpose |
| ---------------- | ---------------------------------------------------------------------------------------------- |
|------------------|------------------------------------------------------------------------------------------------|
| `send_logs` | One row per dispatch attempt (route id, event, target, ok/error, duration, error code, detail) |
| `audit_logs` | One row per admin operation (login/logout, group/route/member/invite changes) |
| `discord_links` | Maps `discord_user_id``github_user_id` for `/gh` Discord commands |

View file

@ -37,10 +37,8 @@ bunx wrangler secret put ADMIN_USER_IDS # comma-separated GitHub IDs/login
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.
:::
::: 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).
::: tip GitHub App ID / private key are optional
`GITHUB_APP_ID` + `GITHUB_PRIVATE_KEY` (PKCS#8 PEM) are only used by the [App install flow](#github-app-setup) to resolve the installing account's login on the post-install choice page. You can skip them — the page then shows an anonymous `inst-{installationId}` group. The OAuth flow only needs `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`.
:::
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.
@ -124,7 +122,7 @@ Your worker is now live at `https://webhooker.<your-subdomain>.workers.dev`.
- **Organization permissions**: Members (read) — if needed
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
5. Generate private key — optional; set `GITHUB_APP_ID` + `GITHUB_PRIVATE_KEY` to show the installing account's login on the post-install page (see the tip above).
### 2. Install App
@ -161,7 +159,7 @@ Messages are sent via the Discord **REST API**, so pushing works with just `DISC
The `/gh` slash command and the `GitHub: 添加/编辑/删除评论` message commands are synced by the scheduled trigger (every 5 minutes): per-guild for instant availability, plus a global registration (24h dedup, ~1h propagation). The bot never connects to the Discord Gateway, so it shows as **offline** — messaging is unaffected (always REST).
Users run `/gh login` to link their GitHub account and can then comment on issues/PRs as themselves. See the [README](https://github.com/ReCloudStudio/WebHooker#bot-commands-comment-on-github-as-yourself) for the full command reference.
Users run `/gh login` to link their GitHub account and can then comment on issues/PRs as themselves. See the [Bot Commands](/guide/commands) page for the full command reference.
## Telegram Bot Setup
@ -170,12 +168,7 @@ Users run `/gh login` to link their GitHub account and can then comment on issue
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
In Telegram, `/gh` commands (`/gh login`, `/gh logout`, `/gh comment <text>`, `/gh merge`, `/gh close`) work by replying to a notification message — see the [Bot Commands](/guide/commands) page.
Avatars are rendered as a link-preview card using the built-in `GET /api/richheader` (overridable with `TELEGRAM_RICH_HEADER_HOST`).

37
docs/guide/faq.md Normal file
View file

@ -0,0 +1,37 @@
# FAQ & Troubleshooting
## Why is the Discord bot showing as offline?
The bot never connects to the Discord Gateway — it always sends messages via the REST API and receives interactions through the HTTPS Interactions Endpoint. **Offline is normal** and does not affect messaging.
## My webhooks are not being forwarded
Check in order:
1. `GET /health` returns `{"status":"ok"}`.
2. The webhook URL points at `{BASE_URL}/webhook` and the secret matches `GITHUB_WEBHOOK_SECRET` / `GITEA_WEBHOOK_SECRET`.
3. At least one **enabled** route exists for the event (`event` filter), and its group accepts the sender (see `owners` / `providers` / `installationId` on the group).
4. The route has at least one target with a valid channel/chat id.
5. Look at the console **Logs** tab — every dispatch attempt is recorded with the error.
## The Discord bot does not reply to commands / buttons
- `DISCORD_PUBLIC_KEY` must be set and the **Interactions Endpoint URL** must point at `{BASE_URL}/discord/interactions`.
- The user must run `/gh login` first and the OAuth secrets (`GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`, `BASE_URL`) must be configured.
- Slash commands sync from the scheduled trigger every 5 minutes; global registration can take ~1 hour to propagate.
## I deleted a branch but got a "0 commits" push message
Branch deletions via `git push --delete` arrive as push events with `deleted: true` — they are rendered as a normal delete message. If you still see "0 commits", the payload's `deleted` flag was absent (e.g. an old delivery).
## How do I point a group at its own webhook endpoint?
See [Per-group endpoint](./configuration#per-group-endpoint) — generate a secret from the group's **Webhook endpoint** panel (owner role), then use `POST /webhook/{groupId}` with the group secret.
## Can I run this outside Cloudflare Workers?
No — the worker requires the KV and D1 bindings declared in `wrangler.jsonc` and runs on the `cloudflare_module` Nitro preset.
## Where is data stored?
Configuration lives in Cloudflare KV (`config:routes`, `config:groups`); send/audit logs and platform↔GitHub links live in D1. See [Storage Layout](./configuration#kv-storage-layout).

View file

@ -38,7 +38,7 @@ BASE_URL=http://localhost:8787
```
::: tip
`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`.
`GITHUB_APP_ID` / `GITHUB_PRIVATE_KEY` are optional and only used by the App install flow to resolve the installing account's login (the OAuth flow only needs the client ID/secret). 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

24
docs/guide/i18n.md Normal file
View file

@ -0,0 +1,24 @@
# Message Language (i18n)
Messages are rendered in the language configured per group. WebHooker ships with **English** (default) and **Simplified Chinese** dictionaries.
## Per-Group Language
Set `Group.lang` (e.g. `"zh"`) to choose the message language for every route in a group — see [Groups → Group Schema](./configuration#group-schema). The group's webhook log-channel summaries use the same language.
## Custom Overrides
Translation overrides are merged from KV under the key `i18n:<lang>` as a flat JSON object of key → text. Any key from the built-in dictionaries (en/zh) can be overridden; unknown keys fall back to English.
```jsonc
// KV key: i18n:zh
{
"events.push.title": "{repo}: 推送了 {count} 个提交到 {ref}"
}
```
Overrides apply to group messages (and console UI where applicable). To add a brand-new language, store a full dictionary at `i18n:<lang>` — every key you do not provide falls back to English.
## Emoji Toggle
`Group.emoji` (default `true`) controls whether event emoji appear in that group's messages. When disabled, all emoji are stripped from titles, descriptions, fields, and links. Milestone progress bars (🟢🟡🟠⬜) are data visualization and stay regardless of the toggle.

37
docs/guide/logs.md Normal file
View file

@ -0,0 +1,37 @@
# Logs
## Send Logs (`send_logs`)
Every dispatch attempt is recorded in the D1 `send_logs` table and browsable in the console (**Logs** tab). Fields:
| Field | Meaning |
|--------------|-------------------------------------------------------------------------|
| `routeId` | Route that matched |
| `groupId` | Route's group |
| `event` | Event type (e.g. `push`, `pull_request`, `custom`) |
| `repo` | Repository full name (when present) |
| `target` | Target id the message was sent to |
| `platform` | `discord` or `telegram` |
| `ok` | Whether the send succeeded |
| `status` | HTTP status from the platform API (when applicable) |
| `error` | Error message (when failed) |
| `errorCode` | Stable error code (e.g. `NO_TARGET`, `NO_TOKEN`, `RATE_LIMITED`) |
| `attempts` | Send attempts including retries |
| `durationMs` | Time spent sending |
| `deliveryId` | Webhook delivery id (when provided) |
| `messageId` | Platform message id (used for in-place edits) |
| `actor` | Sender login |
| `action` | Event action (when present) |
| `detail` | Extra JSON details (when present) |
The console's **Logs** tab lists recent entries (filterable by group) and shows full details for a single entry. Entries are written best-effort — a failed insert never breaks dispatch.
## Audit Log (`audit_logs`)
Every admin operation is recorded in D1 `audit_logs` and browsable in the console (**Audit** tab): logins/logouts, group/route/member/invite changes, token revocations, installation bindings. Fields: timestamp, actor (GitHub id + login), action, target type/id, group id, ip, and detail JSON.
Entries are pruned automatically by the scheduled trigger after `AUDIT_RETENTION_DAYS` (default 90). Like send logs, writes are best-effort.
## Webhook Log Channel
Groups can additionally receive a per-webhook summary message in a Discord channel/thread or Telegram chat/topic — see [Groups → Webhook Log Channel](./configuration#webhook-log-channel). These summaries are best-effort and are **not** recorded in `send_logs`.

View file

@ -0,0 +1,32 @@
# Message Format
Every event formatter produces a platform-neutral message (`NeutralMessage`), rendered by the platform drivers into a Discord embed or Telegram HTML message.
## Title
Every title must start with the repo, then an optional `#number`, then `: subject`:
```
{repo}{#number}: {subject} e.g. acme/widget#7: Add feature
```
The repo comes from `payload.repository.full_name` (falling back to a generic "repository" label when missing). Comments, reviews, and inline comments use the same `{repo}{#number}: {title}` title as their parent object — never `"Comment on org/repo"` prefixes.
## Links
Only the repo head is hyperlinked — never the whole title:
- **Discord** (embed titles cannot contain partial links): the title is the repo head `{repo}{#number}`, linked to the repository; the `: {subject}` text is rendered as the first line of the description, unlinked.
- **Telegram** (HTML supports inline links): the one-line title keeps the subject, with only the repo head wrapped in a link.
Messages whose title has no colon separator (a `:` followed by a space) keep the legacy whole-title link behavior.
Commit hashes, branches, and tags render as inline code wrapped in a hyperlink (e.g. ``[`abc123d`](https://…/commit/abc123def456)``), falling back to plain inline code when the repo base URL is unavailable.
## Emoji
Event-specific emoji are added by the formatters; per-group `Group.emoji` (default true) strips them all when disabled. Milestone progress bars are exempt. See [Message Language](./i18n).
## In-Place Updates
`workflow_run` and `check_run` messages are sent once and edited in place as the run progresses (queued → running → success/failure) — no duplicate messages. Tracking uses KV `msg:*` with a stable `updateKey` per run.

11
docs/guide/tasks.md Normal file
View file

@ -0,0 +1,11 @@
# Scheduled Tasks
WebHooker runs three maintenance tasks on the scheduled trigger (`*/5 * * * *`, every 5 minutes). They only run on the deployed worker (Cloudflare cron); local `wrangler dev` runs them when triggered via `wrangler dev --test-scheduled`.
| Task | Purpose |
|-----------------|------------------------------------------------------------------------------------------------------------------|
| `discord-sync` | Registers the Discord slash/context-menu commands: per-guild (instant) and globally (24h dedup, ~1h propagation) |
| `telegram-sync` | Calls `setWebhook` to `{BASE_URL}/telegram/webhook` (with `TELEGRAM_WEBHOOK_SECRET` as `secret_token` when set) |
| `audit-prune` | Deletes `audit_logs` entries older than `AUDIT_RETENTION_DAYS` (default 90) |
There is nothing to configure beyond the secrets the tasks use (`DISCORD_TOKEN`, `DISCORD_APPLICATION_ID`, `TELEGRAM_TOKEN`, `BASE_URL`, `AUDIT_RETENTION_DAYS`).

6
docs/public/logo.svg Normal file
View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
<rect width="64" height="64" rx="14" fill="#4f46e5"/>
<path d="M14 20c0-2.2 1.8-4 4-4h28c2.2 0 4 1.8 4 4v12c0 2.2-1.8 4-4 4H26l-8 8v-8h-4c-2.2 0-4-1.8-4-4V20z" fill="none" stroke="#fff" stroke-width="4.5" stroke-linejoin="round"/>
<path d="M26 28h14" stroke="#fff" stroke-width="4.5" stroke-linecap="round"/>
<path d="M26 36h8" stroke="#fff" stroke-width="4.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 485 B

View file

@ -10,40 +10,52 @@ https://your-worker.workers.dev
## 端点
| 方法 | 路径 | 鉴权 | 说明 |
| -------- | ------------------------------ | ------------ | ---------------------------------------------------- |
| `GET` | `/health` | 无 | 健康检查 |
| `POST` | `/webhook` | HMAC 签名 | GitHub / Gitea / 自定义 webhook 接入(自动识别来源) |
| `POST` | `/webhook/:groupId` | 分组 secret | 分组级 webhook 入口(只触发该分组的路由) |
| `POST` | `/discord/interactions` | Ed25519 签名 | Discord 交互斜杠命令、按钮、modal |
| `POST` | `/telegram/webhook` | Secret token | Telegram 更新bot `/gh` 命令) |
| `GET` | `/api/richheader` | 无 | 用于 Telegram 头像链接预览卡片的 Open Graph 页面 |
| `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/close` | Bearer Token | 关闭拉取请求 |
| `POST` | `/api/react` | Bearer Token | 添加议题反应 |
| `GET` | `/admin` | 管理员会话 | 配置控制台页面 |
| `GET` | `/admin/api/routes` | 管理员会话 | 列出路由 |
| `PUT` | `/admin/api/routes` | 管理员会话 | 替换路由 |
| `GET` | `/admin/api/groups` | 管理员会话 | 列出分组(按权限过滤) |
| `PUT` | `/admin/api/groups` | 管理员会话 | 替换分组(仅超级管理员) |
| `GET` | `/admin/api/groups/:id/routes` | 管理员会话 | 列出某分组的路由 |
| `PUT` | `/admin/api/groups/:id/routes` | 管理员会话 | 替换某分组的路由 |
| `PUT` | `/admin/api/groups/:id/rename` | 管理员会话 | 重命名分组owner路由/secret/邀请自动跟随 |
| `GET` | `/admin/api/me` | 管理员会话 | 当前会话信息 |
| `GET` | `/admin/api/logs` | 管理员会话 | 发送日志(按权限过滤) |
| `GET` | `/admin/api/logs/:id` | 管理员会话 | 单条发送日志(按权限过滤) |
| 方法 | 路径 | 鉴权 | 说明 |
|----------|--------------------------------------------|--------------|------------------------------------------------------|
| `GET` | `/health` | 无 | 健康检查 |
| `POST` | `/webhook` | HMAC 签名 | GitHub / Gitea / 自定义 webhook 接入(自动识别来源) |
| `POST` | `/webhook/:groupId` | 分组 secret | 分组级 webhook 入口(只触发该分组的路由) |
| `POST` | `/discord/interactions` | Ed25519 签名 | Discord 交互斜杠命令、按钮、modal |
| `POST` | `/telegram/webhook` | Secret token | Telegram 更新bot `/gh` 命令) |
| `GET` | `/api/richheader` | 无 | 用于 Telegram 头像链接预览卡片的 Open Graph 页面 |
| `GET` | `/auth/github` | 无 | 启动 GitHub OAuth 流程 |
| `GET` | `/auth/github/callback` | 无 | OAuth 回调 |
| `GET` | `/auth/github/install` | 管理员会话 | 安装后选择页:将安装绑定到某个分组 |
| `POST` | `/auth/github/install/bind` | 管理员会话 | 执行选定的安装绑定 |
| `DELETE` | `/auth/token/:userId` | 管理员会话 | 撤销用户 Token |
| `POST` | `/api/comment` | Bearer Token | 创建议题评论 |
| `POST` | `/api/merge` | Bearer Token | 合并拉取请求 |
| `POST` | `/api/close` | Bearer Token | 关闭拉取请求 |
| `POST` | `/api/react` | Bearer Token | 添加议题反应 |
| `GET` | `/admin` | 管理员会话 | 配置控制台页面 |
| `GET` | `/admin/login` | 无 | 开始管理员登录GitHub OAuth |
| `GET` | `/admin/logout` | 管理员会话 | 退出登录并销毁会话 |
| `GET` | `/admin/invite` | 管理员会话 | 接受分组邀请(浏览器页面,`?token=…` |
| `GET` | `/admin/api/me` | 管理员会话 | 当前会话、权限范围、分组与角色 |
| `GET` | `/admin/api/routes` | 管理员会话 | 列出路由(按权限过滤) |
| `PUT` | `/admin/api/routes` | 管理员会话 | 替换路由(按分组 owner/admin |
| `GET` | `/admin/api/groups` | 管理员会话 | 列出分组 + 当前用户在各分组的角色 |
| `PUT` | `/admin/api/groups` | 管理员会话 | 替换分组超级管理员全部owner 仅自己的) |
| `GET` | `/admin/api/groups/:id/routes` | 管理员会话 | 列出某分组的路由 |
| `PUT` | `/admin/api/groups/:id/routes` | 管理员会话 | 替换某分组的路由owner/admin |
| `PUT` | `/admin/api/groups/:id/rename` | 管理员会话 | 重命名分组owner路由/secret/邀请自动跟随 |
| `GET` | `/admin/api/groups/:id/invites` | 管理员会话 | 列出待处理的邀请owner |
| `POST` | `/admin/api/groups/:id/invites` | 管理员会话 | 创建邀请链接owner |
| `DELETE` | `/admin/api/invites/:token` | 管理员会话 | 撤销邀请owner |
| `GET` | `/admin/api/groups/:id/webhook` | 管理员会话 | 分组 webhook 端点信息owner |
| `POST` | `/admin/api/groups/:id/webhook/regenerate` | 管理员会话 | 生成/重新生成分组 webhook secretowner |
| `DELETE` | `/admin/api/groups/:id/webhook` | 管理员会话 | 停用分组 webhook 入口owner |
| `GET` | `/admin/api/logs` | 管理员会话 | 发送日志(按权限过滤) |
| `GET` | `/admin/api/logs/:id` | 管理员会话 | 单条发送日志(按权限过滤) |
| `GET` | `/admin/api/audit` | 管理员会话 | 审计日志(按可访问的分组过滤) |
## 管理控制台
参见[配置 → Web 控制台](../guide/configuration.md#web-ui)了解设置方法。管理端点需要会话 Cookie可通过 `GET /admin/login`GitHub OAuth获取登录用户必须列在 `ADMIN_USER_IDS` 中。
参见[配置 → Web 控制台](../guide/configuration.md#web-ui)了解设置方法。管理端点需要会话 Cookie可通过 `GET /admin/login`GitHub OAuth获取登录用户必须列在 `ADMIN_USER_IDS`,或管理某个分组
- `GET /admin` — 提供配置控制台 HTML
- `GET /admin/api/routes` — 返回 `{ "routes": Route[] }`
- `PUT /admin/api/routes` — 请求体为 `{ "routes": Route[] }`校验每条路由id 格式、唯一 id、name、enabled、groupId、过滤器、可选的 `discordRoleIds`(身份组 id 字符串列表)、平台感知的 targetsDiscord 需 `target.channelId`Telegram 需 `target.chatId`)并持久化到 KV `config:routes`。返回 `200 { ok, count }``400 { error }` / `401 { error }` / `403 { error }`
- `PUT /admin/api/routes` — 请求体为 `{ "routes": Route[] }`校验每条路由id 格式、唯一 id、name、enabled、groupId、过滤器——**仅 `fallback` 路由允许空过滤器**——可选的 `discordRoleIds`(身份组 id 字符串列表)、平台感知的 targetsDiscord 需 `target.channelId`Telegram 需 `target.chatId`)并持久化到 KV `config:routes`。返回 `200 { ok, count }``400 { error }` / `401 { error }` / `403 { error }`
## 健康检查
@ -70,7 +82,7 @@ POST /webhook
**请求头:**
| 头部 | 必需 | 说明 |
| --------------------- | ---- | ----------------------------- |
|-----------------------|------|-------------------------------|
| `X-Hub-Signature-256` | 是 | HMAC-SHA256 签名 |
| `X-GitHub-Event` | 是 | 事件类型名称 |
| `X-GitHub-Delivery` | 否 | 唯一投递 ID存在时用于去重 |
@ -90,7 +102,7 @@ POST /webhook
**错误响应:**
| 状态码 | 响应体 | 原因 |
| ------ | -------------------------------- | ---------------------------- |
|--------|----------------------------------|------------------------------|
| `401` | `{"error": "Invalid signature"}` | 签名验证失败 |
| `400` | `{"error": "Invalid event"}` | 缺少事件头或格式错误的请求体 |
| `413` | `{"error": "Request too large"}` | 请求体超过 1MB 限制 |
@ -110,7 +122,7 @@ POST /webhook
主要流程是 App 的 **Setup URL** —— 将其设置为 `{BASE_URL}/auth/github/install`。用户安装 App 后浏览器会跳转到:
| 方法 | 路径 | 说明 |
| ------ | --------------------------- | ------------------------------------------------------------- |
|--------|-----------------------------|---------------------------------------------------------------|
| `GET` | `/auth/github/install` | 选择页:将安装绑定到新分组或登录用户拥有 owner 权限的已有分组 |
| `POST` | `/auth/github/install/bind` | 执行绑定(再次校验 owner 角色)并跳转 `/admin?install=ok` |

View file

@ -40,7 +40,7 @@ server/ # Nitro 服务器H3 处理器位于 server/routes/
│ ├── github/ # X-GitHub-Event + X-Hub-Signature-256
│ └── gitea/ # X-Gitea-Event + X-Gitea-Signature归一化载荷
├── formatters/ # 平台中立格式化器(产出 NeutralMessage
│ ├── index.ts # formatEvent29 事件 switch → NeutralMessage + re-export
│ ├── index.ts # formatEvent28 事件 switch + custom → NeutralMessage + re-export
│ ├── colors.ts # GITHUB_COLORS + WORKFLOW_CONCLUSION_EMOJI
│ ├── helpers.ts # emojiPrefix、T、buildMessage、commitLink/branchLink/tagLink
│ └── *.ts # push、pull-request、issues、comments、workflow、release、create、repo、

View file

@ -1,46 +1,47 @@
# 支持的事件
WebHooker 支持 28 种 GitHub webhook 事件类型,每种都有专用的格式化器,生成丰富的 Discord 嵌入消息与 Telegram HTML 消息。不支持的事件会回退到通用格式化器。
WebHooker 支持 28 种 GitHub webhook 事件类型,每种都有专用的格式化器,生成丰富的 Discord 嵌入消息与 Telegram HTML 消息——此外还有来自签名自定义 JSON webhook 的 `custom` 事件。不支持的事件会回退到通用格式化器。
## 事件表
| 事件 | 说明 | 嵌入亮点 |
| ----------------------------- | ---------------------- | ------------------------------------------------ |
| `push` | 代码推送到分支 | 提交列表、分支、作者、差异统计 |
| `pull_request` | PR 打开/关闭/合并/编辑 | PR 标题、分支、差异统计、标签 |
| `issues` | 议题打开/关闭/编辑 | 议题标题、标签、指派人 |
| `issue_comment` | 议题或 PR 的评论 | 评论内容、议题引用 |
| `workflow_run` | CI/CD 工作流阶段更新 | 工作流状态、结论、耗时;各阶段原地更新同一条消息 |
| `workflow_job` | CI 作业阶段更新 | 作业名、状态、结论、工作流 |
| `status` | 提交状态更新 | 提交状态、上下文、状态值、提交链接 |
| `deployment` | 部署已创建 | 环境、引用、任务 |
| `deployment_status` | 部署状态更新 | 环境、状态、提交引用 |
| `check_run` | 检查运行阶段更新 | 状态、结论、详情 URL各阶段原地更新同一条消息 |
| `check_suite` | 检查套件完成 | 套件结论、head 分支、提交链接 |
| `ping` | Webhook 确认 | Webhook 确认、已订阅的事件类型 |
| `release` | 发布创建/编辑 | 标签、内容、附件、预发布标记 |
| `create` | 分支或标签已创建 | 引用名称、引用类型 |
| `delete` | 分支或标签已删除 | 引用名称、引用类型 |
| `star` | 仓库加星/取消星标 | 星标数、操作 |
| `fork` | 仓库已复刻 | 源 → 目标复刻 |
| `pull_request_review` | PR 审查已提交 | 审查状态(已批准/需修改/已评论)、正文 |
| `pull_request_review_comment` | 行内代码审查评论 | 文件路径、行号、评论内容 |
| `commit_comment` | 提交的评论 | 提交 SHA、评论内容 |
| `member` | 协作者添加/移除 | 成员登录名、操作 |
| `label` | 标签创建/编辑/删除 | 标签名称、颜色、描述 |
| `milestone` | 里程碑打开/关闭 | 进度条、议题计数、截止日期 |
| `discussion` | 讨论创建/回答 | 标题、分类、操作 |
| `discussion_comment` | 讨论的评论 | 评论内容、讨论引用 |
| `repository` | 仓库重命名/转移 | 旧 → 新名称、变更 |
| `code_scanning_alert` | 代码扫描告警 | 严重程度、规则 ID、文件路径 |
| `dependabot_alert` | Dependabot 告警 | 严重程度、包、受影响版本、修复版本 |
| 事件 | 说明 | 嵌入亮点 |
|-------------------------------|---------------------------|----------------------------------------------------------------------------------------------------------------|
| `push` | 代码推送到分支 | 提交列表、分支、作者、差异统计 |
| `pull_request` | PR 打开/关闭/合并/编辑 | PR 标题、分支、差异统计、标签 |
| `issues` | 议题打开/关闭/编辑 | 议题标题、标签、指派人 |
| `issue_comment` | 议题或 PR 的评论 | 评论内容、议题引用 |
| `workflow_run` | CI/CD 工作流阶段更新 | 工作流状态、结论、耗时;各阶段原地更新同一条消息 |
| `workflow_job` | CI 作业阶段更新 | 作业名、状态、结论、工作流 |
| `status` | 提交状态更新 | 提交状态、上下文、状态值、提交链接 |
| `deployment` | 部署已创建 | 环境、引用、任务 |
| `deployment_status` | 部署状态更新 | 环境、状态、提交引用 |
| `check_run` | 检查运行阶段更新 | 状态、结论、详情 URL各阶段原地更新同一条消息 |
| `check_suite` | 检查套件完成 | 套件结论、head 分支、提交链接 |
| `ping` | Webhook 确认 | Webhook 确认、已订阅的事件类型 |
| `release` | 发布创建/编辑 | 标签、内容、附件、预发布标记 |
| `create` | 分支或标签已创建 | 引用名称、引用类型 |
| `delete` | 分支或标签已删除 | 引用名称、引用类型 |
| `star` | 仓库加星/取消星标 | 星标数、操作 |
| `fork` | 仓库已复刻 | 源 → 目标复刻 |
| `pull_request_review` | PR 审查已提交 | 审查状态(已批准/需修改/已评论)、正文 |
| `pull_request_review_comment` | 行内代码审查评论 | 文件路径、行号、评论内容 |
| `commit_comment` | 提交的评论 | 提交 SHA、评论内容 |
| `member` | 协作者添加/移除 | 成员登录名、操作 |
| `label` | 标签创建/编辑/删除 | 标签名称、颜色、描述 |
| `milestone` | 里程碑打开/关闭 | 进度条、议题计数、截止日期 |
| `discussion` | 讨论创建/回答 | 标题、分类、操作 |
| `discussion_comment` | 讨论的评论 | 评论内容、讨论引用 |
| `repository` | 仓库重命名/转移 | 旧 → 新名称、变更 |
| `code_scanning_alert` | 代码扫描告警 | 严重程度、规则 ID、文件路径 |
| `dependabot_alert` | Dependabot 告警 | 严重程度、包、受影响版本、修复版本 |
| `custom` | 签名的自定义 JSON webhook | 任意 title/description/color/url/author/fields见[自定义 webhook](../guide/configuration.md#自定义-webhook) |
## 颜色编码
每种事件类型在 Discord 嵌入中使用不同的颜色(来自 `src/formatters/colors.ts`
每种事件类型在 Discord 嵌入中使用不同的颜色(来自 `server/lib/formatters/colors.ts`
| 颜色 | 事件 |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|------------------|---------------------------------------------------------------------------------------------------------------------------------|
| 绿色 (`#2da44e`) | push、PR 打开/可审查、issue 打开、工作流成功、发布已发布、检查成功、审查已批准、部署成功、成员添加、里程碑关闭、讨论已回答 |
| 红色 (`#f85149`) | PR 关闭、issue 关闭、工作流失败、发布已删除、delete、检查失败、审查请求修改、部署失败、成员移除、代码扫描/Dependabot 严重与高危 |
| 紫色 (`#8957e5`) | PR 合并、label、discussion |
@ -53,11 +54,9 @@ WebHooker 支持 28 种 GitHub webhook 事件类型,每种都有专用的格
没有专用格式化器的事件类型会回退到通用格式化器,生成包含以下内容的基础嵌入:
- 事件类型作为标题
- 操作(如果可用)
- 发送者登录名
- 仓库名称
- 原始载荷作为代码块(截断到 1000 字符)
- 事件类型作为标题(可用时附带操作)
- 发送者登录名(作者行)
- 仓库名称(页脚)
## 原地消息更新
@ -68,7 +67,7 @@ WebHooker 支持 28 种 GitHub webhook 事件类型,每种都有专用的格
实操指南见[过滤器教程](../guide/filters),包含完整示例。
| 过滤器 | 适用事件 |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|-----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `event` | 所有事件 |
| `repo` | 所有事件 |
| `actor` | 所有事件 |

62
docs/zh/guide/commands.md Normal file
View file

@ -0,0 +1,62 @@
# 机器人命令
[绑定你的 GitHub 账号](#绑定账号)后,即可在 Discord 和 Telegram 上**以本人身份**操作 GitHub——评论使用你自己的 OAuth token 发布,权限由 GitHub 强制校验。若 GitHub 拒绝操作(例如编辑他人的评论),机器人会明确告知。
## 绑定账号
使用任何命令前,需先绑定一次 GitHub 账号:
| 平台 | 命令 | 效果 |
|----------|----------------------------------|-------------------------------------------------------------|
| Discord | `/gh login` | 返回一条仅你可见的 OAuth 链接,用于授权 GitHub 账号 |
| Discord | `/gh logout` | 解除绑定 |
| Telegram | `/gh login`(引用一条消息) | 返回 OAuth 链接 |
| Telegram | `/gh logout`(引用一条消息) | 解除绑定 |
链接保存在服务端KV并在 D1 中映射到你的 Discord/Telegram 用户 ID。
## Discord
Discord 命令为**斜杠命令**与**消息右键菜单命令**,由定时任务同步(每 5 分钟按服务器即时注册并全局注册24h 去重,约 1 小时传播)。所有回复均为临时消息(仅你可见)。
### 评论 issue / PR
两种等效方式:
- **右键通知**(推荐):右键机器人发出的 issue / PR / 评论通知 → **应用****GitHub: 添加评论 / 编辑评论 / 删除评论**。目标自动从通知嵌入中提取,无需链接。
- **带链接的斜杠命令**
```
/gh comment add link:<issue PR 链接> 例如 https://github.com/owner/repo/issues/123
/gh comment edit link:<评论链接> 链接需包含 #issuecomment-<id>
/gh comment del link:<评论链接> 链接需包含 #issuecomment-<id>
```
`edit` / `del` 需要在 GitHub 上复制具体评论链接(评论 ⋯ 菜单 → **复制链接**)。`add` / `edit` 会打开模态框输入/调整评论内容edit 会预填)。
### 合并 / 关闭 PR
开放 PR 的通知附带 **合并 / 关闭** 按钮:
- 点击按钮即以你绑定的 GitHub 账号合并squash或关闭 PR权限由 GitHub 强制校验。
- 成功后按钮会从通知中移除,结果以临时消息展示。
### 前置条件
| 项目 | 如何满足 |
|--------------|-----------------------------------------------------------------------------------------------------|
| 公钥 | 设置 `DISCORD_PUBLIC_KEY` 并配置 Interactions Endpoint URL |
| 邀请 scope | 机器人以 `applications.commands` scope 邀请(见 [Discord Bot 设置](./deployment#discord-bot-设置) |
| OAuth | 配置 `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET``BASE_URL` |
| 绑定账号 | 每位用户先执行 `/gh login` |
## Telegram
Telegram `/gh` 命令通过**引用一条通知消息**来使用:
- `/gh login` — 绑定 GitHub 账号(返回 OAuth 链接)
- `/gh logout` — 解除绑定
- `/gh comment <文本>` — 引用 issue/PR 通知以本人身份评论
- `/gh merge` / `/gh close` — 引用 PR 通知进行合并/关闭
目标 issue/PR 从你引用的消息(通知嵌入中的链接)解析。命令通过 Telegram webhook`POST /telegram/webhook`,可用 `TELEGRAM_WEBHOOK_SECRET` 校验)送达。

View file

@ -7,7 +7,7 @@ WebHooker 需要多个密钥才能运行。本地开发时存储在 `.dev.vars`
### 必需密钥
| 变量 | 说明 |
| ----------------------- | -------------------------------------------------------- |
|-------------------------|----------------------------------------------------------|
| `GITHUB_WEBHOOK_SECRET` | GitHub App 设置中的 Webhook 密钥 |
| `GITEA_WEBHOOK_SECRET` | Gitea 实例的 Webhook 密钥(仅接收 Gitea webhook 时需要) |
| `GITHUB_CLIENT_ID` | App 设置中的 OAuth 客户端 ID |
@ -16,18 +16,22 @@ WebHooker 需要多个密钥才能运行。本地开发时存储在 `.dev.vars`
| `TELEGRAM_TOKEN` | Telegram Bot TokenBotFather 获取)—— Telegram 路由必需 |
> [!NOTE]
> `GITHUB_APP_ID``GITHUB_PRIVATE_KEY` 当前未被代码使用——OAuth 流程只需要
> `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`。为兼容性保留在模式中,以备日后启用
> GitHub App 认证。
> `GITHUB_APP_ID``GITHUB_PRIVATE_KEY`PKCS#8 PEM用于 GitHub App **安装流程**
> `/auth/github/install`),通过 App JWT 解析安装所属账号的登录名。两者均为可选——
> 未设置时安装页仍可正常使用,但会显示无账号名的匿名 `inst-{installationId}` 分组。
> OAuth 流程本身只需要 `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`
### 可选密钥
| 变量 | 说明 | 默认值 |
| --------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------- |
|-----------------------------|--------------------------------------------------------------------------------------------------|-------------------------|
| `BASE_URL` | OAuth 回调的公开 URL | `http://localhost:8787` |
| `ADMIN_USER_IDS` | 允许访问 WebUI 的 GitHub 用户 ID或登录名逗号分隔 | 未设置时 WebUI 关闭 |
| `ALLOW_SELF_SIGNUP` | 开启(`1`/`true`)后,没有任何分组权限的 GitHub 用户首次登录会自动获得个人分组而非 403 | 关闭 |
| `AUDIT_RETENTION_DAYS` | 定时清理时审计日志的保留天数 | `90` |
| `NUXT_PUBLIC_DOCS_URL` | 落地页使用的文档站 URL客户端运行时配置 | 落地页默认值 |
| `NUXT_PUBLIC_REPO_URL` | 落地页使用的 GitHub 仓库 URL | 落地页默认值 |
| `NUXT_PUBLIC_LEGAL_CONTACT` | `/terms``/privacy` 页面展示的联系方式 | 未设置时显示占位文本 |
| `DISCORD_PUBLIC_KEY` | Discord 应用的公钥(开发者门户获取),交互功能必需 | 未设置时交互返回 401 |
| `DISCORD_APPLICATION_ID` | Discord 应用 ID省略时自动获取 | 自动获取 |
| `TELEGRAM_WEBHOOK_SECRET` | `POST /telegram/webhook` 验签密钥X-Telegram-Bot-Api-Secret-Token | 未设置时不校验 |
@ -38,10 +42,12 @@ WebHooker 需要多个密钥才能运行。本地开发时存储在 `.dev.vars`
WebHooker 通过同一个 `POST /webhook` 端点接收多个 forge 的 webhook按请求头自动识别来源只需把各 forge 的 webhook 指向 `{BASE_URL}/webhook` 即可。
| 提供方 | 事件请求头 | 签名请求头 | 签名格式 | 密钥 |
| ------ | ---------------- | --------------------- | -------------------------- | ----------------------- |
|--------|------------------|-----------------------|----------------------------|-------------------------|
| GitHub | `X-GitHub-Event` | `X-Hub-Signature-256` | `sha256=<hex>` HMAC-SHA256 | `GITHUB_WEBHOOK_SECRET` |
| Gitea | `X-Gitea-Event` | `X-Gitea-Signature` | 纯 hex HMAC-SHA256 | `GITEA_WEBHOOK_SECRET` |
投递 id 去重使用 `X-GitHub-Delivery`GitHub`X-Gitea-Delivery`Gitea请求头存在时
Gitea payload 会被归一化为与 GitHub 相同的内部结构,因此路由、过滤器与 28 个格式化器无需改动即可复用;未知或未映射的 Gitea 事件回退到通用格式化器。仓库/提交/用户链接基于 payload 的 `repository.html_url` 生成,会指向你的 Gitea 实例。
## Web 控制台
@ -50,7 +56,7 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
### 设置
1. 配置 `ADMIN_USER_IDS`,填写允许管理路由的 GitHub 用户 ID也支持登录名例如 `ADMIN_USER_IDS=12345,RhenCloud`。未设置时控制台禁用(除非开启 `ALLOW_SELF_SIGNUP`)。
1. 配置 `ADMIN_USER_IDS`,填写允许管理一切的 GitHub 用户 ID也支持登录名例如 `ADMIN_USER_IDS=12345,RhenCloud`。未设置时控制台禁用(除非开启 `ALLOW_SELF_SIGNUP`)。
2. 打开 `/admin` 并使用 GitHub 登录。
3. 没有任何权限的用户收到 `403`,除非开启 `ALLOW_SELF_SIGNUP=1`(自动获得个人分组)或通过分组[邀请链接](#邀请)加入。
@ -59,7 +65,7 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
控制台以 SPA 形式挂在 `/admin`,各标签页可通过 URL 路径直达(`/admin/groups``/admin/logs``/admin/audit`)。`/admin` 之外且未匹配下方端点的 URL 直接返回 `404`,不会再被吞进控制台。
| 端点 | 说明 |
| ----------------------------------------------- | -------------------------------------------------------- |
|-------------------------------------------------|----------------------------------------------------------|
| `GET /admin` | 配置控制台页面 |
| `GET /admin/login` | 开始 GitHub OAuth 登录 |
| `GET /admin/logout` | 销毁会话 |
@ -82,7 +88,7 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
| `POST /admin/api/groups/:id/webhook/regenerate` | 生成/重新生成分组 webhook secretowner |
| `DELETE /admin/api/groups/:id/webhook` | 停用分组 webhook 入口owner |
控制台支持新增、编辑、删除和开关路由。保存后立即写入 KV `config:routes` 并使配置缓存失效,下一次 webhook 处理即会生效。
控制台支持新增、编辑、删除和开关路由。保存后立即写入 KV `config:routes` 并使配置缓存失效,下一次 webhook 处理即会生效。上限:每个实例最多 **200 条路由****100 个分组**
## Webhook 端点
@ -124,7 +130,7 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
```
| 字段 | 类型 | 说明 |
| ------------- | -------- | -------------------------------------------------------------------------------------------------------- |
|---------------|----------|----------------------------------------------------------------------------------------------------------|
| `title` | string | 消息标题(缺失时回退为「自定义消息」) |
| `description` | string | 可选的消息正文 |
| `color` | string | 可选消息颜色:颜色词(`red``green``yellow``blue``purple``orange``cyan``gray`)或 `#rrggbb` |
@ -139,7 +145,7 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
GitHub App 安装后,**所有**安装方的事件都会到达全局端点。要让租户互相隔离,请把每个分组绑定到应当为其提供事件的安装 ID`"installationId": 12345678`。该 ID 可从 App 安装 webhook 载荷(`installation.id`)或 GitHub App 安装页 URL 看到。即使分组的 `owners` 为空,来自其它安装的事件也会被拒绝。未设置 `installationId` 的分组保持旧行为(`owners` 过滤)。
绑定是**自动配置**的 —— 将 GitHub App 的 _Setup URL_ 指向 `{BASE_URL}/auth/github/install`。用户安装 App 后浏览器立即跳转到该页面,可选择将安装绑定到:**新分组**`inst-{installationId}`,默认)或任意**自己拥有 owner 权限的已有分组**(提交时再次校验角色;由 `POST /auth/github/install/bind` 完成配置)。无需手动填写 ID。作为兜底例如未配置 Setup URL 时),`installation.created` webhook 事件也会自动创建/绑定分组 —— `owners` 匹配安装账号的现有分组会被绑定,否则创建独立的 `inst-{installationId}` 分组。之后在控制台为分组添加路由和成员即可。
绑定是**自动配置**的 —— 将 GitHub App 的 _Setup URL_ 指向 `{BASE_URL}/auth/github/install`。用户安装 App 后浏览器立即跳转到该页面(页面需要已登录的管理员会话——未登录用户会先被重定向走 OAuth 流程),可选择将安装绑定到:**新分组**`inst-{installationId}`,默认)或任意**自己拥有 owner 权限的已有分组**(提交时再次校验角色;由 `POST /auth/github/install/bind` 完成配置)。无需手动填写 ID。作为兜底例如未配置 Setup URL 时),`installation.created` webhook 事件也会自动创建/绑定分组 —— `owners` 匹配安装账号的现有分组会被绑定,否则创建独立的 `inst-{installationId}` 分组。之后在控制台为分组添加路由和成员即可。
## 路由
@ -195,7 +201,7 @@ GitHub App 安装后,**所有**安装方的事件都会到达全局端点。
其他路由字段:
| 字段 | 类型 | 必需 | 说明 |
| ---------------- | -------- | ---- | ---------------------------------------------------------------------- |
|------------------|----------|------|------------------------------------------------------------------------|
| `groupId` | string | 是 | 该路由所属[分组](#分组)的 id |
| `fallback` | boolean | 否 | 为 `true` 时,仅当没有其它路由匹配该事件时才发送,其自身过滤器会被忽略 |
| `stop` | boolean | 否 | 为 `true` 且该路由匹配时,停止评估后续路由 |
@ -249,7 +255,7 @@ GitHub App 安装后,**所有**安装方的事件都会到达全局端点。
```
| 字段 | 类型 | 必需 | 说明 |
| ---------------- | -------- | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|------------------|----------|------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | string | 是 | 小写 id`a-z0-9``-`),由每条路由的 `groupId` 引用。可修改:重命名分组会同步更新其路由、分组级 webhook secret 与待接受邀请 |
| `name` | string | 是 | 可读的分组名称 |
| `members` | object[] | 否 | `{ login, role }` 列表;角色为 `owner``admin``viewer` |
@ -266,7 +272,7 @@ GitHub App 安装后,**所有**安装方的事件都会到达全局端点。
每个分组成员拥有三种角色之一。超级管理员(`ADMIN_USER_IDS`)始终绕过角色限制。
| 角色 | 查看路由/日志 | 编辑路由 | 管理成员与邀请 | 编辑分组设置 |
| -------- | ------------- | -------- | -------------- | ------------------ |
|----------|---------------|----------|----------------|--------------------|
| `owner` | ✓ | ✓ | ✓ | ✓(`owners` 除外) |
| `admin` | ✓ | ✓ | ✗ | ✗ |
| `viewer` | ✓(只读) | ✗ | ✗ | ✗ |
@ -282,7 +288,7 @@ GitHub App 安装后,**所有**安装方的事件都会到达全局端点。
### Webhook 日志频道
分组可以设置 `logTarget` 指向一个 Discord 频道/子区或 Telegram 群组/话题。每当该分组的路由分发dispatch一个 webhook就会向那里发送一条摘要消息事件类型/动作、仓库、投递 ID以及每条「路由 × 目标」一行的 ✅/❌ 结果(失败时附带错误信息)。全部成功时消息为绿色,任一失败则为红色。摘要使用分组的消息语言。日志消息尽力发送,本身不会被记入 D1 发送日志。
分组可以设置 `logTarget` 指向一个 Discord 频道/子区或 Telegram 群组/话题。每当该分组的路由分发dispatch一个 webhook就会向那里发送一条摘要消息事件类型/动作、仓库、投递 ID以及每条「路由 × 目标」一行的 ✅/❌ 结果(失败时附带错误信息;最多列出前 10 行,其余以 `+N` 汇总)。全部成功时消息为绿色,任一失败则为红色。摘要使用分组的消息语言。日志消息尽力发送,本身不会被记入 D1 发送日志。
### 邀请
@ -297,13 +303,13 @@ owner及超级管理员可在分组的「成员」面板创建一次性邀
实操指南见[过滤器教程](./filters),包含完整示例。
| 类型 | 匹配对象 | 示例 |
| --------- | ---------------- | ----------------------------------------- |
|-----------|------------------|-------------------------------------------|
| `event` | GitHub 事件名称 | `push`, `pull_*`, `pull_request` |
| `repo` | 仓库全名 | `org/repo`, `org/*` |
| `actor` | 发送者登录名 | `username`, `[bot]`, `*[bot]` |
| `action` | 事件操作 | `opened`, `closed`, `published` |
| `branch` | 分支名称 | `main`, `feature-?`, `/^release-/` |
| `keyword` | 载荷正文中的文本 | `deploy`, `*release-*`, `/fix\s+\d+/` |
| `keyword` | 载荷正文中的文本 | `deploy`, `/fix\s+\d+/` |
### 过滤器行为
@ -326,7 +332,7 @@ owner及超级管理员可在分组的「成员」面板创建一次性邀
## KV 存储布局
| 键模式 | 值 | TTL |
| ------------------------------ | ----------------------------------------------------------------------------- | ------------------ |
|--------------------------------|-------------------------------------------------------------------------------|--------------------|
| `config:routes` | JSON 路由数组 | 永久 |
| `config:groups` | JSON 分组数组 | 永久 |
| `session:{id}` | 管理员会话 `{ userId, login }` | 7 天 |
@ -336,6 +342,8 @@ owner及超级管理员可在分组的「成员」面板创建一次性邀
| `invite:{token}` | `{ groupId, role, expiresAt, createdBy, note? }` | 7 天 |
| `invite:group:{id}` | 每组的 Token 索引(保证邀请列表一致性) | 永久 |
| `delivery:{id}` | Webhook 投递 id去重标记 | 300 秒 |
| `delivery:{groupId}:{id}` | 分组级 webhook 入口的租户级投递去重 | 300 秒 |
| `tenant:{groupId}` | 分组 webhook secret64 位 hex控制台生成 | 永久 |
| `msg:{routeId}:{key}:{target}` | 原地更新用消息 id 追踪(如 `workflow_run` / `check_run` | 7 天 |
| `cmd:guild:{id}` | 已注册命令的服务器 id去重标记 | 永久 |
| `cmd:registered:global` | 全局命令已注册标记24h 去重) | 1 天 |
@ -347,7 +355,7 @@ owner及超级管理员可在分组的「成员」面板创建一次性邀
D1 数据库(`DB` 绑定,数据库 `webhooker`)包含四张表:
| 表 | 用途 |
| ---------------- | ---------------------------------------------------------------------- |
|------------------|------------------------------------------------------------------------|
| `send_logs` | 每次分发尝试一行(路由 id、事件、目标、成功/失败、耗时、错误码、详情) |
| `audit_logs` | 每次管理操作一行(登录/登出、分组/路由/成员/邀请变更) |
| `discord_links` | 映射 `discord_user_id``github_user_id`,用于 Discord `/gh` 命令 |

View file

@ -37,9 +37,8 @@ bunx wrangler secret put ADMIN_USER_IDS # 逗号分隔的 GitHub ID/登录
不存在全局频道密钥。每条路由在 [Web 控制台](/zh/guide/configuration#web-控制台) 中声明各自的目标频道(及可选的子区/thread因此不需要 `DISCORD_CHANNEL_ID`
:::
::: tip GitHub App ID / 私钥未使用
`GITHUB_APP_ID``GITHUB_PRIVATE_KEY` 当前未被代码使用——OAuth 流程只需要
`GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`。无需设置(也无需进行 PKCS#8 转换)。
::: tip GitHub App ID / 私钥为可选
`GITHUB_APP_ID` + `GITHUB_PRIVATE_KEY`PKCS#8 PEM仅用于 [App 安装流程](#github-app-设置),在安装后选择页解析安装所属账号的登录名。可以跳过不设——页面会显示匿名 `inst-{installationId}` 分组。OAuth 流程只需要 `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`
:::
Discord 交互通过 HTTPS Interactions Endpoint 送达,需要设置 `DISCORD_PUBLIC_KEY` 并把 **Interactions Endpoint URL** 指向 `https://your-domain/discord/interactions`。参见下方 [Interactions Endpoint](#interactions-endpoint)。
@ -123,7 +122,7 @@ Worker 现在可通过 `https://webhooker.<your-subdomain>.workers.dev` 访问
- **Organization permissions**: Members (read) —— 如果需要
4. 订阅事件(全部 28 种支持的事件):
- Push、Pull request、Issues、Issue comment、Workflow run、Workflow job、Status、Deployment、Deployment status、Ping、Release、Create、Delete、Star、Fork、Check run、Check suite、Pull request review、Pull request review comment、Commit comment、Member、Label、Milestone、Discussion、Discussion comment、Repository、Code scanning alert、Dependabot alert
5. 生成私钥 → 将内容保存到 `GITHUB_PRIVATE_KEY` 环境变量
5. 生成私钥 — 可选;设置 `GITHUB_APP_ID` + `GITHUB_PRIVATE_KEY` 后,安装后页面会显示安装所属账号的登录名(见上方提示)。
### 2. 安装 App
@ -160,7 +159,7 @@ Worker 现在可通过 `https://webhooker.<your-subdomain>.workers.dev` 访问
`/gh` 斜杠命令与 `GitHub: 添加/编辑/删除评论` 消息命令由定时任务(每 5 分钟同步注册按服务器即时可用同时全局注册24h 去重,约 1 小时传播。Bot 从不连接 Discord Gateway因此显示为**离线**——消息推送不受影响(始终走 REST
用户运行 `/gh login` 绑定自己的 GitHub 账号,即可以本人身份评论 issue/PR。完整命令说明见 [README](https://github.com/ReCloudStudio/WebHooker#bot-commands-comment-on-github-as-yourself)。
用户运行 `/gh login` 绑定自己的 GitHub 账号,即可以本人身份评论 issue/PR。完整命令说明见[机器人命令](/zh/guide/commands)。
## Telegram 机器人配置
@ -169,12 +168,7 @@ Worker 现在可通过 `https://webhooker.<your-subdomain>.workers.dev` 访问
3. Worker 会在定时任务中自动同步 webhook`setWebhook` 指向 `{BASE_URL}/telegram/webhook`),因此无需手动调用 `setWebhook`——只需确保 `BASE_URL` 已设置。
4. 将机器人加入群组(或启用话题),在路由配置中用 `chatId` / `topicId` 指定目标。
在 Telegram 中,`/gh` 命令通过在通知消息上**回复**来使用:
- `/gh login` — 绑定你的 GitHub 账号(返回 OAuth 链接)
- `/gh logout` — 解除绑定
- `/gh comment <内容>` — 回复一条 issue/PR 通知,以本人身份评论
- `/gh merge` / `/gh close` — 回复一条 PR 通知,合并/关闭该 PR
在 Telegram 中,`/gh` 命令(`/gh login``/gh logout``/gh comment <内容>``/gh merge``/gh close`)通过在通知消息上**回复**来使用——见[机器人命令](/zh/guide/commands)。
头像使用内置 `GET /api/richheader` 渲染为链接预览卡片(可用 `TELEGRAM_RICH_HEADER_HOST` 覆盖)。

37
docs/zh/guide/faq.md Normal file
View file

@ -0,0 +1,37 @@
# 常见问题与故障排查
## 为什么 Discord 机器人显示为离线?
机器人从不连接 Discord Gateway——它始终通过 REST API 发送消息,并通过 HTTPS Interactions Endpoint 接收交互。**离线是正常现象**,不影响消息投递。
## 我的 webhook 没有被转发
按顺序检查:
1. `GET /health` 返回 `{"status":"ok"}`
2. webhook URL 指向 `{BASE_URL}/webhook`,且密钥与 `GITHUB_WEBHOOK_SECRET` / `GITEA_WEBHOOK_SECRET` 一致。
3. 存在至少一条**启用**且匹配该事件(`event` 过滤器)的路由,且其分组允许该发送者(见分组的 `owners` / `providers` / `installationId`)。
4. 路由至少有一个目标,且频道/群组 id 有效。
5. 查看控制台**日志**标签页——每次分发尝试都会记录错误。
## Discord 机器人不响应命令/按钮
- 必须设置 `DISCORD_PUBLIC_KEY`,且 **Interactions Endpoint URL** 指向 `{BASE_URL}/discord/interactions`
- 用户需先执行 `/gh login`,且 OAuth 密钥(`GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET``BASE_URL`)已配置。
- 斜杠命令由定时任务每 5 分钟同步;全局注册可能需要约 1 小时传播。
## 删除分支后收到"0 个提交"的推送消息
通过 `git push --delete` 删除分支时,事件以 `deleted: true` 的 push 事件到达——会被渲染为正常的删除消息。若仍看到"0 个提交",说明载荷中缺少 `deleted` 标志(例如旧投递)。
## 如何让分组使用自己的 webhook 端点?
见[分组端点](./configuration#分组端点)——在分组的 **Webhook 端点**面板owner 角色)生成密钥,然后用 `POST /webhook/{groupId}` 与分组密钥发送。
## 可以脱离 Cloudflare Workers 运行吗?
不能——Worker 依赖 `wrangler.jsonc` 中声明的 KV 与 D1 绑定,并运行在 `cloudflare_module` Nitro preset 上。
## 数据存储在哪里?
配置存于 Cloudflare KV`config:routes``config:groups`);发送/审计日志与平台↔GitHub 绑定存于 D1。见[存储布局](./configuration#kv-存储布局)。

View file

@ -157,7 +157,7 @@
### `keyword` — 载荷中的文本
匹配整个 JSON 载荷(转为小写)。它是最灵活的过滤器:纯文本在任意位置搜索,`*`/`?` 通配符带通配搜索,`/` 包裹的模式按正则表达式编译(带 `i` 标志)。
匹配整个 JSON 载荷(转为小写)。它是最灵活的过滤器:纯文本在任意位置搜索,`*`/`?` 通配符带通配搜索,`//` 包裹的模式按正则表达式编译(带 `i` 标志)。
```json
{ "type": "keyword", "match": "deploy" }

View file

@ -38,7 +38,7 @@ BASE_URL=http://localhost:8787
```
::: tip
`GITHUB_APP_ID` / `GITHUB_PRIVATE_KEY` 未被代码使用OAuth 流程只需要 Client ID/Secret可省略。目标频道在 Web UI 中按路由设置,因此不需要 `DISCORD_CHANNEL_ID`。若要在本地启用 `/gh` 命令,请在开发者门户复制 **Public Key** 填入 `DISCORD_PUBLIC_KEY`,并把 Interactions Endpoint URL 设为 `http://localhost:8787/discord/interactions`
`GITHUB_APP_ID` / `GITHUB_PRIVATE_KEY` 为可选,仅用于 App 安装流程解析安装所属账号的登录名OAuth 流程只需要 Client ID/Secret。目标频道在 Web UI 中按路由设置,因此不需要 `DISCORD_CHANNEL_ID`。若要在本地启用 `/gh` 命令,请在开发者门户复制 **Public Key** 填入 `DISCORD_PUBLIC_KEY`,并把 Interactions Endpoint URL 设为 `http://localhost:8787/discord/interactions`
:::
::: warning
@ -62,14 +62,16 @@ curl http://localhost:8787/health
## 可用脚本
| 脚本 | 说明 |
| ---------------------- | ----------------------------- |
| `bun run dev` | 启动本地开发服务器 (wrangler) |
| `bun run deploy` | 部署到 Cloudflare |
| `bun run typecheck` | TypeScript 类型检查 |
| `bun run lint` | ESLint |
| `bun run lint:md` | Markdownlint |
| `bun run format` | 使用 Prettier 格式化 |
| `bun run format:check` | 检查 Prettier 格式 |
| `bun run docs:dev` | 启动文档开发服务器 |
| `bun run docs:build` | 构建文档站点 |
| 脚本 | 说明 |
|------------------------|--------------------------------------|
| `bun run dev` | 启动 Nuxt 开发服务器 (HMR) |
| `bun run build` | 生产构建cloudflare_module preset |
| `bun run deploy` | 部署到 Cloudflare |
| `bun run typecheck` | TypeScript 类型检查 |
| `bun run lint` | ESLint |
| `bun run lint:md` | Markdownlint |
| `bun run format` | 使用 Prettier 格式化 |
| `bun run format:check` | 检查 Prettier 格式 |
| `bun test` | 单元测试 |
| `bun run docs:dev` | 启动文档开发服务器 |
| `bun run docs:build` | 构建文档站点 |

24
docs/zh/guide/i18n.md Normal file
View file

@ -0,0 +1,24 @@
# 消息语言i18n
消息按每个分组配置的语言渲染。WebHooker 内置**英文**(默认)与**简体中文**两套字典。
## 分组语言
设置 `Group.lang`(如 `"zh"`)可为分组内所有路由选择消息语言——见[分组 → 分组模式](./configuration#分组模式)。分组的 webhook 日志频道摘要使用相同的语言。
## 自定义覆盖
翻译覆盖从 KV 键 `i18n:<lang>` 读取,值为扁平的「键 → 文本」JSON 对象。内置字典en/zh中的任意键都可覆盖未提供的键回退为英文。
```jsonc
// KV 键i18n:zh
{
"events.push.title": "{repo}: 推送了 {count} 个提交到 {ref}"
}
```
覆盖对分组消息(以及适用的控制台界面)生效。要新增一种语言,可在 `i18n:<lang>` 存放完整字典——未提供的键都会回退为英文。
## 表情开关
`Group.emoji`(默认 `true`)控制该分组消息中是否显示事件表情。关闭后,标题、描述、字段与链接中的所有表情都会被去除。里程碑进度条(🟢🟡🟠⬜)属于数据可视化,不受该开关影响。

View file

@ -41,7 +41,7 @@ GitHub / Gitea Webhook → Cloudflare Worker (Nuxt 4 / Nitro)
- **HTTP 框架**: Nuxt 4 / Nitro (H3)
- **Discord 投递**: Discord REST API交互通过 Ed25519 验签的 HTTPS Interactions Endpoint
- **Telegram 投递**: Telegram Bot APIwebhook 带可选 secret-token 校验)
- **Web UI**: Nuxt 3 静态 SPA由 Worker 资源托管
- **Web UI**: Nuxt 4Vue 3 + Tailwind CSS v3——首页/法律页面服务端渲染,`/admin` 控制台客户端渲染
- **存储**: Cloudflare KV + D1
- **鉴权**: Web Crypto API (HMAC-SHA256、Ed25519)、octokit (GitHub API)
- **语言**: TypeScript

37
docs/zh/guide/logs.md Normal file
View file

@ -0,0 +1,37 @@
# 日志
## 发送日志(`send_logs`
每次分发尝试都会记录到 D1 `send_logs` 表,并可在控制台(**日志**标签页)查看。字段:
| 字段 | 含义 |
|--------------|-------------------------------------------------------------|
| `routeId` | 匹配的路由 |
| `groupId` | 路由所属分组 |
| `event` | 事件类型(如 `push``pull_request``custom` |
| `repo` | 仓库全名(存在时) |
| `target` | 消息发送到的目标 id |
| `platform` | `discord``telegram` |
| `ok` | 发送是否成功 |
| `status` | 平台 API 的 HTTP 状态码(适用时) |
| `error` | 失败时的错误信息 |
| `errorCode` | 稳定错误码(如 `NO_TARGET``NO_TOKEN``RATE_LIMITED` |
| `attempts` | 含重试在内的发送次数 |
| `durationMs` | 发送耗时 |
| `deliveryId` | Webhook 投递 id提供时 |
| `messageId` | 平台消息 id用于原地编辑 |
| `actor` | 发送者登录名 |
| `action` | 事件动作(存在时) |
| `detail` | 附加 JSON 详情(存在时) |
控制台的**日志**标签页列出最近记录(可按分组过滤),并可查看单条完整详情。写入为尽力而为——插入失败不会中断分发。
## 审计日志(`audit_logs`
所有管理员操作都会记录到 D1 `audit_logs` 表,并可在控制台(**审计**标签页)查看:登录/登出、分组/路由/成员/邀请变更、Token 撤销、安装绑定等。字段时间戳、操作者GitHub id + 登录名)、动作、目标类型/id、分组 id、IP 与详情 JSON。
记录由定时任务在 `AUDIT_RETENTION_DAYS`(默认 90后自动清理。与发送日志一样写入为尽力而为。
## Webhook 日志频道
分组还可以在 Discord 频道/子区或 Telegram 群组/话题中接收每条 webhook 的摘要消息——见[分组 → Webhook 日志频道](./configuration#webhook-日志频道)。这些摘要是尽力发送的,**不会**记录到 `send_logs`

View file

@ -0,0 +1,32 @@
# 消息格式
每个事件格式化器都会产出一条平台中立的消息(`NeutralMessage`),由平台驱动渲染为 Discord 嵌入或 Telegram HTML 消息。
## 标题
每个标题必须以仓库名开头,随后是可选的 `#number`,再是 `: 描述`
```
{repo}{#number}: {subject} 例如 acme/widget#7: Add feature
```
仓库名取自 `payload.repository.full_name`缺失时回退为通用的「repository」标签。评论、审查与行内评论使用与其父对象相同的 `{repo}{#number}: {title}` 标题——绝不使用 `"Comment on org/repo"` 前缀。
## 链接
只有仓库头会被加超链接——整条标题永远不会整体链接:
- **Discord**(嵌入标题不支持局部链接):标题为仓库头 `{repo}{#number}`,链接到仓库;`: {subject}` 文本作为描述首行渲染,不带链接。
- **Telegram**HTML 支持行内链接):单行标题保留描述文本,仅仓库头被包在链接中。
标题中不含冒号分隔符(`:` 后跟一个空格)的消息保持旧的整行链接行为。
提交哈希、分支与标签渲染为带超链接的行内代码(如 ``[`abc123d`](https://…/commit/abc123def456)``),仓库基地址不可用时回退为纯行内代码。
## 表情
事件专属表情由格式化器添加;分组级 `Group.emoji`(默认开启)关闭后会全部去除。里程碑进度条不受影响。见[消息语言](./i18n)。
## 原地更新
`workflow_run``check_run` 消息只发送一次随运行进度原地编辑queued → running → success/failure不会重复发消息。追踪使用 KV `msg:*` 与每次运行的稳定 `updateKey`

11
docs/zh/guide/tasks.md Normal file
View file

@ -0,0 +1,11 @@
# 定时任务
WebHooker 通过定时触发器(`*/5 * * * *`,每 5 分钟)运行三个维护任务。它们只在部署后的 Worker 上运行Cloudflare cron本地 `wrangler dev` 可用 `wrangler dev --test-scheduled` 触发。
| 任务 | 用途 |
|-----------------|---------------------------------------------------------------------------------------------------------------------|
| `discord-sync` | 注册 Discord 斜杠/右键菜单命令按服务器即时注册并全局注册24h 去重,约 1 小时传播) |
| `telegram-sync` | 调用 `setWebhook` 指向 `{BASE_URL}/telegram/webhook`(设置了 `TELEGRAM_WEBHOOK_SECRET` 时作为 `secret_token` 传入) |
| `audit-prune` | 删除早于 `AUDIT_RETENTION_DAYS`(默认 90天的 `audit_logs` 记录 |
除任务用到的密钥(`DISCORD_TOKEN``DISCORD_APPLICATION_ID``TELEGRAM_TOKEN``BASE_URL``AUDIT_RETENTION_DAYS`)外无需其他配置。

View file

@ -19,11 +19,11 @@ features:
- title: 灵活的过滤器
details: 支持按事件类型、仓库、参与者、操作、分支(含 PR和关键字支持正则过滤。支持排除模式。
- title: Cloudflare Workers
details: 运行在 Cloudflare 边缘网络上。通过 Discord REST API 与 Telegram Bot API 发送消息,并通过 Ed25519 验签的 Interactions Endpoint 支持 `/gh` 命令
details: 运行在 Cloudflare 边缘网络上。通过 Discord REST API 与 Telegram Bot API 发送消息,并通过 Ed25519 验签的 Interactions Endpoint 支持 `/gh` 斜杠命令与按钮
- title: Web UI、分组与命令
details: "在内置管理控制台中管理路由、分组与发送日志。绑定你的 GitHub 账号,通过 /gh 命令以本人身份评论 issue/PRDiscord 或 Telegram。"
- title: 签名验证
details: 使用 Web Crypto API 进行 HMAC-SHA256 webhook 签名验证与 Ed25519 交互签名验证,支持时间安全比较。
details: 按提供方识别进行 HMAC-SHA256 webhook 签名验证GitHub X-Hub-Signature-256、Gitea X-Gitea-Signature与 Ed25519 交互签名验证,使用 Web Crypto API 并支持时间安全比较。
- title: 原地更新
details: workflow_run / check_run 进度在运行推进时于同一条消息上原地更新Discord 与 Telegram 均支持。
---

View file

@ -285,12 +285,12 @@ export function validateGroups(
g.providers !== undefined &&
(!Array.isArray(g.providers) ||
!g.providers.every(
(p) => typeof p === "string" && ["github", "gitea", "gitlab"].includes(p),
(p) => typeof p === "string" && ["github", "gitea"].includes(p),
))
) {
return {
ok: false,
error: `group "${g.id}".providers must be a list of "github" | "gitea" | "gitlab"`,
error: `group "${g.id}".providers must be a list of "github" | "gitea"`,
};
}
if (g.installationId !== undefined && g.installationId !== null) {

View file

@ -1,7 +1,7 @@
{
"name": "webhooker",
"main": ".output/server/index.mjs",
"compatibility_date": "2025-01-01",
"compatibility_date": "2026-",
"compatibility_flags": ["nodejs_compat"],
"build": {
"command": "bun run build"