@jiexiaoyin/wecom-api 0.0.7 → 0.0.9
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/package.json +1 -1
- package/skills/wecom-api/SKILL.md +6 -26
- package/skills/wecom-api/index.js +0 -41
- package/src/modules/callback/index.js +84 -121
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# WeCom API Skill
|
|
2
2
|
|
|
3
|
-
企业微信 API
|
|
3
|
+
企业微信 API 调用技能,支持客户管理、审批、会议、打卡、通讯录、素材等操作。
|
|
4
|
+
|
|
5
|
+
> 💡 注意:消息发送统一由 @sunnoy/wecom 通道处理,本技能不暴露发送消息相关动作。媒体上传下载也由 @sunnoy/wecom 统一处理。
|
|
4
6
|
|
|
5
7
|
## 触发方式
|
|
6
8
|
|
|
7
|
-
当用户提到企业微信相关操作时自动触发,如"
|
|
9
|
+
当用户提到企业微信相关操作时自动触发,如"查询客户"、"创建会议"、"查打卡记录"等。
|
|
8
10
|
|
|
9
11
|
## 参数说明
|
|
10
12
|
|
|
@@ -24,20 +26,6 @@
|
|
|
24
26
|
|
|
25
27
|
## 支持的动作
|
|
26
28
|
|
|
27
|
-
### 💬 消息发送
|
|
28
|
-
|
|
29
|
-
| 动作 | 说明 |
|
|
30
|
-
|------|------|
|
|
31
|
-
| `send_message` | 发送文本消息(兼容旧名) |
|
|
32
|
-
| `send_text` | 发送文本消息 |
|
|
33
|
-
| `send_text_card` | 发送文本卡片 |
|
|
34
|
-
| `send_markdown` | 发送 Markdown 消息 |
|
|
35
|
-
| `send_template_card` | 发送模板卡片 |
|
|
36
|
-
| `send_image` | 发送图片消息 |
|
|
37
|
-
| `send_file` | 发送文件消息 |
|
|
38
|
-
| `send_video` | 发送视频消息 |
|
|
39
|
-
| `send_news` | 发送图文消息 |
|
|
40
|
-
|
|
41
29
|
### 👥 客户联系
|
|
42
30
|
|
|
43
31
|
| 动作 | 说明 |
|
|
@@ -118,15 +106,6 @@
|
|
|
118
106
|
| `delete_event` | 删除日程 |
|
|
119
107
|
| `add_event_attendees` | 添加参与者 |
|
|
120
108
|
|
|
121
|
-
### 📁 素材
|
|
122
|
-
|
|
123
|
-
| 动作 | 说明 |
|
|
124
|
-
|------|------|
|
|
125
|
-
| `upload_image` | 上传图片 |
|
|
126
|
-
| `upload_media` | 上传媒体文件 |
|
|
127
|
-
| `get_media` | 下载媒体文件 |
|
|
128
|
-
| `get_high_definition_voice` | 获取高清语音 |
|
|
129
|
-
|
|
130
109
|
### 📱 应用
|
|
131
110
|
|
|
132
111
|
| 动作 | 说明 |
|
|
@@ -152,7 +131,6 @@
|
|
|
152
131
|
## 使用示例
|
|
153
132
|
|
|
154
133
|
```
|
|
155
|
-
发送消息给张三,内容是测试
|
|
156
134
|
查一下张三的客户列表
|
|
157
135
|
查询昨天的打卡记录
|
|
158
136
|
创建一个会议,主题是周会,时间是明天下午2点
|
|
@@ -161,4 +139,6 @@
|
|
|
161
139
|
获取审批列表,3月1日到3月7日
|
|
162
140
|
给李四添加客户备注
|
|
163
141
|
查一下客户群列表
|
|
142
|
+
获取假期余额
|
|
143
|
+
查一下客户流失统计
|
|
164
144
|
```
|
|
@@ -234,34 +234,6 @@ async function executeAction(action, params) {
|
|
|
234
234
|
try {
|
|
235
235
|
switch (action) {
|
|
236
236
|
|
|
237
|
-
// ========== 消息发送 ==========
|
|
238
|
-
case 'send_message':
|
|
239
|
-
return await wecom.message.sendText(args.userId, args.content, args.agentId);
|
|
240
|
-
|
|
241
|
-
case 'send_text':
|
|
242
|
-
return await wecom.message.sendText(args.toUser, args.content, args.agentId);
|
|
243
|
-
|
|
244
|
-
case 'send_text_card':
|
|
245
|
-
return await wecom.message.sendTextCard(args.toUser, args.title, args.content, args.agentId);
|
|
246
|
-
|
|
247
|
-
case 'send_markdown':
|
|
248
|
-
return await wecom.message.sendMarkdown(args.toUser, args.content, args.agentId);
|
|
249
|
-
|
|
250
|
-
case 'send_template_card':
|
|
251
|
-
return await wecom.message.sendTemplateCard(args.toUser, args.templateCard, args.agentId);
|
|
252
|
-
|
|
253
|
-
case 'send_image':
|
|
254
|
-
return await wecom.message.sendImage(args.toUser, args.mediaId, args.agentId);
|
|
255
|
-
|
|
256
|
-
case 'send_file':
|
|
257
|
-
return await wecom.message.sendFile(args.toUser, args.mediaId, args.agentId);
|
|
258
|
-
|
|
259
|
-
case 'send_video':
|
|
260
|
-
return await wecom.message.sendVideo(args.toUser, args.mediaId, args.agentId);
|
|
261
|
-
|
|
262
|
-
case 'send_news':
|
|
263
|
-
return await wecom.message.sendNews(args.toUser, args.articles, args.agentId);
|
|
264
|
-
|
|
265
237
|
// ========== 客户联系 ==========
|
|
266
238
|
case 'get_customer_list':
|
|
267
239
|
return await wecom.contact.getCustomerList(args.userId, args.cursor);
|
|
@@ -451,19 +423,6 @@ async function executeAction(action, params) {
|
|
|
451
423
|
case 'add_event_attendees':
|
|
452
424
|
return await wecom.schedule.addEventAttendees(args.scheduleId, args.attendees);
|
|
453
425
|
|
|
454
|
-
// ========== 素材 ==========
|
|
455
|
-
case 'upload_image':
|
|
456
|
-
return await wecom.media.uploadImage(args.filePath);
|
|
457
|
-
|
|
458
|
-
case 'upload_media':
|
|
459
|
-
return await wecom.media.uploadMedia(args.filePath, args.type);
|
|
460
|
-
|
|
461
|
-
case 'get_media':
|
|
462
|
-
return await wecom.media.getMedia(args.mediaId, args.savePath);
|
|
463
|
-
|
|
464
|
-
case 'get_high_definition_voice':
|
|
465
|
-
return await wecom.media.getHighDefinitionVoice(args.mediaId, args.savePath);
|
|
466
|
-
|
|
467
426
|
// ========== 应用 ==========
|
|
468
427
|
case 'get_agent_list':
|
|
469
428
|
return await wecom.app.getAgentList();
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 回调处理模块
|
|
3
3
|
* 企业微信回调消息处理框架
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* 功能:
|
|
6
6
|
* 1. 统一回调入口 - 处理所有企业微信回调事件
|
|
7
7
|
* 2. 事件记录 - 自动记录所有回调事件
|
|
8
8
|
* 3. 事件分发 - 根据事件类型自动调用对应处理器
|
|
9
9
|
* 4. 响应生成 - 自动生成响应消息
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
*
|
|
11
|
+
* 使用方式:
|
|
12
12
|
* const callback = new Callback(config);
|
|
13
13
|
* callback.on('change_contact', async (event) => { ... });
|
|
14
14
|
* callback.handle(req, res);
|
|
@@ -27,31 +27,31 @@ class Callback extends EventEmitter {
|
|
|
27
27
|
this.encodingAESKey = config.encodingAESKey || '';
|
|
28
28
|
this.corpId = config.corpId || '';
|
|
29
29
|
this.agentId = config.agentId || '';
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
// 事件记录存储
|
|
32
32
|
this.eventHistory = [];
|
|
33
33
|
this.maxHistorySize = config.maxHistorySize || 1000;
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
// 事件处理器映射
|
|
36
36
|
this.handlers = {};
|
|
37
37
|
|
|
38
|
-
//
|
|
38
|
+
// 审批模块(用于获取审批详情)
|
|
39
39
|
this.approval = new Approval(config);
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
// 初始化默认处理器
|
|
42
42
|
this._initDefaultHandlers();
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
// 加载已有事件历史
|
|
45
45
|
this.loadEventHistory();
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// ========== 初始化默认处理器 ==========
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
_initDefaultHandlers() {
|
|
51
51
|
// 通讯录事件
|
|
52
52
|
this.handlers['change_contact'] = 'handleContactChange';
|
|
53
53
|
this.handlers['change_external_contact'] = 'handleExternalContactChange';
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
// 客户联系事件
|
|
56
56
|
this.handlers['add_external_contact'] = 'handleAddExternalContact';
|
|
57
57
|
this.handlers['del_external_contact'] = 'handleDelExternalContact';
|
|
@@ -59,70 +59,58 @@ class Callback extends EventEmitter {
|
|
|
59
59
|
this.handlers['add_half_external_contact'] = 'handleAddHalfExternalContact';
|
|
60
60
|
this.handlers['del_follow_user'] = 'handleDelFollowUser';
|
|
61
61
|
this.handlers['transfer_fail'] = 'handleTransferFail';
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
// 客户群事件
|
|
64
64
|
this.handlers['create_chat'] = 'handleCreateChat';
|
|
65
65
|
this.handlers['update_chat'] = 'handleUpdateChat';
|
|
66
66
|
this.handlers['dismiss_chat'] = 'handleDismissChat';
|
|
67
|
-
|
|
68
|
-
// 消息事件
|
|
69
|
-
this.handlers['user_click'] = 'handleUserClick';
|
|
70
|
-
this.handlers['view'] = 'handleUserView';
|
|
71
|
-
this.handlers['scancode_push'] = 'handleScanCodePush';
|
|
72
|
-
this.handlers['scancode_waitmsg'] = 'handleScanCodeWaitMsg';
|
|
73
|
-
this.handlers['pic_sysphoto'] = 'handlePicSysPhoto';
|
|
74
|
-
this.handlers['pic_photo_or_album'] = 'handlePicPhotoOrAlbum';
|
|
75
|
-
this.handlers['pic_weixin'] = 'handlePicWeixin';
|
|
76
|
-
this.handlers['location_select'] = 'handleLocationSelect';
|
|
77
|
-
this.handlers['enter_agent'] = 'handleEnterAgent';
|
|
78
|
-
this.handlers['message'] = 'handleMessage';
|
|
79
|
-
|
|
67
|
+
|
|
80
68
|
// 审批事件
|
|
81
69
|
this.handlers['submit_approval'] = 'handleSubmitApproval';
|
|
82
70
|
this.handlers['sys_approval_change'] = 'handleSysApprovalChange';
|
|
83
71
|
this.handlers['Approval'] = 'handleApproval';
|
|
84
|
-
|
|
72
|
+
|
|
85
73
|
// 打卡事件
|
|
86
74
|
this.handlers['checkin'] = 'handleCheckin';
|
|
87
75
|
this.handlers['report_checkin'] = 'handleReportCheckin';
|
|
88
|
-
|
|
76
|
+
|
|
89
77
|
// 会议事件
|
|
90
78
|
this.handlers['meeting_start'] = 'handleMeetingStart';
|
|
91
79
|
this.handlers['meeting_end'] = 'handleMeetingEnd';
|
|
92
80
|
this.handlers['meeting_created'] = 'handleMeetingCreated';
|
|
93
81
|
this.handlers['meeting_cancelled'] = 'handleMeetingCancelled';
|
|
94
|
-
|
|
82
|
+
|
|
95
83
|
// 回调验证事件
|
|
96
84
|
this.handlers['url_verification'] = 'handleUrlVerification';
|
|
97
85
|
this.handlers['callback_verification'] = 'handleCallbackVerification';
|
|
98
|
-
|
|
99
|
-
// ==========
|
|
86
|
+
|
|
87
|
+
// ========== 客户联系回调(新增)==========
|
|
100
88
|
// 联系我相关
|
|
101
89
|
this.handlers['add_contact_way'] = 'handleAddContactWay';
|
|
102
90
|
this.handlers['del_contact_way'] = 'handleDelContactWay';
|
|
103
|
-
|
|
91
|
+
|
|
104
92
|
// 入群方式相关
|
|
105
93
|
this.handlers['add_join_way'] = 'handleAddJoinWay';
|
|
106
94
|
this.handlers['del_join_way'] = 'handleDelJoinWay';
|
|
107
|
-
|
|
95
|
+
|
|
108
96
|
// 客服消息相关
|
|
109
97
|
this.handlers['kf_msg_push'] = 'handleKfMsgPush';
|
|
110
98
|
this.handlers['kf_msg_send'] = 'handleKfMsgSend';
|
|
111
99
|
this.handlers['msg_dialogice_send'] = 'handleMsgDialogiceSend';
|
|
112
|
-
|
|
113
|
-
// ==========
|
|
100
|
+
|
|
101
|
+
// ========== 通讯录变更回调(新增)==========
|
|
114
102
|
this.handlers['change_member'] = 'handleChangeMember';
|
|
115
103
|
this.handlers['change_department'] = 'handleChangeDepartment';
|
|
116
104
|
this.handlers['change_tag'] = 'handleChangeTag';
|
|
117
|
-
|
|
118
|
-
// ==========
|
|
105
|
+
|
|
106
|
+
// ========== 会议回调(补充)==========
|
|
119
107
|
this.handlers['meeting_ended'] = 'handleMeetingEnded';
|
|
120
108
|
this.handlers['meetingParticipantJoin'] = 'handleMeetingParticipantJoin';
|
|
121
109
|
this.handlers['meetingParticipantLeave'] = 'handleMeetingParticipantLeave';
|
|
122
|
-
|
|
110
|
+
|
|
123
111
|
// ========== 直播回调 ==========
|
|
124
112
|
this.handlers['living_status'] = 'handleLivingStatus';
|
|
125
|
-
|
|
113
|
+
|
|
126
114
|
// ========== 微盘回调 ==========
|
|
127
115
|
this.handlers['change_psm'] = 'handleChangePsm';
|
|
128
116
|
this.handlers['change_disk'] = 'handleChangeDisk';
|
|
@@ -131,7 +119,7 @@ class Callback extends EventEmitter {
|
|
|
131
119
|
// ========== 消息验证 ==========
|
|
132
120
|
|
|
133
121
|
/**
|
|
134
|
-
* 验证 URL
|
|
122
|
+
* 验证 URL(用于首次配置回调)
|
|
135
123
|
* @param {string} msgSignature 签名
|
|
136
124
|
* @param {string} timestamp 时间戳
|
|
137
125
|
* @param {string} nonce 随机字符串
|
|
@@ -233,7 +221,7 @@ class Callback extends EventEmitter {
|
|
|
233
221
|
const xml = await this.parseXML(xmlContent);
|
|
234
222
|
console.log('[wecom-api] parseMessage xml keys:', Object.keys(xml));
|
|
235
223
|
console.log('[wecom-api] parseMessage xml:', JSON.stringify(xml).substring(0, 500));
|
|
236
|
-
|
|
224
|
+
|
|
237
225
|
const message = {
|
|
238
226
|
ToUserName: xml.ToUserName,
|
|
239
227
|
FromUserName: xml.FromUserName,
|
|
@@ -271,28 +259,28 @@ class Callback extends EventEmitter {
|
|
|
271
259
|
// ========== 统一回调入口 ==========
|
|
272
260
|
|
|
273
261
|
/**
|
|
274
|
-
*
|
|
262
|
+
* 处理企业微信回调请求(统一入口)
|
|
275
263
|
* @param {object} params 请求参数
|
|
276
264
|
* @param {string} params.msgSignature 签名
|
|
277
265
|
* @param {string} params.timestamp 时间戳
|
|
278
266
|
* @param {string} params.nonce 随机字符串
|
|
279
|
-
* @param {string} params.echostr
|
|
267
|
+
* @param {string} params.echostr 加密字符串(验证URL时)
|
|
280
268
|
* @param {string} params.xmlBody XML请求体
|
|
281
269
|
* @returns {Promise<object>} 处理结果
|
|
282
270
|
*/
|
|
283
271
|
async handle(params) {
|
|
284
272
|
const { msgSignature, timestamp, nonce, echostr, xmlBody } = params;
|
|
285
|
-
|
|
286
|
-
// URL
|
|
273
|
+
|
|
274
|
+
// URL验证模式(首次配置回调)
|
|
287
275
|
if (echostr) {
|
|
288
276
|
return this._handleUrlVerification(msgSignature, timestamp, nonce, echostr);
|
|
289
277
|
}
|
|
290
|
-
|
|
278
|
+
|
|
291
279
|
// 消息处理模式
|
|
292
280
|
if (xmlBody) {
|
|
293
281
|
return this._handleMessage(msgSignature, timestamp, nonce, xmlBody);
|
|
294
282
|
}
|
|
295
|
-
|
|
283
|
+
|
|
296
284
|
throw new Error('缺少必要参数');
|
|
297
285
|
}
|
|
298
286
|
|
|
@@ -301,7 +289,7 @@ class Callback extends EventEmitter {
|
|
|
301
289
|
*/
|
|
302
290
|
async _handleUrlVerification(msgSignature, timestamp, nonce, echostr) {
|
|
303
291
|
const result = this.verifyURL(msgSignature, timestamp, nonce, echostr);
|
|
304
|
-
|
|
292
|
+
|
|
305
293
|
if (result.success) {
|
|
306
294
|
// 记录事件
|
|
307
295
|
this._recordEvent({
|
|
@@ -309,13 +297,13 @@ class Callback extends EventEmitter {
|
|
|
309
297
|
success: true,
|
|
310
298
|
timestamp: Date.now()
|
|
311
299
|
});
|
|
312
|
-
|
|
300
|
+
|
|
313
301
|
return {
|
|
314
302
|
type: 'success',
|
|
315
303
|
body: result.echostr
|
|
316
304
|
};
|
|
317
305
|
}
|
|
318
|
-
|
|
306
|
+
|
|
319
307
|
return {
|
|
320
308
|
type: 'error',
|
|
321
309
|
message: result.message
|
|
@@ -324,28 +312,30 @@ class Callback extends EventEmitter {
|
|
|
324
312
|
|
|
325
313
|
/**
|
|
326
314
|
* 处理消息事件
|
|
315
|
+
* 注意:消息类事件(text/image/voice等)由 @sunnoy/wecom 统一处理
|
|
316
|
+
* 这里只处理事件类消息(有 Event 字段)
|
|
327
317
|
*/
|
|
328
318
|
async _handleMessage(msgSignature, timestamp, nonce, xmlBody) {
|
|
329
|
-
console.log('[wecom-api] _handleMessage called');
|
|
330
|
-
console.log('[wecom-api] xmlBody preview:', xmlBody ? xmlBody.substring(0, 300) : 'missing');
|
|
331
319
|
const xml = await this.parseXML(xmlBody);
|
|
332
|
-
console.log('[wecom-api] parsed xml Encrypt length:', xml.Encrypt ? xml.Encrypt.length : 'null');
|
|
333
320
|
const encrypt = xml.Encrypt;
|
|
334
|
-
|
|
321
|
+
|
|
335
322
|
// 验证签名
|
|
336
|
-
console.log('[wecom-api] 验证签名');
|
|
337
323
|
if (!this.verifyMessage(msgSignature, timestamp, nonce, encrypt)) {
|
|
338
324
|
throw new Error('签名验证失败');
|
|
339
325
|
}
|
|
340
|
-
|
|
326
|
+
|
|
341
327
|
// 解密消息
|
|
342
|
-
console.log('[wecom-api] decrypting...');
|
|
343
328
|
const decryptedXml = this.decrypt(encrypt);
|
|
344
329
|
const message = await this.parseMessage(decryptedXml);
|
|
345
|
-
|
|
330
|
+
|
|
331
|
+
// 非事件消息(text/image/voice等)由 @sunnoy/wecom 处理,直接返回成功
|
|
332
|
+
if (!message.Event) {
|
|
333
|
+
return { type: 'success', body: 'success' };
|
|
334
|
+
}
|
|
335
|
+
|
|
346
336
|
// 记录事件
|
|
347
337
|
const eventRecord = this._recordEvent({
|
|
348
|
-
type: message.Event
|
|
338
|
+
type: message.Event,
|
|
349
339
|
msgType: message.MsgType,
|
|
350
340
|
fromUserName: message.FromUserName,
|
|
351
341
|
createTime: message.CreateTime,
|
|
@@ -355,22 +345,17 @@ class Callback extends EventEmitter {
|
|
|
355
345
|
raw: message,
|
|
356
346
|
timestamp: Date.now()
|
|
357
347
|
});
|
|
358
|
-
|
|
348
|
+
|
|
359
349
|
// 触发事件
|
|
360
|
-
this.emit(message.Event
|
|
361
|
-
|
|
350
|
+
this.emit(message.Event, message, eventRecord);
|
|
351
|
+
|
|
362
352
|
// 调用对应处理器
|
|
363
|
-
const handlerName = this.handlers[message.Event
|
|
353
|
+
const handlerName = this.handlers[message.Event];
|
|
364
354
|
if (handlerName && typeof this[handlerName] === 'function') {
|
|
365
355
|
await this[handlerName](message, eventRecord);
|
|
366
356
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
return {
|
|
370
|
-
type: 'success',
|
|
371
|
-
body: 'success',
|
|
372
|
-
message: message // 返回解析后的消息对象
|
|
373
|
-
};
|
|
357
|
+
|
|
358
|
+
return { type: 'success', body: 'success', message };
|
|
374
359
|
}
|
|
375
360
|
|
|
376
361
|
// ========== 事件记录 ==========
|
|
@@ -385,21 +370,21 @@ class Callback extends EventEmitter {
|
|
|
385
370
|
...event,
|
|
386
371
|
timestamp: event.timestamp || Date.now()
|
|
387
372
|
};
|
|
388
|
-
|
|
373
|
+
|
|
389
374
|
this.eventHistory.unshift(record);
|
|
390
|
-
|
|
375
|
+
|
|
391
376
|
// 限制历史记录数量
|
|
392
377
|
if (this.eventHistory.length > this.maxHistorySize) {
|
|
393
378
|
this.eventHistory.pop();
|
|
394
379
|
}
|
|
395
|
-
|
|
380
|
+
|
|
396
381
|
// 根据类型分别保存到不同文件
|
|
397
382
|
if (record.type === 'text' || record.msgType === 'text') {
|
|
398
383
|
this._appendToFile('message_history.jsonl', record);
|
|
399
384
|
} else {
|
|
400
385
|
this._appendToFile('event_history.jsonl', record);
|
|
401
386
|
}
|
|
402
|
-
|
|
387
|
+
|
|
403
388
|
return record;
|
|
404
389
|
}
|
|
405
390
|
|
|
@@ -412,13 +397,13 @@ class Callback extends EventEmitter {
|
|
|
412
397
|
*/
|
|
413
398
|
getEventHistory(options = {}) {
|
|
414
399
|
let { type, limit = 100, offset = 0 } = options;
|
|
415
|
-
|
|
400
|
+
|
|
416
401
|
let history = this.eventHistory;
|
|
417
|
-
|
|
402
|
+
|
|
418
403
|
if (type) {
|
|
419
404
|
history = history.filter(e => e.type === type);
|
|
420
405
|
}
|
|
421
|
-
|
|
406
|
+
|
|
422
407
|
return history.slice(offset, offset + limit);
|
|
423
408
|
}
|
|
424
409
|
|
|
@@ -469,8 +454,8 @@ class Callback extends EventEmitter {
|
|
|
469
454
|
}
|
|
470
455
|
|
|
471
456
|
/**
|
|
472
|
-
*
|
|
473
|
-
* 文本消息从 message_history.jsonl
|
|
457
|
+
* 加载事件历史(兼容旧数组格式 + 新增追加格式)
|
|
458
|
+
* 文本消息从 message_history.jsonl 加载,事件从 event_history.jsonl 加载
|
|
474
459
|
*/
|
|
475
460
|
loadEventHistory() {
|
|
476
461
|
try {
|
|
@@ -478,12 +463,12 @@ class Callback extends EventEmitter {
|
|
|
478
463
|
const path = require('path');
|
|
479
464
|
const dataDir = '/root/.openclaw/extensions/wecom-api/data';
|
|
480
465
|
const records = [];
|
|
481
|
-
|
|
466
|
+
|
|
482
467
|
const files = ['event_history.jsonl', 'message_history.jsonl'];
|
|
483
|
-
|
|
468
|
+
|
|
484
469
|
for (const filename of files) {
|
|
485
470
|
const filePath = path.join(dataDir, filename);
|
|
486
|
-
|
|
471
|
+
|
|
487
472
|
if (!fs.existsSync(filePath)) {
|
|
488
473
|
// 尝试旧格式数组文件
|
|
489
474
|
const oldPath = path.join(dataDir, 'event_history.jsonl');
|
|
@@ -497,17 +482,17 @@ class Callback extends EventEmitter {
|
|
|
497
482
|
}
|
|
498
483
|
continue;
|
|
499
484
|
}
|
|
500
|
-
|
|
485
|
+
|
|
501
486
|
const raw = fs.readFileSync(filePath, 'utf8').trim();
|
|
502
487
|
if (!raw) continue;
|
|
503
|
-
|
|
488
|
+
|
|
504
489
|
const fileRecords = raw.split('\n')
|
|
505
490
|
.filter(line => line.trim())
|
|
506
491
|
.map(line => JSON.parse(line));
|
|
507
492
|
records.push(...fileRecords);
|
|
508
493
|
console.log('[wecom-api] 加载', filename, ':', fileRecords.length, '条');
|
|
509
494
|
}
|
|
510
|
-
|
|
495
|
+
|
|
511
496
|
// 按时间倒序
|
|
512
497
|
records.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
|
|
513
498
|
this.eventHistory = records.slice(0, this.maxHistorySize);
|
|
@@ -518,34 +503,34 @@ class Callback extends EventEmitter {
|
|
|
518
503
|
}
|
|
519
504
|
|
|
520
505
|
/**
|
|
521
|
-
*
|
|
506
|
+
* 加载事件历史从文件(兼容旧数组格式 + 新追加格式)
|
|
522
507
|
*/
|
|
523
508
|
loadEventHistory() {
|
|
524
509
|
try {
|
|
525
510
|
const fs = require('fs');
|
|
526
511
|
const path = require('path');
|
|
527
512
|
const filePath = '/root/.openclaw/extensions/wecom-api/data/event_history.jsonl';
|
|
528
|
-
|
|
513
|
+
|
|
529
514
|
if (!fs.existsSync(filePath)) {
|
|
530
515
|
return;
|
|
531
516
|
}
|
|
532
|
-
|
|
517
|
+
|
|
533
518
|
const raw = fs.readFileSync(filePath, 'utf8').trim();
|
|
534
519
|
if (!raw) {
|
|
535
520
|
return;
|
|
536
521
|
}
|
|
537
|
-
|
|
538
|
-
//
|
|
522
|
+
|
|
523
|
+
// 兼容旧格式(JSON 数组)
|
|
539
524
|
if (raw.startsWith('[')) {
|
|
540
525
|
this.eventHistory = JSON.parse(raw);
|
|
541
526
|
} else {
|
|
542
|
-
//
|
|
527
|
+
// 新格式:每行一个 JSON 对象
|
|
543
528
|
this.eventHistory = raw.split('\n')
|
|
544
529
|
.filter(line => line.trim())
|
|
545
530
|
.map(line => JSON.parse(line))
|
|
546
|
-
.reverse(); // reverse
|
|
531
|
+
.reverse(); // reverse 让最新的在前面(与 unshift 一致)
|
|
547
532
|
}
|
|
548
|
-
|
|
533
|
+
|
|
549
534
|
console.log('[wecom-api] 已加载事件历史:', this.eventHistory.length, '条');
|
|
550
535
|
} catch (e) {
|
|
551
536
|
console.log('[wecom-api] 加载事件历史失败:', e.message);
|
|
@@ -558,7 +543,7 @@ class Callback extends EventEmitter {
|
|
|
558
543
|
* 通讯录变更事件
|
|
559
544
|
*/
|
|
560
545
|
/**
|
|
561
|
-
*
|
|
546
|
+
* 通讯录变更事件(通用)
|
|
562
547
|
* 关键字段: ChangeType(1=新增 2=更新 3=删除), UserID, Name, Department
|
|
563
548
|
*/
|
|
564
549
|
async handleContactChange(event, record) {
|
|
@@ -714,30 +699,8 @@ class Callback extends EventEmitter {
|
|
|
714
699
|
/**
|
|
715
700
|
* 用户点击菜单
|
|
716
701
|
*/
|
|
717
|
-
async handleUserClick(event, record) {
|
|
718
|
-
console.log('[Callback] 用户点击:', event);
|
|
719
|
-
this.emit('menu:click', event, record);
|
|
720
|
-
return { handled: true };
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
/**
|
|
724
|
-
* 用户点击链接
|
|
725
|
-
*/
|
|
726
|
-
async handleUserView(event, record) {
|
|
727
|
-
console.log('[Callback] 用户点击链接:', event);
|
|
728
|
-
return { handled: true };
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
/**
|
|
732
|
-
* 扫码事件
|
|
733
|
-
*/
|
|
734
|
-
async handleScanCodePush(event, record) {
|
|
735
|
-
console.log('[Callback] 扫码:', event);
|
|
736
|
-
return { handled: true };
|
|
737
|
-
}
|
|
738
|
-
|
|
739
702
|
/**
|
|
740
|
-
*
|
|
703
|
+
* 审批事件(提交)
|
|
741
704
|
*/
|
|
742
705
|
/**
|
|
743
706
|
* 审批提交事件
|
|
@@ -763,7 +726,7 @@ class Callback extends EventEmitter {
|
|
|
763
726
|
}
|
|
764
727
|
|
|
765
728
|
/**
|
|
766
|
-
*
|
|
729
|
+
* 审批变更事件(官方 key: sys_approval_change)
|
|
767
730
|
* 关键字段: ApprovalId, SpStatus, OpenSpid
|
|
768
731
|
*/
|
|
769
732
|
async handleSysApprovalChange(event, record) {
|
|
@@ -781,7 +744,7 @@ class Callback extends EventEmitter {
|
|
|
781
744
|
}
|
|
782
745
|
|
|
783
746
|
/**
|
|
784
|
-
*
|
|
747
|
+
* 审批通过/变更事件(兼容旧 key: Approval)
|
|
785
748
|
*/
|
|
786
749
|
async handleApproval(event, record) {
|
|
787
750
|
const approvalId = event.ApprovalId || event.approval_id || '';
|
|
@@ -829,7 +792,7 @@ class Callback extends EventEmitter {
|
|
|
829
792
|
const spNoList = idListRes?.sp_no_list || [];
|
|
830
793
|
|
|
831
794
|
if (spNoList.length === 0) {
|
|
832
|
-
console.log('[Callback]
|
|
795
|
+
console.log('[Callback] 未找到审批单(' + trigger + ')');
|
|
833
796
|
return;
|
|
834
797
|
}
|
|
835
798
|
|
|
@@ -1038,7 +1001,7 @@ class Callback extends EventEmitter {
|
|
|
1038
1001
|
|
|
1039
1002
|
|
|
1040
1003
|
/**
|
|
1041
|
-
*
|
|
1004
|
+
* 会议真正结束(历史记录生成后)
|
|
1042
1005
|
*/
|
|
1043
1006
|
async handleMeetingEnded(event, record) {
|
|
1044
1007
|
console.log('[Callback] 会议结束(历史记录):', event);
|
|
@@ -1130,7 +1093,7 @@ class Callback extends EventEmitter {
|
|
|
1130
1093
|
*/
|
|
1131
1094
|
_buildReply(toUser, fromUser, msgType, content) {
|
|
1132
1095
|
let contentXml = '';
|
|
1133
|
-
|
|
1096
|
+
|
|
1134
1097
|
switch (msgType) {
|
|
1135
1098
|
case 'text':
|
|
1136
1099
|
contentXml = `<Content><![CDATA[${content.content}]]></Content>`;
|