wechaty-web-panel 1.6.120 → 1.6.122

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.
@@ -34,6 +34,16 @@ async function onRecordMessage(msg) {
34
34
  if (isOfficial)
35
35
  return;
36
36
  console.log('msg', msg);
37
+ let isMention = false;
38
+ if (room) {
39
+ const userSelfName = this.currentUser?.name() || '';
40
+ const msgText = msg.type() === this.Message.Type.Text ? msg.text() : '';
41
+ isMention = (await msg.mentionSelf()) || msgText.includes(`@${userSelfName}`);
42
+ const isMentionAll = await msg.isMentionAll();
43
+ if (isMentionAll && isMention) {
44
+ isMention = false;
45
+ }
46
+ }
37
47
  const baseMsg = {
38
48
  conversionId: room ? room.id : contact.id,
39
49
  conversionName: room ? roomName : contactName,
@@ -43,7 +53,8 @@ async function onRecordMessage(msg) {
43
53
  chatAlias: contactAlias,
44
54
  chatUserWeixin: userWeixin,
45
55
  isMyself: !!msgSelf,
46
- time: timestamp.length > 10 ? parseInt(timestamp / 1000) : timestamp
56
+ isMention,
57
+ time: String(timestamp).length > 10 ? Math.floor(timestamp / 1000) : timestamp
47
58
  };
48
59
  switch (type) {
49
60
  case this.Message.Type.Channel:
@@ -127,12 +138,12 @@ async function onRecordMessage(msg) {
127
138
  break;
128
139
  case this.Message.Type.ChatHistory:
129
140
  const historys = await msg.toChatHistory();
130
- const contents = await formatHistory(this, historys.payload, conversationRecord);
141
+ const contents = await formatHistory(this, historys.payload, conversationRecord, msgId);
131
142
  baseMsg.type = '历史记录';
132
143
  baseMsg.content = contents;
133
144
  break;
134
145
  case this.Message.Type.Location:
135
- const location = await msg.toLocation();
146
+ const locationInfo = await msg.toLocation();
136
147
  const locationParse = `【位置解析结果】\n纬度:${locationInfo?.payload?.latitude}\n经度:${locationInfo?.payload?.longitude}\n地点名:${locationInfo?.payload?.name}\n城市:${locationInfo?.payload?.city || ''}\n具体地址:${locationInfo?.payload?.address}\nPoiId:${locationInfo?.payload?.poiId || ''}`;
137
148
  baseMsg.type = '位置';
138
149
  baseMsg.content = locationParse;
@@ -149,7 +160,6 @@ async function onRecordMessage(msg) {
149
160
  baseMsg.content = sysText;
150
161
  }
151
162
  }
152
- default:
153
163
  break;
154
164
  }
155
165
  console.log('baseMsg', baseMsg);
@@ -159,9 +169,9 @@ async function onRecordMessage(msg) {
159
169
  console.log('记录消息失败', e);
160
170
  }
161
171
  }
162
- async function formatHistory(that, history, conversationRecord) {
172
+ async function formatHistory(that, historyList, conversationRecord, msgId) {
163
173
  const contents = [];
164
- for (const history of historys) {
174
+ for (const history of historyList) {
165
175
  if (history.type === that.Message.Type.Text) {
166
176
  contents.push(history.message);
167
177
  }
@@ -169,20 +179,19 @@ async function formatHistory(that, history, conversationRecord) {
169
179
  const attachFileBox = await history.message.toFileBox();
170
180
  const fileExtname = path.extname(attachFileBox.name);
171
181
  const isImage = fileExtname.includes('.png') || fileExtname.includes('.jpg') || fileExtname.includes('.jpeg') || fileExtname.includes('.gif');
172
- baseMsg.type = isImage ? '图片' : '文件';
173
182
  const buffer = await attachFileBox.toBuffer();
174
183
  const url = await uploadOssFile(`${conversationRecord?.ossConfig?.custom_path || ''}${msgId}_${dayjs().valueOf()}_${attachFileBox.name}`, buffer);
175
- contents.push(`()[${url}]`);
184
+ contents.push(`[${isImage ? '图片' : '文件'}](${url})`);
176
185
  }
177
186
  else if (history.type === that.Message.Type.ChatHistory) {
178
- const res = await formatHistory(that, history.message, conversationRecord);
187
+ const res = await formatHistory(that, history.message, conversationRecord, msgId);
179
188
  contents.push(res);
180
189
  }
181
190
  }
182
191
  return contents.join('\n');
183
192
  }
184
193
  function sendMessage(msgInfo, recordConfig, robotInfo) {
185
- const blackKey = ['conversationId', 'conversionName', 'isRoom', 'isRobot', 'chatName', 'chatId', 'chatAlias', 'time', 'type', 'url', 'mediaInfo', 'content', 'isMyself'];
194
+ const blackKey = Object.keys(msgInfo);
186
195
  const baseData = {
187
196
  ...msgInfo
188
197
  };
@@ -19,19 +19,19 @@ async function checkAllow(clawConfig, contactId, roomId) {
19
19
  }
20
20
  else if (allowScope === 2) {
21
21
  // 仅所有群
22
- return !!room;
22
+ return !!roomId;
23
23
  }
24
24
  else if (allowScope === 3) {
25
25
  // 仅所有好友
26
- return !room;
26
+ return !roomId;
27
27
  }
28
28
  else if (allowScope === 4) {
29
29
  // 部分群和部分好友
30
- if (room) {
30
+ if (roomId) {
31
31
  return allowRooms.some(r => r.value === roomId);
32
32
  }
33
33
  else {
34
- return allowFriends.some(f => f.value === contactId);
34
+ return allowFriends.some(f => f.id === contactId);
35
35
  }
36
36
  }
37
37
  return false;
@@ -71,6 +71,16 @@ async function onClawMessage(msg) {
71
71
  if (isOfficial)
72
72
  return;
73
73
  console.log('msg', msg);
74
+ let isMention = false;
75
+ if (room) {
76
+ const userSelfName = this.currentUser?.name() || '';
77
+ const msgText = type === this.Message.Type.Text ? msg.text() : '';
78
+ isMention = (await msg.mentionSelf()) || msgText.includes(`@${userSelfName}`);
79
+ const isMentionAll = await msg.isMentionAll();
80
+ if (isMentionAll && isMention) {
81
+ isMention = false;
82
+ }
83
+ }
74
84
  const baseMsg = {
75
85
  conversionId: room ? room.id : contact.id,
76
86
  conversionName: room ? roomName : contactName,
@@ -80,14 +90,15 @@ async function onClawMessage(msg) {
80
90
  chatAlias: contactAlias,
81
91
  chatUserWeixin: userWeixin,
82
92
  isMyself: !!msgSelf,
83
- time: timestamp.length > 10 ? parseInt(timestamp / 1000) : timestamp,
93
+ time: String(timestamp).length > 10 ? Math.floor(timestamp / 1000) : timestamp,
84
94
  msgId: msg.id,
95
+ isMention,
85
96
  };
86
97
  switch (type) {
87
98
  case this.Message.Type.Channel:
88
99
  baseMsg.type = '视频号';
89
100
  const channelInfo = await msg.toChannel();
90
- baseMsg.content = `【视频号消息】\n视频号昵称:${channelInfo.nickname()}\n视频号简介:${channelInfo.desc}\n视频号链接:${channelInfo.url}`;
101
+ baseMsg.content = `【视频号消息】\n视频号昵称:${channelInfo.nickname()}\n视频号简介:${channelInfo.desc()}\n视频号链接:${channelInfo.url()}`;
91
102
  baseMsg.mediaInfo = {
92
103
  nickname: channelInfo.nickname(),
93
104
  coverUrl: channelInfo.coverUrl(),
@@ -144,7 +155,7 @@ async function onClawMessage(msg) {
144
155
  }
145
156
  else {
146
157
  // 没有配置OSS,使用base64
147
- const base64 = attachFileBox.toBase64();
158
+ const base64 = await attachFileBox.toBase64();
148
159
  baseMsg.url = base64;
149
160
  baseMsg.content = `【${isImage ? '图片' : '文件'}消息】\n文件名:${attachFileBox.name}\n文件大小:${buffer.length} bytes\n(文件内容未上传,仅提供base64字符串)`;
150
161
  }
@@ -165,7 +176,7 @@ async function onClawMessage(msg) {
165
176
  }
166
177
  else {
167
178
  // 没有配置OSS,使用base64
168
- const audioBase64 = audioFileBox.toBase64();
179
+ const audioBase64 = await audioFileBox.toBase64();
169
180
  baseMsg.url = audioBase64;
170
181
  baseMsg.content = `【语音消息】\n文件名:${audioFileBox.name}\n文件大小:${audioBuffer.length} bytes\n(文件内容未上传,仅提供base64字符串)`;
171
182
  }
@@ -177,7 +188,7 @@ async function onClawMessage(msg) {
177
188
  baseMsg.content = contents;
178
189
  break;
179
190
  case this.Message.Type.Location:
180
- const location = await msg.toLocation();
191
+ const locationInfo = await msg.toLocation();
181
192
  const locationParse = `【位置解析结果】\n纬度:${locationInfo?.payload?.latitude}\n经度:${locationInfo?.payload?.longitude}\n地点名:${locationInfo?.payload?.name}\n城市:${locationInfo?.payload?.city || ''}\n具体地址:${locationInfo?.payload?.address}\nPoiId:${locationInfo?.payload?.poiId || ''}`;
182
193
  baseMsg.type = '位置';
183
194
  baseMsg.content = locationParse;
@@ -204,9 +215,9 @@ async function onClawMessage(msg) {
204
215
  console.log('claw 消息处理失败', e);
205
216
  }
206
217
  }
207
- async function formatHistory(that, history, clawConfig) {
218
+ async function formatHistory(that, historyList, clawConfig) {
208
219
  const contents = [];
209
- for (const history of historys) {
220
+ for (const history of historyList) {
210
221
  if (history.type === that.Message.Type.Text) {
211
222
  contents.push(history.message);
212
223
  }
@@ -214,7 +225,6 @@ async function formatHistory(that, history, clawConfig) {
214
225
  const attachFileBox = await history.message.toFileBox();
215
226
  const fileExtname = path.extname(attachFileBox.name);
216
227
  const isImage = fileExtname.includes('.png') || fileExtname.includes('.jpg') || fileExtname.includes('.jpeg') || fileExtname.includes('.gif');
217
- baseMsg.type = isImage ? '图片' : '文件';
218
228
  if (attachFileBox.remoteUrl) {
219
229
  // 直接使用远程链接,无需上传
220
230
  const url = attachFileBox.remoteUrl;
@@ -223,7 +233,7 @@ async function formatHistory(that, history, clawConfig) {
223
233
  }
224
234
  const buffer = await attachFileBox.toBuffer();
225
235
  if (!clawConfig?.ossConfig.type) {
226
- const base64 = attachFileBox.toBase64();
236
+ const base64 = await attachFileBox.toBase64();
227
237
  contents.push(`【${isImage ? '图片' : '文件'}消息】\n文件名:${attachFileBox.name}\n文件大小:${buffer.length} bytes\n(文件内容未上传,仅提供base64字符串)`);
228
238
  continue;
229
239
  }
@@ -252,21 +262,22 @@ function publishClawMessage(baseMsg, robotInfo) {
252
262
  isGroup: baseMsg.isRoom,
253
263
  groupId: baseMsg.isRoom ? baseMsg.conversionId : undefined,
254
264
  groupName: baseMsg.isRoom ? baseMsg.conversionName : undefined,
255
- timestamp: baseMsg.time * 1000, // 转换为 11 位毫秒时间戳
265
+ isGroupMention: baseMsg.isMention,
266
+ timestamp: baseMsg.time * 1000, // 转换为 13 位毫秒时间戳
256
267
  messageId: baseMsg.msgId,
257
268
  type: baseMsg.type,
258
269
  mediaInfo: baseMsg.mediaInfo,
259
270
  url: baseMsg.url,
260
271
  };
261
- console.log('发布 Claw MQTT 消息', payload);
272
+ console.log('发布 OpenClaw(小龙虾) 消息', payload);
262
273
  clawMqttClient.publish(clawMqttClient._clawReciveTopic, JSON.stringify(payload), { qos: 1 });
263
274
  }
264
275
  catch (e) {
265
- console.log('发布 Claw MQTT 消息失败', e);
276
+ console.log('发布 OpenClaw(小龙虾) 消息失败', e);
266
277
  }
267
278
  }
268
279
  /**
269
- * 初始化 Claw MQTT 连接
280
+ * 初始化 OpenClaw(小龙虾) 连接
270
281
  * 1. 连接 MQTT,将入站消息发布到 reciveTopic
271
282
  * 2. 订阅 sendTopic,收到消息后通过微信发送
272
283
  */
@@ -285,7 +296,7 @@ async function initClawMqtt(that) {
285
296
  const { port, host, username, password, clientId, reciveTopic, sendTopic } = mqttConfig;
286
297
  if (!host)
287
298
  return;
288
- clawMqttClient = mqtt.connect(`mqtt://${host}:${port}`, {
299
+ clawMqttClient = mqtt.connect(`${host}:${port}`, {
289
300
  username,
290
301
  password,
291
302
  clientId: clientId + randomRange(1, 10000),
@@ -293,21 +304,21 @@ async function initClawMqtt(that) {
293
304
  // 在 client 上挂载 topic 便于 publishClawMessage 使用
294
305
  clawMqttClient._clawReciveTopic = reciveTopic;
295
306
  clawMqttClient.on('connect', function () {
296
- console.log('Claw MQTT 连接成功');
307
+ console.log('OpenClaw(小龙虾) 连接成功');
297
308
  clawMqttClient.subscribe(sendTopic, function (err) {
298
309
  if (err) {
299
- console.log('Claw MQTT 订阅失败', err);
310
+ console.log('OpenClaw(小龙虾) 订阅失败', err);
300
311
  }
301
312
  else {
302
- console.log(`Claw MQTT 订阅成功: ${sendTopic}`);
313
+ console.log(`OpenClaw(小龙虾) 订阅成功`);
303
314
  }
304
315
  });
305
316
  });
306
317
  clawMqttClient.on('reconnect', function () {
307
- console.log('Claw MQTT 重连中...');
318
+ console.log('OpenClaw(小龙虾) 重连中...');
308
319
  });
309
320
  clawMqttClient.on('error', function (e) {
310
- console.log('Claw MQTT 错误', e);
321
+ console.log('OpenClaw(小龙虾) 连接错误', e);
311
322
  });
312
323
  clawMqttClient.on('message', async function (topic, message) {
313
324
  try {
@@ -331,7 +342,7 @@ async function initClawMqtt(that) {
331
342
  if (isGroup && groupId) {
332
343
  const room = await that.Room.find({ id: groupId });
333
344
  if (!room) {
334
- console.log(`Claw MQTT: 查找不到群 ${groupId}`);
345
+ console.log(`OpenClaw(小龙虾): 查找不到群 ${groupId}`);
335
346
  return;
336
347
  }
337
348
  // 解析 @ 联系人
@@ -362,7 +373,7 @@ async function initClawMqtt(that) {
362
373
  else if (!isGroup && contactId) {
363
374
  const contact = await that.Contact.find({ id: contactId });
364
375
  if (!contact) {
365
- console.log(`Claw MQTT: 查找不到联系人 ${contactId}`);
376
+ console.log(`OpenClaw(小龙虾): 查找不到联系人 ${contactId}`);
366
377
  return;
367
378
  }
368
379
  for (const msg of messages) {
@@ -378,12 +389,12 @@ async function initClawMqtt(that) {
378
389
  }
379
390
  }
380
391
  catch (e) {
381
- console.log('Claw MQTT 处理消息失败', e);
392
+ console.log('OpenClaw(小龙虾) 处理消息失败', e);
382
393
  }
383
394
  });
384
395
  }
385
396
  catch (e) {
386
- console.log('Claw MQTT 初始化失败', e);
397
+ console.log('OpenClaw(小龙虾) 初始化失败', e);
387
398
  }
388
399
  }
389
400
  function closeClawMqtt() {
@@ -139,7 +139,7 @@ async function dispatchFriendFilterByMsgType(that, msg) {
139
139
  const text = puppetInfo.puppetType.includes('PuppetService') && !msg.text().startsWith('@') ? msg.text().trim() : await getVoiceText(audioFileBox, finalConfig.botConfig.whisperConfig);
140
140
  console.log('语音解析结果:', text);
141
141
  const keyword = finalConfig.botConfig.whisperConfig?.keywords?.length ? finalConfig.botConfig?.whisperConfig.keywords?.find((item) => text.includes(item)) : true;
142
- const isIgnore = checkIgnore(content.trim(), aibotConfig.ignoreMessages);
142
+ const isIgnore = checkIgnore(text.trim(), aibotConfig.ignoreMessages);
143
143
  if (text.trim() && !isIgnore && keyword) {
144
144
  const gpt4vReplys = await getGpt4vChat({
145
145
  that,
@@ -332,7 +332,7 @@ async function dispatchRoomFilterByMsgType(that, room, msg) {
332
332
  const type = msg.type();
333
333
  const receiver = msg.to();
334
334
  let content = '';
335
- let replys = '';
335
+ let replys = [];
336
336
  let contactId = contact.id;
337
337
  let contactAvatar = await contact.avatar();
338
338
  const userSelfName = that.currentUser?.name() || that.userSelf()?.name();
@@ -526,7 +526,7 @@ async function dispatchRoomFilterByMsgType(that, room, msg) {
526
526
  const text = puppetInfo.puppetType.includes('PuppetService') && !msg.text().startsWith('@') ? msg.text().trim() : await getVoiceText(audioFileBox, finalConfig.botConfig.whisperConfig);
527
527
  console.log('语音解析结果', text);
528
528
  const keyword = finalConfig.botConfig.whisperConfig?.keywords?.length ? finalConfig.botConfig?.whisperConfig?.keywords?.find((item) => text.includes(item)) : true;
529
- const isIgnore = checkIgnore(content.trim(), aibotConfig.ignoreMessages);
529
+ const isIgnore = checkIgnore(text.trim(), aibotConfig.ignoreMessages);
530
530
  if (text.trim() && !isIgnore && keyword) {
531
531
  const gpt4vReplys = await getGpt4vChat({
532
532
  that,
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const packageJson = {
5
5
  "name": "wechaty-web-panel",
6
- "version": "1.6.120",
6
+ "version": "1.6.122",
7
7
  "exports": {
8
8
  ".": {
9
9
  "import": "./dist/index.js"
@@ -237,9 +237,9 @@ async function getConfig() {
237
237
  uploadFileConfig: null,
238
238
  ignoreFiles: false,
239
239
  filterLinkContent: false,
240
+ clawConfig: { open: false, allowScope: 1, allowFriends: [], allowRooms: [], forwardAllMsg: true, forwardIncludeKeywordsMsg: [], forwardMediaMsg: false, ossConfig: {} },
240
241
  ...config,
241
242
  cloudRoom,
242
- clawConfig: { open: false, allowScope: 1, allowFriends: [], allowRooms: [], forwardAllMsg: true, forwardIncludeKeywordsMsg: [], forwardMediaMsg: false, ossConfig: {} }
243
243
  });
244
244
  return cres;
245
245
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wechaty-web-panel",
3
- "version": "1.6.120",
3
+ "version": "1.6.122",
4
4
  "exports": {
5
5
  ".": {
6
6
  "import": "./dist/index.js"
@@ -34,6 +34,16 @@ async function onRecordMessage(msg) {
34
34
  const userWeixin = contact && contact.weixin() || ''
35
35
  if (isOfficial) return
36
36
  console.log('msg', msg)
37
+ let isMention = false
38
+ if (room) {
39
+ const userSelfName = this.currentUser?.name() || ''
40
+ const msgText = msg.type() === this.Message.Type.Text ? msg.text() : ''
41
+ isMention = (await msg.mentionSelf()) || msgText.includes(`@${userSelfName}`)
42
+ const isMentionAll = await msg.isMentionAll()
43
+ if (isMentionAll && isMention) {
44
+ isMention = false
45
+ }
46
+ }
37
47
  const baseMsg = {
38
48
  conversionId: room ? room.id : contact.id,
39
49
  conversionName: room ? roomName : contactName,
@@ -43,7 +53,8 @@ async function onRecordMessage(msg) {
43
53
  chatAlias: contactAlias,
44
54
  chatUserWeixin: userWeixin,
45
55
  isMyself: !!msgSelf,
46
- time: timestamp.length > 10 ? parseInt(timestamp / 1000) : timestamp
56
+ isMention,
57
+ time: String(timestamp).length > 10 ? Math.floor(timestamp / 1000) : timestamp
47
58
  }
48
59
  switch (type) {
49
60
  case this.Message.Type.Channel:
@@ -127,12 +138,12 @@ async function onRecordMessage(msg) {
127
138
  break
128
139
  case this.Message.Type.ChatHistory:
129
140
  const historys = await msg.toChatHistory()
130
- const contents = await formatHistory(this, historys.payload, conversationRecord)
141
+ const contents = await formatHistory(this, historys.payload, conversationRecord, msgId)
131
142
  baseMsg.type = '历史记录'
132
143
  baseMsg.content = contents
133
144
  break
134
145
  case this.Message.Type.Location:
135
- const location = await msg.toLocation()
146
+ const locationInfo = await msg.toLocation()
136
147
  const locationParse = `【位置解析结果】\n纬度:${locationInfo?.payload?.latitude}\n经度:${locationInfo?.payload?.longitude}\n地点名:${locationInfo?.payload?.name}\n城市:${locationInfo?.payload?.city || ''}\n具体地址:${locationInfo?.payload?.address}\nPoiId:${locationInfo?.payload?.poiId || ''}`
137
148
  baseMsg.type = '位置'
138
149
  baseMsg.content = locationParse
@@ -148,7 +159,6 @@ async function onRecordMessage(msg) {
148
159
  baseMsg.content = sysText
149
160
  }
150
161
  }
151
- default:
152
162
  break
153
163
  }
154
164
  console.log('baseMsg', baseMsg)
@@ -158,21 +168,20 @@ async function onRecordMessage(msg) {
158
168
  }
159
169
  }
160
170
 
161
- async function formatHistory(that, history, conversationRecord) {
171
+ async function formatHistory(that, historyList, conversationRecord, msgId) {
162
172
  const contents = []
163
- for (const history of historys) {
173
+ for (const history of historyList) {
164
174
  if (history.type === that.Message.Type.Text) {
165
175
  contents.push(history.message)
166
176
  } else if (history.type === that.Message.Type.Recalled || history.type === that.Message.Type.Attachment || history.type === that.Message.Type.Image || history.type === that.Message.Type.Video) {
167
177
  const attachFileBox = await history.message.toFileBox();
168
178
  const fileExtname = path.extname(attachFileBox.name);
169
179
  const isImage = fileExtname.includes('.png') || fileExtname.includes('.jpg') || fileExtname.includes('.jpeg') || fileExtname.includes('.gif')
170
- baseMsg.type = isImage ? '图片' : '文件'
171
180
  const buffer = await attachFileBox.toBuffer()
172
181
  const url = await uploadOssFile(`${conversationRecord?.ossConfig?.custom_path || ''}${msgId}_${dayjs().valueOf()}_${attachFileBox.name}`, buffer)
173
- contents.push(`()[${url}]`)
182
+ contents.push(`[${isImage ? '图片' : '文件'}](${url})`)
174
183
  } else if (history.type === that.Message.Type.ChatHistory) {
175
- const res = await formatHistory(that, history.message, conversationRecord)
184
+ const res = await formatHistory(that, history.message, conversationRecord, msgId)
176
185
  contents.push(res)
177
186
  }
178
187
  }
@@ -180,7 +189,7 @@ async function formatHistory(that, history, conversationRecord) {
180
189
  }
181
190
 
182
191
  function sendMessage(msgInfo, recordConfig, robotInfo) {
183
- const blackKey = ['conversationId', 'conversionName', 'isRoom', 'isRobot', 'chatName', 'chatId', 'chatAlias', 'time', 'type', 'url', 'mediaInfo', 'content', 'isMyself']
192
+ const blackKey = Object.keys(msgInfo)
184
193
  const baseData = {
185
194
  ...msgInfo
186
195
  }
@@ -21,16 +21,16 @@ async function checkAllow(clawConfig, contactId, roomId) {
21
21
  return true
22
22
  } else if (allowScope === 2) {
23
23
  // 仅所有群
24
- return !!room
24
+ return !!roomId
25
25
  } else if (allowScope === 3) {
26
26
  // 仅所有好友
27
- return !room
27
+ return !roomId
28
28
  } else if (allowScope === 4) {
29
29
  // 部分群和部分好友
30
- if (room) {
30
+ if (roomId) {
31
31
  return allowRooms.some(r => r.value === roomId)
32
32
  } else {
33
- return allowFriends.some(f => f.value === contactId)
33
+ return allowFriends.some(f => f.id === contactId)
34
34
  }
35
35
  }
36
36
  return false
@@ -70,6 +70,16 @@ async function onClawMessage(msg) {
70
70
  const userWeixin = contact && contact.weixin() || ''
71
71
  if (isOfficial) return
72
72
  console.log('msg', msg)
73
+ let isMention = false
74
+ if (room) {
75
+ const userSelfName = this.currentUser?.name() || ''
76
+ const msgText = type === this.Message.Type.Text ? msg.text() : ''
77
+ isMention = (await msg.mentionSelf()) || msgText.includes(`@${userSelfName}`)
78
+ const isMentionAll = await msg.isMentionAll()
79
+ if (isMentionAll && isMention) {
80
+ isMention = false
81
+ }
82
+ }
73
83
  const baseMsg = {
74
84
  conversionId: room ? room.id : contact.id,
75
85
  conversionName: room ? roomName : contactName,
@@ -79,14 +89,15 @@ async function onClawMessage(msg) {
79
89
  chatAlias: contactAlias,
80
90
  chatUserWeixin: userWeixin,
81
91
  isMyself: !!msgSelf,
82
- time: timestamp.length > 10 ? parseInt(timestamp / 1000) : timestamp,
92
+ time: String(timestamp).length > 10 ? Math.floor(timestamp / 1000) : timestamp,
83
93
  msgId: msg.id,
94
+ isMention,
84
95
  }
85
96
  switch (type) {
86
97
  case this.Message.Type.Channel:
87
98
  baseMsg.type = '视频号'
88
99
  const channelInfo = await msg.toChannel();
89
- baseMsg.content = `【视频号消息】\n视频号昵称:${channelInfo.nickname()}\n视频号简介:${channelInfo.desc}\n视频号链接:${channelInfo.url}`
100
+ baseMsg.content = `【视频号消息】\n视频号昵称:${channelInfo.nickname()}\n视频号简介:${channelInfo.desc()}\n视频号链接:${channelInfo.url()}`
90
101
  baseMsg.mediaInfo = {
91
102
  nickname: channelInfo.nickname(),
92
103
  coverUrl: channelInfo.coverUrl(),
@@ -143,7 +154,7 @@ async function onClawMessage(msg) {
143
154
  baseMsg.content = `【${isImage ? '图片' : '文件'}消息】\n文件名:${attachFileBox.name}\n下载链接:${url}`
144
155
  } else {
145
156
  // 没有配置OSS,使用base64
146
- const base64 = attachFileBox.toBase64()
157
+ const base64 = await attachFileBox.toBase64()
147
158
  baseMsg.url = base64
148
159
  baseMsg.content = `【${isImage ? '图片' : '文件'}消息】\n文件名:${attachFileBox.name}\n文件大小:${buffer.length} bytes\n(文件内容未上传,仅提供base64字符串)`
149
160
  }
@@ -163,7 +174,7 @@ async function onClawMessage(msg) {
163
174
  baseMsg.content = `【语音消息】\n文件名:${audioFileBox.name}\n下载链接:${audioUrl}`
164
175
  } else {
165
176
  // 没有配置OSS,使用base64
166
- const audioBase64 = audioFileBox.toBase64()
177
+ const audioBase64 = await audioFileBox.toBase64()
167
178
  baseMsg.url = audioBase64
168
179
  baseMsg.content = `【语音消息】\n文件名:${audioFileBox.name}\n文件大小:${audioBuffer.length} bytes\n(文件内容未上传,仅提供base64字符串)`
169
180
  }
@@ -175,7 +186,7 @@ async function onClawMessage(msg) {
175
186
  baseMsg.content = contents
176
187
  break
177
188
  case this.Message.Type.Location:
178
- const location = await msg.toLocation()
189
+ const locationInfo = await msg.toLocation()
179
190
  const locationParse = `【位置解析结果】\n纬度:${locationInfo?.payload?.latitude}\n经度:${locationInfo?.payload?.longitude}\n地点名:${locationInfo?.payload?.name}\n城市:${locationInfo?.payload?.city || ''}\n具体地址:${locationInfo?.payload?.address}\nPoiId:${locationInfo?.payload?.poiId || ''}`
180
191
  baseMsg.type = '位置'
181
192
  baseMsg.content = locationParse
@@ -199,16 +210,15 @@ async function onClawMessage(msg) {
199
210
  }
200
211
  }
201
212
 
202
- async function formatHistory(that, history, clawConfig) {
213
+ async function formatHistory(that, historyList, clawConfig) {
203
214
  const contents = []
204
- for (const history of historys) {
215
+ for (const history of historyList) {
205
216
  if (history.type === that.Message.Type.Text) {
206
217
  contents.push(history.message)
207
218
  } else if (history.type === that.Message.Type.Recalled || history.type === that.Message.Type.Attachment || history.type === that.Message.Type.Image || history.type === that.Message.Type.Video) {
208
219
  const attachFileBox = await history.message.toFileBox();
209
220
  const fileExtname = path.extname(attachFileBox.name);
210
221
  const isImage = fileExtname.includes('.png') || fileExtname.includes('.jpg') || fileExtname.includes('.jpeg') || fileExtname.includes('.gif')
211
- baseMsg.type = isImage ? '图片' : '文件'
212
222
  if (attachFileBox.remoteUrl) {
213
223
  // 直接使用远程链接,无需上传
214
224
  const url = attachFileBox.remoteUrl
@@ -217,7 +227,7 @@ async function formatHistory(that, history, clawConfig) {
217
227
  }
218
228
  const buffer = await attachFileBox.toBuffer()
219
229
  if (!clawConfig?.ossConfig.type) {
220
- const base64 = attachFileBox.toBase64()
230
+ const base64 = await attachFileBox.toBase64()
221
231
  contents.push(`【${isImage ? '图片' : '文件'}消息】\n文件名:${attachFileBox.name}\n文件大小:${buffer.length} bytes\n(文件内容未上传,仅提供base64字符串)`)
222
232
  continue
223
233
  }
@@ -245,21 +255,22 @@ function publishClawMessage(baseMsg, robotInfo) {
245
255
  isGroup: baseMsg.isRoom,
246
256
  groupId: baseMsg.isRoom ? baseMsg.conversionId : undefined,
247
257
  groupName: baseMsg.isRoom ? baseMsg.conversionName : undefined,
248
- timestamp: baseMsg.time * 1000, // 转换为 11 位毫秒时间戳
258
+ isGroupMention: baseMsg.isMention,
259
+ timestamp: baseMsg.time * 1000, // 转换为 13 位毫秒时间戳
249
260
  messageId: baseMsg.msgId,
250
261
  type: baseMsg.type,
251
262
  mediaInfo: baseMsg.mediaInfo,
252
263
  url: baseMsg.url,
253
264
  }
254
- console.log('发布 Claw MQTT 消息', payload)
265
+ console.log('发布 OpenClaw(小龙虾) 消息', payload)
255
266
  clawMqttClient.publish(clawMqttClient._clawReciveTopic, JSON.stringify(payload), { qos: 1 })
256
267
  } catch (e) {
257
- console.log('发布 Claw MQTT 消息失败', e)
268
+ console.log('发布 OpenClaw(小龙虾) 消息失败', e)
258
269
  }
259
270
  }
260
271
 
261
272
  /**
262
- * 初始化 Claw MQTT 连接
273
+ * 初始化 OpenClaw(小龙虾) 连接
263
274
  * 1. 连接 MQTT,将入站消息发布到 reciveTopic
264
275
  * 2. 订阅 sendTopic,收到消息后通过微信发送
265
276
  */
@@ -277,7 +288,7 @@ async function initClawMqtt(that) {
277
288
  const { port, host, username, password, clientId, reciveTopic, sendTopic } = mqttConfig
278
289
  if (!host) return
279
290
 
280
- clawMqttClient = mqtt.connect(`mqtt://${host}:${port}`, {
291
+ clawMqttClient = mqtt.connect(`${host}:${port}`, {
281
292
  username,
282
293
  password,
283
294
  clientId: clientId + randomRange(1, 10000),
@@ -286,22 +297,22 @@ async function initClawMqtt(that) {
286
297
  clawMqttClient._clawReciveTopic = reciveTopic
287
298
 
288
299
  clawMqttClient.on('connect', function () {
289
- console.log('Claw MQTT 连接成功')
300
+ console.log('OpenClaw(小龙虾) 连接成功')
290
301
  clawMqttClient.subscribe(sendTopic, function (err) {
291
302
  if (err) {
292
- console.log('Claw MQTT 订阅失败', err)
303
+ console.log('OpenClaw(小龙虾) 订阅失败', err)
293
304
  } else {
294
- console.log(`Claw MQTT 订阅成功: ${sendTopic}`)
305
+ console.log(`OpenClaw(小龙虾) 订阅成功`)
295
306
  }
296
307
  })
297
308
  })
298
309
 
299
310
  clawMqttClient.on('reconnect', function () {
300
- console.log('Claw MQTT 重连中...')
311
+ console.log('OpenClaw(小龙虾) 重连中...')
301
312
  })
302
313
 
303
314
  clawMqttClient.on('error', function (e) {
304
- console.log('Claw MQTT 错误', e)
315
+ console.log('OpenClaw(小龙虾) 连接错误', e)
305
316
  })
306
317
 
307
318
  clawMqttClient.on('message', async function (topic, message) {
@@ -326,7 +337,7 @@ async function initClawMqtt(that) {
326
337
  if (isGroup && groupId) {
327
338
  const room = await that.Room.find({ id: groupId })
328
339
  if (!room) {
329
- console.log(`Claw MQTT: 查找不到群 ${groupId}`)
340
+ console.log(`OpenClaw(小龙虾): 查找不到群 ${groupId}`)
330
341
  return
331
342
  }
332
343
  // 解析 @ 联系人
@@ -353,7 +364,7 @@ async function initClawMqtt(that) {
353
364
  } else if (!isGroup && contactId) {
354
365
  const contact = await that.Contact.find({ id: contactId })
355
366
  if (!contact) {
356
- console.log(`Claw MQTT: 查找不到联系人 ${contactId}`)
367
+ console.log(`OpenClaw(小龙虾): 查找不到联系人 ${contactId}`)
357
368
  return
358
369
  }
359
370
  for (const msg of messages) {
@@ -367,11 +378,11 @@ async function initClawMqtt(that) {
367
378
  }
368
379
  }
369
380
  } catch (e) {
370
- console.log('Claw MQTT 处理消息失败', e)
381
+ console.log('OpenClaw(小龙虾) 处理消息失败', e)
371
382
  }
372
383
  })
373
384
  } catch (e) {
374
- console.log('Claw MQTT 初始化失败', e)
385
+ console.log('OpenClaw(小龙虾) 初始化失败', e)
375
386
  }
376
387
  }
377
388
 
@@ -137,7 +137,7 @@ async function dispatchFriendFilterByMsgType(that, msg) {
137
137
  const text = puppetInfo.puppetType.includes('PuppetService') && !msg.text().startsWith('@') ? msg.text().trim() : await getVoiceText(audioFileBox, finalConfig.botConfig.whisperConfig)
138
138
  console.log('语音解析结果:', text)
139
139
  const keyword = finalConfig.botConfig.whisperConfig?.keywords?.length ? finalConfig.botConfig?.whisperConfig.keywords?.find((item) => text.includes(item)) : true
140
- const isIgnore = checkIgnore(content.trim(), aibotConfig.ignoreMessages)
140
+ const isIgnore = checkIgnore(text.trim(), aibotConfig.ignoreMessages)
141
141
  if (text.trim() && !isIgnore && keyword) {
142
142
  const gpt4vReplys = await getGpt4vChat({
143
143
  that,
@@ -333,7 +333,7 @@ async function dispatchRoomFilterByMsgType(that, room, msg) {
333
333
  const type = msg.type()
334
334
  const receiver = msg.to()
335
335
  let content = ''
336
- let replys = ''
336
+ let replys = []
337
337
  let contactId = contact.id
338
338
  let contactAvatar = await contact.avatar()
339
339
  const userSelfName = that.currentUser?.name() || that.userSelf()?.name()
@@ -532,7 +532,7 @@ async function dispatchRoomFilterByMsgType(that, room, msg) {
532
532
  const text = puppetInfo.puppetType.includes('PuppetService') && !msg.text().startsWith('@') ? msg.text().trim() : await getVoiceText(audioFileBox, finalConfig.botConfig.whisperConfig)
533
533
  console.log('语音解析结果', text)
534
534
  const keyword = finalConfig.botConfig.whisperConfig?.keywords?.length ? finalConfig.botConfig?.whisperConfig?.keywords?.find((item) => text.includes(item)) : true
535
- const isIgnore = checkIgnore(content.trim(), aibotConfig.ignoreMessages)
535
+ const isIgnore = checkIgnore(text.trim(), aibotConfig.ignoreMessages)
536
536
  if (text.trim() && !isIgnore && keyword) {
537
537
  const gpt4vReplys = await getGpt4vChat({
538
538
  that,
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const packageJson = {
5
5
  "name": "wechaty-web-panel",
6
- "version": "1.6.120",
6
+ "version": "1.6.122",
7
7
  "exports": {
8
8
  ".": {
9
9
  "import": "./dist/index.js"
@@ -239,9 +239,9 @@ async function getConfig() {
239
239
  uploadFileConfig: null,
240
240
  ignoreFiles: false,
241
241
  filterLinkContent: false,
242
+ clawConfig: { open: false, allowScope: 1, allowFriends: [], allowRooms: [], forwardAllMsg: true, forwardIncludeKeywordsMsg: [], forwardMediaMsg: false, ossConfig: {} },
242
243
  ...config,
243
244
  cloudRoom,
244
- clawConfig: { open: false, allowScope: 1, allowFriends: [], allowRooms: [], forwardAllMsg: true, forwardIncludeKeywordsMsg: [], forwardMediaMsg: false, ossConfig: {} }
245
245
  })
246
246
  return cres
247
247
  } catch (e) {