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,155 @@
|
||||
"""酷我榜单(对应 sdk/kw/leaderboard.ts)。
|
||||
|
||||
`get_boards` 直接返回硬编码榜单表(不发请求),id 形如 `kw__93`(路由层会剥掉前缀
|
||||
再把裸 bangid 传回 `get_list`)。详情走 `wbd.kuwo.cn/api/bd/bang/bang_info`:
|
||||
请求体整体 AES-128-ECB 加密 + 签名,响应同样是加密体,`util.wbd_*` 负责双向转换。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from app.sdk.common.request import http_fetch
|
||||
from app.sdk.common.utils import decode_name, format_play_time
|
||||
from app.sdk.platforms.kw.util import format_singer, wbd_build_param, wbd_decode_data
|
||||
|
||||
LIST_DETAIL_LIMIT = 100
|
||||
|
||||
BOARD_LIST: list[dict[str, str]] = [
|
||||
{"id": "kw__93", "name": "飙升榜", "bangid": "93"},
|
||||
{"id": "kw__17", "name": "新歌榜", "bangid": "17"},
|
||||
{"id": "kw__16", "name": "热歌榜", "bangid": "16"},
|
||||
{"id": "kw__158", "name": "抖音热歌榜", "bangid": "158"},
|
||||
{"id": "kw__292", "name": "铃声榜", "bangid": "292"},
|
||||
{"id": "kw__284", "name": "热评榜", "bangid": "284"},
|
||||
{"id": "kw__290", "name": "ACG新歌榜", "bangid": "290"},
|
||||
{"id": "kw__286", "name": "台湾KKBOX榜", "bangid": "286"},
|
||||
{"id": "kw__279", "name": "冬日暖心榜", "bangid": "279"},
|
||||
{"id": "kw__281", "name": "巴士随身听榜", "bangid": "281"},
|
||||
{"id": "kw__255", "name": "KTV点唱榜", "bangid": "255"},
|
||||
{"id": "kw__280", "name": "家务进行曲榜", "bangid": "280"},
|
||||
{"id": "kw__282", "name": "熬夜修仙榜", "bangid": "282"},
|
||||
{"id": "kw__283", "name": "枕边轻音乐榜", "bangid": "283"},
|
||||
{"id": "kw__278", "name": "古风音乐榜", "bangid": "278"},
|
||||
{"id": "kw__264", "name": "Vlog音乐榜", "bangid": "264"},
|
||||
{"id": "kw__242", "name": "电音榜", "bangid": "242"},
|
||||
{"id": "kw__187", "name": "流行趋势榜", "bangid": "187"},
|
||||
{"id": "kw__204", "name": "现场音乐榜", "bangid": "204"},
|
||||
{"id": "kw__186", "name": "ACG神曲榜", "bangid": "186"},
|
||||
{"id": "kw__185", "name": "最强翻唱榜", "bangid": "185"},
|
||||
{"id": "kw__26", "name": "经典怀旧榜", "bangid": "26"},
|
||||
{"id": "kw__104", "name": "华语榜", "bangid": "104"},
|
||||
{"id": "kw__182", "name": "粤语榜", "bangid": "182"},
|
||||
{"id": "kw__22", "name": "欧美榜", "bangid": "22"},
|
||||
{"id": "kw__184", "name": "韩语榜", "bangid": "184"},
|
||||
{"id": "kw__183", "name": "日语榜", "bangid": "183"},
|
||||
{"id": "kw__145", "name": "会员畅听榜", "bangid": "145"},
|
||||
{"id": "kw__153", "name": "网红新歌榜", "bangid": "153"},
|
||||
{"id": "kw__64", "name": "影视金曲榜", "bangid": "64"},
|
||||
{"id": "kw__176", "name": "DJ嗨歌榜", "bangid": "176"},
|
||||
{"id": "kw__106", "name": "真声音", "bangid": "106"},
|
||||
{"id": "kw__12", "name": "Billboard榜", "bangid": "12"},
|
||||
{"id": "kw__49", "name": "iTunes音乐榜", "bangid": "49"},
|
||||
{"id": "kw__180", "name": "beatport电音榜", "bangid": "180"},
|
||||
{"id": "kw__13", "name": "英国UK榜", "bangid": "13"},
|
||||
{"id": "kw__164", "name": "百大DJ榜", "bangid": "164"},
|
||||
{"id": "kw__246", "name": "YouTube音乐排行榜", "bangid": "246"},
|
||||
{"id": "kw__265", "name": "韩国Genie榜", "bangid": "265"},
|
||||
{"id": "kw__14", "name": "韩国M-net榜", "bangid": "14"},
|
||||
{"id": "kw__8", "name": "香港电台榜", "bangid": "8"},
|
||||
{"id": "kw__15", "name": "日本公信榜", "bangid": "15"},
|
||||
{"id": "kw__151", "name": "腾讯音乐人原创榜", "bangid": "151"},
|
||||
]
|
||||
|
||||
BANG_API = "https://wbd.kuwo.cn/api/bd/bang/bang_info"
|
||||
|
||||
_BITRATE_QUALITY = {"4000": "flac24bit", "2000": "flac", "320": "320k", "128": "128k"}
|
||||
# 榜单列表按音质等级升序排列(128k → 320k → flac → flac24bit)
|
||||
_QUALITY_RANK = {"flac24bit": 4, "flac": 3, "320k": 2, "128k": 1}
|
||||
|
||||
_MININFO_RE = re.compile(r"level:(\w+),bitrate:(\d+),format:(\w+),size:([\w.]+)")
|
||||
|
||||
|
||||
def _filter_data(raw_list: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
result: list[dict[str, Any]] = []
|
||||
for item in raw_list:
|
||||
types: list[dict[str, str]] = []
|
||||
_types: dict[str, dict[str, str]] = {}
|
||||
seen: set[str] = set()
|
||||
for entry in str(item.get("n_minfo") or "").split(";"):
|
||||
match = _MININFO_RE.search(entry)
|
||||
if match is None:
|
||||
continue
|
||||
bitrate = match.group(2)
|
||||
if bitrate in seen:
|
||||
continue
|
||||
seen.add(bitrate)
|
||||
quality = _BITRATE_QUALITY.get(bitrate)
|
||||
if quality is None:
|
||||
continue
|
||||
# 原实现 types 留原样、_types 转大写,两者不完全一致
|
||||
types.append({"type": quality, "size": match.group(4)})
|
||||
_types[quality] = {"size": match.group(4).upper()}
|
||||
types.sort(key=lambda entry: _QUALITY_RANK[entry["type"]])
|
||||
|
||||
result.append(
|
||||
{
|
||||
"singer": format_singer(decode_name(item.get("artist"))),
|
||||
"name": decode_name(item.get("name")),
|
||||
"albumName": decode_name(item.get("album")),
|
||||
"albumId": item.get("albumId"),
|
||||
"songmid": item.get("id"),
|
||||
"source": "kw",
|
||||
"interval": format_play_time(_to_int(item.get("duration"))),
|
||||
"img": item.get("pic"),
|
||||
"lrc": None,
|
||||
"otherSource": None,
|
||||
"types": types,
|
||||
"_types": _types,
|
||||
"typeUrl": {},
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def _to_int(value: Any) -> int:
|
||||
try:
|
||||
return int(float(value))
|
||||
except (TypeError, ValueError):
|
||||
return 0
|
||||
|
||||
|
||||
async def get_boards() -> dict[str, Any]:
|
||||
return {"list": BOARD_LIST, "source": "kw"}
|
||||
|
||||
|
||||
async def get_list(board_id: str, page: int = 1, retry_num: int = 0) -> dict[str, Any]:
|
||||
retry_num += 1
|
||||
if retry_num > 3:
|
||||
raise RuntimeError("try max num")
|
||||
|
||||
body = {
|
||||
"uid": "",
|
||||
"devId": "",
|
||||
"sFrom": "kuwo_sdk",
|
||||
"user_type": "AP",
|
||||
"carSource": "kwplayercar_ar_6.0.1.0_apk_keluze.apk",
|
||||
"id": board_id,
|
||||
"pn": page - 1,
|
||||
"rn": LIST_DETAIL_LIMIT,
|
||||
}
|
||||
resp = await http_fetch(f"{BANG_API}?{wbd_build_param(body)}")
|
||||
# 原实现直接调 decodeData,解密失败即 reject、不重试,这里保持一致
|
||||
raw_data = wbd_decode_data(resp.body) if isinstance(resp.body, str) else {}
|
||||
data = raw_data.get("data") or {}
|
||||
if resp.status_code != 200 or raw_data.get("code") != 200 or not data.get("musiclist"):
|
||||
return await get_list(board_id, page, retry_num)
|
||||
|
||||
return {
|
||||
"total": _to_int(data.get("total")),
|
||||
"list": _filter_data(data["musiclist"]),
|
||||
"limit": LIST_DETAIL_LIMIT,
|
||||
"page": page,
|
||||
"source": "kw",
|
||||
}
|
||||
Reference in new issue
Block a user