@henryxiaoyang/wechat-access-unqclawed 1.1.0-beta.3 → 1.1.0-beta.30
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/index.js +526 -868
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7890,6 +7890,7 @@ var CentrifugeGatewayClient = class _CentrifugeGatewayClient {
|
|
|
7890
7890
|
callbacks;
|
|
7891
7891
|
client = null;
|
|
7892
7892
|
sub = null;
|
|
7893
|
+
extraSubs = [];
|
|
7893
7894
|
state = "disconnected";
|
|
7894
7895
|
processedMsgIds = /* @__PURE__ */ new Set();
|
|
7895
7896
|
static MAX_MSG_ID_CACHE = 1e3;
|
|
@@ -7949,6 +7950,10 @@ var CentrifugeGatewayClient = class _CentrifugeGatewayClient {
|
|
|
7949
7950
|
console.log(`${this.logPrefix} \u6B63\u5728\u505C\u6B62...`);
|
|
7950
7951
|
this.state = "disconnected";
|
|
7951
7952
|
this.processedMsgIds.clear();
|
|
7953
|
+
for (const sub of this.extraSubs) {
|
|
7954
|
+
sub.unsubscribe();
|
|
7955
|
+
}
|
|
7956
|
+
this.extraSubs = [];
|
|
7952
7957
|
if (this.sub) {
|
|
7953
7958
|
this.sub.unsubscribe();
|
|
7954
7959
|
this.sub = null;
|
|
@@ -7963,6 +7968,26 @@ var CentrifugeGatewayClient = class _CentrifugeGatewayClient {
|
|
|
7963
7968
|
setCallbacks = (callbacks) => {
|
|
7964
7969
|
this.callbacks = { ...this.callbacks, ...callbacks };
|
|
7965
7970
|
};
|
|
7971
|
+
/** 订阅额外的 channel(用于 Claw workspace 等,复用同一个 Centrifuge 连接) */
|
|
7972
|
+
subscribeChannel = (channel, subscriptionToken) => {
|
|
7973
|
+
if (!this.client) {
|
|
7974
|
+
console.warn(`${this.logPrefix} \u65E0\u6CD5\u8BA2\u9605: \u5BA2\u6237\u7AEF\u672A\u521D\u59CB\u5316`);
|
|
7975
|
+
return;
|
|
7976
|
+
}
|
|
7977
|
+
console.log(`${this.logPrefix} \u8BA2\u9605\u989D\u5916 channel: ${channel}`);
|
|
7978
|
+
const sub = this.client.newSubscription(channel, { token: subscriptionToken });
|
|
7979
|
+
sub.on("publication", (ctx) => {
|
|
7980
|
+
this.handlePublication(ctx.data);
|
|
7981
|
+
});
|
|
7982
|
+
sub.on("error", (ctx) => {
|
|
7983
|
+
console.error(`${this.logPrefix} \u989D\u5916\u8BA2\u9605\u9519\u8BEF (${channel}): ${ctx.error.message}`);
|
|
7984
|
+
});
|
|
7985
|
+
sub.on("subscribed", () => {
|
|
7986
|
+
console.log(`${this.logPrefix} \u989D\u5916 channel \u5DF2\u8BA2\u9605: ${channel}`);
|
|
7987
|
+
});
|
|
7988
|
+
this.extraSubs.push(sub);
|
|
7989
|
+
sub.subscribe();
|
|
7990
|
+
};
|
|
7966
7991
|
// ============================================
|
|
7967
7992
|
// 上行消息发送(与 WechatAccessWebSocketClient 同签名)
|
|
7968
7993
|
// ============================================
|
|
@@ -7994,6 +8019,41 @@ var CentrifugeGatewayClient = class _CentrifugeGatewayClient {
|
|
|
7994
8019
|
this.sendEnvelope("session.update", payload, guid, userId);
|
|
7995
8020
|
};
|
|
7996
8021
|
sendPromptResponse = (payload, guid, userId) => {
|
|
8022
|
+
if (this.config.httpBaseUrl && this.config.httpAccessToken) {
|
|
8023
|
+
const message = payload.content?.map((c) => c.text).join("") || payload.error || "";
|
|
8024
|
+
const httpPayload = {
|
|
8025
|
+
type: "COPILOT_RESPONSE",
|
|
8026
|
+
msgId: payload.prompt_id,
|
|
8027
|
+
chatId: payload.session_id,
|
|
8028
|
+
// 原始 WeChat KF chatId(从 incoming message 透传)
|
|
8029
|
+
success: payload.stop_reason === "end_turn",
|
|
8030
|
+
message,
|
|
8031
|
+
metadata: {
|
|
8032
|
+
sessionId: this.config.workspaceSessionId || payload.session_id,
|
|
8033
|
+
// workspace sessionId
|
|
8034
|
+
requestId: payload.prompt_id,
|
|
8035
|
+
state: payload.stop_reason === "end_turn" ? "completed" : payload.stop_reason
|
|
8036
|
+
}
|
|
8037
|
+
};
|
|
8038
|
+
const url = `${this.config.httpBaseUrl}/v2/backgroundagent/wecom/local-proxy/receive`;
|
|
8039
|
+
fetch(url, {
|
|
8040
|
+
method: "POST",
|
|
8041
|
+
headers: {
|
|
8042
|
+
"Content-Type": "application/json",
|
|
8043
|
+
"Authorization": `Bearer ${this.config.httpAccessToken}`
|
|
8044
|
+
},
|
|
8045
|
+
body: JSON.stringify(httpPayload),
|
|
8046
|
+
signal: AbortSignal.timeout(3e4)
|
|
8047
|
+
}).then(async (res) => {
|
|
8048
|
+
if (!res.ok) {
|
|
8049
|
+
const body = await res.text().catch(() => "");
|
|
8050
|
+
console.error(`${this.logPrefix} HTTP COPILOT_RESPONSE \u5931\u8D25: ${res.status} ${body.substring(0, 200)}`);
|
|
8051
|
+
}
|
|
8052
|
+
}).catch((err) => {
|
|
8053
|
+
console.error(`${this.logPrefix} HTTP COPILOT_RESPONSE \u5F02\u5E38:`, err);
|
|
8054
|
+
});
|
|
8055
|
+
return;
|
|
8056
|
+
}
|
|
7997
8057
|
this.sendEnvelope("session.promptResponse", payload, guid, userId);
|
|
7998
8058
|
};
|
|
7999
8059
|
// ============================================
|
|
@@ -8001,28 +8061,57 @@ var CentrifugeGatewayClient = class _CentrifugeGatewayClient {
|
|
|
8001
8061
|
// ============================================
|
|
8002
8062
|
handlePublication = (data) => {
|
|
8003
8063
|
try {
|
|
8004
|
-
const
|
|
8005
|
-
if (
|
|
8006
|
-
|
|
8064
|
+
const raw = data;
|
|
8065
|
+
if (raw?.method && raw?.msg_id) {
|
|
8066
|
+
const envelope = raw;
|
|
8067
|
+
if (this.processedMsgIds.has(envelope.msg_id)) {
|
|
8068
|
+
console.log(`${this.logPrefix} \u91CD\u590D\u6D88\u606F\uFF0C\u8DF3\u8FC7: ${envelope.msg_id}`);
|
|
8069
|
+
return;
|
|
8070
|
+
}
|
|
8071
|
+
this.processedMsgIds.add(envelope.msg_id);
|
|
8072
|
+
this.cleanMsgIdCache();
|
|
8073
|
+
console.log(`${this.logPrefix} \u6536\u5230 AGP \u6D88\u606F: method=${envelope.method}, msg_id=${envelope.msg_id}`);
|
|
8074
|
+
switch (envelope.method) {
|
|
8075
|
+
case "session.prompt":
|
|
8076
|
+
this.callbacks.onPrompt?.(envelope);
|
|
8077
|
+
break;
|
|
8078
|
+
case "session.cancel":
|
|
8079
|
+
this.callbacks.onCancel?.(envelope);
|
|
8080
|
+
break;
|
|
8081
|
+
default:
|
|
8082
|
+
console.warn(`${this.logPrefix} \u672A\u77E5 AGP \u6D88\u606F\u7C7B\u578B: ${envelope.method}`);
|
|
8083
|
+
}
|
|
8007
8084
|
return;
|
|
8008
8085
|
}
|
|
8009
|
-
if (
|
|
8010
|
-
|
|
8086
|
+
if (raw?.chatId && raw?.msgId) {
|
|
8087
|
+
const msgId = String(raw.msgId);
|
|
8088
|
+
if (this.processedMsgIds.has(msgId)) {
|
|
8089
|
+
console.log(`${this.logPrefix} \u91CD\u590D\u6D88\u606F\uFF0C\u8DF3\u8FC7: ${msgId}`);
|
|
8090
|
+
return;
|
|
8091
|
+
}
|
|
8092
|
+
this.processedMsgIds.add(msgId);
|
|
8093
|
+
this.cleanMsgIdCache();
|
|
8094
|
+
const content = String(raw.content ?? "");
|
|
8095
|
+
const chatId = String(raw.chatId);
|
|
8096
|
+
console.log(`${this.logPrefix} \u6536\u5230 WeChat KF \u6D88\u606F: msgId=${msgId}, chatId=${chatId}, content=${content.substring(0, 50)}`);
|
|
8097
|
+
const envelope = {
|
|
8098
|
+
msg_id: msgId,
|
|
8099
|
+
guid: this.config.guid,
|
|
8100
|
+
user_id: this.config.userId,
|
|
8101
|
+
method: "session.prompt",
|
|
8102
|
+
payload: {
|
|
8103
|
+
session_id: chatId,
|
|
8104
|
+
prompt_id: msgId,
|
|
8105
|
+
agent_app: "default",
|
|
8106
|
+
content: [{ type: "text", text: content }]
|
|
8107
|
+
}
|
|
8108
|
+
};
|
|
8109
|
+
envelope._wechatKf = { chatId, msgId, raw };
|
|
8110
|
+
this.callbacks.onPrompt?.(envelope);
|
|
8011
8111
|
return;
|
|
8012
8112
|
}
|
|
8013
|
-
|
|
8014
|
-
this.
|
|
8015
|
-
console.log(`${this.logPrefix} \u6536\u5230\u6D88\u606F: method=${envelope.method}, msg_id=${envelope.msg_id}`);
|
|
8016
|
-
switch (envelope.method) {
|
|
8017
|
-
case "session.prompt":
|
|
8018
|
-
this.callbacks.onPrompt?.(envelope);
|
|
8019
|
-
break;
|
|
8020
|
-
case "session.cancel":
|
|
8021
|
-
this.callbacks.onCancel?.(envelope);
|
|
8022
|
-
break;
|
|
8023
|
-
default:
|
|
8024
|
-
console.warn(`${this.logPrefix} \u672A\u77E5\u6D88\u606F\u7C7B\u578B: ${envelope.method}`);
|
|
8025
|
-
}
|
|
8113
|
+
const preview = JSON.stringify(data).substring(0, 500);
|
|
8114
|
+
console.warn(`${this.logPrefix} \u6536\u5230\u672A\u77E5\u683C\u5F0F\u6D88\u606F: ${preview}`);
|
|
8026
8115
|
} catch (error) {
|
|
8027
8116
|
console.error(`${this.logPrefix} \u6D88\u606F\u5904\u7406\u5931\u8D25:`, error);
|
|
8028
8117
|
this.callbacks.onError?.(
|
|
@@ -8170,6 +8259,8 @@ var buildMessageContext = (message) => {
|
|
|
8170
8259
|
// 原始消息内容
|
|
8171
8260
|
CommandBody: content,
|
|
8172
8261
|
// 命令体(用于解析命令)
|
|
8262
|
+
CommandAuthorized: true,
|
|
8263
|
+
// 自建通道,所有命令均授权
|
|
8173
8264
|
From: `wechat-access:${userId}`,
|
|
8174
8265
|
// 发送者标识(带频道前缀)
|
|
8175
8266
|
To: `wechat-access:${toUser}`,
|
|
@@ -8335,7 +8426,7 @@ var handlePrompt = async (message, client) => {
|
|
|
8335
8426
|
route.agentId
|
|
8336
8427
|
);
|
|
8337
8428
|
let finalText = null;
|
|
8338
|
-
|
|
8429
|
+
await runtime2.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
8339
8430
|
ctx,
|
|
8340
8431
|
cfg,
|
|
8341
8432
|
dispatcherOptions: {
|
|
@@ -8615,7 +8706,8 @@ var QClawAPI = class {
|
|
|
8615
8706
|
};
|
|
8616
8707
|
|
|
8617
8708
|
// auth/codebuddy-api.ts
|
|
8618
|
-
import { hostname } from "os";
|
|
8709
|
+
import { hostname, homedir as homedir2 } from "os";
|
|
8710
|
+
import { join as join2 } from "path";
|
|
8619
8711
|
var DEFAULT_BASE_URL = "https://copilot.tencent.com";
|
|
8620
8712
|
var PREFIX_PATH = "/plugin";
|
|
8621
8713
|
var PLATFORM = "ide";
|
|
@@ -8759,9 +8851,10 @@ var CodeBuddyAPI = class {
|
|
|
8759
8851
|
throw new Error("refreshToken: missing accessToken in response");
|
|
8760
8852
|
}
|
|
8761
8853
|
// ==================== WeChat KF 绑定 ====================
|
|
8762
|
-
/** 构造 sessionId */
|
|
8763
|
-
buildSessionId(workspacePath
|
|
8764
|
-
|
|
8854
|
+
/** 构造 sessionId(与 WorkBuddy 保持一致) */
|
|
8855
|
+
buildSessionId(workspacePath) {
|
|
8856
|
+
const wp = workspacePath ?? join2(homedir2(), "WorkBuddy", "Claw");
|
|
8857
|
+
return `${this.userId}_${this.hostId}_${wp}`;
|
|
8765
8858
|
}
|
|
8766
8859
|
/** 获取微信客服绑定链接 (QR 码) */
|
|
8767
8860
|
async wechatkfGetLink(sessionId, userId) {
|
|
@@ -8867,153 +8960,30 @@ var CodeBuddyAPI = class {
|
|
|
8867
8960
|
}
|
|
8868
8961
|
};
|
|
8869
8962
|
|
|
8870
|
-
// auth/state-store.ts
|
|
8871
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2, chmodSync } from "fs";
|
|
8872
|
-
import { join as join2, dirname as dirname2 } from "path";
|
|
8873
|
-
import { homedir as homedir2 } from "os";
|
|
8874
|
-
var DEFAULT_STATE_PATH = join2(homedir2(), ".openclaw", "wechat-access-auth.json");
|
|
8875
|
-
var loadState = () => {
|
|
8876
|
-
try {
|
|
8877
|
-
const raw = readFileSync2(DEFAULT_STATE_PATH, "utf-8");
|
|
8878
|
-
return JSON.parse(raw);
|
|
8879
|
-
} catch {
|
|
8880
|
-
return null;
|
|
8881
|
-
}
|
|
8882
|
-
};
|
|
8883
|
-
var saveState = (state) => {
|
|
8884
|
-
mkdirSync2(dirname2(DEFAULT_STATE_PATH), { recursive: true });
|
|
8885
|
-
writeFileSync2(DEFAULT_STATE_PATH, JSON.stringify(state, null, 2), { encoding: "utf-8", mode: 384 });
|
|
8886
|
-
try {
|
|
8887
|
-
chmodSync(DEFAULT_STATE_PATH, 384);
|
|
8888
|
-
} catch {
|
|
8889
|
-
}
|
|
8890
|
-
};
|
|
8891
|
-
var clearState = () => {
|
|
8892
|
-
try {
|
|
8893
|
-
unlinkSync(DEFAULT_STATE_PATH);
|
|
8894
|
-
} catch {
|
|
8895
|
-
}
|
|
8896
|
-
};
|
|
8897
|
-
|
|
8898
8963
|
// auth/wechat-login.ts
|
|
8899
|
-
import {
|
|
8900
|
-
import { join as join3 } from "path";
|
|
8901
|
-
import { homedir as homedir3 } from "os";
|
|
8964
|
+
import { createInterface } from "readline";
|
|
8902
8965
|
|
|
8903
|
-
// auth/
|
|
8904
|
-
var
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
} catch {
|
|
8915
|
-
}
|
|
8916
|
-
}
|
|
8917
|
-
async function performDeviceBinding(options) {
|
|
8918
|
-
const {
|
|
8919
|
-
api,
|
|
8920
|
-
openKfId = DEFAULT_OPEN_KFID,
|
|
8921
|
-
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
8922
|
-
log: log2 = { info: console.log, warn: console.warn, error: console.error },
|
|
8923
|
-
showQr = defaultShowQr
|
|
8924
|
-
} = options;
|
|
8925
|
-
log2.info("[device-bind] \u751F\u6210\u4F01\u5FAE\u5BA2\u670D\u94FE\u63A5...");
|
|
8926
|
-
let linkResult;
|
|
8927
|
-
try {
|
|
8928
|
-
linkResult = await api.generateContactLink(openKfId);
|
|
8929
|
-
} catch (e) {
|
|
8930
|
-
const msg = `\u751F\u6210\u5BA2\u670D\u94FE\u63A5\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`;
|
|
8931
|
-
log2.warn(`[device-bind] ${msg}`);
|
|
8932
|
-
return { success: false, message: msg };
|
|
8933
|
-
}
|
|
8934
|
-
if (!linkResult.success) {
|
|
8935
|
-
const msg = `\u751F\u6210\u5BA2\u670D\u94FE\u63A5\u5931\u8D25: ${linkResult.message ?? "\u672A\u77E5\u9519\u8BEF"}`;
|
|
8936
|
-
log2.warn(`[device-bind] ${msg}`);
|
|
8937
|
-
return { success: false, message: msg };
|
|
8938
|
-
}
|
|
8939
|
-
const linkData = linkResult.data;
|
|
8940
|
-
const contactUrl = nested(linkData, "url") || nested(linkData, "data", "url") || nested(linkData, "resp", "url") || "";
|
|
8941
|
-
if (!contactUrl) {
|
|
8942
|
-
const msg = "\u670D\u52A1\u7AEF\u672A\u8FD4\u56DE\u5BA2\u670D\u94FE\u63A5 URL";
|
|
8943
|
-
log2.warn(`[device-bind] ${msg}`);
|
|
8944
|
-
return { success: false, message: msg };
|
|
8945
|
-
}
|
|
8946
|
-
console.log("");
|
|
8947
|
-
console.log("=".repeat(64));
|
|
8948
|
-
console.log("\u8BF7\u7528\u300C\u63A7\u5236\u7AEF\u5FAE\u4FE1\u300D\u6253\u5F00\u4E0B\u65B9\u94FE\u63A5\uFF0C\u5B8C\u6210\u8BBE\u5907\u7ED1\u5B9A");
|
|
8949
|
-
console.log("\u7ED1\u5B9A\u540E\u5FAE\u4FE1\u4E2D\u4F1A\u51FA\u73B0\u5BF9\u8BDD\u5165\u53E3");
|
|
8950
|
-
console.log("=".repeat(64));
|
|
8951
|
-
await showQr(contactUrl);
|
|
8952
|
-
console.log(`
|
|
8953
|
-
\u94FE\u63A5: ${contactUrl}
|
|
8954
|
-
`);
|
|
8955
|
-
log2.info("[device-bind] \u7B49\u5F85\u8BBE\u5907\u7ED1\u5B9A...");
|
|
8956
|
-
const deadline = Date.now() + timeoutMs;
|
|
8957
|
-
while (Date.now() < deadline) {
|
|
8958
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
8959
|
-
try {
|
|
8960
|
-
const status = await api.queryDeviceByGuid();
|
|
8961
|
-
if (status.success) {
|
|
8962
|
-
const sd = status.data;
|
|
8963
|
-
const nickname = nested(sd, "nickname") || nested(sd, "data", "nickname");
|
|
8964
|
-
const externalUserId = nested(sd, "external_user_id") || nested(sd, "data", "external_user_id");
|
|
8965
|
-
if (nickname || externalUserId) {
|
|
8966
|
-
const msg = `\u8BBE\u5907\u7ED1\u5B9A\u6210\u529F!${nickname ? ` \u5FAE\u4FE1\u6635\u79F0: ${nickname}` : ""}`;
|
|
8967
|
-
log2.info(`[device-bind] ${msg}`);
|
|
8968
|
-
return { success: true, contactUrl, nickname: nickname || void 0, message: msg };
|
|
8969
|
-
}
|
|
8970
|
-
}
|
|
8971
|
-
} catch {
|
|
8972
|
-
}
|
|
8973
|
-
}
|
|
8974
|
-
return {
|
|
8975
|
-
success: false,
|
|
8976
|
-
contactUrl,
|
|
8977
|
-
message: "\u8BBE\u5907\u7ED1\u5B9A\u8D85\u65F6\u3002\u8BF7\u786E\u8BA4\u5DF2\u5728\u5FAE\u4FE1\u4E2D\u6253\u5F00\u4E0A\u65B9\u94FE\u63A5\uFF0C\u7136\u540E\u91CD\u542F Gateway \u91CD\u8BD5\u3002"
|
|
8978
|
-
};
|
|
8979
|
-
}
|
|
8966
|
+
// auth/wechat-qr-poll.ts
|
|
8967
|
+
var buildAuthUrl = (state, env) => {
|
|
8968
|
+
const params = new URLSearchParams({
|
|
8969
|
+
appid: env.wxAppId,
|
|
8970
|
+
redirect_uri: env.wxLoginRedirectUri,
|
|
8971
|
+
response_type: "code",
|
|
8972
|
+
scope: "snsapi_login",
|
|
8973
|
+
state
|
|
8974
|
+
});
|
|
8975
|
+
return `https://open.weixin.qq.com/connect/qrconnect?${params.toString()}#wechat_redirect`;
|
|
8976
|
+
};
|
|
8980
8977
|
|
|
8981
8978
|
// auth/wechat-login.ts
|
|
8982
|
-
var
|
|
8983
|
-
const
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
log2("\u9700\u8981\u9080\u8BF7\u7801\u9A8C\u8BC1\u3002\u8BF7\u5728\u53E6\u4E00\u4E2A\u7EC8\u7AEF\u6267\u884C\uFF1A");
|
|
8991
|
-
log2("");
|
|
8992
|
-
log2(`echo "\u4F60\u7684\u9080\u8BF7\u7801" > ${inviteCodeTmpFile}`);
|
|
8993
|
-
log2("");
|
|
8994
|
-
log2("\u6CA1\u6709\u9080\u8BF7\u7801\uFF1F\u76F4\u63A5\u6267\u884C\uFF1A");
|
|
8995
|
-
log2(`echo "" > ${inviteCodeTmpFile}`);
|
|
8996
|
-
log2("");
|
|
8997
|
-
log2("\u672C\u7A97\u53E3\u4F1A\u81EA\u52A8\u68C0\u6D4B\u5E76\u7EE7\u7EED\u3002");
|
|
8998
|
-
log2("=".repeat(64));
|
|
8999
|
-
const deadline = Date.now() + 3e5;
|
|
9000
|
-
while (Date.now() < deadline) {
|
|
9001
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
9002
|
-
if (!existsSync(inviteCodeTmpFile)) continue;
|
|
9003
|
-
let raw = "";
|
|
9004
|
-
try {
|
|
9005
|
-
raw = readFileSync3(inviteCodeTmpFile, "utf-8").trim();
|
|
9006
|
-
unlinkSync2(inviteCodeTmpFile);
|
|
9007
|
-
} catch {
|
|
9008
|
-
continue;
|
|
9009
|
-
}
|
|
9010
|
-
return raw;
|
|
9011
|
-
}
|
|
9012
|
-
try {
|
|
9013
|
-
unlinkSync2(inviteCodeTmpFile);
|
|
9014
|
-
} catch {
|
|
9015
|
-
}
|
|
9016
|
-
return "";
|
|
8979
|
+
var askInput = (prompt) => {
|
|
8980
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
8981
|
+
return new Promise((resolve) => {
|
|
8982
|
+
rl.question(prompt, (answer) => {
|
|
8983
|
+
rl.close();
|
|
8984
|
+
resolve(answer.trim());
|
|
8985
|
+
});
|
|
8986
|
+
});
|
|
9017
8987
|
};
|
|
9018
8988
|
var performLogin = async (options) => {
|
|
9019
8989
|
const { guid, env, inviteCode, log: log2 } = options;
|
|
@@ -9028,66 +8998,44 @@ var performLogin = async (options) => {
|
|
|
9028
8998
|
if (s) state = s;
|
|
9029
8999
|
}
|
|
9030
9000
|
info(`[Login] state=${state}`);
|
|
9031
|
-
info("[Login] \u6B65\u9AA4 2/5: \u8BF7\
|
|
9032
|
-
|
|
9033
|
-
const codeTmpFile = join3(homedir3(), ".openclaw", "wechat-auth-code.tmp");
|
|
9034
|
-
try {
|
|
9035
|
-
unlinkSync2(codeTmpFile);
|
|
9036
|
-
} catch {
|
|
9037
|
-
}
|
|
9001
|
+
info("[Login] \u6B65\u9AA4 2/5: \u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u4EE5\u4E0B\u94FE\u63A5\u626B\u7801\u767B\u5F55...");
|
|
9002
|
+
const authUrl = buildAuthUrl(state, env);
|
|
9038
9003
|
info("");
|
|
9039
9004
|
info("=".repeat(64));
|
|
9040
|
-
info("\
|
|
9041
|
-
info("\u5730\u5740\u680F URL \u5F62\u5982\uFF1A");
|
|
9005
|
+
info("\u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u4EE5\u4E0B\u94FE\u63A5\uFF0C\u7528\u5FAE\u4FE1\u626B\u7801\u767B\u5F55\uFF1A");
|
|
9042
9006
|
info("");
|
|
9043
|
-
info(
|
|
9007
|
+
info(authUrl);
|
|
9008
|
+
info("=".repeat(64));
|
|
9009
|
+
info("[Login] \u6B65\u9AA4 3/5: \u7B49\u5F85\u5FAE\u4FE1\u626B\u7801\u6388\u6743...");
|
|
9044
9010
|
info("");
|
|
9045
|
-
info("\
|
|
9011
|
+
info("\u626B\u7801\u5E76\u5728\u624B\u673A\u4E0A\u786E\u8BA4\u540E\uFF0C\u6D4F\u89C8\u5668\u4F1A\u8DF3\u8F6C\u5230\u65B0\u9875\u9762\u3002");
|
|
9012
|
+
info("\u5730\u5740\u680F URL \u5F62\u5982\uFF1A");
|
|
9046
9013
|
info("");
|
|
9047
|
-
info(
|
|
9014
|
+
info(" https://security.guanjia.qq.com/login?code=0a1B2c...&state=xxx");
|
|
9048
9015
|
info("");
|
|
9049
|
-
info("\
|
|
9050
|
-
info("\u672C\u7A97\u53E3\u4F1A\u81EA\u52A8\u68C0\u6D4B\u5E76\u5B8C\u6210\u767B\u5F55\u3002");
|
|
9051
|
-
info("=".repeat(64));
|
|
9016
|
+
info("\u8BF7\u590D\u5236 code= \u540E\u9762\u7684\u503C\uFF08\u5230 & \u4E4B\u524D\uFF09\uFF0C\u6216\u76F4\u63A5\u7C98\u8D34\u5B8C\u6574 URL\u3002");
|
|
9052
9017
|
let code = "";
|
|
9053
|
-
const
|
|
9054
|
-
|
|
9055
|
-
|
|
9056
|
-
|
|
9057
|
-
|
|
9018
|
+
const raw = await askInput("\n\u8BF7\u7C98\u8D34 code \u6216\u5B8C\u6574 URL: ");
|
|
9019
|
+
if (!raw) {
|
|
9020
|
+
throw new Error("\u672A\u83B7\u53D6\u5230\u6388\u6743 code");
|
|
9021
|
+
}
|
|
9022
|
+
const cleaned = raw.replace(/\\([?=&#])/g, "$1");
|
|
9023
|
+
if (cleaned.includes("open.weixin.qq.com/connect/qrconnect")) {
|
|
9024
|
+
throw new Error("\u8FD9\u662F\u626B\u7801\u9875\u9762\u7684 URL\uFF0C\u4E0D\u662F\u56DE\u8C03 URL\u3002\u8BF7\u5148\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u6B64\u94FE\u63A5\u626B\u7801\uFF0C\u626B\u7801\u786E\u8BA4\u540E\u6D4F\u89C8\u5668\u4F1A\u8DF3\u8F6C\u5230\u65B0\u9875\u9762\uFF0C\u518D\u590D\u5236\u65B0\u9875\u9762\u5730\u5740\u680F\u4E2D\u7684 URL\u3002");
|
|
9025
|
+
}
|
|
9026
|
+
if (cleaned.includes("code=")) {
|
|
9058
9027
|
try {
|
|
9059
|
-
|
|
9060
|
-
|
|
9028
|
+
const url = new URL(cleaned);
|
|
9029
|
+
const c = url.searchParams.get("code");
|
|
9030
|
+
if (c) code = c;
|
|
9061
9031
|
} catch {
|
|
9062
|
-
|
|
9063
|
-
|
|
9064
|
-
if (!raw) continue;
|
|
9065
|
-
raw = raw.replace(/\\([?=&#])/g, "$1");
|
|
9066
|
-
if (raw.includes("code=")) {
|
|
9067
|
-
try {
|
|
9068
|
-
const url = new URL(raw);
|
|
9069
|
-
const c = url.searchParams.get("code");
|
|
9070
|
-
if (c) {
|
|
9071
|
-
code = c;
|
|
9072
|
-
break;
|
|
9073
|
-
}
|
|
9074
|
-
} catch {
|
|
9075
|
-
const match = raw.match(/[?&#]code=([^&#]+)/);
|
|
9076
|
-
if (match?.[1]) {
|
|
9077
|
-
code = match[1];
|
|
9078
|
-
break;
|
|
9079
|
-
}
|
|
9080
|
-
}
|
|
9032
|
+
const match = cleaned.match(/[?&#]code=([^&#]+)/);
|
|
9033
|
+
if (match?.[1]) code = match[1];
|
|
9081
9034
|
}
|
|
9082
|
-
code = raw;
|
|
9083
|
-
break;
|
|
9084
|
-
}
|
|
9085
|
-
try {
|
|
9086
|
-
unlinkSync2(codeTmpFile);
|
|
9087
|
-
} catch {
|
|
9088
9035
|
}
|
|
9089
|
-
if (!code)
|
|
9090
|
-
|
|
9036
|
+
if (!code) code = cleaned;
|
|
9037
|
+
if (code.startsWith("http")) {
|
|
9038
|
+
throw new Error("\u65E0\u6CD5\u4ECE URL \u4E2D\u63D0\u53D6 code\u3002\u8BF7\u786E\u8BA4\u7C98\u8D34\u7684\u662F\u626B\u7801\u786E\u8BA4\u540E\u8DF3\u8F6C\u9875\u9762\u7684 URL\u3002");
|
|
9091
9039
|
}
|
|
9092
9040
|
info(`[Login] \u6B65\u9AA4 4/5: \u7528\u6388\u6743\u7801\u767B\u5F55 (code=${code.substring(0, 10)}...)`);
|
|
9093
9041
|
const loginResult = await api.wxLogin(code, state);
|
|
@@ -9125,7 +9073,9 @@ var performLogin = async (options) => {
|
|
|
9125
9073
|
if (!verified) {
|
|
9126
9074
|
let codeToSubmit = inviteCode;
|
|
9127
9075
|
if (codeToSubmit === void 0) {
|
|
9128
|
-
|
|
9076
|
+
info("");
|
|
9077
|
+
info("\u9700\u8981\u9080\u8BF7\u7801\u9A8C\u8BC1\u3002\u6CA1\u6709\u9080\u8BF7\u7801\u53EF\u76F4\u63A5\u6309\u56DE\u8F66\u8DF3\u8FC7\u3002");
|
|
9078
|
+
codeToSubmit = await askInput("\u8BF7\u8F93\u5165\u9080\u8BF7\u7801: ");
|
|
9129
9079
|
}
|
|
9130
9080
|
const submitResult = await api.submitInviteCode(userId, codeToSubmit ?? "");
|
|
9131
9081
|
if (!submitResult.success) {
|
|
@@ -9146,109 +9096,123 @@ var performLogin = async (options) => {
|
|
|
9146
9096
|
apiKey,
|
|
9147
9097
|
guid
|
|
9148
9098
|
};
|
|
9149
|
-
|
|
9150
|
-
jwtToken,
|
|
9151
|
-
channelToken,
|
|
9152
|
-
apiKey,
|
|
9153
|
-
guid,
|
|
9154
|
-
userInfo,
|
|
9155
|
-
savedAt: Date.now()
|
|
9156
|
-
};
|
|
9157
|
-
saveState(persistedState);
|
|
9158
|
-
info("[Login] \u767B\u5F55\u6001\u5DF2\u4FDD\u5B58");
|
|
9159
|
-
info("[Login] \u5F00\u59CB\u8BBE\u5907\u7ED1\u5B9A...");
|
|
9160
|
-
const bindResult = await performDeviceBinding({
|
|
9161
|
-
api,
|
|
9162
|
-
log: log2 ?? { info: console.log, warn: console.warn, error: console.error }
|
|
9163
|
-
});
|
|
9164
|
-
if (bindResult.success) {
|
|
9165
|
-
info(`[Login] ${bindResult.message}`);
|
|
9166
|
-
} else {
|
|
9167
|
-
warn(`[Login] ${bindResult.message}`);
|
|
9168
|
-
warn("[Login] \u53EF\u7A0D\u540E\u91CD\u65B0\u6267\u884C\u767B\u5F55\u547D\u4EE4\u5B8C\u6210\u7ED1\u5B9A\u3002");
|
|
9169
|
-
}
|
|
9099
|
+
info("[Login] \u767B\u5F55\u5B8C\u6210");
|
|
9170
9100
|
return credentials;
|
|
9171
9101
|
};
|
|
9172
9102
|
|
|
9173
|
-
// auth/
|
|
9174
|
-
var
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
response_type: "code",
|
|
9179
|
-
scope: "snsapi_login",
|
|
9180
|
-
state
|
|
9181
|
-
});
|
|
9182
|
-
return `https://open.weixin.qq.com/connect/qrconnect?${params.toString()}#wechat_redirect`;
|
|
9183
|
-
};
|
|
9184
|
-
var fetchQrUuid = async (authUrl) => {
|
|
9185
|
-
const res = await fetch(authUrl, {
|
|
9186
|
-
headers: {
|
|
9187
|
-
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
|
9188
|
-
},
|
|
9189
|
-
signal: AbortSignal.timeout(15e3)
|
|
9190
|
-
});
|
|
9191
|
-
const html = await res.text();
|
|
9192
|
-
const match = html.match(/\/connect\/qrcode\/([a-zA-Z0-9_=-]+)/);
|
|
9193
|
-
if (!match?.[1]) {
|
|
9194
|
-
throw new Error("\u65E0\u6CD5\u4ECE\u5FAE\u4FE1\u767B\u5F55\u9875\u9762\u63D0\u53D6 QR UUID");
|
|
9195
|
-
}
|
|
9196
|
-
return match[1];
|
|
9197
|
-
};
|
|
9198
|
-
var fetchQrImageDataUrl = async (uuid) => {
|
|
9199
|
-
const url = `https://open.weixin.qq.com/connect/qrcode/${uuid}`;
|
|
9200
|
-
const res = await fetch(url, { signal: AbortSignal.timeout(15e3) });
|
|
9201
|
-
const buf = Buffer.from(await res.arrayBuffer());
|
|
9202
|
-
const contentType = res.headers.get("content-type") || "image/png";
|
|
9203
|
-
return `data:${contentType};base64,${buf.toString("base64")}`;
|
|
9204
|
-
};
|
|
9205
|
-
var pollQrStatus = async (uuid) => {
|
|
9103
|
+
// auth/device-bind.ts
|
|
9104
|
+
var DEFAULT_OPEN_KFID = "wkzLlJLAAAfbxEV3ZcS-lHZxkaKmpejQ";
|
|
9105
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
9106
|
+
var DEFAULT_TIMEOUT_MS = 3e5;
|
|
9107
|
+
async function defaultShowQr(url) {
|
|
9206
9108
|
try {
|
|
9207
|
-
const
|
|
9208
|
-
const
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
},
|
|
9212
|
-
signal: AbortSignal.timeout(3e4)
|
|
9109
|
+
const qrterm = await import("qrcode-terminal");
|
|
9110
|
+
const generate = qrterm.default?.generate ?? qrterm.generate;
|
|
9111
|
+
generate(url, { small: true }, (qrcode) => {
|
|
9112
|
+
console.log(qrcode);
|
|
9213
9113
|
});
|
|
9214
|
-
const text = await res.text();
|
|
9215
|
-
const errMatch = text.match(/wx_errcode=(\d+)/);
|
|
9216
|
-
const codeMatch = text.match(/wx_code='([^']*)'/);
|
|
9217
|
-
const errCode = errMatch ? parseInt(errMatch[1], 10) : 0;
|
|
9218
|
-
const wxCode = codeMatch?.[1] || "";
|
|
9219
|
-
if (errCode === 408) return { status: "waiting" };
|
|
9220
|
-
if (errCode === 404) return { status: "scanned" };
|
|
9221
|
-
if (errCode === 405 && wxCode) return { status: "confirmed", code: wxCode };
|
|
9222
|
-
if (errCode === 403 || errCode === 402) return { status: "expired" };
|
|
9223
|
-
return { status: "error" };
|
|
9224
9114
|
} catch {
|
|
9225
|
-
return { status: "error" };
|
|
9226
9115
|
}
|
|
9227
|
-
}
|
|
9116
|
+
}
|
|
9117
|
+
async function performDeviceBinding(options) {
|
|
9118
|
+
const {
|
|
9119
|
+
api,
|
|
9120
|
+
openKfId = DEFAULT_OPEN_KFID,
|
|
9121
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
9122
|
+
log: log2 = { info: console.log, warn: console.warn, error: console.error },
|
|
9123
|
+
showQr = defaultShowQr
|
|
9124
|
+
} = options;
|
|
9125
|
+
log2.info("[device-bind] \u751F\u6210\u4F01\u5FAE\u5BA2\u670D\u94FE\u63A5...");
|
|
9126
|
+
let linkResult;
|
|
9127
|
+
try {
|
|
9128
|
+
linkResult = await api.generateContactLink(openKfId);
|
|
9129
|
+
} catch (e) {
|
|
9130
|
+
const msg = `\u751F\u6210\u5BA2\u670D\u94FE\u63A5\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`;
|
|
9131
|
+
log2.warn(`[device-bind] ${msg}`);
|
|
9132
|
+
return { success: false, message: msg };
|
|
9133
|
+
}
|
|
9134
|
+
if (!linkResult.success) {
|
|
9135
|
+
const msg = `\u751F\u6210\u5BA2\u670D\u94FE\u63A5\u5931\u8D25: ${linkResult.message ?? "\u672A\u77E5\u9519\u8BEF"}`;
|
|
9136
|
+
log2.warn(`[device-bind] ${msg}`);
|
|
9137
|
+
return { success: false, message: msg };
|
|
9138
|
+
}
|
|
9139
|
+
const linkData = linkResult.data;
|
|
9140
|
+
const contactUrl = nested(linkData, "url") || nested(linkData, "data", "url") || nested(linkData, "resp", "url") || "";
|
|
9141
|
+
if (!contactUrl) {
|
|
9142
|
+
const msg = "\u670D\u52A1\u7AEF\u672A\u8FD4\u56DE\u5BA2\u670D\u94FE\u63A5 URL";
|
|
9143
|
+
log2.warn(`[device-bind] ${msg}`);
|
|
9144
|
+
return { success: false, message: msg };
|
|
9145
|
+
}
|
|
9146
|
+
console.log("");
|
|
9147
|
+
console.log("=".repeat(64));
|
|
9148
|
+
console.log("\u8BF7\u7528\u300C\u63A7\u5236\u7AEF\u5FAE\u4FE1\u300D\u6253\u5F00\u4E0B\u65B9\u94FE\u63A5\uFF0C\u5B8C\u6210\u8BBE\u5907\u7ED1\u5B9A");
|
|
9149
|
+
console.log("\u7ED1\u5B9A\u540E\u5FAE\u4FE1\u4E2D\u4F1A\u51FA\u73B0\u5BF9\u8BDD\u5165\u53E3");
|
|
9150
|
+
console.log("=".repeat(64));
|
|
9151
|
+
await showQr(contactUrl);
|
|
9152
|
+
console.log(`
|
|
9153
|
+
\u94FE\u63A5: ${contactUrl}
|
|
9154
|
+
`);
|
|
9155
|
+
log2.info("[device-bind] \u7B49\u5F85\u8BBE\u5907\u7ED1\u5B9A...");
|
|
9156
|
+
const deadline = Date.now() + timeoutMs;
|
|
9157
|
+
while (Date.now() < deadline) {
|
|
9158
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
9159
|
+
try {
|
|
9160
|
+
const status = await api.queryDeviceByGuid();
|
|
9161
|
+
if (status.success) {
|
|
9162
|
+
const sd = status.data;
|
|
9163
|
+
const nickname = nested(sd, "nickname") || nested(sd, "data", "nickname");
|
|
9164
|
+
const externalUserId = nested(sd, "external_user_id") || nested(sd, "data", "external_user_id");
|
|
9165
|
+
if (nickname || externalUserId) {
|
|
9166
|
+
const msg = `\u8BBE\u5907\u7ED1\u5B9A\u6210\u529F!${nickname ? ` \u5FAE\u4FE1\u6635\u79F0: ${nickname}` : ""}`;
|
|
9167
|
+
log2.info(`[device-bind] ${msg}`);
|
|
9168
|
+
return { success: true, contactUrl, nickname: nickname || void 0, message: msg };
|
|
9169
|
+
}
|
|
9170
|
+
}
|
|
9171
|
+
} catch {
|
|
9172
|
+
}
|
|
9173
|
+
}
|
|
9174
|
+
return {
|
|
9175
|
+
success: false,
|
|
9176
|
+
contactUrl,
|
|
9177
|
+
message: "\u8BBE\u5907\u7ED1\u5B9A\u8D85\u65F6\u3002\u8BF7\u786E\u8BA4\u5DF2\u5728\u5FAE\u4FE1\u4E2D\u6253\u5F00\u4E0A\u65B9\u94FE\u63A5\uFF0C\u7136\u540E\u91CD\u542F Gateway \u91CD\u8BD5\u3002"
|
|
9178
|
+
};
|
|
9179
|
+
}
|
|
9228
9180
|
|
|
9229
9181
|
// index.ts
|
|
9182
|
+
import { join as join3 } from "path";
|
|
9183
|
+
import { homedir as homedir3 } from "os";
|
|
9230
9184
|
var wsClients = /* @__PURE__ */ new Map();
|
|
9231
|
-
var
|
|
9185
|
+
var wbHttpContext = null;
|
|
9186
|
+
var lastChatIdByTo = /* @__PURE__ */ new Map();
|
|
9187
|
+
function readChannelConfig() {
|
|
9188
|
+
const runtime2 = getWecomRuntime();
|
|
9189
|
+
const cfg = runtime2.config.loadConfig();
|
|
9190
|
+
return cfg?.channels?.["wechat-access-unqclawed"] ?? {};
|
|
9191
|
+
}
|
|
9192
|
+
async function writeChannelConfig(update) {
|
|
9193
|
+
const runtime2 = getWecomRuntime();
|
|
9194
|
+
const cfg = runtime2.config.loadConfig();
|
|
9195
|
+
const channels = { ...cfg.channels ?? {} };
|
|
9196
|
+
channels["wechat-access-unqclawed"] = {
|
|
9197
|
+
...channels["wechat-access-unqclawed"] ?? {},
|
|
9198
|
+
...update
|
|
9199
|
+
};
|
|
9200
|
+
await runtime2.config.writeConfigFile({ ...cfg, channels });
|
|
9201
|
+
}
|
|
9232
9202
|
var meta = {
|
|
9233
9203
|
id: "wechat-access-unqclawed",
|
|
9234
9204
|
label: "\u817E\u8BAF\u901A\u8DEF",
|
|
9235
|
-
/** 选择时的显示文本 */
|
|
9236
9205
|
selectionLabel: "\u817E\u8BAF\u901A\u8DEF",
|
|
9237
9206
|
detailLabel: "\u817E\u8BAF\u901A\u8DEF",
|
|
9238
|
-
/** 文档路径 */
|
|
9239
9207
|
docsPath: "/channels/wechat-access",
|
|
9240
9208
|
docsLabel: "wechat-access-unqclawed",
|
|
9241
|
-
/** 简介 */
|
|
9242
9209
|
blurb: "\u901A\u7528\u901A\u8DEF",
|
|
9243
|
-
/** 图标 */
|
|
9244
9210
|
systemImage: "message.fill",
|
|
9245
|
-
/** 排序权重 */
|
|
9246
9211
|
order: 85
|
|
9247
9212
|
};
|
|
9248
9213
|
var tencentAccessPlugin = {
|
|
9249
9214
|
id: "wechat-access-unqclawed",
|
|
9250
9215
|
meta,
|
|
9251
|
-
// 能力声明
|
|
9252
9216
|
capabilities: {
|
|
9253
9217
|
chatTypes: ["direct"],
|
|
9254
9218
|
reactions: false,
|
|
@@ -9257,13 +9221,9 @@ var tencentAccessPlugin = {
|
|
|
9257
9221
|
nativeCommands: false,
|
|
9258
9222
|
blockStreaming: false
|
|
9259
9223
|
},
|
|
9260
|
-
// 热重载:token 或 wsUrl 变更时触发 gateway 重启
|
|
9261
9224
|
reload: {
|
|
9262
9225
|
configPrefixes: ["channels.wechat-access-unqclawed.token", "channels.wechat-access-unqclawed.wsUrl"]
|
|
9263
9226
|
},
|
|
9264
|
-
// 声明支持的 gateway 方法(框架通过此字段找到 login provider)
|
|
9265
|
-
gatewayMethods: ["web.login.start", "web.login.wait"],
|
|
9266
|
-
// 配置适配器(必需)
|
|
9267
9227
|
config: {
|
|
9268
9228
|
listAccountIds: (cfg) => {
|
|
9269
9229
|
const accounts = cfg.channels?.["wechat-access-unqclawed"]?.accounts;
|
|
@@ -9276,174 +9236,62 @@ var tencentAccessPlugin = {
|
|
|
9276
9236
|
const accounts = cfg.channels?.["wechat-access-unqclawed"]?.accounts;
|
|
9277
9237
|
const account = accounts?.[accountId ?? "default"];
|
|
9278
9238
|
return account ?? { accountId: accountId ?? "default" };
|
|
9279
|
-
}
|
|
9239
|
+
},
|
|
9240
|
+
resolveAllowFrom: () => ["*"]
|
|
9280
9241
|
},
|
|
9281
|
-
//
|
|
9242
|
+
// 认证适配器:提示用户使用 CLI
|
|
9282
9243
|
auth: {
|
|
9283
|
-
login: async ({
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
runtime2.log("(qrcode-terminal \u4E0D\u53EF\u7528)");
|
|
9307
|
-
}
|
|
9308
|
-
runtime2.log(`
|
|
9309
|
-
\u6216\u5728\u6D4F\u89C8\u5668\u6253\u5F00: ${authUrl}
|
|
9310
|
-
`);
|
|
9311
|
-
const { join: join4 } = await import("path");
|
|
9312
|
-
const { homedir: homedir4 } = await import("os");
|
|
9313
|
-
const { readFileSync: readFileSync4, unlinkSync: unlinkSync3, existsSync: existsSync2 } = await import("fs");
|
|
9314
|
-
const codeTmpFile = join4(homedir4(), ".openclaw", "wechat-auth-code.tmp");
|
|
9315
|
-
try {
|
|
9316
|
-
unlinkSync3(codeTmpFile);
|
|
9317
|
-
} catch {
|
|
9318
|
-
}
|
|
9319
|
-
runtime2.log("");
|
|
9320
|
-
runtime2.log("=".repeat(64));
|
|
9321
|
-
runtime2.log("\u626B\u7801\u5E76\u5728\u624B\u673A\u4E0A\u786E\u8BA4\u540E\uFF0C\u6D4F\u89C8\u5668\u4F1A\u8DF3\u8F6C\u5230\u65B0\u9875\u9762\u3002");
|
|
9322
|
-
runtime2.log("\u5730\u5740\u680F URL \u5F62\u5982\uFF1A");
|
|
9323
|
-
runtime2.log("");
|
|
9324
|
-
runtime2.log("https://security.guanjia.qq.com/login?code=0a1B2c...&state=xxx");
|
|
9325
|
-
runtime2.log("");
|
|
9326
|
-
runtime2.log("\u8BF7\u590D\u5236 code= \u540E\u9762\u7684\u503C\uFF08\u5230 & \u4E4B\u524D\uFF09\uFF0C\u5728\u53E6\u4E00\u4E2A\u7EC8\u7AEF\u6267\u884C\uFF1A");
|
|
9327
|
-
runtime2.log("");
|
|
9328
|
-
runtime2.log(`echo "0a1B2c3D4e" > ${codeTmpFile}`);
|
|
9329
|
-
runtime2.log("");
|
|
9330
|
-
runtime2.log("\u4E5F\u53EF\u4EE5\u76F4\u63A5\u7C98\u8D34\u5B8C\u6574 URL\uFF0C\u4F1A\u81EA\u52A8\u63D0\u53D6 code\u3002");
|
|
9331
|
-
runtime2.log("\u672C\u7A97\u53E3\u4F1A\u81EA\u52A8\u68C0\u6D4B\u5E76\u5B8C\u6210\u767B\u5F55\u3002");
|
|
9332
|
-
runtime2.log("=".repeat(64));
|
|
9333
|
-
const deadline = Date.now() + 3e5;
|
|
9334
|
-
while (Date.now() < deadline) {
|
|
9335
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
9336
|
-
if (!existsSync2(codeTmpFile)) continue;
|
|
9337
|
-
let raw = "";
|
|
9338
|
-
try {
|
|
9339
|
-
raw = readFileSync4(codeTmpFile, "utf-8").trim();
|
|
9340
|
-
unlinkSync3(codeTmpFile);
|
|
9341
|
-
} catch {
|
|
9342
|
-
continue;
|
|
9343
|
-
}
|
|
9344
|
-
if (!raw) continue;
|
|
9345
|
-
raw = raw.replace(/\\([?=&#])/g, "$1");
|
|
9346
|
-
let code = raw;
|
|
9347
|
-
if (raw.includes("code=")) {
|
|
9348
|
-
try {
|
|
9349
|
-
const url = new URL(raw);
|
|
9350
|
-
const c = url.searchParams.get("code");
|
|
9351
|
-
if (c) code = c;
|
|
9352
|
-
} catch {
|
|
9353
|
-
const match = raw.match(/[?&#]code=([^&#]+)/);
|
|
9354
|
-
if (match?.[1]) code = match[1];
|
|
9355
|
-
}
|
|
9356
|
-
}
|
|
9357
|
-
if (!code) {
|
|
9358
|
-
runtime2.log("[wechat-access] \u672A\u80FD\u4ECE\u8F93\u5165\u4E2D\u63D0\u53D6 code\u3002\u8BF7\u53EA\u590D\u5236 code= \u540E\u9762\u7684\u503C\uFF08\u5230 & \u4E4B\u524D\uFF09\uFF0C\u91CD\u65B0\u5199\u5165\u6587\u4EF6\u3002");
|
|
9359
|
-
continue;
|
|
9360
|
-
}
|
|
9361
|
-
runtime2.log(`[wechat-access] \u6536\u5230 code: ${code.substring(0, 10)}...\uFF0C\u6B63\u5728\u83B7\u53D6 token...`);
|
|
9362
|
-
const loginResult = await api.wxLogin(code, state);
|
|
9363
|
-
if (!loginResult.success) {
|
|
9364
|
-
throw new Error(`\u767B\u5F55\u5931\u8D25: ${loginResult.message ?? "\u672A\u77E5\u9519\u8BEF"}`);
|
|
9365
|
-
}
|
|
9366
|
-
const loginData = loginResult.data;
|
|
9367
|
-
const jwtToken = nested(loginData, "token") || nested(loginData, "data", "token") || "";
|
|
9368
|
-
const channelToken = nested(loginData, "openclaw_channel_token") || nested(loginData, "data", "openclaw_channel_token") || "";
|
|
9369
|
-
const userInfo = nested(loginData, "user_info") || nested(loginData, "data", "user_info") || {};
|
|
9370
|
-
runtime2.log(`[wechat-access] wxLogin \u54CD\u5E94: jwtToken=${jwtToken ? jwtToken.substring(0, 8) + "..." : "(\u7A7A)"}, channelToken=${channelToken ? channelToken.substring(0, 8) + "..." : "(\u7A7A)"}, userId=${nested(userInfo, "user_id") ?? "(\u65E0)"}`);
|
|
9371
|
-
const loginKey = userInfo.loginKey;
|
|
9372
|
-
if (loginKey) api.loginKey = loginKey;
|
|
9373
|
-
api.jwtToken = jwtToken;
|
|
9374
|
-
api.userId = String(userInfo.user_id ?? "");
|
|
9375
|
-
let apiKey = "";
|
|
9376
|
-
try {
|
|
9377
|
-
const keyResult = await api.createApiKey();
|
|
9378
|
-
if (keyResult.success) {
|
|
9379
|
-
apiKey = nested(keyResult.data, "key") ?? nested(keyResult.data, "resp", "data", "key") ?? "";
|
|
9244
|
+
login: async ({ runtime: runtime2 }) => {
|
|
9245
|
+
runtime2.log("[wechat-access] \u8BF7\u4F7F\u7528 'openclaw wechat login' \u547D\u4EE4\u767B\u5F55");
|
|
9246
|
+
}
|
|
9247
|
+
},
|
|
9248
|
+
// 出站适配器
|
|
9249
|
+
outbound: {
|
|
9250
|
+
deliveryMode: "direct",
|
|
9251
|
+
sendText: async (ctx) => {
|
|
9252
|
+
if (wbHttpContext) {
|
|
9253
|
+
const chatId = lastChatIdByTo.get(ctx.to) || lastChatIdByTo.get(ctx.to.split(":").pop() || "");
|
|
9254
|
+
if (!chatId) {
|
|
9255
|
+
console.warn(`[wechat-access] sendText: \u672A\u627E\u5230 chatId for to=${ctx.to}, \u8DF3\u8FC7`);
|
|
9256
|
+
return { ok: true };
|
|
9257
|
+
}
|
|
9258
|
+
const httpPayload = {
|
|
9259
|
+
type: "COPILOT_RESPONSE",
|
|
9260
|
+
msgId: ctx.replyToId || `outbound-${Date.now()}`,
|
|
9261
|
+
chatId,
|
|
9262
|
+
success: true,
|
|
9263
|
+
message: ctx.text,
|
|
9264
|
+
metadata: {
|
|
9265
|
+
sessionId: wbHttpContext.sessionId,
|
|
9266
|
+
state: "completed"
|
|
9380
9267
|
}
|
|
9381
|
-
} catch {
|
|
9382
|
-
}
|
|
9383
|
-
const runtimeLog = {
|
|
9384
|
-
info: (...args) => runtime2.log(...args),
|
|
9385
|
-
warn: (...args) => runtime2.log(...args),
|
|
9386
|
-
error: (...args) => runtime2.log(...args)
|
|
9387
9268
|
};
|
|
9388
|
-
const
|
|
9389
|
-
api,
|
|
9390
|
-
log: runtimeLog,
|
|
9391
|
-
showQr: async (url) => {
|
|
9392
|
-
try {
|
|
9393
|
-
const qrterm = await import("qrcode-terminal");
|
|
9394
|
-
const generate = qrterm.default?.generate ?? qrterm.generate;
|
|
9395
|
-
generate(url, { small: true }, (qrcode) => {
|
|
9396
|
-
runtime2.log("\n" + qrcode);
|
|
9397
|
-
});
|
|
9398
|
-
} catch {
|
|
9399
|
-
runtime2.log("(qrcode-terminal \u4E0D\u53EF\u7528)");
|
|
9400
|
-
}
|
|
9401
|
-
}
|
|
9402
|
-
});
|
|
9403
|
-
if (!bindResult.success) {
|
|
9404
|
-
throw new Error(`\u8BBE\u5907\u7ED1\u5B9A\u5931\u8D25: ${bindResult.message}\u3002\u8BF7\u91CD\u65B0\u767B\u5F55\u91CD\u8BD5\u3002`);
|
|
9405
|
-
}
|
|
9406
|
-
runtime2.log(`[wechat-access] ${bindResult.message}`);
|
|
9269
|
+
const url = `${wbHttpContext.baseUrl}/v2/backgroundagent/wecom/local-proxy/receive`;
|
|
9407
9270
|
try {
|
|
9408
|
-
const
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
providers.qclaw = { ...providers.qclaw ?? {}, apiKey };
|
|
9423
|
-
models.providers = providers;
|
|
9424
|
-
nextCfg.models = models;
|
|
9271
|
+
const res = await fetch(url, {
|
|
9272
|
+
method: "POST",
|
|
9273
|
+
headers: {
|
|
9274
|
+
"Content-Type": "application/json",
|
|
9275
|
+
"Authorization": `Bearer ${wbHttpContext.accessToken}`
|
|
9276
|
+
},
|
|
9277
|
+
body: JSON.stringify(httpPayload),
|
|
9278
|
+
signal: AbortSignal.timeout(3e4)
|
|
9279
|
+
});
|
|
9280
|
+
if (!res.ok) {
|
|
9281
|
+
const body = await res.text().catch(() => "");
|
|
9282
|
+
console.error(`[wechat-access] sendText HTTP \u5931\u8D25: ${res.status} ${body.substring(0, 200)}`);
|
|
9283
|
+
} else {
|
|
9284
|
+
console.log(`[wechat-access] sendText HTTP \u53D1\u9001\u6210\u529F: to=${ctx.to}`);
|
|
9425
9285
|
}
|
|
9426
|
-
|
|
9427
|
-
|
|
9286
|
+
} catch (err) {
|
|
9287
|
+
console.error(`[wechat-access] sendText HTTP \u5F02\u5E38:`, err);
|
|
9428
9288
|
}
|
|
9429
|
-
|
|
9430
|
-
const nickname = userInfo.nickname ?? "\u7528\u6237";
|
|
9431
|
-
runtime2.log(`[wechat-access] \u767B\u5F55\u6210\u529F! \u6B22\u8FCE ${nickname}\uFF0Ctoken \u5DF2\u4FDD\u5B58\u3002\u8BF7\u91CD\u542F Gateway \u751F\u6548\u3002`);
|
|
9432
|
-
return;
|
|
9433
|
-
}
|
|
9434
|
-
try {
|
|
9435
|
-
unlinkSync3(codeTmpFile);
|
|
9436
|
-
} catch {
|
|
9289
|
+
return { ok: true };
|
|
9437
9290
|
}
|
|
9438
|
-
|
|
9291
|
+
return { ok: true };
|
|
9439
9292
|
}
|
|
9440
9293
|
},
|
|
9441
|
-
//
|
|
9442
|
-
outbound: {
|
|
9443
|
-
deliveryMode: "direct",
|
|
9444
|
-
sendText: async () => ({ ok: true })
|
|
9445
|
-
},
|
|
9446
|
-
// 状态适配器:上报 WebSocket 连接状态
|
|
9294
|
+
// 状态适配器
|
|
9447
9295
|
status: {
|
|
9448
9296
|
buildAccountSnapshot: ({ accountId }) => {
|
|
9449
9297
|
const client = wsClients.get(accountId ?? "default");
|
|
@@ -9451,68 +9299,62 @@ var tencentAccessPlugin = {
|
|
|
9451
9299
|
return { running };
|
|
9452
9300
|
}
|
|
9453
9301
|
},
|
|
9454
|
-
// Gateway
|
|
9302
|
+
// Gateway 适配器
|
|
9455
9303
|
gateway: {
|
|
9456
9304
|
startAccount: async (ctx) => {
|
|
9457
9305
|
const { cfg, accountId, abortSignal, log: log2 } = ctx;
|
|
9458
9306
|
log2?.info(`[wechat-access] >>> startAccount \u88AB\u8C03\u7528, accountId=${accountId}`);
|
|
9459
9307
|
try {
|
|
9460
|
-
const
|
|
9461
|
-
log2?.info(`[wechat-access] tencentAccessConfig=${JSON.stringify(tencentAccessConfig ?? null)}`);
|
|
9462
|
-
const authStatePath = tencentAccessConfig?.authStatePath ? String(tencentAccessConfig.authStatePath) : void 0;
|
|
9308
|
+
const channelCfg = readChannelConfig();
|
|
9463
9309
|
const gatewayPort = cfg?.gateway?.port ? String(cfg.gateway.port) : "unknown";
|
|
9464
9310
|
const guid = getDeviceGuid();
|
|
9465
|
-
const
|
|
9466
|
-
const loginMode = tencentAccessConfig?.loginMode || savedState?.loginMode || "qclaw";
|
|
9311
|
+
const loginMode = channelCfg.loginMode || "qclaw";
|
|
9467
9312
|
log2?.info(`[wechat-access] \u542F\u52A8\u8D26\u53F7 ${accountId}, loginMode=${loginMode}`);
|
|
9468
|
-
if (loginMode === "
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9313
|
+
if (loginMode === "workbuddy") {
|
|
9314
|
+
const creds = channelCfg.workbuddy;
|
|
9315
|
+
log2?.info(`[wechat-access] WorkBuddy \u6A21\u5F0F, hasCredentials=${!!creds}, hasAccessToken=${!!creds?.accessToken}`);
|
|
9316
|
+
if (!creds?.accessToken) {
|
|
9317
|
+
log2?.warn(`[wechat-access] WorkBuddy \u6A21\u5F0F\u672A\u627E\u5230 accessToken\uFF0C\u8BF7\u8FD0\u884C "openclaw wechat login" \u5B8C\u6210\u767B\u5F55`);
|
|
9318
|
+
return;
|
|
9319
|
+
}
|
|
9320
|
+
const cbApi = new CodeBuddyAPI(creds.baseUrl);
|
|
9321
|
+
cbApi.accessToken = creds.accessToken;
|
|
9322
|
+
cbApi.refreshToken = creds.refreshToken || "";
|
|
9323
|
+
cbApi.userId = creds.userId || String(creds.userInfo?.userId ?? "") || String(creds.userInfo?.user_id ?? "") || String(creds.userInfo?.uid ?? "");
|
|
9324
|
+
cbApi.hostId = creds.hostId || cbApi.hostId;
|
|
9325
|
+
log2?.info(`[wechat-access] CodeBuddy API \u521D\u59CB\u5316\u5B8C\u6210, userId=${cbApi.userId}, hostId=${cbApi.hostId}`);
|
|
9326
|
+
if (!cbApi.userId) {
|
|
9327
|
+
log2?.warn(`[wechat-access] userId \u4E3A\u7A7A\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C "openclaw wechat login" \u767B\u5F55`);
|
|
9472
9328
|
return;
|
|
9473
9329
|
}
|
|
9474
|
-
const cbBaseUrl = tencentAccessConfig?.codebuddyBaseUrl ? String(tencentAccessConfig.codebuddyBaseUrl) : void 0;
|
|
9475
|
-
const cbApi = new CodeBuddyAPI(cbBaseUrl);
|
|
9476
|
-
cbApi.accessToken = savedState.accessToken;
|
|
9477
|
-
cbApi.refreshToken = savedState.refreshToken || "";
|
|
9478
|
-
cbApi.userId = String(savedState.userInfo?.userId ?? "");
|
|
9479
|
-
cbApi.hostId = savedState.hostId || cbApi.hostId;
|
|
9480
|
-
log2?.info(`[wechat-access] CodeBuddy API \u521D\u59CB\u5316\u5B8C\u6210, userId=${cbApi.userId}, hostId=${cbApi.hostId}, hasRefreshToken=${!!cbApi.refreshToken}`);
|
|
9481
9330
|
try {
|
|
9482
|
-
log2?.info(`[wechat-access] \u6B63\u5728\u5237\u65B0
|
|
9483
|
-
|
|
9484
|
-
log2?.info(`[wechat-access]
|
|
9485
|
-
if (savedState) {
|
|
9486
|
-
savedState.accessToken = refreshed.accessToken;
|
|
9487
|
-
savedState.refreshToken = refreshed.refreshToken;
|
|
9488
|
-
savedState.savedAt = Date.now();
|
|
9489
|
-
saveState(savedState, authStatePath);
|
|
9490
|
-
}
|
|
9331
|
+
log2?.info(`[wechat-access] \u6B63\u5728\u5237\u65B0 accessToken...`);
|
|
9332
|
+
await cbApi.doRefreshToken();
|
|
9333
|
+
log2?.info(`[wechat-access] accessToken \u5DF2\u5237\u65B0`);
|
|
9491
9334
|
} catch (e) {
|
|
9492
9335
|
const msg = e instanceof Error ? e.message : String(e);
|
|
9493
|
-
log2?.warn(`[wechat-access]
|
|
9336
|
+
log2?.warn(`[wechat-access] token \u5237\u65B0\u5F02\u5E38: ${msg}`);
|
|
9494
9337
|
if (msg.includes("\u8FC7\u671F") || msg.includes("401") || msg.includes("403")) {
|
|
9495
|
-
|
|
9496
|
-
log2?.warn(`[wechat-access] CodeBuddy token \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C "openclaw wechat login --mode codebuddy"`);
|
|
9338
|
+
log2?.warn(`[wechat-access] token \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C "openclaw wechat login"`);
|
|
9497
9339
|
return;
|
|
9498
9340
|
}
|
|
9499
|
-
log2?.warn(`[wechat-access]
|
|
9341
|
+
log2?.warn(`[wechat-access] token \u5237\u65B0\u5931\u8D25\uFF0C\u4F7F\u7528\u65E7 token \u7EE7\u7EED`);
|
|
9500
9342
|
}
|
|
9501
|
-
log2?.info(`[wechat-access] \u6B63\u5728\u6CE8\u518C
|
|
9343
|
+
log2?.info(`[wechat-access] \u6B63\u5728\u6CE8\u518C Host Channel...`);
|
|
9502
9344
|
let centrifugeParams;
|
|
9503
9345
|
try {
|
|
9504
9346
|
centrifugeParams = await cbApi.registerWorkspace({
|
|
9505
9347
|
userId: cbApi.userId,
|
|
9506
9348
|
hostId: cbApi.hostId,
|
|
9507
|
-
workspaceId:
|
|
9508
|
-
workspaceName: `
|
|
9349
|
+
workspaceId: "",
|
|
9350
|
+
workspaceName: `Host Channel (${cbApi.hostId})`
|
|
9509
9351
|
});
|
|
9510
|
-
log2?.info(`[wechat-access]
|
|
9352
|
+
log2?.info(`[wechat-access] Host Channel \u6CE8\u518C\u6210\u529F, channel=${centrifugeParams.channel}, url=${centrifugeParams.url}`);
|
|
9511
9353
|
} catch (e) {
|
|
9512
9354
|
log2?.error(`[wechat-access] Workspace \u6CE8\u518C\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
|
|
9513
9355
|
return;
|
|
9514
9356
|
}
|
|
9515
|
-
const
|
|
9357
|
+
const client = new CentrifugeGatewayClient(
|
|
9516
9358
|
{
|
|
9517
9359
|
url: centrifugeParams.url,
|
|
9518
9360
|
connectionToken: centrifugeParams.connectionToken,
|
|
@@ -9520,37 +9362,62 @@ var tencentAccessPlugin = {
|
|
|
9520
9362
|
subscriptionToken: centrifugeParams.subscriptionToken,
|
|
9521
9363
|
guid,
|
|
9522
9364
|
userId: cbApi.userId,
|
|
9523
|
-
gatewayPort
|
|
9365
|
+
gatewayPort,
|
|
9366
|
+
httpBaseUrl: creds.baseUrl || "https://copilot.tencent.com",
|
|
9367
|
+
httpAccessToken: cbApi.accessToken,
|
|
9368
|
+
workspaceSessionId: cbApi.buildSessionId()
|
|
9524
9369
|
},
|
|
9525
9370
|
{
|
|
9526
9371
|
onConnected: () => {
|
|
9527
9372
|
log2?.info(`[wechat-access] Centrifuge \u8FDE\u63A5\u6210\u529F`);
|
|
9528
9373
|
ctx.setStatus({ running: true });
|
|
9374
|
+
const clawPath = join3(homedir3(), "WorkBuddy", "Claw");
|
|
9375
|
+
cbApi.registerWorkspace({
|
|
9376
|
+
userId: cbApi.userId,
|
|
9377
|
+
hostId: cbApi.hostId,
|
|
9378
|
+
workspaceId: clawPath,
|
|
9379
|
+
workspaceName: "Claw"
|
|
9380
|
+
}).then((clawParams) => {
|
|
9381
|
+
log2?.info(`[wechat-access] Claw workspace \u6CE8\u518C\u6210\u529F, channel=${clawParams.channel}`);
|
|
9382
|
+
client.subscribeChannel(clawParams.channel, clawParams.subscriptionToken);
|
|
9383
|
+
}).catch((e) => {
|
|
9384
|
+
log2?.warn(`[wechat-access] Claw workspace \u6CE8\u518C\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
|
|
9385
|
+
});
|
|
9529
9386
|
},
|
|
9530
9387
|
onDisconnected: (reason) => {
|
|
9531
9388
|
log2?.warn(`[wechat-access] Centrifuge \u8FDE\u63A5\u65AD\u5F00: ${reason}`);
|
|
9532
9389
|
ctx.setStatus({ running: false });
|
|
9533
9390
|
},
|
|
9534
9391
|
onPrompt: (message) => {
|
|
9535
|
-
|
|
9392
|
+
if (message._wechatKf?.chatId) {
|
|
9393
|
+
const sessionKey = `agent:main:wechat-access:direct:${cbApi.userId}`;
|
|
9394
|
+
lastChatIdByTo.set(sessionKey, message._wechatKf.chatId);
|
|
9395
|
+
lastChatIdByTo.set(cbApi.userId, message._wechatKf.chatId);
|
|
9396
|
+
}
|
|
9397
|
+
void handlePrompt(message, client).catch((err) => {
|
|
9536
9398
|
log2?.error(`[wechat-access] \u5904\u7406 prompt \u5931\u8D25: ${err.message}`);
|
|
9537
9399
|
});
|
|
9538
9400
|
},
|
|
9539
9401
|
onCancel: (message) => {
|
|
9540
|
-
handleCancel(message,
|
|
9402
|
+
handleCancel(message, client);
|
|
9541
9403
|
},
|
|
9542
9404
|
onError: (error) => {
|
|
9543
9405
|
log2?.error(`[wechat-access] Centrifuge \u9519\u8BEF: ${error.message}`);
|
|
9544
9406
|
}
|
|
9545
9407
|
}
|
|
9546
9408
|
);
|
|
9547
|
-
wsClients.set(accountId,
|
|
9548
|
-
|
|
9409
|
+
wsClients.set(accountId, client);
|
|
9410
|
+
client.start();
|
|
9411
|
+
wbHttpContext = {
|
|
9412
|
+
baseUrl: creds.baseUrl || "https://copilot.tencent.com",
|
|
9413
|
+
accessToken: cbApi.accessToken,
|
|
9414
|
+
sessionId: cbApi.buildSessionId()
|
|
9415
|
+
};
|
|
9549
9416
|
await new Promise((resolve) => {
|
|
9550
9417
|
abortSignal.addEventListener("abort", () => {
|
|
9551
9418
|
log2?.info(`[wechat-access] \u505C\u6B62\u8D26\u53F7 ${accountId}`);
|
|
9552
|
-
|
|
9553
|
-
if (wsClients.get(accountId) ===
|
|
9419
|
+
client.stop();
|
|
9420
|
+
if (wsClients.get(accountId) === client) {
|
|
9554
9421
|
wsClients.delete(accountId);
|
|
9555
9422
|
ctx.setStatus({ running: false });
|
|
9556
9423
|
}
|
|
@@ -9559,11 +9426,11 @@ var tencentAccessPlugin = {
|
|
|
9559
9426
|
});
|
|
9560
9427
|
return;
|
|
9561
9428
|
}
|
|
9562
|
-
|
|
9563
|
-
const
|
|
9564
|
-
const envName = tencentAccessConfig?.environment ? String(tencentAccessConfig.environment) : "production";
|
|
9429
|
+
const qclawCreds = channelCfg.qclaw;
|
|
9430
|
+
const envName = channelCfg.environment ? String(channelCfg.environment) : "production";
|
|
9565
9431
|
const env = getEnvironment(envName);
|
|
9566
|
-
const wsUrl =
|
|
9432
|
+
const wsUrl = qclawCreds?.wsUrl || env.wechatWsUrl;
|
|
9433
|
+
let token = qclawCreds?.channelToken || "";
|
|
9567
9434
|
log2?.info(`[wechat-access] QClaw \u6A21\u5F0F\u542F\u52A8`, {
|
|
9568
9435
|
platform: process.platform,
|
|
9569
9436
|
nodeVersion: process.version,
|
|
@@ -9572,19 +9439,15 @@ var tencentAccessPlugin = {
|
|
|
9572
9439
|
url: wsUrl || "(\u672A\u914D\u7F6E)",
|
|
9573
9440
|
tokenPrefix: token ? token.substring(0, 6) + "..." : "(\u672A\u914D\u7F6E)"
|
|
9574
9441
|
});
|
|
9575
|
-
if (!token && savedState?.channelToken) {
|
|
9576
|
-
token = savedState.channelToken;
|
|
9577
|
-
log2?.info(`[wechat-access] \u4F7F\u7528\u5DF2\u4FDD\u5B58\u7684 token: ${token.substring(0, 6)}...`);
|
|
9578
|
-
}
|
|
9579
9442
|
if (!token) {
|
|
9580
|
-
log2?.warn(`[wechat-access] \u672A\u627E\u5230 token\uFF0C\u8BF7\u8FD0\u884C "openclaw
|
|
9443
|
+
log2?.warn(`[wechat-access] \u672A\u627E\u5230 token\uFF0C\u8BF7\u8FD0\u884C "openclaw wechat login" \u5B8C\u6210\u626B\u7801\u767B\u5F55\uFF0C\u7136\u540E\u91CD\u542F Gateway`);
|
|
9581
9444
|
return;
|
|
9582
9445
|
}
|
|
9583
|
-
const jwtToken =
|
|
9446
|
+
const jwtToken = qclawCreds?.jwtToken || "";
|
|
9584
9447
|
if (jwtToken) {
|
|
9585
9448
|
const api = new QClawAPI(env, guid, jwtToken);
|
|
9586
|
-
api.userId =
|
|
9587
|
-
const savedLoginKey =
|
|
9449
|
+
api.userId = qclawCreds?.userId || "";
|
|
9450
|
+
const savedLoginKey = qclawCreds?.userInfo?.loginKey;
|
|
9588
9451
|
if (savedLoginKey) api.loginKey = savedLoginKey;
|
|
9589
9452
|
let refreshed = false;
|
|
9590
9453
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
@@ -9593,29 +9456,12 @@ var tencentAccessPlugin = {
|
|
|
9593
9456
|
if (newToken) {
|
|
9594
9457
|
token = newToken;
|
|
9595
9458
|
log2?.info(`[wechat-access] channel_token \u5DF2\u5237\u65B0: ${token.substring(0, 6)}...`);
|
|
9596
|
-
if (savedState) {
|
|
9597
|
-
savedState.channelToken = newToken;
|
|
9598
|
-
savedState.savedAt = Date.now();
|
|
9599
|
-
saveState(savedState, authStatePath);
|
|
9600
|
-
}
|
|
9601
|
-
try {
|
|
9602
|
-
const wRuntime = getWecomRuntime();
|
|
9603
|
-
const fullCfg = wRuntime.config.loadConfig();
|
|
9604
|
-
const channels = { ...fullCfg.channels ?? {} };
|
|
9605
|
-
channels["wechat-access-unqclawed"] = {
|
|
9606
|
-
...channels["wechat-access-unqclawed"] ?? {},
|
|
9607
|
-
token: newToken
|
|
9608
|
-
};
|
|
9609
|
-
await wRuntime.config.writeConfigFile({ ...fullCfg, channels });
|
|
9610
|
-
} catch {
|
|
9611
|
-
}
|
|
9612
9459
|
refreshed = true;
|
|
9613
9460
|
break;
|
|
9614
9461
|
}
|
|
9615
9462
|
} catch (e) {
|
|
9616
9463
|
if (e instanceof TokenExpiredError) {
|
|
9617
|
-
|
|
9618
|
-
log2?.warn(`[wechat-access] jwt_token \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C "openclaw channels login --channel wechat-access-unqclawed"`);
|
|
9464
|
+
log2?.warn(`[wechat-access] jwt_token \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C "openclaw wechat login"`);
|
|
9619
9465
|
return;
|
|
9620
9466
|
}
|
|
9621
9467
|
log2?.warn(`[wechat-access] token \u5237\u65B0\u5931\u8D25 (${attempt + 1}/3): ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -9626,7 +9472,7 @@ var tencentAccessPlugin = {
|
|
|
9626
9472
|
log2?.info(`[wechat-access] token \u5237\u65B0\u5931\u8D25\uFF0C\u4F7F\u7528\u65E7 token \u5C1D\u8BD5\u8FDE\u63A5`);
|
|
9627
9473
|
}
|
|
9628
9474
|
}
|
|
9629
|
-
const userId =
|
|
9475
|
+
const userId = qclawCreds?.userId || "";
|
|
9630
9476
|
const wsConfig = {
|
|
9631
9477
|
url: wsUrl,
|
|
9632
9478
|
token,
|
|
@@ -9637,7 +9483,7 @@ var tencentAccessPlugin = {
|
|
|
9637
9483
|
maxReconnectAttempts: 10,
|
|
9638
9484
|
heartbeatInterval: 2e4
|
|
9639
9485
|
};
|
|
9640
|
-
const
|
|
9486
|
+
const qclawClient = new WechatAccessWebSocketClient(wsConfig, {
|
|
9641
9487
|
onConnected: () => {
|
|
9642
9488
|
log2?.info(`[wechat-access] WebSocket \u8FDE\u63A5\u6210\u529F`);
|
|
9643
9489
|
ctx.setStatus({ running: true });
|
|
@@ -9647,24 +9493,24 @@ var tencentAccessPlugin = {
|
|
|
9647
9493
|
ctx.setStatus({ running: false });
|
|
9648
9494
|
},
|
|
9649
9495
|
onPrompt: (message) => {
|
|
9650
|
-
void handlePrompt(message,
|
|
9496
|
+
void handlePrompt(message, qclawClient).catch((err) => {
|
|
9651
9497
|
log2?.error(`[wechat-access] \u5904\u7406 prompt \u5931\u8D25: ${err.message}`);
|
|
9652
9498
|
});
|
|
9653
9499
|
},
|
|
9654
9500
|
onCancel: (message) => {
|
|
9655
|
-
handleCancel(message,
|
|
9501
|
+
handleCancel(message, qclawClient);
|
|
9656
9502
|
},
|
|
9657
9503
|
onError: (error) => {
|
|
9658
9504
|
log2?.error(`[wechat-access] WebSocket \u9519\u8BEF: ${error.message}`);
|
|
9659
9505
|
}
|
|
9660
9506
|
});
|
|
9661
|
-
wsClients.set(accountId,
|
|
9662
|
-
|
|
9507
|
+
wsClients.set(accountId, qclawClient);
|
|
9508
|
+
qclawClient.start();
|
|
9663
9509
|
await new Promise((resolve) => {
|
|
9664
9510
|
abortSignal.addEventListener("abort", () => {
|
|
9665
9511
|
log2?.info(`[wechat-access] \u505C\u6B62\u8D26\u53F7 ${accountId}`);
|
|
9666
|
-
|
|
9667
|
-
if (wsClients.get(accountId) ===
|
|
9512
|
+
qclawClient.stop();
|
|
9513
|
+
if (wsClients.get(accountId) === qclawClient) {
|
|
9668
9514
|
wsClients.delete(accountId);
|
|
9669
9515
|
ctx.setStatus({ running: false });
|
|
9670
9516
|
}
|
|
@@ -9688,253 +9534,6 @@ ${e?.stack ?? ""}`);
|
|
|
9688
9534
|
} else {
|
|
9689
9535
|
log2?.warn(`[wechat-access] stopAccount: \u672A\u627E\u5230\u8D26\u53F7 ${accountId} \u7684\u5BA2\u6237\u7AEF`);
|
|
9690
9536
|
}
|
|
9691
|
-
},
|
|
9692
|
-
// QR 扫码登录:生成二维码(openclaw channels login 调用)
|
|
9693
|
-
loginWithQrStart: async (_params) => {
|
|
9694
|
-
try {
|
|
9695
|
-
const runtime2 = getWecomRuntime();
|
|
9696
|
-
const cfg = runtime2.config.loadConfig();
|
|
9697
|
-
const channelCfg = cfg?.channels?.["wechat-access-unqclawed"];
|
|
9698
|
-
const loginMode = channelCfg?.loginMode || "qclaw";
|
|
9699
|
-
if (loginMode === "codebuddy") {
|
|
9700
|
-
const cbBaseUrl = channelCfg?.codebuddyBaseUrl ? String(channelCfg.codebuddyBaseUrl) : void 0;
|
|
9701
|
-
const cbApi = new CodeBuddyAPI(cbBaseUrl);
|
|
9702
|
-
const { authUrl: authUrl2, state: cbState } = await cbApi.fetchAuthState();
|
|
9703
|
-
pendingQrLogin = { loginMode: "codebuddy", cbApi, cbState, cbPhase: "oauth" };
|
|
9704
|
-
return {
|
|
9705
|
-
// 没有 qrDataUrl,前端应该展示 authUrl 链接让用户在浏览器中打开
|
|
9706
|
-
authUrl: authUrl2,
|
|
9707
|
-
message: `\u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u4EE5\u4E0B\u94FE\u63A5\u5B8C\u6210\u767B\u5F55\uFF1A
|
|
9708
|
-
${authUrl2}`
|
|
9709
|
-
};
|
|
9710
|
-
}
|
|
9711
|
-
const envName = channelCfg?.environment ? String(channelCfg.environment) : "production";
|
|
9712
|
-
const bypassInvite = channelCfg?.bypassInvite === true;
|
|
9713
|
-
const authStatePath = channelCfg?.authStatePath ? String(channelCfg.authStatePath) : void 0;
|
|
9714
|
-
const env = getEnvironment(envName);
|
|
9715
|
-
const guid = getDeviceGuid();
|
|
9716
|
-
const api = new QClawAPI(env, guid);
|
|
9717
|
-
const stateResult = await api.getWxLoginState();
|
|
9718
|
-
let state = String(Math.floor(Math.random() * 1e4));
|
|
9719
|
-
if (stateResult.success) {
|
|
9720
|
-
const s = nested(stateResult.data, "state");
|
|
9721
|
-
if (s) state = s;
|
|
9722
|
-
}
|
|
9723
|
-
const authUrl = buildAuthUrl(state, env);
|
|
9724
|
-
const uuid = await fetchQrUuid(authUrl);
|
|
9725
|
-
const qrDataUrl = await fetchQrImageDataUrl(uuid);
|
|
9726
|
-
pendingQrLogin = { loginMode: "qclaw", state, uuid, env, guid, bypassInvite, authStatePath };
|
|
9727
|
-
return { qrDataUrl, message: "\u8BF7\u7528\u5FAE\u4FE1\u626B\u63CF\u4E8C\u7EF4\u7801\u767B\u5F55" };
|
|
9728
|
-
} catch (err) {
|
|
9729
|
-
return { message: `\u767B\u5F55\u521D\u59CB\u5316\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}` };
|
|
9730
|
-
}
|
|
9731
|
-
},
|
|
9732
|
-
// QR 扫码登录:轮询扫码状态(openclaw channels login 循环调用)
|
|
9733
|
-
loginWithQrWait: async (_params) => {
|
|
9734
|
-
if (!pendingQrLogin) {
|
|
9735
|
-
return { connected: false, message: "\u8BF7\u5148\u6267\u884C loginWithQrStart" };
|
|
9736
|
-
}
|
|
9737
|
-
if (pendingQrLogin.loginMode === "codebuddy") {
|
|
9738
|
-
try {
|
|
9739
|
-
const { cbApi, cbState, cbPhase } = pendingQrLogin;
|
|
9740
|
-
if (!cbApi || !cbState) {
|
|
9741
|
-
pendingQrLogin = null;
|
|
9742
|
-
return { connected: false, message: "CodeBuddy \u767B\u5F55\u72B6\u6001\u5F02\u5E38" };
|
|
9743
|
-
}
|
|
9744
|
-
if (cbPhase === "oauth") {
|
|
9745
|
-
try {
|
|
9746
|
-
const url = `${cbApi.baseUrl}/v2/plugin/auth/token?state=${cbState}`;
|
|
9747
|
-
const res = await fetch(url, {
|
|
9748
|
-
method: "GET",
|
|
9749
|
-
headers: { "Content-Type": "application/json", "X-No-Authorization": "true" },
|
|
9750
|
-
signal: AbortSignal.timeout(5e3)
|
|
9751
|
-
});
|
|
9752
|
-
if (!res.ok) {
|
|
9753
|
-
const body = await res.text().catch(() => "");
|
|
9754
|
-
if (body.includes("11217")) {
|
|
9755
|
-
return { connected: false, message: "\u7B49\u5F85\u6D4F\u89C8\u5668\u767B\u5F55..." };
|
|
9756
|
-
}
|
|
9757
|
-
return { connected: false, message: "\u7B49\u5F85\u6D4F\u89C8\u5668\u767B\u5F55..." };
|
|
9758
|
-
}
|
|
9759
|
-
const data = await res.json();
|
|
9760
|
-
const token = data?.data;
|
|
9761
|
-
if (!token?.accessToken) {
|
|
9762
|
-
return { connected: false, message: "\u7B49\u5F85\u6D4F\u89C8\u5668\u767B\u5F55..." };
|
|
9763
|
-
}
|
|
9764
|
-
cbApi.accessToken = token.accessToken;
|
|
9765
|
-
cbApi.refreshToken = token.refreshToken || "";
|
|
9766
|
-
let userInfo = {};
|
|
9767
|
-
try {
|
|
9768
|
-
userInfo = await cbApi.getAccount(cbState);
|
|
9769
|
-
} catch {
|
|
9770
|
-
}
|
|
9771
|
-
cbApi.userId = String(userInfo.userId ?? userInfo.user_id ?? "");
|
|
9772
|
-
const sessionId = cbApi.buildSessionId(`openclaw-${_params.accountId ?? "default"}`);
|
|
9773
|
-
pendingQrLogin.cbSessionId = sessionId;
|
|
9774
|
-
let bindUrl = "";
|
|
9775
|
-
try {
|
|
9776
|
-
const linkResult = await cbApi.wechatkfGetLink(sessionId);
|
|
9777
|
-
if (linkResult.success && linkResult.url) {
|
|
9778
|
-
bindUrl = linkResult.url;
|
|
9779
|
-
}
|
|
9780
|
-
} catch {
|
|
9781
|
-
}
|
|
9782
|
-
const guid = getDeviceGuid();
|
|
9783
|
-
const authStatePath = (() => {
|
|
9784
|
-
try {
|
|
9785
|
-
const rt = getWecomRuntime();
|
|
9786
|
-
const c = rt.config.loadConfig();
|
|
9787
|
-
const cc = c?.channels?.["wechat-access-unqclawed"];
|
|
9788
|
-
return cc?.authStatePath ? String(cc.authStatePath) : void 0;
|
|
9789
|
-
} catch {
|
|
9790
|
-
return void 0;
|
|
9791
|
-
}
|
|
9792
|
-
})();
|
|
9793
|
-
const persistedState = {
|
|
9794
|
-
loginMode: "codebuddy",
|
|
9795
|
-
jwtToken: "",
|
|
9796
|
-
channelToken: "",
|
|
9797
|
-
apiKey: "",
|
|
9798
|
-
guid,
|
|
9799
|
-
userInfo,
|
|
9800
|
-
savedAt: Date.now(),
|
|
9801
|
-
accessToken: cbApi.accessToken,
|
|
9802
|
-
refreshToken: cbApi.refreshToken,
|
|
9803
|
-
hostId: cbApi.hostId
|
|
9804
|
-
};
|
|
9805
|
-
saveState(persistedState, authStatePath);
|
|
9806
|
-
try {
|
|
9807
|
-
const wRuntime = getWecomRuntime();
|
|
9808
|
-
const fullCfg = wRuntime.config.loadConfig();
|
|
9809
|
-
const channels = { ...fullCfg.channels ?? {} };
|
|
9810
|
-
channels["wechat-access-unqclawed"] = {
|
|
9811
|
-
...channels["wechat-access-unqclawed"] ?? {},
|
|
9812
|
-
loginMode: "codebuddy"
|
|
9813
|
-
};
|
|
9814
|
-
await wRuntime.config.writeConfigFile({ ...fullCfg, channels });
|
|
9815
|
-
} catch {
|
|
9816
|
-
}
|
|
9817
|
-
pendingQrLogin = null;
|
|
9818
|
-
const nickname = String(userInfo.nickName ?? userInfo.nickname ?? "\u7528\u6237");
|
|
9819
|
-
if (bindUrl) {
|
|
9820
|
-
return {
|
|
9821
|
-
connected: true,
|
|
9822
|
-
bindUrl,
|
|
9823
|
-
message: `CodeBuddy \u767B\u5F55\u6210\u529F! \u6B22\u8FCE ${nickname}\u3002
|
|
9824
|
-
|
|
9825
|
-
\u8BF7\u5728\u5FAE\u4FE1\u4E2D\u6253\u5F00\u4EE5\u4E0B\u94FE\u63A5\u7ED1\u5B9A\u5FAE\u4FE1\u5BA2\u670D\u53F7\uFF08\u7ED1\u5B9A\u540E\u624D\u6709\u5BF9\u8BDD\u5165\u53E3\uFF09\uFF1A
|
|
9826
|
-
${bindUrl}
|
|
9827
|
-
|
|
9828
|
-
\u7ED1\u5B9A\u5B8C\u6210\u540E\u8BF7\u91CD\u542F Gateway \u751F\u6548\u3002`
|
|
9829
|
-
};
|
|
9830
|
-
}
|
|
9831
|
-
return {
|
|
9832
|
-
connected: true,
|
|
9833
|
-
message: `CodeBuddy \u767B\u5F55\u6210\u529F! \u6B22\u8FCE ${nickname}\u3002Token \u5DF2\u4FDD\u5B58\uFF0C\u8BF7\u91CD\u542F Gateway \u751F\u6548\u3002`
|
|
9834
|
-
};
|
|
9835
|
-
} catch (err) {
|
|
9836
|
-
return { connected: false, message: "\u7B49\u5F85\u6D4F\u89C8\u5668\u767B\u5F55..." };
|
|
9837
|
-
}
|
|
9838
|
-
}
|
|
9839
|
-
return { connected: false, message: "\u7B49\u5F85\u4E2D..." };
|
|
9840
|
-
} catch (err) {
|
|
9841
|
-
return { connected: false, message: `CodeBuddy \u8F6E\u8BE2\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}` };
|
|
9842
|
-
}
|
|
9843
|
-
}
|
|
9844
|
-
try {
|
|
9845
|
-
const result = await pollQrStatus(pendingQrLogin.uuid);
|
|
9846
|
-
if (result.status === "waiting") {
|
|
9847
|
-
return { connected: false, message: "\u7B49\u5F85\u626B\u7801..." };
|
|
9848
|
-
}
|
|
9849
|
-
if (result.status === "scanned") {
|
|
9850
|
-
return { connected: false, message: "\u5DF2\u626B\u7801\uFF0C\u8BF7\u5728\u624B\u673A\u4E0A\u786E\u8BA4..." };
|
|
9851
|
-
}
|
|
9852
|
-
if (result.status === "expired") {
|
|
9853
|
-
pendingQrLogin = null;
|
|
9854
|
-
return { connected: false, message: "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C openclaw channels login" };
|
|
9855
|
-
}
|
|
9856
|
-
if (result.status === "confirmed" && result.code) {
|
|
9857
|
-
const { state, env, guid, authStatePath } = pendingQrLogin;
|
|
9858
|
-
const api = new QClawAPI(env, guid);
|
|
9859
|
-
const loginResult = await api.wxLogin(result.code, state);
|
|
9860
|
-
if (!loginResult.success) {
|
|
9861
|
-
pendingQrLogin = null;
|
|
9862
|
-
return { connected: false, message: `\u767B\u5F55\u5931\u8D25: ${loginResult.message ?? "\u672A\u77E5\u9519\u8BEF"}` };
|
|
9863
|
-
}
|
|
9864
|
-
const loginData = loginResult.data;
|
|
9865
|
-
const jwtToken = nested(loginData, "token") || nested(loginData, "data", "token") || "";
|
|
9866
|
-
const channelToken = nested(loginData, "openclaw_channel_token") || nested(loginData, "data", "openclaw_channel_token") || "";
|
|
9867
|
-
const userInfo = nested(loginData, "user_info") || nested(loginData, "data", "user_info") || {};
|
|
9868
|
-
const loginKey = userInfo.loginKey;
|
|
9869
|
-
if (loginKey) api.loginKey = loginKey;
|
|
9870
|
-
api.jwtToken = jwtToken;
|
|
9871
|
-
api.userId = String(userInfo.user_id ?? "");
|
|
9872
|
-
let apiKey = "";
|
|
9873
|
-
try {
|
|
9874
|
-
const keyResult = await api.createApiKey();
|
|
9875
|
-
if (keyResult.success) {
|
|
9876
|
-
apiKey = nested(keyResult.data, "key") ?? nested(keyResult.data, "resp", "data", "key") ?? "";
|
|
9877
|
-
}
|
|
9878
|
-
} catch {
|
|
9879
|
-
}
|
|
9880
|
-
try {
|
|
9881
|
-
const wRuntime = getWecomRuntime();
|
|
9882
|
-
const fullCfg = wRuntime.config.loadConfig();
|
|
9883
|
-
const channels = { ...fullCfg.channels ?? {} };
|
|
9884
|
-
channels["wechat-access-unqclawed"] = {
|
|
9885
|
-
...channels["wechat-access-unqclawed"] ?? {},
|
|
9886
|
-
token: channelToken,
|
|
9887
|
-
guid,
|
|
9888
|
-
userId: String(userInfo.user_id ?? "")
|
|
9889
|
-
};
|
|
9890
|
-
const nextCfg = { ...fullCfg, channels };
|
|
9891
|
-
if (apiKey) {
|
|
9892
|
-
const models = { ...fullCfg.models ?? {} };
|
|
9893
|
-
const providers = { ...models.providers ?? {} };
|
|
9894
|
-
providers.qclaw = { ...providers.qclaw ?? {}, apiKey };
|
|
9895
|
-
models.providers = providers;
|
|
9896
|
-
nextCfg.models = models;
|
|
9897
|
-
}
|
|
9898
|
-
await wRuntime.config.writeConfigFile(nextCfg);
|
|
9899
|
-
} catch {
|
|
9900
|
-
}
|
|
9901
|
-
saveState({ loginMode: "qclaw", jwtToken, channelToken, apiKey, guid, userInfo, savedAt: Date.now() }, authStatePath);
|
|
9902
|
-
let bindUrl = "";
|
|
9903
|
-
let bindError = "";
|
|
9904
|
-
try {
|
|
9905
|
-
const OPEN_KFID = "wkzLlJLAAAfbxEV3ZcS-lHZxkaKmpejQ";
|
|
9906
|
-
const linkResult = await api.generateContactLink(OPEN_KFID);
|
|
9907
|
-
if (linkResult.success) {
|
|
9908
|
-
const linkData = linkResult.data;
|
|
9909
|
-
bindUrl = nested(linkData, "url") || nested(linkData, "data", "url") || "";
|
|
9910
|
-
}
|
|
9911
|
-
if (!bindUrl) {
|
|
9912
|
-
bindError = "\u751F\u6210\u8BBE\u5907\u7ED1\u5B9A\u94FE\u63A5\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55\u91CD\u8BD5";
|
|
9913
|
-
}
|
|
9914
|
-
} catch (e) {
|
|
9915
|
-
bindError = `\u751F\u6210\u8BBE\u5907\u7ED1\u5B9A\u94FE\u63A5\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`;
|
|
9916
|
-
}
|
|
9917
|
-
pendingQrLogin = null;
|
|
9918
|
-
const nickname = userInfo.nickname ?? "\u7528\u6237";
|
|
9919
|
-
if (!bindUrl) {
|
|
9920
|
-
return {
|
|
9921
|
-
connected: false,
|
|
9922
|
-
message: `\u767B\u5F55\u6210\u529F\u4F46\u8BBE\u5907\u7ED1\u5B9A\u5931\u8D25: ${bindError}\u3002\u8BF7\u91CD\u65B0\u767B\u5F55\u91CD\u8BD5\u3002`
|
|
9923
|
-
};
|
|
9924
|
-
}
|
|
9925
|
-
return {
|
|
9926
|
-
connected: true,
|
|
9927
|
-
bindUrl,
|
|
9928
|
-
message: `\u767B\u5F55\u6210\u529F! \u6B22\u8FCE ${nickname}\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u4E2D\u6253\u5F00\u4EE5\u4E0B\u94FE\u63A5\u5B8C\u6210\u8BBE\u5907\u7ED1\u5B9A\uFF08\u7ED1\u5B9A\u540E\u624D\u6709\u5BF9\u8BDD\u5165\u53E3\uFF09\uFF1A
|
|
9929
|
-
${bindUrl}
|
|
9930
|
-
|
|
9931
|
-
\u7ED1\u5B9A\u5B8C\u6210\u540E\u8BF7\u91CD\u542F Gateway \u751F\u6548\u3002`
|
|
9932
|
-
};
|
|
9933
|
-
}
|
|
9934
|
-
return { connected: false, message: "\u7B49\u5F85\u626B\u7801..." };
|
|
9935
|
-
} catch (err) {
|
|
9936
|
-
return { connected: false, message: `\u8F6E\u8BE2\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}` };
|
|
9937
|
-
}
|
|
9938
9537
|
}
|
|
9939
9538
|
}
|
|
9940
9539
|
};
|
|
@@ -9943,21 +9542,26 @@ var index = {
|
|
|
9943
9542
|
name: "\u901A\u7528\u901A\u8DEF\u63D2\u4EF6",
|
|
9944
9543
|
description: "\u817E\u8BAF\u901A\u7528\u901A\u8DEF\u63D2\u4EF6",
|
|
9945
9544
|
configSchema: emptyPluginConfigSchema(),
|
|
9946
|
-
/**
|
|
9947
|
-
* 插件注册入口点
|
|
9948
|
-
*/
|
|
9949
9545
|
register(api) {
|
|
9950
9546
|
setWecomRuntime(api.runtime);
|
|
9951
9547
|
api.registerChannel({ plugin: tencentAccessPlugin });
|
|
9952
9548
|
api.registerCli(
|
|
9953
9549
|
({ program, config }) => {
|
|
9954
9550
|
const wechat = program.command("wechat").description("\u5FAE\u4FE1\u901A\u8DEF\u767B\u5F55\u7BA1\u7406");
|
|
9955
|
-
wechat.command("login").description("\u767B\u5F55\uFF08\
|
|
9956
|
-
const
|
|
9957
|
-
const
|
|
9958
|
-
const
|
|
9959
|
-
|
|
9960
|
-
|
|
9551
|
+
wechat.command("login").description("\u767B\u5F55\uFF08\u4EA4\u4E92\u5F0F\u9009\u62E9 QClaw \u6216 WorkBuddy\uFF09").action(async () => {
|
|
9552
|
+
const { createInterface: createInterface2 } = await import("readline");
|
|
9553
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
9554
|
+
const ask = (q) => new Promise((r) => rl.question(q, (a) => {
|
|
9555
|
+
r(a.trim());
|
|
9556
|
+
}));
|
|
9557
|
+
console.log("\n\u8BF7\u9009\u62E9\u767B\u5F55\u65B9\u5F0F\uFF1A");
|
|
9558
|
+
console.log(" 1. QClaw\uFF08\u5FAE\u4FE1\u626B\u7801 \u2192 JPRX \u7F51\u5173\uFF09");
|
|
9559
|
+
console.log(" 2. WorkBuddy\uFF08CodeBuddy OAuth \u2192 Centrifuge\uFF09");
|
|
9560
|
+
const choice = await ask("\n\u8BF7\u8F93\u5165 1 \u6216 2: ");
|
|
9561
|
+
rl.close();
|
|
9562
|
+
if (choice === "2") {
|
|
9563
|
+
console.log("[wechat login] \u4F7F\u7528 WorkBuddy \u6A21\u5F0F\u767B\u5F55...");
|
|
9564
|
+
const channelCfg = config?.channels?.["wechat-access-unqclawed"];
|
|
9961
9565
|
const cbBaseUrl = channelCfg?.codebuddyBaseUrl ? String(channelCfg.codebuddyBaseUrl) : void 0;
|
|
9962
9566
|
const cbApi = new CodeBuddyAPI(cbBaseUrl);
|
|
9963
9567
|
try {
|
|
@@ -9974,82 +9578,135 @@ var index = {
|
|
|
9974
9578
|
let userInfo = {};
|
|
9975
9579
|
try {
|
|
9976
9580
|
userInfo = await cbApi.getAccount(state);
|
|
9977
|
-
cbApi.userId = String(userInfo.userId ?? userInfo.user_id ?? "");
|
|
9978
|
-
} catch {
|
|
9581
|
+
cbApi.userId = String(userInfo.uid ?? userInfo.userId ?? userInfo.user_id ?? "");
|
|
9582
|
+
} catch (e) {
|
|
9583
|
+
console.warn(`[wechat login] \u83B7\u53D6\u8D26\u53F7\u4FE1\u606F\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
|
|
9979
9584
|
}
|
|
9980
|
-
const
|
|
9981
|
-
const persistedState = {
|
|
9982
|
-
loginMode: "codebuddy",
|
|
9983
|
-
jwtToken: "",
|
|
9984
|
-
channelToken: "",
|
|
9985
|
-
apiKey: "",
|
|
9986
|
-
guid: guid2,
|
|
9987
|
-
userInfo,
|
|
9988
|
-
savedAt: Date.now(),
|
|
9585
|
+
const wbCreds = {
|
|
9989
9586
|
accessToken: tokenResult.accessToken,
|
|
9990
9587
|
refreshToken: tokenResult.refreshToken,
|
|
9991
|
-
|
|
9588
|
+
userId: cbApi.userId,
|
|
9589
|
+
hostId: cbApi.hostId,
|
|
9590
|
+
baseUrl: cbBaseUrl,
|
|
9591
|
+
userInfo
|
|
9992
9592
|
};
|
|
9993
|
-
|
|
9994
|
-
try {
|
|
9995
|
-
const wRuntime = getWecomRuntime();
|
|
9996
|
-
const fullCfg = wRuntime.config.loadConfig();
|
|
9997
|
-
const channels = { ...fullCfg.channels ?? {} };
|
|
9998
|
-
channels["wechat-access-unqclawed"] = {
|
|
9999
|
-
...channels["wechat-access-unqclawed"] ?? {},
|
|
10000
|
-
loginMode: "codebuddy"
|
|
10001
|
-
};
|
|
10002
|
-
await wRuntime.config.writeConfigFile({ ...fullCfg, channels });
|
|
10003
|
-
} catch {
|
|
10004
|
-
}
|
|
10005
|
-
const sessionId = cbApi.buildSessionId(`openclaw-default`);
|
|
10006
|
-
try {
|
|
10007
|
-
const linkResult = await cbApi.wechatkfGetLink(sessionId);
|
|
10008
|
-
if (linkResult.success && linkResult.url) {
|
|
10009
|
-
console.log("\n" + "=".repeat(64));
|
|
10010
|
-
console.log("\u8BF7\u5728\u5FAE\u4FE1\u4E2D\u6253\u5F00\u4EE5\u4E0B\u94FE\u63A5\u7ED1\u5B9A\u5FAE\u4FE1\u5BA2\u670D\u53F7\uFF08\u7ED1\u5B9A\u540E\u624D\u6709\u5BF9\u8BDD\u5165\u53E3\uFF09\uFF1A");
|
|
10011
|
-
console.log("");
|
|
10012
|
-
console.log(linkResult.url);
|
|
10013
|
-
console.log("=".repeat(64));
|
|
10014
|
-
}
|
|
10015
|
-
} catch {
|
|
10016
|
-
}
|
|
9593
|
+
await writeChannelConfig({ loginMode: "workbuddy", workbuddy: wbCreds });
|
|
10017
9594
|
const nickname = String(userInfo.nickName ?? userInfo.nickname ?? "\u7528\u6237");
|
|
10018
9595
|
console.log(`
|
|
10019
|
-
|
|
10020
|
-
console.log("
|
|
9596
|
+
WorkBuddy \u767B\u5F55\u6210\u529F! \u6B22\u8FCE ${nickname}`);
|
|
9597
|
+
console.log("\u8BF7\u8FD0\u884C openclaw gateway restart \u542F\u52A8\u901A\u8DEF\u3002");
|
|
9598
|
+
console.log("\u9996\u6B21\u4F7F\u7528\u8BF7\u5728 gateway \u542F\u52A8\u540E\u8FD0\u884C openclaw wechat bind \u83B7\u53D6\u5FAE\u4FE1\u5BA2\u670D\u7ED1\u5B9A\u94FE\u63A5\u3002");
|
|
10021
9599
|
} catch (err) {
|
|
10022
9600
|
console.error(`
|
|
10023
|
-
|
|
9601
|
+
WorkBuddy \u767B\u5F55\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
10024
9602
|
process.exit(1);
|
|
10025
9603
|
}
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
9604
|
+
} else {
|
|
9605
|
+
const channelCfg = config?.channels?.["wechat-access-unqclawed"];
|
|
9606
|
+
const envName = channelCfg?.environment ? String(channelCfg.environment) : "production";
|
|
9607
|
+
const env = getEnvironment(envName);
|
|
9608
|
+
const guid = getDeviceGuid();
|
|
9609
|
+
try {
|
|
9610
|
+
const credentials = await performLogin({ guid, env });
|
|
9611
|
+
const qclawCreds = {
|
|
9612
|
+
jwtToken: credentials.jwtToken,
|
|
9613
|
+
channelToken: credentials.channelToken,
|
|
9614
|
+
apiKey: credentials.apiKey,
|
|
9615
|
+
guid: credentials.guid,
|
|
9616
|
+
userId: String(credentials.userInfo?.user_id ?? ""),
|
|
9617
|
+
wsUrl: env.wechatWsUrl,
|
|
9618
|
+
userInfo: credentials.userInfo
|
|
9619
|
+
};
|
|
9620
|
+
await writeChannelConfig({ loginMode: "qclaw", qclaw: qclawCreds });
|
|
9621
|
+
if (credentials.apiKey) {
|
|
9622
|
+
try {
|
|
9623
|
+
const wRuntime = getWecomRuntime();
|
|
9624
|
+
const fullCfg = wRuntime.config.loadConfig();
|
|
9625
|
+
const models = { ...fullCfg.models ?? {} };
|
|
9626
|
+
const providers = { ...models.providers ?? {} };
|
|
9627
|
+
providers.qclaw = { ...providers.qclaw ?? {}, apiKey: credentials.apiKey };
|
|
9628
|
+
models.providers = providers;
|
|
9629
|
+
await wRuntime.config.writeConfigFile({ ...fullCfg, models });
|
|
9630
|
+
} catch {
|
|
9631
|
+
}
|
|
9632
|
+
}
|
|
9633
|
+
console.log(`
|
|
10040
9634
|
\u767B\u5F55\u6210\u529F! token: ${credentials.channelToken.substring(0, 6)}...`);
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
9635
|
+
console.log("\u8BF7\u8FD0\u884C openclaw gateway restart \u542F\u52A8\u901A\u8DEF\u3002");
|
|
9636
|
+
console.log("\u9996\u6B21\u4F7F\u7528\u8BF7\u5728 gateway \u542F\u52A8\u540E\u8FD0\u884C openclaw wechat bind \u5B8C\u6210\u8BBE\u5907\u7ED1\u5B9A\u3002");
|
|
9637
|
+
} catch (err) {
|
|
9638
|
+
console.error(`
|
|
10044
9639
|
\u767B\u5F55\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
10045
|
-
|
|
9640
|
+
process.exit(1);
|
|
9641
|
+
}
|
|
10046
9642
|
}
|
|
10047
9643
|
});
|
|
10048
|
-
wechat.command("logout").description("\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u5FAE\u4FE1\u767B\u5F55\u6001").action(() => {
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
9644
|
+
wechat.command("logout").description("\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u5FAE\u4FE1\u767B\u5F55\u6001").action(async () => {
|
|
9645
|
+
await writeChannelConfig({ loginMode: void 0, qclaw: void 0, workbuddy: void 0 });
|
|
9646
|
+
console.log("\u5DF2\u6E05\u9664\u767B\u5F55\u6001\u3002");
|
|
9647
|
+
});
|
|
9648
|
+
wechat.command("bind").description("\u83B7\u53D6\u8BBE\u5907\u7ED1\u5B9A\u94FE\u63A5\uFF08\u9700\u5148\u767B\u5F55\uFF09").action(async () => {
|
|
9649
|
+
const channelCfg = readChannelConfig();
|
|
9650
|
+
if (channelCfg.loginMode === "workbuddy") {
|
|
9651
|
+
const creds = channelCfg.workbuddy;
|
|
9652
|
+
if (!creds?.accessToken) {
|
|
9653
|
+
console.error("\u8BF7\u5148\u767B\u5F55: openclaw wechat login\uFF08\u9009\u62E9 2\uFF09");
|
|
9654
|
+
process.exit(1);
|
|
9655
|
+
}
|
|
9656
|
+
const cbApi = new CodeBuddyAPI(creds.baseUrl);
|
|
9657
|
+
cbApi.accessToken = creds.accessToken;
|
|
9658
|
+
cbApi.refreshToken = creds.refreshToken || "";
|
|
9659
|
+
cbApi.userId = creds.userId || "";
|
|
9660
|
+
cbApi.hostId = creds.hostId || cbApi.hostId;
|
|
9661
|
+
try {
|
|
9662
|
+
const refreshed = await cbApi.doRefreshToken();
|
|
9663
|
+
await writeChannelConfig({
|
|
9664
|
+
workbuddy: { ...creds, accessToken: refreshed.accessToken, refreshToken: refreshed.refreshToken }
|
|
9665
|
+
});
|
|
9666
|
+
} catch {
|
|
9667
|
+
console.warn("token \u5237\u65B0\u5931\u8D25\uFF0C\u4F7F\u7528\u65E7 token \u7EE7\u7EED...");
|
|
9668
|
+
}
|
|
9669
|
+
const sessionId = cbApi.buildSessionId();
|
|
9670
|
+
try {
|
|
9671
|
+
const linkResult = await cbApi.wechatkfGetLink(sessionId);
|
|
9672
|
+
if (linkResult.success && linkResult.url) {
|
|
9673
|
+
console.log("\n" + "=".repeat(64));
|
|
9674
|
+
console.log("\u8BF7\u5728\u5FAE\u4FE1\u4E2D\u6253\u5F00\u4EE5\u4E0B\u94FE\u63A5\u7ED1\u5B9A\u5FAE\u4FE1\u5BA2\u670D\u53F7\uFF1A");
|
|
9675
|
+
console.log("");
|
|
9676
|
+
console.log(linkResult.url);
|
|
9677
|
+
console.log("");
|
|
9678
|
+
console.log("\u7ED1\u5B9A\u5B8C\u6210\u540E\u5373\u53EF\u901A\u8FC7\u5FAE\u4FE1\u5BA2\u670D\u53F7\u5BF9\u8BDD\u3002");
|
|
9679
|
+
console.log("=".repeat(64));
|
|
9680
|
+
} else {
|
|
9681
|
+
console.error(`\u83B7\u53D6\u7ED1\u5B9A\u94FE\u63A5\u5931\u8D25: ${linkResult.message ?? "\u672A\u77E5\u9519\u8BEF"}`);
|
|
9682
|
+
}
|
|
9683
|
+
} catch (err) {
|
|
9684
|
+
console.error(`\u83B7\u53D6\u7ED1\u5B9A\u94FE\u63A5\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
9685
|
+
process.exit(1);
|
|
9686
|
+
}
|
|
9687
|
+
return;
|
|
9688
|
+
}
|
|
9689
|
+
if (channelCfg.loginMode === "qclaw") {
|
|
9690
|
+
const creds = channelCfg.qclaw;
|
|
9691
|
+
if (!creds?.channelToken) {
|
|
9692
|
+
console.error("\u8BF7\u5148\u767B\u5F55: openclaw wechat login\uFF08\u9009\u62E9 1\uFF09");
|
|
9693
|
+
process.exit(1);
|
|
9694
|
+
}
|
|
9695
|
+
const envName = channelCfg.environment ? String(channelCfg.environment) : "production";
|
|
9696
|
+
const env = getEnvironment(envName);
|
|
9697
|
+
const api2 = new QClawAPI(env, creds.guid, creds.jwtToken);
|
|
9698
|
+
api2.userId = creds.userId || "";
|
|
9699
|
+
const loginKey = creds.userInfo?.loginKey;
|
|
9700
|
+
if (loginKey) api2.loginKey = loginKey;
|
|
9701
|
+
const bindResult = await performDeviceBinding({ api: api2 });
|
|
9702
|
+
if (!bindResult.success) {
|
|
9703
|
+
console.error(bindResult.message);
|
|
9704
|
+
process.exit(1);
|
|
9705
|
+
}
|
|
9706
|
+
return;
|
|
9707
|
+
}
|
|
9708
|
+
console.error("\u8BF7\u5148\u767B\u5F55: openclaw wechat login");
|
|
9709
|
+
process.exit(1);
|
|
10053
9710
|
});
|
|
10054
9711
|
},
|
|
10055
9712
|
{ commands: ["wechat"] }
|
|
@@ -10059,18 +9716,21 @@ CodeBuddy \u767B\u5F55\u5931\u8D25: ${err instanceof Error ? err.message : Strin
|
|
|
10059
9716
|
description: "\u624B\u52A8\u6267\u884C\u5FAE\u4FE1\u626B\u7801\u767B\u5F55\uFF0C\u83B7\u53D6 channel token",
|
|
10060
9717
|
handler: async ({ config }) => {
|
|
10061
9718
|
const channelCfg = config?.channels?.["wechat-access-unqclawed"];
|
|
10062
|
-
const bypassInvite = channelCfg?.bypassInvite === true;
|
|
10063
|
-
const authStatePath = channelCfg?.authStatePath ? String(channelCfg.authStatePath) : void 0;
|
|
10064
9719
|
const envName = channelCfg?.environment ? String(channelCfg.environment) : "production";
|
|
10065
9720
|
const env = getEnvironment(envName);
|
|
10066
9721
|
const guid = getDeviceGuid();
|
|
10067
9722
|
try {
|
|
10068
|
-
const credentials = await performLogin({
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
9723
|
+
const credentials = await performLogin({ guid, env });
|
|
9724
|
+
const qclawCreds = {
|
|
9725
|
+
jwtToken: credentials.jwtToken,
|
|
9726
|
+
channelToken: credentials.channelToken,
|
|
9727
|
+
apiKey: credentials.apiKey,
|
|
9728
|
+
guid: credentials.guid,
|
|
9729
|
+
userId: String(credentials.userInfo?.user_id ?? ""),
|
|
9730
|
+
wsUrl: env.wechatWsUrl,
|
|
9731
|
+
userInfo: credentials.userInfo
|
|
9732
|
+
};
|
|
9733
|
+
await writeChannelConfig({ loginMode: "qclaw", qclaw: qclawCreds });
|
|
10074
9734
|
return { text: `\u767B\u5F55\u6210\u529F! token: ${credentials.channelToken.substring(0, 6)}... (\u5DF2\u4FDD\u5B58\uFF0C\u91CD\u542F Gateway \u751F\u6548)` };
|
|
10075
9735
|
} catch (err) {
|
|
10076
9736
|
return { text: `\u767B\u5F55\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`, isError: true };
|
|
@@ -10080,11 +9740,9 @@ CodeBuddy \u767B\u5F55\u5931\u8D25: ${err instanceof Error ? err.message : Strin
|
|
|
10080
9740
|
api.registerCommand?.({
|
|
10081
9741
|
name: "wechat-logout",
|
|
10082
9742
|
description: "\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u5FAE\u4FE1\u767B\u5F55\u6001",
|
|
10083
|
-
handler: async (
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
clearState(authStatePath);
|
|
10087
|
-
return { text: "\u5DF2\u6E05\u9664\u767B\u5F55\u6001\uFF0C\u4E0B\u6B21\u542F\u52A8\u5C06\u91CD\u65B0\u626B\u7801\u767B\u5F55\u3002" };
|
|
9743
|
+
handler: async () => {
|
|
9744
|
+
await writeChannelConfig({ loginMode: void 0, qclaw: void 0, workbuddy: void 0 });
|
|
9745
|
+
return { text: "\u5DF2\u6E05\u9664\u767B\u5F55\u6001\uFF0C\u4E0B\u6B21\u542F\u52A8\u5C06\u91CD\u65B0\u767B\u5F55\u3002" };
|
|
10088
9746
|
}
|
|
10089
9747
|
});
|
|
10090
9748
|
console.log("[wechat-access] \u817E\u8BAF\u901A\u8DEF\u63D2\u4EF6\u5DF2\u6CE8\u518C");
|