fix: 修复 QQBot 更新登录链路
This commit is contained in:
parent
b0cf9323b8
commit
fbaaee8bd7
@ -55,6 +55,7 @@ export class QqbotNapcatLoginService {
|
||||
{
|
||||
accountId: account.id,
|
||||
expectedSelfId: account.selfId,
|
||||
forceRelogin: true,
|
||||
mode: 'refresh',
|
||||
},
|
||||
container,
|
||||
@ -83,12 +84,16 @@ export class QqbotNapcatLoginService {
|
||||
}
|
||||
|
||||
if (loginStatus.isOffline) {
|
||||
if (session.mode === 'refresh') {
|
||||
await this.resetNapcatForLogin(container, { waitForReady: false });
|
||||
} else {
|
||||
await this.restartNapcatForLogin(container, { waitForReady: false });
|
||||
}
|
||||
session.lastRestartedAt = Date.now();
|
||||
return this.keepSessionPending(
|
||||
session,
|
||||
loginStatus.loginError ||
|
||||
'NapCat 账号已离线,已重启容器并重新生成二维码',
|
||||
'NapCat 账号已离线,已重新生成二维码',
|
||||
true,
|
||||
);
|
||||
}
|
||||
@ -162,6 +167,7 @@ export class QqbotNapcatLoginService {
|
||||
options: {
|
||||
accountId?: string;
|
||||
expectedSelfId?: string;
|
||||
forceRelogin?: boolean;
|
||||
mode: QqbotLoginScanMode;
|
||||
},
|
||||
container: QqbotNapcatRuntime,
|
||||
@ -169,9 +175,17 @@ export class QqbotNapcatLoginService {
|
||||
await this.cleanupSessions();
|
||||
|
||||
try {
|
||||
if (options.forceRelogin) {
|
||||
await this.resetNapcatForLogin(container);
|
||||
}
|
||||
|
||||
const loginStatus = await this.getLoginStatus(container, true);
|
||||
if (loginStatus.isOffline) {
|
||||
if (options.forceRelogin) {
|
||||
await this.resetNapcatForLogin(container, { waitForReady: false });
|
||||
} else {
|
||||
await this.restartNapcatForLogin(container, { waitForReady: false });
|
||||
}
|
||||
const session = this.createSession({
|
||||
...options,
|
||||
container,
|
||||
@ -180,7 +194,18 @@ export class QqbotNapcatLoginService {
|
||||
session.lastRestartedAt = Date.now();
|
||||
session.errorMessage =
|
||||
loginStatus.loginError ||
|
||||
'NapCat 账号已离线,已重启容器并重新生成二维码';
|
||||
'NapCat 账号已离线,已重新生成二维码';
|
||||
this.sessions.set(session.id, session);
|
||||
return this.toResult(session);
|
||||
}
|
||||
|
||||
if (options.forceRelogin && loginStatus.isLogin) {
|
||||
const session = this.createSession({
|
||||
...options,
|
||||
container,
|
||||
status: 'pending',
|
||||
});
|
||||
session.errorMessage = 'NapCat 正在重新生成二维码,请稍后刷新';
|
||||
this.sessions.set(session.id, session);
|
||||
return this.toResult(session);
|
||||
}
|
||||
@ -588,6 +613,24 @@ export class QqbotNapcatLoginService {
|
||||
return this.requestNapcat<T>(container, path, body, credential);
|
||||
}
|
||||
|
||||
private async resetNapcatForLogin(
|
||||
container: QqbotNapcatRuntime,
|
||||
options: NapcatRestartOptions = {},
|
||||
) {
|
||||
const resetByContainer =
|
||||
await this.containerService.resetRuntimeLoginState(container);
|
||||
if (!resetByContainer) {
|
||||
await this.restartNapcatForLogin(container, options);
|
||||
return;
|
||||
}
|
||||
|
||||
this.credentials.delete(this.getCredentialCacheKey(container));
|
||||
if (options.waitForReady === false) return;
|
||||
|
||||
await this.toolsService.sleep(this.getRestartDelayMs());
|
||||
await this.getLoginStatus(container, true);
|
||||
}
|
||||
|
||||
private async restartNapcatForLogin(
|
||||
container: QqbotNapcatRuntime,
|
||||
options: NapcatRestartOptions = {},
|
||||
|
||||
@ -166,6 +166,34 @@ export class QqbotNapcatContainerService {
|
||||
return true;
|
||||
}
|
||||
|
||||
async resetRuntimeLoginState(runtime: QqbotNapcatRuntime) {
|
||||
if (this.getManagedMode() !== 'ssh' || !runtime.id || !runtime.name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const container = await this.containerRepository.findOne({
|
||||
where: {
|
||||
id: runtime.id,
|
||||
isDeleted: false,
|
||||
},
|
||||
});
|
||||
if (!container) {
|
||||
throwVbenError('NapCat 容器不存在或已删除');
|
||||
}
|
||||
|
||||
const script = this.buildRemoteResetLoginStateScript(container);
|
||||
await this.runProcess('ssh', [...this.getSshArgs(), 'sh -s'], script);
|
||||
await this.containerRepository.update(
|
||||
{ id: runtime.id },
|
||||
{
|
||||
lastError: null,
|
||||
lastStartedAt: new Date(),
|
||||
status: 'running',
|
||||
},
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async removeContainer(containerId: string) {
|
||||
const container = await this.containerRepository.findOne({
|
||||
where: {
|
||||
@ -221,6 +249,39 @@ fi
|
||||
`;
|
||||
}
|
||||
|
||||
private buildRemoteResetLoginStateScript(container: QqbotNapcatContainer) {
|
||||
const dataDir = this.sh(container.dataDir || '');
|
||||
const name = this.sh(container.name);
|
||||
const rootDir = this.sh(this.getRootDir());
|
||||
|
||||
return `
|
||||
set -eu
|
||||
NAME=${name}
|
||||
DATA_DIR=${dataDir}
|
||||
ROOT_DIR=${rootDir}
|
||||
|
||||
if [ -z "$DATA_DIR" ] || [ "$DATA_DIR" = "/" ]; then
|
||||
echo "unsafe empty data dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$DATA_DIR" in
|
||||
"$ROOT_DIR"/*)
|
||||
;;
|
||||
*)
|
||||
echo "skip unsafe data dir: $DATA_DIR" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
docker exec "$NAME" rm -f /app/napcat/cache/qrcode.png >/dev/null 2>&1 || true
|
||||
docker stop "$NAME" >/dev/null 2>&1 || true
|
||||
mkdir -p "$DATA_DIR/QQ"
|
||||
find "$DATA_DIR/QQ" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
|
||||
docker start "$NAME" >/dev/null
|
||||
`;
|
||||
}
|
||||
|
||||
private async getPrimaryRuntime(accountId: string) {
|
||||
const binding = await this.bindingRepository.findOne({
|
||||
order: {
|
||||
|
||||
@ -90,12 +90,59 @@ describe('QqbotNapcatLoginService', () => {
|
||||
{
|
||||
accountId: 'account-1',
|
||||
expectedSelfId: '10001',
|
||||
forceRelogin: true,
|
||||
mode: 'refresh',
|
||||
},
|
||||
existingContainer,
|
||||
);
|
||||
});
|
||||
|
||||
it('resets the existing container login state before refresh login scan', async () => {
|
||||
const container = {
|
||||
baseUrl: 'http://127.0.0.1:6103/',
|
||||
id: 'container-current',
|
||||
name: 'napcat-10001',
|
||||
};
|
||||
const containerService = {
|
||||
resetRuntimeLoginState: jest.fn().mockResolvedValue(true),
|
||||
};
|
||||
const refreshService = new QqbotNapcatLoginService(
|
||||
{ get: jest.fn() } as unknown as ConfigService,
|
||||
{} as QqbotAccountService,
|
||||
containerService as unknown as QqbotNapcatContainerService,
|
||||
new ToolsService(),
|
||||
);
|
||||
jest
|
||||
.spyOn((refreshService as any).toolsService, 'sleep')
|
||||
.mockResolvedValue(undefined);
|
||||
jest
|
||||
.spyOn(refreshService as any, 'cleanupSessions')
|
||||
.mockResolvedValue(undefined);
|
||||
jest
|
||||
.spyOn(refreshService as any, 'getLoginStatus')
|
||||
.mockResolvedValueOnce({ isLogin: false })
|
||||
.mockResolvedValueOnce({ isLogin: false });
|
||||
jest
|
||||
.spyOn(refreshService as any, 'refreshOrGetQrcode')
|
||||
.mockResolvedValue('fresh-qrcode');
|
||||
|
||||
const result = await (refreshService as any).startScan(
|
||||
{
|
||||
accountId: 'account-1',
|
||||
expectedSelfId: '10001',
|
||||
forceRelogin: true,
|
||||
mode: 'refresh',
|
||||
},
|
||||
container,
|
||||
);
|
||||
|
||||
expect(result.status).toBe('pending');
|
||||
expect(result.qrcode).toBe('fresh-qrcode');
|
||||
expect(containerService.resetRuntimeLoginState).toHaveBeenCalledWith(
|
||||
container,
|
||||
);
|
||||
});
|
||||
|
||||
it('requires a qrcode different from the current one when refreshing qrcode', async () => {
|
||||
(service as any).sessions.set('session-refresh-qrcode', {
|
||||
containerId: 'container-3',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user