feat(check_run): edit messages in place as the run progresses, like workflow_run

formatCheckRun now sets a stable updateKey (check_run:{repo}:{id}) so queued → in_progress → completed phases update the original message via KV msg:* tracking instead of posting new ones. Adds tests and syncs README/docs.
This commit is contained in:
RhenCloud 2026-08-13 06:47:30 +08:00
parent bf76b9342f
commit b35c2c2f90
No known key found for this signature in database
GPG key ID: A574A617378C4E0B
13 changed files with 31 additions and 15 deletions

View file

@ -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) - 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) - Format 28 event types as platform-neutral messages (Discord embeds + Telegram HTML)
- Route messages to Discord channels/threads and Telegram chats/topics via REST - 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) - 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 `/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 - Serve Telegram `/gh` commands (login/logout/comment/merge/close) via reply-message parsing

View file

@ -10,7 +10,7 @@ GitHub / Gitea webhook → Discord / Telegram dispatcher. Receives webhook event
- Filter by event type, repo, actor, action, branch, keyword (supports regex) - 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 - 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) - 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) - 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 - **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 - **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 | | `status` | Commit status, context, state |
| `deployment` | Environment, ref, task | | `deployment` | Environment, ref, task |
| `deployment_status` | Environment, status, commit ref | | `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 | | `check_suite` | Suite conclusion, head branch, commit |
| `ping` | Webhook confirmation | | `ping` | Webhook confirmation |
| `release` | Tag, body, assets | | `release` | Tag, body, assets |

View file

@ -10,7 +10,7 @@ GitHub / Gitea webhook → Discord / Telegram 分发服务。通过 Cloudflare W
- 按事件类型、仓库、操作人、操作、分支、关键词(支持正则)过滤 - 按事件类型、仓库、操作人、操作、分支、关键词(支持正则)过滤
- 富消息:颜色编码、作者头像、字段、时间戳——渲染为 Discord embed 与 Telegram HTML - 富消息:颜色编码、作者头像、字段、时间戳——渲染为 Discord embed 与 Telegram HTML
- 路由到 Discord 频道/子区与 Telegram 群组/话题(一条路由可多目标) - 路由到 Discord 频道/子区与 Telegram 群组/话题(一条路由可多目标)
- `workflow_run` 进度**原地编辑**同一条消息(工作流推进时更新),两个平台均支持 - `workflow_run` / `check_run` 进度**原地编辑**同一条消息(运行推进时更新),两个平台均支持
- GitHub OAuth 用户授权(评论、编辑评论、删除评论、合并、关闭、反应) - GitHub OAuth 用户授权(评论、编辑评论、删除评论、合并、关闭、反应)
- **Web 配置控制台**`/admin`)— 通过 GitHub OAuth + 管理员白名单管理路由与分组、查看发送日志 - **Web 配置控制台**`/admin`)— 通过 GitHub OAuth + 管理员白名单管理路由与分组、查看发送日志
- **Discord Interactions Endpoint**Ed25519 验签)支持 `/gh` 斜杠命令、消息右键菜单命令、PR 合并/关闭按钮与评论 modal - **Discord Interactions Endpoint**Ed25519 验签)支持 `/gh` 斜杠命令、消息右键菜单命令、PR 合并/关闭按钮与评论 modal
@ -332,7 +332,7 @@ npm test # 单元测试bun test
| `status` | 提交状态、上下文、状态值 | | `status` | 提交状态、上下文、状态值 |
| `deployment` | 环境、引用、任务 | | `deployment` | 环境、引用、任务 |
| `deployment_status` | 环境、状态、commit ref | | `deployment_status` | 环境、状态、commit ref |
| `check_run` | 状态、结论、详情链接 | | `check_run` | 状态、结论、详情链接(原地更新) |
| `check_suite` | 套件结论、head 分支、提交 | | `check_suite` | 套件结论、head 分支、提交 |
| `ping` | Webhook 确认 | | `ping` | Webhook 确认 |
| `release` | Tag、内容、资产 | | `release` | Tag、内容、资产 |

View file

@ -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 | | `status` | Commit status updated | Commit status, context, state, commit link |
| `deployment` | Deployment created | Environment, ref, task | | `deployment` | Deployment created | Environment, ref, task |
| `deployment_status` | Deployment status updated | Environment, status, commit ref | | `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 | | `check_suite` | Check suite completed | Suite conclusion, head branch, commit link |
| `ping` | Webhook confirmation | Webhook confirmation, event types subscribed | | `ping` | Webhook confirmation | Webhook confirmation, event types subscribed |
| `release` | Release published/edited | Tag, body, assets, pre-release flag | | `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 ## 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 ## Filter Compatibility

View file

@ -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:{token}` | `{ groupId, role, expiresAt, createdBy, note? }` | 7 days |
| `invite:group:{id}` | Token index per group (keeps invite listing consistent) | Permanent | | `invite:group:{id}` | Token index per group (keeps invite listing consistent) | Permanent |
| `delivery:{id}` | Webhook delivery id (dedup marker) | 300 seconds | | `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:guild:{id}` | Guild id whose commands were registered (dedup) | Permanent |
| `cmd:registered:global` | Global command registration marker (dedup) | 1 day | | `cmd:registered:global` | Global command registration marker (dedup) | 1 day |
| `config:discord-app-id` | Cached Discord application id | Permanent | | `config:discord-app-id` | Cached Discord application id | Permanent |

View file

@ -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 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 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 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 ## Tech Stack

View file

@ -25,5 +25,5 @@ features:
- title: Signature Verification - 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. 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 - 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.
--- ---

View file

@ -15,7 +15,7 @@ WebHooker 支持 28 种 GitHub webhook 事件类型,每种都有专用的格
| `status` | 提交状态更新 | 提交状态、上下文、状态值、提交链接 | | `status` | 提交状态更新 | 提交状态、上下文、状态值、提交链接 |
| `deployment` | 部署已创建 | 环境、引用、任务 | | `deployment` | 部署已创建 | 环境、引用、任务 |
| `deployment_status` | 部署状态更新 | 环境、状态、提交引用 | | `deployment_status` | 部署状态更新 | 环境、状态、提交引用 |
| `check_run` | 检查运行完成 | 状态、结论、详情 URL | | `check_run` | 检查运行阶段更新 | 状态、结论、详情 URL各阶段原地更新同一条消息 |
| `check_suite` | 检查套件完成 | 套件结论、head 分支、提交链接 | | `check_suite` | 检查套件完成 | 套件结论、head 分支、提交链接 |
| `ping` | Webhook 确认 | Webhook 确认、已订阅的事件类型 | | `ping` | Webhook 确认 | Webhook 确认、已订阅的事件类型 |
| `release` | 发布创建/编辑 | 标签、内容、附件、预发布标记 | | `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`)均支持。
## 过滤器兼容性 ## 过滤器兼容性

View file

@ -266,7 +266,7 @@ owner及超级管理员可在分组的「成员」面板创建一次性邀
| `invite:{token}` | `{ groupId, role, expiresAt, createdBy, note? }` | 7 天 | | `invite:{token}` | `{ groupId, role, expiresAt, createdBy, note? }` | 7 天 |
| `invite:group:{id}` | 每组的 Token 索引(保证邀请列表一致性) | 永久 | | `invite:group:{id}` | 每组的 Token 索引(保证邀请列表一致性) | 永久 |
| `delivery:{id}` | Webhook 投递 id去重标记 | 300 秒 | | `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:guild:{id}` | 已注册命令的服务器 id去重标记 | 永久 |
| `cmd:registered:global` | 全局命令已注册标记24h 去重) | 1 天 | | `cmd:registered:global` | 全局命令已注册标记24h 去重) | 1 天 |
| `config:discord-app-id` | Discord 应用 id 缓存 | 永久 | | `config:discord-app-id` | Discord 应用 id 缓存 | 永久 |

View file

@ -33,7 +33,7 @@ GitHub / Gitea Webhook → Cloudflare Worker (Hono)
4. Worker 解析事件类型并将载荷归一化为 GitHub 形状的事件 4. Worker 解析事件类型并将载荷归一化为 GitHub 形状的事件
5. 根据过滤器event、repo、actor、action、branch、keyword与分组所有者限制评估路由 5. 根据过滤器event、repo、actor、action、branch、keyword与分组所有者限制评估路由
6. 匹配的路由触发格式化器函数生成平台中立消息 6. 匹配的路由触发格式化器函数生成平台中立消息
7. 每条消息通过 Discord 或 Telegram REST API 发送到对应路由的目标,并处理速率限制重试;`workflow_run` 进度原地更新。每次尝试都记录到 D1 发送日志 7. 每条消息通过 Discord 或 Telegram REST API 发送到对应路由的目标,并处理速率限制重试;`workflow_run` / `check_run` 进度原地更新。每次尝试都记录到 D1 发送日志
## 技术栈 ## 技术栈

View file

@ -25,5 +25,5 @@ features:
- title: 签名验证 - title: 签名验证
details: 使用 Web Crypto API 进行 HMAC-SHA256 webhook 签名验证与 Ed25519 交互签名验证,支持时间安全比较。 details: 使用 Web Crypto API 进行 HMAC-SHA256 webhook 签名验证与 Ed25519 交互签名验证,支持时间安全比较。
- title: 原地更新 - title: 原地更新
details: workflow_run 进度在运行推进时于同一条消息上原地更新Discord 与 Telegram 均支持。 details: workflow_run / check_run 进度在运行推进时于同一条消息上原地更新Discord 与 Telegram 均支持。
--- ---

View file

@ -137,6 +137,7 @@ describe("message title spec", () => {
it("check_run uses status for queued and running", () => { it("check_run uses status for queued and running", () => {
const checkRun = { const checkRun = {
id: 42,
name: "Lint", name: "Lint",
html_url: "https://github.com/acme/widget/runs/1", html_url: "https://github.com/acme/widget/runs/1",
conclusion: null as string | null, conclusion: null as string | null,
@ -148,6 +149,19 @@ describe("message title spec", () => {
); );
expect(msg.title).toBe("acme/widget: Lint — running"); expect(msg.title).toBe("acme/widget: Lint — running");
expect(msg.fields![0].value).toBe("🔄 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", () => { it("check_suite shows conclusion, service, branch and commit", () => {

View file

@ -164,6 +164,7 @@ export function formatCheckRun(
showEmoji: boolean, showEmoji: boolean,
): NeutralMessage { ): NeutralMessage {
const checkRun = payload.check_run as { const checkRun = payload.check_run as {
id?: number;
name?: string; name?: string;
conclusion?: string; conclusion?: string;
html_url?: string; html_url?: string;
@ -213,6 +214,7 @@ export function formatCheckRun(
url: checkRun.html_url, url: checkRun.html_url,
color: GITHUB_COLORS[colorKey], color: GITHUB_COLORS[colorKey],
fields, fields,
updateKey: repo && checkRun.id != null ? `check_run:${repo}:${checkRun.id}` : undefined,
}, },
t, t,
repo, repo,