diff --git a/AGENTS.md b/AGENTS.md index 01f7cd7..e8f9bd9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -104,7 +104,7 @@ src/__tests__/ # bun test unit tests (webhook, formatter, discord, te - Mention Discord roles on route trigger: route-level `discordRoleIds` are rendered as `<@&id>` into the Discord message `content` (Telegram targets ignore the field) - Format 28 event types 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` progress (stable `updateKey`, KV `msg:*` tracking) +- 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) - Serve `/gh` slash commands + message context-menu commands + PR merge/close buttons + comment modals - Serve Telegram `/gh` commands (login/logout/comment/merge/close) via reply-message parsing diff --git a/README.md b/README.md index 892b9bc..d0c0fe2 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ GitHub / Gitea webhook → Discord / Telegram dispatcher. Receives webhook event - Filter by event type, repo, actor, action, branch, keyword (supports regex) - Rich messages with color coding, author avatars, fields, and timestamps — rendered as Discord embeds and Telegram HTML - Route to Discord channels/threads and Telegram chats/topics (multi-target routes) -- `workflow_run` progress is edited **in place** (single message updated as the workflow advances) on both platforms +- `workflow_run` / `check_run` progress is edited **in place** (single message updated as the run advances) on both platforms - GitHub OAuth for user actions (comment, edit comment, delete comment, merge, close, react) - **Web UI config console** (`/admin`) — manage routes and groups with GitHub OAuth + admin whitelist, view send logs - **Discord Interactions Endpoint** (Ed25519-verified) for `/gh` slash commands, message context-menu commands, PR merge/close buttons, and comment modals @@ -333,7 +333,7 @@ npm test # Unit tests (bun test) | `status` | Commit status, context, state | | `deployment` | Environment, ref, task | | `deployment_status` | Environment, status, commit ref | -| `check_run` | Status, conclusion, details URL | +| `check_run` | Status, conclusion, details URL (edited in place) | | `check_suite` | Suite conclusion, head branch, commit | | `ping` | Webhook confirmation | | `release` | Tag, body, assets | diff --git a/README.zh.md b/README.zh.md index 64a2dc5..2eaa02f 100644 --- a/README.zh.md +++ b/README.zh.md @@ -10,7 +10,7 @@ GitHub / Gitea webhook → Discord / Telegram 分发服务。通过 Cloudflare W - 按事件类型、仓库、操作人、操作、分支、关键词(支持正则)过滤 - 富消息:颜色编码、作者头像、字段、时间戳——渲染为 Discord embed 与 Telegram HTML - 路由到 Discord 频道/子区与 Telegram 群组/话题(一条路由可多目标) -- `workflow_run` 进度**原地编辑**同一条消息(工作流推进时更新),两个平台均支持 +- `workflow_run` / `check_run` 进度**原地编辑**同一条消息(运行推进时更新),两个平台均支持 - GitHub OAuth 用户授权(评论、编辑评论、删除评论、合并、关闭、反应) - **Web 配置控制台**(`/admin`)— 通过 GitHub OAuth + 管理员白名单管理路由与分组、查看发送日志 - **Discord Interactions Endpoint**(Ed25519 验签)支持 `/gh` 斜杠命令、消息右键菜单命令、PR 合并/关闭按钮与评论 modal @@ -332,7 +332,7 @@ npm test # 单元测试(bun test) | `status` | 提交状态、上下文、状态值 | | `deployment` | 环境、引用、任务 | | `deployment_status` | 环境、状态、commit ref | -| `check_run` | 状态、结论、详情链接 | +| `check_run` | 状态、结论、详情链接(原地更新) | | `check_suite` | 套件结论、head 分支、提交 | | `ping` | Webhook 确认 | | `release` | Tag、内容、资产 | diff --git a/docs/events/supported.md b/docs/events/supported.md index a78ad65..5cf4c41 100644 --- a/docs/events/supported.md +++ b/docs/events/supported.md @@ -15,7 +15,7 @@ WebHooker supports 28 GitHub webhook event types, each with a dedicated formatte | `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 completed | Status, conclusion, details URL | +| `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 | @@ -61,7 +61,7 @@ Any event type without a dedicated formatter falls through to the generic format ## In-Place Message Updates -`workflow_run` events (queued → running → success/failure) are sent once and then **edited in place** for each subsequent phase instead of posting a new message. The original message's link preview, author, and field layout are preserved; only the status, conclusion emoji, duration, and title are refreshed. Supported on both Discord (`editMessage`) and Telegram (`editMessageText` / `editMessageCaption`). +`workflow_run` and `check_run` events (queued → running → success/failure) are sent once and then **edited in place** for each subsequent phase instead of posting a new message. The original message's link preview, author, and field layout are preserved; only the status, conclusion emoji, duration, and title are refreshed. Supported on both Discord (`editMessage`) and Telegram (`editMessageText` / `editMessageCaption`). ## Filter Compatibility diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index f7a33a9..467a456 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -266,7 +266,7 @@ 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 | -| `msg:{routeId}:{key}:{target}` | Message id tracking for in-place updates (e.g. `workflow_run`) | 7 days | +| `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 | | `config:discord-app-id` | Cached Discord application id | Permanent | diff --git a/docs/guide/introduction.md b/docs/guide/introduction.md index 0e2add0..e083dff 100644 --- a/docs/guide/introduction.md +++ b/docs/guide/introduction.md @@ -33,7 +33,7 @@ GitHub / Gitea Webhook → Cloudflare Worker (Hono) 4. Worker parses the event type and normalizes the payload to a GitHub-shaped event 5. Routes are evaluated against filters (event, repo, actor, action, branch, keyword) and group owner restrictions 6. Matching routes trigger formatter functions that produce platform-neutral messages -7. Each message is sent to its route's target(s) via the Discord or Telegram REST API with rate-limit retry; `workflow_run` progress is edited in place. Every attempt is recorded in the D1 send log +7. Each message is sent to its route's target(s) via the Discord or Telegram REST API with rate-limit retry; `workflow_run` / `check_run` progress is edited in place. Every attempt is recorded in the D1 send log ## Tech Stack diff --git a/docs/index.md b/docs/index.md index 81e5938..8cdeeea 100644 --- a/docs/index.md +++ b/docs/index.md @@ -25,5 +25,5 @@ features: - title: Signature Verification details: Provider-aware HMAC-SHA256 webhook signature verification (GitHub X-Hub-Signature-256, Gitea X-Gitea-Signature) and Ed25519 interaction signature verification using the Web Crypto API with timing-safe comparison. - title: In-Place Updates - details: workflow_run progress is edited in place on a single message as the run advances, on both Discord and Telegram. + details: workflow_run and check_run progress are edited in place on a single message as the run advances, on both Discord and Telegram. --- diff --git a/docs/zh/events/supported.md b/docs/zh/events/supported.md index 138f08f..0a43e6d 100644 --- a/docs/zh/events/supported.md +++ b/docs/zh/events/supported.md @@ -15,7 +15,7 @@ WebHooker 支持 28 种 GitHub webhook 事件类型,每种都有专用的格 | `status` | 提交状态更新 | 提交状态、上下文、状态值、提交链接 | | `deployment` | 部署已创建 | 环境、引用、任务 | | `deployment_status` | 部署状态更新 | 环境、状态、提交引用 | -| `check_run` | 检查运行完成 | 状态、结论、详情 URL | +| `check_run` | 检查运行阶段更新 | 状态、结论、详情 URL;各阶段原地更新同一条消息 | | `check_suite` | 检查套件完成 | 套件结论、head 分支、提交链接 | | `ping` | Webhook 确认 | Webhook 确认、已订阅的事件类型 | | `release` | 发布创建/编辑 | 标签、内容、附件、预发布标记 | @@ -61,7 +61,7 @@ WebHooker 支持 28 种 GitHub webhook 事件类型,每种都有专用的格 ## 原地消息更新 -`workflow_run` 事件(queued → running → success/failure)只发送一条消息,后续每个阶段会**原地编辑**该消息,而不是发送新消息。消息的链接预览、作者和字段布局保持不变,仅刷新状态、结论 emoji、耗时和标题。Discord(`editMessage`)和 Telegram(`editMessageText` / `editMessageCaption`)均支持。 +`workflow_run` / `check_run` 事件(queued → running → success/failure)只发送一条消息,后续每个阶段会**原地编辑**该消息,而不是发送新消息。消息的链接预览、作者和字段布局保持不变,仅刷新状态、结论 emoji、耗时和标题。Discord(`editMessage`)和 Telegram(`editMessageText` / `editMessageCaption`)均支持。 ## 过滤器兼容性 diff --git a/docs/zh/guide/configuration.md b/docs/zh/guide/configuration.md index 5914a64..bf41b1e 100644 --- a/docs/zh/guide/configuration.md +++ b/docs/zh/guide/configuration.md @@ -266,7 +266,7 @@ owner(及超级管理员)可在分组的「成员」面板创建一次性邀 | `invite:{token}` | `{ groupId, role, expiresAt, createdBy, note? }` | 7 天 | | `invite:group:{id}` | 每组的 Token 索引(保证邀请列表一致性) | 永久 | | `delivery:{id}` | Webhook 投递 id(去重标记) | 300 秒 | -| `msg:{routeId}:{key}:{target}` | 原地更新用消息 id 追踪(如 `workflow_run`) | 7 天 | +| `msg:{routeId}:{key}:{target}` | 原地更新用消息 id 追踪(如 `workflow_run` / `check_run`) | 7 天 | | `cmd:guild:{id}` | 已注册命令的服务器 id(去重标记) | 永久 | | `cmd:registered:global` | 全局命令已注册标记(24h 去重) | 1 天 | | `config:discord-app-id` | Discord 应用 id 缓存 | 永久 | diff --git a/docs/zh/guide/introduction.md b/docs/zh/guide/introduction.md index 2eef344..ade587d 100644 --- a/docs/zh/guide/introduction.md +++ b/docs/zh/guide/introduction.md @@ -33,7 +33,7 @@ GitHub / Gitea Webhook → Cloudflare Worker (Hono) 4. Worker 解析事件类型并将载荷归一化为 GitHub 形状的事件 5. 根据过滤器(event、repo、actor、action、branch、keyword)与分组所有者限制评估路由 6. 匹配的路由触发格式化器函数生成平台中立消息 -7. 每条消息通过 Discord 或 Telegram REST API 发送到对应路由的目标,并处理速率限制重试;`workflow_run` 进度原地更新。每次尝试都记录到 D1 发送日志 +7. 每条消息通过 Discord 或 Telegram REST API 发送到对应路由的目标,并处理速率限制重试;`workflow_run` / `check_run` 进度原地更新。每次尝试都记录到 D1 发送日志 ## 技术栈 diff --git a/docs/zh/index.md b/docs/zh/index.md index 7a21688..8bdd3dc 100644 --- a/docs/zh/index.md +++ b/docs/zh/index.md @@ -25,5 +25,5 @@ features: - title: 签名验证 details: 使用 Web Crypto API 进行 HMAC-SHA256 webhook 签名验证与 Ed25519 交互签名验证,支持时间安全比较。 - title: 原地更新 - details: workflow_run 进度在运行推进时于同一条消息上原地更新,Discord 与 Telegram 均支持。 + details: workflow_run / check_run 进度在运行推进时于同一条消息上原地更新,Discord 与 Telegram 均支持。 --- diff --git a/src/__tests__/formatter.test.ts b/src/__tests__/formatter.test.ts index abb0032..e1c12ed 100644 --- a/src/__tests__/formatter.test.ts +++ b/src/__tests__/formatter.test.ts @@ -137,6 +137,7 @@ describe("message title spec", () => { it("check_run uses status for queued and running", () => { const checkRun = { + id: 42, name: "Lint", html_url: "https://github.com/acme/widget/runs/1", conclusion: null as string | null, @@ -148,6 +149,19 @@ describe("message title spec", () => { ); expect(msg.title).toBe("acme/widget: Lint — running"); expect(msg.fields![0].value).toBe("🔄 running"); + expect(msg.updateKey).toBe("check_run:acme/widget:42"); + }); + + it("check_run omits updateKey without a run id", () => { + const msg = formatEvent( + route, + event("check_run", { + check_run: { name: "Lint", status: "completed", conclusion: "success" }, + repository: repo, + sender, + }), + ); + expect(msg.updateKey).toBeUndefined(); }); it("check_suite shows conclusion, service, branch and commit", () => { diff --git a/src/formatters/check.ts b/src/formatters/check.ts index 3230f29..45ff0d6 100644 --- a/src/formatters/check.ts +++ b/src/formatters/check.ts @@ -164,6 +164,7 @@ export function formatCheckRun( showEmoji: boolean, ): NeutralMessage { const checkRun = payload.check_run as { + id?: number; name?: string; conclusion?: string; html_url?: string; @@ -213,6 +214,7 @@ export function formatCheckRun( url: checkRun.html_url, color: GITHUB_COLORS[colorKey], fields, + updateKey: repo && checkRun.id != null ? `check_run:${repo}:${checkRun.id}` : undefined, }, t, repo,