@harrylabsj/kiwi 0.6.0 → 0.6.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.
- package/README.md +21 -0
- package/contracts/candidate-agent-dto-1.0.README.md +12 -8
- package/contracts/conformance/python-service/README.md +19 -0
- package/contracts/conformance/python-service/run.py +134 -0
- package/contracts/kiwi-catalog/1.0/listing-record.schema.json +11 -1
- package/contracts/kiwi-contracts.lock.json +7 -0
- package/contracts/manifest.json +61 -0
- package/contracts/vectors/candidate-agent.invalid-private-field.json +7 -0
- package/contracts/vectors/candidate-agent.valid.json +26 -0
- package/dist/a2a/client/client.js +39 -34
- package/dist/a2a/client/client.js.map +1 -1
- package/dist/a2a/client/url-policy.d.ts +8 -1
- package/dist/a2a/client/url-policy.js +18 -2
- package/dist/a2a/client/url-policy.js.map +1 -1
- package/dist/a2a/clock.d.ts +30 -0
- package/dist/a2a/clock.js +39 -0
- package/dist/a2a/clock.js.map +1 -0
- package/dist/a2a/negotiate.d.ts +6 -0
- package/dist/a2a/negotiate.js +10 -12
- package/dist/a2a/negotiate.js.map +1 -1
- package/dist/a2a/node.d.ts +23 -3
- package/dist/a2a/node.js +128 -18
- package/dist/a2a/node.js.map +1 -1
- package/dist/a2a/server/merchant-handler.d.ts +4 -0
- package/dist/a2a/server/merchant-handler.js +238 -25
- package/dist/a2a/server/merchant-handler.js.map +1 -1
- package/dist/a2a/server/pipeline.d.ts +17 -0
- package/dist/a2a/server/pipeline.js +89 -1
- package/dist/a2a/server/pipeline.js.map +1 -1
- package/dist/a2a/server/ucp.d.ts +30 -2
- package/dist/a2a/server/ucp.js +23 -14
- package/dist/a2a/server/ucp.js.map +1 -1
- package/dist/agent/agent-db.js +5 -0
- package/dist/agent/agent-db.js.map +1 -1
- package/dist/agent/buyer/buyer-tools.d.ts +5 -0
- package/dist/agent/buyer/buyer-tools.js +8 -1
- package/dist/agent/buyer/buyer-tools.js.map +1 -1
- package/dist/agent/buyer/types.d.ts +0 -25
- package/dist/agent/buyer/types.js.map +1 -1
- package/dist/agent/kernel-builder.d.ts +27 -0
- package/dist/agent/kernel-builder.js +180 -0
- package/dist/agent/kernel-builder.js.map +1 -0
- package/dist/agent/kernel.d.ts +6 -0
- package/dist/agent/kernel.js +95 -8
- package/dist/agent/kernel.js.map +1 -1
- package/dist/agent/memory/store.d.ts +5 -0
- package/dist/agent/memory/store.js +29 -1
- package/dist/agent/memory/store.js.map +1 -1
- package/dist/agent/merchant/action-candidate.js +15 -1
- package/dist/agent/merchant/action-candidate.js.map +1 -1
- package/dist/agent/merchant/merchant-client.js +22 -4
- package/dist/agent/merchant/merchant-client.js.map +1 -1
- package/dist/agent/merchant/types.d.ts +5 -2
- package/dist/agent/merchant/types.js +6 -2
- package/dist/agent/merchant/types.js.map +1 -1
- package/dist/agent/negotiation-chat.js +34 -14
- package/dist/agent/negotiation-chat.js.map +1 -1
- package/dist/cli.d.ts +56 -0
- package/dist/cli.js +141 -156
- package/dist/cli.js.map +1 -1
- package/dist/commerce/data-source.d.ts +7 -3
- package/dist/commerce/data-source.js.map +1 -1
- package/dist/commerce/http-client.js +35 -31
- package/dist/commerce/http-client.js.map +1 -1
- package/dist/commerce/shopping-cli-source.js +66 -48
- package/dist/commerce/shopping-cli-source.js.map +1 -1
- package/dist/config/profile.d.ts +7 -0
- package/dist/config/profile.js +23 -0
- package/dist/config/profile.js.map +1 -1
- package/dist/counterparty/a2a-direct/index.d.ts +5 -0
- package/dist/counterparty/a2a-direct/index.js +24 -0
- package/dist/counterparty/a2a-direct/index.js.map +1 -1
- package/dist/counterparty/shopping-cli-hosted/index.js +52 -19
- package/dist/counterparty/shopping-cli-hosted/index.js.map +1 -1
- package/dist/discovery/catalog-source/kiwi-record.d.ts +3 -0
- package/dist/discovery/catalog-source/kiwi-record.js.map +1 -1
- package/dist/discovery/catalog-source/kiwi-source.js +46 -36
- package/dist/discovery/catalog-source/kiwi-source.js.map +1 -1
- package/dist/discovery/catalog-source/register.d.ts +6 -0
- package/dist/discovery/catalog-source/register.js +27 -8
- package/dist/discovery/catalog-source/register.js.map +1 -1
- package/dist/discovery/catalog-source/source.js +39 -35
- package/dist/discovery/catalog-source/source.js.map +1 -1
- package/dist/discovery/resolve.d.ts +15 -0
- package/dist/discovery/resolve.js +59 -35
- package/dist/discovery/resolve.js.map +1 -1
- package/dist/discovery/ucp/index.d.ts +1 -1
- package/dist/discovery/ucp/index.js +1 -1
- package/dist/discovery/ucp/index.js.map +1 -1
- package/dist/discovery/ucp/resolver.d.ts +5 -0
- package/dist/discovery/ucp/resolver.js +65 -38
- package/dist/discovery/ucp/resolver.js.map +1 -1
- package/dist/discovery/ucp/vendor.d.ts +1 -1
- package/dist/discovery/ucp/vendor.js +1 -1
- package/dist/handoff/delivery.d.ts +4 -2
- package/dist/handoff/delivery.js +6 -1
- package/dist/handoff/delivery.js.map +1 -1
- package/dist/handoff/errors.d.ts +1 -1
- package/dist/handoff/errors.js +1 -0
- package/dist/handoff/errors.js.map +1 -1
- package/dist/handoff/idempotency.d.ts +10 -1
- package/dist/handoff/idempotency.js +13 -3
- package/dist/handoff/idempotency.js.map +1 -1
- package/dist/handoff/index.d.ts +1 -1
- package/dist/handoff/index.js +1 -1
- package/dist/handoff/index.js.map +1 -1
- package/dist/handoff/ledger.d.ts +9 -0
- package/dist/handoff/ledger.js +66 -0
- package/dist/handoff/ledger.js.map +1 -1
- package/dist/handoff/transaction.d.ts +21 -3
- package/dist/handoff/transaction.js +50 -11
- package/dist/handoff/transaction.js.map +1 -1
- package/dist/handoff/ucp-checkout/channel.js +15 -10
- package/dist/handoff/ucp-checkout/channel.js.map +1 -1
- package/dist/negotiation/jcs.js +11 -8
- package/dist/negotiation/jcs.js.map +1 -1
- package/dist/negotiation/lease/store.d.ts +25 -0
- package/dist/negotiation/lease/store.js +101 -0
- package/dist/negotiation/lease/store.js.map +1 -0
- package/dist/negotiation/recovery/recover.js +25 -1
- package/dist/negotiation/recovery/recover.js.map +1 -1
- package/dist/negotiation/state/phase.js +12 -4
- package/dist/negotiation/state/phase.js.map +1 -1
- package/dist/operator/runner.d.ts +11 -0
- package/dist/operator/runner.js +20 -1
- package/dist/operator/runner.js.map +1 -1
- package/dist/product-catalog.d.ts +43 -0
- package/dist/product-catalog.js +60 -0
- package/dist/product-catalog.js.map +1 -0
- package/dist/product-cli.d.ts +1 -1
- package/dist/product-cli.js +1 -1
- package/dist/product-publish.d.ts +15 -17
- package/dist/product-publish.js +279 -46
- package/dist/product-publish.js.map +1 -1
- package/dist/supervisor/stack-config.d.ts +6 -1
- package/dist/supervisor/stack-config.js +11 -3
- package/dist/supervisor/stack-config.js.map +1 -1
- package/dist/trust/identity/message-signature.js +17 -0
- package/dist/trust/identity/message-signature.js.map +1 -1
- package/dist/weixin/channel.d.ts +89 -0
- package/dist/weixin/channel.js +370 -0
- package/dist/weixin/channel.js.map +1 -0
- package/dist/weixin/cli-weixin.d.ts +29 -0
- package/dist/weixin/cli-weixin.js +130 -0
- package/dist/weixin/cli-weixin.js.map +1 -0
- package/dist/weixin/credentials.d.ts +33 -0
- package/dist/weixin/credentials.js +107 -0
- package/dist/weixin/credentials.js.map +1 -0
- package/dist/weixin/ilink-client.d.ts +44 -0
- package/dist/weixin/ilink-client.js +287 -0
- package/dist/weixin/ilink-client.js.map +1 -0
- package/dist/weixin/login.d.ts +37 -0
- package/dist/weixin/login.js +93 -0
- package/dist/weixin/login.js.map +1 -0
- package/dist/weixin/qr.d.ts +26 -0
- package/dist/weixin/qr.js +525 -0
- package/dist/weixin/qr.js.map +1 -0
- package/dist/weixin/types.d.ts +114 -0
- package/dist/weixin/types.js +48 -0
- package/dist/weixin/types.js.map +1 -0
- package/package.json +12 -2
- package/dist/commerce/erp-source.d.ts +0 -51
- package/dist/commerce/erp-source.js +0 -184
- package/dist/commerce/erp-source.js.map +0 -1
- package/dist/commerce/local-db-source.d.ts +0 -50
- package/dist/commerce/local-db-source.js +0 -162
- package/dist/commerce/local-db-source.js.map +0 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 harrylabsj
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* `kiwi weixin` 命令——微信远程控制通道入口。
|
|
18
|
+
*
|
|
19
|
+
* 接线对齐 cmdChat:profile 加载 → buildChatKernel → (可选 --a2a 节点)→
|
|
20
|
+
* WeixinChannel.open(登录或加载凭证)→ run();SIGINT/SIGTERM 优雅关闭。
|
|
21
|
+
* A2A 节点默认关(headless 控制面收窄攻击面),--a2a 开启。
|
|
22
|
+
*/
|
|
23
|
+
import { buildChatKernel, defaultChatProfile } from "../agent/kernel-builder.js";
|
|
24
|
+
import { startA2aNode } from "../a2a/node.js";
|
|
25
|
+
import { loadProfile } from "../config/profile.js";
|
|
26
|
+
import { EXIT } from "../exit-codes.js";
|
|
27
|
+
import { DEFAULT_CATALOG_URL } from "../product-cli.js";
|
|
28
|
+
import { agentDataDir, ensurePathsForDir } from "../agent/agent-db.js";
|
|
29
|
+
import { WeixinChannel } from "./channel.js";
|
|
30
|
+
import { credentialsPathFor, syncStatePathFor } from "./credentials.js";
|
|
31
|
+
import { ILINK_DEFAULT_BASE_URL } from "./types.js";
|
|
32
|
+
export function weixinUsage() {
|
|
33
|
+
return `kiwi weixin — 微信远程控制通道(iLink Bot)
|
|
34
|
+
|
|
35
|
+
Usage:
|
|
36
|
+
kiwi weixin [--profile FILE] [--data-dir DIR] [--allow id1,id2] [--relogin]
|
|
37
|
+
[--a2a] [--port N] [--qr-scale 1|2] [--no-qr]
|
|
38
|
+
|
|
39
|
+
--profile FILE profile(缺省 defaultChatProfile,同裸 kiwi)
|
|
40
|
+
--data-dir DIR kernel 数据目录 + 微信凭证/游标(缺省 agentDataDir)
|
|
41
|
+
--allow id,… 额外授权微信用户(配对扫描者始终自动授权)
|
|
42
|
+
--relogin 丢弃已存凭证重新扫码(会话过期/换设备时)
|
|
43
|
+
--a2a 同时启动 A2A 节点(默认关;port 复用 --port)
|
|
44
|
+
--qr-scale N 终端二维码每模块列数(1 或 2;默认 1)
|
|
45
|
+
--no-qr 仅打印 URL + 提示(兜底;默认渲染二维码)
|
|
46
|
+
|
|
47
|
+
扫码登录后微信发消息即远程控制 kiwi(对话 + /slash 命令:
|
|
48
|
+
/mode /approve /reject /status /quit 等)。首次请先发一句任意消息唤醒通道。
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
/** `kiwi weixin` 主入口。返回进程退出码(0 OK / 2 CONFIG / 3 AUTH / 10 TRANSIENT)。 */
|
|
52
|
+
export async function cmdWeixin(args) {
|
|
53
|
+
const profile = args.profile !== undefined ? loadProfile(args.profile) : defaultChatProfile();
|
|
54
|
+
const catalog = args.catalog ?? process.env.KIWI_CATALOG_URL ?? DEFAULT_CATALOG_URL;
|
|
55
|
+
const dataDir = args.dataDir ?? agentDataDir(profile.agent_id);
|
|
56
|
+
ensurePathsForDir(dataDir);
|
|
57
|
+
const kernel = await buildChatKernel(profile, dataDir, catalog);
|
|
58
|
+
let node = null;
|
|
59
|
+
if (args.a2a) {
|
|
60
|
+
try {
|
|
61
|
+
node = await startA2aNode({
|
|
62
|
+
profile,
|
|
63
|
+
catalog,
|
|
64
|
+
preferredPort: args.port,
|
|
65
|
+
...(args.dataDir !== undefined ? { dataDir: args.dataDir } : {}),
|
|
66
|
+
ownerTokenSecret: process.env.KIWI_CATALOG_OWNER_TOKEN_SECRET,
|
|
67
|
+
});
|
|
68
|
+
process.stderr.write(`[a2a] ${node.role}@${node.url}${node.catalogAgentId !== undefined ? ` registered ${node.catalogAgentId}` : ""}\n`);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
process.stderr.write(`[a2a] 节点启动失败(微信通道继续):${err instanceof Error ? err.message : String(err)}\n`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// 白名单:--allow > KIWI_WEIXIN_ALLOW_USERS > profile weixin.allow_users
|
|
75
|
+
const allowUsers = resolveAllowUsers(args.allow, profile.weixin?.allow_users);
|
|
76
|
+
// base URL:KIWI_WEIXIN_BASE_URL > profile weixin.base_url > 默认
|
|
77
|
+
const apiBaseUrl = process.env.KIWI_WEIXIN_BASE_URL ?? profile.weixin?.base_url ?? ILINK_DEFAULT_BASE_URL;
|
|
78
|
+
let channel = null;
|
|
79
|
+
const shutdown = async (code) => {
|
|
80
|
+
await channel?.stop().catch(() => undefined);
|
|
81
|
+
await node?.stop().catch(() => undefined);
|
|
82
|
+
await kernel.close().catch(() => undefined);
|
|
83
|
+
process.exit(code);
|
|
84
|
+
};
|
|
85
|
+
process.on("SIGINT", () => void shutdown(0));
|
|
86
|
+
process.on("SIGTERM", () => void shutdown(0));
|
|
87
|
+
try {
|
|
88
|
+
channel = await WeixinChannel.open({
|
|
89
|
+
kernel,
|
|
90
|
+
apiBaseUrl,
|
|
91
|
+
credentialsPath: credentialsPathFor(dataDir),
|
|
92
|
+
syncBufPath: syncStatePathFor(dataDir),
|
|
93
|
+
allowUsers,
|
|
94
|
+
forceRelogin: args.relogin,
|
|
95
|
+
...(args.qrScale > 1 ? { qrScale: args.qrScale } : {}),
|
|
96
|
+
renderQr: args.noQr
|
|
97
|
+
? undefined
|
|
98
|
+
: (qr) => {
|
|
99
|
+
for (const line of qr)
|
|
100
|
+
process.stdout.write(`${line}\n`);
|
|
101
|
+
},
|
|
102
|
+
notice: (line) => process.stderr.write(`${line}\n`),
|
|
103
|
+
});
|
|
104
|
+
const exitCode = await channel.run();
|
|
105
|
+
return exitCode;
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
process.stderr.write(`[weixin] ${err instanceof Error ? err.message : String(err)}\n`);
|
|
109
|
+
return EXIT.CONFIG;
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
process.removeListener("SIGINT", shutdown);
|
|
113
|
+
process.removeListener("SIGTERM", shutdown);
|
|
114
|
+
await channel?.stop().catch(() => undefined);
|
|
115
|
+
await node?.stop().catch(() => undefined);
|
|
116
|
+
await kernel.close().catch(() => undefined);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** 白名单合并:--allow(逗号分隔)> env > profile。 */
|
|
120
|
+
function resolveAllowUsers(cliAllow, profileAllow) {
|
|
121
|
+
const env = process.env.KIWI_WEIXIN_ALLOW_USERS;
|
|
122
|
+
const source = cliAllow ?? env ?? (profileAllow !== undefined ? profileAllow.join(",") : undefined);
|
|
123
|
+
if (source === undefined || source === "")
|
|
124
|
+
return [];
|
|
125
|
+
return source
|
|
126
|
+
.split(",")
|
|
127
|
+
.map((s) => s.trim())
|
|
128
|
+
.filter((s) => s !== "");
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=cli-weixin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-weixin.js","sourceRoot":"","sources":["../../src/weixin/cli-weixin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 harrylabsj
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import type { BotCredentials } from "./types.js";
|
|
17
|
+
/** 同步游标 + 去重指纹(seen = MD5 指纹 LRU,cap 由调用方裁剪)。 */
|
|
18
|
+
export interface StoredSyncState {
|
|
19
|
+
get_updates_buf: string;
|
|
20
|
+
seen: string[];
|
|
21
|
+
}
|
|
22
|
+
/** 保存凭证(0600 + 原子写)。 */
|
|
23
|
+
export declare function saveCredentials(pathName: string, creds: BotCredentials): void;
|
|
24
|
+
/** 加载凭证;缺失 → not_configured;损坏/缺字段 → validation(fail-closed)。 */
|
|
25
|
+
export declare function loadCredentials(pathName: string): BotCredentials;
|
|
26
|
+
/** 保存同步游标 + 去重指纹(0600 + 原子写)。 */
|
|
27
|
+
export declare function saveSyncState(pathName: string, state: StoredSyncState): void;
|
|
28
|
+
/** 加载同步游标;缺失 → 默认(空游标);损坏 → validation(fail-closed)。 */
|
|
29
|
+
export declare function loadSyncState(pathName: string): StoredSyncState;
|
|
30
|
+
/** 便捷:凭证文件路径(dataDir 下)。 */
|
|
31
|
+
export declare function credentialsPathFor(dataDir: string): string;
|
|
32
|
+
/** 便捷:同步状态文件路径(dataDir 下)。 */
|
|
33
|
+
export declare function syncStatePathFor(dataDir: string): string;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 harrylabsj
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* 微信通道凭证与同步游标持久化——0600、原子写、损坏 fail-closed。
|
|
18
|
+
*
|
|
19
|
+
* - `weixin-credentials.json`:bot 凭证(token/base_url/user_id/saved_at);
|
|
20
|
+
* - `weixin-sync-buf.json`:getupdates 游标 + 去重指纹(重启零丢失)。
|
|
21
|
+
* 写文件用 tmp + renameSync 原子替换(防崩溃半写);损坏/缺字段一律
|
|
22
|
+
* WeixinError(fail-closed,拒绝静默重扫)。
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
import { WeixinError } from "./types.js";
|
|
27
|
+
/** 保存凭证(0600 + 原子写)。 */
|
|
28
|
+
export function saveCredentials(pathName, creds) {
|
|
29
|
+
const payload = {
|
|
30
|
+
ilink_bot_id: creds.ilink_bot_id,
|
|
31
|
+
bot_token: creds.bot_token,
|
|
32
|
+
base_url: creds.base_url,
|
|
33
|
+
ilink_user_id: creds.ilink_user_id,
|
|
34
|
+
saved_at: creds.saved_at,
|
|
35
|
+
};
|
|
36
|
+
atomicWrite(pathName, JSON.stringify(payload));
|
|
37
|
+
}
|
|
38
|
+
/** 加载凭证;缺失 → not_configured;损坏/缺字段 → validation(fail-closed)。 */
|
|
39
|
+
export function loadCredentials(pathName) {
|
|
40
|
+
if (!existsSync(pathName)) {
|
|
41
|
+
throw new WeixinError("not_configured", `微信凭证不存在:${pathName}(请先 kiwi weixin 扫码登录)`);
|
|
42
|
+
}
|
|
43
|
+
let raw;
|
|
44
|
+
try {
|
|
45
|
+
raw = JSON.parse(readFileSync(pathName, "utf-8"));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw new WeixinError("validation", `微信凭证文件损坏:${pathName}`);
|
|
49
|
+
}
|
|
50
|
+
if (!isRecord(raw))
|
|
51
|
+
throw new WeixinError("validation", `微信凭证文件格式错误:${pathName}`);
|
|
52
|
+
for (const key of ["ilink_bot_id", "bot_token", "base_url", "ilink_user_id", "saved_at"]) {
|
|
53
|
+
if (typeof raw[key] !== "string") {
|
|
54
|
+
throw new WeixinError("validation", `微信凭证缺少字段 ${key}:${pathName}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
ilink_bot_id: raw.ilink_bot_id,
|
|
59
|
+
bot_token: raw.bot_token,
|
|
60
|
+
base_url: raw.base_url,
|
|
61
|
+
ilink_user_id: raw.ilink_user_id,
|
|
62
|
+
saved_at: raw.saved_at,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** 保存同步游标 + 去重指纹(0600 + 原子写)。 */
|
|
66
|
+
export function saveSyncState(pathName, state) {
|
|
67
|
+
atomicWrite(pathName, JSON.stringify(state));
|
|
68
|
+
}
|
|
69
|
+
/** 加载同步游标;缺失 → 默认(空游标);损坏 → validation(fail-closed)。 */
|
|
70
|
+
export function loadSyncState(pathName) {
|
|
71
|
+
if (!existsSync(pathName)) {
|
|
72
|
+
return { get_updates_buf: "", seen: [] };
|
|
73
|
+
}
|
|
74
|
+
let raw;
|
|
75
|
+
try {
|
|
76
|
+
raw = JSON.parse(readFileSync(pathName, "utf-8"));
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
throw new WeixinError("validation", `微信同步状态文件损坏:${pathName}`);
|
|
80
|
+
}
|
|
81
|
+
if (!isRecord(raw))
|
|
82
|
+
throw new WeixinError("validation", `微信同步状态文件格式错误:${pathName}`);
|
|
83
|
+
const buf = raw.get_updates_buf;
|
|
84
|
+
const seen = raw.seen;
|
|
85
|
+
if (typeof buf !== "string" || !Array.isArray(seen) || !seen.every((s) => typeof s === "string")) {
|
|
86
|
+
throw new WeixinError("validation", `微信同步状态字段错误:${pathName}`);
|
|
87
|
+
}
|
|
88
|
+
return { get_updates_buf: buf, seen: seen };
|
|
89
|
+
}
|
|
90
|
+
// ── 内部:原子写(tmp + renameSync,0600)─────────────────────────────
|
|
91
|
+
function atomicWrite(pathName, content) {
|
|
92
|
+
const tmp = `${pathName}.tmp`;
|
|
93
|
+
writeFileSync(tmp, content, { mode: 0o600 });
|
|
94
|
+
renameSync(tmp, pathName);
|
|
95
|
+
}
|
|
96
|
+
function isRecord(v) {
|
|
97
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
98
|
+
}
|
|
99
|
+
/** 便捷:凭证文件路径(dataDir 下)。 */
|
|
100
|
+
export function credentialsPathFor(dataDir) {
|
|
101
|
+
return path.join(dataDir, "weixin-credentials.json");
|
|
102
|
+
}
|
|
103
|
+
/** 便捷:同步状态文件路径(dataDir 下)。 */
|
|
104
|
+
export function syncStatePathFor(dataDir) {
|
|
105
|
+
return path.join(dataDir, "weixin-sync-buf.json");
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/weixin/credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 harrylabsj
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import type { BotCredentials, BotQrcode, GetUpdatesResult, QrcodeStatus, SendMessageInput } from "./types.js";
|
|
17
|
+
export interface IlinkClientOptions {
|
|
18
|
+
/** iLink base URL(confirmed 的 baseurl 或默认)。 */
|
|
19
|
+
baseUrl?: string;
|
|
20
|
+
log?: (line: string) => void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* iLink 客户端:登录三端点 + 消息收发。所有请求带 iLink 头,响应严格
|
|
24
|
+
* 解析(未知字段忽略,必需字段缺失 fail-closed),错误类型化分类。
|
|
25
|
+
*/
|
|
26
|
+
export declare class IlinkClient {
|
|
27
|
+
private baseUrl;
|
|
28
|
+
private readonly log?;
|
|
29
|
+
/** sendmessage 的 client_id(每进程一个,幂等标识)。 */
|
|
30
|
+
private readonly clientId;
|
|
31
|
+
constructor(options?: IlinkClientOptions);
|
|
32
|
+
/** scaned_but_redirect 时切换 base URL(进程内生效;持久化由调用方负责)。 */
|
|
33
|
+
setBaseUrl(url: string): void;
|
|
34
|
+
get base(): string;
|
|
35
|
+
/** GET ilink/bot/get_bot_qrcode?bot_type=3 → 二维码(hex token + 完整可扫 URL)。 */
|
|
36
|
+
getBotQrcode(): Promise<BotQrcode>;
|
|
37
|
+
/** GET ilink/bot/get_qrcode_status?qrcode=<hex> → 扫码状态(判别联合)。 */
|
|
38
|
+
getQrcodeStatus(qrcode: string): Promise<QrcodeStatus>;
|
|
39
|
+
/** POST ilink/bot/getupdates(长轮询)→ 消息 + 新游标。 */
|
|
40
|
+
getUpdates(syncBuf: string, credentials: BotCredentials, signal?: AbortSignal): Promise<GetUpdatesResult>;
|
|
41
|
+
/** POST ilink/bot/sendmessage → 回显 context_token(协议要求)。 */
|
|
42
|
+
sendMessage(input: SendMessageInput, credentials: BotCredentials): Promise<void>;
|
|
43
|
+
private request;
|
|
44
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 harrylabsj
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* iLink Bot API 客户端(微信远程控制通道)——腾讯 iLink 协议实现。
|
|
18
|
+
*
|
|
19
|
+
* 协议学习自 Hermes Agent weixin adapter(gateway/platforms/weixin.py):
|
|
20
|
+
* - 登录:get_bot_qrcode → 轮询 get_qrcode_status → confirmed 得凭证;
|
|
21
|
+
* - 消息:getupdates 长轮询(get_updates_buf 游标);sendmessage 回显 context_token;
|
|
22
|
+
* - 头:iLink-App-Id: "bot"、iLink-App-ClientVersion: "131584"(=(2<<16)|(2<<8)|0);
|
|
23
|
+
* - 错误:ret/errcode 非 0 → protocol;-14 或 (-2+"unknown error") → session_stale。
|
|
24
|
+
* 出站纪律全走 safe-http(manual redirect / 超时覆盖 body / 1 MiB 上限)。
|
|
25
|
+
*/
|
|
26
|
+
import { isRedirectResponse, readJsonBody, SafeHttpError } from "../net/safe-http.js";
|
|
27
|
+
import { ILINK_DEFAULT_BASE_URL, WeixinError } from "./types.js";
|
|
28
|
+
/** iLink 端点(相对 base URL)。 */
|
|
29
|
+
const EP_GET_UPDATES = "ilink/bot/getupdates";
|
|
30
|
+
const EP_SEND_MESSAGE = "ilink/bot/sendmessage";
|
|
31
|
+
const EP_GET_BOT_QR = "ilink/bot/get_bot_qrcode";
|
|
32
|
+
const EP_GET_QR_STATUS = "ilink/bot/get_qrcode_status";
|
|
33
|
+
/** 协议常量(Hermes weixin.py 对齐)。 */
|
|
34
|
+
const ILINK_APP_ID = "bot";
|
|
35
|
+
const ILINK_APP_CLIENT_VERSION = String((2 << 16) | (2 << 8) | 0); // "131584"
|
|
36
|
+
const MSG_TYPE_BOT = 2;
|
|
37
|
+
const MSG_STATE_FINISH = 2;
|
|
38
|
+
const ITEM_TEXT = 1;
|
|
39
|
+
const LONG_POLL_DEFAULT_MS = 35_000;
|
|
40
|
+
const API_TIMEOUT_MS = 15_000;
|
|
41
|
+
const QR_TIMEOUT_MS = 35_000;
|
|
42
|
+
const MAX_RESPONSE_BYTES = 1024 * 1024; // 1 MiB(iLink 响应远小于默认 8 MiB)
|
|
43
|
+
const SESSION_EXPIRED_ERRCODE = -14;
|
|
44
|
+
const RATE_LIMIT_ERRCODE = -2;
|
|
45
|
+
/**
|
|
46
|
+
* iLink 客户端:登录三端点 + 消息收发。所有请求带 iLink 头,响应严格
|
|
47
|
+
* 解析(未知字段忽略,必需字段缺失 fail-closed),错误类型化分类。
|
|
48
|
+
*/
|
|
49
|
+
export class IlinkClient {
|
|
50
|
+
baseUrl;
|
|
51
|
+
log;
|
|
52
|
+
/** sendmessage 的 client_id(每进程一个,幂等标识)。 */
|
|
53
|
+
clientId;
|
|
54
|
+
constructor(options = {}) {
|
|
55
|
+
this.baseUrl = (options.baseUrl ?? ILINK_DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
56
|
+
this.log = options.log;
|
|
57
|
+
this.clientId = crypto.randomUUID();
|
|
58
|
+
}
|
|
59
|
+
/** scaned_but_redirect 时切换 base URL(进程内生效;持久化由调用方负责)。 */
|
|
60
|
+
setBaseUrl(url) {
|
|
61
|
+
this.baseUrl = url.replace(/\/+$/, "");
|
|
62
|
+
}
|
|
63
|
+
get base() {
|
|
64
|
+
return this.baseUrl;
|
|
65
|
+
}
|
|
66
|
+
/** GET ilink/bot/get_bot_qrcode?bot_type=3 → 二维码(hex token + 完整可扫 URL)。 */
|
|
67
|
+
async getBotQrcode() {
|
|
68
|
+
const body = (await this.request("GET", `${EP_GET_BOT_QR}?bot_type=3`, {
|
|
69
|
+
timeoutMs: QR_TIMEOUT_MS,
|
|
70
|
+
}));
|
|
71
|
+
const qrcode = body.qrcode;
|
|
72
|
+
const qrcodeImgContent = body.qrcode_img_content;
|
|
73
|
+
if (typeof qrcode !== "string" || qrcode === "" || typeof qrcodeImgContent !== "string") {
|
|
74
|
+
throw new WeixinError("validation", "get_bot_qrcode 响应缺少 qrcode/qrcode_img_content");
|
|
75
|
+
}
|
|
76
|
+
return { qrcode, qrcode_img_content: qrcodeImgContent };
|
|
77
|
+
}
|
|
78
|
+
/** GET ilink/bot/get_qrcode_status?qrcode=<hex> → 扫码状态(判别联合)。 */
|
|
79
|
+
async getQrcodeStatus(qrcode) {
|
|
80
|
+
const body = (await this.request("GET", `${EP_GET_QR_STATUS}?qrcode=${encodeURIComponent(qrcode)}`, {
|
|
81
|
+
timeoutMs: QR_TIMEOUT_MS,
|
|
82
|
+
}));
|
|
83
|
+
const status = String(body.status ?? "");
|
|
84
|
+
if (status === "confirmed") {
|
|
85
|
+
const accountId = body.ilink_bot_id;
|
|
86
|
+
const token = body.bot_token;
|
|
87
|
+
const baseUrl = body.baseurl;
|
|
88
|
+
const userId = body.ilink_user_id;
|
|
89
|
+
if (typeof accountId !== "string" || accountId === "" || typeof token !== "string" || token === "") {
|
|
90
|
+
throw new WeixinError("validation", "get_qrcode_status confirmed 缺少凭证字段");
|
|
91
|
+
}
|
|
92
|
+
const credentials = {
|
|
93
|
+
ilink_bot_id: accountId,
|
|
94
|
+
bot_token: token,
|
|
95
|
+
base_url: typeof baseUrl === "string" && baseUrl !== "" ? baseUrl : ILINK_DEFAULT_BASE_URL,
|
|
96
|
+
ilink_user_id: typeof userId === "string" ? userId : "",
|
|
97
|
+
saved_at: "",
|
|
98
|
+
};
|
|
99
|
+
return { state: "confirmed", credentials };
|
|
100
|
+
}
|
|
101
|
+
if (status === "scaned_but_redirect") {
|
|
102
|
+
const redirectHost = String(body.redirect_host ?? "");
|
|
103
|
+
if (redirectHost === "") {
|
|
104
|
+
throw new WeixinError("validation", "scaned_but_redirect 缺少 redirect_host");
|
|
105
|
+
}
|
|
106
|
+
// 审查 P3:redirect_host 是后续 bot_token 直送目标(经 base_info)——
|
|
107
|
+
// 此前零校验直接拼 https://${redirectHost}。至少校验是纯 hostname
|
|
108
|
+
// (无 scheme/path/port/userinfo),防 iLink 被攻破时把凭证流量导到
|
|
109
|
+
// 任意主机(协议内信任模型可接受,但语法边界必须 fail-closed)。
|
|
110
|
+
let parsed;
|
|
111
|
+
try {
|
|
112
|
+
parsed = new URL(`https://${redirectHost}`);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
throw new WeixinError("validation", `scaned_but_redirect 非法 redirect_host: ${redirectHost}`);
|
|
116
|
+
}
|
|
117
|
+
if (parsed.hostname !== redirectHost.toLowerCase() ||
|
|
118
|
+
parsed.pathname !== "/" ||
|
|
119
|
+
parsed.port !== "" ||
|
|
120
|
+
parsed.username !== "" ||
|
|
121
|
+
parsed.password !== "") {
|
|
122
|
+
throw new WeixinError("validation", `scaned_but_redirect redirect_host 必须是无 scheme/path/port 的纯 hostname`);
|
|
123
|
+
}
|
|
124
|
+
return { state: "scaned_but_redirect", baseUrl: `https://${redirectHost}` };
|
|
125
|
+
}
|
|
126
|
+
if (status === "wait" || status === "scaned" || status === "expired") {
|
|
127
|
+
return { state: status };
|
|
128
|
+
}
|
|
129
|
+
throw new WeixinError("validation", `get_qrcode_status 未知状态 "${status}"`);
|
|
130
|
+
}
|
|
131
|
+
/** POST ilink/bot/getupdates(长轮询)→ 消息 + 新游标。 */
|
|
132
|
+
async getUpdates(syncBuf, credentials, signal) {
|
|
133
|
+
const body = (await this.request("POST", EP_GET_UPDATES, {
|
|
134
|
+
timeoutMs: LONG_POLL_DEFAULT_MS + 20_000, // 长轮询需覆盖服务端 longpoll
|
|
135
|
+
payload: {
|
|
136
|
+
get_updates_buf: syncBuf,
|
|
137
|
+
base_info: baseInfoOf(credentials),
|
|
138
|
+
},
|
|
139
|
+
...(signal !== undefined ? { signal } : {}),
|
|
140
|
+
}));
|
|
141
|
+
assertOk(body, "getupdates");
|
|
142
|
+
const messages = [];
|
|
143
|
+
for (const raw of Array.isArray(body.msgs) ? body.msgs : []) {
|
|
144
|
+
if (!isRecord(raw))
|
|
145
|
+
continue;
|
|
146
|
+
const text = extractText(raw);
|
|
147
|
+
const message = {
|
|
148
|
+
account: String(raw.from_user_id ?? ""),
|
|
149
|
+
from_user_id: String(raw.from_user_id ?? ""),
|
|
150
|
+
message_id: String(raw.message_id ?? ""),
|
|
151
|
+
text,
|
|
152
|
+
context_token: String(raw.context_token ?? ""),
|
|
153
|
+
};
|
|
154
|
+
messages.push(message);
|
|
155
|
+
}
|
|
156
|
+
const nextSyncBuf = String(body.get_updates_buf ?? syncBuf);
|
|
157
|
+
const longpoll = Number(body.longpolling_timeout_ms ?? LONG_POLL_DEFAULT_MS);
|
|
158
|
+
return {
|
|
159
|
+
messages,
|
|
160
|
+
next_sync_buf: nextSyncBuf,
|
|
161
|
+
longpoll_timeout_ms: Number.isFinite(longpoll) && longpoll > 0 ? longpoll : LONG_POLL_DEFAULT_MS,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/** POST ilink/bot/sendmessage → 回显 context_token(协议要求)。 */
|
|
165
|
+
async sendMessage(input, credentials) {
|
|
166
|
+
const message = {
|
|
167
|
+
from_user_id: "",
|
|
168
|
+
to_user_id: input.to_user_id,
|
|
169
|
+
client_id: this.clientId,
|
|
170
|
+
message_type: MSG_TYPE_BOT,
|
|
171
|
+
message_state: MSG_STATE_FINISH,
|
|
172
|
+
item_list: [{ type: ITEM_TEXT, text_item: { text: input.text } }],
|
|
173
|
+
};
|
|
174
|
+
if (input.context_token !== undefined && input.context_token !== "") {
|
|
175
|
+
message.context_token = input.context_token;
|
|
176
|
+
}
|
|
177
|
+
const body = (await this.request("POST", EP_SEND_MESSAGE, {
|
|
178
|
+
timeoutMs: API_TIMEOUT_MS,
|
|
179
|
+
payload: { msg: message, base_info: baseInfoOf(credentials) },
|
|
180
|
+
}));
|
|
181
|
+
assertOk(body, "sendmessage");
|
|
182
|
+
}
|
|
183
|
+
// ── 内部:统一请求(safe-http 纪律)──────────────────────────────────
|
|
184
|
+
async request(method, path, options) {
|
|
185
|
+
const controller = new AbortController();
|
|
186
|
+
const timer = setTimeout(() => controller.abort(), options.timeoutMs);
|
|
187
|
+
// 审查 P2-M:外部 abort(通道 stop())转发到内部 controller——长轮询
|
|
188
|
+
// 在飞时 Ctrl+C 不再等满轮询超时(此前 pollAbort 声明后从未赋值,
|
|
189
|
+
// stop() 的 abort 是空操作,进程退出卡 ~55s)。
|
|
190
|
+
const onExternalAbort = () => controller.abort();
|
|
191
|
+
options.signal?.addEventListener("abort", onExternalAbort, { once: true });
|
|
192
|
+
try {
|
|
193
|
+
const headers = {
|
|
194
|
+
"iLink-App-Id": ILINK_APP_ID,
|
|
195
|
+
"iLink-App-ClientVersion": ILINK_APP_CLIENT_VERSION,
|
|
196
|
+
};
|
|
197
|
+
const init = {
|
|
198
|
+
method,
|
|
199
|
+
headers,
|
|
200
|
+
signal: controller.signal,
|
|
201
|
+
redirect: "manual",
|
|
202
|
+
};
|
|
203
|
+
if (options.payload !== undefined) {
|
|
204
|
+
headers["content-type"] = "application/json";
|
|
205
|
+
init.body = JSON.stringify(options.payload);
|
|
206
|
+
}
|
|
207
|
+
const response = await fetch(`${this.baseUrl}/${path}`, init);
|
|
208
|
+
if (isRedirectResponse(response)) {
|
|
209
|
+
throw new WeixinError("redirect", `iLink ${path} 重定向被拒绝 (${response.status})`);
|
|
210
|
+
}
|
|
211
|
+
if (!response.ok) {
|
|
212
|
+
throw new WeixinError("http", `iLink ${path} HTTP ${response.status}`);
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
return await readJsonBody(response, { maxBytes: MAX_RESPONSE_BYTES, signal: controller.signal });
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
if (err instanceof SafeHttpError) {
|
|
219
|
+
if (err.code === "response_too_large")
|
|
220
|
+
throw new WeixinError("response_too_large", err.message);
|
|
221
|
+
if (err.code === "invalid_json")
|
|
222
|
+
throw new WeixinError("invalid_json", err.message);
|
|
223
|
+
}
|
|
224
|
+
throw err;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
if (err instanceof WeixinError)
|
|
229
|
+
throw err;
|
|
230
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
231
|
+
throw new WeixinError("timeout", `iLink ${path} 超时 (${options.timeoutMs}ms)`);
|
|
232
|
+
}
|
|
233
|
+
throw new WeixinError("network", `iLink ${path} 网络错误: ${err instanceof Error ? err.message : String(err)}`);
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
clearTimeout(timer);
|
|
237
|
+
options.signal?.removeEventListener("abort", onExternalAbort);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/** base_info 身份三件套(getupdates/sendmessage 通用)。 */
|
|
242
|
+
function baseInfoOf(credentials) {
|
|
243
|
+
return {
|
|
244
|
+
ilink_bot_id: credentials.ilink_bot_id,
|
|
245
|
+
bot_token: credentials.bot_token,
|
|
246
|
+
ilink_user_id: credentials.ilink_user_id,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/** 解析 ret/errcode(-14 / -2+"unknown error" → session_stale;其余 → protocol)。 */
|
|
250
|
+
function assertOk(body, endpoint) {
|
|
251
|
+
const ret = body.ret;
|
|
252
|
+
const errcode = body.errcode;
|
|
253
|
+
if (ret === undefined && errcode === undefined)
|
|
254
|
+
return;
|
|
255
|
+
const retNum = Number(ret ?? 0);
|
|
256
|
+
const errNum = Number(errcode ?? 0);
|
|
257
|
+
if (retNum === 0 && errNum === 0)
|
|
258
|
+
return;
|
|
259
|
+
if (retNum === SESSION_EXPIRED_ERRCODE || errNum === SESSION_EXPIRED_ERRCODE) {
|
|
260
|
+
throw new WeixinError("session_stale", `${endpoint}: 会话过期 (ret=${ret}, errcode=${errcode})`);
|
|
261
|
+
}
|
|
262
|
+
if ((retNum === RATE_LIMIT_ERRCODE || errNum === RATE_LIMIT_ERRCODE) &&
|
|
263
|
+
String(body.errmsg ?? "").toLowerCase() === "unknown error") {
|
|
264
|
+
throw new WeixinError("session_stale", `${endpoint}: 会话过期 (ret=-2 unknown error)`);
|
|
265
|
+
}
|
|
266
|
+
throw new WeixinError("protocol", `${endpoint}: ret=${ret} errcode=${errcode} errmsg=${String(body.errmsg ?? "")}`);
|
|
267
|
+
}
|
|
268
|
+
/** 从 item_list 提取文本(type:1 的 text_item.text;非文本消息返回空串)。 */
|
|
269
|
+
function extractText(raw) {
|
|
270
|
+
const items = raw.item_list;
|
|
271
|
+
if (!Array.isArray(items))
|
|
272
|
+
return "";
|
|
273
|
+
for (const item of items) {
|
|
274
|
+
if (!isRecord(item))
|
|
275
|
+
continue;
|
|
276
|
+
if (item.type === ITEM_TEXT) {
|
|
277
|
+
const textItem = item.text_item;
|
|
278
|
+
if (isRecord(textItem) && typeof textItem.text === "string")
|
|
279
|
+
return textItem.text;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return "";
|
|
283
|
+
}
|
|
284
|
+
function isRecord(v) {
|
|
285
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=ilink-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ilink-client.js","sourceRoot":"","sources":["../../src/weixin/ilink-client.ts"],"names":[],"mappings":"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"}
|