@sfmc-bds/bds-tools 0.2.0-beta.7 → 0.2.0-beta.9

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 (84) hide show
  1. package/dist/bds-manager.d.ts.map +1 -1
  2. package/dist/bds-manager.js +40 -65
  3. package/dist/bds-manager.js.map +2 -2
  4. package/dist/check-update.d.ts.map +1 -1
  5. package/dist/check-update.js +4 -6
  6. package/dist/check-update.js.map +2 -2
  7. package/dist/fsx.d.ts +2 -7
  8. package/dist/fsx.d.ts.map +1 -1
  9. package/dist/fsx.js +0 -11
  10. package/dist/fsx.js.map +2 -2
  11. package/dist/host-platform.d.ts +25 -0
  12. package/dist/host-platform.d.ts.map +1 -0
  13. package/dist/host-platform.js +50 -0
  14. package/dist/host-platform.js.map +7 -0
  15. package/dist/pack-manager.d.ts.map +1 -1
  16. package/dist/pack-manager.js +6 -1
  17. package/dist/pack-manager.js.map +2 -2
  18. package/dist/pack-update/bindings.d.ts +16 -0
  19. package/dist/pack-update/bindings.d.ts.map +1 -0
  20. package/dist/pack-update/bindings.js +69 -0
  21. package/dist/pack-update/bindings.js.map +7 -0
  22. package/dist/pack-update/config.d.ts +16 -0
  23. package/dist/pack-update/config.d.ts.map +1 -0
  24. package/dist/pack-update/config.js +146 -0
  25. package/dist/pack-update/config.js.map +7 -0
  26. package/dist/pack-update/deps.d.ts +28 -0
  27. package/dist/pack-update/deps.d.ts.map +1 -0
  28. package/dist/pack-update/deps.js +20 -0
  29. package/dist/pack-update/deps.js.map +7 -0
  30. package/dist/pack-update/index.d.ts +55 -0
  31. package/dist/pack-update/index.d.ts.map +1 -0
  32. package/dist/pack-update/index.js +72 -0
  33. package/dist/pack-update/index.js.map +7 -0
  34. package/dist/pack-update/providers/curseforge.d.ts +23 -0
  35. package/dist/pack-update/providers/curseforge.d.ts.map +1 -0
  36. package/dist/pack-update/providers/curseforge.js +229 -0
  37. package/dist/pack-update/providers/curseforge.js.map +7 -0
  38. package/dist/pack-update/providers/index.d.ts +16 -0
  39. package/dist/pack-update/providers/index.d.ts.map +1 -0
  40. package/dist/pack-update/providers/index.js +37 -0
  41. package/dist/pack-update/providers/index.js.map +7 -0
  42. package/dist/pack-update/service.d.ts +28 -0
  43. package/dist/pack-update/service.d.ts.map +1 -0
  44. package/dist/pack-update/service.js +570 -0
  45. package/dist/pack-update/service.js.map +7 -0
  46. package/dist/pack-update/types.d.ts +126 -0
  47. package/dist/pack-update/types.d.ts.map +1 -0
  48. package/dist/pack-update/types.js +8 -0
  49. package/dist/pack-update/types.js.map +7 -0
  50. package/dist/pack-update/version-policy.d.ts +81 -0
  51. package/dist/pack-update/version-policy.d.ts.map +1 -0
  52. package/dist/pack-update/version-policy.js +210 -0
  53. package/dist/pack-update/version-policy.js.map +7 -0
  54. package/dist/paths.d.ts +1 -1
  55. package/dist/paths.d.ts.map +1 -1
  56. package/dist/paths.js +3 -5
  57. package/dist/paths.js.map +2 -2
  58. package/dist/process-probe.d.ts +34 -0
  59. package/dist/process-probe.d.ts.map +1 -0
  60. package/dist/process-probe.js +152 -0
  61. package/dist/process-probe.js.map +7 -0
  62. package/dist/qq-events-notify.d.ts +16 -0
  63. package/dist/qq-events-notify.d.ts.map +1 -0
  64. package/dist/qq-events-notify.js +70 -0
  65. package/dist/qq-events-notify.js.map +7 -0
  66. package/dist/qq-events-notify.test.d.ts +5 -0
  67. package/dist/qq-events-notify.test.d.ts.map +1 -0
  68. package/dist/qq-events-notify.test.js +41 -0
  69. package/dist/qq-events-notify.test.js.map +7 -0
  70. package/dist/qqutil.d.ts +2 -1
  71. package/dist/qqutil.d.ts.map +1 -1
  72. package/dist/qqutil.js +49 -8
  73. package/dist/qqutil.js.map +2 -2
  74. package/dist/upstream.d.ts +4 -3
  75. package/dist/upstream.d.ts.map +1 -1
  76. package/dist/upstream.js +8 -7
  77. package/dist/upstream.js.map +2 -2
  78. package/dist/version.js +2 -2
  79. package/dist/version.js.map +2 -2
  80. package/dist/world-packs.d.ts +0 -2
  81. package/dist/world-packs.d.ts.map +1 -1
  82. package/dist/world-packs.js +0 -2
  83. package/dist/world-packs.js.map +2 -2
  84. package/package.json +8 -6
@@ -0,0 +1,152 @@
1
+ import { exec, execSync } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { resolveRuntimeRoot, stateDir } from "@sfmc-bds/sdk/node/config";
7
+ import { bdsExeName } from "./host-platform.js";
8
+ const execAsync = promisify(exec);
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ const DEFAULT_ROOT_DIR = resolveRuntimeRoot(__dirname);
12
+ let execImpl = async (cmd, opts) => {
13
+ const result = await execAsync(cmd, opts);
14
+ return { stdout: String(result.stdout), stderr: String(result.stderr) };
15
+ };
16
+ function setExecForTesting(fn) {
17
+ execImpl = fn ?? (async (cmd, opts) => {
18
+ const result = await execAsync(cmd, opts);
19
+ return { stdout: String(result.stdout), stderr: String(result.stderr) };
20
+ });
21
+ }
22
+ function resolveRoot(rootDir) {
23
+ return rootDir ?? DEFAULT_ROOT_DIR;
24
+ }
25
+ function pidFilePath(rootDir) {
26
+ return path.join(stateDir(resolveRoot(rootDir)), "bds.pid");
27
+ }
28
+ function ensureStateDirForRoot(rootDir) {
29
+ fs.mkdirSync(stateDir(resolveRoot(rootDir)), { recursive: true });
30
+ }
31
+ function readBdsPidFile(rootDir) {
32
+ try {
33
+ return parseInt(fs.readFileSync(pidFilePath(rootDir), "utf-8").trim(), 10) || 0;
34
+ } catch {
35
+ return 0;
36
+ }
37
+ }
38
+ function writeBdsPidFile(pid, rootDir) {
39
+ try {
40
+ ensureStateDirForRoot(rootDir);
41
+ fs.writeFileSync(pidFilePath(rootDir), String(pid));
42
+ } catch {
43
+ }
44
+ }
45
+ function clearBdsPidFile(rootDir) {
46
+ try {
47
+ const file = pidFilePath(rootDir);
48
+ if (fs.existsSync(file)) fs.unlinkSync(file);
49
+ } catch {
50
+ }
51
+ }
52
+ let aliveOverride = null;
53
+ function setIsAliveForTesting(fn) {
54
+ aliveOverride = fn;
55
+ }
56
+ async function isProcessAlive(pid) {
57
+ if (!pid) return false;
58
+ if (aliveOverride) return aliveOverride(pid);
59
+ try {
60
+ if (process.platform === "win32") {
61
+ const { stdout } = await execImpl(`tasklist /fi "PID eq ${pid}" /nh`, { windowsHide: true });
62
+ return stdout.includes(String(pid));
63
+ }
64
+ process.kill(pid, 0);
65
+ return true;
66
+ } catch {
67
+ return false;
68
+ }
69
+ }
70
+ function isProcessAliveSync(pid) {
71
+ if (!pid) return false;
72
+ if (aliveOverride) return aliveOverride(pid);
73
+ try {
74
+ if (process.platform === "win32") {
75
+ const stdout = execSync(`tasklist /fi "PID eq ${pid}" /nh`, {
76
+ windowsHide: true,
77
+ encoding: "utf8"
78
+ });
79
+ return String(stdout).includes(String(pid));
80
+ }
81
+ process.kill(pid, 0);
82
+ return true;
83
+ } catch {
84
+ return false;
85
+ }
86
+ }
87
+ async function findBedrockServerPids() {
88
+ try {
89
+ if (process.platform === "win32") {
90
+ const image = bdsExeName("windows");
91
+ const { stdout: stdout2 } = await execImpl(`tasklist /fi "IMAGENAME eq ${image}" /fo csv /nh`, {
92
+ windowsHide: true
93
+ });
94
+ const pids = [];
95
+ const re = new RegExp(`"${image.replace(".", "\\.")}","(\\d+)"`, "i");
96
+ for (const line of stdout2.split(/\r?\n/)) {
97
+ const m = line.match(re);
98
+ if (m) {
99
+ const pid = parseInt(m[1] ?? "0", 10);
100
+ if (pid > 0) pids.push(pid);
101
+ }
102
+ }
103
+ return pids;
104
+ }
105
+ const { stdout } = await execImpl("pgrep -x bedrock_server", { windowsHide: true });
106
+ return stdout.split(/\r?\n/).map((s) => parseInt(s.trim(), 10)).filter((n) => n > 0);
107
+ } catch {
108
+ return [];
109
+ }
110
+ }
111
+ async function killBedrockServerByImage() {
112
+ try {
113
+ if (process.platform === "win32") {
114
+ await execImpl(`taskkill /f /im ${bdsExeName("windows")}`, { windowsHide: true });
115
+ } else {
116
+ await execImpl("pkill -x bedrock_server", { windowsHide: true });
117
+ }
118
+ } catch {
119
+ }
120
+ }
121
+ async function probeBdsStatus(opts) {
122
+ const managedPid = opts.managedPid ?? 0;
123
+ const hasStdin = opts.hasStdin ?? false;
124
+ if (managedPid > 0 && hasStdin && await isProcessAlive(managedPid)) {
125
+ return { state: "managed", pid: managedPid };
126
+ }
127
+ const filePid = readBdsPidFile(opts.rootDir);
128
+ if (filePid > 0 && await isProcessAlive(filePid)) {
129
+ return { state: "external", pid: filePid };
130
+ }
131
+ const imagePids = await findBedrockServerPids();
132
+ if (imagePids.length > 0) {
133
+ return { state: "external", pid: imagePids[0] ?? 0 };
134
+ }
135
+ if (filePid > 0) {
136
+ clearBdsPidFile(opts.rootDir);
137
+ }
138
+ return { state: "stopped", pid: 0 };
139
+ }
140
+ export {
141
+ clearBdsPidFile,
142
+ findBedrockServerPids,
143
+ isProcessAlive,
144
+ isProcessAliveSync,
145
+ killBedrockServerByImage,
146
+ probeBdsStatus,
147
+ readBdsPidFile,
148
+ setExecForTesting,
149
+ setIsAliveForTesting,
150
+ writeBdsPidFile
151
+ };
152
+ //# sourceMappingURL=process-probe.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/process-probe.ts"],
4
+ "sourcesContent": ["/**\n * process-probe.ts \u2014 OS \u7EA7 BDS \u8FDB\u7A0B\u63A2\u6D4B\u4E0E PID \u6587\u4EF6\u7BA1\u7406\n *\n * \u4F9B bds-manager / sfmc \u5171\u7528\uFF0C\u907F\u514D\u91CD\u590D tasklist / taskkill \u903B\u8F91\u3002\n */\n\nimport { exec, execSync } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { resolveRuntimeRoot, stateDir } from \"@sfmc-bds/sdk/node/config\";\nimport { bdsExeName } from \"./host-platform.js\";\n\nconst execAsync = promisify(exec);\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/** \u4E0E paths.ROOT_DIR \u4E00\u81F4\uFF1ASFMC_ROOT > walk-up monorepo */\nconst DEFAULT_ROOT_DIR = resolveRuntimeRoot(__dirname);\n\nexport type ExecFn = (\n cmd: string,\n opts?: { windowsHide?: boolean }\n) => Promise<{ stdout: string; stderr: string }>;\n\nlet execImpl: ExecFn = async (cmd, opts) => {\n const result = await execAsync(cmd, opts);\n return { stdout: String(result.stdout), stderr: String(result.stderr) };\n};\n\n/** \u6D4B\u8BD5\u6CE8\u5165 exec\uFF0C\u4F20 null \u6062\u590D\u9ED8\u8BA4 */\nexport function setExecForTesting(fn: ExecFn | null): void {\n execImpl =\n fn ??\n (async (cmd, opts) => {\n const result = await execAsync(cmd, opts);\n return { stdout: String(result.stdout), stderr: String(result.stderr) };\n });\n}\n\nfunction resolveRoot(rootDir?: string): string {\n return rootDir ?? DEFAULT_ROOT_DIR;\n}\n\nfunction pidFilePath(rootDir?: string): string {\n return path.join(stateDir(resolveRoot(rootDir)), \"bds.pid\");\n}\n\nfunction ensureStateDirForRoot(rootDir?: string): void {\n fs.mkdirSync(stateDir(resolveRoot(rootDir)), { recursive: true });\n}\n\nexport function readBdsPidFile(rootDir?: string): number {\n try {\n return parseInt(fs.readFileSync(pidFilePath(rootDir), \"utf-8\").trim(), 10) || 0;\n } catch {\n return 0;\n }\n}\n\nexport function writeBdsPidFile(pid: number, rootDir?: string): void {\n try {\n ensureStateDirForRoot(rootDir);\n fs.writeFileSync(pidFilePath(rootDir), String(pid));\n } catch {\n /* ignore */\n }\n}\n\nexport function clearBdsPidFile(rootDir?: string): void {\n try {\n const file = pidFilePath(rootDir);\n if (fs.existsSync(file)) fs.unlinkSync(file);\n } catch {\n /* ignore */\n }\n}\n\nlet aliveOverride: ((pid: number) => boolean) | null = null;\n\n/** \u6D4B\u8BD5\u6CE8\u5165\u63A2\u6D3B\uFF0C\u4F20 null \u6062\u590D\u9ED8\u8BA4\uFF08\u907F\u514D Linux \u4E0A process.kill \u78B0\u5230\u771F\u5B9E PID\uFF09 */\nexport function setIsAliveForTesting(fn: ((pid: number) => boolean) | null): void {\n aliveOverride = fn;\n}\n\nexport async function isProcessAlive(pid: number): Promise<boolean> {\n if (!pid) return false;\n if (aliveOverride) return aliveOverride(pid);\n try {\n if (process.platform === \"win32\") {\n const { stdout } = await execImpl(`tasklist /fi \"PID eq ${pid}\" /nh`, { windowsHide: true });\n return stdout.includes(String(pid));\n }\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** \u540C\u6B65\u63A2\u6D3B\uFF08\u4F9B sync \u7684 sendCommand \u7B49\u8DEF\u5F84\uFF1B\u907F\u514D Promise \u88AB\u5F53 truthy\uFF09 */\nexport function isProcessAliveSync(pid: number): boolean {\n if (!pid) return false;\n if (aliveOverride) return aliveOverride(pid);\n try {\n if (process.platform === \"win32\") {\n const stdout = execSync(`tasklist /fi \"PID eq ${pid}\" /nh`, {\n windowsHide: true,\n encoding: \"utf8\",\n });\n return String(stdout).includes(String(pid));\n }\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function findBedrockServerPids(): Promise<number[]> {\n try {\n if (process.platform === \"win32\") {\n const image = bdsExeName(\"windows\");\n const { stdout } = await execImpl(`tasklist /fi \"IMAGENAME eq ${image}\" /fo csv /nh`, {\n windowsHide: true,\n });\n const pids: number[] = [];\n const re = new RegExp(`\"${image.replace(\".\", \"\\\\.\")}\",\"(\\\\d+)\"`, \"i\");\n for (const line of stdout.split(/\\r?\\n/)) {\n const m = line.match(re);\n if (m) {\n const pid = parseInt(m[1] ?? \"0\", 10);\n if (pid > 0) pids.push(pid);\n }\n }\n return pids;\n }\n const { stdout } = await execImpl(\"pgrep -x bedrock_server\", { windowsHide: true });\n return stdout\n .split(/\\r?\\n/)\n .map((s) => parseInt(s.trim(), 10))\n .filter((n) => n > 0);\n } catch {\n return [];\n }\n}\n\nexport async function killBedrockServerByImage(): Promise<void> {\n try {\n if (process.platform === \"win32\") {\n await execImpl(`taskkill /f /im ${bdsExeName(\"windows\")}`, { windowsHide: true });\n } else {\n await execImpl(\"pkill -x bedrock_server\", { windowsHide: true });\n }\n } catch {\n /* ignore \u2014 \u65E0\u8FDB\u7A0B\u65F6 taskkill/pkill \u4F1A\u975E\u96F6\u9000\u51FA */\n }\n}\n\nexport type BdsProbeState = \"stopped\" | \"managed\" | \"external\";\n\nexport interface BdsProbeResult {\n state: BdsProbeState;\n pid: number;\n}\n\nexport async function probeBdsStatus(opts: {\n managedPid?: number;\n hasStdin?: boolean;\n rootDir?: string;\n}): Promise<BdsProbeResult> {\n const managedPid = opts.managedPid ?? 0;\n const hasStdin = opts.hasStdin ?? false;\n\n if (managedPid > 0 && hasStdin && (await isProcessAlive(managedPid))) {\n return { state: \"managed\", pid: managedPid };\n }\n\n const filePid = readBdsPidFile(opts.rootDir);\n if (filePid > 0 && (await isProcessAlive(filePid))) {\n return { state: \"external\", pid: filePid };\n }\n\n const imagePids = await findBedrockServerPids();\n if (imagePids.length > 0) {\n return { state: \"external\", pid: imagePids[0] ?? 0 };\n }\n\n if (filePid > 0) {\n clearBdsPidFile(opts.rootDir);\n }\n\n return { state: \"stopped\", pid: 0 };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["stdout"]
7
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * qq-events-notify.ts — 向 db-server 上报 BDS 启停事件
3
+ *
4
+ * 只 POST 结构化事件;群 openid / 频控由 db-server 处理(DIP)。
5
+ * 失败仅 warn,不抛错。
6
+ */
7
+ export type BdsLifecycleEventType = "crash" | "start";
8
+ /**
9
+ * POST /api/sfmc/qq/events;超时/非 2xx 不抛。
10
+ */
11
+ export declare function postBdsLifecycleEvent(type: BdsLifecycleEventType, detail?: string, opts?: {
12
+ host?: string;
13
+ port?: number;
14
+ timeoutMs?: number;
15
+ }): Promise<void>;
16
+ //# sourceMappingURL=qq-events-notify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qq-events-notify.d.ts","sourceRoot":"","sources":["../src/qq-events-notify.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,OAAO,CAAC;AAiBtD;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,qBAAqB,EAC3B,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1D,OAAO,CAAC,IAAI,CAAC,CA6Cf"}
@@ -0,0 +1,70 @@
1
+ import {
2
+ DEFAULT_DB_CONFIG,
3
+ loadEnsuredConfig
4
+ } from "@sfmc-bds/sdk/node/config";
5
+ import http from "node:http";
6
+ import { log } from "./log.js";
7
+ import { ROOT_DIR } from "./paths.js";
8
+ function resolveDbPort() {
9
+ try {
10
+ const cfg = loadEnsuredConfig(
11
+ ROOT_DIR,
12
+ "db_config.json",
13
+ "db_config",
14
+ { ...DEFAULT_DB_CONFIG }
15
+ );
16
+ const n = Number(cfg.db_port ?? 3001);
17
+ return Number.isFinite(n) && n > 0 ? n : 3001;
18
+ } catch {
19
+ return 3001;
20
+ }
21
+ }
22
+ function postBdsLifecycleEvent(type, detail, opts) {
23
+ const host = opts?.host ?? "127.0.0.1";
24
+ const port = opts?.port ?? resolveDbPort();
25
+ const timeoutMs = opts?.timeoutMs ?? 3e3;
26
+ const payload = JSON.stringify({
27
+ type,
28
+ detail: detail ?? void 0
29
+ });
30
+ return new Promise((resolve) => {
31
+ const req = http.request(
32
+ {
33
+ hostname: host,
34
+ port,
35
+ path: "/api/sfmc/qq/events",
36
+ method: "POST",
37
+ headers: {
38
+ "Content-Type": "application/json",
39
+ "Content-Length": Buffer.byteLength(payload)
40
+ },
41
+ timeout: timeoutMs
42
+ },
43
+ (res) => {
44
+ res.resume();
45
+ res.on("end", () => {
46
+ const code = res.statusCode ?? 0;
47
+ if (code < 200 || code >= 300) {
48
+ log.warn(`[QQ\u4E8B\u4EF6] db-server \u8FD4\u56DE HTTP ${code} (type=${type})`);
49
+ }
50
+ resolve();
51
+ });
52
+ }
53
+ );
54
+ req.on("timeout", () => {
55
+ req.destroy();
56
+ log.warn(`[QQ\u4E8B\u4EF6] \u4E0A\u62A5\u8D85\u65F6 ${timeoutMs}ms (type=${type})`);
57
+ resolve();
58
+ });
59
+ req.on("error", (err) => {
60
+ log.warn(`[QQ\u4E8B\u4EF6] \u4E0A\u62A5\u5931\u8D25: ${err.message} (type=${type})`);
61
+ resolve();
62
+ });
63
+ req.write(payload);
64
+ req.end();
65
+ });
66
+ }
67
+ export {
68
+ postBdsLifecycleEvent
69
+ };
70
+ //# sourceMappingURL=qq-events-notify.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/qq-events-notify.ts"],
4
+ "sourcesContent": ["/**\n * qq-events-notify.ts \u2014 \u5411 db-server \u4E0A\u62A5 BDS \u542F\u505C\u4E8B\u4EF6\n *\n * \u53EA POST \u7ED3\u6784\u5316\u4E8B\u4EF6\uFF1B\u7FA4 openid / \u9891\u63A7\u7531 db-server \u5904\u7406\uFF08DIP\uFF09\u3002\n * \u5931\u8D25\u4EC5 warn\uFF0C\u4E0D\u629B\u9519\u3002\n */\n\nimport {\n DEFAULT_DB_CONFIG,\n loadEnsuredConfig,\n type DBConfig,\n} from \"@sfmc-bds/sdk/node/config\";\nimport http from \"node:http\";\nimport { log } from \"./log.js\";\nimport { ROOT_DIR } from \"./paths.js\";\n\nexport type BdsLifecycleEventType = \"crash\" | \"start\";\n\nfunction resolveDbPort(): number {\n try {\n const cfg = loadEnsuredConfig(\n ROOT_DIR,\n \"db_config.json\",\n \"db_config\",\n { ...DEFAULT_DB_CONFIG } as Record<string, unknown>\n ) as DBConfig;\n const n = Number(cfg.db_port ?? 3001);\n return Number.isFinite(n) && n > 0 ? n : 3001;\n } catch {\n return 3001;\n }\n}\n\n/**\n * POST /api/sfmc/qq/events\uFF1B\u8D85\u65F6/\u975E 2xx \u4E0D\u629B\u3002\n */\nexport function postBdsLifecycleEvent(\n type: BdsLifecycleEventType,\n detail?: string,\n opts?: { host?: string; port?: number; timeoutMs?: number }\n): Promise<void> {\n const host = opts?.host ?? \"127.0.0.1\";\n const port = opts?.port ?? resolveDbPort();\n const timeoutMs = opts?.timeoutMs ?? 3_000;\n const payload = JSON.stringify({\n type,\n detail: detail ?? undefined,\n });\n\n return new Promise((resolve) => {\n const req = http.request(\n {\n hostname: host,\n port,\n path: \"/api/sfmc/qq/events\",\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(payload),\n },\n timeout: timeoutMs,\n },\n (res) => {\n res.resume();\n res.on(\"end\", () => {\n const code = res.statusCode ?? 0;\n if (code < 200 || code >= 300) {\n log.warn(`[QQ\u4E8B\u4EF6] db-server \u8FD4\u56DE HTTP ${code} (type=${type})`);\n }\n resolve();\n });\n }\n );\n req.on(\"timeout\", () => {\n req.destroy();\n log.warn(`[QQ\u4E8B\u4EF6] \u4E0A\u62A5\u8D85\u65F6 ${timeoutMs}ms (type=${type})`);\n resolve();\n });\n req.on(\"error\", (err) => {\n log.warn(`[QQ\u4E8B\u4EF6] \u4E0A\u62A5\u5931\u8D25: ${err.message} (type=${type})`);\n resolve();\n });\n req.write(payload);\n req.end();\n });\n}\n"],
5
+ "mappings": "AAOA;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,OAAO,UAAU;AACjB,SAAS,WAAW;AACpB,SAAS,gBAAgB;AAIzB,SAAS,gBAAwB;AAC/B,MAAI;AACF,UAAM,MAAM;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA,EAAE,GAAG,kBAAkB;AAAA,IACzB;AACA,UAAM,IAAI,OAAO,IAAI,WAAW,IAAI;AACpC,WAAO,OAAO,SAAS,CAAC,KAAK,IAAI,IAAI,IAAI;AAAA,EAC3C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,sBACd,MACA,QACA,MACe;AACf,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,OAAO,MAAM,QAAQ,cAAc;AACzC,QAAM,YAAY,MAAM,aAAa;AACrC,QAAM,UAAU,KAAK,UAAU;AAAA,IAC7B;AAAA,IACA,QAAQ,UAAU;AAAA,EACpB,CAAC;AAED,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,MAAM,KAAK;AAAA,MACf;AAAA,QACE,UAAU;AAAA,QACV;AAAA,QACA,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,kBAAkB,OAAO,WAAW,OAAO;AAAA,QAC7C;AAAA,QACA,SAAS;AAAA,MACX;AAAA,MACA,CAAC,QAAQ;AACP,YAAI,OAAO;AACX,YAAI,GAAG,OAAO,MAAM;AAClB,gBAAM,OAAO,IAAI,cAAc;AAC/B,cAAI,OAAO,OAAO,QAAQ,KAAK;AAC7B,gBAAI,KAAK,gDAA4B,IAAI,UAAU,IAAI,GAAG;AAAA,UAC5D;AACA,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF;AACA,QAAI,GAAG,WAAW,MAAM;AACtB,UAAI,QAAQ;AACZ,UAAI,KAAK,6CAAe,SAAS,YAAY,IAAI,GAAG;AACpD,cAAQ;AAAA,IACV,CAAC;AACD,QAAI,GAAG,SAAS,CAAC,QAAQ;AACvB,UAAI,KAAK,8CAAgB,IAAI,OAAO,UAAU,IAAI,GAAG;AACrD,cAAQ;AAAA,IACV,CAAC;AACD,QAAI,MAAM,OAAO;AACjB,QAAI,IAAI;AAAA,EACV,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * qq-events-notify 单测:失败不抛、可注入 host/port
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=qq-events-notify.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qq-events-notify.test.d.ts","sourceRoot":"","sources":["../src/qq-events-notify.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,41 @@
1
+ import assert from "node:assert/strict";
2
+ import http from "node:http";
3
+ import test from "node:test";
4
+ import { postBdsLifecycleEvent } from "./qq-events-notify.js";
5
+ test("postBdsLifecycleEvent\uFF1Adb \u4E0D\u53EF\u8FBE\u4E0D\u629B\u9519", async () => {
6
+ await assert.doesNotReject(
7
+ () => postBdsLifecycleEvent("start", "pid=1", {
8
+ host: "127.0.0.1",
9
+ port: 1,
10
+ timeoutMs: 200
11
+ })
12
+ );
13
+ });
14
+ test("postBdsLifecycleEvent\uFF1A2xx \u6210\u529F", async () => {
15
+ const server = http.createServer((req, res) => {
16
+ let body = "";
17
+ req.on("data", (c) => body += c);
18
+ req.on("end", () => {
19
+ assert.equal(req.method, "POST");
20
+ assert.equal(req.url, "/api/sfmc/qq/events");
21
+ const parsed = JSON.parse(body);
22
+ assert.equal(parsed.type, "crash");
23
+ assert.equal(parsed.detail, "code=1");
24
+ res.writeHead(200, { "Content-Type": "application/json" });
25
+ res.end(JSON.stringify({ success: true }));
26
+ });
27
+ });
28
+ await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
29
+ const addr = server.address();
30
+ assert.ok(addr && typeof addr === "object");
31
+ try {
32
+ await postBdsLifecycleEvent("crash", "code=1", {
33
+ host: "127.0.0.1",
34
+ port: addr.port,
35
+ timeoutMs: 2e3
36
+ });
37
+ } finally {
38
+ await new Promise((resolve, reject) => server.close((e) => e ? reject(e) : resolve()));
39
+ }
40
+ });
41
+ //# sourceMappingURL=qq-events-notify.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/qq-events-notify.test.ts"],
4
+ "sourcesContent": ["/**\n * qq-events-notify \u5355\u6D4B\uFF1A\u5931\u8D25\u4E0D\u629B\u3001\u53EF\u6CE8\u5165 host/port\n */\n\nimport assert from \"node:assert/strict\";\nimport http from \"node:http\";\nimport test from \"node:test\";\nimport { postBdsLifecycleEvent } from \"./qq-events-notify.js\";\n\ntest(\"postBdsLifecycleEvent\uFF1Adb \u4E0D\u53EF\u8FBE\u4E0D\u629B\u9519\", async () => {\n await assert.doesNotReject(() =>\n postBdsLifecycleEvent(\"start\", \"pid=1\", {\n host: \"127.0.0.1\",\n port: 1,\n timeoutMs: 200,\n })\n );\n});\n\ntest(\"postBdsLifecycleEvent\uFF1A2xx \u6210\u529F\", async () => {\n const server = http.createServer((req, res) => {\n let body = \"\";\n req.on(\"data\", (c) => (body += c));\n req.on(\"end\", () => {\n assert.equal(req.method, \"POST\");\n assert.equal(req.url, \"/api/sfmc/qq/events\");\n const parsed = JSON.parse(body) as { type: string; detail?: string };\n assert.equal(parsed.type, \"crash\");\n assert.equal(parsed.detail, \"code=1\");\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ success: true }));\n });\n });\n await new Promise<void>((resolve) => server.listen(0, \"127.0.0.1\", resolve));\n const addr = server.address();\n assert.ok(addr && typeof addr === \"object\");\n try {\n await postBdsLifecycleEvent(\"crash\", \"code=1\", {\n host: \"127.0.0.1\",\n port: addr.port,\n timeoutMs: 2000,\n });\n } finally {\n await new Promise<void>((resolve, reject) => server.close((e) => (e ? reject(e) : resolve())));\n }\n});\n"],
5
+ "mappings": "AAIA,OAAO,YAAY;AACnB,OAAO,UAAU;AACjB,OAAO,UAAU;AACjB,SAAS,6BAA6B;AAEtC,KAAK,sEAAmC,YAAY;AAClD,QAAM,OAAO;AAAA,IAAc,MACzB,sBAAsB,SAAS,SAAS;AAAA,MACtC,MAAM;AAAA,MACN,MAAM;AAAA,MACN,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AACF,CAAC;AAED,KAAK,+CAAgC,YAAY;AAC/C,QAAM,SAAS,KAAK,aAAa,CAAC,KAAK,QAAQ;AAC7C,QAAI,OAAO;AACX,QAAI,GAAG,QAAQ,CAAC,MAAO,QAAQ,CAAE;AACjC,QAAI,GAAG,OAAO,MAAM;AAClB,aAAO,MAAM,IAAI,QAAQ,MAAM;AAC/B,aAAO,MAAM,IAAI,KAAK,qBAAqB;AAC3C,YAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,aAAO,MAAM,OAAO,MAAM,OAAO;AACjC,aAAO,MAAM,OAAO,QAAQ,QAAQ;AACpC,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,SAAS,KAAK,CAAC,CAAC;AAAA,IAC3C,CAAC;AAAA,EACH,CAAC;AACD,QAAM,IAAI,QAAc,CAAC,YAAY,OAAO,OAAO,GAAG,aAAa,OAAO,CAAC;AAC3E,QAAM,OAAO,OAAO,QAAQ;AAC5B,SAAO,GAAG,QAAQ,OAAO,SAAS,QAAQ;AAC1C,MAAI;AACF,UAAM,sBAAsB,SAAS,UAAU;AAAA,MAC7C,MAAM;AAAA,MACN,MAAM,KAAK;AAAA,MACX,WAAW;AAAA,IACb,CAAC;AAAA,EACH,UAAE;AACA,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW,OAAO,MAAM,CAAC,MAAO,IAAI,OAAO,CAAC,IAAI,QAAQ,CAAE,CAAC;AAAA,EAC/F;AACF,CAAC;",
6
+ "names": []
7
+ }
package/dist/qqutil.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
- * qqutil.ts — QQ 通知工具 (基于 LLBot HTTP OneBot 11)
2
+ * qqutil.ts — QQ 通知工具(官方 Bot / LLBot 双后端)
3
3
  *
4
4
  * 改进:
5
5
  * - sendTimeout 提供总超时,避免通知发送挂死主流程
6
6
  * - 静默模式 (失败不抛出),保证主流程不被通知干扰
7
+ * - qq_backend=official 时走 SDK 发群;llbot 仍走 OneBot HTTP
7
8
  */
8
9
  /** 检查 qq-bridge 模块是否启用 */
9
10
  export declare function isQqBridgeEnabled(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"qqutil.d.ts","sourceRoot":"","sources":["../src/qqutil.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAuBH,0BAA0B;AAC1B,wBAAgB,iBAAiB,IAAI,OAAO,CAO3C;AA6CD,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAY1D;AAED,wBAAsB,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAYlE;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMlF"}
1
+ {"version":3,"file":"qqutil.d.ts","sourceRoot":"","sources":["../src/qqutil.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiCH,0BAA0B;AAC1B,wBAAgB,iBAAiB,IAAI,OAAO,CAQ3C;AAgED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB1D;AAED,wBAAsB,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BlE;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMlF"}
package/dist/qqutil.js CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  modulePath,
4
4
  readJson
5
5
  } from "@sfmc-bds/sdk/node/config";
6
+ import { sendGroupTextMessage } from "@sfmc-bds/sdk/node/qq-official";
6
7
  import http from "node:http";
7
8
  import { log } from "./log.js";
8
9
  import { ROOT_DIR } from "./paths.js";
@@ -19,15 +20,18 @@ function isQqBridgeEnabled() {
19
20
  const mod = catalog.modules?.find((m) => m.id === "qq-bridge" || m.configKey === "qq_bridge");
20
21
  return mod ? lock.modules?.[mod.id ?? ""]?.enabled === true : false;
21
22
  }
23
+ function isOfficialBackend(cfg) {
24
+ return (cfg.qq_backend ?? "official") === "official";
25
+ }
22
26
  function sendToLLBot(payload, timeoutMs = 5e3) {
23
27
  const cfg = getConfig();
24
- const url = new URL(cfg.llbot_http || "http://127.0.0.1:3000");
28
+ const url = new URL(cfg.llbot_http || "http://127.0.0.1:3004");
25
29
  const data = JSON.stringify(payload);
26
30
  return new Promise((resolve, reject) => {
27
31
  const req = http.request(
28
32
  {
29
33
  hostname: url.hostname,
30
- port: url.port || 3e3,
34
+ port: url.port || 3004,
31
35
  path: "/send_group_msg",
32
36
  method: "POST",
33
37
  headers: {
@@ -60,14 +64,36 @@ async function safeSend(label, fn) {
60
64
  log.warn(`[QQ] ${label} \u5931\u8D25: ${e.message}`);
61
65
  }
62
66
  }
67
+ async function sendOfficialText(text) {
68
+ const cfg = getConfig();
69
+ const appId = String(cfg.qq_app_id ?? "");
70
+ const appSecret = String(cfg.qq_app_secret ?? "");
71
+ const groupOpenid = String(cfg.qq_group_openid ?? "");
72
+ if (!appId || !appSecret || !groupOpenid) {
73
+ throw new Error("\u5B98\u65B9\u540E\u7AEF\u7F3A\u5C11 qq_app_id / qq_app_secret / qq_group_openid");
74
+ }
75
+ const result = await sendGroupTextMessage(
76
+ { appId, appSecret, sandbox: cfg.qq_sandbox === true },
77
+ { groupOpenid, content: text }
78
+ );
79
+ if (!result.ok) throw new Error(result.error);
80
+ }
63
81
  async function sendText(text) {
64
82
  const cfg = getConfig();
65
- if (!isQqBridgeEnabled() || !cfg.qq_group_id) {
66
- log.warn("[QQ] qq-bridge \u672A\u542F\u7528\u6216 qq_group_id \u7F3A\u5931");
83
+ if (!isQqBridgeEnabled()) {
84
+ log.warn("[QQ] qq-bridge \u672A\u542F\u7528");
85
+ return;
86
+ }
87
+ if (isOfficialBackend(cfg)) {
88
+ await safeSend("sendText(official)", () => sendOfficialText(text));
89
+ return;
90
+ }
91
+ if (!cfg.qq_group_id) {
92
+ log.warn("[QQ] qq_group_id \u7F3A\u5931");
67
93
  return;
68
94
  }
69
95
  await safeSend(
70
- "sendText",
96
+ "sendText(llbot)",
71
97
  () => sendToLLBot({
72
98
  group_id: parseInt(cfg.qq_group_id ?? "0", 10),
73
99
  message: [{ type: "text", data: { text } }]
@@ -76,12 +102,27 @@ async function sendText(text) {
76
102
  }
77
103
  async function sendMixed(segments) {
78
104
  const cfg = getConfig();
79
- if (!isQqBridgeEnabled() || !cfg.qq_group_id) {
80
- log.warn("[QQ] qq-bridge \u672A\u542F\u7528\u6216 qq_group_id \u7F3A\u5931");
105
+ if (!isQqBridgeEnabled()) {
106
+ log.warn("[QQ] qq-bridge \u672A\u542F\u7528");
107
+ return;
108
+ }
109
+ if (isOfficialBackend(cfg)) {
110
+ const text = segments.map((seg) => {
111
+ const s = seg;
112
+ if (s?.type === "text") return String(s.data?.text ?? "");
113
+ if (s?.type === "image") return "[\u56FE\u7247]";
114
+ return "";
115
+ }).join("").trim();
116
+ if (!text) return;
117
+ await safeSend("sendMixed(official)", () => sendOfficialText(text));
118
+ return;
119
+ }
120
+ if (!cfg.qq_group_id) {
121
+ log.warn("[QQ] qq_group_id \u7F3A\u5931");
81
122
  return;
82
123
  }
83
124
  await safeSend(
84
- "sendMixed",
125
+ "sendMixed(llbot)",
85
126
  () => sendToLLBot({
86
127
  group_id: parseInt(cfg.qq_group_id ?? "0", 10),
87
128
  message: segments
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/qqutil.ts"],
4
- "sourcesContent": ["/**\n * qqutil.ts \u2014 QQ \u901A\u77E5\u5DE5\u5177 (\u57FA\u4E8E LLBot HTTP OneBot 11)\n *\n * \u6539\u8FDB:\n * - sendTimeout \u63D0\u4F9B\u603B\u8D85\u65F6\uFF0C\u907F\u514D\u901A\u77E5\u53D1\u9001\u6302\u6B7B\u4E3B\u6D41\u7A0B\n * - \u9759\u9ED8\u6A21\u5F0F (\u5931\u8D25\u4E0D\u629B\u51FA)\uFF0C\u4FDD\u8BC1\u4E3B\u6D41\u7A0B\u4E0D\u88AB\u901A\u77E5\u5E72\u6270\n */\n\nimport {\n configPath,\n modulePath,\n readJson,\n type Catalog,\n type ModuleLock,\n type QQBridgeConfig,\n} from \"@sfmc-bds/sdk/node/config\";\nimport http from \"node:http\";\nimport { log } from \"./log.js\";\nimport { ROOT_DIR } from \"./paths.js\";\n\ntype QqConfig = Pick<QQBridgeConfig, \"llbot_http\" | \"qq_group_id\">;\n\nlet cachedCfg: QqConfig | null = null;\nfunction getConfig(): QqConfig {\n if (cachedCfg) return cachedCfg;\n cachedCfg = (readJson<QQBridgeConfig>(configPath(ROOT_DIR, \"qq_config.json\")) ?? {}) as QqConfig;\n return cachedCfg;\n}\n\n/** \u68C0\u67E5 qq-bridge \u6A21\u5757\u662F\u5426\u542F\u7528 */\nexport function isQqBridgeEnabled(): boolean {\n const catalog = readJson<Catalog>(modulePath(ROOT_DIR, \"catalog.json\"));\n const lock = readJson<ModuleLock>(modulePath(ROOT_DIR, \"module-lock.json\"));\n if (!catalog || !lock) return true; // \u6A21\u5757\u76EE\u5F55\u7F3A\u5931\u5219\u4FDD\u5B88\u89C6\u4E3A\u53EF\u7528\n const mod = catalog.modules?.find((m) => m.id === \"qq-bridge\" || m.configKey === \"qq_bridge\") as\n { id?: string } | undefined;\n return mod ? lock.modules?.[mod.id ?? \"\"]?.enabled === true : false;\n}\n\nfunction sendToLLBot(payload: unknown, timeoutMs = 5_000): Promise<void> {\n const cfg = getConfig();\n const url = new URL(cfg.llbot_http || \"http://127.0.0.1:3000\");\n const data = JSON.stringify(payload);\n return new Promise((resolve, reject) => {\n const req = http.request(\n {\n hostname: url.hostname,\n port: url.port || 3000,\n path: \"/send_group_msg\",\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(data),\n },\n timeout: timeoutMs,\n },\n (res) => {\n let body = \"\";\n res.on(\"data\", (c) => (body += c));\n res.on(\"end\", () => {\n if (res.statusCode === 200) resolve();\n else reject(new Error(`LLBot HTTP ${res.statusCode}: ${body.slice(0, 200)}`));\n });\n }\n );\n req.on(\"timeout\", () => {\n req.destroy(new Error(`LLBot \u8D85\u65F6 ${timeoutMs}ms`));\n });\n req.on(\"error\", reject);\n req.write(data);\n req.end();\n });\n}\n\nasync function safeSend(label: string, fn: () => Promise<void>): Promise<void> {\n try {\n await fn();\n } catch (e) {\n log.warn(`[QQ] ${label} \u5931\u8D25: ${(e as Error).message}`);\n }\n}\n\nexport async function sendText(text: string): Promise<void> {\n const cfg = getConfig();\n if (!isQqBridgeEnabled() || !cfg.qq_group_id) {\n log.warn(\"[QQ] qq-bridge \u672A\u542F\u7528\u6216 qq_group_id \u7F3A\u5931\");\n return;\n }\n await safeSend(\"sendText\", () =>\n sendToLLBot({\n group_id: parseInt(cfg.qq_group_id ?? \"0\", 10),\n message: [{ type: \"text\", data: { text } }],\n })\n );\n}\n\nexport async function sendMixed(segments: unknown[]): Promise<void> {\n const cfg = getConfig();\n if (!isQqBridgeEnabled() || !cfg.qq_group_id) {\n log.warn(\"[QQ] qq-bridge \u672A\u542F\u7528\u6216 qq_group_id \u7F3A\u5931\");\n return;\n }\n await safeSend(\"sendMixed\", () =>\n sendToLLBot({\n group_id: parseInt(cfg.qq_group_id ?? \"0\", 10),\n message: segments,\n })\n );\n}\n\nexport async function sendWithImage(text: string, base64Img: string): Promise<void> {\n const segments: unknown[] = [];\n if (text) segments.push({ type: \"text\", data: { text } });\n if (base64Img) segments.push({ type: \"image\", data: { file: `base64://${base64Img}` } });\n if (segments.length === 0) return;\n await sendMixed(segments);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\r\n * qqutil.ts \u2014 QQ \u901A\u77E5\u5DE5\u5177\uFF08\u5B98\u65B9 Bot / LLBot \u53CC\u540E\u7AEF\uFF09\r\n *\r\n * \u6539\u8FDB:\r\n * - sendTimeout \u63D0\u4F9B\u603B\u8D85\u65F6\uFF0C\u907F\u514D\u901A\u77E5\u53D1\u9001\u6302\u6B7B\u4E3B\u6D41\u7A0B\r\n * - \u9759\u9ED8\u6A21\u5F0F (\u5931\u8D25\u4E0D\u629B\u51FA)\uFF0C\u4FDD\u8BC1\u4E3B\u6D41\u7A0B\u4E0D\u88AB\u901A\u77E5\u5E72\u6270\r\n * - qq_backend=official \u65F6\u8D70 SDK \u53D1\u7FA4\uFF1Bllbot \u4ECD\u8D70 OneBot HTTP\r\n */\r\n\r\nimport {\r\n configPath,\r\n modulePath,\r\n readJson,\r\n type Catalog,\r\n type ModuleLock,\r\n type QQBridgeConfig,\r\n} from \"@sfmc-bds/sdk/node/config\";\r\nimport { sendGroupTextMessage } from \"@sfmc-bds/sdk/node/qq-official\";\r\nimport http from \"node:http\";\r\nimport { log } from \"./log.js\";\r\nimport { ROOT_DIR } from \"./paths.js\";\r\n\r\ntype QqConfig = Pick<\r\n QQBridgeConfig,\r\n | \"llbot_http\"\r\n | \"qq_group_id\"\r\n | \"qq_backend\"\r\n | \"qq_app_id\"\r\n | \"qq_app_secret\"\r\n | \"qq_sandbox\"\r\n | \"qq_group_openid\"\r\n>;\r\n\r\nlet cachedCfg: QqConfig | null = null;\r\nfunction getConfig(): QqConfig {\r\n if (cachedCfg) return cachedCfg;\r\n cachedCfg = (readJson<QQBridgeConfig>(configPath(ROOT_DIR, \"qq_config.json\")) ?? {}) as QqConfig;\r\n return cachedCfg;\r\n}\r\n\r\n/** \u68C0\u67E5 qq-bridge \u6A21\u5757\u662F\u5426\u542F\u7528 */\r\nexport function isQqBridgeEnabled(): boolean {\r\n const catalog = readJson<Catalog>(modulePath(ROOT_DIR, \"catalog.json\"));\r\n const lock = readJson<ModuleLock>(modulePath(ROOT_DIR, \"module-lock.json\"));\r\n if (!catalog || !lock) return true; // \u6A21\u5757\u76EE\u5F55\u7F3A\u5931\u5219\u4FDD\u5B88\u89C6\u4E3A\u53EF\u7528\r\n const mod = catalog.modules?.find((m) => m.id === \"qq-bridge\" || m.configKey === \"qq_bridge\") as\r\n | { id?: string }\r\n | undefined;\r\n return mod ? lock.modules?.[mod.id ?? \"\"]?.enabled === true : false;\r\n}\r\n\r\nfunction isOfficialBackend(cfg: QqConfig): boolean {\r\n return (cfg.qq_backend ?? \"official\") === \"official\";\r\n}\r\n\r\nfunction sendToLLBot(payload: unknown, timeoutMs = 5_000): Promise<void> {\r\n const cfg = getConfig();\r\n const url = new URL(cfg.llbot_http || \"http://127.0.0.1:3004\");\r\n const data = JSON.stringify(payload);\r\n return new Promise((resolve, reject) => {\r\n const req = http.request(\r\n {\r\n hostname: url.hostname,\r\n port: url.port || 3004,\r\n path: \"/send_group_msg\",\r\n method: \"POST\",\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n \"Content-Length\": Buffer.byteLength(data),\r\n },\r\n timeout: timeoutMs,\r\n },\r\n (res) => {\r\n let body = \"\";\r\n res.on(\"data\", (c) => (body += c));\r\n res.on(\"end\", () => {\r\n if (res.statusCode === 200) resolve();\r\n else reject(new Error(`LLBot HTTP ${res.statusCode}: ${body.slice(0, 200)}`));\r\n });\r\n }\r\n );\r\n req.on(\"timeout\", () => {\r\n req.destroy(new Error(`LLBot \u8D85\u65F6 ${timeoutMs}ms`));\r\n });\r\n req.on(\"error\", reject);\r\n req.write(data);\r\n req.end();\r\n });\r\n}\r\n\r\nasync function safeSend(label: string, fn: () => Promise<void>): Promise<void> {\r\n try {\r\n await fn();\r\n } catch (e) {\r\n log.warn(`[QQ] ${label} \u5931\u8D25: ${(e as Error).message}`);\r\n }\r\n}\r\n\r\nasync function sendOfficialText(text: string): Promise<void> {\r\n const cfg = getConfig();\r\n const appId = String(cfg.qq_app_id ?? \"\");\r\n const appSecret = String(cfg.qq_app_secret ?? \"\");\r\n const groupOpenid = String(cfg.qq_group_openid ?? \"\");\r\n if (!appId || !appSecret || !groupOpenid) {\r\n throw new Error(\"\u5B98\u65B9\u540E\u7AEF\u7F3A\u5C11 qq_app_id / qq_app_secret / qq_group_openid\");\r\n }\r\n const result = await sendGroupTextMessage(\r\n { appId, appSecret, sandbox: cfg.qq_sandbox === true },\r\n { groupOpenid, content: text }\r\n );\r\n if (!result.ok) throw new Error(result.error);\r\n}\r\n\r\nexport async function sendText(text: string): Promise<void> {\r\n const cfg = getConfig();\r\n if (!isQqBridgeEnabled()) {\r\n log.warn(\"[QQ] qq-bridge \u672A\u542F\u7528\");\r\n return;\r\n }\r\n if (isOfficialBackend(cfg)) {\r\n await safeSend(\"sendText(official)\", () => sendOfficialText(text));\r\n return;\r\n }\r\n if (!cfg.qq_group_id) {\r\n log.warn(\"[QQ] qq_group_id \u7F3A\u5931\");\r\n return;\r\n }\r\n await safeSend(\"sendText(llbot)\", () =>\r\n sendToLLBot({\r\n group_id: parseInt(cfg.qq_group_id ?? \"0\", 10),\r\n message: [{ type: \"text\", data: { text } }],\r\n })\r\n );\r\n}\r\n\r\nexport async function sendMixed(segments: unknown[]): Promise<void> {\r\n const cfg = getConfig();\r\n if (!isQqBridgeEnabled()) {\r\n log.warn(\"[QQ] qq-bridge \u672A\u542F\u7528\");\r\n return;\r\n }\r\n if (isOfficialBackend(cfg)) {\r\n // \u5B98\u65B9\u8DEF\u5F84\u4EC5\u62FC\u7EAF\u6587\u672C\uFF1B\u56FE\u7247\u6BB5\u964D\u7EA7\u4E3A [\u56FE\u7247]\r\n const text = segments\r\n .map((seg) => {\r\n const s = seg as { type?: string; data?: { text?: string } };\r\n if (s?.type === \"text\") return String(s.data?.text ?? \"\");\r\n if (s?.type === \"image\") return \"[\u56FE\u7247]\";\r\n return \"\";\r\n })\r\n .join(\"\")\r\n .trim();\r\n if (!text) return;\r\n await safeSend(\"sendMixed(official)\", () => sendOfficialText(text));\r\n return;\r\n }\r\n if (!cfg.qq_group_id) {\r\n log.warn(\"[QQ] qq_group_id \u7F3A\u5931\");\r\n return;\r\n }\r\n await safeSend(\"sendMixed(llbot)\", () =>\r\n sendToLLBot({\r\n group_id: parseInt(cfg.qq_group_id ?? \"0\", 10),\r\n message: segments,\r\n })\r\n );\r\n}\r\n\r\nexport async function sendWithImage(text: string, base64Img: string): Promise<void> {\r\n const segments: unknown[] = [];\r\n if (text) segments.push({ type: \"text\", data: { text } });\r\n if (base64Img) segments.push({ type: \"image\", data: { file: `base64://${base64Img}` } });\r\n if (segments.length === 0) return;\r\n await sendMixed(segments);\r\n}\r\n\r"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -6,13 +6,14 @@
6
6
  * - sha1 / sha256 同时校验
7
7
  * - 流式校验 + 边下边校验
8
8
  */
9
+ import { type BdsHostOs } from "./host-platform.js";
9
10
  import type { BdsUpdaterConfig, VersionDetails, VersionInfo } from "./types.js";
10
11
  /** 获取最新版本号 (含 cdn_root),3 次重试 */
11
- export declare function getVersionInfo(cfg: BdsUpdaterConfig, channel: string): Promise<VersionInfo>;
12
+ export declare function getVersionInfo(cfg: BdsUpdaterConfig, channel: string, hostOs?: BdsHostOs): Promise<VersionInfo>;
12
13
  /** 获取 per-version 的下载链接 + 哈希 (fallback GitHub → jsdelivr) */
13
- export declare function fetchVersionDetails(cfg: BdsUpdaterConfig, channel: string, version: string): Promise<VersionDetails>;
14
+ export declare function fetchVersionDetails(cfg: BdsUpdaterConfig, channel: string, version: string, hostOs?: BdsHostOs): Promise<VersionDetails>;
14
15
  /** 候选下载 URL (按优先级) */
15
- export declare function buildDownloadUrls(cfg: BdsUpdaterConfig, channel: string, version: string, details: VersionDetails): string[];
16
+ export declare function buildDownloadUrls(cfg: BdsUpdaterConfig, channel: string, version: string, details: VersionDetails, hostOs?: BdsHostOs): string[];
16
17
  /** 同步 / 异步校验文件哈希 */
17
18
  export declare function verifyFileHash(filePath: string, expectedSha1: string, expectedSha256: string): Promise<boolean>;
18
19
  /** 检查版本兼容性 (白名单) */
@@ -1 +1 @@
1
- {"version":3,"file":"upstream.d.ts","sourceRoot":"","sources":["../src/upstream.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAchF,iCAAiC;AACjC,wBAAsB,cAAc,CAAC,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA8BjG;AAED,6DAA6D;AAC7D,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,gBAAgB,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,cAAc,CAAC,CA2BzB;AAED,sBAAsB;AACtB,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,gBAAgB,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,cAAc,GACtB,MAAM,EAAE,CAeV;AAED,oBAAoB;AACpB,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiBrH;AAED,oBAAoB;AACpB,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAInF"}
1
+ {"version":3,"file":"upstream.d.ts","sourceRoot":"","sources":["../src/upstream.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAqD,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGvG,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAchF,iCAAiC;AACjC,wBAAsB,cAAc,CAClC,GAAG,EAAE,gBAAgB,EACrB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,SAAuB,GAC9B,OAAO,CAAC,WAAW,CAAC,CA8BtB;AAED,6DAA6D;AAC7D,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,gBAAgB,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,SAAuB,GAC9B,OAAO,CAAC,cAAc,CAAC,CA2BzB;AAED,sBAAsB;AACtB,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,gBAAgB,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,cAAc,EACvB,MAAM,GAAE,SAAuB,GAC9B,MAAM,EAAE,CAYV;AAED,oBAAoB;AACpB,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiBrH;AAED,oBAAoB;AACpB,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAInF"}
package/dist/upstream.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
+ import { bdsCdnZipSuffix, bdsDetailsPlatformKey, bdsHostOs } from "./host-platform.js";
3
4
  import { fetchJsonWithFallback } from "./http.js";
4
5
  import { log } from "./log.js";
5
6
  import { toVer3 } from "./version.js";
@@ -12,7 +13,7 @@ function resolveTemplate(tpl, vars) {
12
13
  }
13
14
  return s;
14
15
  }
15
- async function getVersionInfo(cfg, channel) {
16
+ async function getVersionInfo(cfg, channel, hostOs = bdsHostOs()) {
16
17
  const api = cfg.version_versions || process.env["BDS_VERSIONS_API"] || DEFAULT_VERSIONS_API;
17
18
  const mirror = cfg.version_versions_mirror || process.env["BDS_VERSIONS_MIRROR"] || DEFAULT_VERSIONS_MIRROR;
18
19
  const sources = [api, mirror].filter(Boolean);
@@ -26,7 +27,7 @@ async function getVersionInfo(cfg, channel) {
26
27
  const entry = channel === "preview" ? json.preview : json.release;
27
28
  ver = entry?.latest;
28
29
  } else {
29
- const platform = json.windows || json.linux;
30
+ const platform = hostOs === "windows" ? json.windows || json.linux : json.linux || json.windows;
30
31
  ver = channel === "preview" ? platform?.preview : platform?.stable;
31
32
  }
32
33
  if (!ver) throw new Error("\u672A\u627E\u5230\u6700\u65B0\u7248\u672C\u53F7");
@@ -42,8 +43,8 @@ async function getVersionInfo(cfg, channel) {
42
43
  }
43
44
  throw lastErr instanceof Error ? lastErr : new Error("\u7248\u672C\u53F7\u83B7\u53D6\u5931\u8D25");
44
45
  }
45
- async function fetchVersionDetails(cfg, channel, version) {
46
- const platform = channel === "preview" ? "windows_preview" : "windows";
46
+ async function fetchVersionDetails(cfg, channel, version, hostOs = bdsHostOs()) {
47
+ const platform = bdsDetailsPlatformKey(channel, hostOs);
47
48
  const ch = channel === "preview" ? "preview" : "release";
48
49
  const vars = { version, ver3: toVer3(version), platform, channel: ch };
49
50
  const detailsTpl = cfg.version_details || "https://raw.githubusercontent.com/Bedrock-OSS/BDS-Versions/main/{platform}/{version}.json";
@@ -52,7 +53,7 @@ async function fetchVersionDetails(cfg, channel, version) {
52
53
  const json = await fetchJsonWithFallback(sources);
53
54
  const mode = cfg.version_mode || "bedrock-oss";
54
55
  if (mode === "endstone") {
55
- const bw = json.binary?.windows;
56
+ const bw = hostOs === "windows" ? json.binary?.windows : json.binary?.linux;
56
57
  return {
57
58
  downloadUrl: bw?.url ?? "",
58
59
  sha1: "",
@@ -67,7 +68,7 @@ async function fetchVersionDetails(cfg, channel, version) {
67
68
  size: json.size_in_bytes ?? 0
68
69
  };
69
70
  }
70
- function buildDownloadUrls(cfg, channel, version, details) {
71
+ function buildDownloadUrls(cfg, channel, version, details, hostOs = bdsHostOs()) {
71
72
  const urls = [];
72
73
  if (cfg.download_mirror) {
73
74
  const mirrorVer = channel === "preview" ? `${version}-preview` : version;
@@ -75,7 +76,7 @@ function buildDownloadUrls(cfg, channel, version, details) {
75
76
  }
76
77
  if (details.downloadUrl) urls.push(details.downloadUrl);
77
78
  const cdnRoot = cfg.cdn_root || "https://www.minecraft.net/bedrockdedicatedserver";
78
- const suffix = channel === "preview" ? `/bin-win-preview/bedrock-server-${version}-preview.zip` : `/bin-win/bedrock-server-${version}.zip`;
79
+ const suffix = bdsCdnZipSuffix(channel, version, hostOs);
79
80
  urls.push(cdnRoot + suffix);
80
81
  urls.push(`https://minecraft.azureedge.net${suffix}`);
81
82
  return [...new Set(urls)];