diff --git a/packages/napcat-shell/base.ts b/packages/napcat-shell/base.ts index c813a92b..e8909d5e 100644 --- a/packages/napcat-shell/base.ts +++ b/packages/napcat-shell/base.ts @@ -20,6 +20,7 @@ import { hostname, systemVersion } from 'napcat-common/src/system'; import path from 'path'; import fs from 'fs'; import os from 'os'; +import { createHash } from 'node:crypto'; import { LoginListItem, NodeIKernelLoginService } from 'napcat-core/services'; import qrcode from 'napcat-qrcode/lib/main'; import { NapCatAdapterManager } from 'napcat-adapter'; @@ -194,6 +195,24 @@ async function handleLogin ( return await selfInfo; } async function handleLoginInner (context: { isLogined: boolean; }, logger: LogWrapper, loginService: NodeIKernelLoginService, quickLoginUin: string | undefined, historyLoginList: LoginListItem[]) { + const resolveQuickPasswordMd5 = (): string | undefined => { + const quickPasswordMd5 = process.env['NAPCAT_QUICK_PASSWORD_MD5']?.trim(); + if (quickPasswordMd5) { + if (/^[a-fA-F0-9]{32}$/.test(quickPasswordMd5)) { + return quickPasswordMd5.toLowerCase(); + } + logger.logError('NAPCAT_QUICK_PASSWORD_MD5 格式无效(需为 32 位 MD5)'); + } + + const quickPassword = process.env['NAPCAT_QUICK_PASSWORD']; + if (typeof quickPassword === 'string' && quickPassword.length > 0) { + logger.log('检测到 NAPCAT_QUICK_PASSWORD,已在内存中计算 MD5 用于回退登录'); + return createHash('md5').update(quickPassword, 'utf8').digest('hex'); + } + + return undefined; + }; + // 注册刷新二维码回调 WebUiDataRuntime.setRefreshQRCodeCallback(async () => { loginService.getQRCodePicture(); @@ -204,10 +223,12 @@ async function handleLoginInner (context: { isLogined: boolean; }, logger: LogWr if (uin) { logger.log('正在快速登录 ', uin); loginService.quickLoginWithUin(uin).then(res => { - if (res.loginErrorInfo.errMsg) { - WebUiDataRuntime.setQQLoginError(res.loginErrorInfo.errMsg); + const quickLoginSuccess = res.result === '0' && !res.loginErrorInfo?.errMsg; + if (!quickLoginSuccess) { + const errMsg = res.loginErrorInfo?.errMsg || `快速登录失败,错误码: ${res.result}`; + WebUiDataRuntime.setQQLoginError(errMsg); loginService.getQRCodePicture(); - resolve({ result: false, message: res.loginErrorInfo.errMsg }); + resolve({ result: false, message: errMsg }); } else { WebUiDataRuntime.setQQLoginStatus(true); WebUiDataRuntime.setQQLoginError(''); @@ -292,6 +313,38 @@ async function handleLoginInner (context: { isLogined: boolean; }, logger: LogWr } }); }); + const tryPasswordFallbackLogin = async (uin: string): Promise<{ success: boolean, attempted: boolean; }> => { + const quickPasswordMd5 = resolveQuickPasswordMd5(); + if (!quickPasswordMd5) { + logger.log(`QQ ${uin} 未配置回退密码环境变量,建议优先使用 ACCOUNT + NAPCAT_QUICK_PASSWORD(NAPCAT_QUICK_PASSWORD_MD5 作为备用),将使用二维码登录方式`); + return { success: false, attempted: false }; + } + + logger.log('正在尝试密码回退登录 ', uin); + const fallbackResult = await WebUiDataRuntime.requestPasswordLogin(uin, quickPasswordMd5); + if (fallbackResult.result) { + logger.log('密码回退登录成功 ', uin); + return { success: true, attempted: true }; + } + if (fallbackResult.needCaptcha) { + const captchaTip = fallbackResult.proofWaterUrl + ? `密码回退需要验证码,请在 WebUi 中继续完成验证:${fallbackResult.proofWaterUrl}` + : '密码回退需要验证码,请在 WebUi 中继续完成验证'; + logger.logWarn(captchaTip); + WebUiDataRuntime.setQQLoginError('密码回退需要验证码,请在 WebUi 中继续完成验证'); + return { success: false, attempted: true }; + } + if (fallbackResult.needNewDevice) { + const newDeviceTip = fallbackResult.jumpUrl + ? `密码回退需要新设备验证,请在 WebUi 中继续完成验证:${fallbackResult.jumpUrl}` + : '密码回退需要新设备验证,请在 WebUi 中继续完成验证'; + logger.logWarn(newDeviceTip); + WebUiDataRuntime.setQQLoginError('密码回退需要新设备验证,请在 WebUi 中继续完成验证'); + return { success: false, attempted: true }; + } + logger.logError('密码回退登录失败:', fallbackResult.message); + return { success: false, attempted: true }; + }; // 注册验证码登录回调(密码登录需要验证码时的第二步) WebUiDataRuntime.setCaptchaLoginCall(async (uin: string, passwordMd5: string, ticket: string, randstr: string, sid: string) => { @@ -404,17 +457,26 @@ async function handleLoginInner (context: { isLogined: boolean; }, logger: LogWr if (historyLoginList.some(u => u.uin === quickLoginUin)) { logger.log('正在快速登录 ', quickLoginUin); loginService.quickLoginWithUin(quickLoginUin) - .then(result => { - if (result.loginErrorInfo.errMsg) { - logger.logError('快速登录错误:', result.loginErrorInfo.errMsg); - WebUiDataRuntime.setQQLoginError(result.loginErrorInfo.errMsg); - if (!context.isLogined) loginService.getQRCodePicture(); + .then(async result => { + const quickLoginSuccess = result.result === '0' && !result.loginErrorInfo?.errMsg; + if (!quickLoginSuccess) { + const errMsg = result.loginErrorInfo?.errMsg || `快速登录失败,错误码: ${result.result}`; + logger.logError('快速登录错误:', errMsg); + WebUiDataRuntime.setQQLoginError(errMsg); + const { success, attempted } = await tryPasswordFallbackLogin(quickLoginUin); + if (!success && !attempted && !context.isLogined) loginService.getQRCodePicture(); } }) - .catch(); + .catch(async (error) => { + logger.logError('快速登录异常:', error); + WebUiDataRuntime.setQQLoginError('快速登录发生错误'); + const { success, attempted } = await tryPasswordFallbackLogin(quickLoginUin); + if (!success && !attempted && !context.isLogined) loginService.getQRCodePicture(); + }); } else { - logger.logError('快速登录失败,未找到该 QQ 历史登录记录,将使用二维码登录方式'); - if (!context.isLogined) loginService.getQRCodePicture(); + logger.logError('快速登录失败,未找到该 QQ 历史登录记录,将尝试密码回退登录'); + const { success, attempted } = await tryPasswordFallbackLogin(quickLoginUin); + if (!success && !attempted && !context.isLogined) loginService.getQRCodePicture(); } } else { logger.log('没有 -q 指令指定快速登录,将使用二维码登录方式'); diff --git a/packages/napcat-webui-backend/index.ts b/packages/napcat-webui-backend/index.ts index 87481d6b..ddff5d8c 100644 --- a/packages/napcat-webui-backend/index.ts +++ b/packages/napcat-webui-backend/index.ts @@ -5,7 +5,7 @@ import express from 'express'; import type { WebUiConfigType } from './src/types'; import { createServer } from 'http'; -import { randomUUID } from 'node:crypto'; +import { createHash, randomUUID } from 'node:crypto'; import { createServer as createHttpsServer } from 'https'; import { NapCatPathWrapper } from 'napcat-common/src/path'; import { WebUiConfigWrapper } from '@/napcat-webui-backend/src/helper/config'; @@ -156,16 +156,60 @@ export async function InitWebUi (logger: ILogWrapper, pathWrapper: NapCatPathWra WebUiDataRuntime.setWebUiConfigQuickFunction( async () => { const autoLoginAccount = process.env['NAPCAT_QUICK_ACCOUNT'] || WebUiConfig.getAutoLoginAccount(); - if (autoLoginAccount) { - try { - const { result, message } = await WebUiDataRuntime.requestQuickLogin(autoLoginAccount); - if (!result) { - throw new Error(message); + const resolveQuickPasswordMd5 = (): string | undefined => { + const quickPasswordMd5FromEnv = process.env['NAPCAT_QUICK_PASSWORD_MD5']?.trim(); + if (quickPasswordMd5FromEnv) { + if (/^[a-fA-F0-9]{32}$/.test(quickPasswordMd5FromEnv)) { + return quickPasswordMd5FromEnv.toLowerCase(); } - console.log(`[NapCat] [WebUi] Auto login account: ${autoLoginAccount}`); - } catch (error) { - console.log('[NapCat] [WebUi] Auto login account failed.' + error); + console.log('[NapCat] [WebUi] NAPCAT_QUICK_PASSWORD_MD5 格式无效(需为 32 位 MD5)'); } + + const quickPassword = process.env['NAPCAT_QUICK_PASSWORD']; + if (typeof quickPassword === 'string' && quickPassword.length > 0) { + console.log('[NapCat] [WebUi] 检测到 NAPCAT_QUICK_PASSWORD,已在内存中计算 MD5 用于回退登录'); + return createHash('md5').update(quickPassword, 'utf8').digest('hex'); + } + return undefined; + }; + if (!autoLoginAccount) { + return; + } + const quickPasswordMd5 = resolveQuickPasswordMd5(); + + try { + const { result, message } = await WebUiDataRuntime.requestQuickLogin(autoLoginAccount); + if (result) { + console.log(`[NapCat] [WebUi] 自动快速登录成功: ${autoLoginAccount}`); + return; + } + console.log(`[NapCat] [WebUi] 自动快速登录失败: ${message || '未知错误'}`); + } catch (error) { + console.log('[NapCat] [WebUi] 自动快速登录异常:' + error); + } + + if (!quickPasswordMd5) { + console.log(`[NapCat] [WebUi] QQ ${autoLoginAccount} 未配置回退密码环境变量,建议优先使用 ACCOUNT + NAPCAT_QUICK_PASSWORD(NAPCAT_QUICK_PASSWORD_MD5 作为备用),保持二维码登录兜底`); + return; + } + + try { + const { result, message, needCaptcha, needNewDevice } = await WebUiDataRuntime.requestPasswordLogin(autoLoginAccount, quickPasswordMd5); + if (result) { + console.log(`[NapCat] [WebUi] 自动密码回退登录成功: ${autoLoginAccount}`); + return; + } + if (needCaptcha) { + console.log(`[NapCat] [WebUi] 自动密码回退登录需要验证码,请在登录页面继续完成: ${autoLoginAccount}`); + return; + } + if (needNewDevice) { + console.log(`[NapCat] [WebUi] 自动密码回退登录需要新设备验证,请在登录页面继续完成: ${autoLoginAccount}`); + return; + } + console.log(`[NapCat] [WebUi] 自动密码回退登录失败: ${message || '未知错误'}`); + } catch (error) { + console.log('[NapCat] [WebUi] 自动密码回退登录异常:' + error); } }); // ------------注册中间件------------