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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.fr.md +501 -0
  3. package/README.md +504 -0
  4. package/README.zhHans.md +501 -0
  5. package/README.zhHant.md +501 -0
  6. package/README.zhHantHK.md +501 -0
  7. package/dist/accountMessages.d.ts +12 -0
  8. package/dist/accountMessages.js +41 -0
  9. package/dist/auth.d.ts +13 -0
  10. package/dist/auth.js +70 -0
  11. package/dist/calendars.d.ts +84 -0
  12. package/dist/calendars.js +278 -0
  13. package/dist/callbacks.d.ts +384 -0
  14. package/dist/callbacks.js +712 -0
  15. package/dist/chats.d.ts +22 -0
  16. package/dist/chats.js +88 -0
  17. package/dist/client.d.ts +439 -0
  18. package/dist/client.js +712 -0
  19. package/dist/config.d.ts +14 -0
  20. package/dist/config.js +42 -0
  21. package/dist/constants.d.ts +30 -0
  22. package/dist/constants.js +187 -0
  23. package/dist/contacts.d.ts +38 -0
  24. package/dist/contacts.js +161 -0
  25. package/dist/departments.d.ts +18 -0
  26. package/dist/departments.js +69 -0
  27. package/dist/exceptions.d.ts +20 -0
  28. package/dist/exceptions.js +42 -0
  29. package/dist/groupMessages.d.ts +11 -0
  30. package/dist/groupMessages.js +39 -0
  31. package/dist/groups.d.ts +66 -0
  32. package/dist/groups.js +218 -0
  33. package/dist/http.d.ts +7 -0
  34. package/dist/http.js +67 -0
  35. package/dist/index.d.ts +24 -0
  36. package/dist/index.js +189 -0
  37. package/dist/media.d.ts +16 -0
  38. package/dist/media.js +178 -0
  39. package/dist/models.d.ts +925 -0
  40. package/dist/models.js +991 -0
  41. package/dist/oauth.d.ts +17 -0
  42. package/dist/oauth.js +107 -0
  43. package/dist/persistence.d.ts +26 -0
  44. package/dist/persistence.js +210 -0
  45. package/dist/reminders.d.ts +10 -0
  46. package/dist/reminders.js +31 -0
  47. package/dist/streaming.d.ts +9 -0
  48. package/dist/streaming.js +40 -0
  49. package/dist/todos.d.ts +75 -0
  50. package/dist/todos.js +282 -0
  51. package/dist/urlHelpers.d.ts +7 -0
  52. package/dist/urlHelpers.js +22 -0
  53. package/dist/userMessages.d.ts +8 -0
  54. package/dist/userMessages.js +34 -0
  55. package/dist/users.d.ts +6 -0
  56. package/dist/users.js +32 -0
  57. package/package.json +33 -0
@@ -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
+ 藍信(Lansenger)平臺的 TypeScript SDK — 支援 藍信應用、組織機器人 及 個人機器人。
6
+
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+ [![TypeScript 5+](https://img.shields.io/badge/TypeScript-5%2B-blue)](https://www.typescriptlang.org/)
9
+ [![Node 18+](https://img.shields.io/badge/Node-18%2B-green)](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、換取 userToken、刷新令牌
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` 自動取得並刷新 appToken,透過 `GET /v1/apptoken/create` 端點。你無需手動管理 appToken — `TokenManager` 負責整個生命週期:
62
+
63
+ 1. **首次呼叫** → 使用 app_id + app_secret 請求 `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
+ ### 解析回調載荷(自動辨識加密/明文)
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 # 5 語言 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)。