@bundy-lmw/hive-plugin-feishu 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 bundy-lmw
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,188 @@
1
+ # @bundy-lmw/hive-plugin-feishu
2
+
3
+ 飞书消息通道插件,基于 `@larksuiteoapi/node-sdk` 实现。
4
+
5
+ ## 功能
6
+
7
+ - ✅ 接收飞书消息事件(通过 Webhook)
8
+ - ✅ 发送文本消息
9
+ - ✅ 发送卡片消息
10
+ - ✅ 发送 Markdown 消息
11
+ - ✅ 回复消息
12
+ - ✅ 多租户支持(多个飞书应用)
13
+ - ✅ 签名验证
14
+ - ✅ Challenge 响应
15
+
16
+ ## 安装
17
+
18
+ ```bash
19
+ pnpm add @bundy-lmw/hive-plugin-feishu
20
+ ```
21
+
22
+ ## 配置
23
+
24
+ 在 `hive.config.json` 中添加插件配置:
25
+
26
+ ```json
27
+ {
28
+ "plugins": {
29
+ "@bundy-lmw/hive-plugin-feishu": {
30
+ "apps": [
31
+ {
32
+ "appId": "${FEISHU_APP_ID}",
33
+ "appSecret": "${FEISHU_APP_SECRET}"
34
+ }
35
+ ]
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ ### 环境变量
42
+
43
+ ```bash
44
+ # .env - 必需
45
+ FEISHU_APP_ID=cli_xxxxxx
46
+ FEISHU_APP_SECRET=xxxxxx
47
+
48
+ # 可选(用于签名验证)
49
+ # FEISHU_ENCRYPT_KEY=xxxxxx
50
+ # FEISHU_VERIFY_TOKEN=xxxxxx
51
+ ```
52
+
53
+ ### 多租户配置
54
+
55
+ ```json
56
+ {
57
+ "plugins": {
58
+ "@bundy-lmw/hive-plugin-feishu": {
59
+ "apps": [
60
+ {
61
+ "appId": "${FEISHU_APP_ID_1}",
62
+ "appSecret": "${FEISHU_APP_SECRET_1}"
63
+ },
64
+ {
65
+ "appId": "${FEISHU_APP_ID_2}",
66
+ "appSecret": "${FEISHU_APP_SECRET_2}"
67
+ }
68
+ ]
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ## Webhook 配置
75
+
76
+ ### 1. 飞书开放平台权限配置
77
+
78
+ 在飞书开放平台 → 应用 → 权限管理中开通以下权限:
79
+
80
+ | 权限 | 说明 |
81
+ |------|------|
82
+ | `im:message` | 获取与发送单聊、群组消息 |
83
+ | `im:message:send_as_bot` | 以应用身份发消息 |
84
+ | `im:chat` | 获取群组信息 |
85
+ | `im:chat:readonly` | 获取群组列表 |
86
+
87
+ ### 2. 事件订阅
88
+
89
+ 在飞书开放平台 → 应用 → 事件订阅中配置:
90
+
91
+ ```
92
+ https://your-server.com/webhook/feishu/{appId}
93
+ ```
94
+
95
+ 订阅事件:
96
+ - `im.message.receive_v1` - 接收消息
97
+
98
+ ### 3. 机器人能力
99
+
100
+ 在飞书开放平台 → 应用 → 机器人配置中:
101
+ - 启用机器人
102
+ - 配置消息卡片(可选)
103
+
104
+ ## 消息格式
105
+
106
+ ### 接收消息
107
+
108
+ 插件会将飞书消息转换为通用 `ChannelMessage` 格式:
109
+
110
+ ```typescript
111
+ interface ChannelMessage {
112
+ id: string // 消息 ID
113
+ content: string // 消息内容
114
+ type: 'text' | 'image' | 'file' | 'card' | 'markdown'
115
+ from: {
116
+ id: string // 发送者 ID
117
+ type: 'user'
118
+ }
119
+ to: {
120
+ id: string // 群聊 ID
121
+ type: 'group'
122
+ }
123
+ timestamp: number // 时间戳(毫秒)
124
+ raw: unknown // 原始飞书事件
125
+ }
126
+ ```
127
+
128
+ ### 发送消息
129
+
130
+ ```typescript
131
+ // 获取通道
132
+ const channel = plugin.getChannelByAppId('cli_xxxxxx')
133
+
134
+ // 发送文本消息
135
+ await channel.send({
136
+ to: 'oc_xxxxxx', // 群聊 ID
137
+ content: 'Hello!',
138
+ type: 'text'
139
+ })
140
+
141
+ // 发送 Markdown 消息
142
+ await channel.send({
143
+ to: 'oc_xxxxxx',
144
+ content: '# Title\n\n**Bold text**',
145
+ type: 'markdown'
146
+ })
147
+
148
+ // 发送卡片消息
149
+ await channel.send({
150
+ to: 'oc_xxxxxx',
151
+ content: JSON.stringify({
152
+ type: 'template',
153
+ data: {
154
+ template_id: 'xxxxx'
155
+ }
156
+ }),
157
+ type: 'card'
158
+ })
159
+ ```
160
+
161
+ ## 事件
162
+
163
+ 插件通过 MessageBus 发布以下事件:
164
+
165
+ | 事件 | 描述 |
166
+ |------|------|
167
+ | `channel:feishu:{appId}:message:received` | 收到飞书消息 |
168
+
169
+ ## 开发
170
+
171
+ ```bash
172
+ # 构建
173
+ pnpm build
174
+
175
+ # 监视模式
176
+ pnpm dev
177
+
178
+ # 测试
179
+ pnpm test
180
+ ```
181
+
182
+ ## GitHub
183
+
184
+ [https://github.com/1695365384/hive](https://github.com/1695365384/hive)
185
+
186
+ ## 许可证
187
+
188
+ MIT
@@ -0,0 +1,166 @@
1
+ /**
2
+ * @bundy-lmw/hive-plugin-feishu - FeishuChannel
3
+ *
4
+ * 飞书通道实现,基于 @larksuiteoapi/node-sdk。
5
+ * 支持 WebSocket 长连接模式(推荐)和 Webhook 模式(备用)。
6
+ */
7
+ import * as lark from '@larksuiteoapi/node-sdk';
8
+ import type { ChannelCapabilities, ChannelSendOptions, ChannelSendResult, IMessageBus, ILogger } from '@bundy-lmw/hive-core';
9
+ import type { FeishuAppConfig, IFeishuChannel } from './types.js';
10
+ /**
11
+ * 飞书通道实现
12
+ */
13
+ export declare class FeishuChannel implements IFeishuChannel {
14
+ readonly id: string;
15
+ readonly name: string;
16
+ readonly type = "feishu";
17
+ readonly appId: string;
18
+ readonly capabilities: ChannelCapabilities;
19
+ /** HTTP API 客户端,用于发送消息 */
20
+ private client;
21
+ /** WebSocket 客户端,用于接收事件 */
22
+ private wsClient;
23
+ /** 事件分发器 */
24
+ private dispatcher;
25
+ private messageBus;
26
+ private logger;
27
+ private config;
28
+ /** 文件接收存储目录 */
29
+ private readonly receivedDir;
30
+ constructor(config: FeishuAppConfig, messageBus: IMessageBus, logger: ILogger, workspaceDir?: string | null);
31
+ /**
32
+ * 获取飞书 HTTP API Client
33
+ */
34
+ getClient(): lark.Client;
35
+ /**
36
+ * 启动通道 — 建立 WebSocket 长连接
37
+ */
38
+ start(): Promise<void>;
39
+ /**
40
+ * 停止通道 — 关闭 WebSocket 连接
41
+ */
42
+ stop(): Promise<void>;
43
+ /**
44
+ * 发送消息
45
+ */
46
+ send(options: ChannelSendOptions): Promise<ChannelSendResult>;
47
+ /**
48
+ * 回复消息
49
+ */
50
+ reply(messageId: string, options: ChannelSendOptions): Promise<ChannelSendResult>;
51
+ /**
52
+ * 处理 Webhook 请求(Webhook 模式备用)
53
+ */
54
+ handleWebhook(body: unknown, signature: string, timestamp: string, nonce: string): Promise<unknown>;
55
+ /**
56
+ * 处理 WebSocket 接收到的消息事件
57
+ */
58
+ private handleWSMessageEvent;
59
+ /**
60
+ * 转换 WebSocket 事件数据为通用消息格式
61
+ */
62
+ private convertWSMessage;
63
+ /**
64
+ * 处理 Webhook 消息事件
65
+ */
66
+ private handleWebhookMessageEvent;
67
+ /**
68
+ * 转换 Webhook 消息为通用格式
69
+ */
70
+ private convertWebhookMessage;
71
+ /**
72
+ * 从飞书 interactive card JSON 中提取纯文本
73
+ *
74
+ * 卡片结构: { header: { title: { content } }, elements: [{ tag: "div", text: { content } }] }
75
+ */
76
+ private extractCardText;
77
+ /**
78
+ * 递归提取 elements 数组中的文本内容
79
+ */
80
+ private extractElementsText;
81
+ /**
82
+ * 从飞书 post JSON 中提取纯文本
83
+ *
84
+ * Post 结构: { zh_cn: { title, content: [{ tag: "text", text: "..." }] } }
85
+ */
86
+ private extractPostText;
87
+ /**
88
+ * 将 Markdown 文本构建为飞书 interactive card JSON
89
+ *
90
+ * 使用 lark_md tag 让卡片内支持 Markdown 渲染
91
+ */
92
+ private buildCardContent;
93
+ /**
94
+ * 图片扩展名集合
95
+ */
96
+ private static readonly IMAGE_EXTENSIONS;
97
+ /**
98
+ * 根据文件扩展名推断发送类型
99
+ */
100
+ private resolveSendType;
101
+ /**
102
+ * 上传文件到飞书
103
+ *
104
+ * @returns file_key
105
+ */
106
+ private uploadFile;
107
+ /**
108
+ * 上传图片到飞书
109
+ *
110
+ * @returns image_key
111
+ */
112
+ private uploadImage;
113
+ /**
114
+ * 下载文件并保存到本地
115
+ *
116
+ * @returns 本地文件路径,失败时返回 null
117
+ */
118
+ private downloadFile;
119
+ /**
120
+ * 下载图片并保存到本地
121
+ *
122
+ * @returns 本地文件路径,失败时返回 null
123
+ */
124
+ private downloadImage;
125
+ /**
126
+ * 发送文件消息(上传 + 发送)
127
+ */
128
+ private sendFileMessage;
129
+ /**
130
+ * 发送图片消息(上传 + 发送)
131
+ */
132
+ private sendImageMessage;
133
+ /**
134
+ * 回复文件消息(上传 + 回复)
135
+ */
136
+ private replyFileMessage;
137
+ /**
138
+ * 回复图片消息(上传 + 回复)
139
+ */
140
+ private replyImageMessage;
141
+ /**
142
+ * 验证飞书签名
143
+ */
144
+ private verifySignature;
145
+ /**
146
+ * 处理接收到的图片消息,下载并返回本地路径
147
+ */
148
+ private handleReceiveImage;
149
+ /**
150
+ * 处理接收到的文件消息,下载并返回本地路径
151
+ */
152
+ private handleReceiveFile;
153
+ /**
154
+ * 构建消息内容
155
+ */
156
+ private buildContent;
157
+ /**
158
+ * 映射消息类型(发送时)
159
+ */
160
+ private mapMessageType;
161
+ /**
162
+ * 映射到通用消息类型(接收时)
163
+ */
164
+ private mapToMessageType;
165
+ }
166
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,IAAI,MAAM,yBAAyB,CAAA;AAI/C,OAAO,KAAK,EAEV,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,WAAW,EACX,OAAO,EAER,MAAM,sBAAsB,CAAA;AAC7B,OAAO,KAAK,EACV,eAAe,EAIf,cAAc,EACf,MAAM,YAAY,CAAA;AASnB;;GAEG;AACH,qBAAa,aAAc,YAAW,cAAc;IAClD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,YAAW;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IAEtB,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CASzC;IAED,0BAA0B;IAC1B,OAAO,CAAC,MAAM,CAAa;IAC3B,2BAA2B;IAC3B,OAAO,CAAC,QAAQ,CAAe;IAC/B,YAAY;IACZ,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAiB;IAC/B,eAAe;IACf,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;gBAExB,MAAM,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI;IA2C3G;;OAEG;IACH,SAAS,IAAI,IAAI,CAAC,MAAM;IAIxB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAU5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B;;OAEG;IACG,IAAI,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA4CnE;;OAEG;IACG,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA2CvF;;OAEG;IACG,aAAa,CACjB,IAAI,EAAE,OAAO,EACb,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC;IA6BnB;;OAEG;YACW,oBAAoB;IAYlC;;OAEG;YACW,gBAAgB;IAqD9B;;OAEG;YACW,yBAAyB;IAYvC;;OAEG;YACW,qBAAqB;IAyCnC;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAqBvB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA2B3B;;;;OAIG;IACH,OAAO,CAAC,eAAe;IA0BvB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAiBxB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAEtC;IAEF;;OAEG;IACH,OAAO,CAAC,eAAe;IASvB;;;;OAIG;YACW,UAAU;IAwBxB;;;;OAIG;YACW,WAAW;IAoBzB;;;;OAIG;YACW,YAAY;IAoB1B;;;;OAIG;YACW,aAAa;IAmB3B;;OAEG;YACW,eAAe;IAuB7B;;OAEG;YACW,gBAAgB;IAuB9B;;OAEG;YACW,gBAAgB;IAsB9B;;OAEG;YACW,iBAAiB;IAsB/B;;OAEG;IACH,OAAO,CAAC,eAAe;IAoBvB;;OAEG;YACW,kBAAkB;IAahC;;OAEG;YACW,iBAAiB;IAc/B;;OAEG;IACH,OAAO,CAAC,YAAY;IAYpB;;OAEG;IACH,OAAO,CAAC,cAAc;IAWtB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAazB"}