|
|
@@ -2,15 +2,15 @@ const fs = require('fs');
|
|
|
const path = require('path');
|
|
|
const { spawn } = require('child_process');
|
|
|
const {
|
|
|
- readQiweAuthToken,
|
|
|
- ensureQiweUid,
|
|
|
- readQiweApiBase,
|
|
|
- saveQiweClientConfig
|
|
|
+ readQiweiAuthToken,
|
|
|
+ ensureQiweiUid,
|
|
|
+ readQiweiApiBase,
|
|
|
+ saveQiweiClientConfig
|
|
|
} = require('../core/credentials');
|
|
|
const { callFmodeWecomGateway, redactSecret } = require('../providers/fmode-wecom-gateway');
|
|
|
const { okResult, errorResult } = require('../core/result-envelope');
|
|
|
const { latestPath } = require('../core/output-paths');
|
|
|
-const { saveSubscribePage, QIWE_MONTHLY_PRICE } = require('../core/subscribe-page');
|
|
|
+const { saveSubscribePage, QIWEI_MONTHLY_PRICE } = require('../core/subscribe-page');
|
|
|
const { startLoginFlowServer } = require('../core/login-flow-server');
|
|
|
|
|
|
const QR_STATUS = {
|
|
|
@@ -27,7 +27,7 @@ function authRequiredResult() {
|
|
|
status: 'needs_auth',
|
|
|
assistantMessage: [
|
|
|
'还没有找到 Fmode 鉴权 token,无法调用由 Fmode 网关转发的企业微信接口。',
|
|
|
- '优先复用 Claude Code 已配置的 Fmode NewAPI sk- token;也可配置 QIWE_AUTH_TOKEN、FMODE_API_KEY 或平台 sessionToken。',
|
|
|
+ '优先复用 Claude Code 已配置的 Fmode NewAPI sk- token;也可配置 QIWEI_AUTH_TOKEN、FMODE_API_KEY 或平台 sessionToken。',
|
|
|
'企业微信接口访问凭据和设备上下文由 Fmode 网关管理,技能不会读取或保存。'
|
|
|
].join('\n'),
|
|
|
summary: { configured: false, errorKind: 'missing_auth_token', recoverable: true },
|
|
|
@@ -41,7 +41,7 @@ function authRequiredResult() {
|
|
|
|
|
|
function saveQrcodeImage(base64Data) {
|
|
|
try {
|
|
|
- const filePath = latestPath('login', 'qiwe-login-qrcode.png');
|
|
|
+ const filePath = latestPath('login', 'qiwei-login-qrcode.png');
|
|
|
const data = String(base64Data || '').replace(/^data:image\/\w+;base64,/, '');
|
|
|
fs.writeFileSync(filePath, Buffer.from(data, 'base64'));
|
|
|
return filePath;
|
|
|
@@ -52,7 +52,7 @@ function saveQrcodeImage(base64Data) {
|
|
|
|
|
|
function saveQrcodePreviewHtml(qrcodeImagePath) {
|
|
|
try {
|
|
|
- const htmlPath = latestPath('login', 'qiwe-login-qrcode.html');
|
|
|
+ const htmlPath = latestPath('login', 'qiwei-login-qrcode.html');
|
|
|
const html = [
|
|
|
'<!DOCTYPE html>',
|
|
|
'<html lang="zh-CN">',
|
|
|
@@ -108,11 +108,11 @@ function gatewayErrorResult(error, stage, fallbackMessage) {
|
|
|
if (kind === 'billing') {
|
|
|
return {
|
|
|
status: 'needs_subscription',
|
|
|
- assistantMessage: `企微订阅未开通、已到期或飞马余额不足(${safeMessage})。请先调用 qiwe_subscription_status,必要时用 qiwe_subscribe 开通或续费。`,
|
|
|
+ assistantMessage: `企微订阅未开通、已到期或飞马余额不足(${safeMessage})。请先调用 qiwei_subscription_status,必要时用 qiwei_subscribe 开通或续费。`,
|
|
|
summary: { stage, errorKind: kind, recoverable: true },
|
|
|
data: {},
|
|
|
files: [],
|
|
|
- nextActions: ['调用 qiwe_subscription_status', '调用 qiwe_subscribe 开通或续费'],
|
|
|
+ nextActions: ['调用 qiwei_subscription_status', '调用 qiwei_subscribe 开通或续费'],
|
|
|
warnings: [],
|
|
|
errors: []
|
|
|
};
|
|
|
@@ -124,7 +124,7 @@ function gatewayErrorResult(error, stage, fallbackMessage) {
|
|
|
summary: { stage, errorKind: kind, recoverable: true },
|
|
|
data: {},
|
|
|
files: [],
|
|
|
- nextActions: ['调用 qiwe_subscription_status', '调用 qiwe_subscribe 增购席位'],
|
|
|
+ nextActions: ['调用 qiwei_subscription_status', '调用 qiwei_subscribe 增购席位'],
|
|
|
warnings: [],
|
|
|
errors: []
|
|
|
};
|
|
|
@@ -135,11 +135,11 @@ function gatewayErrorResult(error, stage, fallbackMessage) {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-async function qiweLoginStatus(input = {}) {
|
|
|
- const token = readQiweAuthToken(input);
|
|
|
+async function qiweiLoginStatus(input = {}) {
|
|
|
+ const token = readQiweiAuthToken(input);
|
|
|
if (!token) return authRequiredResult();
|
|
|
- const uid = ensureQiweUid(input);
|
|
|
- const apiBase = readQiweApiBase(input);
|
|
|
+ const uid = ensureQiweiUid(input);
|
|
|
+ const apiBase = readQiweiApiBase(input);
|
|
|
try {
|
|
|
const result = await callFmodeWecomGateway({
|
|
|
gatewayPath: '/login/status',
|
|
|
@@ -154,8 +154,8 @@ async function qiweLoginStatus(input = {}) {
|
|
|
assistantMessage: data.configured
|
|
|
? online
|
|
|
? `企业微信设备在线(uid: ${uid}),可以直接调用业务接口。`
|
|
|
- : `企业微信设备当前不在线(状态 ${data.statusCode ?? '未知'}),请调用 qiwe_login_start 重新登录。`
|
|
|
- : `当前 uid(${uid})尚未配置企业微信设备,请调用 qiwe_login_start 开始扫码登录。`,
|
|
|
+ : `企业微信设备当前不在线(状态 ${data.statusCode ?? '未知'}),请调用 qiwei_login_start 重新登录。`
|
|
|
+ : `当前 uid(${uid})尚未配置企业微信设备,请调用 qiwei_login_start 开始扫码登录。`,
|
|
|
summary: {
|
|
|
configured: Boolean(data.configured),
|
|
|
online,
|
|
|
@@ -169,7 +169,7 @@ async function qiweLoginStatus(input = {}) {
|
|
|
statusCode: data.statusCode ?? null,
|
|
|
detail: data.detail
|
|
|
},
|
|
|
- nextActions: online ? [] : ['调用 qiwe_login_start 开始扫码登录']
|
|
|
+ nextActions: online ? [] : ['调用 qiwei_login_start 开始扫码登录']
|
|
|
});
|
|
|
} catch (error) {
|
|
|
return gatewayErrorResult(error, 'loginStatus', '查询登录状态失败');
|
|
|
@@ -182,30 +182,30 @@ function subscriptionRequiredResult({ apiBase, token, uid, detail }) {
|
|
|
return {
|
|
|
status: 'needs_subscription',
|
|
|
assistantMessage: [
|
|
|
- `登录前需要先开通企微包月订阅:每个账号 ¥${QIWE_MONTHLY_PRICE}/月,从飞马余额扣费。`,
|
|
|
+ `登录前需要先开通企微包月订阅:每个账号 ¥${QIWEI_MONTHLY_PRICE}/月,从飞马余额扣费。`,
|
|
|
opened
|
|
|
? `已自动打开套餐选择页:${pagePath},请在页面上选择席位与时长并点击「开通服务」。`
|
|
|
: pagePath
|
|
|
? `请打开套餐选择页完成开通:${pagePath}`
|
|
|
- : '套餐页生成失败,可直接调用 qiwe_subscribe 开通。',
|
|
|
- '开通成功后重新调用 qiwe_login_start 进入扫码登录(第 2 步)。',
|
|
|
+ : '套餐页生成失败,可直接调用 qiwei_subscribe 开通。',
|
|
|
+ '开通成功后重新调用 qiwei_login_start 进入扫码登录(第 2 步)。',
|
|
|
detail ? `订阅状态:${detail}` : ''
|
|
|
].filter(Boolean).join('\n'),
|
|
|
- summary: { stage: 'loginStart', errorKind: 'subscription_required', uid, subscribePage: pagePath || null, browserOpened: opened, monthlyPrice: QIWE_MONTHLY_PRICE },
|
|
|
+ summary: { stage: 'loginStart', errorKind: 'subscription_required', uid, subscribePage: pagePath || null, browserOpened: opened, monthlyPrice: QIWEI_MONTHLY_PRICE },
|
|
|
data: { uid, subscribePage: pagePath || null },
|
|
|
files: pagePath ? [pagePath] : [],
|
|
|
- nextActions: ['在套餐页开通订阅(或调用 qiwe_subscribe)', '开通后重新调用 qiwe_login_start'],
|
|
|
+ nextActions: ['在套餐页开通订阅(或调用 qiwei_subscribe)', '开通后重新调用 qiwei_login_start'],
|
|
|
warnings: [],
|
|
|
errors: []
|
|
|
};
|
|
|
}
|
|
|
|
|
|
-async function qiweLoginStart(input = {}) {
|
|
|
- const token = readQiweAuthToken(input);
|
|
|
+async function qiweiLoginStart(input = {}) {
|
|
|
+ const token = readQiweiAuthToken(input);
|
|
|
if (!token) return authRequiredResult();
|
|
|
- const uid = ensureQiweUid(input);
|
|
|
- const apiBase = readQiweApiBase(input);
|
|
|
- if (input.persistConfig !== false) saveQiweClientConfig({ uid, apiBase });
|
|
|
+ const uid = ensureQiweiUid(input);
|
|
|
+ const apiBase = readQiweiApiBase(input);
|
|
|
+ if (input.persistConfig !== false) saveQiweiClientConfig({ uid, apiBase });
|
|
|
if (input.flowUi !== false) {
|
|
|
try {
|
|
|
const { url } = await startLoginFlowServer({
|
|
|
@@ -215,7 +215,7 @@ async function qiweLoginStart(input = {}) {
|
|
|
port: input.flowPort,
|
|
|
onQrcode: (buffer) => {
|
|
|
try {
|
|
|
- fs.writeFileSync(latestPath('login', 'qiwe-login-qrcode.png'), buffer);
|
|
|
+ fs.writeFileSync(latestPath('login', 'qiwei-login-qrcode.png'), buffer);
|
|
|
} catch {}
|
|
|
}
|
|
|
});
|
|
|
@@ -223,12 +223,12 @@ async function qiweLoginStart(input = {}) {
|
|
|
return okResult({
|
|
|
assistantMessage: [
|
|
|
`已启动企微登录流程页服务:${url}${opened ? '(已自动打开浏览器)' : ''}。`,
|
|
|
- `页面会自动完成两步流程:未开通订阅时先展示套餐页(每号 ¥${QIWE_MONTHLY_PRICE}/月,飞马余额扣费),开通后自动生成二维码并每 3 秒自动检测扫码状态,需要验证码时页面会提示输入。`,
|
|
|
- '也可以用 qiwe_login_check 在对话中轮询登录状态。'
|
|
|
+ `页面会自动完成两步流程:未开通订阅时先展示套餐页(每号 ¥${QIWEI_MONTHLY_PRICE}/月,飞马余额扣费),开通后自动生成二维码并每 3 秒自动检测扫码状态,需要验证码时页面会提示输入。`,
|
|
|
+ '也可以用 qiwei_login_check 在对话中轮询登录状态。'
|
|
|
].join('\n'),
|
|
|
- summary: { uid, flowUrl: url, browserOpened: Boolean(opened), monthlyPrice: QIWE_MONTHLY_PRICE },
|
|
|
+ summary: { uid, flowUrl: url, browserOpened: Boolean(opened), monthlyPrice: QIWEI_MONTHLY_PRICE },
|
|
|
data: { uid, flowUrl: url, browserOpened: Boolean(opened) },
|
|
|
- nextActions: ['引导用户在流程页完成开通与扫码', '用 qiwe_login_check 轮询登录状态']
|
|
|
+ nextActions: ['引导用户在流程页完成开通与扫码', '用 qiwei_login_check 轮询登录状态']
|
|
|
});
|
|
|
} catch (error) {
|
|
|
// 流程页服务启动失败时回退到静态二维码流程
|
|
|
@@ -273,8 +273,8 @@ async function qiweLoginStart(input = {}) {
|
|
|
: filePath
|
|
|
? `请打开或展示二维码图片:${filePath}`
|
|
|
: 'Fmode 网关未返回可保存的二维码图片,请重试。',
|
|
|
- '用户扫码并在手机上确认后,调用 qiwe_login_check 轮询状态。',
|
|
|
- '如果返回状态 10,请向用户索要 6 位验证码并调用 qiwe_login_verify。'
|
|
|
+ '用户扫码并在手机上确认后,调用 qiwei_login_check 轮询状态。',
|
|
|
+ '如果返回状态 10,请向用户索要 6 位验证码并调用 qiwei_login_verify。'
|
|
|
].join('\n'),
|
|
|
summary: {
|
|
|
uid,
|
|
|
@@ -290,7 +290,7 @@ async function qiweLoginStart(input = {}) {
|
|
|
loginQrcodeBase64Data: filePath ? undefined : data.loginQrcodeBase64Data
|
|
|
},
|
|
|
files: [filePath, htmlPath].filter(Boolean),
|
|
|
- nextActions: ['确认用户看到二维码并扫码', '调用 qiwe_login_check 轮询登录状态']
|
|
|
+ nextActions: ['确认用户看到二维码并扫码', '调用 qiwei_login_check 轮询登录状态']
|
|
|
});
|
|
|
} catch (error) {
|
|
|
if (error && (error.kind === 'billing' || error.httpStatus === 402)) {
|
|
|
@@ -300,11 +300,11 @@ async function qiweLoginStart(input = {}) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-async function qiweLoginCheck(input = {}) {
|
|
|
- const token = readQiweAuthToken(input);
|
|
|
+async function qiweiLoginCheck(input = {}) {
|
|
|
+ const token = readQiweiAuthToken(input);
|
|
|
if (!token) return authRequiredResult();
|
|
|
- const uid = ensureQiweUid(input);
|
|
|
- const apiBase = readQiweApiBase(input);
|
|
|
+ const uid = ensureQiweiUid(input);
|
|
|
+ const apiBase = readQiweiApiBase(input);
|
|
|
try {
|
|
|
const result = await callFmodeWecomGateway({
|
|
|
gatewayPath: '/login/check',
|
|
|
@@ -333,11 +333,11 @@ async function qiweLoginCheck(input = {}) {
|
|
|
if (statusCode === '10') {
|
|
|
return {
|
|
|
status: 'needs_verify_code',
|
|
|
- assistantMessage: '用户已在手机上确认,但需要输入手机端显示的 6 位验证码。请取得验证码后调用 qiwe_login_verify。',
|
|
|
+ assistantMessage: '用户已在手机上确认,但需要输入手机端显示的 6 位验证码。请取得验证码后调用 qiwei_login_verify。',
|
|
|
summary: { loggedIn: false, uid, statusCode: 10 },
|
|
|
data: { uid, status: detail },
|
|
|
files: [],
|
|
|
- nextActions: ['向用户索要 6 位验证码', '调用 qiwe_login_verify'],
|
|
|
+ nextActions: ['向用户索要 6 位验证码', '调用 qiwei_login_verify'],
|
|
|
warnings: [],
|
|
|
errors: []
|
|
|
};
|
|
|
@@ -345,34 +345,34 @@ async function qiweLoginCheck(input = {}) {
|
|
|
return okResult({
|
|
|
assistantMessage: `当前扫码状态:${statusCode}(${statusText})。${
|
|
|
['-1', '1'].includes(statusCode)
|
|
|
- ? '请继续等待用户扫码/确认,3-5 秒后再次调用 qiwe_login_check。'
|
|
|
+ ? '请继续等待用户扫码/确认,3-5 秒后再次调用 qiwei_login_check。'
|
|
|
: statusCode === '4'
|
|
|
- ? '用户取消了登录,可重新调用 qiwe_login_start。'
|
|
|
+ ? '用户取消了登录,可重新调用 qiwei_login_start。'
|
|
|
: statusCode === '0'
|
|
|
- ? '可再次调用 qiwe_login_check 并传 manual=true 尝试免扫码登录。'
|
|
|
+ ? '可再次调用 qiwei_login_check 并传 manual=true 尝试免扫码登录。'
|
|
|
: ''
|
|
|
}`,
|
|
|
summary: { loggedIn: false, uid, statusCode: Number(statusCode) },
|
|
|
data: { uid, status: detail },
|
|
|
nextActions:
|
|
|
statusCode === '4'
|
|
|
- ? ['重新调用 qiwe_login_start']
|
|
|
+ ? ['重新调用 qiwei_login_start']
|
|
|
: statusCode === '0'
|
|
|
- ? ['调用 qiwe_login_check,传 manual=true']
|
|
|
- : ['稍后再次调用 qiwe_login_check']
|
|
|
+ ? ['调用 qiwei_login_check,传 manual=true']
|
|
|
+ : ['稍后再次调用 qiwei_login_check']
|
|
|
});
|
|
|
} catch (error) {
|
|
|
return gatewayErrorResult(error, 'loginCheck', '检测扫码状态失败');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-async function qiweLoginVerify(input = {}) {
|
|
|
- const token = readQiweAuthToken(input);
|
|
|
+async function qiweiLoginVerify(input = {}) {
|
|
|
+ const token = readQiweiAuthToken(input);
|
|
|
if (!token) return authRequiredResult();
|
|
|
- const uid = ensureQiweUid(input);
|
|
|
+ const uid = ensureQiweiUid(input);
|
|
|
const code = String(input.code || '').trim();
|
|
|
if (!/^\d{6}$/.test(code)) return errorResult('请提供手机端显示的 6 位数字验证码(入参 code)。');
|
|
|
- const apiBase = readQiweApiBase(input);
|
|
|
+ const apiBase = readQiweiApiBase(input);
|
|
|
try {
|
|
|
await callFmodeWecomGateway({
|
|
|
gatewayPath: '/login/verify',
|
|
|
@@ -381,9 +381,9 @@ async function qiweLoginVerify(input = {}) {
|
|
|
apiBase
|
|
|
});
|
|
|
return okResult({
|
|
|
- assistantMessage: '验证码已提交。请调用 qiwe_login_check 再次确认登录状态。',
|
|
|
+ assistantMessage: '验证码已提交。请调用 qiwei_login_check 再次确认登录状态。',
|
|
|
summary: { verified: true, uid },
|
|
|
- nextActions: ['调用 qiwe_login_check 确认登录状态']
|
|
|
+ nextActions: ['调用 qiwei_login_check 确认登录状态']
|
|
|
});
|
|
|
} catch (error) {
|
|
|
return gatewayErrorResult(error, 'loginVerify', '验证码校验失败');
|
|
|
@@ -391,8 +391,8 @@ async function qiweLoginVerify(input = {}) {
|
|
|
}
|
|
|
|
|
|
module.exports = {
|
|
|
- qiweLoginStatus,
|
|
|
- qiweLoginStart,
|
|
|
- qiweLoginCheck,
|
|
|
- qiweLoginVerify
|
|
|
+ qiweiLoginStatus,
|
|
|
+ qiweiLoginStart,
|
|
|
+ qiweiLoginCheck,
|
|
|
+ qiweiLoginVerify
|
|
|
};
|