Files
MusicServer/app/sdk/platforms/wy/leaderboard.py
T
snowy-yang dfc06bf4de feat(python): 完成 Python 全量迁移——剩余平台、登录通道与公共层修复
- 新增 5sing/apple/xm/me/qt/lr 平台包与取流实现(含 xm-sign、MissEvan 签名、蜻蜓 HMAC、Apple developer token)
- 新增 wy/kg/tx/lr 登录:扫码、手机号、账密(kg 20028 风控指纹)、cookie 绑定、kg/tx 续期
- request 层:保留多值 Set-Cookie;JSON body 紧凑化(修复 kg 网关验签);直连不经系统代理(对齐 Node)
- 登录路由统一 Body(embed=True),修复扫码轮询 422;lifespan 接入登录续期调度
- ruff/format/pyright 全绿;14 平台真实联网与 HTTP 路由验证通过
2026-10-23 21:55:45 +08:00

118 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""网易云榜单(移植自 sdk/wy/leaderboard.ts)。
榜单列表是硬编码的 top_list(原实现的 get_boards 直接返回它,不再请求 weapi/toplist);
详情走 weapi/v3/playlist/detail 拿 trackIds,再用 song/detail 批量补全曲目。
id 必须是 `wy__<bangid>` 形态:路由 `_board_handler` 会剥掉 `wy__` 前缀得到裸 bangid,
前端拿到的 id 也只有带前缀时才和详情接口闭环。
"""
from __future__ import annotations
import asyncio
from typing import Any
from app.sdk.common.request import http_fetch
from app.sdk.platforms.wy import music_detail
from app.sdk.platforms.wy.utils import weapi_headers
from app.sdk.platforms.wy.utils.crypto import weapi
LIMIT = 100000
TOP_LIST: list[dict[str, str]] = [
{"id": "wy__19723756", "name": "飙升榜", "bangid": "19723756"},
{"id": "wy__3779629", "name": "新歌榜", "bangid": "3779629"},
{"id": "wy__2884035", "name": "原创榜", "bangid": "2884035"},
{"id": "wy__3778678", "name": "热歌榜", "bangid": "3778678"},
{"id": "wy__991319590", "name": "说唱榜", "bangid": "991319590"},
{"id": "wy__71384707", "name": "古典榜", "bangid": "71384707"},
{"id": "wy__1978921795", "name": "电音榜", "bangid": "1978921795"},
{"id": "wy__5453912201", "name": "黑胶VIP爱听榜", "bangid": "5453912201"},
{"id": "wy__71385702", "name": "ACG榜", "bangid": "71385702"},
{"id": "wy__745956260", "name": "韩语榜", "bangid": "745956260"},
{"id": "wy__10520166", "name": "国电榜", "bangid": "10520166"},
{"id": "wy__180106", "name": "UK排行榜周榜", "bangid": "180106"},
{"id": "wy__60198", "name": "美国Billboard榜", "bangid": "60198"},
{"id": "wy__3812895", "name": "Beatport全球电子舞曲榜", "bangid": "3812895"},
{"id": "wy__21845217", "name": "KTV唛榜", "bangid": "21845217"},
{"id": "wy__60131", "name": "日本Oricon榜", "bangid": "60131"},
{"id": "wy__2809513713", "name": "欧美热歌榜", "bangid": "2809513713"},
{"id": "wy__2809577409", "name": "欧美新歌榜", "bangid": "2809577409"},
{"id": "wy__27135204", "name": "法国 NRJ Vos Hits 周榜", "bangid": "27135204"},
{"id": "wy__3001835560", "name": "ACG动画榜", "bangid": "3001835560"},
{"id": "wy__3001795926", "name": "ACG游戏榜", "bangid": "3001795926"},
{"id": "wy__3001890046", "name": "ACG VOCALOID榜", "bangid": "3001890046"},
{"id": "wy__3112516681", "name": "中国新乡村音乐排行榜", "bangid": "3112516681"},
{"id": "wy__5059644681", "name": "日语榜", "bangid": "5059644681"},
{"id": "wy__5059633707", "name": "摇滚榜", "bangid": "5059633707"},
{"id": "wy__5059642708", "name": "国风榜", "bangid": "5059642708"},
{"id": "wy__5338990334", "name": "潜力爆款榜", "bangid": "5338990334"},
{"id": "wy__5059661515", "name": "民谣榜", "bangid": "5059661515"},
{"id": "wy__6688069460", "name": "听歌识曲榜", "bangid": "6688069460"},
{"id": "wy__6723173524", "name": "网络热歌榜", "bangid": "6723173524"},
{"id": "wy__6732051320", "name": "俄语榜", "bangid": "6732051320"},
{"id": "wy__6732014811", "name": "越南语榜", "bangid": "6732014811"},
{"id": "wy__6886768100", "name": "中文DJ榜", "bangid": "6886768100"},
{"id": "wy__6939992364", "name": "俄罗斯top hit流行音乐榜", "bangid": "6939992364"},
{"id": "wy__7095271308", "name": "泰语榜", "bangid": "7095271308"},
{"id": "wy__7356827205", "name": "BEAT排行榜", "bangid": "7356827205"},
{"id": "wy__7325478166", "name": "编辑推荐榜VOL.44 天才女子摇滚乐队boygenius剖白卑微心迹", "bangid": "7325478166"},
{"id": "wy__7603212484", "name": "LOOK直播歌曲榜", "bangid": "7603212484"},
{"id": "wy__7775163417", "name": "赏音榜", "bangid": "7775163417"},
{"id": "wy__7785123708", "name": "黑胶VIP新歌榜", "bangid": "7785123708"},
{"id": "wy__7785066739", "name": "黑胶VIP热歌榜", "bangid": "7785066739"},
{"id": "wy__7785091694", "name": "黑胶VIP爱搜榜", "bangid": "7785091694"},
]
def filter_boards_data(raw_list: Any) -> list[dict[str, str]]:
"""weapi/toplist 响应 → 榜单列表(原实现的 filterBoardsData;当前 get_boards 不走它)。"""
return [
{"id": f"wy__{board['id']}", "name": str(board.get("name")), "bangid": str(board["id"])}
for board in raw_list or []
if isinstance(board, dict) and board.get("id") is not None
]
async def get_boards() -> dict[str, Any]:
"""榜单列表。原实现直接返回硬编码 top_list,不请求上游。"""
return {"list": TOP_LIST, "source": "wy"}
async def get_list(bangid: str, page: int = 1, retry_num: int = 0) -> dict[str, Any]:
"""榜单详情。失败递归重试最多 6 次,超出抛 try max num(文案与原文一致)。"""
retry_num += 1
if retry_num > 6:
raise RuntimeError("try max num")
resp = await http_fetch(
"https://music.163.com/weapi/v3/playlist/detail",
method="post",
headers=weapi_headers(with_cookie=False),
form=weapi({"id": bangid, "n": LIMIT, "p": 1}),
)
body = resp.body if isinstance(resp.body, dict) else {}
if resp.status_code != 200 or body.get("code") != 200:
# 让出事件循环:上游持续 5xx 时递归重试不应霸占单线程事件循环
await asyncio.sleep(0)
return await get_list(bangid, page, retry_num)
playlist = body.get("playlist") or {}
ids = [track.get("id") for track in playlist.get("trackIds") or [] if isinstance(track, dict)]
try:
detail = await music_detail.get_list(ids)
except Exception as err:
print(f"[wy] 榜单歌曲详情获取失败将重试({retry_num + 1}): {err}")
if str(err) == "try max num":
raise
await asyncio.sleep(0)
return await get_list(bangid, page, retry_num)
rows = detail["list"]
return {"total": len(rows), "list": rows, "limit": LIMIT, "page": page, "source": "wy"}
def get_detail_page_url(bangid: str) -> str:
bare = bangid.replace("wy__", "") if isinstance(bangid, str) else bangid
return f"https://music.163.com/#/discover/toplist?id={bare}"