fix(api): 修复QQBot更新登录二维码刷新
This commit is contained in:
parent
3720819b96
commit
8a03e8c8b9
@ -53,6 +53,93 @@ describe('QqbotNapcatLoginService', () => {
|
||||
expect(getQrcode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('reuses the existing account container when refreshing login', async () => {
|
||||
const account = {
|
||||
id: 'account-1',
|
||||
selfId: '10001',
|
||||
};
|
||||
const existingContainer = {
|
||||
id: 'container-current',
|
||||
name: 'napcat-10001',
|
||||
};
|
||||
const accountService = {
|
||||
findById: jest.fn().mockResolvedValue(account),
|
||||
};
|
||||
const containerService = {
|
||||
prepareAccountContainer: jest.fn().mockResolvedValue(existingContainer),
|
||||
};
|
||||
const refreshService = new QqbotNapcatLoginService(
|
||||
{ get: jest.fn() } as unknown as ConfigService,
|
||||
accountService as unknown as QqbotAccountService,
|
||||
containerService as unknown as QqbotNapcatContainerService,
|
||||
);
|
||||
const startScan = jest
|
||||
.spyOn(refreshService as any, 'startScan')
|
||||
.mockResolvedValue({ sessionId: 'session-refresh' });
|
||||
|
||||
await refreshService.startRefresh('account-1');
|
||||
|
||||
expect(containerService.prepareAccountContainer).toHaveBeenCalledWith(
|
||||
account,
|
||||
);
|
||||
expect(startScan).toHaveBeenCalledWith(
|
||||
{
|
||||
accountId: 'account-1',
|
||||
expectedSelfId: '10001',
|
||||
mode: 'refresh',
|
||||
},
|
||||
existingContainer,
|
||||
);
|
||||
});
|
||||
|
||||
it('requires a qrcode different from the current one when refreshing qrcode', async () => {
|
||||
(service as any).sessions.set('session-refresh-qrcode', {
|
||||
containerId: 'container-3',
|
||||
containerName: 'napcat-3',
|
||||
createdAt: Date.now(),
|
||||
expiresAt: Date.now() + 60_000,
|
||||
id: 'session-refresh-qrcode',
|
||||
mode: 'refresh',
|
||||
qrcode: 'old-qrcode',
|
||||
status: 'pending',
|
||||
webuiPort: 6103,
|
||||
});
|
||||
const container = { id: 'container-3' };
|
||||
jest
|
||||
.spyOn(service as any, 'getSessionContainer')
|
||||
.mockResolvedValue(container);
|
||||
jest.spyOn(service as any, 'callRefreshQrcode').mockResolvedValue('');
|
||||
jest.spyOn(service as any, 'getQrcode').mockResolvedValue('new-qrcode');
|
||||
|
||||
const result = await service.refreshQrcode('session-refresh-qrcode');
|
||||
|
||||
expect(result.qrcode).toBe('new-qrcode');
|
||||
expect((service as any).getQrcode).toHaveBeenCalledWith(container, true, {
|
||||
requireFresh: true,
|
||||
staleQrcode: 'old-qrcode',
|
||||
});
|
||||
});
|
||||
|
||||
it('retries while NapCat still exposes the stale qrcode', async () => {
|
||||
jest.spyOn(service as any, 'sleep').mockResolvedValue(undefined);
|
||||
jest
|
||||
.spyOn(service as any, 'postNapcat')
|
||||
.mockResolvedValueOnce({ qrcode: 'old-qrcode' })
|
||||
.mockResolvedValueOnce({ qrcode: 'new-qrcode' });
|
||||
|
||||
const result = await (service as any).getQrcode(
|
||||
{ id: 'container-4' },
|
||||
true,
|
||||
{
|
||||
requireFresh: true,
|
||||
staleQrcode: 'old-qrcode',
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toBe('new-qrcode');
|
||||
expect((service as any).postNapcat).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('does not replace current qrcode with expired status qrcode', async () => {
|
||||
(service as any).sessions.set('session-2', {
|
||||
containerId: 'container-2',
|
||||
|
||||
@ -57,6 +57,15 @@ type QqbotLoginScanSession = {
|
||||
webuiPort?: null | number;
|
||||
};
|
||||
|
||||
type QrcodeLookupOptions = {
|
||||
requireFresh?: boolean;
|
||||
staleQrcode?: string;
|
||||
};
|
||||
|
||||
type QrcodeRefreshOptions = QrcodeLookupOptions & {
|
||||
fallbackStatus?: NapcatLoginStatus;
|
||||
};
|
||||
|
||||
export type QqbotLoginScanResult = {
|
||||
accountId?: string;
|
||||
containerId?: string;
|
||||
@ -116,7 +125,10 @@ export class QqbotNapcatLoginService {
|
||||
}
|
||||
|
||||
const container = await this.getSessionContainer(session);
|
||||
session.qrcode = await this.refreshOrGetQrcode(container, true);
|
||||
session.qrcode = await this.refreshOrGetQrcode(container, true, {
|
||||
requireFresh: true,
|
||||
staleQrcode: session.qrcode,
|
||||
});
|
||||
session.expiresAt = Date.now() + this.getSessionTtlMs();
|
||||
session.errorMessage = undefined;
|
||||
this.sessions.set(session.id, session);
|
||||
@ -174,11 +186,11 @@ export class QqbotNapcatLoginService {
|
||||
return this.completeLogin(session, container);
|
||||
}
|
||||
|
||||
const qrcode = await this.refreshOrGetQrcode(
|
||||
container,
|
||||
true,
|
||||
loginStatus,
|
||||
);
|
||||
const qrcode = await this.refreshOrGetQrcode(container, true, {
|
||||
fallbackStatus: loginStatus,
|
||||
requireFresh: this.isExpiredQrcodeStatus(loginStatus),
|
||||
staleQrcode: loginStatus.qrcodeurl,
|
||||
});
|
||||
const session = this.createSession({
|
||||
...options,
|
||||
container,
|
||||
@ -390,7 +402,11 @@ export class QqbotNapcatLoginService {
|
||||
});
|
||||
}
|
||||
|
||||
private async getQrcode(container: QqbotNapcatRuntime, retry = false) {
|
||||
private async getQrcode(
|
||||
container: QqbotNapcatRuntime,
|
||||
retry = false,
|
||||
options: QrcodeLookupOptions = {},
|
||||
) {
|
||||
return this.executeNapcatRequest(retry, async () => {
|
||||
try {
|
||||
const data = await this.postNapcat<NapcatQrcode>(
|
||||
@ -399,16 +415,16 @@ export class QqbotNapcatLoginService {
|
||||
);
|
||||
const qrcode = this.pickQrcode(data);
|
||||
if (!qrcode) {
|
||||
return this.getQrcodeFromStatus(container);
|
||||
return this.getQrcodeFromStatus(container, options);
|
||||
}
|
||||
return qrcode;
|
||||
return this.ensureFreshQrcode(qrcode, options);
|
||||
} catch (err) {
|
||||
if (this.isAlreadyLoggedIn(err)) {
|
||||
const status = await this.getLoginStatus(container);
|
||||
return status.qrcodeurl || '';
|
||||
return this.ensureFreshQrcode(status.qrcodeurl || '', options);
|
||||
}
|
||||
if (this.isQrcodePending(err)) {
|
||||
return this.getQrcodeFromStatus(container);
|
||||
return this.getQrcodeFromStatus(container, options);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
@ -418,31 +434,63 @@ export class QqbotNapcatLoginService {
|
||||
private async refreshOrGetQrcode(
|
||||
container: QqbotNapcatRuntime,
|
||||
retry = false,
|
||||
fallbackStatus?: NapcatLoginStatus,
|
||||
options: QrcodeRefreshOptions = {},
|
||||
) {
|
||||
const lookupOptions: QrcodeLookupOptions = {
|
||||
requireFresh: options.requireFresh,
|
||||
staleQrcode: options.staleQrcode,
|
||||
};
|
||||
try {
|
||||
const refreshedQrcode = await this.callRefreshQrcode(container, retry);
|
||||
if (refreshedQrcode) return refreshedQrcode;
|
||||
return await this.getQrcode(container, retry);
|
||||
if (refreshedQrcode) {
|
||||
return this.ensureFreshQrcode(refreshedQrcode, lookupOptions);
|
||||
}
|
||||
return await this.getQrcode(container, retry, lookupOptions);
|
||||
} catch (err) {
|
||||
if (
|
||||
fallbackStatus?.qrcodeurl &&
|
||||
!this.isExpiredQrcodeStatus(fallbackStatus)
|
||||
!lookupOptions.requireFresh &&
|
||||
options.fallbackStatus?.qrcodeurl &&
|
||||
!this.isExpiredQrcodeStatus(options.fallbackStatus)
|
||||
) {
|
||||
return fallbackStatus.qrcodeurl;
|
||||
return options.fallbackStatus.qrcodeurl;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private async getQrcodeFromStatus(container: QqbotNapcatRuntime) {
|
||||
private async getQrcodeFromStatus(
|
||||
container: QqbotNapcatRuntime,
|
||||
options: QrcodeLookupOptions = {},
|
||||
) {
|
||||
const status = await this.getLoginStatus(container);
|
||||
if (status.qrcodeurl && !this.isExpiredQrcodeStatus(status)) {
|
||||
return status.qrcodeurl;
|
||||
return this.ensureFreshQrcode(status.qrcodeurl, options);
|
||||
}
|
||||
if (options.requireFresh && status.qrcodeurl) {
|
||||
throw new Error('NapCat 二维码仍未刷新');
|
||||
}
|
||||
throwVbenError('NapCat 未返回登录二维码');
|
||||
}
|
||||
|
||||
private ensureFreshQrcode(qrcode: string, options: QrcodeLookupOptions = {}) {
|
||||
const normalized = `${qrcode || ''}`.trim();
|
||||
if (options.requireFresh && !normalized) {
|
||||
throw new Error('NapCat 二维码仍未刷新');
|
||||
}
|
||||
if (
|
||||
normalized &&
|
||||
options.requireFresh &&
|
||||
this.isSameQrcode(normalized, options.staleQrcode)
|
||||
) {
|
||||
throw new Error('NapCat 二维码仍未刷新');
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private isSameQrcode(left: string, right?: string) {
|
||||
return !!right && left.trim() === right.trim();
|
||||
}
|
||||
|
||||
private pickQrcode(data?: NapcatQrcode | null) {
|
||||
if (!data) return '';
|
||||
return `${data.qrcode || data.qrcodeurl || data.url || ''}`.trim();
|
||||
@ -582,6 +630,7 @@ export class QqbotNapcatLoginService {
|
||||
'ETIMEDOUT',
|
||||
'NapCat 请求超时',
|
||||
'NapCat 未返回登录二维码',
|
||||
'NapCat 二维码仍未刷新',
|
||||
'QRCode Get Error',
|
||||
'socket hang up',
|
||||
].some((keyword) => message.includes(keyword));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user