mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
fix: QR login now persists cookies, shows server-generated QR image, passes timestamp
- Auth router accepts CookieStore and persists cookies on login - NeteaseProvider passes timestamp to prevent cached QR responses - QR image from server used directly (qrimg field) - Cookie saved to disk on confirmed QR scan Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f77a6f0fd7
commit
9755ba2909
6 files changed
+50
-14
No files matched your search
@@ -58,6 +58,7 @@ async function main() {
|
|||||||
database: db,
|
database: db,
|
||||||
config,
|
config,
|
||||||
logger,
|
logger,
|
||||||
|
cookieStore,
|
||||||
staticDir: STATIC_DIR,
|
staticDir: STATIC_DIR,
|
||||||
});
|
});
|
||||||
await webServer.start();
|
await webServer.start();
|
||||||
|
|||||||
@@ -188,13 +188,16 @@ export class NeteaseProvider implements MusicProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getQrCode(): Promise<QrCodeResult> {
|
async getQrCode(): Promise<QrCodeResult> {
|
||||||
const keyRes = await this.api.get("/login/qr/key");
|
const keyRes = await this.api.get("/login/qr/key", {
|
||||||
|
params: { timestamp: Date.now() },
|
||||||
|
});
|
||||||
const key = keyRes.data?.data?.unikey ?? "";
|
const key = keyRes.data?.data?.unikey ?? "";
|
||||||
const createRes = await this.api.get("/login/qr/create", {
|
const createRes = await this.api.get("/login/qr/create", {
|
||||||
params: { key, qrimg: true },
|
params: { key, qrimg: true },
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
qrUrl: createRes.data?.data?.qrurl ?? "",
|
qrUrl: createRes.data?.data?.qrurl ?? "",
|
||||||
|
qrImg: createRes.data?.data?.qrimg ?? "",
|
||||||
key,
|
key,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -203,7 +206,7 @@ export class NeteaseProvider implements MusicProvider {
|
|||||||
key: string
|
key: string
|
||||||
): Promise<"waiting" | "scanned" | "confirmed" | "expired"> {
|
): Promise<"waiting" | "scanned" | "confirmed" | "expired"> {
|
||||||
const res = await this.api.get("/login/qr/check", {
|
const res = await this.api.get("/login/qr/check", {
|
||||||
params: { key },
|
params: { key, timestamp: Date.now() },
|
||||||
});
|
});
|
||||||
const code = res.data?.code;
|
const code = res.data?.code;
|
||||||
switch (code) {
|
switch (code) {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export interface SearchResult {
|
|||||||
|
|
||||||
export interface QrCodeResult {
|
export interface QrCodeResult {
|
||||||
qrUrl: string;
|
qrUrl: string;
|
||||||
|
qrImg?: string; // base64 data URL of QR image
|
||||||
key: string;
|
key: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
-1
@@ -1,11 +1,13 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import type { MusicProvider } from "../../music/provider.js";
|
import type { MusicProvider } from "../../music/provider.js";
|
||||||
|
import type { CookieStore } from "../../music/auth.js";
|
||||||
import type { Logger } from "../../logger.js";
|
import type { Logger } from "../../logger.js";
|
||||||
|
|
||||||
export function createAuthRouter(
|
export function createAuthRouter(
|
||||||
neteaseProvider: MusicProvider,
|
neteaseProvider: MusicProvider,
|
||||||
qqProvider: MusicProvider,
|
qqProvider: MusicProvider,
|
||||||
logger: Logger
|
logger: Logger,
|
||||||
|
cookieStore?: CookieStore
|
||||||
): Router {
|
): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -18,8 +20,10 @@ export function createAuthRouter(
|
|||||||
const platform = req.query.platform as string;
|
const platform = req.query.platform as string;
|
||||||
const provider = getProvider(platform);
|
const provider = getProvider(platform);
|
||||||
const status = await provider.getAuthStatus();
|
const status = await provider.getAuthStatus();
|
||||||
|
logger.debug({ platform, status }, "Auth status check");
|
||||||
res.json({ platform: provider.platform, ...status });
|
res.json({ platform: provider.platform, ...status });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
logger.error({ err }, "Auth status check failed");
|
||||||
res.status(500).json({ error: (err as Error).message });
|
res.status(500).json({ error: (err as Error).message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -29,6 +33,7 @@ export function createAuthRouter(
|
|||||||
const { platform } = req.body;
|
const { platform } = req.body;
|
||||||
const provider = getProvider(platform);
|
const provider = getProvider(platform);
|
||||||
const qr = await provider.getQrCode();
|
const qr = await provider.getQrCode();
|
||||||
|
logger.info({ platform, key: qr.key }, "QR code generated");
|
||||||
res.json(qr);
|
res.json(qr);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error({ err }, "QR code generation failed");
|
logger.error({ err }, "QR code generation failed");
|
||||||
@@ -45,8 +50,21 @@ export function createAuthRouter(
|
|||||||
}
|
}
|
||||||
const provider = getProvider(platform as string);
|
const provider = getProvider(platform as string);
|
||||||
const status = await provider.checkQrCodeStatus(key as string);
|
const status = await provider.checkQrCodeStatus(key as string);
|
||||||
|
logger.info({ platform, status, key }, "QR status check");
|
||||||
|
|
||||||
|
// When confirmed, persist cookie
|
||||||
|
if (status === "confirmed") {
|
||||||
|
const cookie = provider.getCookie();
|
||||||
|
const plat = (platform as string) === "qq" ? "qq" as const : "netease" as const;
|
||||||
|
if (cookie && cookieStore) {
|
||||||
|
cookieStore.save(plat, cookie);
|
||||||
|
logger.info({ platform: plat }, "Cookie persisted to disk");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res.json({ status });
|
res.json({ status });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
logger.error({ err }, "QR status check failed");
|
||||||
res.status(500).json({ error: (err as Error).message });
|
res.status(500).json({ error: (err as Error).message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -83,6 +101,9 @@ export function createAuthRouter(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const success = await neteaseProvider.loginWithSms(phone, code);
|
const success = await neteaseProvider.loginWithSms(phone, code);
|
||||||
|
if (success && cookieStore) {
|
||||||
|
cookieStore.save("netease", neteaseProvider.getCookie());
|
||||||
|
}
|
||||||
res.json({ success });
|
res.json({ success });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(500).json({ error: (err as Error).message });
|
res.status(500).json({ error: (err as Error).message });
|
||||||
@@ -97,6 +118,10 @@ export function createAuthRouter(
|
|||||||
}
|
}
|
||||||
const provider = getProvider(platform);
|
const provider = getProvider(platform);
|
||||||
provider.setCookie(cookie);
|
provider.setCookie(cookie);
|
||||||
|
const plat = platform === "qq" ? "qq" as const : "netease" as const;
|
||||||
|
if (cookieStore) {
|
||||||
|
cookieStore.save(plat, cookie);
|
||||||
|
}
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -7,6 +7,7 @@ import type { MusicProvider } from "../music/provider.js";
|
|||||||
import type { BotDatabase } from "../data/database.js";
|
import type { BotDatabase } from "../data/database.js";
|
||||||
import type { BotConfig } from "../data/config.js";
|
import type { BotConfig } from "../data/config.js";
|
||||||
import type { Logger } from "../logger.js";
|
import type { Logger } from "../logger.js";
|
||||||
|
import type { CookieStore } from "../music/auth.js";
|
||||||
import { createBotRouter } from "./api/bot.js";
|
import { createBotRouter } from "./api/bot.js";
|
||||||
import { createMusicRouter } from "./api/music.js";
|
import { createMusicRouter } from "./api/music.js";
|
||||||
import { createPlayerRouter } from "./api/player.js";
|
import { createPlayerRouter } from "./api/player.js";
|
||||||
@@ -21,6 +22,7 @@ export interface WebServerOptions {
|
|||||||
database: BotDatabase;
|
database: BotDatabase;
|
||||||
config: BotConfig;
|
config: BotConfig;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
|
cookieStore?: CookieStore;
|
||||||
staticDir?: string;
|
staticDir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +49,7 @@ export function createWebServer(options: WebServerOptions): WebServer {
|
|||||||
app.use("/api/player", createPlayerRouter(options.botManager, logger));
|
app.use("/api/player", createPlayerRouter(options.botManager, logger));
|
||||||
app.use(
|
app.use(
|
||||||
"/api/auth",
|
"/api/auth",
|
||||||
createAuthRouter(options.neteaseProvider, options.qqProvider, logger)
|
createAuthRouter(options.neteaseProvider, options.qqProvider, logger, options.cookieStore)
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get("/api/health", (_req, res) => {
|
app.get("/api/health", (_req, res) => {
|
||||||
|
|||||||
+14
-10
@@ -277,18 +277,22 @@ async function startQrLogin(platform: string) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.post('/api/auth/qrcode', { platform });
|
const res = await axios.post('/api/auth/qrcode', { platform });
|
||||||
const { qrUrl, key } = res.data;
|
const { qrUrl, qrImg, key } = res.data;
|
||||||
qr.key = key;
|
qr.key = key;
|
||||||
|
|
||||||
// Generate QR code image
|
// Use server-generated QR image if available, otherwise generate client-side
|
||||||
qr.dataUrl = await QRCode.toDataURL(qrUrl, {
|
if (qrImg) {
|
||||||
width: 200,
|
qr.dataUrl = qrImg;
|
||||||
margin: 2,
|
} else {
|
||||||
color: {
|
qr.dataUrl = await QRCode.toDataURL(qrUrl, {
|
||||||
dark: store.theme === 'dark' ? '#ffffff' : '#000000',
|
width: 200,
|
||||||
light: store.theme === 'dark' ? '#2a2a2a' : '#ffffff',
|
margin: 2,
|
||||||
},
|
color: {
|
||||||
});
|
dark: store.theme === 'dark' ? '#ffffff' : '#000000',
|
||||||
|
light: store.theme === 'dark' ? '#2a2a2a' : '#ffffff',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
qr.loading = false;
|
qr.loading = false;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user