mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-03 05:22:49 +08:00
feat(fm): let each web user link their own NetEase account for personal FM (#164)
With several people sharing one bot, personal FM always followed the one account the bot was logged in with. Each signed-in (non-guest) web user can now scan a QR code under Settings → 账户 to link their own NetEase account; FM they start from the WebUI then comes from their account. - user_music_cookies table (per user + platform, dropped with the user). - NeteaseProvider.pollQrLogin returns the cookie without storing it, so a personal login can never replace the bot's shared account; checkQrCodeStatus is now built on it. withCookie gives a view bound to another account. - /api/me/music/netease: status / qrcode / qrcode/status / unlink, acting only on req.user. The cookie never leaves the server. - POST /api/player/:botId/fm uses the caller's linked account for NetEase. Songs still resolve through the shared provider when played. TeamSpeak chat !fm keeps using the shared account: chat users are not tied to web accounts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
8ff51ea6e0
commit
ac4a12d8bd
11 files changed
+652
-11
No files matched your search
+33
-9
@@ -111,8 +111,10 @@ export class NeteaseProvider implements MusicProvider {
|
||||
private api: AxiosInstance;
|
||||
private cookie = "";
|
||||
private quality = "exhigh";
|
||||
private readonly baseUrl: string;
|
||||
|
||||
constructor(baseUrl: string) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.api = axios.create({
|
||||
baseURL: baseUrl,
|
||||
timeout: 10000,
|
||||
@@ -243,25 +245,47 @@ export class NeteaseProvider implements MusicProvider {
|
||||
async checkQrCodeStatus(
|
||||
key: string
|
||||
): Promise<"waiting" | "scanned" | "confirmed" | "expired"> {
|
||||
const { status, cookie } = await this.pollQrLogin(key);
|
||||
if (cookie) this.cookie = cookie;
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll a QR login and hand back the resulting cookie WITHOUT storing it on
|
||||
* this provider — for a web user linking their own account (#164), which
|
||||
* must never replace the bot's shared login.
|
||||
*/
|
||||
async pollQrLogin(
|
||||
key: string
|
||||
): Promise<{ status: "waiting" | "scanned" | "confirmed" | "expired"; cookie?: string }> {
|
||||
const res = await this.api.get("/login/qr/check", {
|
||||
params: { key, timestamp: Date.now() },
|
||||
});
|
||||
const code = res.data?.code;
|
||||
switch (code) {
|
||||
switch (res.data?.code) {
|
||||
case 801:
|
||||
return "waiting";
|
||||
return { status: "waiting" };
|
||||
case 802:
|
||||
return "scanned";
|
||||
return { status: "scanned" };
|
||||
case 803:
|
||||
if (res.data?.cookie) {
|
||||
this.cookie = res.data.cookie;
|
||||
}
|
||||
return "confirmed";
|
||||
return res.data?.cookie
|
||||
? { status: "confirmed", cookie: res.data.cookie }
|
||||
: { status: "confirmed" };
|
||||
default:
|
||||
return "expired";
|
||||
return { status: "expired" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A provider for the same API server logged in as another account (#164):
|
||||
* a web user's personal FM uses their own taste instead of the shared login.
|
||||
*/
|
||||
withCookie(cookie: string): NeteaseProvider {
|
||||
const view = new NeteaseProvider(this.baseUrl);
|
||||
view.setQuality(this.quality);
|
||||
view.setCookie(cookie);
|
||||
return view;
|
||||
}
|
||||
|
||||
async sendSmsCode(phone: string): Promise<boolean> {
|
||||
const res = await this.api.get("/captcha/sent", {
|
||||
params: { phone },
|
||||
|
||||
Reference in new issue
Block a user