From cbb969e0ffca33790e986a3da5d691bfabbca92f Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 12 Aug 2026 13:13:54 +0800 Subject: [PATCH] fix(music): default Douban explore to categories --- app/api/endpoints/music.py | 6 +++--- docs/mcp-api.md | 20 +++++++++----------- skills/moviepilot-api/SKILL.md | 22 ++++++++++------------ tests/test_music_endpoint.py | 6 +++--- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/app/api/endpoints/music.py b/app/api/endpoints/music.py index 0b6ecadb5..105f01db8 100644 --- a/app/api/endpoints/music.py +++ b/app/api/endpoints/music.py @@ -29,7 +29,7 @@ MusicExploreSourceParam = Annotated[ str, Query(pattern="^(musicbrainz|doubanmusic)$"), ] -MusicModeParam = Annotated[str, Query(pattern="^(chart|fresh|tag)$")] +MusicModeParam = Annotated[str, Query(pattern="^(chart|fresh)$")] MusicEntityParam = Annotated[str, Query(pattern="^(recording|album)$")] MusicRangeParam = Annotated[str, Query(pattern=f"^({'|'.join(LISTENBRAINZ_CHART_RANGES)})$")] MusicSortParam = Annotated[str, Query(pattern="^listen_count\\.(desc|asc)$")] @@ -153,7 +153,7 @@ async def explore_music( douban_sort: DoubanMusicSortParam = "U", _: schemas.TokenPayload = Depends(verify_token), ) -> list[schemas.MusicInfo]: - """按音乐来源返回可订阅的榜单或新发行候选。""" + """MusicBrainz 返回榜单或新发行,豆瓣音乐固定按官方标签分类浏览。""" chain = MusicChain() if media_source != "musicbrainz": results = await chain.async_discover( @@ -161,7 +161,7 @@ async def explore_music( page=page, count=count, entity=entity, - mode=mode, + mode="tag", tags=tags, sort=douban_sort, ) diff --git a/docs/mcp-api.md b/docs/mcp-api.md index 1c4bbe136..424d70ea5 100644 --- a/docs/mcp-api.md +++ b/docs/mcp-api.md @@ -194,18 +194,16 @@ AniList 榜单、探索、详情、人物和推荐接口优先通过 `anilist-ch | 方法 | 路径 | 说明 | | :--- | :--- | :--- | -| GET | `/api/v1/media/search` | 当 `type=music` 或 `source=musicbrainz` 时按歌曲、专辑或歌手关键词搜索音乐元数据,参数:`title`、`type`、`count` | -| POST | `/api/v1/music/recognize` | 按 `source` + `media_id` 识别音乐详情,请求体:`MusicRecognizeRequest` | -| GET | `/api/v1/music/explore` | 按来源浏览音乐;`source=musicbrainz` 支持热门榜单与新发行,`source=theaudiodb` 支持国家/地区趋势专辑或单曲,`source=doubanmusic` 支持豆瓣音乐推荐。参数:`source`、`mode=chart|fresh`、`entity=recording|album`、`country`、`range_name`、`sort_by`、`sort`、`days`、`past`、`future`、`min_listen_count`、`with_cover`、`page`、`count` | -| GET | `/api/v1/music/album/{album_id}` | 按来源专辑 ID 查询专辑详情、完整曲目和发行版本,参数:`source` | -| GET | `/api/v1/music/album/{album_id}/related` | 按来源查询关联专辑,参数:`source`、`count` | -| GET | `/api/v1/music/artist/{artist_id}` | 查询艺术家详情;艺术家为只读浏览实体,参数:`source` | -| GET | `/api/v1/music/artist/{artist_id}/albums` | 分页查询艺术家的专辑、EP 和单曲,参数:`source`、`page`、`count`、`album_type` | -| GET | `/api/v1/music/artist/{artist_id}/related` | 查询关联艺术家,参数:`source`、`count` | +| GET | `/api/v1/media/search` | 当 `type=music` 或指定音乐 `media_source` 时按歌曲、专辑或歌手关键词搜索音乐元数据,参数:`title`、`type`、`count`、`media_source` | +| POST | `/api/v1/music/recognize` | 按 `media_source` + `media_id` 识别音乐详情,请求体:`MusicRecognizeRequest` | +| GET | `/api/v1/music/explore` | 按来源浏览音乐;`media_source=musicbrainz` 支持 `mode=chart|fresh` 榜单与新发行,`media_source=doubanmusic` 固定按官方标签分类浏览,使用 `tags` 和 `douban_sort=U|S|R|O` 筛选。其它参数:`entity=recording|album`、`range_name`、`sort_by`、`sort`、`days`、`past`、`future`、`min_listen_count`、`with_cover`、`page`、`count` | +| GET | `/api/v1/music/album/{album_id}` | 按来源专辑 ID 查询专辑详情、完整曲目和发行版本,参数:`media_source` | +| GET | `/api/v1/music/album/{album_id}/related` | 按来源查询关联专辑,参数:`media_source`、`count` | +| GET | `/api/v1/music/artist/{artist_id}` | 查询艺术家详情;艺术家为只读浏览实体,参数:`media_source` | +| GET | `/api/v1/music/artist/{artist_id}/albums` | 分页查询艺术家的专辑、EP 和单曲,参数:`media_source`、`page`、`count`、`album_type` | +| GET | `/api/v1/music/artist/{artist_id}/related` | 查询关联艺术家,参数:`media_source`、`count` | | GET | `/api/v1/recommend/music_weekly` | 浏览本周热门音乐,参数:`page`、`count` | -| GET | `/api/v1/recommend/music_theaudiodb_albums` | 浏览 TheAudioDB 热门专辑,参数:`country`、`page`、`count` | -| GET | `/api/v1/recommend/music_theaudiodb_tracks` | 浏览 TheAudioDB 热门单曲,参数:`country`、`page`、`count` | -| GET | `/api/v1/recommend/music_douban` | 浏览豆瓣音乐推荐,参数:`page`、`count` | +| GET | `/api/v1/recommend/music_douban` | 浏览豆瓣音乐新碟榜,参数:`page`、`count` | 专辑下载与订阅按“整包”处理:下载层会读取种子文件清单并以专辑 `total_tracks` 校验独立音频文件数量;未确认完整覆盖时不会把专辑订阅销订,也不会把部分曲目报告为完整专辑已入库。音乐刮削遵循 `music` 的标签、封面和歌词策略,歌词通过带有界 TTL/LRU 缓存的 LRCLIB 模块保存为同名 `.lrc` 或 `.txt` 旁挂文件。 diff --git a/skills/moviepilot-api/SKILL.md b/skills/moviepilot-api/SKILL.md index b5fc00e59..1579abebd 100644 --- a/skills/moviepilot-api/SKILL.md +++ b/skills/moviepilot-api/SKILL.md @@ -187,18 +187,18 @@ music on configured music-capable media servers; it does not manage playlists. | Method | Path | Description | |--------|------|-------------| -| GET | `/api/v1/media/search` | Search tracks, albums, or artists with `type=music` or `source=musicbrainz`. Params: `title`, `type`, `count` | -| POST | `/api/v1/music/recognize` | Resolve music metadata. Body: `source`, `media_id` | -| GET | `/api/v1/music/explore` | Explore music by `source`: MusicBrainz charts/fresh releases, TheAudioDB country trends, or Douban music recommendations. Params: `source`, `mode`, `entity`, `country`, `range_name`, `sort_by`, `sort`, `days`, `past`, `future`, `min_listen_count`, `with_cover`, `page`, `count` | -| GET | `/api/v1/music/album/{album_id}` | Album detail with tracks and releases. Params: `source` | -| GET | `/api/v1/music/album/{album_id}/related` | Related albums for the selected source. Params: `source`, `count` | -| GET | `/api/v1/music/artist/{artist_id}` | Browse artist detail. Params: `source` | -| GET | `/api/v1/music/artist/{artist_id}/albums` | Browse artist albums/EPs/singles. Params: `source`, `page`, `count`, `album_type` | -| GET | `/api/v1/music/artist/{artist_id}/related` | Browse related artists. Params: `source`, `count` | +| GET | `/api/v1/media/search` | Search tracks, albums, or artists with `type=music` or a music `media_source`. Params: `title`, `type`, `count`, `media_source` | +| POST | `/api/v1/music/recognize` | Resolve music metadata. Body: `media_source`, `media_id` | +| GET | `/api/v1/music/explore` | Explore by `media_source`: MusicBrainz supports `mode=chart|fresh`; Douban Music always uses official tag categories with `tags` and `douban_sort=U|S|R|O`. Other params: `entity`, `range_name`, `sort_by`, `sort`, `days`, `past`, `future`, `min_listen_count`, `with_cover`, `page`, `count` | +| GET | `/api/v1/music/album/{album_id}` | Album detail with tracks and releases. Params: `media_source` | +| GET | `/api/v1/music/album/{album_id}/related` | Related albums for the selected source. Params: `media_source`, `count` | +| GET | `/api/v1/music/artist/{artist_id}` | Browse artist detail. Params: `media_source` | +| GET | `/api/v1/music/artist/{artist_id}/albums` | Browse artist albums/EPs/singles. Params: `media_source`, `page`, `count`, `album_type` | +| GET | `/api/v1/music/artist/{artist_id}/related` | Browse related artists. Params: `media_source`, `count` | Music acquisition rules: -- Reuse `source`, `media_id`, and `music_type` from search/detail results. Never substitute a same-name entity. +- Reuse `media_source`, `media_id`, and `music_type` from search/detail results. Never substitute a same-name entity. - Subscribe/download one recording as one track. Subscribe/download one album as a complete multi-track pack. - Album torrent validation compares supported audio files with `total_tracks`; incomplete resources do not complete the subscription. - Artist IDs are never subscription, torrent, download, transfer, or library-existence targets. @@ -474,9 +474,7 @@ Streaming search sends `{"type":"heartbeat"}` every 15 seconds without business | GET | `/api/v1/recommend/source` | Recommendation data sources | | GET | `/api/v1/recommend/bangumi_calendar` | Bangumi daily schedule. Params: `page`, `count` | | GET | `/api/v1/recommend/music_weekly` | ListenBrainz weekly site-wide music chart. Params: `page`, `count` | -| GET | `/api/v1/recommend/music_theaudiodb_albums` | TheAudioDB trending albums. Params: `country`, `page`, `count` | -| GET | `/api/v1/recommend/music_theaudiodb_tracks` | TheAudioDB trending tracks. Params: `country`, `page`, `count` | -| GET | `/api/v1/recommend/music_douban` | Douban music recommendations. Params: `page`, `count` | +| GET | `/api/v1/recommend/music_douban` | Douban new album chart. Params: `page`, `count` | | GET | `/api/v1/recommend/douban_showing` | Douban now showing. Params: `page`, `count` | | GET | `/api/v1/recommend/douban_movies` | Douban movies. Params: `sort`, `tags`, `page`, `count` | | GET | `/api/v1/recommend/douban_tvs` | Douban TV. Params: `sort`, `tags`, `page`, `count` | diff --git a/tests/test_music_endpoint.py b/tests/test_music_endpoint.py index 8c1e5af86..740558784 100644 --- a/tests/test_music_endpoint.py +++ b/tests/test_music_endpoint.py @@ -258,8 +258,8 @@ def test_explore_music_supports_official_fresh_release_mode(): ) -def test_explore_music_forwards_douban_music_source(): - """豆瓣音乐探索应走可扩展发现链而不是 ListenBrainz。""" +def test_explore_music_forces_douban_music_to_tag_browsing(): + """豆瓣音乐探索即使收到榜单模式也应固定分类浏览,不与推荐页重复。""" chain = Mock() chain.async_discover = AsyncMock( return_value=[ @@ -277,7 +277,7 @@ def test_explore_music_forwards_douban_music_source(): explore_music( media_source="doubanmusic", entity="album", - mode="tag", + mode="chart", tags="流行,华语", douban_sort="S", page=2,