@henryxiaoyang/wechat-access-unqclawed 1.0.5 → 1.0.6
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/index.ts +106 -0
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -81,6 +81,112 @@ const tencentAccessPlugin = {
|
|
|
81
81
|
},
|
|
82
82
|
},
|
|
83
83
|
|
|
84
|
+
// 认证适配器:openclaw channels login --channel wechat-access-unqclawed
|
|
85
|
+
auth: {
|
|
86
|
+
login: async ({ cfg, accountId, runtime }: { cfg: any; accountId?: string; runtime: any; verbose?: boolean; channelInput?: string }) => {
|
|
87
|
+
const channelCfg = cfg?.channels?.["wechat-access-unqclawed"];
|
|
88
|
+
const envName = channelCfg?.environment ? String(channelCfg.environment) : "production";
|
|
89
|
+
const bypassInvite = channelCfg?.bypassInvite === true;
|
|
90
|
+
const authStatePath = channelCfg?.authStatePath ? String(channelCfg.authStatePath) : undefined;
|
|
91
|
+
|
|
92
|
+
const env = getEnvironment(envName);
|
|
93
|
+
const guid = getDeviceGuid();
|
|
94
|
+
|
|
95
|
+
// 1. 获取 OAuth state
|
|
96
|
+
runtime.log("[wechat-access] 获取登录 state...");
|
|
97
|
+
const api = new QClawAPI(env, guid);
|
|
98
|
+
const stateResult = await api.getWxLoginState();
|
|
99
|
+
let state = String(Math.floor(Math.random() * 10000));
|
|
100
|
+
if (stateResult.success) {
|
|
101
|
+
const s = nested(stateResult.data, "state") as string | undefined;
|
|
102
|
+
if (s) state = s;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 2. 构造 auth URL → 抓取 QR 页面拿 uuid
|
|
106
|
+
runtime.log("[wechat-access] 生成微信登录二维码...");
|
|
107
|
+
const authUrl = buildAuthUrl(state, env);
|
|
108
|
+
const uuid = await fetchQrUuid(authUrl);
|
|
109
|
+
|
|
110
|
+
// 3. 终端显示 QR 码
|
|
111
|
+
try {
|
|
112
|
+
const qrterm = await import("qrcode-terminal");
|
|
113
|
+
const generate = qrterm.default?.generate ?? qrterm.generate;
|
|
114
|
+
const qrContent = `https://open.weixin.qq.com/connect/confirm?uuid=${uuid}`;
|
|
115
|
+
generate(qrContent, { small: true }, (qrcode: string) => {
|
|
116
|
+
runtime.log("\n" + qrcode);
|
|
117
|
+
});
|
|
118
|
+
} catch {
|
|
119
|
+
runtime.log("(qrcode-terminal 不可用,请用浏览器打开下方链接)");
|
|
120
|
+
}
|
|
121
|
+
runtime.log(`\n或在浏览器打开: ${authUrl}\n`);
|
|
122
|
+
|
|
123
|
+
// 4. 轮询等待扫码
|
|
124
|
+
runtime.log("[wechat-access] 等待微信扫码...");
|
|
125
|
+
const deadline = Date.now() + 180_000; // 3 分钟超时
|
|
126
|
+
while (Date.now() < deadline) {
|
|
127
|
+
const result = await pollQrStatus(uuid);
|
|
128
|
+
|
|
129
|
+
if (result.status === "scanned") {
|
|
130
|
+
runtime.log("[wechat-access] 已扫码,请在手机上确认...");
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (result.status === "expired") {
|
|
135
|
+
throw new Error("二维码已过期,请重新执行登录");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (result.status === "confirmed" && result.code) {
|
|
139
|
+
// 5. 用 code 换 token
|
|
140
|
+
runtime.log("[wechat-access] 授权成功,正在获取 token...");
|
|
141
|
+
const loginResult = await api.wxLogin(result.code, state);
|
|
142
|
+
if (!loginResult.success) {
|
|
143
|
+
throw new Error(`登录失败: ${loginResult.message ?? "未知错误"}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const loginData = loginResult.data as Record<string, unknown>;
|
|
147
|
+
const jwtToken = (loginData.token as string) || "";
|
|
148
|
+
const channelToken = (loginData.openclaw_channel_token as string) || "";
|
|
149
|
+
const userInfo = (loginData.user_info as Record<string, unknown>) || {};
|
|
150
|
+
|
|
151
|
+
// 保存登录态
|
|
152
|
+
const persistedState: PersistedAuthState = {
|
|
153
|
+
jwtToken,
|
|
154
|
+
channelToken,
|
|
155
|
+
apiKey: "",
|
|
156
|
+
guid,
|
|
157
|
+
userInfo,
|
|
158
|
+
savedAt: Date.now(),
|
|
159
|
+
};
|
|
160
|
+
saveState(persistedState, authStatePath);
|
|
161
|
+
|
|
162
|
+
// 创建 API Key(非致命)
|
|
163
|
+
api.jwtToken = jwtToken;
|
|
164
|
+
api.userId = String(userInfo.user_id ?? "");
|
|
165
|
+
try {
|
|
166
|
+
const keyResult = await api.createApiKey();
|
|
167
|
+
if (keyResult.success) {
|
|
168
|
+
const apiKey =
|
|
169
|
+
(nested(keyResult.data, "key") as string) ??
|
|
170
|
+
(nested(keyResult.data, "resp", "data", "key") as string) ??
|
|
171
|
+
"";
|
|
172
|
+
if (apiKey) {
|
|
173
|
+
persistedState.apiKey = apiKey;
|
|
174
|
+
saveState(persistedState, authStatePath);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} catch { /* non-fatal */ }
|
|
178
|
+
|
|
179
|
+
const nickname = (userInfo.nickname as string) ?? "用户";
|
|
180
|
+
runtime.log(`[wechat-access] 登录成功! 欢迎 ${nickname},token 已保存。请重启 Gateway 生效。`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// waiting / error → 继续轮询
|
|
185
|
+
}
|
|
186
|
+
throw new Error("登录超时(3 分钟),请重试");
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
|
|
84
190
|
// 出站适配器(必需)
|
|
85
191
|
outbound: {
|
|
86
192
|
deliveryMode: "direct" as const,
|