Overview
NapCat supports multiple login methods for QQ accounts. This guide covers QR code scanning, password login, and quick login with practical code examples from the framework.
Login Flow
The login process is managed by the NodeIKernelLoginListener which handles various login events:
const loginListener = new NodeIKernelLoginListener ();
// Login event handlers
loginListener . onQRCodeLoginSucceed = async ( loginResult ) => { ... }
loginListener . onQRCodeGetPicture = ({ qrcodeUrl }) => { ... }
loginListener . onLoginFailed = ( ... args ) => { ... }
loginListener . onPasswordLoginFailed = ( ... args ) => { ... }
The login system is implemented in napcat.ts:92-160 and provides callback-based event handling for all login methods.
QR Code Login
QR code login is the most secure and commonly used method.
Basic QR Code Flow
Initialize Login Service
The login service connects automatically when NapCat starts: loginListener . onLoginConnected = () => {
logger . log ( '[NapCat] [Framework] 登录服务已连接' );
// Request QR code
loginService . getQRCodePicture ();
};
Receive QR Code
When the QR code is generated, the onQRCodeGetPicture event fires: loginListener . onQRCodeGetPicture = ({ qrcodeUrl }) => {
WebUiDataRuntime . setQQLoginQrcodeURL ( qrcodeUrl );
logger . log ( '[NapCat] [Framework] 二维码已更新, URL:' , qrcodeUrl );
};
The qrcodeUrl is a data URL that can be displayed directly in the WebUI.
User Scans QR Code
When a user scans the QR code with their phone: loginListener . onQRCodeSessionUserScaned = () => {
logger . log ( '[NapCat] [Framework] 二维码已被扫描,等待确认...' );
};
Login Success
After the user confirms on their phone: loginListener . onQRCodeLoginSucceed = async ( loginResult ) => {
loginContext . isLogined = true ;
WebUiDataRuntime . setQQLoginStatus ( true );
WebUiDataRuntime . setQQLoginError ( '' );
await new Promise < void >( resolve => {
registerInitCallback (() => resolve ());
});
resolve ({
uid: loginResult . uid ,
uin: loginResult . uin ,
nick: '' , // Not available in QR login
online: true ,
});
};
Handle QR Code Expiration
QR codes expire after a certain time:
loginListener . onQRCodeSessionFailed = ( errType : number , errCode : number ) => {
if ( ! loginContext . isLogined ) {
logger . logError ( '[NapCat] [Framework] [Login] QRCode Session Failed, ErrType:' , errType , 'ErrCode:' , errCode );
if ( errType === 1 && errCode === 3 ) {
WebUiDataRuntime . setQQLoginError ( '二维码已过期,请刷新' );
}
}
};
Refresh QR Code
Implement QR code refresh functionality:
WebUiDataRuntime . setRefreshQRCodeCallback ( async () => {
loginService . getQRCodePicture ();
});
Password Login
Password login requires MD5-hashed password and may need additional verification.
Basic Password Login
Prepare Login Data
const loginData = {
uin: '123456789' ,
passwordMd5: 'md5_hashed_password' ,
step: 0 , // Initial step
newDeviceLoginSig: new Uint8Array (),
proofWaterSig: new Uint8Array (),
proofWaterRand: new Uint8Array (),
proofWaterSid: new Uint8Array (),
unusualDeviceCheckSig: new Uint8Array (),
};
Execute Password Login
From napcat.ts:244-276: WebUiDataRuntime . setPasswordLoginCall ( async ( uin : string , passwordMd5 : string ) => {
return await new Promise (( resolve ) => {
if ( ! uin || ! passwordMd5 ) {
return resolve ({
result: false ,
message: '密码登录失败:参数不完整'
});
}
logger . log ( '[NapCat] [Framework] 正在密码登录' , uin );
loginService . passwordLogin ({
uin ,
passwordMd5 ,
step: 0 ,
newDeviceLoginSig: new Uint8Array (),
proofWaterSig: new Uint8Array (),
proofWaterRand: new Uint8Array (),
proofWaterSid: new Uint8Array (),
unusualDeviceCheckSig: new Uint8Array (),
}). then ( res => {
// Handle response
});
});
});
Handle CAPTCHA Verification
If Tencent requires CAPTCHA verification:
loginService . passwordLogin ( loginData ). then ( res => {
// CAPTCHA required
if ( res . result === '140022008' ) {
const proofWaterUrl = res . loginErrorInfo ?. proofWaterUrl || '' ;
resolve ({
result: false ,
message: '需要验证码' ,
needCaptcha: true ,
proofWaterUrl
});
}
});
CAPTCHA Login (Step 2)
After user completes CAPTCHA:
WebUiDataRuntime . setCaptchaLoginCall ( async (
uin : string ,
passwordMd5 : string ,
ticket : string ,
randstr : string ,
sid : string
) => {
return await new Promise (( resolve ) => {
loginService . passwordLogin ({
uin ,
passwordMd5 ,
step: 1 , // CAPTCHA verification step
newDeviceLoginSig: new Uint8Array (),
proofWaterSig: new TextEncoder (). encode ( ticket ),
proofWaterRand: new TextEncoder (). encode ( randstr ),
proofWaterSid: new TextEncoder (). encode ( sid ),
unusualDeviceCheckSig: new Uint8Array (),
}). then ( res => {
if ( res . result === '0' ) {
loginContext . isLogined = true ;
WebUiDataRuntime . setQQLoginStatus ( true );
resolve ({ result: true , message: '' });
} else {
const errMsg = res . loginErrorInfo ?. errMsg || '验证码登录失败' ;
resolve ({ result: false , message: errMsg });
}
});
});
});
New Device Verification
For new or unusual devices:
loginService . passwordLogin ( loginData ). then ( res => {
// New device verification required
if ( res . result === '140022010' || res . result === '140022011' ) {
const jumpUrl = res . loginErrorInfo ?. jumpUrl || '' ;
const newDevicePullQrCodeSig = res . loginErrorInfo ?. newDevicePullQrCodeSig || '' ;
resolve ({
result: false ,
message: '新设备需要扫码验证' ,
needNewDevice: true ,
jumpUrl ,
newDevicePullQrCodeSig
});
}
});
New Device Login (Step 3)
WebUiDataRuntime . setNewDeviceLoginCall ( async (
uin : string ,
passwordMd5 : string ,
newDevicePullQrCodeSig : string
) => {
return await new Promise (( resolve ) => {
loginService . passwordLogin ({
uin ,
passwordMd5 ,
step: 2 , // New device verification step
newDeviceLoginSig: new TextEncoder (). encode ( newDevicePullQrCodeSig ),
proofWaterSig: new Uint8Array (),
proofWaterRand: new Uint8Array (),
proofWaterSid: new Uint8Array (),
unusualDeviceCheckSig: new Uint8Array (),
}). then ( res => {
if ( res . result === '0' ) {
loginContext . isLogined = true ;
resolve ({ result: true , message: '' });
}
});
});
});
Quick Login
Quick login allows re-authentication with previously logged-in accounts.
Get Login List
Retrieve the list of accounts that support quick login:
loginListener . onLoginConnected = () => {
// Get historical login list for WebUI
loginService . getLoginList (). then (( res ) => {
const list = res . LocalLoginInfoList . filter (( item ) => item . isQuickLogin );
WebUiDataRuntime . setQQQuickLoginList ( list . map (( item ) => item . uin . toString ()));
WebUiDataRuntime . setQQNewLoginList ( list );
}). catch ( e => logger . logError ( '[NapCat] [Framework] 获取登录列表失败:' , e ));
};
Execute Quick Login
From napcat.ts:211-235:
WebUiDataRuntime . setQuickLoginCall ( async ( uin : string ) => {
return await new Promise (( resolve ) => {
if ( ! uin ) {
return resolve ({ result: false , message: '快速登录失败' });
}
logger . log ( '[NapCat] [Framework] 正在快速登录' , uin );
loginService . quickLoginWithUin ( uin ). then ( res => {
const success = res . result === '0' && ! res . loginErrorInfo ?. errMsg ;
if ( ! success ) {
const errMsg = res . loginErrorInfo ?. errMsg ||
`快速登录失败,错误码: ${ res . result } ` ;
WebUiDataRuntime . setQQLoginError ( errMsg );
resolve ({ result: false , message: errMsg });
} else {
loginContext . isLogined = true ;
WebUiDataRuntime . setQQLoginStatus ( true );
WebUiDataRuntime . setQQLoginError ( '' );
resolve ({ result: true , message: '' });
}
}). catch (( e ) => {
logger . logError ( '[NapCat] [Framework] 快速登录异常:' , e );
resolve ({ result: false , message: '快速登录发生错误' });
});
});
});
Handle Login Failures
Implement comprehensive error handling:
loginListener . onLoginFailed = ( ... args ) => {
const errInfo = JSON . stringify ( args );
logger . logError ( '[NapCat] [Framework] [Login] Login Failed, ErrInfo:' , errInfo );
WebUiDataRuntime . setQQLoginError ( `登录失败: ${ errInfo } ` );
};
loginListener . onPasswordLoginFailed = ( ... args ) => {
logger . logError ( '[NapCat] [Framework] 密码登录失败:' , ... args );
};
Check Existing Login
Prevent duplicate logins:
loginListener . onUserLoggedIn = ( userid : string ) => {
const tips = `当前账号( ${ userid } )已登录,无法重复登录` ;
logger . logError ( tips );
WebUiDataRuntime . setQQLoginError ( tips );
};
Always handle login errors gracefully and provide clear feedback to users. Password login may trigger additional security verifications.
Complete Login Example
QR Code Login
Password Login
Quick Login
// Initialize login listener
const loginListener = new NodeIKernelLoginListener ();
// QR code received
loginListener . onQRCodeGetPicture = ({ qrcodeUrl }) => {
console . log ( 'QR Code URL:' , qrcodeUrl );
// Display QR code to user
};
// QR code scanned
loginListener . onQRCodeSessionUserScaned = () => {
console . log ( 'QR code scanned, waiting for confirmation...' );
};
// Login successful
loginListener . onQRCodeLoginSucceed = async ( result ) => {
console . log ( 'Login successful!' , result . uid , result . uin );
};
// Register listener
loginService . addKernelLoginListener ( loginListener );
// Request QR code
loginService . getQRCodePicture ();
Best Practices
Prefer QR Code Login : Most secure and requires no password handling
Hash Passwords : Always use MD5-hashed passwords, never plain text
Handle All Error Cases : Implement handlers for CAPTCHA and device verification
Timeout Management : Set appropriate timeouts for login operations
State Management : Track login state to prevent duplicate attempts
Next Steps
Message Handling Learn how to send and receive messages
Deployment Deploy NapCat in production