mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-03-01 08:10:25 +00:00
Improve new-device QR handling and bypass init
Refactor new-device QR flow and streamline bypass init: - napcat-shell: stop verbose logging and removed check of enableAllBypasses return value; just invoke native enableAllBypasses when not disabled by env. - backend (QQLogin): simplify extraction of tokens from jumpUrl (use sig and uin-token), return an error if missing, and send oidbRequest directly (removed nested try/catch and regex fallback). - frontend (new_device_verify): accept result.str_url without requiring bytes_token and pass an empty string to polling when bytes_token is absent. - frontend (password_login): change render order to show captcha modal before new-device verification UI. - frontend (qq_manager): normalize GetNewDeviceQRCode response — derive bytes_token from str_url's str_url query param (base64) when bytes_token is missing, and preserve extra status/error fields in the returned object. These changes improve robustness when OIDB responses omit bytes_token, reduce noisy logs, and ensure the UI and polling still function.
This commit is contained in:
@@ -541,11 +541,7 @@ export async function NCoreInitShell () {
|
|||||||
// wrapper.node 加载后立刻启用 Bypass(可通过环境变量禁用)
|
// wrapper.node 加载后立刻启用 Bypass(可通过环境变量禁用)
|
||||||
if (process.env['NAPCAT_DISABLE_BYPASS'] !== '1') {
|
if (process.env['NAPCAT_DISABLE_BYPASS'] !== '1') {
|
||||||
const bypassOptions = napcatConfig.bypass ?? {};
|
const bypassOptions = napcatConfig.bypass ?? {};
|
||||||
logger.logDebug('[NapCat] Bypass 配置:', bypassOptions);
|
napi2nativeLoader.nativeExports.enableAllBypasses?.(bypassOptions);
|
||||||
const bypassEnabled = napi2nativeLoader.nativeExports.enableAllBypasses?.(bypassOptions);
|
|
||||||
if (bypassEnabled) {
|
|
||||||
logger.log('[NapCat] Napi2NativeLoader: 已启用Bypass');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
logger.log('[NapCat] Napi2NativeLoader: Bypass已通过环境变量禁用');
|
logger.log('[NapCat] Napi2NativeLoader: Bypass已通过环境变量禁用');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -509,20 +509,15 @@ export const QQGetNewDeviceQRCodeHandler: RequestHandler = async (req, res) => {
|
|||||||
return sendError(res, 'uin and jumpUrl are required');
|
return sendError(res, 'uin and jumpUrl are required');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// 从 jumpUrl 中提取参数
|
||||||
// 从 jumpUrl 中提取 str_url 参数作为 str_dev_auth_token
|
// jumpUrl 格式: https://accounts.qq.com/safe/verify?...&uin-token=xxx&sig=yyy
|
||||||
let strDevAuthToken = '';
|
// sig -> str_dev_auth_token, uin-token -> str_uin_token
|
||||||
let strUinToken = '';
|
|
||||||
try {
|
|
||||||
const url = new URL(jumpUrl);
|
const url = new URL(jumpUrl);
|
||||||
strDevAuthToken = url.searchParams.get('str_url') || '';
|
const strDevAuthToken = url.searchParams.get('sig') || '';
|
||||||
strUinToken = url.searchParams.get('str_uin_token') || '';
|
const strUinToken = url.searchParams.get('uin-token') || '';
|
||||||
} catch {
|
|
||||||
// 如果 URL 解析失败,尝试正则提取
|
if (!strDevAuthToken || !strUinToken) {
|
||||||
const strUrlMatch = jumpUrl.match(/str_url=([^&]*)/);
|
return sendError(res, 'Failed to get new device QR code: unable to extract sig/uin-token from jumpUrl');
|
||||||
const uinTokenMatch = jumpUrl.match(/str_uin_token=([^&]*)/);
|
|
||||||
strDevAuthToken = strUrlMatch ? decodeURIComponent(strUrlMatch[1]) : '';
|
|
||||||
strUinToken = uinTokenMatch ? decodeURIComponent(uinTokenMatch[1]) : '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
@@ -535,11 +530,7 @@ export const QQGetNewDeviceQRCodeHandler: RequestHandler = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const result = await oidbRequest(uin, body);
|
const result = await oidbRequest(uin, body);
|
||||||
// result 应包含 str_url (二维码内容) 和 bytes_token 等
|
|
||||||
return sendSuccess(res, result);
|
return sendSuccess(res, result);
|
||||||
} catch (e) {
|
|
||||||
return sendError(res, `Failed to get new device QR code: ${(e as Error).message}`);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 轮询新设备验证二维码状态
|
// 轮询新设备验证二维码状态
|
||||||
|
|||||||
@@ -66,10 +66,11 @@ const NewDeviceVerify: React.FC<NewDeviceVerifyProps> = ({
|
|||||||
try {
|
try {
|
||||||
const result = await QQManager.getNewDeviceQRCode(uin, jumpUrl);
|
const result = await QQManager.getNewDeviceQRCode(uin, jumpUrl);
|
||||||
if (!mountedRef.current) return;
|
if (!mountedRef.current) return;
|
||||||
if (result?.str_url && result?.bytes_token) {
|
if (result?.str_url) {
|
||||||
setQrUrl(result.str_url);
|
setQrUrl(result.str_url);
|
||||||
setStatus('waiting');
|
setStatus('waiting');
|
||||||
startPolling(result.bytes_token);
|
// bytes_token 用于轮询,如果 OIDB 未返回则用空字符串
|
||||||
|
startPolling(result.bytes_token || '');
|
||||||
} else {
|
} else {
|
||||||
setStatus('error');
|
setStatus('error');
|
||||||
setErrorMsg('获取二维码失败,请重试');
|
setErrorMsg('获取二维码失败,请重试');
|
||||||
|
|||||||
@@ -52,14 +52,7 @@ const PasswordLogin: React.FC<PasswordLoginProps> = ({ onSubmit, onCaptchaSubmit
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col gap-8'>
|
<div className='flex flex-col gap-8'>
|
||||||
{newDeviceState?.needNewDevice && newDeviceState.jumpUrl ? (
|
{captchaState?.needCaptcha && captchaState.proofWaterUrl ? (
|
||||||
<NewDeviceVerify
|
|
||||||
jumpUrl={newDeviceState.jumpUrl}
|
|
||||||
uin={newDeviceState.uin}
|
|
||||||
onVerified={(token) => onNewDeviceVerified?.(token)}
|
|
||||||
onCancel={onNewDeviceCancel}
|
|
||||||
/>
|
|
||||||
) : captchaState?.needCaptcha && captchaState.proofWaterUrl ? (
|
|
||||||
<div className='flex flex-col gap-4 items-center'>
|
<div className='flex flex-col gap-4 items-center'>
|
||||||
<p className='text-warning text-sm'>登录需要安全验证,请完成验证码</p>
|
<p className='text-warning text-sm'>登录需要安全验证,请完成验证码</p>
|
||||||
<TencentCaptchaModal
|
<TencentCaptchaModal
|
||||||
@@ -78,6 +71,13 @@ const PasswordLogin: React.FC<PasswordLoginProps> = ({ onSubmit, onCaptchaSubmit
|
|||||||
取消验证
|
取消验证
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
) : newDeviceState?.needNewDevice && newDeviceState.jumpUrl ? (
|
||||||
|
<NewDeviceVerify
|
||||||
|
jumpUrl={newDeviceState.jumpUrl}
|
||||||
|
uin={newDeviceState.uin}
|
||||||
|
onVerified={(token) => onNewDeviceVerified?.(token)}
|
||||||
|
onCancel={onNewDeviceCancel}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className='flex justify-center'>
|
<div className='flex justify-center'>
|
||||||
|
|||||||
@@ -141,11 +141,37 @@ export default class QQManager {
|
|||||||
const data = await serverRequest.post<ServerResponse<{
|
const data = await serverRequest.post<ServerResponse<{
|
||||||
str_url?: string;
|
str_url?: string;
|
||||||
bytes_token?: string;
|
bytes_token?: string;
|
||||||
|
uint32_guarantee_status?: number;
|
||||||
|
ActionStatus?: string;
|
||||||
|
ErrorCode?: number;
|
||||||
|
ErrorInfo?: string;
|
||||||
}>>('/QQLogin/GetNewDeviceQRCode', {
|
}>>('/QQLogin/GetNewDeviceQRCode', {
|
||||||
uin,
|
uin,
|
||||||
jumpUrl,
|
jumpUrl,
|
||||||
});
|
});
|
||||||
return data.data.data;
|
const result = data.data.data;
|
||||||
|
if (result?.str_url) {
|
||||||
|
let bytesToken = result.bytes_token || '';
|
||||||
|
if (!bytesToken && result.str_url) {
|
||||||
|
// 只对 str_url 参数值做 base64 编码
|
||||||
|
try {
|
||||||
|
const urlObj = new URL(result.str_url);
|
||||||
|
const strUrlParam = urlObj.searchParams.get('str_url') || '';
|
||||||
|
bytesToken = strUrlParam ? btoa(strUrlParam) : '';
|
||||||
|
} catch {
|
||||||
|
bytesToken = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
str_url: result.str_url,
|
||||||
|
bytes_token: bytesToken,
|
||||||
|
uint32_guarantee_status: result.uint32_guarantee_status,
|
||||||
|
ActionStatus: result.ActionStatus,
|
||||||
|
ErrorCode: result.ErrorCode,
|
||||||
|
ErrorInfo: result.ErrorInfo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async pollNewDeviceQR (uin: string, bytesToken: string) {
|
public static async pollNewDeviceQR (uin: string, bytesToken: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user