- 新增 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 路由验证通过
118 lines
6.1 KiB
Python
118 lines
6.1 KiB
Python
"""网易云榜单(移植自 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}"
|