mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-05 14:32:50 +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
@@ -144,6 +144,10 @@ export interface BotDatabase {
|
||||
removeFavorite(userId: string, playlistId: string, platform: string): boolean;
|
||||
getFavorites(userId: string): FavoritePlaylist[];
|
||||
isFavorited(userId: string, playlistId: string, platform: string): boolean;
|
||||
// Per-user music account cookies (#164).
|
||||
getUserMusicCookie(userId: string, platform: string): string | null;
|
||||
setUserMusicCookie(userId: string, platform: string, cookie: string): void;
|
||||
deleteUserMusicCookie(userId: string, platform: string): boolean;
|
||||
// Saved queues (Feature 1) — upsert by (ownerId, name), capped.
|
||||
saveQueue(ownerId: string, name: string, songs: StoredSong[]): SavedQueue;
|
||||
listSavedQueues(ownerId: string, includeShared: boolean): SavedQueueMeta[];
|
||||
@@ -328,6 +332,17 @@ function initTables(db: Database.Database): void {
|
||||
fmPlatform TEXT NOT NULL DEFAULT '',
|
||||
updatedAt TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- A web user's own music-platform login (#164), used for their personal
|
||||
-- FM instead of the bot's shared account. Secret: never sent to clients.
|
||||
CREATE TABLE IF NOT EXISTS user_music_cookies (
|
||||
userId TEXT NOT NULL,
|
||||
platform TEXT NOT NULL,
|
||||
cookie TEXT NOT NULL,
|
||||
updatedAt TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
PRIMARY KEY (userId, platform),
|
||||
FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
@@ -453,6 +468,17 @@ export function createDatabase(dbPath: string): BotDatabase {
|
||||
SELECT 1 FROM favorite_playlists WHERE userId = ? AND playlistId = ? AND platform = ?
|
||||
`);
|
||||
|
||||
const selectUserMusicCookie = db.prepare(
|
||||
`SELECT cookie FROM user_music_cookies WHERE userId = ? AND platform = ?`,
|
||||
);
|
||||
const upsertUserMusicCookie = db.prepare(`
|
||||
INSERT INTO user_music_cookies (userId, platform, cookie) VALUES (?, ?, ?)
|
||||
ON CONFLICT(userId, platform) DO UPDATE SET cookie = excluded.cookie, updatedAt = datetime('now')
|
||||
`);
|
||||
const deleteUserMusicCookieStmt = db.prepare(
|
||||
`DELETE FROM user_music_cookies WHERE userId = ? AND platform = ?`,
|
||||
);
|
||||
|
||||
// A corrupt/hand-edited songs blob must never throw into a route or the
|
||||
// restore path — degrade to an empty list instead.
|
||||
const parseSongs = (raw: string): StoredSong[] => {
|
||||
@@ -634,6 +660,19 @@ export function createDatabase(dbPath: string): BotDatabase {
|
||||
return row !== undefined;
|
||||
},
|
||||
|
||||
getUserMusicCookie(userId, platform) {
|
||||
const row = selectUserMusicCookie.get(userId, platform) as { cookie: string } | undefined;
|
||||
return row?.cookie ?? null;
|
||||
},
|
||||
|
||||
setUserMusicCookie(userId, platform, cookie) {
|
||||
upsertUserMusicCookie.run(userId, platform, cookie);
|
||||
},
|
||||
|
||||
deleteUserMusicCookie(userId, platform) {
|
||||
return deleteUserMusicCookieStmt.run(userId, platform).changes > 0;
|
||||
},
|
||||
|
||||
saveQueue(ownerId, name, songs) {
|
||||
if (songs.length > MAX_QUEUE_SONGS) {
|
||||
throw new Error(`保存失败:歌曲数量超过上限 ${MAX_QUEUE_SONGS}`);
|
||||
|
||||
Reference in new issue
Block a user