@jiexiaoyin/wecom-api 0.0.2

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 (58) hide show
  1. package/README.md +228 -0
  2. package/config.example.json +7 -0
  3. package/config.js +76 -0
  4. package/docs/approval-templates.example.json +11 -0
  5. package/docs/nginx-mirror.md +193 -0
  6. package/openclaw.plugin.json +15 -0
  7. package/package.json +34 -0
  8. package/plugin.cjs +172 -0
  9. package/plugin.ts +136 -0
  10. package/skills/wecom-api/SKILL.md +40 -0
  11. package/skills/wecom-api/index.js +288 -0
  12. package/skills/wecom-api/openclaw.plugin.json +10 -0
  13. package/src/callback-helper.js +198 -0
  14. package/src/config.cjs +286 -0
  15. package/src/core/permission.js +479 -0
  16. package/src/crypto.js +130 -0
  17. package/src/index.js +199 -0
  18. package/src/modules/addressbook/index.js +413 -0
  19. package/src/modules/addressbook_cache/index.js +365 -0
  20. package/src/modules/advanced/index.js +159 -0
  21. package/src/modules/app/index.js +102 -0
  22. package/src/modules/approval/index.js +146 -0
  23. package/src/modules/auth/index.js +103 -0
  24. package/src/modules/callback/index.js +1180 -0
  25. package/src/modules/chain/index.js +193 -0
  26. package/src/modules/checkin/index.js +142 -0
  27. package/src/modules/checkin_rules/index.js +251 -0
  28. package/src/modules/contact/index.js +481 -0
  29. package/src/modules/contact_stats/index.js +349 -0
  30. package/src/modules/custom/index.js +140 -0
  31. package/src/modules/customer/index.js +51 -0
  32. package/src/modules/disk/index.js +245 -0
  33. package/src/modules/document/index.js +282 -0
  34. package/src/modules/hr/index.js +93 -0
  35. package/src/modules/intelligence/index.js +346 -0
  36. package/src/modules/kf/index.js +74 -0
  37. package/src/modules/live/index.js +122 -0
  38. package/src/modules/media/index.js +183 -0
  39. package/src/modules/meeting/index.js +665 -0
  40. package/src/modules/message/index.js +402 -0
  41. package/src/modules/messenger/index.js +208 -0
  42. package/src/modules/moments/index.js +161 -0
  43. package/src/modules/msgaudit/index.js +24 -0
  44. package/src/modules/notify/index.js +81 -0
  45. package/src/modules/oceanengine/index.js +199 -0
  46. package/src/modules/openchat/index.js +197 -0
  47. package/src/modules/phone/index.js +45 -0
  48. package/src/modules/room/index.js +178 -0
  49. package/src/modules/schedule/index.js +246 -0
  50. package/src/modules/school/index.js +199 -0
  51. package/src/modules/security/index.js +223 -0
  52. package/src/modules/sensitive/index.js +170 -0
  53. package/src/modules/thirdparty/index.js +145 -0
  54. package/src/sdk/index.js +269 -0
  55. package/src/utils/callback-helper.js +198 -0
  56. package/test/callback-crypto.test.js +55 -0
  57. package/test/crypto.test.js +85 -0
  58. package/test/permission.test.js +115 -0
@@ -0,0 +1,365 @@
1
+ /**
2
+ * 通讯录缓存模块
3
+ *
4
+ * 功能:
5
+ * 1. 本地缓存部门树和员工列表
6
+ * 2. 事件驱动的自动更新
7
+ * 3. 快速查询接口
8
+ *
9
+ * 数据文件:data/addressbook_cache.json
10
+ *
11
+ * 首次使用会检查是否需要同步,用户确认后才同步
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ class AddressBookCache {
18
+ constructor(config, options = {}) {
19
+ this.config = config;
20
+ this.cachePath = path.join(process.cwd(), 'data', 'addressbook_cache.json');
21
+ this.cache = this._loadCache();
22
+ this.syncConfirmed = false; // 是否已确认同步
23
+ this.onConfirmCallback = options.onConfirm; // 确认回调
24
+ }
25
+
26
+ /**
27
+ * 检查是否需要同步
28
+ * @returns {boolean}
29
+ */
30
+ needsSync() {
31
+ // 没有缓存数据,需要同步
32
+ if (!this.cache.updatedAt) {
33
+ return true;
34
+ }
35
+ // 缓存超过7天,建议重新同步
36
+ const cacheDate = new Date(this.cache.updatedAt);
37
+ const now = new Date();
38
+ const daysDiff = (now - cacheDate) / (1000 * 60 * 60 * 24);
39
+ return daysDiff > 7;
40
+ }
41
+
42
+ /**
43
+ * 获取同步状态信息
44
+ */
45
+ getSyncStatus() {
46
+ return {
47
+ needsSync: this.needsSync(),
48
+ lastSyncTime: this.cache.updatedAt,
49
+ departmentCount: this.cache.departments?.length || 0,
50
+ userCount: this.cache.users?.length || 0
51
+ };
52
+ }
53
+
54
+ /**
55
+ * 请求同步(首次或用户主动触发)
56
+ * @returns {object} 状态信息
57
+ */
58
+ requestSync() {
59
+ const status = this.getSyncStatus();
60
+
61
+ if (!status.needsSync && this.syncConfirmed) {
62
+ return {
63
+ needConfirm: false,
64
+ alreadySynced: true,
65
+ status: status
66
+ };
67
+ }
68
+
69
+ return {
70
+ needConfirm: true,
71
+ confirmed: this.syncConfirmed,
72
+ status: status,
73
+ message: status.lastSyncTime
74
+ ? `通讯录缓存已过期(上一次同步:${status.lastSyncTime}),是否重新同步?`
75
+ : '通讯录缓存尚未初始化,是否立即同步?'
76
+ };
77
+ }
78
+
79
+ /**
80
+ * 确认同步
81
+ */
82
+ confirmSync() {
83
+ this.syncConfirmed = true;
84
+ if (this.onConfirmCallback) {
85
+ this.onConfirmCallback();
86
+ }
87
+ }
88
+
89
+ /**
90
+ * 跳过同步
91
+ */
92
+ skipSync() {
93
+ this.syncConfirmed = false;
94
+ }
95
+
96
+ /**
97
+ * 加载本地缓存
98
+ */
99
+ _loadCache() {
100
+ try {
101
+ if (fs.existsSync(this.cachePath)) {
102
+ const data = JSON.parse(fs.readFileSync(this.cachePath, 'utf8'));
103
+ console.log('[AddressBookCache] 已加载本地缓存');
104
+ return data;
105
+ }
106
+ } catch (e) {
107
+ console.log('[AddressBookCache] 加载缓存失败:', e.message);
108
+ }
109
+ return this._createEmptyCache();
110
+ }
111
+
112
+ /**
113
+ * 创建空缓存结构
114
+ */
115
+ _createEmptyCache() {
116
+ return {
117
+ updatedAt: null,
118
+ departments: [],
119
+ users: [],
120
+ userMap: {}, // userId -> user
121
+ departmentMap: {} // deptId -> department
122
+ };
123
+ }
124
+
125
+ /**
126
+ * 保存缓存到文件
127
+ */
128
+ _saveCache() {
129
+ try {
130
+ const dir = path.dirname(this.cachePath);
131
+ if (!fs.existsSync(dir)) {
132
+ fs.mkdirSync(dir, { recursive: true });
133
+ }
134
+ this.cache.updatedAt = new Date().toISOString();
135
+ fs.writeFileSync(this.cachePath, JSON.stringify(this.cache, null, 2));
136
+ console.log('[AddressBookCache] 缓存已保存');
137
+ } catch (e) {
138
+ console.log('[AddressBookCache] 保存缓存失败:', e.message);
139
+ }
140
+ }
141
+
142
+ /**
143
+ * 初始化/更新完整通讯录
144
+ *
145
+ * ⚠️ 注意:只需获取根部门(id=1)并设置 fetch_child=true
146
+ * 因为它会递归获取所有子部门成员,避免重复获取
147
+ */
148
+ async syncFromAPI(addressbook) {
149
+ console.log('[AddressBookCache] 从 API 同步通讯录...');
150
+
151
+ try {
152
+ // 获取部门列表
153
+ const deptResult = await addressbook.getDepartmentList();
154
+ this.cache.departments = deptResult.department || [];
155
+
156
+ // 构建部门 map
157
+ this.cache.departmentMap = {};
158
+ for (const dept of this.cache.departments) {
159
+ this.cache.departmentMap[dept.id] = dept;
160
+ }
161
+
162
+ // 获取所有员工(只需获取根部门,fetch_child=true 会递归获取子部门)
163
+ this.cache.users = [];
164
+ this.cache.userMap = {};
165
+
166
+ try {
167
+ // 只获取根部门,fetch_child=true 会递归获取所有子部门成员
168
+ const users = await addressbook.getDepartmentUsers(1, true);
169
+ if (users.userlist) {
170
+ for (const user of users.userlist) {
171
+ // 根据用户所在的部门设置信息
172
+ const dept = this.cache.departmentMap[user.department?.[0] || 1];
173
+ user.departmentId = user.department?.[0] || 1;
174
+ user.departmentName = dept?.name || '未知部门';
175
+
176
+ // 使用 userId 去重
177
+ if (!this.cache.userMap[user.userid]) {
178
+ this.cache.users.push(user);
179
+ this.cache.userMap[user.userid] = user;
180
+ }
181
+ }
182
+ }
183
+ console.log(`[AddressBookCache] 获取成员成功: ${users.userlist?.length || 0} 人`);
184
+ } catch (e) {
185
+ console.log('[AddressBookCache] 获取成员失败:', e.message);
186
+ }
187
+
188
+ this._saveCache();
189
+ console.log(`[AddressBookCache] 同步完成: ${this.cache.departments.length} 部门, ${this.cache.users.length} 员工`);
190
+
191
+ } catch (e) {
192
+ console.log('[AddressBookCache] 同步失败:', e.message);
193
+ }
194
+ }
195
+
196
+ // ========== 事件驱动的更新 ==========
197
+
198
+ /**
199
+ * 处理成员新增事件
200
+ */
201
+ onUserAdd(user) {
202
+ console.log('[AddressBookCache] 成员新增:', user.name || user.userid);
203
+
204
+ // 检查是否已存在
205
+ if (!this.cache.userMap[user.userid]) {
206
+ this.cache.users.push(user);
207
+ this.cache.userMap[user.userid] = user;
208
+ this._saveCache();
209
+ }
210
+ }
211
+
212
+ /**
213
+ * 处理成员删除事件
214
+ */
215
+ onUserDelete(userId) {
216
+ console.log('[AddressBookCache] 成员删除:', userId);
217
+
218
+ if (this.cache.userMap[userId]) {
219
+ const user = this.cache.userMap[userId];
220
+ this.cache.users = this.cache.users.filter(u => u.userid !== userId);
221
+ delete this.cache.userMap[userId];
222
+ this._saveCache();
223
+ return user;
224
+ }
225
+ return null;
226
+ }
227
+
228
+ /**
229
+ * 处理成员更新事件
230
+ */
231
+ onUserUpdate(user) {
232
+ console.log('[AddressBookCache] 成员更新:', user.userid);
233
+
234
+ if (this.cache.userMap[user.userid]) {
235
+ Object.assign(this.cache.userMap[user.userid], user);
236
+ const index = this.cache.users.findIndex(u => u.userid === user.userid);
237
+ if (index >= 0) {
238
+ Object.assign(this.cache.users[index], user);
239
+ }
240
+ this._saveCache();
241
+ }
242
+ }
243
+
244
+ /**
245
+ * 处理部门新增事件
246
+ */
247
+ onDepartmentAdd(dept) {
248
+ console.log('[AddressBookCache] 部门新增:', dept.name);
249
+
250
+ if (!this.cache.departmentMap[dept.id]) {
251
+ this.cache.departments.push(dept);
252
+ this.cache.departmentMap[dept.id] = dept;
253
+ this._saveCache();
254
+ }
255
+ }
256
+
257
+ /**
258
+ * 处理部门删除事件
259
+ */
260
+ onDepartmentDelete(deptId) {
261
+ console.log('[AddressBookCache] 部门删除:', deptId);
262
+
263
+ if (this.cache.departmentMap[deptId]) {
264
+ this.cache.departments = this.cache.departments.filter(d => d.id !== deptId);
265
+ delete this.cache.departmentMap[deptId];
266
+ this._saveCache();
267
+ }
268
+ }
269
+
270
+ /**
271
+ * 处理部门更新事件
272
+ */
273
+ onDepartmentUpdate(dept) {
274
+ console.log('[AddressBookCache] 部门更新:', dept.id);
275
+
276
+ if (this.cache.departmentMap[dept.id]) {
277
+ Object.assign(this.cache.departmentMap[dept.id], dept);
278
+ const index = this.cache.departments.findIndex(d => d.id === dept.id);
279
+ if (index >= 0) {
280
+ Object.assign(this.cache.departments[index], dept);
281
+ }
282
+ this._saveCache();
283
+ }
284
+ }
285
+
286
+ // ========== 查询接口 ==========
287
+
288
+ /**
289
+ * 获取所有部门
290
+ */
291
+ getDepartments() {
292
+ return this.cache.departments;
293
+ }
294
+
295
+ /**
296
+ * 获取所有员工
297
+ */
298
+ getUsers() {
299
+ return this.cache.users;
300
+ }
301
+
302
+ /**
303
+ * 根据 ID 获取员工
304
+ */
305
+ getUser(userId) {
306
+ return this.cache.userMap[userId];
307
+ }
308
+
309
+ /**
310
+ * 根据部门 ID 获取员工
311
+ */
312
+ getUsersByDepartment(deptId) {
313
+ return this.cache.users.filter(u => u.departmentId === deptId);
314
+ }
315
+
316
+ /**
317
+ * 根据部门 ID 获取子部门(包括自己)
318
+ */
319
+ getSubDepartments(deptId) {
320
+ const result = [];
321
+ const addDeptAndChildren = (id) => {
322
+ result.push(this.cache.departmentMap[id]);
323
+ for (const dept of this.cache.departments) {
324
+ if (dept.parentid === id) {
325
+ addDeptAndChildren(dept.id);
326
+ }
327
+ }
328
+ };
329
+ addDeptAndChildren(deptId);
330
+ return result;
331
+ }
332
+
333
+ /**
334
+ * 获取部门树
335
+ */
336
+ getDepartmentTree() {
337
+ const buildTree = (parentId) => {
338
+ return this.cache.departments
339
+ .filter(d => d.parentid === parentId)
340
+ .map(d => ({
341
+ ...d,
342
+ children: buildTree(d.id)
343
+ }));
344
+ };
345
+ return buildTree(0);
346
+ }
347
+
348
+ /**
349
+ * 获取统计信息
350
+ */
351
+ getStats() {
352
+ return {
353
+ updatedAt: this.cache.updatedAt,
354
+ departmentCount: this.cache.departments.length,
355
+ userCount: this.cache.users.length,
356
+ usersByDepartment: this.cache.departments.map(d => ({
357
+ id: d.id,
358
+ name: d.name,
359
+ userCount: this.getUsersByDepartment(d.id).length
360
+ }))
361
+ };
362
+ }
363
+ }
364
+
365
+ module.exports = AddressBookCache;
@@ -0,0 +1,159 @@
1
+ /**
2
+ * 高级功能模块
3
+ * API 章节:二十六 - 高级功能
4
+ * 包含:成员申请回调、审批单设置、批量获取申请单
5
+ */
6
+
7
+ const WeComSDK = require('../../sdk');
8
+
9
+ class Advanced extends WeComSDK {
10
+ constructor(config) {
11
+ super(config);
12
+ }
13
+
14
+ // ========== 成员申请的提交回调 ==========
15
+
16
+ /**
17
+ * 获取成员申请列表
18
+ * @param {number} startTime 开始时间戳
19
+ * @param {number} endTime 结束时间戳
20
+ * @param {number} cursor 分页游标
21
+ * @param {number} size 每页数量
22
+ */
23
+ async getMemberApplicationList(startTime, endTime, cursor = 0, size = 100) {
24
+ return this.post('/member_application/list', {
25
+ start_time: startTime,
26
+ end_time: endTime,
27
+ cursor,
28
+ limit: size
29
+ });
30
+ }
31
+
32
+ /**
33
+ * 获取成员申请详情
34
+ * @param {string} applicationId 申请 ID
35
+ */
36
+ async getMemberApplicationDetail(applicationId) {
37
+ return this.post('/member_application/get', { application_id: applicationId });
38
+ }
39
+
40
+ // ========== 审批单相关 ==========
41
+
42
+ /**
43
+ * 设置审批单审批信息
44
+ * @param {string} spNo 审批单号
45
+ * @param {object} params 审批信息
46
+ */
47
+ async setApprovalInfo(spNo, { approver, status, remark }) {
48
+ return this.post('/approval/spinfo/set', {
49
+ sp_no: spNo,
50
+ approver,
51
+ status,
52
+ remark
53
+ });
54
+ }
55
+
56
+ /**
57
+ * 批量获取审批单 ID
58
+ * @param {number} startTime 开始时间戳
59
+ * @param {number} endTime 结束时间戳
60
+ * @param {number} cursor 分页游标
61
+ * @param {number} size 每页数量
62
+ */
63
+ async getApprovalIdList(startTime, endTime, cursor = 0, size = 100) {
64
+ return this.post('/approval/spno/list', {
65
+ start_time: startTime,
66
+ end_time: endTime,
67
+ cursor,
68
+ limit: size
69
+ });
70
+ }
71
+
72
+ /**
73
+ * 获取审批单详细信息
74
+ * @param {string} spNo 审批单号
75
+ */
76
+ async getApprovalDetail(spNo) {
77
+ return this.post('/approval/spno/get', { sp_no: spNo });
78
+ }
79
+
80
+ // ========== 企业标签高级管理 ==========
81
+
82
+ /**
83
+ * 创建企业标签组
84
+ * @param {string} groupName 组名称
85
+ * @param {string[]} tags 标签列表
86
+ */
87
+ async createCorpTagGroup(groupName, tags = []) {
88
+ return this.post('/corpgroup/tag/add', {
89
+ group_name: groupName,
90
+ tags
91
+ });
92
+ }
93
+
94
+ /**
95
+ * 获取企业标签组列表
96
+ */
97
+ async getCorpTagGroupList() {
98
+ return this.post('/corpgroup/tag/list', {});
99
+ }
100
+
101
+ /**
102
+ * 更新企业标签
103
+ * @param {string} tagId 标签 ID
104
+ * @param {string} tagName 新标签名
105
+ */
106
+ async updateCorpTag(tagId, tagName) {
107
+ return this.post('/corpgroup/tag/update', {
108
+ id: tagId,
109
+ name: tagName
110
+ });
111
+ }
112
+
113
+ /**
114
+ * 删除企业标签
115
+ * @param {string} tagId 标签 ID
116
+ */
117
+ async deleteCorpTag(tagId) {
118
+ return this.post('/corpgroup/tag/del', { tag_id: tagId });
119
+ }
120
+
121
+ // ========== 企业群聊管理 ==========
122
+
123
+ /**
124
+ * 获取企业群聊列表
125
+ * @param {string} cursor 分页游标
126
+ * @param {number} size 每页数量
127
+ */
128
+ async getCorpGroupChatList(cursor = '', size = 100) {
129
+ return this.post('/corpgroup/chat/list', { cursor, limit: size });
130
+ }
131
+
132
+ /**
133
+ * 获取企业群聊详情
134
+ * @param {string} chatId 群聊 ID
135
+ */
136
+ async getCorpGroupChatDetail(chatId) {
137
+ return this.post('/corpgroup/chat/get', { chat_id: chatId });
138
+ }
139
+
140
+ // ========== 互联企业消息推送 ==========
141
+
142
+ /**
143
+ * 给互联企业发送消息
144
+ * @param {string} corpId 目标企业 ID
145
+ * @param {string} toUser 接收成员
146
+ * @param {object} content 消息内容
147
+ * @param {string} msgType 消息类型
148
+ */
149
+ async sendToLinkedCorp(corpId, toUser, content, msgType = 'text') {
150
+ return this.post('/corpgroup/message/send', {
151
+ corp_id: corpId,
152
+ touser: toUser,
153
+ msgtype: msgType,
154
+ [msgType]: content
155
+ });
156
+ }
157
+ }
158
+
159
+ module.exports = Advanced;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * 应用管理模块
3
+ * API 章节:九 - 应用管理
4
+ * 包含:应用管理、自定义菜单
5
+ */
6
+
7
+ const WeComSDK = require('../../sdk');
8
+
9
+ class App extends WeComSDK {
10
+ constructor(config) {
11
+ super(config);
12
+ }
13
+
14
+ // ========== 应用管理 ==========
15
+
16
+ /**
17
+ * 获取应用
18
+ * @param {number} agentId 应用 id
19
+ */
20
+ async getAgent(agentId) {
21
+ return this.get('/agent/get', { agentid: agentId });
22
+ }
23
+
24
+ /**
25
+ * 设置应用
26
+ * @param {number} agentId 应用 id
27
+ * @param {object} params 应用参数
28
+ */
29
+ async setAgent(agentId, { name, description, squareLogoUrl, redirectDomain, reportLocationFlag, isreportenter, homeUrl }) {
30
+ return this.post('/agent/set', {
31
+ agentid: agentId,
32
+ name,
33
+ description,
34
+ square_logo_url: squareLogoUrl,
35
+ redirect_domain: redirectDomain,
36
+ report_location_flag: reportLocationFlag,
37
+ isreportenter,
38
+ home_url: homeUrl
39
+ });
40
+ }
41
+
42
+ /**
43
+ * 获取应用列表
44
+ */
45
+ async getAgentList() {
46
+ return this.get('/agent/list', {});
47
+ }
48
+
49
+ // ========== 自定义菜单 ==========
50
+
51
+ /**
52
+ * 创建菜单
53
+ * @param {number} agentId 应用 id
54
+ * @param {object} menu 菜单配置
55
+ */
56
+ async createMenu(agentId, menu) {
57
+ return this.post('/menu/create', {
58
+ agentid: agentId,
59
+ button: menu.button || menu
60
+ });
61
+ }
62
+
63
+ /**
64
+ * 获取菜单
65
+ * @param {number} agentId 应用 id
66
+ */
67
+ async getMenu(agentId) {
68
+ return this.get('/menu/get', { agentid: agentId });
69
+ }
70
+
71
+ /**
72
+ * 删除菜单
73
+ * @param {number} agentId 应用 id
74
+ */
75
+ async deleteMenu(agentId) {
76
+ return this.post('/menu/delete', { agentid: agentId });
77
+ }
78
+
79
+ // ========== 工作台自定义展示 ==========
80
+
81
+ /**
82
+ * 设置工作台自定义展示
83
+ * @param {number} agentId 应用 id
84
+ * @param {object} config 工作台配置
85
+ */
86
+ async setWorkbench(agentId, config) {
87
+ return this.post('/agent/set_workbench', {
88
+ agentid: agentId,
89
+ ...config
90
+ });
91
+ }
92
+
93
+ /**
94
+ * 获取工作台自定义展示
95
+ * @param {number} agentId 应用 id
96
+ */
97
+ async getWorkbench(agentId) {
98
+ return this.post('/agent/get_workbench', { agentid: agentId });
99
+ }
100
+ }
101
+
102
+ module.exports = App;