diff --git a/src/music/bilibili.ts b/src/music/bilibili.ts index 34b8a73..c713a54 100644 --- a/src/music/bilibili.ts +++ b/src/music/bilibili.ts @@ -271,13 +271,92 @@ export class BiliBiliProvider implements MusicProvider { return this.cookie; } - // --- Not applicable for BiliBili (video platform) --- + // --- B站推荐内容 --- - async getPlaylistSongs(_playlistId: string): Promise { + /** 热门视频 (无需登录) */ + async getRecommendPlaylists(): Promise { + // B站没有"歌单"概念,返回空 return []; } - async getRecommendPlaylists(): Promise { + /** 音乐区排行榜 + 个性化推荐(如果已登录)作为"每日推荐" */ + async getDailyRecommendSongs(): Promise { + await this.ensureBuvidCookie(); + const songs: Song[] = []; + + // 1. 个性化推荐(带 cookie 效果更好) + try { + const res = await this.api.get("/x/web-interface/index/top/rcmd", { + params: { ps: 10, fresh_type: 3 }, + headers: this.cookieHeaders, + }); + const items = res.data?.data?.item ?? []; + for (const v of items) { + songs.push({ + id: String(v.bvid), + name: v.title ?? "", + artist: v.owner?.name ?? "", + album: "", + duration: v.duration ?? 0, + coverUrl: this.normalizeCover(v.pic ?? ""), + platform: "bilibili" as const, + }); + } + } catch { + // fallback to popular + } + + // 2. 如果推荐为空,用音乐区排行榜(tid=3) + if (songs.length === 0) { + try { + const res = await this.api.get("/x/web-interface/ranking/v2", { + params: { rid: 3, type: "all" }, + headers: this.cookieHeaders, + }); + const list = res.data?.data?.list ?? []; + for (const v of list.slice(0, 20)) { + songs.push({ + id: String(v.bvid), + name: v.title ?? "", + artist: v.owner?.name ?? "", + album: "", + duration: v.duration ?? 0, + coverUrl: this.normalizeCover(v.pic ?? ""), + platform: "bilibili" as const, + }); + } + } catch { + // ignore + } + } + + return songs; + } + + /** 热门视频列表 */ + async getPopularVideos(limit = 20): Promise { + try { + const res = await this.api.get("/x/web-interface/popular", { + params: { ps: limit, pn: 1 }, + headers: this.cookieHeaders, + }); + return (res.data?.data?.list ?? []).map((v: any) => ({ + id: String(v.bvid), + name: v.title ?? "", + artist: v.owner?.name ?? "", + album: "", + duration: v.duration ?? 0, + coverUrl: this.normalizeCover(v.pic ?? ""), + platform: "bilibili" as const, + })); + } catch { + return []; + } + } + + // --- 不适用于B站 --- + + async getPlaylistSongs(_playlistId: string): Promise { return []; } diff --git a/src/web/api/music.ts b/src/web/api/music.ts index 5797b7c..e1ef73b 100644 --- a/src/web/api/music.ts +++ b/src/web/api/music.ts @@ -197,6 +197,23 @@ export function createMusicRouter( } }); + // B站热门视频 + router.get("/bilibili/popular", async (req, res) => { + try { + const provider = bilibiliProvider as any; + if (provider.getPopularVideos) { + const limit = parseInt(req.query.limit as string) || 20; + const songs = await provider.getPopularVideos(limit); + res.json({ songs }); + } else { + res.json({ songs: [] }); + } + } catch (err) { + logger.error({ err }, "Get bilibili popular failed"); + res.status(500).json({ error: (err as Error).message }); + } + }); + // Get current quality router.get("/quality", (_req, res) => { res.json({ diff --git a/web/src/stores/player.ts b/web/src/stores/player.ts index d9c0e22..d9479f4 100644 --- a/web/src/stores/player.ts +++ b/web/src/stores/player.ts @@ -50,6 +50,7 @@ export const usePlayerStore = defineStore('player', { recommendPlaylists: [] as PlaylistItem[], dailySongs: [] as Song[], userPlaylists: [] as PlaylistItem[], + bilibiliPopular: [] as Song[], lastFetchTime: 0, }), @@ -270,10 +271,11 @@ export const usePlayerStore = defineStore('player', { return; } - const [playlistRes, dailyRes, userRes] = await Promise.allSettled([ + const [playlistRes, dailyRes, userRes, biliRes] = await Promise.allSettled([ axios.get('/api/music/recommend/playlists'), axios.get('/api/music/recommend/songs'), axios.get('/api/music/user/playlists'), + axios.get('/api/music/bilibili/popular?limit=12'), ]); if (playlistRes.status === 'fulfilled') { @@ -285,6 +287,9 @@ export const usePlayerStore = defineStore('player', { if (userRes.status === 'fulfilled') { this.userPlaylists = userRes.value.data.playlists; } + if (biliRes.status === 'fulfilled') { + this.bilibiliPopular = biliRes.value.data.songs; + } this.lastFetchTime = Date.now(); }, diff --git a/web/src/views/Home.vue b/web/src/views/Home.vue index 8a282c7..e5a5964 100644 --- a/web/src/views/Home.vue +++ b/web/src/views/Home.vue @@ -95,6 +95,26 @@ + + +
+

+ B + B站热门 +

+
+
+ +
{{ song.name }}
+
{{ song.artist }}
+
+
+
@@ -182,6 +202,22 @@ onMounted(() => { font-size: 22px; font-weight: 700; margin-bottom: 16px; + display: flex; + align-items: center; + gap: 8px; +} + +.bili-badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + background: #00a1d6; + color: white; + border-radius: 4px; + font-size: 14px; + font-weight: 800; } // Now Playing