fix: feishu event

Signed-off-by: d0zingcat <iamtangli42@gmail.com>
This commit is contained in:
2026-01-14 13:35:57 +08:00
parent 9afe1f093c
commit 75306a67b1
3 changed files with 47 additions and 21 deletions

View File

@@ -10,6 +10,7 @@
### 修复
- **WebSocket 初始化**: 修复了 `@larksuiteoapi/node-sdk` v1.56.0+ 中 WebSocket 初始化不正确的 `TypeError`。现在正确使用了 `WSClient` 类并修复了参数类型错误。
- **事件处理**: 修正了 `im.chat.member.bot.added_v1` 事件的 Payload 解析逻辑。
- **Hono 兼容性**: 修正了 `feishu-event.ts``lark.adaptDefault` 的错误用法。改为使用手动 Challenge 处理和 `eventDispatcher.invoke`,解决了与 Hono 请求/响应对象的兼容性问题引发的编译错误。
- **群聊解绑**: 增加对 `im.chat.member.bot.deleted_v1` 事件的支持。当机器人被移除群聊时,自动清理 `known_group_chats``topic_group_chats` 关联,确保订阅关系自动解绑。
### 新增

View File

@@ -5,32 +5,39 @@ import { eventDispatcher } from '../event-handler';
const feishuEvent = new Hono();
// Helper to adapt Hono request to Lark SDK request
const adaptRequest = async (c: any) => {
const headers = c.req.raw.headers;
// Convert Headers object to Record<string, string>
const headerRecord: Record<string, string> = {};
headers.forEach((value: string, key: string) => {
headerRecord[key] = value;
});
return {
headers: headerRecord,
body: await c.req.json(),
};
};
feishuEvent.post('/', async (c) => {
try {
const req = await adaptRequest(c);
const headers = c.req.raw.headers;
const headerRecord: Record<string, string> = {};
headers.forEach((value, key) => {
headerRecord[key] = value;
});
// Use the official SDK to handle the request
// It handles URL verification, encryption, and dispatching
const res = await lark.adaptDefault('/api/feishu/event', req, eventDispatcher, {
autoChallenge: true,
encryptKey: process.env.FEISHU_ENCRYPT_KEY
}) as any;
const body = await c.req.json();
const req = {
headers: headerRecord,
body,
};
return c.json(res?.body || {});
// Use the official SDK functions directly for Hono compatibility
// 1. Handle URL verification (Challenge)
const { isChallenge, challenge } = lark.generateChallenge(body, {
encryptKey: process.env.FEISHU_ENCRYPT_KEY || ''
});
if (isChallenge) {
return c.json(challenge);
}
// 2. Dispatch event
// The dispatcher expects an object containing headers and body
const result = await eventDispatcher.invoke({
...req.body,
headers: req.headers
});
return c.json(result || {});
} catch (e) {
console.error('[Feishu Event] Error:', e);
return c.json({ error: 'Internal Server Error' }, 500);

View File

@@ -69,6 +69,23 @@ The database schema is defined in `apps/server/src/db/schema.ts`.
- `name`: Group name.
- `lastActiveAt`: Timestamp of last event from this group.
- **Purpose**: Caches groups the bot has been added to, facilitating easy selection in the UI.
7. **Alert Task** (`alert_tasks`)
- `id`: UUID (Primary Key).
- `topicSlug`: The slug of the target topic (or `NULL` for DM).
- `senderId`: Foreign Key -> `users.id` (who triggered the webhook).
- `status`: `pending`, `processing`, `completed`, or `failed`.
- `recipientCount`: Total recipients (subscribers + groups).
- `successCount`: Number of successful deliveries.
- `payload`: Snapshot of the incoming webhook body (JSONB).
- `error`: Last error message if failed.
- **Purpose**: Tracks the lifecycle of a single alert ingestion events.
8. **Alert Log** (`alert_logs`)
- `id`: UUID (Primary Key).
- `taskId`: Foreign Key -> `alert_tasks.id`.
- `userId`: Target user open_id (snapshot).
- `status`: `sent` or `failed`.
- **Purpose**: Granular tracking for each individual delivery within a task.
## 4. Key Workflows
@@ -157,6 +174,7 @@ The database schema is defined in `apps/server/src/db/schema.ts`.
### Feishu Event
- `POST /api/feishu/event`: Endpoint for receiving Feishu events (Webhook mode).
- **Note**: This endpoint uses **manual challenge handling** (`lark.generateChallenge`) and `eventDispatcher.invoke` instead of the SDK's `adaptDefault` to maintain compatibility with Hono's non-standard Node.js response object.
### Webhook
- `POST /api/webhook/:token/topic/:slug`: Trigger an alert for a topic.