@dcrays/dcgchat-test 0.2.11 → 0.2.13

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/bot.ts +169 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcrays/dcgchat-test",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
package/src/bot.ts CHANGED
@@ -152,6 +152,116 @@ function createFileExtractor() {
152
152
  return { getNewFiles }
153
153
  }
154
154
 
155
+ /**
156
+ * 从文本中提取 /mobook 目录下的文件
157
+ * @param {string} text
158
+ * @returns {string[]}
159
+ */
160
+ const EXT_LIST = [
161
+ // 文档类
162
+ 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf', 'txt', 'rtf', 'odt',
163
+
164
+ // 数据/开发
165
+ 'json', 'xml', 'csv', 'yaml', 'yml',
166
+
167
+ // 前端/文本
168
+ 'html', 'htm', 'md', 'markdown', 'css', 'js', 'ts',
169
+
170
+ // 图片
171
+ 'png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg', 'ico', 'tiff',
172
+
173
+ // 音频
174
+ 'mp3', 'wav', 'ogg', 'aac', 'flac', 'm4a',
175
+
176
+ // 视频
177
+ 'mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm',
178
+
179
+ // 压缩包
180
+ 'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz',
181
+
182
+ // 可执行/程序
183
+ 'exe', 'dmg', 'pkg', 'apk', 'ipa',
184
+
185
+ // 其他常见
186
+ 'log', 'dat', 'bin'
187
+ ];
188
+
189
+ function extractMobookFiles(text = '') {
190
+ if (typeof text !== 'string' || !text.trim()) return [];
191
+
192
+ const result = new Set();
193
+
194
+ // ✅ 支持的文件类型(可扩展)
195
+ const EXT = `(${EXT_LIST.join('|')})`
196
+
197
+ try {
198
+ // 1️⃣ 提取 `xxx.xxx`(反引号包裹)
199
+ const backtickMatches = text.match(
200
+ new RegExp(`\`([^\\\`]+?\\.${EXT})\``, 'gi')
201
+ ) || [];
202
+
203
+ backtickMatches.forEach(item => {
204
+ const name = item.replace(/`/g, '').trim();
205
+ if (isValidFileName(name)) {
206
+ result.add(`/mobook/${name}`);
207
+ }
208
+ });
209
+
210
+ // 2️⃣ 提取 "/mobook/xxx.xxx"(完整路径)
211
+ const fullPathMatches = text.match(
212
+ new RegExp(`/mobook/[\\w\\u4e00-\\u9fa5.-]+?\\.${EXT}`, 'gi')
213
+ ) || [];
214
+
215
+ fullPathMatches.forEach(p => {
216
+ result.add(normalizePath(p));
217
+ });
218
+
219
+ // 3️⃣ 提取 “mobook下的 xxx.xxx”
220
+ const mobookInlineMatches = text.match(
221
+ new RegExp(`mobook下的\\s*([\\w\\u4e00-\\u9fa5.-]+?\\.${EXT})`, 'gi')
222
+ ) || [];
223
+
224
+ mobookInlineMatches.forEach(item => {
225
+ const match = item.match(
226
+ new RegExp(`([\\w\\u4e00-\\u9fa5.-]+?\\.${EXT})`, 'i')
227
+ );
228
+ if (match && isValidFileName(match[1])) {
229
+ result.add(`/mobook/${match[1]}`);
230
+ }
231
+ });
232
+
233
+ } catch (e) {
234
+ // 容错:解析异常不影响主流程
235
+ console.warn('extractMobookFiles error:', e);
236
+ }
237
+
238
+ return [...result];
239
+ }
240
+
241
+ /**
242
+ * 校验文件名是否合法(避免脏数据)
243
+ */
244
+ function isValidFileName(name: string) {
245
+ if (!name) return false;
246
+
247
+ // 过滤异常字符
248
+ if (/[\/\\<>:"|?*]/.test(name)) return false;
249
+
250
+ // 长度限制(防止异常长字符串)
251
+ if (name.length > 200) return false;
252
+
253
+ return true;
254
+ }
255
+
256
+ /**
257
+ * 规范路径(去重用)
258
+ */
259
+ function normalizePath(path: string) {
260
+ return path
261
+ .replace(/\/+/g, '/') // 多斜杠 → 单斜杠
262
+ .replace(/\/$/, ''); // 去掉结尾 /
263
+ }
264
+
155
265
  /**
156
266
  * 处理一条用户消息,调用 Agent 并返回回复
157
267
  */
@@ -247,6 +357,8 @@ export async function handleDcgchatMessage(params: {
247
357
 
248
358
  log(`dcgchat[${accountId}]: ctxPayload=${JSON.stringify(ctxPayload)}`);
249
359
 
360
+ const sentMediaKeys = new Set<string>()
361
+ const getMediaKey = (url: string) => url.split(/[\\/]/).slice(-2).join('/')
250
362
  let textChunk = ''
251
363
 
252
364
  const prefixContext = createReplyPrefixContext({ cfg, agentId: route.agentId });
@@ -294,11 +406,15 @@ export async function handleDcgchatMessage(params: {
294
406
  const mediaList = resolveReplyMediaList(payload);
295
407
  if (mediaList.length > 0) {
296
408
  for (let i = 0; i < mediaList.length; i++) {
409
+ const mediaUrl = mediaList[i];
410
+ const key = getMediaKey(mediaUrl);
411
+ if (sentMediaKeys.has(key)) continue;
412
+ sentMediaKeys.add(key);
297
413
  await sendDcgchatMedia({
298
414
  cfg,
299
415
  accountId,
300
416
  log,
301
- mediaUrl: mediaList[i],
417
+ mediaUrl,
302
418
  text: "",
303
419
  });
304
420
  }
@@ -334,23 +450,7 @@ export async function handleDcgchatMessage(params: {
334
450
  },
335
451
  });
336
452
  }
337
- log(`dcgchat[${accountId}]: dispatch complete, sending final state`);
338
- params.onChunk({
339
- messageType: "openclaw_bot_chat",
340
- _userId: msg._userId,
341
- source: "client",
342
- content: {
343
- bot_token: msg.content.bot_token,
344
- domain_id: msg.content.domain_id,
345
- app_id: msg.content.app_id,
346
- bot_id: msg.content.bot_id,
347
- agent_id: msg.content.agent_id,
348
- session_id: msg.content.session_id,
349
- message_id: msg.content.message_id,
350
- response: '',
351
- state: 'final',
352
- },
353
- });
453
+
354
454
  const extractor = createFileExtractor()
355
455
  const completeFiles = extractor.getNewFiles(completeText)
356
456
  if (completeFiles.length > 0) {
@@ -359,10 +459,16 @@ export async function handleDcgchatMessage(params: {
359
459
  if (!path.isAbsolute(url)) {
360
460
  url = path.join(getWorkspaceDir(), url)
361
461
  }
462
+ const key = getMediaKey(url);
463
+ if (sentMediaKeys.has(key)) {
464
+ log(`dcgchat[${accountId}]: completeFiles already sent, skipping: ${url}`);
465
+ continue;
466
+ }
362
467
  if (!fs.existsSync(url)) {
363
- log(`dcgchat[${accountId}]: completeFiles file not found, skipping: ${url}`);
468
+ log(`dcgchat[${accountId}]: completeFiles file not found, skipping: ${url}`);
364
469
  continue;
365
470
  }
471
+ sentMediaKeys.add(key);
366
472
  await sendDcgchatMedia({
367
473
  cfg,
368
474
  accountId,
@@ -373,6 +479,50 @@ export async function handleDcgchatMessage(params: {
373
479
  }
374
480
  log(`dcgchat[${accountId}][deliver]: sent ${completeFiles.length} media file(s) through channel adapter`);
375
481
  }
482
+ const mobookFiles = extractMobookFiles(completeText)
483
+ if (mobookFiles.length > 0) {
484
+ for (let i = 0; i < mobookFiles.length; i++) {
485
+ let url = mobookFiles[i] as string
486
+ const key = getMediaKey(url);
487
+ if (sentMediaKeys.has(key)) {
488
+ log(`dcgchat[${accountId}]: mobookFiles already sent, skipping: ${url}`);
489
+ continue;
490
+ }
491
+ if (!fs.existsSync(url)) {
492
+ url = path.join(getWorkspaceDir(), url)
493
+ if (!fs.existsSync(url)) {
494
+ log(`dcgchat[${accountId}]: mobookFiles file not found, skipping: ${url}`);
495
+ continue;
496
+ }
497
+ }
498
+ sentMediaKeys.add(key);
499
+ await sendDcgchatMedia({
500
+ cfg,
501
+ accountId,
502
+ log,
503
+ mediaUrl: url,
504
+ text: "",
505
+ });
506
+ }
507
+ log(`dcgchat[${accountId}][deliver]: sent ${mobookFiles.length} media file(s) through channel adapter`);
508
+ }
509
+ log(`dcgchat[${accountId}]: dispatch complete, sending final state`);
510
+ params.onChunk({
511
+ messageType: "openclaw_bot_chat",
512
+ _userId: msg._userId,
513
+ source: "client",
514
+ content: {
515
+ bot_token: msg.content.bot_token,
516
+ domain_id: msg.content.domain_id,
517
+ app_id: msg.content.app_id,
518
+ bot_id: msg.content.bot_id,
519
+ agent_id: msg.content.agent_id,
520
+ session_id: msg.content.session_id,
521
+ message_id: msg.content.message_id,
522
+ response: '',
523
+ state: 'final',
524
+ },
525
+ });
376
526
 
377
527
  setMsgStatus('finished');
378
528
  textChunk = ''