@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,1180 @@
1
+ /**
2
+ * 回调处理模块
3
+ * 企业微信回调消息处理框架
4
+ *
5
+ * 功能:
6
+ * 1. 统一回调入口 - 处理所有企业微信回调事件
7
+ * 2. 事件记录 - 自动记录所有回调事件
8
+ * 3. 事件分发 - 根据事件类型自动调用对应处理器
9
+ * 4. 响应生成 - 自动生成响应消息
10
+ *
11
+ * 使用方式:
12
+ * const callback = new Callback(config);
13
+ * callback.on('change_contact', async (event) => { ... });
14
+ * callback.handle(req, res);
15
+ */
16
+
17
+ const crypto = require('crypto');
18
+ const xml2js = require('xml2js');
19
+ const EventEmitter = require('events');
20
+ const { verifyWecomSignature, decryptWecomEncrypted, encryptWecomPlaintext } = require('../../crypto');
21
+ const Approval = require('../approval');
22
+
23
+ class Callback extends EventEmitter {
24
+ constructor(config) {
25
+ super();
26
+ this.token = config.token || '';
27
+ this.encodingAESKey = config.encodingAESKey || '';
28
+ this.corpId = config.corpId || '';
29
+ this.agentId = config.agentId || '';
30
+
31
+ // 事件记录存储
32
+ this.eventHistory = [];
33
+ this.maxHistorySize = config.maxHistorySize || 1000;
34
+
35
+ // 事件处理器映射
36
+ this.handlers = {};
37
+
38
+ // 审批模块(用于获取审批详情)
39
+ this.approval = new Approval(config);
40
+
41
+ // 初始化默认处理器
42
+ this._initDefaultHandlers();
43
+
44
+ // 加载已有事件历史
45
+ this.loadEventHistory();
46
+ }
47
+
48
+ // ========== 初始化默认处理器 ==========
49
+
50
+ _initDefaultHandlers() {
51
+ // 通讯录事件
52
+ this.handlers['change_contact'] = 'handleContactChange';
53
+ this.handlers['change_external_contact'] = 'handleExternalContactChange';
54
+
55
+ // 客户联系事件
56
+ this.handlers['add_external_contact'] = 'handleAddExternalContact';
57
+ this.handlers['del_external_contact'] = 'handleDelExternalContact';
58
+ this.handlers['edit_external_contact'] = 'handleEditExternalContact';
59
+ this.handlers['add_half_external_contact'] = 'handleAddHalfExternalContact';
60
+ this.handlers['del_follow_user'] = 'handleDelFollowUser';
61
+ this.handlers['transfer_fail'] = 'handleTransferFail';
62
+
63
+ // 客户群事件
64
+ this.handlers['create_chat'] = 'handleCreateChat';
65
+ this.handlers['update_chat'] = 'handleUpdateChat';
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
+
80
+ // 审批事件
81
+ this.handlers['submit_approval'] = 'handleSubmitApproval';
82
+ this.handlers['sys_approval_change'] = 'handleSysApprovalChange';
83
+ this.handlers['Approval'] = 'handleApproval';
84
+
85
+ // 打卡事件
86
+ this.handlers['checkin'] = 'handleCheckin';
87
+ this.handlers['report_checkin'] = 'handleReportCheckin';
88
+
89
+ // 会议事件
90
+ this.handlers['meeting_start'] = 'handleMeetingStart';
91
+ this.handlers['meeting_end'] = 'handleMeetingEnd';
92
+ this.handlers['meeting_created'] = 'handleMeetingCreated';
93
+ this.handlers['meeting_cancelled'] = 'handleMeetingCancelled';
94
+
95
+ // 回调验证事件
96
+ this.handlers['url_verification'] = 'handleUrlVerification';
97
+ this.handlers['callback_verification'] = 'handleCallbackVerification';
98
+
99
+ // ========== 客户联系回调(新增)==========
100
+ // 联系我相关
101
+ this.handlers['add_contact_way'] = 'handleAddContactWay';
102
+ this.handlers['del_contact_way'] = 'handleDelContactWay';
103
+
104
+ // 入群方式相关
105
+ this.handlers['add_join_way'] = 'handleAddJoinWay';
106
+ this.handlers['del_join_way'] = 'handleDelJoinWay';
107
+
108
+ // 客服消息相关
109
+ this.handlers['kf_msg_push'] = 'handleKfMsgPush';
110
+ this.handlers['kf_msg_send'] = 'handleKfMsgSend';
111
+ this.handlers['msg_dialogice_send'] = 'handleMsgDialogiceSend';
112
+
113
+ // ========== 通讯录变更回调(新增)==========
114
+ this.handlers['change_member'] = 'handleChangeMember';
115
+ this.handlers['change_department'] = 'handleChangeDepartment';
116
+ this.handlers['change_tag'] = 'handleChangeTag';
117
+
118
+ // ========== 会议回调(补充)==========
119
+ this.handlers['meeting_ended'] = 'handleMeetingEnded';
120
+ this.handlers['meetingParticipantJoin'] = 'handleMeetingParticipantJoin';
121
+ this.handlers['meetingParticipantLeave'] = 'handleMeetingParticipantLeave';
122
+
123
+ // ========== 直播回调 ==========
124
+ this.handlers['living_status'] = 'handleLivingStatus';
125
+
126
+ // ========== 微盘回调 ==========
127
+ this.handlers['change_psm'] = 'handleChangePsm';
128
+ this.handlers['change_disk'] = 'handleChangeDisk';
129
+ }
130
+
131
+ // ========== 消息验证 ==========
132
+
133
+ /**
134
+ * 验证 URL(用于首次配置回调)
135
+ * @param {string} msgSignature 签名
136
+ * @param {string} timestamp 时间戳
137
+ * @param {string} nonce 随机字符串
138
+ * @param {string} echostr 加密的随机字符串
139
+ */
140
+ verifyURL(msgSignature, timestamp, nonce, echostr) {
141
+ const signature = this.getSignature(timestamp, nonce, echostr);
142
+ if (signature !== msgSignature) {
143
+ return { success: false, message: '签名验证失败' };
144
+ }
145
+
146
+ const decrypted = this.decrypt(echostr);
147
+ return { success: true, echostr: decrypted };
148
+ }
149
+
150
+ /**
151
+ * 验证消息签名
152
+ * @param {string} msgSignature 签名
153
+ * @param {string} timestamp 时间戳
154
+ * @param {string} nonce 随机字符串
155
+ * @param {string} encrypt 加密内容
156
+ */
157
+ verifyMessage(msgSignature, timestamp, nonce, encrypt) {
158
+ console.log('[wecom-api] verifyMessage params:', {
159
+ token: this.token,
160
+ timestamp,
161
+ nonce,
162
+ encrypt: encrypt ? encrypt.substring(0, 50) + '...' : null,
163
+ signature: msgSignature ? msgSignature.substring(0, 50) + '...' : null
164
+ });
165
+ const result = verifyWecomSignature({
166
+ token: this.token,
167
+ timestamp,
168
+ nonce,
169
+ encrypt,
170
+ signature: msgSignature
171
+ });
172
+ console.log('[wecom-api] verifyMessage result:', result);
173
+ return result;
174
+ }
175
+
176
+ // ========== 消息解密 ==========
177
+
178
+ decrypt(encrypt) {
179
+ try {
180
+ return decryptWecomEncrypted({
181
+ encodingAESKey: this.encodingAESKey,
182
+ encrypt
183
+ });
184
+ } catch (e) {
185
+ throw new Error('解密失败: ' + e.message);
186
+ }
187
+ }
188
+
189
+ /**
190
+ * 加密消息
191
+ * @param {string} content 消息内容
192
+ * @param {string} replyNonce 回复随机字符串
193
+ * @param {number} timestamp 时间戳
194
+ */
195
+ encrypt(content, replyNonce, timestamp) {
196
+ try {
197
+ const encrypted = encryptWecomPlaintext({
198
+ encodingAESKey: this.encodingAESKey,
199
+ receiveId: this.corpId,
200
+ plaintext: content
201
+ });
202
+
203
+ const signature = this.getSignature(timestamp, replyNonce, encrypted);
204
+
205
+ return {
206
+ encrypt: encrypted,
207
+ signature,
208
+ timestamp,
209
+ nonce: replyNonce
210
+ };
211
+ } catch (e) {
212
+ throw new Error('加密失败: ' + e.message);
213
+ }
214
+ }
215
+
216
+ // ========== 消息解析 ==========
217
+
218
+ /**
219
+ * 解析 XML 消息
220
+ * @param {string} xmlContent XML 内容
221
+ */
222
+ async parseXML(xmlContent) {
223
+ const parser = new xml2js.Parser({ explicitArray: false });
224
+ const result = await parser.parseStringPromise(xmlContent);
225
+ return result.xml;
226
+ }
227
+
228
+ /**
229
+ * 解析消息事件
230
+ * @param {string} xmlContent XML 内容
231
+ */
232
+ async parseMessage(xmlContent) {
233
+ const xml = await this.parseXML(xmlContent);
234
+ console.log('[wecom-api] parseMessage xml keys:', Object.keys(xml));
235
+ console.log('[wecom-api] parseMessage xml:', JSON.stringify(xml).substring(0, 500));
236
+
237
+ const message = {
238
+ ToUserName: xml.ToUserName,
239
+ FromUserName: xml.FromUserName,
240
+ CreateTime: parseInt(xml.CreateTime),
241
+ MsgType: xml.MsgType,
242
+ Content: xml.Content,
243
+ MsgId: xml.MsgId,
244
+ Event: xml.Event,
245
+ EventKey: xml.EventKey,
246
+ AgentID: xml.AgentID,
247
+ // 媒体相关
248
+ MediaId: xml.MediaId,
249
+ PicUrl: xml.PicUrl,
250
+ Format: xml.Format,
251
+ ThumbMediaId: xml.ThumbMediaId,
252
+ // 位置相关
253
+ Location_X: xml.Location_X,
254
+ Location_Y: xml.Location_Y,
255
+ Scale: xml.Scale,
256
+ Label: xml.Label,
257
+ // 链接相关
258
+ Title: xml.Title,
259
+ Description: xml.Description,
260
+ Url: xml.Url,
261
+ // 加密消息
262
+ Encrypt: xml.Encrypt,
263
+ // 外部联系人变更事件关键字段
264
+ ChangeType: xml.ChangeType,
265
+ ExternalUserId: xml.ExternalUserId,
266
+ };
267
+
268
+ return message;
269
+ }
270
+
271
+ // ========== 统一回调入口 ==========
272
+
273
+ /**
274
+ * 处理企业微信回调请求(统一入口)
275
+ * @param {object} params 请求参数
276
+ * @param {string} params.msgSignature 签名
277
+ * @param {string} params.timestamp 时间戳
278
+ * @param {string} params.nonce 随机字符串
279
+ * @param {string} params.echostr 加密字符串(验证URL时)
280
+ * @param {string} params.xmlBody XML请求体
281
+ * @returns {Promise<object>} 处理结果
282
+ */
283
+ async handle(params) {
284
+ const { msgSignature, timestamp, nonce, echostr, xmlBody } = params;
285
+
286
+ // URL验证模式(首次配置回调)
287
+ if (echostr) {
288
+ return this._handleUrlVerification(msgSignature, timestamp, nonce, echostr);
289
+ }
290
+
291
+ // 消息处理模式
292
+ if (xmlBody) {
293
+ return this._handleMessage(msgSignature, timestamp, nonce, xmlBody);
294
+ }
295
+
296
+ throw new Error('缺少必要参数');
297
+ }
298
+
299
+ /**
300
+ * 处理 URL 验证
301
+ */
302
+ async _handleUrlVerification(msgSignature, timestamp, nonce, echostr) {
303
+ const result = this.verifyURL(msgSignature, timestamp, nonce, echostr);
304
+
305
+ if (result.success) {
306
+ // 记录事件
307
+ this._recordEvent({
308
+ type: 'url_verification',
309
+ success: true,
310
+ timestamp: Date.now()
311
+ });
312
+
313
+ return {
314
+ type: 'success',
315
+ body: result.echostr
316
+ };
317
+ }
318
+
319
+ return {
320
+ type: 'error',
321
+ message: result.message
322
+ };
323
+ }
324
+
325
+ /**
326
+ * 处理消息事件
327
+ */
328
+ 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
+ const xml = await this.parseXML(xmlBody);
332
+ console.log('[wecom-api] parsed xml Encrypt length:', xml.Encrypt ? xml.Encrypt.length : 'null');
333
+ const encrypt = xml.Encrypt;
334
+
335
+ // 验证签名
336
+ console.log('[wecom-api] 验证签名');
337
+ if (!this.verifyMessage(msgSignature, timestamp, nonce, encrypt)) {
338
+ throw new Error('签名验证失败');
339
+ }
340
+
341
+ // 解密消息
342
+ console.log('[wecom-api] decrypting...');
343
+ const decryptedXml = this.decrypt(encrypt);
344
+ const message = await this.parseMessage(decryptedXml);
345
+
346
+ // 记录事件
347
+ const eventRecord = this._recordEvent({
348
+ type: message.Event || message.MsgType,
349
+ msgType: message.MsgType,
350
+ fromUserName: message.FromUserName,
351
+ createTime: message.CreateTime,
352
+ event: message.Event,
353
+ eventKey: message.EventKey,
354
+ agentID: message.AgentID,
355
+ raw: message,
356
+ timestamp: Date.now()
357
+ });
358
+
359
+ // 触发事件
360
+ this.emit(message.Event || message.MsgType, message, eventRecord);
361
+
362
+ // 调用对应处理器
363
+ const handlerName = this.handlers[message.Event || message.MsgType];
364
+ if (handlerName && typeof this[handlerName] === 'function') {
365
+ await this[handlerName](message, eventRecord);
366
+ }
367
+
368
+ // 返回成功响应
369
+ return {
370
+ type: 'success',
371
+ body: 'success',
372
+ message: message // 返回解析后的消息对象
373
+ };
374
+ }
375
+
376
+ // ========== 事件记录 ==========
377
+
378
+ /**
379
+ * 记录回调事件
380
+ * @param {object} event 事件数据
381
+ */
382
+ _recordEvent(event) {
383
+ const record = {
384
+ id: this._generateId(),
385
+ ...event,
386
+ timestamp: event.timestamp || Date.now()
387
+ };
388
+
389
+ this.eventHistory.unshift(record);
390
+
391
+ // 限制历史记录数量
392
+ if (this.eventHistory.length > this.maxHistorySize) {
393
+ this.eventHistory.pop();
394
+ }
395
+
396
+ // 根据类型分别保存到不同文件
397
+ if (record.type === 'text' || record.msgType === 'text') {
398
+ this._appendToFile('message_history.jsonl', record);
399
+ } else {
400
+ this._appendToFile('event_history.jsonl', record);
401
+ }
402
+
403
+ return record;
404
+ }
405
+
406
+ /**
407
+ * 获取事件历史记录
408
+ * @param {object} options 查询选项
409
+ * @param {string} options.type 事件类型
410
+ * @param {number} options.limit 返回数量
411
+ * @param {number} options.offset 偏移量
412
+ */
413
+ getEventHistory(options = {}) {
414
+ let { type, limit = 100, offset = 0 } = options;
415
+
416
+ let history = this.eventHistory;
417
+
418
+ if (type) {
419
+ history = history.filter(e => e.type === type);
420
+ }
421
+
422
+ return history.slice(offset, offset + limit);
423
+ }
424
+
425
+ /**
426
+ * 获取事件详情
427
+ * @param {string} eventId 事件ID
428
+ */
429
+ getEventById(eventId) {
430
+ return this.eventHistory.find(e => e.id === eventId);
431
+ }
432
+
433
+ /**
434
+ * 清空事件历史
435
+ */
436
+ clearEventHistory() {
437
+ this.eventHistory = [];
438
+ }
439
+
440
+ /**
441
+ * 导出事件历史到文件
442
+ * @param {string} filePath 文件路径
443
+ */
444
+ async exportEventHistory(filePath) {
445
+ const fs = require('fs');
446
+ const data = JSON.stringify(this.eventHistory, null, 2);
447
+ fs.writeFileSync(filePath, data, 'utf8');
448
+ return { success: true, count: this.eventHistory.length, filePath };
449
+ }
450
+
451
+ /**
452
+ * 追加记录到指定文件
453
+ * @param {string} filename 文件名
454
+ * @param {object} record 单条记录
455
+ */
456
+ _appendToFile(filename, record) {
457
+ try {
458
+ const fs = require('fs');
459
+ const path = require('path');
460
+ const dir = '/root/.openclaw/extensions/wecom-api/data';
461
+ if (!fs.existsSync(dir)) {
462
+ fs.mkdirSync(dir, { recursive: true });
463
+ }
464
+ const filePath = path.join(dir, filename);
465
+ fs.appendFileSync(filePath, JSON.stringify(record) + '\n', 'utf8');
466
+ } catch (e) {
467
+ console.log('[wecom-api] 写入文件失败:', e.message);
468
+ }
469
+ }
470
+
471
+ /**
472
+ * 加载事件历史(兼容旧数组格式 + 新增追加格式)
473
+ * 文本消息从 message_history.jsonl 加载,事件从 event_history.jsonl 加载
474
+ */
475
+ loadEventHistory() {
476
+ try {
477
+ const fs = require('fs');
478
+ const path = require('path');
479
+ const dataDir = '/root/.openclaw/extensions/wecom-api/data';
480
+ const records = [];
481
+
482
+ const files = ['event_history.jsonl', 'message_history.jsonl'];
483
+
484
+ for (const filename of files) {
485
+ const filePath = path.join(dataDir, filename);
486
+
487
+ if (!fs.existsSync(filePath)) {
488
+ // 尝试旧格式数组文件
489
+ const oldPath = path.join(dataDir, 'event_history.jsonl');
490
+ if (fs.existsSync(oldPath)) {
491
+ const raw = fs.readFileSync(oldPath, 'utf8').trim();
492
+ if (raw.startsWith('[')) {
493
+ const oldRecords = JSON.parse(raw);
494
+ records.push(...oldRecords);
495
+ console.log('[wecom-api] 兼容加载旧格式:', oldRecords.length, '条 from', filename);
496
+ }
497
+ }
498
+ continue;
499
+ }
500
+
501
+ const raw = fs.readFileSync(filePath, 'utf8').trim();
502
+ if (!raw) continue;
503
+
504
+ const fileRecords = raw.split('\n')
505
+ .filter(line => line.trim())
506
+ .map(line => JSON.parse(line));
507
+ records.push(...fileRecords);
508
+ console.log('[wecom-api] 加载', filename, ':', fileRecords.length, '条');
509
+ }
510
+
511
+ // 按时间倒序
512
+ records.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
513
+ this.eventHistory = records.slice(0, this.maxHistorySize);
514
+ console.log('[wecom-api] 事件历史合计:', this.eventHistory.length, '条');
515
+ } catch (e) {
516
+ console.log('[wecom-api] 加载事件历史失败:', e.message);
517
+ }
518
+ }
519
+
520
+ /**
521
+ * 加载事件历史从文件(兼容旧数组格式 + 新追加格式)
522
+ */
523
+ loadEventHistory() {
524
+ try {
525
+ const fs = require('fs');
526
+ const path = require('path');
527
+ const filePath = '/root/.openclaw/extensions/wecom-api/data/event_history.jsonl';
528
+
529
+ if (!fs.existsSync(filePath)) {
530
+ return;
531
+ }
532
+
533
+ const raw = fs.readFileSync(filePath, 'utf8').trim();
534
+ if (!raw) {
535
+ return;
536
+ }
537
+
538
+ // 兼容旧格式(JSON 数组)
539
+ if (raw.startsWith('[')) {
540
+ this.eventHistory = JSON.parse(raw);
541
+ } else {
542
+ // 新格式:每行一个 JSON 对象
543
+ this.eventHistory = raw.split('\n')
544
+ .filter(line => line.trim())
545
+ .map(line => JSON.parse(line))
546
+ .reverse(); // reverse 让最新的在前面(与 unshift 一致)
547
+ }
548
+
549
+ console.log('[wecom-api] 已加载事件历史:', this.eventHistory.length, '条');
550
+ } catch (e) {
551
+ console.log('[wecom-api] 加载事件历史失败:', e.message);
552
+ }
553
+ }
554
+
555
+ // ========== 事件处理器 ==========
556
+
557
+ /**
558
+ * 通讯录变更事件
559
+ */
560
+ /**
561
+ * 通讯录变更事件(通用)
562
+ * 关键字段: ChangeType(1=新增 2=更新 3=删除), UserID, Name, Department
563
+ */
564
+ async handleContactChange(event, record) {
565
+ const ct = event.ChangeType || event.change_type || 0;
566
+ const ctMap = {1:'新增', 2:'更新', 3:'删除'};
567
+ record.changeType = ct;
568
+ record.userId = event.UserID || event.userid || '';
569
+ record.name = event.Name || event.name || '';
570
+ record.department = event.Department || event.department || [];
571
+ record.changeTypeText = ctMap[ct] || '变更(' + ct + ')';
572
+ console.log('[Callback] 通讯录变更: ' + record.changeTypeText + ' UserId=' + record.userId);
573
+ return { handled: true, changeType: record.changeTypeText, userId: record.userId };
574
+ }
575
+
576
+ /**
577
+ * 外部联系人变更事件
578
+ */
579
+ /**
580
+ * 外部联系人变更事件
581
+ * 关键字段: ChangeType(1=新增 4=删除), UserID, ExternalUserId
582
+ */
583
+ async handleExternalContactChange(event, record) {
584
+ const changeType = event.ChangeType || event.change_type || 0;
585
+ const userId = event.UserID || event.userid || '';
586
+ const externalUserId = event.ExternalUserId || event.external_userid || '';
587
+ record.changeType = changeType;
588
+ record.userId = userId;
589
+ record.externalUserId = externalUserId;
590
+ const actionMap = {1:'新增', 4:'删除'};
591
+ record.action = actionMap[changeType] || '变更(' + changeType + ')';
592
+ console.log('[Callback] 外部联系人变更: ' + record.action + ' UserId=' + userId + ' ExternalUserId=' + externalUserId);
593
+ return { handled: true, changeType: record.action, userId, externalUserId };
594
+ }
595
+
596
+ /**
597
+ * 添加外部联系人
598
+ * 关键字段: UserID, ExternalUserId, WelcomeCode
599
+ */
600
+ async handleAddExternalContact(event, record) {
601
+ record.userId = event.UserID || event.userid || '';
602
+ record.externalUserId = event.ExternalUserId || event.external_userid || '';
603
+ record.welcomeCode = event.WelcomeCode || event.welcome_code || '';
604
+ console.log('[Callback] 添加外部联系人: UserId=' + record.userId + ' ExternalUserId=' + record.externalUserId);
605
+ this.emit('external_contact:add', event, record);
606
+ return { handled: true, userId: record.userId, externalUserId: record.externalUserId };
607
+ }
608
+
609
+ /**
610
+ * 删除外部联系人
611
+ * 关键字段: UserID, ExternalUserId
612
+ */
613
+ async handleDelExternalContact(event, record) {
614
+ record.userId = event.UserID || event.userid || '';
615
+ record.externalUserId = event.ExternalUserId || event.external_userid || '';
616
+ console.log('[Callback] 删除外部联系人: UserId=' + record.userId + ' ExternalUserId=' + record.externalUserId);
617
+ this.emit('external_contact:del', event, record);
618
+ return { handled: true, userId: record.userId, externalUserId: record.externalUserId };
619
+ }
620
+
621
+ /**
622
+ * 编辑外部联系人
623
+ */
624
+ /**
625
+ * 编辑外部联系人
626
+ * 关键字段: UserID, ExternalUserId
627
+ */
628
+ async handleEditExternalContact(event, record) {
629
+ record.userId = event.UserID || event.userid || '';
630
+ record.externalUserId = event.ExternalUserId || event.external_userid || '';
631
+ console.log('[Callback] 编辑外部联系人: UserId=' + record.userId + ' ExternalUserId=' + record.externalUserId);
632
+ return { handled: true, userId: record.userId, externalUserId: record.externalUserId };
633
+ }
634
+
635
+ /**
636
+ * 添加半程外部联系人
637
+ */
638
+ /**
639
+ * 添加半程外部联系人
640
+ * 关键字段: UserID, ExternalUserId, HalfAuthChangeType
641
+ */
642
+ async handleAddHalfExternalContact(event, record) {
643
+ record.userId = event.UserID || event.userid || '';
644
+ record.externalUserId = event.ExternalUserId || event.external_userid || '';
645
+ record.halfAuthChangeType = event.HalfAuthChangeType || event.half_auth_change_type || 0;
646
+ console.log('[Callback] 添加半程外部联系人: UserId=' + record.userId + ' ExternalUserId=' + record.externalUserId);
647
+ return { handled: true, userId: record.userId, externalUserId: record.externalUserId };
648
+ }
649
+
650
+ /**
651
+ * 删除跟进成员
652
+ * 关键字段: UserID, ExternalUserId
653
+ */
654
+ async handleDelFollowUser(event, record) {
655
+ record.userId = event.UserID || event.userid || '';
656
+ record.externalUserId = event.ExternalUserId || event.external_userid || '';
657
+ console.log('[Callback] 删除跟进成员: UserId=' + record.userId + ' ExternalUserId=' + record.externalUserId);
658
+ return { handled: true, userId: record.userId, externalUserId: record.externalUserId };
659
+ }
660
+
661
+ /**
662
+ * 客户接替失败
663
+ * 事件Key: transfer_fail
664
+ * 关键字段: UserID, ExternalUserId, FailReason
665
+ */
666
+ async handleTransferFail(event, record) {
667
+ record.userId = event.UserID || event.userid || '';
668
+ record.externalUserId = event.ExternalUserId || event.external_userid || '';
669
+ record.failReason = event.FailReason || event.fail_reason || '';
670
+ console.log('[Callback] 客户接替失败: UserId=' + record.userId + ' ExternalUserId=' + record.externalUserId + ' Reason=' + record.failReason);
671
+ return { handled: true, userId: record.userId, failReason: record.failReason };
672
+ }
673
+
674
+ /**
675
+ * 客户群创建
676
+ * 关键字段: ChatId, Creator, CreateTime, MemberCount
677
+ */
678
+ async handleCreateChat(event, record) {
679
+ record.chatId = event.ChatId || event.chat_id || '';
680
+ record.creator = event.Creator || event.creator || '';
681
+ record.createTime = event.CreateTime || event.create_time || 0;
682
+ record.memberCount = event.MemberCount || event.member_count || 0;
683
+ console.log('[Callback] 客户群创建: ChatId=' + record.chatId + ' Creator=' + record.creator);
684
+ this.emit('group_chat:create', event, record);
685
+ return { handled: true, chatId: record.chatId, creator: record.creator };
686
+ }
687
+
688
+ /**
689
+ * 客户群变更
690
+ * 关键字段: ChatId, UpdateType, UpdateDetail
691
+ */
692
+ async handleUpdateChat(event, record) {
693
+ record.chatId = event.ChatId || event.chat_id || '';
694
+ record.updateType = event.UpdateType || event.update_type || '';
695
+ record.updateDetail = event.UpdateDetail || event.update_detail || '';
696
+ console.log('[Callback] 客户群变更: ChatId=' + record.chatId + ' UpdateType=' + record.updateType);
697
+ this.emit('group_chat:update', event, record);
698
+ return { handled: true, chatId: record.chatId, updateType: record.updateType };
699
+ }
700
+
701
+ /**
702
+ * 客户群解散
703
+ * 关键字段: ChatId, Creator, CreateTime
704
+ */
705
+ async handleDismissChat(event, record) {
706
+ record.chatId = event.ChatId || event.chat_id || '';
707
+ record.creator = event.Creator || event.creator || '';
708
+ record.createTime = event.CreateTime || event.create_time || 0;
709
+ console.log('[Callback] 客户群解散: ChatId=' + record.chatId + ' Creator=' + record.creator);
710
+ this.emit('group_chat:dismiss', event, record);
711
+ return { handled: true, chatId: record.chatId };
712
+ }
713
+
714
+ /**
715
+ * 用户点击菜单
716
+ */
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
+ /**
740
+ * 审批事件(提交)
741
+ */
742
+ /**
743
+ * 审批提交事件
744
+ * 事件Key: approval_submit
745
+ * 关键字段: ApprovalId, SpStatus, SubmitterUserid, TaskId, UniqueId
746
+ */
747
+ async handleSubmitApproval(event, record) {
748
+ const approvalId = event.ApprovalId || event.approval_id || '';
749
+ const spStatus = event.SpStatus || event.sp_status || 0;
750
+ const submitter = event.SubmitterUserid || event.submitter_userid || '';
751
+ const taskId = event.TaskId || event.task_id || '';
752
+ const uniqueId = event.UniqueId || event.unique_id || '';
753
+ record.approvalId = approvalId;
754
+ record.spStatus = spStatus;
755
+ record.submitter = submitter;
756
+ record.taskId = taskId;
757
+ record.uniqueId = uniqueId;
758
+ record.statusText = this._getApprovalStatusText(spStatus);
759
+ console.log('[Callback] 审批提交: ApprovalId=' + approvalId + ' Status=' + record.statusText + ' Submitter=' + submitter);
760
+ await this._fetchAndSaveApprovalDetail(event, record, 'submit');
761
+ this.emit('approval:submit', event, record);
762
+ return { handled: true, approvalId, spStatus: record.statusText };
763
+ }
764
+
765
+ /**
766
+ * 审批变更事件(官方 key: sys_approval_change)
767
+ * 关键字段: ApprovalId, SpStatus, OpenSpid
768
+ */
769
+ async handleSysApprovalChange(event, record) {
770
+ const approvalId = event.ApprovalId || event.approval_id || '';
771
+ const spStatus = event.SpStatus || event.sp_status || 0;
772
+ const openSpid = event.OpenSpid || event.open_spid || '';
773
+ record.approvalId = approvalId;
774
+ record.spStatus = spStatus;
775
+ record.openSpid = openSpid;
776
+ record.statusText = this._getApprovalStatusText(spStatus);
777
+ console.log('[Callback] 审批变更: ApprovalId=' + approvalId + ' Status=' + record.statusText);
778
+ await this._fetchAndSaveApprovalDetail(event, record, 'change');
779
+ this.emit('approval:change', event, record);
780
+ return { handled: true, approvalId, spStatus: record.statusText };
781
+ }
782
+
783
+ /**
784
+ * 审批通过/变更事件(兼容旧 key: Approval)
785
+ */
786
+ async handleApproval(event, record) {
787
+ const approvalId = event.ApprovalId || event.approval_id || '';
788
+ const spStatus = event.SpStatus || event.sp_status || 0;
789
+ record.approvalId = approvalId;
790
+ record.spStatus = spStatus;
791
+ record.statusText = this._getApprovalStatusText(spStatus);
792
+ console.log('[Callback] 审批变更(Approval): ApprovalId=' + approvalId + ' Status=' + record.statusText);
793
+ await this._fetchAndSaveApprovalDetail(event, record, 'change');
794
+ this.emit('approval:pass', event, record);
795
+ return { handled: true, approvalId, spStatus: record.statusText };
796
+ }
797
+
798
+ /** 审批状态码转文本 */
799
+ _getApprovalStatusText(spStatus) {
800
+ const map = { 1:'审批中', 2:'已通过', 3:'已驳回', 4:'已撤回', 5:'未提交', 6:'已通过', 7:'已驳回', 8:'已转交', 10:'已完成', 11:'已取消' };
801
+ return map[spStatus] || '状态'+spStatus;
802
+ }
803
+
804
+
805
+ /**
806
+ * 审批通过/变更事件
807
+ */
808
+ async handleApproval(event, record) {
809
+ console.log('[Callback] 审批变更:', event);
810
+ // 获取审批详情并保存
811
+ await this._fetchAndSaveApprovalDetail(event, record, 'change');
812
+ this.emit('approval:pass', event, record);
813
+ return { handled: true };
814
+ }
815
+
816
+ /**
817
+ * 根据回调时间拉取审批详情并保存
818
+ * @param {object} event 事件对象
819
+ * @param {object} record 记录对象
820
+ * @param {string} trigger 触发类型 submit|change
821
+ */
822
+ async _fetchAndSaveApprovalDetail(event, record, trigger) {
823
+ try {
824
+ const callbackTime = (event.CreateTime || record.createTime || 0) * 1000;
825
+ const endTime = Math.floor(Date.now() / 1000);
826
+ const startTime = endTime - 600;
827
+
828
+ const idListRes = await this.approval.getApprovalIds(startTime, endTime);
829
+ const spNoList = idListRes?.sp_no_list || [];
830
+
831
+ if (spNoList.length === 0) {
832
+ console.log('[Callback] 未找到审批单(' + trigger + ')');
833
+ return;
834
+ }
835
+
836
+ const matchedSpNo = spNoList[0];
837
+ const detail = await this.approval.getApprovalDetail(matchedSpNo);
838
+ if (detail?.errcode === 0 || detail?.sp_detail) {
839
+ record.spNo = matchedSpNo;
840
+ record.approvalDetail = detail.sp_detail || detail;
841
+ record.fetchTime = new Date().toISOString();
842
+ this._appendToFile('approval_detail.jsonl', {
843
+ sp_no: matchedSpNo, trigger,
844
+ callback_time: new Date(callbackTime).toISOString(),
845
+ fetch_time: record.fetchTime,
846
+ detail: record.approvalDetail, raw: event
847
+ });
848
+ console.log('[Callback] 审批详情已保存: ' + matchedSpNo);
849
+ } else {
850
+ console.log('[Callback] 获取审批详情失败: ' + (detail?.errmsg || JSON.stringify(detail).slice(0, 100)));
851
+ }
852
+ } catch (e) {
853
+ console.log('[Callback] _fetchAndSaveApprovalDetail 异常: ' + e.message);
854
+ }
855
+ }
856
+
857
+
858
+ /**
859
+ * 打卡事件
860
+ */
861
+ async handleCheckin(event, record) {
862
+ console.log('[Callback] 打卡:', event);
863
+ return { handled: true };
864
+ }
865
+
866
+ /**
867
+ * 会议开始
868
+ */
869
+ /**
870
+ * 会议开始
871
+ * 关键字段: MeetingId, RoomId, Topic, StartTime, EndTime, JoinUrl, HostUserId
872
+ */
873
+ async handleMeetingStart(event, record) {
874
+ record.meetingId = event.MeetingId || event.meeting_id || '';
875
+ record.roomId = event.RoomId || event.room_id || '';
876
+ record.topic = event.Topic || event.topic || '';
877
+ record.startTime = event.StartTime || event.start_time || 0;
878
+ record.endTime = event.EndTime || event.end_time || 0;
879
+ record.joinUrl = event.JoinUrl || event.join_url || '';
880
+ record.hostUserId = event.HostUserId || event.host_user_id || '';
881
+ console.log('[Callback] 会议开始: MeetingId=' + record.meetingId + ' Topic=' + record.topic + ' Host=' + record.hostUserId);
882
+ this.emit('meeting:start', event, record);
883
+ return { handled: true, meetingId: record.meetingId, topic: record.topic };
884
+ }
885
+
886
+
887
+ /**
888
+ * 会议结束
889
+ */
890
+ /**
891
+ * 会议结束
892
+ * 关键字段: MeetingId, RoomId, Topic, StartTime, EndTime, HostUserId, RemainDuration
893
+ */
894
+ async handleMeetingEnd(event, record) {
895
+ record.meetingId = event.MeetingId || event.meeting_id || '';
896
+ record.roomId = event.RoomId || event.room_id || '';
897
+ record.topic = event.Topic || event.topic || '';
898
+ record.startTime = event.StartTime || event.start_time || 0;
899
+ record.endTime = event.EndTime || event.end_time || 0;
900
+ record.hostUserId = event.HostUserId || event.host_user_id || '';
901
+ record.remainDuration = event.RemainDuration || event.remain_duration || 0;
902
+ console.log('[Callback] 会议结束: MeetingId=' + record.meetingId + ' Topic=' + record.topic);
903
+ this.emit('meeting:end', event, record);
904
+ return { handled: true, meetingId: record.meetingId, topic: record.topic };
905
+ }
906
+
907
+
908
+ // ========== 新增的回调处理器实现 ==========
909
+
910
+ /**
911
+ * 添加联系我
912
+ */
913
+ async handleAddContactWay(event, record) {
914
+ console.log('[Callback] 添加联系我:', event);
915
+ this.emit('contact_way:add', event, record);
916
+ return { handled: true };
917
+ }
918
+
919
+ /**
920
+ * 删除联系我
921
+ */
922
+ async handleDelContactWay(event, record) {
923
+ console.log('[Callback] 删除联系我:', event);
924
+ this.emit('contact_way:del', event, record);
925
+ return { handled: true };
926
+ }
927
+
928
+ /**
929
+ * 添加加入群聊方式
930
+ */
931
+ async handleAddJoinWay(event, record) {
932
+ console.log('[Callback] 添加加入群聊方式:', event);
933
+ this.emit('join_way:add', event, record);
934
+ return { handled: true };
935
+ }
936
+
937
+ /**
938
+ * 删除加入群聊方式
939
+ */
940
+ async handleDelJoinWay(event, record) {
941
+ console.log('[Callback] 删除加入群聊方式:', event);
942
+ this.emit('join_way:del', event, record);
943
+ return { handled: true };
944
+ }
945
+
946
+ /**
947
+ * 客服消息推送
948
+ */
949
+ async handleKfMsgPush(event, record) {
950
+ console.log('[Callback] 客服消息推送:', event);
951
+ this.emit('kf_msg:push', event, record);
952
+ return { handled: true };
953
+ }
954
+
955
+ /**
956
+ * 客服消息发送
957
+ */
958
+ async handleKfMsgSend(event, record) {
959
+ console.log('[Callback] 客服消息发送:', event);
960
+ this.emit('kf_msg:send', event, record);
961
+ return { handled: true };
962
+ }
963
+
964
+ /**
965
+ * 消息确认
966
+ */
967
+ async handleMsgDialogiceSend(event, record) {
968
+ console.log('[Callback] 消息确认:', event);
969
+ this.emit('msg:confirm', event, record);
970
+ return { handled: true };
971
+ }
972
+
973
+ /**
974
+ * 成员变更通知
975
+ */
976
+ /**
977
+ * 成员变更事件
978
+ * 关键字段: ChangeType, UserID, NewUserID, Name, Department, Position
979
+ */
980
+ async handleChangeMember(event, record) {
981
+ const ct = event.ChangeType || event.change_type || 0;
982
+ record.changeType = ct;
983
+ record.userId = event.UserID || event.userid || '';
984
+ record.newUserId = event.NewUserID || event.new_userid || '';
985
+ record.name = event.Name || event.name || '';
986
+ record.department = event.Department || event.department || [];
987
+ record.position = event.Position || event.position || '';
988
+ const ctMap = {1:'新增', 2:'更新', 3:'删除'};
989
+ record.changeTypeText = ctMap[ct] || '变更('+ct+')';
990
+ console.log('[Callback] 成员变更: ' + record.changeTypeText + ' UserId=' + record.userId + ' Name=' + record.name);
991
+ this.emit('contact:member_change', event, record);
992
+ return { handled: true, changeType: record.changeTypeText, userId: record.userId };
993
+ }
994
+
995
+
996
+ /**
997
+ * 部门变更通知
998
+ */
999
+ /**
1000
+ * 部门变更事件
1001
+ * 关键字段: ChangeType, Id, Name, ParentId, Order
1002
+ */
1003
+ async handleChangeDepartment(event, record) {
1004
+ const ct = event.ChangeType || event.change_type || 0;
1005
+ record.changeType = ct;
1006
+ record.deptId = event.Id || event.id || '';
1007
+ record.name = event.Name || event.name || '';
1008
+ record.parentId = event.ParentId || event.parentid || '';
1009
+ record.order = event.Order || event.order || 0;
1010
+ const ctMap = {1:'新增', 2:'更新', 3:'删除'};
1011
+ record.changeTypeText = ctMap[ct] || '变更('+ct+')';
1012
+ console.log('[Callback] 部门变更: ' + record.changeTypeText + ' DeptId=' + record.deptId + ' Name=' + record.name);
1013
+ this.emit('contact:department_change', event, record);
1014
+ return { handled: true, changeType: record.changeTypeText, deptId: record.deptId };
1015
+ }
1016
+
1017
+
1018
+ /**
1019
+ * 标签变更通知
1020
+ */
1021
+ /**
1022
+ * 标签变更事件
1023
+ * 关键字段: ChangeType, TagId, TagName, AddUserIds, DelUserIds
1024
+ */
1025
+ async handleChangeTag(event, record) {
1026
+ const ct = event.ChangeType || event.change_type || 0;
1027
+ record.changeType = ct;
1028
+ record.tagId = event.TagId || event.tagid || '';
1029
+ record.tagName = event.TagName || event.tagname || '';
1030
+ record.addUserIds = event.AddUserIds || event.add_userids || [];
1031
+ record.delUserIds = event.DelUserIds || event.del_userids || [];
1032
+ const ctMap = {1:'新增', 2:'更新', 3:'删除'};
1033
+ record.changeTypeText = ctMap[ct] || '变更('+ct+')';
1034
+ console.log('[Callback] 标签变更: ' + record.changeTypeText + ' TagId=' + record.tagId + ' TagName=' + record.tagName);
1035
+ this.emit('contact:tag_change', event, record);
1036
+ return { handled: true, changeType: record.changeTypeText, tagId: record.tagId };
1037
+ }
1038
+
1039
+
1040
+ /**
1041
+ * 会议真正结束(历史记录生成后)
1042
+ */
1043
+ async handleMeetingEnded(event, record) {
1044
+ console.log('[Callback] 会议结束(历史记录):', event);
1045
+ this.emit('meeting:ended', event, record);
1046
+ return { handled: true };
1047
+ }
1048
+
1049
+ /**
1050
+ * 参会成员加入
1051
+ */
1052
+ /**
1053
+ * 参会成员加入
1054
+ * 关键字段: MeetingId, RoomId, ParticipantUserId, JoinTime
1055
+ */
1056
+ async handleMeetingParticipantJoin(event, record) {
1057
+ record.meetingId = event.MeetingId || event.meeting_id || '';
1058
+ record.roomId = event.RoomId || event.room_id || '';
1059
+ record.participantUserId = event.ParticipantUserId || event.participant_userid || event.UserId || '';
1060
+ record.joinTime = event.JoinTime || event.join_time || 0;
1061
+ console.log('[Callback] 参会成员加入: MeetingId=' + record.meetingId + ' User=' + record.participantUserId);
1062
+ this.emit('meeting:participant_join', event, record);
1063
+ return { handled: true, meetingId: record.meetingId, participantUserId: record.participantUserId };
1064
+ }
1065
+
1066
+
1067
+ /**
1068
+ * 参会成员离开
1069
+ */
1070
+ /**
1071
+ * 参会成员离开
1072
+ * 关键字段: MeetingId, RoomId, ParticipantUserId, LeaveTime
1073
+ */
1074
+ async handleMeetingParticipantLeave(event, record) {
1075
+ record.meetingId = event.MeetingId || event.meeting_id || '';
1076
+ record.roomId = event.RoomId || event.room_id || '';
1077
+ record.participantUserId = event.ParticipantUserId || event.participant_userid || event.UserId || '';
1078
+ record.leaveTime = event.LeaveTime || event.leave_time || 0;
1079
+ console.log('[Callback] 参会成员离开: MeetingId=' + record.meetingId + ' User=' + record.participantUserId);
1080
+ this.emit('meeting:participant_leave', event, record);
1081
+ return { handled: true, meetingId: record.meetingId, participantUserId: record.participantUserId };
1082
+ }
1083
+
1084
+
1085
+ /**
1086
+ * 直播状态变更
1087
+ */
1088
+ async handleLivingStatus(event, record) {
1089
+ console.log('[Callback] 直播状态变更:', event);
1090
+ this.emit('living:status_change', event, record);
1091
+ return { handled: true };
1092
+ }
1093
+
1094
+ /**
1095
+ * 微盘容量变更
1096
+ */
1097
+ async handleChangePsm(event, record) {
1098
+ console.log('[Callback] 微盘容量变更:', event);
1099
+ this.emit('disk:psm_change', event, record);
1100
+ return { handled: true };
1101
+ }
1102
+
1103
+ /**
1104
+ * 微盘文件变更
1105
+ */
1106
+ async handleChangeDisk(event, record) {
1107
+ console.log('[Callback] 微盘文件变更:', event);
1108
+ this.emit('disk:file_change', event, record);
1109
+ return { handled: true };
1110
+ }
1111
+
1112
+ // ========== 消息构建 ==========
1113
+
1114
+ /**
1115
+ * 构建文本回复
1116
+ */
1117
+ buildTextReply(toUser, fromUser, content) {
1118
+ return this._buildReply(toUser, fromUser, 'text', { content });
1119
+ }
1120
+
1121
+ /**
1122
+ * 构建图片回复
1123
+ */
1124
+ buildImageReply(toUser, fromUser, mediaId) {
1125
+ return this._buildReply(toUser, fromUser, 'image', { mediaId });
1126
+ }
1127
+
1128
+ /**
1129
+ * 构建通用回复
1130
+ */
1131
+ _buildReply(toUser, fromUser, msgType, content) {
1132
+ let contentXml = '';
1133
+
1134
+ switch (msgType) {
1135
+ case 'text':
1136
+ contentXml = `<Content><![CDATA[${content.content}]]></Content>`;
1137
+ break;
1138
+ case 'image':
1139
+ contentXml = `<Image><MediaId><![CDATA[${content.mediaId}]]></MediaId></Image>`;
1140
+ break;
1141
+ }
1142
+
1143
+ return `<xml>
1144
+ <ToUserName><![CDATA[${toUser}]]></ToUserName>
1145
+ <FromUserName><![CDATA[${fromUser}]]></FromUserName>
1146
+ <CreateTime>${Date.now()}</CreateTime>
1147
+ <MsgType><![CDATA[${msgType}]]></MsgType>
1148
+ ${contentXml}
1149
+ </xml>`;
1150
+ }
1151
+
1152
+ // ========== 工具方法 ==========
1153
+
1154
+ getSignature(timestamp, nonce, encrypt) {
1155
+ const arr = [this.token, timestamp, nonce, encrypt].sort();
1156
+ const str = arr.join('');
1157
+ return crypto.createHash('sha1').update(str).digest('hex');
1158
+ }
1159
+
1160
+ generateRandomStr(length = 16) {
1161
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
1162
+ let result = '';
1163
+ for (let i = 0; i < length; i++) {
1164
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
1165
+ }
1166
+ return result;
1167
+ }
1168
+
1169
+ intToBuffer(num) {
1170
+ const buffer = Buffer.alloc(4);
1171
+ buffer.writeUInt32BE(num, 0);
1172
+ return buffer;
1173
+ }
1174
+
1175
+ _generateId() {
1176
+ return `evt_${Date.now()}_${this.generateRandomStr(8)}`;
1177
+ }
1178
+ }
1179
+
1180
+ module.exports = Callback;