@henryxiaoyang/wechat-access-unqclawed 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +12 -21
  2. package/index.ts +57 -32
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -16,13 +16,19 @@ openclaw config set channels.wechat-access-unqclawed.enabled true
16
16
 
17
17
  ## 首次登录
18
18
 
19
- 安装并启用后,在终端执行扫码登录:
19
+ ```bash
20
+ openclaw channels login --channel wechat-access-unqclawed
21
+ ```
22
+
23
+ 终端会显示微信二维码(或浏览器链接),用微信扫码并确认后,浏览器会跳转到新页面。
24
+
25
+ 在**另一个终端窗口**中,将浏览器地址栏的完整 URL 或 `code` 参数值写入临时文件:
20
26
 
21
27
  ```bash
22
- openclaw wechat login
28
+ echo "粘贴的URL或code" > ~/.openclaw/wechat-auth-code.tmp
23
29
  ```
24
30
 
25
- 终端会显示微信二维码,扫码授权后 token 自动保存。然后重启 Gateway:
31
+ 原窗口会自动检测并完成登录,token 自动保存。然后重启 Gateway:
26
32
 
27
33
  ```bash
28
34
  openclaw gateway restart
@@ -36,22 +42,6 @@ openclaw gateway restart
36
42
  - 邀请码验证(可配置跳过)
37
43
  - 支持生产/测试环境切换
38
44
 
39
- ## 命令
40
-
41
- ### CLI 命令(终端)
42
-
43
- | 命令 | 说明 |
44
- |------|------|
45
- | `openclaw wechat login` | 交互式微信扫码登录 |
46
- | `openclaw wechat logout` | 清除已保存的登录态 |
47
-
48
- ### 聊天命令(渠道内)
49
-
50
- | 命令 | 说明 |
51
- |------|------|
52
- | `/wechat-login` | 触发扫码登录(QR 码输出到 Gateway 日志) |
53
- | `/wechat-logout` | 清除已保存的登录态 |
54
-
55
45
  ## 配置
56
46
 
57
47
  在 OpenClaw 配置文件的 `channels.wechat-access-unqclawed` 下:
@@ -83,7 +73,7 @@ openclaw gateway restart
83
73
 
84
74
  1. 读取配置中的 `token` — 如果有,直接使用
85
75
  2. 读取本地保存的登录态(`~/.openclaw/wechat-access-auth.json`)
86
- 3. 以上都没有 — 运行 `openclaw wechat login` 手动登录
76
+ 3. 以上都没有 — 运行 `openclaw channels login --channel wechat-access-unqclawed` 手动登录
87
77
 
88
78
  ## 项目结构
89
79
 
@@ -95,7 +85,8 @@ auth/
95
85
  device-guid.ts # 设备 GUID 生成(随机,持久化)
96
86
  qclaw-api.ts # QClaw JPRX 网关 API 客户端
97
87
  state-store.ts # Token 持久化
98
- wechat-login.ts # 扫码登录流程编排
88
+ wechat-login.ts # 扫码登录流程编排(交互式)
89
+ wechat-qr-poll.ts # QR 码生成与轮询
99
90
  websocket/
100
91
  types.ts # AGP 协议类型
101
92
  websocket-client.ts # WebSocket 客户端(连接、心跳、重连)
package/index.ts CHANGED
@@ -2,7 +2,7 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
3
3
  import { WechatAccessWebSocketClient, handlePrompt, handleCancel } from "./websocket/index.js";
4
4
  // import { handleSimpleWecomWebhook } from "./http/webhook.js";
5
- import { setWecomRuntime } from "./common/runtime.js";
5
+ import { setWecomRuntime, getWecomRuntime } from "./common/runtime.js";
6
6
  import { performLogin, loadState, clearState, saveState, getDeviceGuid, getEnvironment, QClawAPI, buildAuthUrl, fetchQrUuid, fetchQrImageDataUrl, pollQrStatus } from "./auth/index.js";
7
7
  import type { QClawEnvironment, PersistedAuthState } from "./auth/index.js";
8
8
  import { nested } from "./auth/utils.js";
@@ -181,34 +181,46 @@ const tencentAccessPlugin = {
181
181
  const channelToken = (loginData.openclaw_channel_token as string) || "";
182
182
  const userInfo = (loginData.user_info as Record<string, unknown>) || {};
183
183
 
184
- // 保存登录态
185
- const persistedState: PersistedAuthState = {
186
- jwtToken,
187
- channelToken,
188
- apiKey: "",
189
- guid,
190
- userInfo,
191
- savedAt: Date.now(),
192
- };
193
- saveState(persistedState, authStatePath);
184
+ // 更新 loginKey(服务端可能返回新值,后续 API 调用需要)
185
+ const loginKey = userInfo.loginKey as string | undefined;
186
+ if (loginKey) api.loginKey = loginKey;
194
187
 
195
188
  // 创建 API Key(非致命)
196
189
  api.jwtToken = jwtToken;
197
190
  api.userId = String(userInfo.user_id ?? "");
191
+ let apiKey = "";
198
192
  try {
199
193
  const keyResult = await api.createApiKey();
200
194
  if (keyResult.success) {
201
- const apiKey =
195
+ apiKey =
202
196
  (nested(keyResult.data, "key") as string) ??
203
197
  (nested(keyResult.data, "resp", "data", "key") as string) ??
204
198
  "";
205
- if (apiKey) {
206
- persistedState.apiKey = apiKey;
207
- saveState(persistedState, authStatePath);
208
- }
209
199
  }
210
200
  } catch { /* non-fatal */ }
211
201
 
202
+ // 写入 openclaw.json(统一存储)
203
+ try {
204
+ const fullCfg = runtime.config?.loadConfig?.() ?? cfg;
205
+ const channels = { ...(fullCfg.channels ?? {}) } as Record<string, any>;
206
+ channels["wechat-access-unqclawed"] = {
207
+ ...(channels["wechat-access-unqclawed"] ?? {}),
208
+ token: channelToken,
209
+ };
210
+ const nextCfg: Record<string, unknown> = { ...fullCfg, channels };
211
+ if (apiKey) {
212
+ const models = { ...(fullCfg.models ?? {}) } as Record<string, any>;
213
+ const providers = { ...(models.providers ?? {}) } as Record<string, any>;
214
+ providers.qclaw = { ...(providers.qclaw ?? {}), apiKey };
215
+ models.providers = providers;
216
+ nextCfg.models = models;
217
+ }
218
+ await runtime.config.writeConfigFile(nextCfg);
219
+ } catch { /* non-fatal: fallback to state file */ }
220
+
221
+ // 备份到独立文件(兜底)
222
+ saveState({ jwtToken, channelToken, apiKey, guid, userInfo, savedAt: Date.now() }, authStatePath);
223
+
212
224
  const nickname = (userInfo.nickname as string) ?? "用户";
213
225
  runtime.log(`[wechat-access] 登录成功! 欢迎 ${nickname},token 已保存。请重启 Gateway 生效。`);
214
226
  return;
@@ -272,7 +284,7 @@ const tencentAccessPlugin = {
272
284
  token = savedState.channelToken;
273
285
  log?.info(`[wechat-access] 使用已保存的 token: ${token.substring(0, 6)}...`);
274
286
  } else {
275
- log?.warn(`[wechat-access] 未找到 token,请在终端运行 "openclaw wechat-login" 完成扫码登录,然后重启 Gateway`);
287
+ log?.warn(`[wechat-access] 未找到 token,请运行 "openclaw channels login --channel wechat-access-unqclawed" 完成扫码登录,然后重启 Gateway`);
276
288
  return;
277
289
  }
278
290
  }
@@ -426,34 +438,47 @@ const tencentAccessPlugin = {
426
438
  const channelToken = (loginData.openclaw_channel_token as string) || "";
427
439
  const userInfo = (loginData.user_info as Record<string, unknown>) || {};
428
440
 
429
- // 保存登录态
430
- const persistedState: PersistedAuthState = {
431
- jwtToken,
432
- channelToken,
433
- apiKey: "",
434
- guid,
435
- userInfo,
436
- savedAt: Date.now(),
437
- };
438
- saveState(persistedState, authStatePath);
441
+ // 更新 loginKey(服务端可能返回新值,后续 API 调用需要)
442
+ const loginKey = userInfo.loginKey as string | undefined;
443
+ if (loginKey) api.loginKey = loginKey;
439
444
 
440
445
  // 创建 API Key(非致命)
441
446
  api.jwtToken = jwtToken;
442
447
  api.userId = String(userInfo.user_id ?? "");
448
+ let apiKey = "";
443
449
  try {
444
450
  const keyResult = await api.createApiKey();
445
451
  if (keyResult.success) {
446
- const apiKey =
452
+ apiKey =
447
453
  (nested(keyResult.data, "key") as string) ??
448
454
  (nested(keyResult.data, "resp", "data", "key") as string) ??
449
455
  "";
450
- if (apiKey) {
451
- persistedState.apiKey = apiKey;
452
- saveState(persistedState, authStatePath);
453
- }
454
456
  }
455
457
  } catch { /* non-fatal */ }
456
458
 
459
+ // 写入 openclaw.json(统一存储)
460
+ try {
461
+ const wRuntime = getWecomRuntime();
462
+ const fullCfg = wRuntime.config.loadConfig();
463
+ const channels = { ...(fullCfg.channels ?? {}) } as Record<string, any>;
464
+ channels["wechat-access-unqclawed"] = {
465
+ ...(channels["wechat-access-unqclawed"] ?? {}),
466
+ token: channelToken,
467
+ };
468
+ const nextCfg: Record<string, unknown> = { ...fullCfg, channels };
469
+ if (apiKey) {
470
+ const models = { ...(fullCfg.models ?? {}) } as Record<string, any>;
471
+ const providers = { ...(models.providers ?? {}) } as Record<string, any>;
472
+ providers.qclaw = { ...(providers.qclaw ?? {}), apiKey };
473
+ models.providers = providers;
474
+ nextCfg.models = models;
475
+ }
476
+ await wRuntime.config.writeConfigFile(nextCfg);
477
+ } catch { /* non-fatal */ }
478
+
479
+ // 备份到独立文件(兜底)
480
+ saveState({ jwtToken, channelToken, apiKey, guid, userInfo, savedAt: Date.now() }, authStatePath);
481
+
457
482
  pendingQrLogin = null;
458
483
  const nickname = (userInfo.nickname as string) ?? "用户";
459
484
  return { connected: true, message: `登录成功! 欢迎 ${nickname},请重启 Gateway 生效。` };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryxiaoyang/wechat-access-unqclawed",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "type": "module",
5
5
  "description": "OpenClaw 微信通路插件 — 扫码登录 + AGP WebSocket 双向通信",
6
6
  "author": "HenryXiaoYang",