@tencent-connect/openclaw-qqbot 1.5.7 → 1.6.0-alpha.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 (63) hide show
  1. package/README.md +9 -2
  2. package/README.zh.md +7 -2
  3. package/package.json +1 -1
  4. package/scripts/upgrade-via-npm.sh +85 -115
  5. package/scripts/upgrade-via-source.sh +203 -35
  6. package/skills/qqbot-cron/SKILL.md +46 -423
  7. package/skills/qqbot-media/SKILL.md +29 -182
  8. package/src/api.ts +16 -5
  9. package/src/channel.ts +6 -7
  10. package/src/gateway.ts +510 -525
  11. package/src/image-server.ts +72 -10
  12. package/src/openclaw-plugin-sdk.d.ts +1 -1
  13. package/src/outbound.ts +571 -611
  14. package/src/ref-index-store.ts +1 -1
  15. package/src/slash-commands.ts +425 -0
  16. package/src/types.ts +18 -1
  17. package/src/update-checker.ts +102 -0
  18. package/src/user-messages.ts +73 -0
  19. package/src/utils/audio-convert.ts +69 -4
  20. package/src/utils/media-tags.ts +46 -4
  21. package/dist/AI/345/210/233/346/226/260/345/272/224/347/224/250/345/245/226_/347/224/263/346/212/245/344/271/246.md +0 -211
  22. package/dist/index.d.ts +0 -17
  23. package/dist/index.js +0 -22
  24. package/dist/src/api.d.ts +0 -138
  25. package/dist/src/api.js +0 -525
  26. package/dist/src/channel.d.ts +0 -3
  27. package/dist/src/channel.js +0 -337
  28. package/dist/src/config.d.ts +0 -25
  29. package/dist/src/config.js +0 -161
  30. package/dist/src/gateway.d.ts +0 -18
  31. package/dist/src/gateway.js +0 -2468
  32. package/dist/src/image-server.d.ts +0 -62
  33. package/dist/src/image-server.js +0 -401
  34. package/dist/src/known-users.d.ts +0 -100
  35. package/dist/src/known-users.js +0 -263
  36. package/dist/src/onboarding.d.ts +0 -10
  37. package/dist/src/onboarding.js +0 -203
  38. package/dist/src/outbound.d.ts +0 -150
  39. package/dist/src/outbound.js +0 -1175
  40. package/dist/src/proactive.d.ts +0 -170
  41. package/dist/src/proactive.js +0 -399
  42. package/dist/src/runtime.d.ts +0 -3
  43. package/dist/src/runtime.js +0 -10
  44. package/dist/src/session-store.d.ts +0 -52
  45. package/dist/src/session-store.js +0 -254
  46. package/dist/src/slash-commands.d.ts +0 -48
  47. package/dist/src/slash-commands.js +0 -212
  48. package/dist/src/types.d.ts +0 -146
  49. package/dist/src/types.js +0 -1
  50. package/dist/src/utils/audio-convert.d.ts +0 -73
  51. package/dist/src/utils/audio-convert.js +0 -645
  52. package/dist/src/utils/file-utils.d.ts +0 -46
  53. package/dist/src/utils/file-utils.js +0 -107
  54. package/dist/src/utils/image-size.d.ts +0 -51
  55. package/dist/src/utils/image-size.js +0 -234
  56. package/dist/src/utils/media-tags.d.ts +0 -14
  57. package/dist/src/utils/media-tags.js +0 -120
  58. package/dist/src/utils/payload.d.ts +0 -112
  59. package/dist/src/utils/payload.js +0 -186
  60. package/dist/src/utils/platform.d.ts +0 -126
  61. package/dist/src/utils/platform.js +0 -358
  62. package/dist/src/utils/upload-cache.d.ts +0 -34
  63. package/dist/src/utils/upload-cache.js +0 -93
@@ -418,54 +418,116 @@ export async function ensureImageServer(publicBaseUrl?: string): Promise<string
418
418
  * @param url 远程文件 URL
419
419
  * @param destDir 目标目录
420
420
  * @param originalFilename 原始文件名(可选,完整文件名包含扩展名)
421
+ * @param options 下载选项
421
422
  * @returns 本地文件路径,失败返回 null
422
423
  */
423
424
  export async function downloadFile(
424
425
  url: string,
425
426
  destDir: string,
426
- originalFilename?: string
427
+ originalFilename?: string,
428
+ options?: {
429
+ /** 超时时间(毫秒),默认 120000(2分钟) */
430
+ timeoutMs?: number;
431
+ /** 最大文件大小(字节),默认 50MB */
432
+ maxSizeBytes?: number;
433
+ },
427
434
  ): Promise<string | null> {
435
+ const timeoutMs = options?.timeoutMs ?? 120000;
436
+ const maxSizeBytes = options?.maxSizeBytes ?? 50 * 1024 * 1024;
437
+
438
+ const controller = new AbortController();
439
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
440
+
428
441
  try {
429
442
  // 确保目录存在
430
443
  if (!fs.existsSync(destDir)) {
431
444
  fs.mkdirSync(destDir, { recursive: true });
432
445
  }
433
446
 
434
- // 下载文件
435
- const response = await fetch(url);
447
+ // 下载文件(带超时控制)
448
+ const response = await fetch(url, { signal: controller.signal });
436
449
  if (!response.ok) {
437
450
  console.error(`[image-server] Download failed: ${response.status} ${response.statusText}`);
438
451
  return null;
439
452
  }
440
453
 
441
- const buffer = Buffer.from(await response.arrayBuffer());
454
+ // Content-Length 预检
455
+ const contentLength = response.headers.get("content-length");
456
+ if (contentLength) {
457
+ const declaredSize = parseInt(contentLength, 10);
458
+ if (declaredSize > maxSizeBytes) {
459
+ console.error(`[image-server] Download rejected: Content-Length ${declaredSize} exceeds limit ${maxSizeBytes}`);
460
+ return null;
461
+ }
462
+ }
463
+
464
+ // 流式下载,实时监控大小
465
+ const reader = response.body?.getReader();
466
+ if (!reader) {
467
+ console.error(`[image-server] Download failed: no response body`);
468
+ return null;
469
+ }
470
+
471
+ const chunks: Uint8Array[] = [];
472
+ let totalSize = 0;
473
+
474
+ while (true) {
475
+ const { done, value } = await reader.read();
476
+ if (done) break;
477
+ totalSize += value.byteLength;
478
+ if (totalSize > maxSizeBytes) {
479
+ reader.cancel();
480
+ console.error(`[image-server] Download aborted: size ${totalSize} exceeds limit ${maxSizeBytes}`);
481
+ return null;
482
+ }
483
+ chunks.push(value);
484
+ }
485
+
486
+ const buffer = Buffer.concat(chunks);
487
+
488
+ // 从 Content-Disposition 解析文件名(如果没有提供 originalFilename)
489
+ if (!originalFilename) {
490
+ const disposition = response.headers.get("content-disposition");
491
+ if (disposition) {
492
+ const filenameMatch = disposition.match(/filename\*?=(?:UTF-8''|")?([^";]+)"?/i);
493
+ if (filenameMatch?.[1]) {
494
+ try { originalFilename = decodeURIComponent(filenameMatch[1]); } catch { /* keep undefined */ }
495
+ }
496
+ }
497
+ }
442
498
 
443
499
  // 确定文件名
444
500
  let finalFilename: string;
445
501
  if (originalFilename) {
446
- // QQ 平台返回的 filename 可能是 URL 编码的(如 %E7%AC%94%E5%A2%A8...),先解码
447
502
  let decodedFilename = originalFilename;
448
503
  try { decodedFilename = decodeURIComponent(originalFilename); } catch { /* keep original */ }
449
- // 使用原始文件名,但添加时间戳避免冲突
450
504
  const ext = path.extname(decodedFilename);
451
505
  const baseName = path.basename(decodedFilename, ext);
452
506
  const timestamp = Date.now();
453
507
  finalFilename = `${baseName}_${timestamp}${ext}`;
454
508
  } else {
455
- // 没有原始文件名,生成随机名
456
- finalFilename = `${generateImageId()}.bin`;
509
+ // 没有原始文件名,尝试从 Content-Type 推导扩展名
510
+ const contentType = response.headers.get("content-type");
511
+ const ext = contentType ? (getExtFromMime(contentType.split(";")[0]?.trim() ?? "") ?? "bin") : "bin";
512
+ finalFilename = `${generateImageId()}.${ext}`;
457
513
  }
458
514
 
459
515
  const filePath = path.join(destDir, finalFilename);
460
516
 
461
517
  // 保存文件
462
518
  fs.writeFileSync(filePath, buffer);
463
- console.log(`[image-server] Downloaded file: ${filePath}`);
519
+ console.log(`[image-server] Downloaded file: ${filePath} (${buffer.length} bytes, ${Date.now()}ms)`);
464
520
 
465
521
  return filePath;
466
522
  } catch (err) {
467
- console.error(`[image-server] Download error:`, err);
523
+ if (err instanceof Error && err.name === "AbortError") {
524
+ console.error(`[image-server] Download timeout after ${timeoutMs}ms: ${url}`);
525
+ } else {
526
+ console.error(`[image-server] Download error:`, err);
527
+ }
468
528
  return null;
529
+ } finally {
530
+ clearTimeout(timeoutId);
469
531
  }
470
532
  }
471
533
 
@@ -215,7 +215,7 @@ declare module "openclaw/plugin-sdk" {
215
215
  * 频道插件 Messaging 接口
216
216
  */
217
217
  export interface ChannelPluginMessaging {
218
- normalizeTarget?: (target: string) => NormalizeTargetResult;
218
+ normalizeTarget?: (target: string) => string | undefined;
219
219
  targetResolver?: TargetResolver;
220
220
  [key: string]: unknown;
221
221
  }