@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 +21 -0
- package/README.md +188 -0
- package/dist/channel.d.ts +166 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +730 -0
- package/dist/channel.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +52 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +129 -0
- package/dist/plugin.js.map +1 -0
- package/dist/types.d.ts +152 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +54 -0
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"}
|