@wu529778790/open-im 1.8.3-beta.5 → 1.8.3-beta.7
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/config.d.ts +1 -22
- package/dist/config.js +7 -52
- package/dist/setup.js +46 -59
- package/dist/shared/ai-task.test.js +0 -4
- package/dist/wechat/client.d.ts +1 -5
- package/dist/wechat/client.js +5 -56
- package/dist/wechat/transport.d.ts +1 -3
- package/dist/wechat/transport.js +1 -2
- package/package.json +1 -1
- package/dist/wechat/auth/device-bind.d.ts +0 -13
- package/dist/wechat/auth/device-bind.js +0 -75
- package/dist/wechat/auth/device-guid.d.ts +0 -5
- package/dist/wechat/auth/device-guid.js +0 -28
- package/dist/wechat/auth/environments.d.ts +0 -5
- package/dist/wechat/auth/environments.js +0 -21
- package/dist/wechat/auth/index.d.ts +0 -7
- package/dist/wechat/auth/index.js +0 -5
- package/dist/wechat/auth/qclaw-api.d.ts +0 -26
- package/dist/wechat/auth/qclaw-api.js +0 -100
- package/dist/wechat/auth/types.d.ts +0 -18
- package/dist/wechat/auth/types.js +0 -4
- package/dist/wechat/auth/wechat-login.d.ts +0 -17
- package/dist/wechat/auth/wechat-login.js +0 -168
- package/dist/wechat/qclaw-transport.d.ts +0 -66
- package/dist/wechat/qclaw-transport.js +0 -303
package/dist/config.d.ts
CHANGED
|
@@ -6,14 +6,8 @@ export interface Config {
|
|
|
6
6
|
telegramBotToken?: string;
|
|
7
7
|
feishuAppId?: string;
|
|
8
8
|
feishuAppSecret?: string;
|
|
9
|
-
|
|
10
|
-
wechatAppSecret?: string;
|
|
11
|
-
wechatToken?: string;
|
|
12
|
-
wechatJwtToken?: string;
|
|
13
|
-
wechatLoginKey?: string;
|
|
14
|
-
wechatGuid?: string;
|
|
9
|
+
/** WorkBuddy 用户 ID(可选,与 platforms.wechat.userId 二选一,环境变量 WECHAT_USER_ID) */
|
|
15
10
|
wechatUserId?: string;
|
|
16
|
-
wechatWsUrl?: string;
|
|
17
11
|
weworkCorpId?: string;
|
|
18
12
|
weworkSecret?: string;
|
|
19
13
|
weworkWsUrl?: string;
|
|
@@ -64,12 +58,6 @@ export interface Config {
|
|
|
64
58
|
wechat?: {
|
|
65
59
|
enabled: boolean;
|
|
66
60
|
aiCommand?: AiCommand;
|
|
67
|
-
loginMode?: 'qclaw' | 'workbuddy';
|
|
68
|
-
wsUrl?: string;
|
|
69
|
-
token?: string;
|
|
70
|
-
jwtToken?: string;
|
|
71
|
-
loginKey?: string;
|
|
72
|
-
guid?: string;
|
|
73
61
|
userId?: string;
|
|
74
62
|
allowedUserIds: string[];
|
|
75
63
|
workbuddyAccessToken?: string;
|
|
@@ -124,17 +112,8 @@ interface FilePlatformQQ {
|
|
|
124
112
|
}
|
|
125
113
|
interface FilePlatformWechat {
|
|
126
114
|
enabled?: boolean;
|
|
127
|
-
appId?: string;
|
|
128
|
-
appSecret?: string;
|
|
129
115
|
aiCommand?: AiCommand;
|
|
130
|
-
/** 连接模式:qclaw(QClaw JPRX 网关)或 workbuddy(Centrifuge) */
|
|
131
|
-
loginMode?: 'qclaw' | 'workbuddy';
|
|
132
|
-
token?: string;
|
|
133
|
-
jwtToken?: string;
|
|
134
|
-
loginKey?: string;
|
|
135
|
-
guid?: string;
|
|
136
116
|
userId?: string;
|
|
137
|
-
wsUrl?: string;
|
|
138
117
|
allowedUserIds?: string[];
|
|
139
118
|
workbuddyAccessToken?: string;
|
|
140
119
|
workbuddyRefreshToken?: string;
|
package/dist/config.js
CHANGED
|
@@ -179,10 +179,10 @@ export function needsSetup() {
|
|
|
179
179
|
return false;
|
|
180
180
|
if (process.env.QQ_BOT_APPID && process.env.QQ_BOT_SECRET)
|
|
181
181
|
return false;
|
|
182
|
-
if (process.env.
|
|
183
|
-
|
|
184
|
-
if (process.env.WECHAT_TOKEN && process.env.WECHAT_GUID && process.env.WECHAT_USER_ID)
|
|
182
|
+
if (process.env.WECHAT_WORKBUDDY_ACCESS_TOKEN &&
|
|
183
|
+
process.env.WECHAT_WORKBUDDY_REFRESH_TOKEN) {
|
|
185
184
|
return false;
|
|
185
|
+
}
|
|
186
186
|
if (process.env.WEWORK_CORP_ID && process.env.WEWORK_SECRET)
|
|
187
187
|
return false;
|
|
188
188
|
if (process.env.DINGTALK_CLIENT_ID && process.env.DINGTALK_CLIENT_SECRET)
|
|
@@ -197,8 +197,7 @@ export function needsSetup() {
|
|
|
197
197
|
const hasTelegram = !!tg?.botToken;
|
|
198
198
|
const hasFeishu = !!(fs?.appId && fs?.appSecret);
|
|
199
199
|
const hasQQ = !!(qq?.appId && qq?.secret);
|
|
200
|
-
|
|
201
|
-
const hasWechat = !!(wc?.token && wc?.guid && wc?.userId) || !!(wc?.appId && wc?.appSecret);
|
|
200
|
+
const hasWechat = !!(wc?.workbuddyAccessToken && wc?.workbuddyRefreshToken);
|
|
202
201
|
// 企业微信只需要 corpId 和 secret
|
|
203
202
|
const hasWework = !!(ww?.corpId && ww?.secret);
|
|
204
203
|
const hasDingtalk = !!(dt?.clientId && dt?.clientSecret);
|
|
@@ -248,25 +247,8 @@ export function loadConfig() {
|
|
|
248
247
|
fileQQ?.appId;
|
|
249
248
|
const qqSecret = process.env.QQ_BOT_SECRET ??
|
|
250
249
|
fileQQ?.secret;
|
|
251
|
-
// 微信支持两种协议:
|
|
252
|
-
// 1. AGP 协议:token + guid + userId(推荐)
|
|
253
|
-
// 2. 标准协议:appId + appSecret
|
|
254
|
-
const wechatLoginMode = fileWechat?.loginMode ?? 'workbuddy';
|
|
255
|
-
const wechatToken = process.env.WECHAT_TOKEN ??
|
|
256
|
-
fileWechat?.token;
|
|
257
|
-
const wechatJwtToken = fileWechat?.jwtToken;
|
|
258
|
-
const wechatLoginKey = fileWechat?.loginKey;
|
|
259
|
-
const wechatGuid = process.env.WECHAT_GUID ??
|
|
260
|
-
fileWechat?.guid;
|
|
261
250
|
const wechatUserId = process.env.WECHAT_USER_ID ??
|
|
262
251
|
fileWechat?.userId;
|
|
263
|
-
const wechatAppId = process.env.WECHAT_APP_ID ??
|
|
264
|
-
fileWechat?.appId;
|
|
265
|
-
const wechatAppSecret = process.env.WECHAT_APP_SECRET ??
|
|
266
|
-
fileWechat?.appSecret;
|
|
267
|
-
const wechatWsUrl = process.env.WECHAT_WS_URL ??
|
|
268
|
-
fileWechat?.wsUrl;
|
|
269
|
-
// 微信 WorkBuddy 模式凭证(loginMode === 'workbuddy' 时使用)
|
|
270
252
|
const wechatWorkbuddyAccessToken = process.env.WECHAT_WORKBUDDY_ACCESS_TOKEN ??
|
|
271
253
|
fileWechat?.workbuddyAccessToken;
|
|
272
254
|
const wechatWorkbuddyRefreshToken = process.env.WECHAT_WORKBUDDY_REFRESH_TOKEN ??
|
|
@@ -312,15 +294,8 @@ export function loadConfig() {
|
|
|
312
294
|
const telegramEnabled = !!telegramBotToken && (telegramEnabledFlag !== false);
|
|
313
295
|
const feishuEnabled = !!(feishuAppId && feishuAppSecret) && (feishuEnabledFlag !== false);
|
|
314
296
|
const qqEnabled = !!(qqAppId && qqSecret) && (qqEnabledFlag !== false);
|
|
315
|
-
// 微信启用条件:
|
|
316
|
-
// - qclaw 模式:AGP 协议凭证(token + guid + userId)或 标准协议凭证(appId + appSecret)
|
|
317
|
-
// - workbuddy 模式:workbuddy OAuth 凭证
|
|
318
|
-
const hasWechatAGPCreds = !!(wechatToken && wechatGuid && wechatUserId);
|
|
319
|
-
const hasWechatStandardCreds = !!(wechatAppId && wechatAppSecret);
|
|
320
297
|
const hasWechatWorkbuddyCreds = !!(wechatWorkbuddyAccessToken && wechatWorkbuddyRefreshToken);
|
|
321
|
-
const wechatEnabled = (
|
|
322
|
-
? hasWechatWorkbuddyCreds
|
|
323
|
-
: (hasWechatAGPCreds || hasWechatStandardCreds)) && (wechatEnabledFlag !== false);
|
|
298
|
+
const wechatEnabled = hasWechatWorkbuddyCreds && (wechatEnabledFlag !== false);
|
|
324
299
|
// 企业微信只需要 corpId (botId) 和 secret
|
|
325
300
|
const weworkEnabled = !!(weworkCorpId && weworkSecret) && (weworkEnabledFlag !== false);
|
|
326
301
|
const dingtalkEnabled = !!(dingtalkClientId && dingtalkClientSecret) && (dingtalkEnabledFlag !== false);
|
|
@@ -573,12 +548,6 @@ export function loadConfig() {
|
|
|
573
548
|
? {
|
|
574
549
|
enabled: true,
|
|
575
550
|
aiCommand: normalizeAiCommand(file.platforms?.wechat?.aiCommand, aiCommand),
|
|
576
|
-
loginMode: wechatLoginMode,
|
|
577
|
-
wsUrl: wechatWsUrl,
|
|
578
|
-
token: wechatToken,
|
|
579
|
-
jwtToken: wechatJwtToken,
|
|
580
|
-
loginKey: wechatLoginKey,
|
|
581
|
-
guid: wechatGuid,
|
|
582
551
|
userId: wechatUserId,
|
|
583
552
|
allowedUserIds: wechatAllowedUserIds,
|
|
584
553
|
workbuddyAccessToken: wechatWorkbuddyAccessToken,
|
|
@@ -589,12 +558,6 @@ export function loadConfig() {
|
|
|
589
558
|
: {
|
|
590
559
|
enabled: false,
|
|
591
560
|
aiCommand: normalizeAiCommand(file.platforms?.wechat?.aiCommand, aiCommand),
|
|
592
|
-
loginMode: wechatLoginMode,
|
|
593
|
-
wsUrl: wechatWsUrl,
|
|
594
|
-
token: wechatToken,
|
|
595
|
-
jwtToken: wechatJwtToken,
|
|
596
|
-
loginKey: wechatLoginKey,
|
|
597
|
-
guid: wechatGuid,
|
|
598
561
|
userId: wechatUserId,
|
|
599
562
|
allowedUserIds: wechatAllowedUserIds,
|
|
600
563
|
workbuddyAccessToken: wechatWorkbuddyAccessToken,
|
|
@@ -657,14 +620,7 @@ export function loadConfig() {
|
|
|
657
620
|
feishuAppSecret: feishuAppSecret ?? '',
|
|
658
621
|
qqAppId: qqAppId ?? '',
|
|
659
622
|
qqSecret: qqSecret ?? '',
|
|
660
|
-
wechatAppId: wechatAppId ?? '',
|
|
661
|
-
wechatAppSecret: wechatAppSecret ?? '',
|
|
662
|
-
wechatToken: wechatToken,
|
|
663
|
-
wechatJwtToken: wechatJwtToken,
|
|
664
|
-
wechatLoginKey: wechatLoginKey,
|
|
665
|
-
wechatGuid: wechatGuid,
|
|
666
623
|
wechatUserId: wechatUserId,
|
|
667
|
-
wechatWsUrl: wechatWsUrl,
|
|
668
624
|
weworkCorpId: weworkCorpId ?? '',
|
|
669
625
|
weworkSecret: weworkSecret ?? '',
|
|
670
626
|
weworkWsUrl: weworkWsUrl,
|
|
@@ -707,9 +663,8 @@ export function getPlatformsWithCredentials(config) {
|
|
|
707
663
|
r.push('wework');
|
|
708
664
|
if (config.dingtalkClientId && config.dingtalkClientSecret)
|
|
709
665
|
r.push('dingtalk');
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
if (hasWechat)
|
|
666
|
+
const wc = config.platforms.wechat;
|
|
667
|
+
if (wc?.workbuddyAccessToken && wc?.workbuddyRefreshToken)
|
|
713
668
|
r.push('wechat');
|
|
714
669
|
return r;
|
|
715
670
|
}
|
package/dist/setup.js
CHANGED
|
@@ -43,9 +43,8 @@ function getConfiguredPlatforms(existing) {
|
|
|
43
43
|
return !!(p.appId && p.appSecret);
|
|
44
44
|
if (k === "qq")
|
|
45
45
|
return !!(p.appId && p.secret);
|
|
46
|
-
// 微信支持 AGP 协议(token + guid + userId)或标准协议(appId + appSecret)
|
|
47
46
|
if (k === "wechat")
|
|
48
|
-
return !!(p.
|
|
47
|
+
return !!(p.workbuddyAccessToken && p.workbuddyRefreshToken);
|
|
49
48
|
if (k === "wework")
|
|
50
49
|
return !!(p.corpId && p.secret);
|
|
51
50
|
if (k === "dingtalk")
|
|
@@ -116,16 +115,16 @@ function printManualInstructions(configPath) {
|
|
|
116
115
|
},
|
|
117
116
|
"wechat": {
|
|
118
117
|
"enabled": false,
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
"
|
|
118
|
+
"workbuddyAccessToken": "(由 open-im init 在浏览器完成 WorkBuddy 登录后自动写入)",
|
|
119
|
+
"workbuddyRefreshToken": "",
|
|
120
|
+
"userId": "",
|
|
122
121
|
"allowedUserIds": ["允许访问的微信用户 ID(可选)"]
|
|
123
122
|
}
|
|
124
123
|
}
|
|
125
124
|
}`);
|
|
126
125
|
console.log("");
|
|
127
126
|
console.log("提示:至少需要配置 Telegram、Feishu、QQ、WeChat、WeWork 或 DingTalk 其中一个平台");
|
|
128
|
-
console.log("或设置环境变量: TELEGRAM_BOT_TOKEN=xxx、FEISHU_APP_ID=xxx、QQ_BOT_APPID=xxx、
|
|
127
|
+
console.log("或设置环境变量: TELEGRAM_BOT_TOKEN=xxx、FEISHU_APP_ID=xxx、QQ_BOT_APPID=xxx、WECHAT_WORKBUDDY_ACCESS_TOKEN=xxx、WEWORK_CORP_ID=xxx 或 DINGTALK_CLIENT_ID=xxx 后再运行");
|
|
129
128
|
console.log("");
|
|
130
129
|
}
|
|
131
130
|
const CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
|
|
@@ -274,7 +273,7 @@ export async function runInteractiveSetup() {
|
|
|
274
273
|
const hasFs = !!(existing?.platforms?.feishu?.appId && existing?.platforms?.feishu?.appSecret);
|
|
275
274
|
const hasQq = !!(existing?.platforms?.qq?.appId && existing?.platforms?.qq?.secret);
|
|
276
275
|
const wc = existing?.platforms?.wechat;
|
|
277
|
-
const hasWc = !!(wc?.
|
|
276
|
+
const hasWc = !!(wc?.workbuddyAccessToken && wc?.workbuddyRefreshToken);
|
|
278
277
|
const hasWw = !!(existing?.platforms?.wework?.corpId && existing?.platforms?.wework?.secret);
|
|
279
278
|
const hasDt = !!(existing?.platforms?.dingtalk?.clientId && existing?.platforms?.dingtalk?.clientSecret);
|
|
280
279
|
// 第一步:选择平台(在选项和提示中显示已配置项)
|
|
@@ -308,7 +307,7 @@ export async function runInteractiveSetup() {
|
|
|
308
307
|
value: "dingtalk",
|
|
309
308
|
},
|
|
310
309
|
{
|
|
311
|
-
title: "微信 (WeChat)
|
|
310
|
+
title: "微信 (WeChat)" +
|
|
312
311
|
(hasWc ? " ✓已配置" : ""),
|
|
313
312
|
value: "wechat",
|
|
314
313
|
},
|
|
@@ -423,30 +422,25 @@ export async function runInteractiveSetup() {
|
|
|
423
422
|
}
|
|
424
423
|
if (selectedPlatforms.includes("wechat")) {
|
|
425
424
|
const wc = existing?.platforms?.wechat;
|
|
426
|
-
const hasToken = !!(wc?.token && wc?.guid && wc?.userId);
|
|
427
425
|
const hasWbCreds = !!(wc?.workbuddyAccessToken && wc?.workbuddyRefreshToken);
|
|
428
426
|
const wechatModeResp = await prompts({
|
|
429
427
|
type: "select",
|
|
430
428
|
name: "mode",
|
|
431
|
-
message: "
|
|
429
|
+
message: "微信(WorkBuddy / CodeBuddy OAuth)",
|
|
432
430
|
choices: [
|
|
433
431
|
{
|
|
434
|
-
title: "
|
|
435
|
-
value: "
|
|
432
|
+
title: "在浏览器中完成 CodeBuddy 登录并绑定微信客服(推荐)",
|
|
433
|
+
value: "oauth",
|
|
436
434
|
},
|
|
437
435
|
{
|
|
438
|
-
title: "
|
|
439
|
-
value: "qr",
|
|
440
|
-
},
|
|
441
|
-
{
|
|
442
|
-
title: "使用已有配置" + (hasToken || hasWbCreds ? " ✓" : "(需已配置)"),
|
|
436
|
+
title: "使用已有 WorkBuddy 凭证" + (hasWbCreds ? " ✓" : ""),
|
|
443
437
|
value: "keep",
|
|
444
|
-
disabled: !
|
|
438
|
+
disabled: !hasWbCreds,
|
|
445
439
|
},
|
|
446
440
|
],
|
|
447
|
-
initial: hasWbCreds ?
|
|
441
|
+
initial: hasWbCreds ? 1 : 0,
|
|
448
442
|
}, { onCancel });
|
|
449
|
-
if (wechatModeResp.mode === "
|
|
443
|
+
if (wechatModeResp.mode === "oauth") {
|
|
450
444
|
console.log("\n正在启动 WorkBuddy OAuth 登录...\n");
|
|
451
445
|
try {
|
|
452
446
|
const { WorkBuddyOAuth } = await import("./workbuddy/oauth.js");
|
|
@@ -467,12 +461,30 @@ export async function runInteractiveSetup() {
|
|
|
467
461
|
}
|
|
468
462
|
const userId = accountInfo?.uid?.toString() ?? "";
|
|
469
463
|
config.platforms.wechat = {
|
|
470
|
-
loginMode: 'workbuddy',
|
|
471
464
|
enabled: true,
|
|
472
465
|
workbuddyAccessToken: tokenResult.accessToken,
|
|
473
466
|
workbuddyRefreshToken: tokenResult.refreshToken,
|
|
474
467
|
userId,
|
|
475
468
|
};
|
|
469
|
+
console.log("\n正在获取微信客服绑定链接...");
|
|
470
|
+
const sessionId = oauth.buildSessionId();
|
|
471
|
+
const linkResult = await oauth.getWeChatKfLink(sessionId);
|
|
472
|
+
if (linkResult.success && linkResult.url) {
|
|
473
|
+
console.log("\n━━━ 微信客服绑定 ━━━");
|
|
474
|
+
console.log("请复制以下链接,在微信中发给「文件传输助手」并点击打开:");
|
|
475
|
+
console.log(linkResult.url);
|
|
476
|
+
console.log("\n等待绑定完成(最长 5 分钟)...\n");
|
|
477
|
+
const bindResult = await oauth.pollBindStatus(sessionId);
|
|
478
|
+
if (bindResult.bound) {
|
|
479
|
+
console.log(`✅ 微信客服绑定成功!${bindResult.nickname ? ` 用户: ${bindResult.nickname}` : ""}`);
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
console.log("⚠️ 绑定超时,你可以稍后重新运行 open-im init 完成绑定");
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
console.log("⚠️ 获取微信客服链接失败,你可以稍后重新运行 open-im init 完成绑定");
|
|
487
|
+
}
|
|
476
488
|
console.log("\n✅ WorkBuddy 登录成功,配置已保存");
|
|
477
489
|
}
|
|
478
490
|
catch (err) {
|
|
@@ -481,34 +493,7 @@ export async function runInteractiveSetup() {
|
|
|
481
493
|
return false;
|
|
482
494
|
}
|
|
483
495
|
}
|
|
484
|
-
else if (
|
|
485
|
-
console.log("\n正在启动微信扫码登录...\n");
|
|
486
|
-
try {
|
|
487
|
-
const { performWeChatLogin } = await import("./wechat/auth/index.js");
|
|
488
|
-
const credentials = await performWeChatLogin({
|
|
489
|
-
envName: "production",
|
|
490
|
-
appId: wc?.appId || undefined,
|
|
491
|
-
});
|
|
492
|
-
config.platforms.wechat = {
|
|
493
|
-
loginMode: 'qclaw',
|
|
494
|
-
appId: wc?.appId,
|
|
495
|
-
enabled: true,
|
|
496
|
-
token: credentials.channelToken,
|
|
497
|
-
jwtToken: credentials.jwtToken,
|
|
498
|
-
loginKey: credentials.loginKey,
|
|
499
|
-
guid: credentials.guid,
|
|
500
|
-
userId: credentials.userId,
|
|
501
|
-
wsUrl: "wss://mmgrcalltoken.3g.qq.com/agentwss",
|
|
502
|
-
};
|
|
503
|
-
console.log("\n✅ 微信登录成功,配置已获取");
|
|
504
|
-
}
|
|
505
|
-
catch (err) {
|
|
506
|
-
console.error("\n❌ 微信登录失败:", err instanceof Error ? err.message : String(err));
|
|
507
|
-
if (platform === "wechat")
|
|
508
|
-
return false;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
else if (hasToken || hasWbCreds) {
|
|
496
|
+
else if (hasWbCreds) {
|
|
512
497
|
config.platforms.wechat = {
|
|
513
498
|
...wc,
|
|
514
499
|
enabled: true,
|
|
@@ -898,19 +883,21 @@ export async function runInteractiveSetup() {
|
|
|
898
883
|
}
|
|
899
884
|
if (selectedPlatforms.includes("wechat")) {
|
|
900
885
|
const wcConfig = config.platforms?.wechat;
|
|
901
|
-
|
|
902
|
-
|
|
886
|
+
const baseWc = base?.platforms?.wechat;
|
|
887
|
+
const wbOut = {
|
|
903
888
|
enabled: true,
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
jwtToken: wcConfig?.jwtToken ?? base?.platforms?.wechat?.jwtToken,
|
|
908
|
-
loginKey: wcConfig?.loginKey ?? base?.platforms?.wechat?.loginKey,
|
|
909
|
-
guid: wcConfig?.guid ?? base?.platforms?.wechat?.guid,
|
|
910
|
-
userId: wcConfig?.userId ?? base?.platforms?.wechat?.userId,
|
|
911
|
-
wsUrl: wcConfig?.wsUrl ?? base?.platforms?.wechat?.wsUrl,
|
|
889
|
+
workbuddyAccessToken: wcConfig?.workbuddyAccessToken ?? baseWc?.workbuddyAccessToken,
|
|
890
|
+
workbuddyRefreshToken: wcConfig?.workbuddyRefreshToken ?? baseWc?.workbuddyRefreshToken,
|
|
891
|
+
userId: wcConfig?.userId ?? baseWc?.userId ?? "",
|
|
912
892
|
allowedUserIds: wechatIds,
|
|
913
893
|
};
|
|
894
|
+
const wbBaseUrl = wcConfig?.workbuddyBaseUrl ?? baseWc?.workbuddyBaseUrl;
|
|
895
|
+
const wbHostId = wcConfig?.workbuddyHostId ?? baseWc?.workbuddyHostId;
|
|
896
|
+
if (wbBaseUrl)
|
|
897
|
+
wbOut.workbuddyBaseUrl = wbBaseUrl;
|
|
898
|
+
if (wbHostId)
|
|
899
|
+
wbOut.workbuddyHostId = wbHostId;
|
|
900
|
+
out.platforms.wechat = wbOut;
|
|
914
901
|
}
|
|
915
902
|
else if (basePlatforms?.wechat) {
|
|
916
903
|
outPlatforms.wechat = {
|
|
@@ -40,11 +40,7 @@ describe("runAITask", () => {
|
|
|
40
40
|
codebuddyTimeoutMs: 600000,
|
|
41
41
|
claudeModel: "",
|
|
42
42
|
codexProxy: "",
|
|
43
|
-
wechatToken: "",
|
|
44
|
-
wechatGuid: "",
|
|
45
43
|
wechatUserId: "",
|
|
46
|
-
wechatAppId: "",
|
|
47
|
-
wechatAppSecret: "",
|
|
48
44
|
dingtalkClientId: "",
|
|
49
45
|
dingtalkClientSecret: "",
|
|
50
46
|
qqAppId: "",
|
package/dist/wechat/client.d.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WeChat Client
|
|
3
|
-
*
|
|
4
|
-
* 支持两种通道:
|
|
5
|
-
* - workbuddy: 通过 Centrifuge WebSocket 连接(默认)
|
|
6
|
-
* - qclaw: 直连腾讯 JPRX 网关
|
|
2
|
+
* WeChat Client — 通过 WorkBuddy(CodeBuddy OAuth + Centrifuge)连接微信
|
|
7
3
|
*/
|
|
8
4
|
import type { Config } from '../config.js';
|
|
9
5
|
import type { AGPEnvelope, WeChatChannelState, WeChatToken } from './types.js';
|
package/dist/wechat/client.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WeChat Client
|
|
3
|
-
*
|
|
4
|
-
* 支持两种通道:
|
|
5
|
-
* - workbuddy: 通过 Centrifuge WebSocket 连接(默认)
|
|
6
|
-
* - qclaw: 直连腾讯 JPRX 网关
|
|
2
|
+
* WeChat Client — 通过 WorkBuddy(CodeBuddy OAuth + Centrifuge)连接微信
|
|
7
3
|
*/
|
|
8
|
-
import { readFileSync,
|
|
4
|
+
import { readFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
9
5
|
import { join } from 'node:path';
|
|
10
6
|
import { createLogger } from '../logger.js';
|
|
11
|
-
import { QClawTransport } from './qclaw-transport.js';
|
|
12
7
|
import { WorkBuddyTransport } from './workbuddy-transport.js';
|
|
13
8
|
const log = createLogger('WeChat');
|
|
14
9
|
const TOKEN_FILE = 'wechat-token.json';
|
|
@@ -17,7 +12,6 @@ let transport = null;
|
|
|
17
12
|
let channelState = 'disconnected';
|
|
18
13
|
let currentToken = null;
|
|
19
14
|
let tokenStoragePath = null;
|
|
20
|
-
let isStopping = false;
|
|
21
15
|
// Event handlers
|
|
22
16
|
let messageHandler = null;
|
|
23
17
|
let stateChangeHandler = null;
|
|
@@ -27,7 +21,6 @@ let stateChangeHandler = null;
|
|
|
27
21
|
export async function dispatchIncomingAGPEnvelope(envelope, handler) {
|
|
28
22
|
switch (envelope.method) {
|
|
29
23
|
case 'ping':
|
|
30
|
-
// Respond to ping with pong via transport
|
|
31
24
|
if (transport) {
|
|
32
25
|
transport.send('ping', { timestamp: Date.now() }, envelope.msg_id);
|
|
33
26
|
}
|
|
@@ -64,25 +57,14 @@ export function getCurrentToken() {
|
|
|
64
57
|
export async function initWeChat(config, eventHandler, onStateChange) {
|
|
65
58
|
messageHandler = eventHandler;
|
|
66
59
|
stateChangeHandler = onStateChange ?? null;
|
|
67
|
-
isStopping = false;
|
|
68
|
-
// Set up token storage path
|
|
69
60
|
const baseDir = config.logDir ?? join(process.env.HOME ?? '', '.open-im');
|
|
70
61
|
tokenStoragePath = join(baseDir, 'data');
|
|
71
62
|
if (!existsSync(tokenStoragePath)) {
|
|
72
63
|
mkdirSync(tokenStoragePath, { recursive: true });
|
|
73
64
|
}
|
|
74
|
-
// Load existing token if available
|
|
75
65
|
await loadToken();
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
log.info(`Initializing WeChat with loginMode: ${loginMode}`);
|
|
79
|
-
if (loginMode === 'workbuddy') {
|
|
80
|
-
transport = createWorkBuddyTransport(config);
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
transport = createQClawTransport(config);
|
|
84
|
-
}
|
|
85
|
-
// Wire up transport callbacks
|
|
66
|
+
log.info('Initializing WeChat (WorkBuddy)');
|
|
67
|
+
transport = createWorkBuddyTransport(config);
|
|
86
68
|
transport.onMessage(async (envelope) => {
|
|
87
69
|
await dispatchIncomingAGPEnvelope(envelope, messageHandler);
|
|
88
70
|
});
|
|
@@ -93,25 +75,8 @@ export async function initWeChat(config, eventHandler, onStateChange) {
|
|
|
93
75
|
}
|
|
94
76
|
});
|
|
95
77
|
await transport.start();
|
|
96
|
-
log.info(
|
|
78
|
+
log.info('WeChat client initialized (WorkBuddy)');
|
|
97
79
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Create QClaw transport from config
|
|
100
|
-
*/
|
|
101
|
-
function createQClawTransport(config) {
|
|
102
|
-
const qclawConfig = {
|
|
103
|
-
channelToken: config.wechatToken,
|
|
104
|
-
jwtToken: config.wechatJwtToken ?? config.platforms.wechat?.jwtToken,
|
|
105
|
-
loginKey: config.wechatLoginKey ?? config.platforms.wechat?.loginKey,
|
|
106
|
-
guid: config.wechatGuid,
|
|
107
|
-
userId: config.wechatUserId,
|
|
108
|
-
wsUrl: config.wechatWsUrl,
|
|
109
|
-
};
|
|
110
|
-
return new QClawTransport(qclawConfig);
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Create WorkBuddy transport from config
|
|
114
|
-
*/
|
|
115
80
|
function createWorkBuddyTransport(config) {
|
|
116
81
|
const wp = config.platforms.wechat;
|
|
117
82
|
const workbuddyConfig = {
|
|
@@ -137,7 +102,6 @@ export function sendAGPMessage(method, payload, replyTo) {
|
|
|
137
102
|
* Stop WeChat client
|
|
138
103
|
*/
|
|
139
104
|
export function stopWeChat() {
|
|
140
|
-
isStopping = true;
|
|
141
105
|
if (transport) {
|
|
142
106
|
transport.stop();
|
|
143
107
|
transport = null;
|
|
@@ -170,18 +134,3 @@ async function loadToken() {
|
|
|
170
134
|
}
|
|
171
135
|
}
|
|
172
136
|
}
|
|
173
|
-
/**
|
|
174
|
-
* Save token to storage
|
|
175
|
-
*/
|
|
176
|
-
function saveToken() {
|
|
177
|
-
if (!currentToken || !tokenStoragePath)
|
|
178
|
-
return;
|
|
179
|
-
try {
|
|
180
|
-
const tokenPath = join(tokenStoragePath, TOKEN_FILE);
|
|
181
|
-
writeFileSync(tokenPath, JSON.stringify(currentToken, null, 2), 'utf-8');
|
|
182
|
-
log.info('Token saved to storage');
|
|
183
|
-
}
|
|
184
|
-
catch (err) {
|
|
185
|
-
log.error('Error saving token:', err);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WeChat Transport Interface
|
|
3
|
-
* 抽象 QClaw 和 WorkBuddy 两种 WebSocket 传输方式
|
|
2
|
+
* WeChat Transport Interface — WorkBuddy Centrifuge 实现
|
|
4
3
|
*/
|
|
5
4
|
import type { AGPEnvelope } from './types.js';
|
|
6
5
|
import type { WeChatChannelState } from './types.js';
|
|
@@ -11,7 +10,6 @@ export type StateChangeHandler = (state: WeChatChannelState) => void;
|
|
|
11
10
|
/**
|
|
12
11
|
* WeChat 传输接口
|
|
13
12
|
*
|
|
14
|
-
* 所有传输方式(QClaw WebSocket、WorkBuddy Centrifuge)都实现此接口。
|
|
15
13
|
* client.ts 通过此接口与传输层交互,无需关心底层协议。
|
|
16
14
|
*/
|
|
17
15
|
export interface WeChatTransport {
|
package/dist/wechat/transport.js
CHANGED
package/package.json
CHANGED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 设备绑定:生成企微客服链接,用户在微信中打开后才有对话入口
|
|
3
|
-
*/
|
|
4
|
-
import type { QClawAPI } from './qclaw-api.js';
|
|
5
|
-
export interface DeviceBindResult {
|
|
6
|
-
success: boolean;
|
|
7
|
-
contactUrl?: string;
|
|
8
|
-
message: string;
|
|
9
|
-
}
|
|
10
|
-
export declare function performDeviceBinding(api: QClawAPI, options?: {
|
|
11
|
-
timeoutMs?: number;
|
|
12
|
-
showQr?: (url: string) => void | Promise<void>;
|
|
13
|
-
}): Promise<DeviceBindResult>;
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 设备绑定:生成企微客服链接,用户在微信中打开后才有对话入口
|
|
3
|
-
*/
|
|
4
|
-
import { createLogger } from '../../logger.js';
|
|
5
|
-
const log = createLogger('WeChatDeviceBind');
|
|
6
|
-
function nested(obj, ...keys) {
|
|
7
|
-
let cur = obj;
|
|
8
|
-
for (const k of keys) {
|
|
9
|
-
if (cur == null || typeof cur !== 'object')
|
|
10
|
-
return undefined;
|
|
11
|
-
cur = cur[k];
|
|
12
|
-
}
|
|
13
|
-
return cur;
|
|
14
|
-
}
|
|
15
|
-
const DEFAULT_OPEN_KFID = 'wkzLlJLAAAfbxEV3ZcS-lHZxkaKmpejQ';
|
|
16
|
-
const POLL_INTERVAL_MS = 2000;
|
|
17
|
-
const DEFAULT_TIMEOUT_MS = 180_000; // 3 分钟
|
|
18
|
-
export async function performDeviceBinding(api, options) {
|
|
19
|
-
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
20
|
-
const showQr = options?.showQr;
|
|
21
|
-
log.info('正在调用 4018 接口生成绑定链接...');
|
|
22
|
-
let linkResult;
|
|
23
|
-
try {
|
|
24
|
-
linkResult = await Promise.race([
|
|
25
|
-
api.generateContactLink(DEFAULT_OPEN_KFID),
|
|
26
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('4018 接口超时(15秒)')), 15_000)),
|
|
27
|
-
]);
|
|
28
|
-
}
|
|
29
|
-
catch (err) {
|
|
30
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
31
|
-
return { success: false, message: `生成绑定链接失败: ${msg}` };
|
|
32
|
-
}
|
|
33
|
-
if (!linkResult.success) {
|
|
34
|
-
return { success: false, message: `生成绑定链接失败: ${linkResult.message}` };
|
|
35
|
-
}
|
|
36
|
-
const linkData = linkResult.data;
|
|
37
|
-
const bindUrl = nested(linkData, 'url') ||
|
|
38
|
-
nested(linkData, 'data', 'url') ||
|
|
39
|
-
nested(linkData, 'resp', 'url') ||
|
|
40
|
-
nested(linkData, 'resp', 'data', 'url') ||
|
|
41
|
-
'';
|
|
42
|
-
if (!bindUrl) {
|
|
43
|
-
log.warn('4018 响应结构:', JSON.stringify(linkData, null, 2).slice(0, 500));
|
|
44
|
-
return { success: false, message: '生成绑定链接失败,未返回 URL。服务端响应结构可能已变更' };
|
|
45
|
-
}
|
|
46
|
-
if (showQr) {
|
|
47
|
-
await showQr(bindUrl);
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
log.info('【设备绑定】请复制下方链接,在企微/微信中打开:');
|
|
51
|
-
log.info('打开后会进入客服会话,后续发消息必须在此会话中进行');
|
|
52
|
-
log.info(bindUrl);
|
|
53
|
-
}
|
|
54
|
-
const deadline = Date.now() + timeoutMs;
|
|
55
|
-
while (Date.now() < deadline) {
|
|
56
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
57
|
-
const queryResult = await api.queryDeviceByGuid();
|
|
58
|
-
if (!queryResult.success)
|
|
59
|
-
continue;
|
|
60
|
-
const data = queryResult.data;
|
|
61
|
-
const inner = nested(data, 'data');
|
|
62
|
-
const isBind = nested(data, 'is_bind') ?? inner?.is_bind;
|
|
63
|
-
const nickname = (nested(data, 'nickname') ?? inner?.nickname);
|
|
64
|
-
const externalUserId = (nested(data, 'external_user_id') ?? inner?.external_user_id);
|
|
65
|
-
// 与 wechat-access 一致:nickname 或 external_user_id 表示已绑定
|
|
66
|
-
if (isBind === true || isBind === 1 || !!nickname || !!externalUserId) {
|
|
67
|
-
return { success: true, contactUrl: bindUrl, message: '设备绑定成功' };
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return {
|
|
71
|
-
success: false,
|
|
72
|
-
contactUrl: bindUrl,
|
|
73
|
-
message: '绑定超时,请稍后重新登录并完成绑定',
|
|
74
|
-
};
|
|
75
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 设备唯一标识生成
|
|
3
|
-
* 首次运行时随机生成并持久化到 ~/.open-im/wechat-guid
|
|
4
|
-
*/
|
|
5
|
-
import { randomUUID, createHash } from 'node:crypto';
|
|
6
|
-
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
7
|
-
import { join, dirname } from 'node:path';
|
|
8
|
-
import { APP_HOME } from '../../constants.js';
|
|
9
|
-
const GUID_FILE = join(APP_HOME, 'wechat-guid');
|
|
10
|
-
export function getDeviceGuid() {
|
|
11
|
-
try {
|
|
12
|
-
const existing = readFileSync(GUID_FILE, 'utf-8').trim();
|
|
13
|
-
if (existing)
|
|
14
|
-
return existing;
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
/* 文件不存在 */
|
|
18
|
-
}
|
|
19
|
-
const guid = createHash('md5').update(randomUUID()).digest('hex');
|
|
20
|
-
try {
|
|
21
|
-
mkdirSync(dirname(GUID_FILE), { recursive: true });
|
|
22
|
-
writeFileSync(GUID_FILE, guid, 'utf-8');
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
/* 写入失败不致命 */
|
|
26
|
-
}
|
|
27
|
-
return guid;
|
|
28
|
-
}
|