diff --git a/src/qqbot/account/qqbot-napcat-login.service.ts b/src/qqbot/account/qqbot-napcat-login.service.ts index 3649ce2..91ae193 100644 --- a/src/qqbot/account/qqbot-napcat-login.service.ts +++ b/src/qqbot/account/qqbot-napcat-login.service.ts @@ -1232,6 +1232,7 @@ export class QqbotNapcatLoginService { let loginInfo: NapcatLoginInfo | undefined; let loggedInSelfId = ''; + const passwordLogSinceMs = Date.now(); session.passwordMd5 = createHash('md5') .update(password, 'utf8') .digest('hex'); @@ -1268,7 +1269,7 @@ export class QqbotNapcatLoginService { ); loginStatus = await this.waitForPasswordLoginStatus( container, - session.lastRestartedAt, + passwordLogSinceMs, ); if (loginStatus.isLogin) { @@ -1295,7 +1296,7 @@ export class QqbotNapcatLoginService { const captchaUrl = await this.resolvePasswordCaptchaUrl( container, loginStatus, - session.lastRestartedAt, + passwordLogSinceMs, ); if (captchaUrl) { this.keepPasswordCaptchaPending( diff --git a/test/qqbot/account/qqbot-napcat-login.service.spec.ts b/test/qqbot/account/qqbot-napcat-login.service.spec.ts index 5bbefd9..cdb4473 100644 --- a/test/qqbot/account/qqbot-napcat-login.service.spec.ts +++ b/test/qqbot/account/qqbot-napcat-login.service.spec.ts @@ -748,6 +748,81 @@ describe('QqbotNapcatLoginService', () => { expect(session.captchaUrl).toBe(captchaUrl); }); + it('anchors password captcha log lookup before runtime env rebuild', async () => { + const captchaUrl = + 'https://ti.qq.com/safe/tools/captcha/sms-verify-login?uin=10001'; + const container = { + baseUrl: 'http://127.0.0.1:6103/', + id: 'container-password-log-window', + name: 'napcat-10001', + }; + const passwordLogSinceMs = 1000; + const envRebuildStartedAt = 5000; + let envRebuildStarted = false; + const containerService = { + ensureRuntimeLoginEnv: jest.fn().mockImplementation(async () => { + envRebuildStarted = true; + return { changed: true, ok: true }; + }), + restartRuntimeContainer: jest.fn().mockResolvedValue(true), + }; + const refreshService = new QqbotNapcatLoginService( + { + get: jest.fn((key: string) => { + const values: Record = { + QQBOT_NAPCAT_LOGIN_POLL_INTERVAL_MS: '1', + QQBOT_NAPCAT_PASSWORD_LOGIN_WAIT_MS: '1', + }; + return values[key] || ''; + }), + } as unknown as ConfigService, + {} as QqbotAccountService, + containerService as unknown as QqbotNapcatContainerService, + new ToolsService(), + ); + const session = (refreshService as any).createSession({ + accountId: 'account-1', + container, + expectedSelfId: '10001', + mode: 'refresh', + preparingRelogin: true, + status: 'pending', + }); + jest.spyOn(Date, 'now').mockImplementation(() => + envRebuildStarted ? envRebuildStartedAt : passwordLogSinceMs, + ); + jest.spyOn(refreshService as any, 'waitForPasswordLoginStatus') + .mockResolvedValue({ + isLogin: false, + loginError: '密码回退需要验证码,请在 WebUi 中继续完成验证', + }); + jest + .spyOn(refreshService as any, 'resolvePasswordCaptchaUrl') + .mockResolvedValue(captchaUrl); + + const result = await (refreshService as any).tryPasswordRelogin( + session, + container, + 'qq-password', + ); + + expect(result).toBe(true); + expect( + (refreshService as any).waitForPasswordLoginStatus, + ).toHaveBeenCalledWith(container, passwordLogSinceMs); + expect( + (refreshService as any).resolvePasswordCaptchaUrl, + ).toHaveBeenCalledWith( + container, + expect.objectContaining({ isLogin: false }), + passwordLogSinceMs, + ); + expect(session.lastRestartedAt).toBeGreaterThanOrEqual( + envRebuildStartedAt, + ); + expect(session.captchaUrl).toBe(captchaUrl); + }); + it('does not keep stale captcha from tail logs when current password status is processing', async () => { const staleCaptchaUrl = 'https://ti.qq.com/safe/tools/captcha/sms-verify-login?uin=10001';