diff --git a/.env.example b/.env.example index a103b7d..3f9a46c 100644 --- a/.env.example +++ b/.env.example @@ -18,6 +18,10 @@ TELEGRAM_TOKEN=your-bot-token TELEGRAM_WEBHOOK_SECRET=your-webhook-secret # TELEGRAM_RICH_HEADER_HOST=https://your-domain +# Feishu (Lark / 飞书) +FEISHU_APP_ID=your-app-id +FEISHU_APP_SECRET=your-app-secret + # Admin ADMIN_USER_IDS=your-github-id,your-github-login # ALLOW_SELF_SIGNUP=1 # non-admin GitHub users get a personal group on first login (default off) diff --git a/AGENTS.md b/AGENTS.md index 0106e6b..fbd6ed7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Project Purpose -Nuxt 4 (Nitro) app deployed as a Cloudflare Worker that receives GitHub webhooks and dispatches processed events to Discord channels/threads and Telegram chats/topics, and receives Discord interactions (slash commands, buttons, modals) via the Interactions Endpoint plus Telegram bot `/gh` commands via the Telegram webhook. +Nuxt 4 (Nitro) app deployed as a Cloudflare Worker that receives GitHub webhooks and dispatches processed events to Discord channels/threads, Telegram chats/topics and Feishu group chats, and receives Discord interactions (slash commands, buttons, modals) via the Interactions Endpoint plus Telegram bot `/gh` commands and Feishu bot `/gh` commands + card buttons via their respective webhooks. Core pipeline: GitHub Webhook → Worker (verify + filter + format) → Discord (REST) / Telegram (Bot API) @@ -88,7 +88,7 @@ server/ # Nitro server │ # milestone, discussion, repository, security, generic, ping, custom ├── drivers/ # Platform drivers (pluggable push targets) │ ├── types.ts # PlatformDriver interface + SendResult (send + edit) - │ ├── index.ts # getDriver() registry (discord default + telegram) + │ ├── index.ts # getDriver() registry (discord default + telegram + feishu) │ ├── discord/ │ │ ├── index.ts # DiscordDriver: send/edit → renderNeutralMessage + rest.sendMessage/editMessage │ │ ├── render.ts # renderNeutralMessage: NeutralMessage → Discord FormattedMessage @@ -101,6 +101,11 @@ server/ # Nitro server │ ├── rest.ts # Telegram Bot API sendMessage/sendPhoto/editMessage* (chat_id + message_thread_id), retry │ ├── updates.ts # POST /telegram/webhook: secret-token verify + handleTelegramUpdate │ └── commands.ts # Telegram /gh login|logout|comment|merge|close + reply-message parsing + syncTelegramWebhook +│ └── feishu/ +│ ├── index.ts # FeishuDriver: send/edit → renderNeutralMessage + rest.sendMessage/updateMessage +│ ├── render.ts # renderNeutralMessage: NeutralMessage → Feishu interactive card +│ ├── rest.ts # getTenantAccessToken (KV cache) + sendMessage/updateMessage/sendText/updateCard, retry +│ └── updates.ts # X-Lark-Signature verify + url_verification + /gh commands + card.action.trigger buttons ├── github/ │ ├── oauth.ts # OAuth URL, callback token exchange, getUserOctokit, comment/getComment/editComment/deleteComment/merge/close actions │ └── store.ts # KV token CRUD + D1 discord-link/telegram-link mapping (was token-store.ts) @@ -170,6 +175,7 @@ tests/__snapshots__/ # formatter snapshot golden files (toMatchSnapshot) - Send a per-event summary (event, repo, delivery id, per route×target ✅/❌ outcome) to the group's `logTarget` when configured - 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 Feishu `/gh` commands (login/logout/comment/merge/close) and card Merge/Close buttons via the `/feishu/webhook` endpoint (X-Lark-Signature verify + `url_verification` challenge) - Sync application commands from the scheduled trigger (global ~1h propagation + per-guild instant) - Sync the Telegram webhook URL from the scheduled trigger (setWebhook) diff --git a/README.md b/README.md index 16c0e94..88e357d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # WebHooker -GitHub / Gitea webhook → Discord / Telegram dispatcher. Receives webhook events via Cloudflare Workers, applies filters, and routes formatted messages to Discord channels/threads and Telegram chats/topics. Forge-specific adapters live under `server/lib/providers/` (GitHub + Gitea today; GitLab etc. can be added later). +GitHub / Gitea webhook → Discord / Telegram / Feishu dispatcher. Receives webhook events via Cloudflare Workers, applies filters, and routes formatted messages to Discord channels/threads, Telegram chats/topics, and Feishu chat cards. Forge-specific adapters live under `server/lib/providers/` (GitHub + Gitea today; GitLab etc. can be added later). ## Features @@ -12,9 +12,9 @@ GitHub / Gitea webhook → Discord / Telegram dispatcher. Receives webhook event - Filter by event type, repo, actor, action, branch, keyword, or any payload field (`field` with a JSONPath `path`, e.g. `pull_request.user.login`); supports `*`/`?` globs and `/regex/` patterns plus 12 comparison operators (`eq`/`ne`/`contains`/`startsWith`/`endsWith`/`regex`/`gt`/`gte`/`lt`/`lte`/`in`/`exists`) - Combine filters into a boolean AST (`all` / `any` / `not` nodes) via the route editor's visual builder; reuse named filter fragments and dry-run any filter against a pasted JSON payload without storing it - 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` / `check_run` progress is edited **in place** (single message updated as the run advances) on both platforms -- **Per-group webhook log channel** — point a group at a Discord channel/thread or Telegram chat/topic and every webhook the group's routes dispatch is summarized there (✅/❌ per route × target) +- Route to Discord channels/threads, Telegram chats/topics, and Feishu group chats (multi-target routes) +- `workflow_run` / `check_run` progress is edited **in place** (single message updated as the run advances) on Discord, Telegram, and Feishu +- **Per-group webhook log channel** — point a group at a Discord channel/thread, Telegram chat/topic, or Feishu chat and every webhook the group's routes dispatch is summarized there (✅/❌ per route × target) - 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 @@ -27,7 +27,7 @@ GitHub / Gitea webhook → Discord / Telegram dispatcher. Receives webhook event ```text GitHub Webhook → Cloudflare Worker (Nuxt 4 / Nitro) - ├── POST /webhook → verify → dedup → enqueue (Queue) → dispatch → Discord (REST) / Telegram (Bot API) + ├── POST /webhook → verify → dedup → enqueue (Queue) → dispatch → Discord (REST) / Telegram (Bot API) / Feishu (Bot API) ├── POST /discord/interactions → verify (Ed25519) → handle command/button/modal ├── POST /telegram/webhook → verify (secret token) → handle /gh commands ├── GET /auth/github → OAuth flow @@ -70,6 +70,8 @@ bunx wrangler dev # Start local dev server | `TELEGRAM_TOKEN` | Telegram bot token (from BotFather) — required for Telegram routes | | `TELEGRAM_WEBHOOK_SECRET` | Optional secret token for `POST /telegram/webhook` verification | | `TELEGRAM_RICH_HEADER_HOST` | Optional base URL overriding the built-in `GET /api/richheader` for Telegram avatar cards | +| `FEISHU_APP_ID` | Feishu app ID (from the app Credentials page) — required for Feishu routes | +| `FEISHU_APP_SECRET` | Feishu app secret — required for Feishu routes | | `BASE_URL` | Public URL for OAuth callbacks and the Telegram webhook sync | | `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) | @@ -93,13 +95,14 @@ Routes are stored in D1 (`d1_routes`, seeded from legacy KV `config:routes` on f "stop": true, "targets": [ { "platform": "discord", "channelId": "CHANNEL_ID" }, - { "platform": "telegram", "chatId": "-1001234567890" } + { "platform": "telegram", "chatId": "-1001234567890" }, + { "platform": "feishu", "chatId": "oc_xxx" } ] } ] ``` -`target.platform` selects the push target: `discord` (default) or `telegram`. Discord targets require `target.channelId` (optional `threadId` for a thread); Telegram targets require `target.chatId` (optional `topicId` for a topic). A route may also set `stop: true` (skip later routes). Routes belong to **groups** (D1 `d1_groups`, seeded from legacy KV `config:groups`) that scope admin access and can restrict which org/user events flow in. See the [Routes & Targets](https://webhooker.docs.worldexecute.me/guide/routes) and [Groups & Access Control](https://webhooker.docs.worldexecute.me/guide/groups) guides for the full schema. +`target.platform` selects the push target: `discord` (default), `telegram`, or `feishu`. Discord targets require `target.channelId` (optional `threadId` for a thread); Telegram targets require `target.chatId` (optional `topicId` for a topic); Feishu targets require `target.chatId` (optional `topicId` for a topic). A route may also set `stop: true` (skip later routes). Routes belong to **groups** (D1 `d1_groups`, seeded from legacy KV `config:groups`) that scope admin access and can restrict which org/user events flow in. See the [Routes & Targets](https://webhooker.docs.worldexecute.me/guide/routes) and [Groups & Access Control](https://webhooker.docs.worldexecute.me/guide/groups) guides for the full schema. ### Web UI (`/admin`) @@ -161,6 +164,7 @@ Every filter supports plain text, `*`/`?` globs, and `/regex/` patterns (case-in - **GitHub App** — create the app, subscribe to events, configure OAuth, and set the _Setup URL_ for tenant isolation: see [GitHub App Setup](https://webhooker.docs.worldexecute.me/guide/deployment#github-app-setup) - **Discord bot** — create the bot, invite it with `applications.commands` (combined permission integer `274877910016`), and configure the Interactions Endpoint: see [Discord Bot Setup](https://webhooker.docs.worldexecute.me/guide/deployment#discord-bot-setup). The bot never connects to the Discord Gateway, so it shows as **offline** — messaging is unaffected (always REST). - **Telegram bot** — create the bot with [@BotFather](https://t.me/BotFather), set `TELEGRAM_TOKEN` (optional `TELEGRAM_WEBHOOK_SECRET`); the webhook is synced automatically by the scheduled trigger: see [Telegram Bot Setup](https://webhooker.docs.worldexecute.me/guide/deployment#telegram-bot-setup) +- **Feishu bot** — create a custom app, enable the bot, add it to the target group, and set `FEISHU_APP_ID` + `FEISHU_APP_SECRET`: see [Feishu Bot Setup](https://webhooker.docs.worldexecute.me/guide/deployment#feishu-bot-setup) - **Deployment** — KV namespace, D1 database + migrations, optional Queues, secrets, and deploy: see the [Deployment guide](https://webhooker.docs.worldexecute.me/guide/deployment) ### Bot Commands (comment on GitHub as yourself) diff --git a/README.zh.md b/README.zh.md index 6486ba4..81df76a 100644 --- a/README.zh.md +++ b/README.zh.md @@ -1,6 +1,6 @@ # WebHooker -GitHub / Gitea webhook → Discord / Telegram 分发服务。通过 Cloudflare Workers 接收 webhook 事件,应用过滤器,将格式化消息路由到 Discord 频道/子区与 Telegram 群组/话题。各 forge 适配器位于 `server/lib/providers/`(目前支持 GitHub + Gitea;GitLab 等可后续扩展)。 +GitHub / Gitea webhook → Discord / Telegram / 飞书 分发服务。通过 Cloudflare Workers 接收 webhook 事件,应用过滤器,将格式化消息路由到 Discord 频道/子区、Telegram 群组/话题与飞书群聊卡片。各 forge 适配器位于 `server/lib/providers/`(目前支持 GitHub + Gitea;GitLab 等可后续扩展)。 ## 功能特性 @@ -12,9 +12,9 @@ GitHub / Gitea webhook → Discord / Telegram 分发服务。通过 Cloudflare W - 按事件类型、仓库、操作人、操作、分支、关键词,或任意载荷字段(`field` + JSONPath `path`,如 `pull_request.user.login`)过滤;支持 `*`/`?` 通配符与 `/正则/`,另有 12 个比较操作符(`eq`/`ne`/`contains`/`startsWith`/`endsWith`/`regex`/`gt`/`gte`/`lt`/`lte`/`in`/`exists`) - 在路由编辑器的可视化构建器中把过滤器组合成布尔 AST(`all` / `any` / `not` 节点);可复用命名过滤器片段,并可对粘贴的 JSON 载荷做无存储的试匹配 - 富消息:颜色编码、作者头像、字段、时间戳——渲染为 Discord embed 与 Telegram HTML -- 路由到 Discord 频道/子区与 Telegram 群组/话题(一条路由可多目标) -- `workflow_run` / `check_run` 进度**原地编辑**同一条消息(运行推进时更新),两个平台均支持 -- **分组级 Webhook 日志频道** —— 为分组指定一个 Discord 频道/子区或 Telegram 群组/话题,该分组路由每次分发 webhook 都会向其中发送摘要(每条「路由 × 目标」一行,✅/❌ 结果) +- 路由到 Discord 频道/子区、Telegram 群组/话题与飞书群聊(一条路由可多目标) +- `workflow_run` / `check_run` 进度**原地编辑**同一条消息(运行推进时更新),Discord、Telegram 与飞书均支持 +- **分组级 Webhook 日志频道** —— 为分组指定一个 Discord 频道/子区、Telegram 群组/话题或飞书群聊,该分组路由每次分发 webhook 都会向其中发送摘要(每条「路由 × 目标」一行,✅/❌ 结果) - GitHub OAuth 用户授权(评论、编辑评论、删除评论、合并、关闭、反应) - **Web 配置控制台**(`/admin`)— 通过 GitHub OAuth + 管理员白名单管理路由与分组、查看发送日志 - **Discord Interactions Endpoint**(Ed25519 验签)支持 `/gh` 斜杠命令、消息右键菜单命令、PR 合并/关闭按钮与评论 modal @@ -27,7 +27,7 @@ GitHub / Gitea webhook → Discord / Telegram 分发服务。通过 Cloudflare W ```text GitHub Webhook → Cloudflare Worker (Nuxt 4 / Nitro) - ├── POST /webhook → 验证 → 去重 → 入队 (Queue) → 分发 → Discord (REST) / Telegram (Bot API) + ├── POST /webhook → 验证 → 去重 → 入队 (Queue) → 分发 → Discord (REST) / Telegram (Bot API) / 飞书 (Bot API) ├── POST /discord/interactions → 验证 (Ed25519) → 处理命令/按钮/modal ├── POST /telegram/webhook → 验证 (secret token) → 处理 /gh 命令 ├── GET /auth/github → OAuth 流程 @@ -70,6 +70,8 @@ bunx wrangler dev # 启动本地开发服务器 | `TELEGRAM_TOKEN` | Telegram Bot Token(BotFather 获取)—— Telegram 路由必需 | | `TELEGRAM_WEBHOOK_SECRET` | 可选;`POST /telegram/webhook` 的验签密钥 | | `TELEGRAM_RICH_HEADER_HOST` | 可选;覆盖内置 `GET /api/richheader` 的 Telegram 头像卡片地址 | +| `FEISHU_APP_ID` | 飞书应用 ID(应用凭证页获取)—— 飞书路由必需 | +| `FEISHU_APP_SECRET` | 飞书应用密钥 —— 飞书路由必需 | | `BASE_URL` | 公网地址(用于 OAuth 回调与 Telegram webhook 同步) | | `ADMIN_USER_IDS` | 允许访问 `/admin` 的 GitHub 用户 ID(或登录名),逗号分隔 | | `ALLOW_SELF_SIGNUP` | 设为 `1` 时,无权限的 GitHub 用户首次登录自动获得个人分组(默认关闭) | @@ -93,13 +95,14 @@ bunx wrangler dev # 启动本地开发服务器 "stop": true, "targets": [ { "platform": "discord", "channelId": "频道ID" }, - { "platform": "telegram", "chatId": "-1001234567890" } + { "platform": "telegram", "chatId": "-1001234567890" }, + { "platform": "feishu", "chatId": "oc_xxx" } ] } ] ``` -`target.platform` 选择推送目标:`discord`(默认)或 `telegram`。Discord 目标需 `target.channelId`(可选 `threadId` 指向子区);Telegram 目标需 `target.chatId`(可选 `topicId` 指向话题)。路由还可设置 `stop: true`(跳过后续路由)。路由隶属于**分组**(D1 `d1_groups`,首次加载时从旧版 KV `config:groups` 同步),分组用于限定管理权限,并可限制哪些组织/用户的事件流入。完整模式见[路由与目标](https://webhooker.docs.worldexecute.me/zh/guide/routes)与[分组与访问控制](https://webhooker.docs.worldexecute.me/zh/guide/groups)指南。 +`target.platform` 选择推送目标:`discord`(默认)、`telegram` 或 `feishu`。Discord 目标需 `target.channelId`(可选 `threadId` 指向子区);Telegram 目标需 `target.chatId`(可选 `topicId` 指向话题);飞书目标需 `target.chatId`(可选 `topicId` 指向子话题)。路由还可设置 `stop: true`(跳过后续路由)。路由隶属于**分组**(D1 `d1_groups`,首次加载时从旧版 KV `config:groups` 同步),分组用于限定管理权限,并可限制哪些组织/用户的事件流入。完整模式见[路由与目标](https://webhooker.docs.worldexecute.me/zh/guide/routes)与[分组与访问控制](https://webhooker.docs.worldexecute.me/zh/guide/groups)指南。 ### Web 控制台(`/admin`) diff --git a/app/components/AdminHome.vue b/app/components/AdminHome.vue index f69e85c..cd710e5 100644 --- a/app/components/AdminHome.vue +++ b/app/components/AdminHome.vue @@ -104,7 +104,10 @@ function fmtTime(ts: number): string { } function platformLabel(p?: string): string { - return p === "telegram" ? "TG" : p === "discord" ? "DC" : "—"; + if (p === "telegram") return "TG"; + if (p === "feishu") return "FS"; + if (p === "discord") return "DC"; + return "—"; } function routeEvents(r: Route): string[] { diff --git a/app/components/GroupEditor.vue b/app/components/GroupEditor.vue index fc477ae..01b221c 100644 --- a/app/components/GroupEditor.vue +++ b/app/components/GroupEditor.vue @@ -186,6 +186,7 @@ + -