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:
snowy-yang committed 2026-10-23 21:55:45 +08:00
1 parent 800871ef6e
commit dfc06bf4de
182 files changed
+26620

No files matched your search

+62
View File
@@ -0,0 +1,62 @@
"""酷狗封面(对应 sdk/kg/pic.ts)。
走 `media.store.kugou.com/v1/get_res_privilege`,从返回的 `info.image` 里
把 `{size}` 占位替换为上游给的 `imgsize[0]`。
"""
from __future__ import annotations
from typing import Any
from app.sdk.common.request import http_fetch
PIC_API = "http://media.store.kugou.com/v1/get_res_privilege"
HEADERS = {
"KG-RC": "1",
"KG-THash": "expand_search_manager.cpp:852736169:451",
"User-Agent": "KuGou2012-9020-ExpandSearchManager",
}
def _album_audio_id(song_info: dict[str, Any]) -> Any:
"""songmid 为 32 位时是旧形态(audioId_x),要取 `_` 前的段(修复历史存储变更)。"""
songmid = str(song_info.get("songmid") or "")
if len(songmid) == 32:
return str(song_info.get("audioId") or "").split("_")[0]
return song_info.get("songmid")
async def get_pic(song_info: dict[str, Any]) -> str:
body = {
"appid": 1001,
"area_code": "1",
"behavior": "play",
"clientver": "9020",
"need_hash_offset": 1,
"relate": 1,
"resource": [
{
"album_audio_id": _album_audio_id(song_info),
"album_id": song_info.get("albumId"),
"hash": song_info.get("hash"),
"id": 0,
"name": f"{song_info.get('singer')} - {song_info.get('name')}.mp3",
"type": "audio",
}
],
"token": "",
"userid": 2626431536,
"vip": 1,
}
resp = await http_fetch(PIC_API, method="POST", headers=HEADERS, body=body)
payload = resp.body if isinstance(resp.body, dict) else {}
if payload.get("error_code") != 0:
raise RuntimeError("图片获取失败")
data = payload.get("data") or [{}]
info = (data[0] or {}).get("info") or {}
image = str(info.get("image") or "")
img = image.replace("{size}", str(info["imgsize"][0])) if info.get("imgsize") else image
if not img:
raise RuntimeError("Pic get failed")
return img