@tencent-connect/openclaw-qqbot 1.6.5-alpha.2 → 1.6.5-alpha.3

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/dist/src/api.js CHANGED
@@ -2,7 +2,6 @@
2
2
  * QQ Bot API 鉴权和请求封装
3
3
  * [修复版] 已重构为支持多实例并发,消除全局变量冲突
4
4
  */
5
- import { createRequire } from "node:module";
6
5
  import os from "node:os";
7
6
  import { computeFileHash, getCachedFileInfo, setCachedFileInfo } from "./utils/upload-cache.js";
8
7
  import { sanitizeFileName } from "./utils/platform.js";
@@ -11,12 +10,8 @@ const TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
11
10
  // ============ Plugin User-Agent ============
12
11
  // 格式: QQBotPlugin/{version} (Node/{nodeVersion}; {os})
13
12
  // 示例: QQBotPlugin/1.6.0 (Node/22.14.0; darwin)
14
- const _require = createRequire(import.meta.url);
15
- let _pluginVersion = "unknown";
16
- try {
17
- _pluginVersion = _require("../package.json").version ?? "unknown";
18
- }
19
- catch { /* fallback */ }
13
+ import { getPackageVersion } from "./utils/pkg-version.js";
14
+ const _pluginVersion = getPackageVersion(import.meta.url);
20
15
  export const PLUGIN_USER_AGENT = `QQBotPlugin/${_pluginVersion} (Node/${process.versions.node}; ${os.platform()})`;
21
16
  // 运行时配置
22
17
  let currentMarkdownSupport = false;
@@ -234,21 +229,48 @@ export async function apiRequest(accessToken, method, path, body, timeoutMs) {
234
229
  });
235
230
  const traceId = res.headers.get("x-tps-trace-id") ?? "";
236
231
  console.log(`[qqbot-api] <<< Status: ${res.status} ${res.statusText}${traceId ? ` | TraceId: ${traceId}` : ""}`);
237
- let data;
238
232
  let rawBody;
239
233
  try {
240
234
  rawBody = await res.text();
241
- console.log(`[qqbot-api] <<< Body:`, rawBody);
242
- data = JSON.parse(rawBody);
243
235
  }
244
236
  catch (err) {
245
- throw new Error(`Failed to parse response[${path}]: ${err instanceof Error ? err.message : String(err)}`);
237
+ throw new Error(`读取响应失败[${path}]: ${err instanceof Error ? err.message : String(err)}`);
246
238
  }
239
+ console.log(`[qqbot-api] <<< Body:`, rawBody);
240
+ // 检测非 JSON 响应(HTML 网关错误页 / CDN 限流页等)
241
+ const contentType = res.headers.get("content-type") ?? "";
242
+ const isHtmlResponse = contentType.includes("text/html") || rawBody.trimStart().startsWith("<");
247
243
  if (!res.ok) {
248
- const error = data;
249
- throw new Error(`API Error [${path}]: ${error.message ?? JSON.stringify(data)}`);
244
+ if (isHtmlResponse) {
245
+ // HTML 响应 = 网关/限流层返回的错误页,给出友好提示
246
+ const statusHint = res.status === 502 || res.status === 503 || res.status === 504
247
+ ? "调用发生异常,请稍候重试"
248
+ : res.status === 429
249
+ ? "请求过于频繁,已被限流"
250
+ : `开放平台返回 HTTP ${res.status}`;
251
+ throw new Error(`${statusHint}(${path}),请稍后重试`);
252
+ }
253
+ // JSON 错误响应
254
+ try {
255
+ const error = JSON.parse(rawBody);
256
+ throw new Error(`API Error [${path}]: ${error.message ?? rawBody}`);
257
+ }
258
+ catch (parseErr) {
259
+ if (parseErr instanceof Error && parseErr.message.startsWith("API Error"))
260
+ throw parseErr;
261
+ throw new Error(`API Error [${path}] HTTP ${res.status}: ${rawBody.slice(0, 200)}`);
262
+ }
263
+ }
264
+ // 成功响应但不是 JSON(极端异常情况)
265
+ if (isHtmlResponse) {
266
+ throw new Error(`QQ 服务端返回了非 JSON 响应(${path}),可能是临时故障,请稍后重试`);
267
+ }
268
+ try {
269
+ return JSON.parse(rawBody);
270
+ }
271
+ catch {
272
+ throw new Error(`开放平台响应格式异常(${path}),请稍后重试`);
250
273
  }
251
- return data;
252
274
  }
253
275
  // ============ 上传重试(指数退避) ============
254
276
  const UPLOAD_MAX_RETRIES = 2;
@@ -18,16 +18,9 @@ import { getUpdateInfo, checkVersionExists } from "./update-checker.js";
18
18
  import { getHomeDir, getQQBotDataDir, isWindows } from "./utils/platform.js";
19
19
  import { saveCredentialBackup } from "./credential-backup.js";
20
20
  import { fileURLToPath } from "node:url";
21
+ import { getPackageVersion } from "./utils/pkg-version.js";
21
22
  const require = createRequire(import.meta.url);
22
- // 读取 package.json 中的版本号
23
- let PLUGIN_VERSION = "unknown";
24
- try {
25
- const pkg = require("../package.json");
26
- PLUGIN_VERSION = pkg.version ?? "unknown";
27
- }
28
- catch {
29
- // fallback
30
- }
23
+ let PLUGIN_VERSION = getPackageVersion(import.meta.url);
31
24
  // 获取 openclaw 框架版本(缓存结果,只执行一次)
32
25
  let _frameworkVersion = null;
33
26
  function getFrameworkVersion() {
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { createRequire } from "node:module";
11
11
  import https from "node:https";
12
+ import { getPackageVersion } from "./utils/pkg-version.js";
12
13
  const require = createRequire(import.meta.url);
13
14
  const PKG_NAME = "@tencent-connect/openclaw-qqbot";
14
15
  const ENCODED_PKG = encodeURIComponent(PKG_NAME);
@@ -16,14 +17,7 @@ const REGISTRIES = [
16
17
  `https://registry.npmjs.org/${ENCODED_PKG}`,
17
18
  `https://registry.npmmirror.com/${ENCODED_PKG}`,
18
19
  ];
19
- let CURRENT_VERSION = "unknown";
20
- try {
21
- const pkg = require("../package.json");
22
- CURRENT_VERSION = pkg.version ?? "unknown";
23
- }
24
- catch {
25
- // fallback
26
- }
20
+ let CURRENT_VERSION = getPackageVersion(import.meta.url);
27
21
  let _log;
28
22
  function fetchJson(url, timeoutMs) {
29
23
  return new Promise((resolve, reject) => {
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 从 import.meta.url 向上遍历目录树查找 package.json 并读取 version。
3
+ * 不依赖硬编码的 "../" 层级,无论编译输出结构如何变化都能可靠找到。
4
+ */
5
+ export declare function getPackageVersion(metaUrl?: string): string;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * 从 import.meta.url 向上遍历目录树查找 package.json 并读取 version。
3
+ * 不依赖硬编码的 "../" 层级,无论编译输出结构如何变化都能可靠找到。
4
+ */
5
+ import { fileURLToPath } from "node:url";
6
+ import { createRequire } from "node:module";
7
+ import path from "node:path";
8
+ import fs from "node:fs";
9
+ let _cached = null;
10
+ export function getPackageVersion(metaUrl) {
11
+ if (_cached !== null)
12
+ return _cached;
13
+ // Strategy 1: 从调用者的 import.meta.url(或本模块)向上遍历找 package.json
14
+ const startFile = metaUrl ? fileURLToPath(metaUrl) : fileURLToPath(import.meta.url);
15
+ let dir = path.dirname(startFile);
16
+ const root = path.parse(dir).root;
17
+ while (dir !== root) {
18
+ const candidate = path.join(dir, "package.json");
19
+ try {
20
+ if (fs.existsSync(candidate)) {
21
+ const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
22
+ // 确认是我们自己的包(避免找到其他 package.json)
23
+ if (pkg.name === "@tencent-connect/openclaw-qqbot" && pkg.version) {
24
+ _cached = pkg.version;
25
+ return _cached;
26
+ }
27
+ }
28
+ }
29
+ catch {
30
+ // ignore and try parent
31
+ }
32
+ dir = path.dirname(dir);
33
+ }
34
+ // Strategy 2: fallback 用 createRequire 尝试常见相对路径
35
+ try {
36
+ const require = createRequire(metaUrl ?? import.meta.url);
37
+ for (const rel of ["../../package.json", "../package.json", "./package.json"]) {
38
+ try {
39
+ const pkg = require(rel);
40
+ if (pkg?.version) {
41
+ _cached = pkg.version;
42
+ return _cached;
43
+ }
44
+ }
45
+ catch { /* next */ }
46
+ }
47
+ }
48
+ catch { /* fallback */ }
49
+ _cached = "unknown";
50
+ return _cached;
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencent-connect/openclaw-qqbot",
3
- "version": "1.6.5-alpha.2",
3
+ "version": "1.6.5-alpha.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -242,7 +242,7 @@ if ($MissingModules.Count -gt 0) {
242
242
  $nmDir = Join-Path $STAGING_DIR "node_modules"
243
243
  if (Test-Path $nmDir) {
244
244
  $BundledOK = $true
245
- foreach ($dep in @("ws", "undici")) {
245
+ foreach ($dep in @("ws", "silk-wasm")) {
246
246
  if (-not (Test-Path (Join-Path $nmDir $dep))) {
247
247
  Write-Host " [WARN] Bundled dependency missing: $dep" -ForegroundColor Yellow
248
248
  $BundledOK = $false
@@ -273,6 +273,7 @@ Write-Host " [OK] All preflight checks passed"
273
273
  # [3/5] Replace plugin directory (in-place overwrite to avoid file-lock issues)
274
274
  Write-Host ""
275
275
  Write-Host "[3/5] Replacing plugin directory..."
276
+ if (-not (Test-Path $EXTENSIONS_DIR)) { New-Item -ItemType Directory -Path $EXTENSIONS_DIR -Force | Out-Null }
276
277
  $TARGET_DIR = Join-Path $EXTENSIONS_DIR "openclaw-qqbot"
277
278
 
278
279
  if (-not (Test-Path $TARGET_DIR)) {
@@ -236,7 +236,7 @@ fi
236
236
  # (e) bundled node_modules 健康检查
237
237
  if [ -d "$STAGING_DIR/node_modules" ]; then
238
238
  BUNDLED_OK=true
239
- for dep in "ws" "undici"; do
239
+ for dep in "ws" "silk-wasm"; do
240
240
  if [ ! -d "$STAGING_DIR/node_modules/$dep" ]; then
241
241
  echo " ⚠️ bundled 依赖缺失: $dep"
242
242
  BUNDLED_OK=false
@@ -268,6 +268,7 @@ echo " ✅ Preflight 检查全部通过"
268
268
  # 策略:先把 staging 放到 extensions/ 同级的临时名,再做单次 mv 替换
269
269
  echo ""
270
270
  echo "[3/5] 原子替换插件目录..."
271
+ mkdir -p "$EXTENSIONS_DIR"
271
272
  TARGET_DIR="$EXTENSIONS_DIR/openclaw-qqbot"
272
273
  OLD_DIR="$(dirname "$EXTENSIONS_DIR")/.qqbot-upgrade-old"
273
274
 
package/src/api.ts CHANGED
@@ -3,7 +3,6 @@
3
3
  * [修复版] 已重构为支持多实例并发,消除全局变量冲突
4
4
  */
5
5
 
6
- import { createRequire } from "node:module";
7
6
  import os from "node:os";
8
7
  import { computeFileHash, getCachedFileInfo, setCachedFileInfo } from "./utils/upload-cache.js";
9
8
  import { sanitizeFileName } from "./utils/platform.js";
@@ -14,9 +13,8 @@ const TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
14
13
  // ============ Plugin User-Agent ============
15
14
  // 格式: QQBotPlugin/{version} (Node/{nodeVersion}; {os})
16
15
  // 示例: QQBotPlugin/1.6.0 (Node/22.14.0; darwin)
17
- const _require = createRequire(import.meta.url);
18
- let _pluginVersion = "unknown";
19
- try { _pluginVersion = _require("../package.json").version ?? "unknown"; } catch { /* fallback */ }
16
+ import { getPackageVersion } from "./utils/pkg-version.js";
17
+ const _pluginVersion = getPackageVersion(import.meta.url);
20
18
  export const PLUGIN_USER_AGENT = `QQBotPlugin/${_pluginVersion} (Node/${process.versions.node}; ${os.platform()})`;
21
19
 
22
20
  // 运行时配置
@@ -285,22 +283,48 @@ export async function apiRequest<T = unknown>(
285
283
  const traceId = res.headers.get("x-tps-trace-id") ?? "";
286
284
  console.log(`[qqbot-api] <<< Status: ${res.status} ${res.statusText}${traceId ? ` | TraceId: ${traceId}` : ""}`);
287
285
 
288
- let data: T;
289
286
  let rawBody: string;
290
287
  try {
291
288
  rawBody = await res.text();
292
- console.log(`[qqbot-api] <<< Body:`, rawBody);
293
- data = JSON.parse(rawBody) as T;
294
289
  } catch (err) {
295
- throw new Error(`Failed to parse response[${path}]: ${err instanceof Error ? err.message : String(err)}`);
290
+ throw new Error(`读取响应失败[${path}]: ${err instanceof Error ? err.message : String(err)}`);
296
291
  }
292
+ console.log(`[qqbot-api] <<< Body:`, rawBody);
293
+
294
+ // 检测非 JSON 响应(HTML 网关错误页 / CDN 限流页等)
295
+ const contentType = res.headers.get("content-type") ?? "";
296
+ const isHtmlResponse = contentType.includes("text/html") || rawBody.trimStart().startsWith("<");
297
297
 
298
298
  if (!res.ok) {
299
- const error = data as { message?: string; code?: number };
300
- throw new Error(`API Error [${path}]: ${error.message ?? JSON.stringify(data)}`);
299
+ if (isHtmlResponse) {
300
+ // HTML 响应 = 网关/限流层返回的错误页,给出友好提示
301
+ const statusHint = res.status === 502 || res.status === 503 || res.status === 504
302
+ ? "调用发生异常,请稍候重试"
303
+ : res.status === 429
304
+ ? "请求过于频繁,已被限流"
305
+ : `开放平台返回 HTTP ${res.status}`;
306
+ throw new Error(`${statusHint}(${path}),请稍后重试`);
307
+ }
308
+ // JSON 错误响应
309
+ try {
310
+ const error = JSON.parse(rawBody) as { message?: string; code?: number };
311
+ throw new Error(`API Error [${path}]: ${error.message ?? rawBody}`);
312
+ } catch (parseErr) {
313
+ if (parseErr instanceof Error && parseErr.message.startsWith("API Error")) throw parseErr;
314
+ throw new Error(`API Error [${path}] HTTP ${res.status}: ${rawBody.slice(0, 200)}`);
315
+ }
301
316
  }
302
317
 
303
- return data;
318
+ // 成功响应但不是 JSON(极端异常情况)
319
+ if (isHtmlResponse) {
320
+ throw new Error(`QQ 服务端返回了非 JSON 响应(${path}),可能是临时故障,请稍后重试`);
321
+ }
322
+
323
+ try {
324
+ return JSON.parse(rawBody) as T;
325
+ } catch {
326
+ throw new Error(`开放平台响应格式异常(${path}),请稍后重试`);
327
+ }
304
328
  }
305
329
 
306
330
  // ============ 上传重试(指数退避) ============
@@ -20,16 +20,10 @@ import { getUpdateInfo, checkVersionExists } from "./update-checker.js";
20
20
  import { getHomeDir, getQQBotDataDir, isWindows } from "./utils/platform.js";
21
21
  import { saveCredentialBackup } from "./credential-backup.js";
22
22
  import { fileURLToPath } from "node:url";
23
+ import { getPackageVersion } from "./utils/pkg-version.js";
23
24
  const require = createRequire(import.meta.url);
24
25
 
25
- // 读取 package.json 中的版本号
26
- let PLUGIN_VERSION = "unknown";
27
- try {
28
- const pkg = require("../package.json");
29
- PLUGIN_VERSION = pkg.version ?? "unknown";
30
- } catch {
31
- // fallback
32
- }
26
+ let PLUGIN_VERSION = getPackageVersion(import.meta.url);
33
27
 
34
28
  // 获取 openclaw 框架版本(缓存结果,只执行一次)
35
29
  let _frameworkVersion: string | null = null;
@@ -10,6 +10,7 @@
10
10
 
11
11
  import { createRequire } from "node:module";
12
12
  import https from "node:https";
13
+ import { getPackageVersion } from "./utils/pkg-version.js";
13
14
 
14
15
  const require = createRequire(import.meta.url);
15
16
 
@@ -21,13 +22,7 @@ const REGISTRIES = [
21
22
  `https://registry.npmmirror.com/${ENCODED_PKG}`,
22
23
  ];
23
24
 
24
- let CURRENT_VERSION = "unknown";
25
- try {
26
- const pkg = require("../package.json");
27
- CURRENT_VERSION = pkg.version ?? "unknown";
28
- } catch {
29
- // fallback
30
- }
25
+ let CURRENT_VERSION = getPackageVersion(import.meta.url);
31
26
 
32
27
  export interface UpdateInfo {
33
28
  current: string;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * 从 import.meta.url 向上遍历目录树查找 package.json 并读取 version。
3
+ * 不依赖硬编码的 "../" 层级,无论编译输出结构如何变化都能可靠找到。
4
+ */
5
+
6
+ import { fileURLToPath } from "node:url";
7
+ import { createRequire } from "node:module";
8
+ import path from "node:path";
9
+ import fs from "node:fs";
10
+
11
+ let _cached: string | null = null;
12
+
13
+ export function getPackageVersion(metaUrl?: string): string {
14
+ if (_cached !== null) return _cached;
15
+
16
+ // Strategy 1: 从调用者的 import.meta.url(或本模块)向上遍历找 package.json
17
+ const startFile = metaUrl ? fileURLToPath(metaUrl) : fileURLToPath(import.meta.url);
18
+ let dir = path.dirname(startFile);
19
+ const root = path.parse(dir).root;
20
+
21
+ while (dir !== root) {
22
+ const candidate = path.join(dir, "package.json");
23
+ try {
24
+ if (fs.existsSync(candidate)) {
25
+ const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
26
+ // 确认是我们自己的包(避免找到其他 package.json)
27
+ if (pkg.name === "@tencent-connect/openclaw-qqbot" && pkg.version) {
28
+ _cached = pkg.version as string;
29
+ return _cached;
30
+ }
31
+ }
32
+ } catch {
33
+ // ignore and try parent
34
+ }
35
+ dir = path.dirname(dir);
36
+ }
37
+
38
+ // Strategy 2: fallback 用 createRequire 尝试常见相对路径
39
+ try {
40
+ const require = createRequire(metaUrl ?? import.meta.url);
41
+ for (const rel of ["../../package.json", "../package.json", "./package.json"]) {
42
+ try {
43
+ const pkg = require(rel);
44
+ if (pkg?.version) {
45
+ _cached = pkg.version as string;
46
+ return _cached;
47
+ }
48
+ } catch { /* next */ }
49
+ }
50
+ } catch { /* fallback */ }
51
+
52
+ _cached = "unknown";
53
+ return _cached;
54
+ }