lansenger-sdk-ts 1.0.0
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.fr.md +501 -0
- package/README.md +504 -0
- package/README.zhHans.md +501 -0
- package/README.zhHant.md +501 -0
- package/README.zhHantHK.md +501 -0
- package/dist/accountMessages.d.ts +12 -0
- package/dist/accountMessages.js +41 -0
- package/dist/auth.d.ts +13 -0
- package/dist/auth.js +70 -0
- package/dist/calendars.d.ts +84 -0
- package/dist/calendars.js +278 -0
- package/dist/callbacks.d.ts +384 -0
- package/dist/callbacks.js +712 -0
- package/dist/chats.d.ts +22 -0
- package/dist/chats.js +88 -0
- package/dist/client.d.ts +439 -0
- package/dist/client.js +712 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.js +42 -0
- package/dist/constants.d.ts +30 -0
- package/dist/constants.js +187 -0
- package/dist/contacts.d.ts +38 -0
- package/dist/contacts.js +161 -0
- package/dist/departments.d.ts +18 -0
- package/dist/departments.js +69 -0
- package/dist/exceptions.d.ts +20 -0
- package/dist/exceptions.js +42 -0
- package/dist/groupMessages.d.ts +11 -0
- package/dist/groupMessages.js +39 -0
- package/dist/groups.d.ts +66 -0
- package/dist/groups.js +218 -0
- package/dist/http.d.ts +7 -0
- package/dist/http.js +67 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +189 -0
- package/dist/media.d.ts +16 -0
- package/dist/media.js +178 -0
- package/dist/models.d.ts +925 -0
- package/dist/models.js +991 -0
- package/dist/oauth.d.ts +17 -0
- package/dist/oauth.js +107 -0
- package/dist/persistence.d.ts +26 -0
- package/dist/persistence.js +210 -0
- package/dist/reminders.d.ts +10 -0
- package/dist/reminders.js +31 -0
- package/dist/streaming.d.ts +9 -0
- package/dist/streaming.js +40 -0
- package/dist/todos.d.ts +75 -0
- package/dist/todos.js +282 -0
- package/dist/urlHelpers.d.ts +7 -0
- package/dist/urlHelpers.js +22 -0
- package/dist/userMessages.d.ts +8 -0
- package/dist/userMessages.js +34 -0
- package/dist/users.d.ts +6 -0
- package/dist/users.js +32 -0
- package/package.json +33 -0
package/README.zhHans.md
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
[English](README.md) | [简体中文](README.zhHans.md) | [繁体中文](README.zhHant.md) | [繁体中文香港](README.zhHantHK.md) | [Français](README.fr.md)
|
|
2
|
+
|
|
3
|
+
# lansenger-sdk-ts
|
|
4
|
+
|
|
5
|
+
蓝信平台的 TypeScript SDK — 支持蓝信应用、组织机器人和个人机器人。
|
|
6
|
+
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](https://nodejs.org/)
|
|
10
|
+
|
|
11
|
+
> 零框架依赖 — 仅依赖 `node-fetch` (v2,CommonJS 兼容)。适用于任何 Node.js 项目。
|
|
12
|
+
|
|
13
|
+
## 支持的机器人类型
|
|
14
|
+
|
|
15
|
+
| 机器人类型 | 认证方式 | WebSocket 入站 | 全部 API |
|
|
16
|
+
|-----------|---------|----------------|---------|
|
|
17
|
+
| **蓝信应用** | appToken + userToken | ✗ (使用 webhook) | ✓ |
|
|
18
|
+
| **组织机器人** | appToken + userToken | ✗ (使用 webhook) | ✓ |
|
|
19
|
+
| **个人机器人** | appToken | ✓ (WebSocket) | ✓ (非机器人 API 有限) |
|
|
20
|
+
|
|
21
|
+
所有三种机器人类型使用相同的认证机制:每次 API 调用都需要 `appToken`;`userToken` 仅用于特定用户级操作(用户信息、员工搜索、日历等)。
|
|
22
|
+
|
|
23
|
+
## 功能
|
|
24
|
+
|
|
25
|
+
- **异步客户端** — `LansengerClient` 提供 Promise 化 API
|
|
26
|
+
- **凭据与令牌持久化** — `CredentialStore` 将 app_id、app_secret、URL、appToken、userToken 保存到文件(重启后仍有效)
|
|
27
|
+
- **OAuth2 用户认证** — 授权 URL、代码兑换、令牌刷新
|
|
28
|
+
- **组织与部门** — 组织信息、部门详情/子部门/员工
|
|
29
|
+
- **员工与通讯录** — 基础/详细信息、ID 映射、部门祖先链、搜索
|
|
30
|
+
- **消息** — 3 种私聊通道(机器人、公众号、用户模拟)+ 群聊,全部消息类型,@提及,人/机器人发送者身份,紧急提醒
|
|
31
|
+
- **富卡片** — appCard(支持动态状态更新)、oacard、linkCard、appArticles
|
|
32
|
+
- **流式消息** — SSE 实时推送,适用于 AI Agent
|
|
33
|
+
- **媒体上传/下载** — 文件、图片、视频,自动类型检测,媒体路径获取
|
|
34
|
+
- **消息管理** — 撤回、动态卡片更新
|
|
35
|
+
- **群组** — 创建、信息、成员、列表、成员检查、更新设置与成员、解散
|
|
36
|
+
- **日历与日程** — 主日历、日程 CRUD + 更新、参会人管理 + 参会人元数据
|
|
37
|
+
- **统一待办** — 创建、更新、删除、查询、执行人管理、状态计数
|
|
38
|
+
- **回调事件** — 25 种事件类型,结构化解析,AES 解密(4.10.1.4),SHA1 签名验证
|
|
39
|
+
- **聊天读取** — 获取聊天列表、获取聊天消息(4.24 MCP)
|
|
40
|
+
|
|
41
|
+
## 快速安装
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install lansenger-sdk-ts
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
开发环境:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/lansenger-pm/lansenger-sdk-ts.git
|
|
51
|
+
cd lansenger-sdk-ts
|
|
52
|
+
npm install
|
|
53
|
+
npm run build
|
|
54
|
+
npm test
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 1. 认证
|
|
58
|
+
|
|
59
|
+
### appToken — 所有 API 调用必需
|
|
60
|
+
|
|
61
|
+
每个 SDK 方法都需要 `appToken`。客户端使用 `app_id` + `app_secret` 自动获取和刷新。`TokenManager` 处理整个生命周期:
|
|
62
|
+
|
|
63
|
+
1. **首次调用** → `GET /v1/apptoken/create` → 返回 `appToken`(有效期 2 小时)
|
|
64
|
+
2. **后续调用** → 复用缓存的 appToken 直到过期
|
|
65
|
+
3. **令牌过期** → 自动通过相同端点刷新
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { LansengerClient } from "lansenger-sdk-ts";
|
|
69
|
+
|
|
70
|
+
const client = new LansengerClient("你的-appid", "你的-secret");
|
|
71
|
+
|
|
72
|
+
// 也可以手动获取/失效令牌
|
|
73
|
+
const token = await client.getToken();
|
|
74
|
+
client.invalidateToken(); // 强制下次调用刷新
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### userToken — 仅特定端点需要
|
|
78
|
+
|
|
79
|
+
`userToken` 代表特定蓝信用户的授权(通过 OAuth2 获得)。仅在以下场景需要:
|
|
80
|
+
- 用户级信息(fetchUserInfo、fetchStaffDetail、searchStaff)
|
|
81
|
+
- 日历与日程操作(fetchPrimaryCalendar、createSchedule 等)
|
|
82
|
+
- 作为人类发送者的群组操作
|
|
83
|
+
|
|
84
|
+
### 获取凭据
|
|
85
|
+
|
|
86
|
+
| 机器人类型 | 如何获取 app_id + app_secret |
|
|
87
|
+
|-----------|------------------------------|
|
|
88
|
+
| **个人机器人** | 蓝信桌面 → 通讯录 → 智能机器人 → 个人机器人 → 点击 ℹ️ 图标(手机客户端不显示凭据) |
|
|
89
|
+
| **蓝信应用** | 在蓝信开发者中心创建 — 可能需要组织管理员审批 |
|
|
90
|
+
| **组织机器人** | 在蓝信开发者中心创建 — 可能需要组织管理员审批 |
|
|
91
|
+
|
|
92
|
+
### OAuth2 用户级认证
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// 构建授权 URL — 将用户重定向到蓝信通行证
|
|
96
|
+
const url = client.buildAuthorizeUrl("https://myapp.com/callback");
|
|
97
|
+
|
|
98
|
+
// 用户授权后,兑换 code 获取 userToken + refreshToken
|
|
99
|
+
const tokenResult = await client.exchangeCode("回调中的授权码");
|
|
100
|
+
|
|
101
|
+
// 刷新过期的 userToken
|
|
102
|
+
const newToken = await client.refreshUserToken(tokenResult.refresh_token!);
|
|
103
|
+
|
|
104
|
+
// 获取用户信息
|
|
105
|
+
const userInfo = await client.fetchUserInfo(tokenResult.user_token!);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 工厂方法
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// 从环境变量(LANSENGER_APP_ID、LANSENGER_APP_SECRET 等)
|
|
112
|
+
const client = LansengerClient.fromEnv();
|
|
113
|
+
|
|
114
|
+
// 从 LansengerConfig 对象
|
|
115
|
+
const config = new LansengerConfig("appid", "secret", "https://open.e.lanxin.cn/open/apigw");
|
|
116
|
+
const client = LansengerClient.fromConfig(config);
|
|
117
|
+
|
|
118
|
+
// 从 CredentialStore(读取持久化凭据)
|
|
119
|
+
const client = LansengerClient.fromStore();
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## 2. 组织与部门
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// 组织信息
|
|
126
|
+
const org = await client.fetchOrgInfo("orgId");
|
|
127
|
+
|
|
128
|
+
// 部门层级
|
|
129
|
+
const detail = await client.fetchDepartmentDetail("deptId");
|
|
130
|
+
const children = await client.fetchDepartmentChildren("deptId");
|
|
131
|
+
const staffs = await client.fetchDepartmentStaffs("deptId");
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## 3. 员工与通讯录
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// 基础员工信息
|
|
138
|
+
const staff = await client.fetchStaffBasicInfo("staffOpenId");
|
|
139
|
+
|
|
140
|
+
// 详细资料(建议提供 userToken)
|
|
141
|
+
const detail = await client.fetchStaffDetail("staffOpenId", { user_token: "ut" });
|
|
142
|
+
|
|
143
|
+
// 手机号 → staffId 映射
|
|
144
|
+
const mapping = await client.fetchStaffIdMapping("orgId", "mobile", "13800138000");
|
|
145
|
+
|
|
146
|
+
// 员工的部门祖先链
|
|
147
|
+
const ancestors = await client.fetchDepartmentAncestors("staffOpenId");
|
|
148
|
+
|
|
149
|
+
// 搜索员工(需要 userToken)
|
|
150
|
+
const results = await client.searchStaff("张三", { user_token: "ut" });
|
|
151
|
+
|
|
152
|
+
// 组织扩展字段 ID
|
|
153
|
+
const fields = await client.fetchOrgExtraFieldIds("orgId");
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## 4. 消息与媒体
|
|
157
|
+
|
|
158
|
+
#### 机器人私聊 — 最常用
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const result = await client.sendText("staff123", "你好!");
|
|
162
|
+
const result = await client.sendMarkdown("staff123", "**加粗**");
|
|
163
|
+
const result = await client.sendFile("staff123", "/path/to/report.pdf");
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### 公号通道
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
const result = await client.sendAccountMessage(
|
|
170
|
+
"text", { text: { content: "系统通知" } },
|
|
171
|
+
["staff1", "staff2"], undefined,
|
|
172
|
+
{ account_id: "524288-xxxx" },
|
|
173
|
+
);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### 用户模拟通道(需要 userToken)
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const result = await client.sendUserMessage(
|
|
180
|
+
"staff456", "text", { text: { content: "你好" } },
|
|
181
|
+
{ user_token: "ut" },
|
|
182
|
+
);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
#### 群聊
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// 机器人 → 群
|
|
189
|
+
const result = await client.sendText("group123", "通知", { is_group: true });
|
|
190
|
+
|
|
191
|
+
// 人类 → 群(需要 userToken)
|
|
192
|
+
const result = await client.sendGroupMessage(
|
|
193
|
+
"group123", "text", { text: { content: "我来处理" } },
|
|
194
|
+
{ user_token: "ut" },
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// 群聊 @提及
|
|
198
|
+
const result = await client.sendText("group123", "重要!", { is_group: true, reminder_all: true });
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### 富卡片
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
const result = await client.sendAppCard("staff123", "审批", { is_dynamic: true });
|
|
205
|
+
const result = await client.sendLinkCard("staff123", "文章", "https://...");
|
|
206
|
+
const result = await client.sendAppArticles("staff123", [{ title: "文章1", link: "..." }]);
|
|
207
|
+
|
|
208
|
+
// 更新动态卡片状态
|
|
209
|
+
const result = await client.updateDynamicCard("msg123", { is_last_update: true });
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### 流式消息(适用于 AI Agent)
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
const result = await client.createStreamMessage("staff1", "staff", "stream-id-1");
|
|
216
|
+
const result = await client.fetchStreamMessage("msg123");
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
#### 媒体
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
// 上传
|
|
223
|
+
const upload = await client.uploadMediaFile("/path/to/file.pdf");
|
|
224
|
+
|
|
225
|
+
// 下载
|
|
226
|
+
const download = await client.downloadMediaFile("media123");
|
|
227
|
+
|
|
228
|
+
// 保存到文件
|
|
229
|
+
const filePath = await client.downloadMediaToFile("media123", { target_path: "/tmp/file.pdf" });
|
|
230
|
+
|
|
231
|
+
// 获取下载 URL 路径 (4.5.3)
|
|
232
|
+
const pathResult = await client.fetchMediaPathInfo("media123");
|
|
233
|
+
|
|
234
|
+
// 撤回消息
|
|
235
|
+
const result = await client.revokeMessage(["msg1", "msg2"]);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
#### 紧急提醒 (4.6.14)
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { REMINDER_TYPE_POPUP, REMINDER_TYPE_SMS } from "lansenger-sdk-ts";
|
|
242
|
+
|
|
243
|
+
const result = await client.sendReminderMsg(
|
|
244
|
+
"msg123",
|
|
245
|
+
[REMINDER_TYPE_POPUP, REMINDER_TYPE_SMS],
|
|
246
|
+
["staff1", "staff2"],
|
|
247
|
+
);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## 5. 群组
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// 创建群组
|
|
254
|
+
const group = await client.createGroup("项目讨论", "orgId", { staff_id_list: ["s1", "s2", "s3"] });
|
|
255
|
+
|
|
256
|
+
// 获取群信息与成员
|
|
257
|
+
const info = await client.fetchGroupInfo("groupOpenId");
|
|
258
|
+
const members = await client.fetchGroupMembers("groupOpenId");
|
|
259
|
+
const groups = await client.fetchGroupList();
|
|
260
|
+
|
|
261
|
+
// 成员检查
|
|
262
|
+
const result = await client.checkIsInGroup("groupOpenId", { staff_id: "staff1" });
|
|
263
|
+
|
|
264
|
+
// 更新设置
|
|
265
|
+
await client.updateGroupInfo("groupId", { name: "新名称", manage_mode: 1 });
|
|
266
|
+
|
|
267
|
+
// 添加/移除成员
|
|
268
|
+
await client.updateGroupMembers("groupId", { add_user_list: ["staff4"], del_user_list: ["staff3"] });
|
|
269
|
+
|
|
270
|
+
// 解散群组(仅群主,4.28.6)
|
|
271
|
+
await client.dismissGroup("groupId");
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## 6. 日历与日程
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// 获取主日历(需要 userToken 或 userId)
|
|
278
|
+
const cal = await client.fetchPrimaryCalendar({ user_token: "ut" });
|
|
279
|
+
|
|
280
|
+
// 创建日程
|
|
281
|
+
const schedule = await client.createSchedule(
|
|
282
|
+
cal.calendar_id!, "团队会议",
|
|
283
|
+
{ date: "2024-01-15", time: "10:00", timeZone: "Asia/Shanghai" },
|
|
284
|
+
{ date: "2024-01-15", time: "11:00", timeZone: "Asia/Shanghai" },
|
|
285
|
+
[{ staffId: "staff1", attendeeFlag: "required" }],
|
|
286
|
+
{ user_token: "ut" },
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// 获取/删除日程
|
|
290
|
+
const info = await client.fetchSchedule("cal1", "sch1", { user_token: "ut" });
|
|
291
|
+
await client.deleteSchedule("cal1", "sch1", { user_token: "ut" });
|
|
292
|
+
|
|
293
|
+
// 时间范围内的日程列表(最长 42 天)
|
|
294
|
+
const schedules = await client.fetchScheduleList("cal1", 1705276800000, 1707940800000, { user_token: "ut" });
|
|
295
|
+
|
|
296
|
+
// 参会人管理
|
|
297
|
+
const attendees = await client.fetchScheduleAttendees("cal1", "sch1", { user_token: "ut" });
|
|
298
|
+
await client.addScheduleAttendees("cal1", "sch1", ["staff2"], { user_token: "ut" });
|
|
299
|
+
await client.deleteScheduleAttendees("cal1", "sch1", ["staff2"], { user_token: "ut" });
|
|
300
|
+
|
|
301
|
+
// 更新日程 (4.23.12)
|
|
302
|
+
await client.updateSchedule("cal1", "sch1", { summary: "更新后的会议", user_token: "ut" });
|
|
303
|
+
|
|
304
|
+
// 更新参会人元数据 (4.23.17) — RSVP、颜色、忙闲状态、提醒
|
|
305
|
+
await client.updateScheduleAttendeeMeta("cal1", "sch1", {
|
|
306
|
+
rsvp_status: "accept", busy_free_state: "busy", remind_times: [5, 15], user_token: "ut",
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## 7. 统一待办
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { TODO_TYPE_APPROVAL, TODO_TODO_STATUS_DONE } from "lansenger-sdk-ts";
|
|
314
|
+
|
|
315
|
+
// 创建待办任务
|
|
316
|
+
const todo = await client.createTodoTask(
|
|
317
|
+
"审批请求", "https://app.com/a/1", "https://pc.app.com/a/1",
|
|
318
|
+
["staff1"], "org1", TODO_TYPE_APPROVAL,
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
// 更新状态(11=待阅, 12=已阅, 21=待办, 22=已办)
|
|
322
|
+
await client.updateTodoTaskStatus("taskId", TODO_TODO_STATUS_DONE, "org1");
|
|
323
|
+
|
|
324
|
+
// 更新内容
|
|
325
|
+
await client.updateTodoTask("taskId", "已更新", "l", "p", "org1");
|
|
326
|
+
|
|
327
|
+
// 删除(仅发送者)
|
|
328
|
+
await client.deleteTodoTask("taskId", "org1");
|
|
329
|
+
|
|
330
|
+
// 查询
|
|
331
|
+
const listResult = await client.fetchTodoTaskList("org1");
|
|
332
|
+
const task = await client.fetchTodoTaskById("taskId", "org1");
|
|
333
|
+
const task = await client.fetchTodoTaskBySourceId("src1", "org1");
|
|
334
|
+
const counts = await client.fetchTodoTaskStatusCounts("staff1", "org1");
|
|
335
|
+
|
|
336
|
+
// 执行人管理
|
|
337
|
+
await client.addExecutors(["staff2"], "org1", { todotask_id: "taskId" });
|
|
338
|
+
await client.deleteExecutors(["staff2"], "org1", { todotask_id: "taskId" });
|
|
339
|
+
const executors = await client.fetchExecutorList("taskId", "org1");
|
|
340
|
+
await client.updateExecutorStatus(
|
|
341
|
+
[{ executorId: "staff1", todotaskId: "taskId", status: "22" }],
|
|
342
|
+
"org1",
|
|
343
|
+
);
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## 8. 聊天读取 (4.24 MCP)
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
// 获取聊天列表(私聊 + 聊)
|
|
350
|
+
const chatList = await client.fetchChatList({ user_token: "ut" });
|
|
351
|
+
|
|
352
|
+
// 获取聊天消息
|
|
353
|
+
const messages = await client.fetchChatMessages({
|
|
354
|
+
staff_id: "staff1", // 或 group_id: "group1"
|
|
355
|
+
user_token: "ut",
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## 9. 回调事件
|
|
360
|
+
|
|
361
|
+
SDK 支持纯 JSON 和 AES 加密回调载荷(蓝信 API 规范 4.10.1.4)。
|
|
362
|
+
|
|
363
|
+
### 配置
|
|
364
|
+
|
|
365
|
+
设置 `encoding_key` 和 `callback_token`(从蓝信开发者中心回调设置获取):
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
const client = new LansengerClient("appid", "secret", undefined, undefined, undefined, undefined, "BASE64_AES_KEY", "CALLBACK_TOKEN");
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
或通过环境变量:`LANSENGER_ENCODING_KEY`、`LANSENGER_CALLBACK_TOKEN`。
|
|
372
|
+
|
|
373
|
+
### 解析回调载荷(自动检测加密 vs 纯 JSON)
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
import { parseCallbackPayload } from "lansenger-sdk-ts";
|
|
377
|
+
|
|
378
|
+
// 纯 JSON webhook
|
|
379
|
+
const events = parseCallbackPayload('{"events": [...]}');
|
|
380
|
+
|
|
381
|
+
// AES 加密载荷(使用 encodingKey 自动解密)
|
|
382
|
+
const events = parseCallbackPayload(encryptedData, {
|
|
383
|
+
encoding_key: "BASE64_AES_KEY",
|
|
384
|
+
known_app_id: "your-appid",
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### 验证签名
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
import { verifyCallbackSignature } from "lansenger-sdk-ts";
|
|
392
|
+
|
|
393
|
+
// sha1(sort(token, timestamp, nonce, dataEncrypt))
|
|
394
|
+
const isValid = verifyCallbackSignature(
|
|
395
|
+
timestamp, nonce, signature, encodingKey,
|
|
396
|
+
{ data_encrypt: encryptedData, callback_token: "CALLBACK_TOKEN" },
|
|
397
|
+
);
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### 事件类型
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
const types = LansengerClient.getCallbackEventTypes(); // 25 种事件类型,13 个类别
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## 消息类型能力矩阵
|
|
407
|
+
|
|
408
|
+
| msgType | Markdown | @提及 | 附件 | 私聊通道 | 群聊 | 备注 |
|
|
409
|
+
|---------|----------|-------|------|---------|------|------|
|
|
410
|
+
| `text` | ✗ | ✓ (群) | ✓ | 机器人、公号、用户模拟 | ✓ | 最多 6000 字节 |
|
|
411
|
+
| `formatText` | ✓ | ✗ | ✗ | 仅用户模拟 | ✓ | formatType=1 支持 Markdown |
|
|
412
|
+
| `oacard` | ✗ | ✗ | ✗ | 机器人、公号、用户模拟 | ✓ | 简单卡片带字段 |
|
|
413
|
+
| `appCard` | ✓ (div 标签) | ✗ | ✗ | 机器人、公号、用户模拟 | ✓ | 富卡片,支持动态更新 |
|
|
414
|
+
| `linkCard` | ✗ | ✗ | ✗ | 机器人、公号 | ✓ | 链接预览卡片 |
|
|
415
|
+
| `appArticles` | ✗ | ✗ | ✗ | 仅机器人私聊 | ✓ | 文章列表(1+ 篇) |
|
|
416
|
+
|
|
417
|
+
**群聊**支持所有消息类型。仅群聊支持 @提及。
|
|
418
|
+
|
|
419
|
+
## 配置
|
|
420
|
+
|
|
421
|
+
### 环境变量
|
|
422
|
+
|
|
423
|
+
| 变量 | 必需 | 描述 | 默认值 |
|
|
424
|
+
|------|------|------|--------|
|
|
425
|
+
| `LANSENGER_APP_ID` | ✓ | 应用/机器人 ID | — |
|
|
426
|
+
| `LANSENGER_APP_SECRET` | ✓ | 应用/机器人密钥 | — |
|
|
427
|
+
| `LANSENGER_API_GATEWAY_URL` | ✗ | API 网关 URL | `https://open.e.lanxin.cn/open/apigw` |
|
|
428
|
+
| `LANSENGER_PASSPORT_URL` | ✗ | 通行证 URL(OAuth2) | — |
|
|
429
|
+
| `LANSENGER_ENCODING_KEY` | ✗ | 回调 AES 加密密钥 (Base64) | — |
|
|
430
|
+
| `LANSENGER_CALLBACK_TOKEN` | ✗ | 回调签名令牌 | — |
|
|
431
|
+
|
|
432
|
+
### 凭据与令牌持久化
|
|
433
|
+
|
|
434
|
+
默认情况下,凭据和令牌仅保存在内存中(进程退出后丢失)。启用文件持久化使用 `storePath`:
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
import { LansengerClient, CredentialStore } from "lansenger-sdk-ts";
|
|
438
|
+
|
|
439
|
+
// 自动持久化到 ~/.lansenger/sdk_state.json
|
|
440
|
+
const client = new LansengerClient("appid", "secret", undefined, undefined, undefined, "~/.lansenger/sdk_state.json", "BASE64_AES_KEY", "CALLBACK_TOKEN");
|
|
441
|
+
|
|
442
|
+
// 或从环境变量加持久化
|
|
443
|
+
const client = LansengerClient.fromEnv("~/.lansenger/sdk_state.json");
|
|
444
|
+
|
|
445
|
+
// 手动 Store 操作
|
|
446
|
+
const store = new CredentialStore("~/.lansenger/sdk_state.json");
|
|
447
|
+
store.saveCredentials("app_id", "app_secret", "https://open.e.lanxin.cn/open/apigw");
|
|
448
|
+
store.saveUserToken("user_token", "refresh_token");
|
|
449
|
+
const token = store.loadAppToken(); // 过期则为 null
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## 项目结构
|
|
453
|
+
|
|
454
|
+
```
|
|
455
|
+
lansenger-sdk-ts/
|
|
456
|
+
├── src/
|
|
457
|
+
│ ├── index.ts # 所有导出
|
|
458
|
+
│ ├── client.ts # LansengerClient(异步)
|
|
459
|
+
│ ├── config.ts # LansengerConfig
|
|
460
|
+
│ ├── auth.ts # TokenManager — appToken 生命周期
|
|
461
|
+
│ ├── oauth.ts # OAuth2 辅助
|
|
462
|
+
│ ├── constants.ts # API 端点、媒体类型、OAuth scope
|
|
463
|
+
│ ├── exceptions.ts # LansengerError 层级
|
|
464
|
+
│ ├── models.ts # 38+ 结果类类型
|
|
465
|
+
│ ├── http.ts # doGet、doPost、doPostMultipart、parseApiResponse
|
|
466
|
+
│ ├── urlHelpers.ts # buildApiUrl (支持 pathVars)
|
|
467
|
+
│ ├── contacts.ts # 员工与组织信息 API
|
|
468
|
+
│ ├── departments.ts # 部门 API
|
|
469
|
+
│ ├── accountMessages.ts # 公号通道
|
|
470
|
+
│ ├── userMessages.ts # 用户模拟通道
|
|
471
|
+
│ ├── groupMessages.ts # 群聊通道
|
|
472
|
+
│ ├── media.ts # 上传/下载
|
|
473
|
+
│ ├── streaming.ts # SSE 流式
|
|
474
|
+
│ ├── persistence.ts # CredentialStore — 文件持久化
|
|
475
|
+
│ ├── callbacks.ts # 回调事件 — 25 种事件、AES 解密、SHA1 签名验证
|
|
476
|
+
│ ├── groups.ts # 群组 API(含解散 4.28.6)
|
|
477
|
+
│ ├── todos.ts # 统一待办
|
|
478
|
+
│ ├── calendars.ts # 日历与日程
|
|
479
|
+
│ ├── reminders.ts # 紧急提醒 (4.6.14)
|
|
480
|
+
│ ├── chats.ts # 聊天读取 (4.24 MCP)
|
|
481
|
+
│ └── users.ts # 用户信息
|
|
482
|
+
├── tests/ # 单元测试
|
|
483
|
+
├── package.json
|
|
484
|
+
├── tsconfig.json
|
|
485
|
+
├── jest.config.js
|
|
486
|
+
├── LICENSE
|
|
487
|
+
└── README*.md # 双语 README
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
## 开发
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
npm install
|
|
494
|
+
npm run build # 编译 TypeScript → dist/
|
|
495
|
+
npm test # 运行 Jest 测试
|
|
496
|
+
npm run lint # 仅类型检查 (tsc --noEmit)
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
## 许可证
|
|
500
|
+
|
|
501
|
+
MIT — 详见 [LICENSE](LICENSE)。
|