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

+43
View File
@@ -0,0 +1,43 @@
"""5sing 取流(对应 sdk/apis/5sing.ts,上游 fivesing/download.go 的 GetDownloadURL)。
链路:GET http://mobileapi.5sing.kugou.com/song/getSongUrl?songid=<id>&songtype=<yc|fc|bz>
→ { code: 1000, data: { squrl/squrl_backup, hqurl/hqurl_backup, lqurl/lqurl_backup, ... } }
直链为 CDN 明文地址(wsaudiobssdlbig.kugou.com,https,无需 Referer/防盗链签名;实测 Range 206 可播)。
档位:上游固定 sq(最高)→ hq → lq,返回首个非空(含 _backup);只有显式请求 64k(或 lq)时
才倒序优先 lq,返回实际命中档位。songmid 约定 ``<songId>|<songType>``;
Parse 出来的歌曲带 url 时直接复用(上游 GetDownloadURL: if s.URL != "" return)。
"""
from __future__ import annotations
from typing import Any
from app.sdk.platforms.fivesing.music_info import fetch_audio_link
from app.sdk.platforms.fivesing.util import extract_song_id, is_foreign_source
#: 上游只有 sq/hq/lq 三档,实际命中档位以返回 type 为准(sq 常缺,多命中 hq)
SUPPORT_QUALITYS: list[str] = ["sq", "hq", "lq"]
async def get_download_url(song_info: dict[str, Any], quality: str | None = None) -> dict[str, str]:
"""上游 GetDownloadURL 的等价实现。"""
if is_foreign_source(song_info):
raise RuntimeError("source mismatch")
# 上游逻辑:Song.URL 已带直链(Parse 场景)直接复用
if song_info.get("url"):
return {"type": str(song_info.get("type") or quality or "sq"), "url": str(song_info["url"])}
ids = extract_song_id(
song_info.get("songmid") or song_info.get("id") or song_info.get("songid"), song_info.get("songType")
)
if not ids["songID"] or not ids["songType"]:
raise RuntimeError('invalid id structure: 5sing songmid 需为 "<songId>|<songType>"(songType 为 yc/fc/bz)')
prefer_low = quality in ("64k", "lq")
return await fetch_audio_link(ids["songID"], ids["songType"], prefer_low)
async def get_music_url(song_info: dict[str, Any], quality: str, retry_num: int = 0) -> dict[str, Any]:
"""统一取流:返回 ``{type: 'sq'|'hq'|'lq', url}``(type 为实际命中档位,可能低于请求档位)。"""
del retry_num # 契约参数,重试已在 fetch_5sing_json 内
hit = await get_download_url(song_info, quality)
return {"type": hit["type"], "url": hit["url"]}