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 路由验证通过
This commit is contained in:
1 parent
800871ef6e
commit
dfc06bf4de
182 files changed
+26620
No files matched your search
@@ -0,0 +1,125 @@
|
||||
"""网易云搜索(移植自 sdk/wy/musicSearch.ts)。
|
||||
|
||||
走 eapi 通道 /api/search/song/list/page(cloudsearch pc 已被注释掉,保留原样)。
|
||||
响应结构是 resources[].baseInfo.simpleSongData,与 song/detail 的扁平结构不同,
|
||||
因此音质解析单独一份(privilege 内嵌在 simpleSongData 里)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from app.sdk.common.utils import format_play_time, size_formate
|
||||
from app.sdk.platforms.wy.utils import eapi_request
|
||||
|
||||
DEFAULT_LIMIT = 30
|
||||
|
||||
# 模块级状态与原 TS 一致(路由不读这些字段,但 allPage/total 由它们推导)
|
||||
_state: dict[str, Any] = {"total": 0, "page": 0, "all_page": 1, "limit": DEFAULT_LIMIT}
|
||||
|
||||
|
||||
def get_singer(singers: Any) -> str:
|
||||
return "、".join(str(singer.get("name")) for singer in singers or [] if isinstance(singer, dict))
|
||||
|
||||
|
||||
def _size_of(block: Any) -> str | None:
|
||||
if isinstance(block, dict) and block.get("size"):
|
||||
return size_formate(block["size"])
|
||||
return None
|
||||
|
||||
|
||||
def handle_result(raw_list: Any) -> list[dict[str, Any]]:
|
||||
if not raw_list:
|
||||
return []
|
||||
rows: list[dict[str, Any]] = []
|
||||
for raw in raw_list:
|
||||
item = ((raw or {}).get("baseInfo") or {}).get("simpleSongData") or {}
|
||||
privilege = item.get("privilege") or {}
|
||||
types: list[dict[str, Any]] = []
|
||||
_types: dict[str, dict[str, Any]] = {}
|
||||
|
||||
if privilege.get("maxBrLevel") == "hires":
|
||||
size = _size_of(item.get("hr"))
|
||||
types.append({"type": "flac24bit", "size": size})
|
||||
_types["flac24bit"] = {"size": size}
|
||||
|
||||
maxbr = privilege.get("maxbr")
|
||||
# 无 break 的 switch 直落:高码率同时声明向下兼容档位
|
||||
if maxbr in (999000, 320000, 192000, 128000):
|
||||
if maxbr == 999000:
|
||||
size = _size_of(item.get("sq"))
|
||||
types.append({"type": "flac", "size": size})
|
||||
_types["flac"] = {"size": size}
|
||||
if maxbr in (999000, 320000):
|
||||
size = _size_of(item.get("h"))
|
||||
types.append({"type": "320k", "size": size})
|
||||
_types["320k"] = {"size": size}
|
||||
size = _size_of(item.get("l"))
|
||||
types.append({"type": "128k", "size": size})
|
||||
_types["128k"] = {"size": size}
|
||||
|
||||
types.reverse()
|
||||
|
||||
album = item.get("al") or {}
|
||||
rows.append(
|
||||
{
|
||||
"singer": get_singer(item.get("ar")),
|
||||
"name": item.get("name"),
|
||||
"albumName": album.get("name"),
|
||||
"albumId": album.get("id"),
|
||||
"source": "wy",
|
||||
"interval": format_play_time((item.get("dt") or 0) / 1000),
|
||||
"songmid": item.get("id"),
|
||||
"img": album.get("picUrl"),
|
||||
"lrc": None,
|
||||
"types": types,
|
||||
"_types": _types,
|
||||
"typeUrl": {},
|
||||
}
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
async def _music_search(keyword: str, page: int, limit: int) -> Any:
|
||||
resp = await eapi_request(
|
||||
"/api/search/song/list/page",
|
||||
{
|
||||
"keyword": keyword,
|
||||
"needCorrect": "1",
|
||||
"channel": "typing",
|
||||
"offset": limit * (page - 1),
|
||||
"scene": "normal",
|
||||
"total": page == 1,
|
||||
"limit": limit,
|
||||
},
|
||||
)
|
||||
return resp.body
|
||||
|
||||
|
||||
async def search(keyword: str, page: int = 1, limit: int = 20, retry_num: int = 0) -> dict[str, Any]:
|
||||
"""搜索单曲。失败重试上限 3 次(文案与原文一致:try max num)。"""
|
||||
retry_num += 1
|
||||
if retry_num > 3:
|
||||
raise RuntimeError("try max num")
|
||||
if not limit:
|
||||
limit = DEFAULT_LIMIT
|
||||
|
||||
result = await _music_search(keyword, page, limit)
|
||||
if not isinstance(result, dict) or result.get("code") != 200:
|
||||
return await search(keyword, page, limit, retry_num)
|
||||
|
||||
data = result.get("data") or {}
|
||||
rows = handle_result(data.get("resources") or [])
|
||||
|
||||
_state["total"] = data.get("totalCount") or 0
|
||||
_state["page"] = page
|
||||
_state["all_page"] = -(-int(_state["total"]) // DEFAULT_LIMIT) # ceil 除法
|
||||
_state["limit"] = DEFAULT_LIMIT
|
||||
|
||||
return {
|
||||
"list": rows,
|
||||
"allPage": _state["all_page"],
|
||||
"limit": DEFAULT_LIMIT,
|
||||
"total": _state["total"],
|
||||
"source": "wy",
|
||||
}
|
||||
Reference in new issue
Block a user