mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
feat(feishu): add Feishu inbound webhook, /gh commands and card actions
- add feishu_links D1 table and link store helpers - implement X-Lark-Signature verification, url_verification, /gh login|logout|comment|merge|close and card.action.trigger Merge/Close - render interactive cards with clickable title link, inline links and callback buttons (no whole-card card_link) - bind Feishu account in OAuth callback - document event subscription and required scopes
This commit is contained in:
parent
3437ac5513
commit
4b99f6d33d
38 changed files with 2449 additions and 1412 deletions
|
|
@ -48,7 +48,7 @@ server/ # Nitro server (H3 handlers in server/routes/)
|
|||
│ # discussion, repository, security, generic, ping, custom
|
||||
├── drivers/ # Platform drivers (pluggable push targets)
|
||||
│ ├── types.ts # PlatformDriver interface + SendResult (send + edit)
|
||||
│ ├── index.ts # getDriver() registry (discord + telegram)
|
||||
│ ├── index.ts # getDriver() registry (discord + telegram + feishu)
|
||||
│ ├── discord/ # index.ts (driver), render.ts (NeutralMessage → embed),
|
||||
│ │ # rest.ts, interactions.ts, commands.ts
|
||||
│ └── telegram/ # index.ts (driver), render.ts (NeutralMessage → Telegram HTML),
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ WebHooker requires several secrets to function. For local development, store the
|
|||
| `GITHUB_CLIENT_SECRET` | OAuth client secret from App settings |
|
||||
| `DISCORD_TOKEN` | Discord bot token |
|
||||
| `TELEGRAM_TOKEN` | Telegram bot token (from BotFather) — required for Telegram routes |
|
||||
| `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 |
|
||||
|
||||
> [!NOTE]
|
||||
> `GITHUB_APP_ID` and `GITHUB_PRIVATE_KEY` (PKCS#8 PEM) are used by the GitHub App
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ bunx wrangler secret put GITHUB_CLIENT_SECRET
|
|||
bunx wrangler secret put DISCORD_TOKEN
|
||||
bunx wrangler secret put DISCORD_PUBLIC_KEY # Discord app public key (Developer Portal) — required for interactions
|
||||
bunx wrangler secret put TELEGRAM_TOKEN # Telegram bot token (BotFather) — required for Telegram routes
|
||||
bunx wrangler secret put FEISHU_APP_ID # Feishu app ID — required for Feishu routes
|
||||
bunx wrangler secret put FEISHU_APP_SECRET # Feishu app secret — required for Feishu routes
|
||||
bunx wrangler secret put ADMIN_USER_IDS # comma-separated GitHub IDs/logins allowed into the Web UI
|
||||
```
|
||||
|
||||
|
|
@ -196,6 +198,48 @@ In Telegram, `/gh` commands (`/gh login`, `/gh logout`, `/gh comment <text>`, `/
|
|||
|
||||
Avatars are rendered as a link-preview card using the built-in `GET /api/richheader` (overridable with `TELEGRAM_RICH_HEADER_HOST`).
|
||||
|
||||
## Feishu Bot Setup
|
||||
|
||||
1. Go to [Feishu Open Platform](https://open.feishu.cn/app) → **Create App** → choose **Custom App** → give it a name.
|
||||
2. In **Credentials & Basic Info**, copy **App ID** and **App Secret** to `FEISHU_APP_ID` and `FEISHU_APP_SECRET`.
|
||||
3. In **Permissions & Scopes**, add at least one of the following message scopes so the app can send (and read) messages:
|
||||
- `im:message` (read and send direct messages and group chat messages)
|
||||
- `im:message:send_as_bot` (send messages as an app)
|
||||
- `im:message:send` (historical version)
|
||||
4. In **Bot** tab, turn on the bot capability. Add the bot to the target group chat (or create a new group) and copy the **Chat ID** from the group settings.
|
||||
5. In WebHooker `/admin`, create or edit a route and add a target with `platform: "feishu"`, `chatId` set to the Feishu **Chat ID**, and optional `topicId` for a topic inside the chat.
|
||||
|
||||
WebHooker uses the app-level credentials (`FEISHU_APP_ID` / `FEISHU_APP_SECRET`) to request a `tenant_access_token`, caches it until expiry, and sends messages as an interactive card (`interactive` message type). The same token is used to edit messages in place for `workflow_run` / `check_run` progress updates.
|
||||
|
||||
### Inbound: commands & buttons
|
||||
|
||||
WebHooker can receive Feishu events and let users act on PRs/Issues from chat, the same way as Discord and Telegram:
|
||||
|
||||
- `/gh login` — link your GitHub account (opens an OAuth page).
|
||||
- `/gh logout` — unlink your GitHub account.
|
||||
- `/gh comment <PR/Issue 链接> <内容>` — comment as your linked GitHub user.
|
||||
- `/gh merge <PR 链接>` / `/gh close <PR 链接>` — merge / close the PR as your linked user.
|
||||
- The **Merge** / **Close** buttons on a card trigger the same actions.
|
||||
|
||||
To enable inbound:
|
||||
|
||||
1. In the app **Events & Callbacks** → **Event Subscriptions**, set the **Request URL** to `https://<your-worker>/feishu/webhook` (Feishu will send a `url_verification` challenge, which WebHooker answers automatically).
|
||||
2. Subscribe to the events:
|
||||
- `im.message.receive_v1` — receive `/gh` commands (requires the `im:message` scope).
|
||||
- `card.action.trigger` — receive button clicks on cards.
|
||||
3. In **Credentials & Basic Info**, set the **App Secret** (already used for `FEISHU_APP_SECRET`) — it also signs the inbound callback via the `X-Lark-Signature` header, and WebHooker verifies it.
|
||||
|
||||
### Required permissions
|
||||
|
||||
| Permission | Purpose |
|
||||
| ---------- | ------- |
|
||||
| `im:message` | Read and send direct messages and group chat messages. |
|
||||
| `im:message:send_as_bot` | Send messages as an app bot (alternative to `im:message`). |
|
||||
| `im:message:send` | Send messages V2 (historical version, alternative). |
|
||||
|
||||
> [!NOTE]
|
||||
> Custom bots (group-level webhook URL) are not supported. WebHooker uses an **app bot** so message editing, token caching, multi-group routing, and inbound commands work the same way as Discord and Telegram.
|
||||
|
||||
## Custom Domain (Optional)
|
||||
|
||||
To use a custom domain instead of `*.workers.dev`:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ Every dispatch attempt is recorded in the D1 `send_logs` table and browsable in
|
|||
| `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` |
|
||||
| `platform` | `discord`, `telegram` or `feishu` |
|
||||
| `ok` | Whether the send succeeded |
|
||||
| `status` | HTTP status from the platform API (when applicable) |
|
||||
| `error` | Error message (when failed) |
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ There are **no default routes** — each route must define its own target. If no
|
|||
}
|
||||
```
|
||||
|
||||
Each entry of `targets` is a push destination, so one route can forward to several channels at once (e.g. a Discord channel **and** a Telegram group). `target.platform` selects the platform: `discord` (default) or `telegram`. For **Discord**, `target.channelId` is required (a thread in `target.threadId` is optional). For **Telegram**, `target.chatId` (the group/supergroup chat id, e.g. `-1001234567890`) is required and `target.topicId` (the `message_thread_id` of a topic, equivalent of a Discord thread) is optional. There is no fallback to a default channel.
|
||||
Each entry of `targets` is a push destination, so one route can forward to several channels at once (e.g. a Discord channel **and** a Telegram group). `target.platform` selects the platform: `discord` (default), `telegram` or `feishu`. For **Discord**, `target.channelId` is required (a thread in `target.threadId` is optional). For **Telegram** and **Feishu**, `target.chatId` (the group/supergroup chat id, e.g. `-1001234567890`) is required and `target.topicId` (the `message_thread_id` of a topic, equivalent of a Discord thread) is optional. There is no fallback to a default channel.
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ---------------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ WebHooker 的运行需要若干密钥。本地开发时放入 `.dev.vars`,生
|
|||
| `GITHUB_CLIENT_SECRET` | App 设置中的 OAuth 客户端密钥 |
|
||||
| `DISCORD_TOKEN` | Discord 机器人 Token |
|
||||
| `TELEGRAM_TOKEN` | Telegram 机器人 Token(BotFather 获取)—— Telegram 路由必需 |
|
||||
| `FEISHU_APP_ID` | 飞书应用 ID(应用凭证页获取)—— 飞书路由必需 |
|
||||
| `FEISHU_APP_SECRET` | 飞书应用密钥 —— 飞书路由必需 |
|
||||
|
||||
> [!NOTE]
|
||||
> `GITHUB_APP_ID` 与 `GITHUB_PRIVATE_KEY`(PKCS#8 PEM)用于 GitHub App **安装流程**
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ bunx wrangler secret put GITHUB_CLIENT_SECRET
|
|||
bunx wrangler secret put DISCORD_TOKEN
|
||||
bunx wrangler secret put DISCORD_PUBLIC_KEY # Discord 应用的公钥(开发者门户获取),交互功能必需
|
||||
bunx wrangler secret put TELEGRAM_TOKEN # Telegram Bot Token(BotFather 获取)—— Telegram 路由必需
|
||||
bunx wrangler secret put FEISHU_APP_ID # 飞书应用 ID —— 飞书路由必需
|
||||
bunx wrangler secret put FEISHU_APP_SECRET # 飞书应用密钥 —— 飞书路由必需
|
||||
bunx wrangler secret put ADMIN_USER_IDS # 逗号分隔的 GitHub ID/登录名,允许进入 Web UI
|
||||
```
|
||||
|
||||
|
|
@ -196,6 +198,48 @@ Worker 现在可通过 `https://webhooker.<your-subdomain>.workers.dev` 访问
|
|||
|
||||
头像使用内置 `GET /api/richheader` 渲染为链接预览卡片(可用 `TELEGRAM_RICH_HEADER_HOST` 覆盖)。
|
||||
|
||||
## 飞书机器人配置
|
||||
|
||||
1. 进入[飞书开放平台](https://open.feishu.cn/app) → **创建应用** → 选择**企业自建应用** → 填写应用名称。
|
||||
2. 在**凭证与基础信息**中复制 **App ID** 与 **App Secret**,分别填入 `FEISHU_APP_ID` 和 `FEISHU_APP_SECRET`。
|
||||
3. 在**权限管理**中添加以下任一消息发送权限,用于发送(与读取)消息:
|
||||
- `im:message`(读取和发送单聊与群聊消息)
|
||||
- `im:message:send_as_bot`(以应用机器人身份发送消息)
|
||||
- `im:message:send`(旧版发送消息权限)
|
||||
4. 在**机器人**功能中启用机器人。将机器人添加到目标群聊(或创建新群),并在群设置中复制 **Chat ID**。
|
||||
5. 在 WebHooker `/admin` 中创建或编辑路由,添加目标:`platform: "feishu"`,`chatId` 填写飞书 **Chat ID**,子话题可填 `topicId`。
|
||||
|
||||
WebHooker 使用应用级凭证(`FEISHU_APP_ID` / `FEISHU_APP_SECRET`)请求 `tenant_access_token`(有效期约 2 小时),缓存到期前复用,并以**卡片消息**(`interactive`)形式发送。`workflow_run` / `check_run` 的进度更新同样会调用飞书消息编辑接口,原地更新消息。
|
||||
|
||||
### 入站:指令与按钮
|
||||
|
||||
与 Discord、Telegram 一样,WebHooker 可接收飞书事件,让用户直接在聊天里操作 PR/Issue:
|
||||
|
||||
- `/gh login` —— 绑定 GitHub 账号(打开 OAuth 页面)。
|
||||
- `/gh logout` —— 解绑 GitHub 账号。
|
||||
- `/gh comment <PR/Issue 链接> <内容>` —— 以绑定的 GitHub 身份发表评论。
|
||||
- `/gh merge <PR 链接>` / `/gh close <PR 链接>` —— 以绑定身份合并 / 关闭 PR。
|
||||
- 卡片上的 **合并** / **关闭** 按钮触发相同操作。
|
||||
|
||||
开启入站:
|
||||
|
||||
1. 在应用的**事件订阅**中,把**请求地址**设为 `https://<你的-worker>/feishu/webhook`(飞书会发送 `url_verification` 校验,WebHooker 自动应答)。
|
||||
2. 订阅以下事件:
|
||||
- `im.message.receive_v1` —— 接收 `/gh` 指令(依赖 `im:message` 权限)。
|
||||
- `card.action.trigger` —— 接收卡片按钮点击。
|
||||
3. **凭证与基础信息**中的 **App Secret**(即 `FEISHU_APP_SECRET`)同时用于对入站回调做 `X-Lark-Signature` 签名,WebHooker 会校验它。
|
||||
|
||||
### 所需权限
|
||||
|
||||
| 权限 | 用途 |
|
||||
| ---- | ---- |
|
||||
| `im:message` | 读取和发送单聊与群聊消息。 |
|
||||
| `im:message:send_as_bot` | 以应用机器人身份发送消息(`im:message` 的替代)。 |
|
||||
| `im:message:send` | 旧版发送消息权限(`im:message` 的替代)。 |
|
||||
|
||||
> [!NOTE]
|
||||
> 不支持“自定义机器人”的群级 Webhook URL。WebHooker 统一使用**应用机器人**,以保持与 Discord、Telegram 一致的凭证管理、消息编辑、多群路由与入站指令能力。
|
||||
|
||||
## 自定义域名(可选)
|
||||
|
||||
要使用自定义域名替代 `*.workers.dev`:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
| `event` | 事件类型(如 `push`、`pull_request`、`custom`) |
|
||||
| `repo` | 仓库全名(存在时) |
|
||||
| `target` | 消息发送到的目标 id |
|
||||
| `platform` | `discord` 或 `telegram` |
|
||||
| `platform` | `discord`、`telegram` 或 `feishu` |
|
||||
| `ok` | 发送是否成功 |
|
||||
| `status` | 平台 API 的 HTTP 状态码(适用时) |
|
||||
| `error` | 失败时的错误信息 |
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
`targets` 的每一项都是一个推送目标,因此一条路由可同时转发到多个频道(例如一个 Discord 频道**和**一个 Telegram 群组)。`target.platform` 选择平台:`discord`(默认)或 `telegram`。**Discord** 目标要求 `target.channelId`(可选 `target.threadId` 指定子区);**Telegram** 目标要求 `target.chatId`(群组/超级群组 id,如 `-1001234567890`),可选 `target.topicId`(话题的 `message_thread_id`,相当于 Discord 子区)。没有默认频道回退。
|
||||
`targets` 的每一项都是一个推送目标,因此一条路由可同时转发到多个频道(例如一个 Discord 频道**和**一个 Telegram 群组)。`target.platform` 选择平台:`discord`(默认)、`telegram` 或 `feishu`。**Discord** 目标要求 `target.channelId`(可选 `target.threadId` 指定子区);**Telegram** 与 **飞书** 目标要求 `target.chatId`(群组/超级群组 id,如 `-1001234567890`),可选 `target.topicId`(话题的 `message_thread_id`,相当于 Discord 子区)。没有默认频道回退。
|
||||
|
||||
| 字段 | 类型 | 必需 | 说明 |
|
||||
| ---------------- | -------- | ---- | ------------------------------------------------------------------------------------- |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue