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,147 @@
|
||||
"""QQ音乐歌手(移植自 sdk/tx/singer.ts)。
|
||||
|
||||
上游歌手详情接口(musichallSinger.GetSingerDetail 等)已停用(104403),这里按原文的
|
||||
合成路径取信息:
|
||||
* 歌手名 —— 从 GetSingerSongList 第一条曲目的 singer 数组里按 mid 反查(找不到取首位)
|
||||
* 作品数 —— GetSingerSongList 的 totalNum
|
||||
* 专辑数 —— GetAlbumList 的 total
|
||||
* 头像/性别 —— 头像走固定 URL 规则(T001),性别接口已无可信来源,返回 null
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from app.sdk.platforms.tx.filter import album_img, music_info_item
|
||||
from app.sdk.platforms.tx.utils import create_musicu_fetch
|
||||
|
||||
SONG_LIST_MODULE = "musichall.song_list_server"
|
||||
ALBUM_LIST_MODULE = "music.musichallAlbum.AlbumListServer"
|
||||
|
||||
|
||||
def filter_song_list(raw: Any) -> list[dict[str, Any]]:
|
||||
"""GetSingerSongList 的 songList(每项包一层 {songInfo})→ SongInfo 列表。"""
|
||||
return [music_info_item((item or {}).get("songInfo")) for item in raw or []]
|
||||
|
||||
|
||||
def filter_album_list(raw: Any) -> list[dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"id": item.get("albumID"),
|
||||
"mid": item.get("albumMid"),
|
||||
"count": item.get("totalNum"),
|
||||
"info": {
|
||||
"name": item.get("albumName"),
|
||||
"author": item.get("singerName"),
|
||||
"img": album_img(item.get("albumMid")),
|
||||
"desc": None,
|
||||
},
|
||||
}
|
||||
for item in raw or []
|
||||
if isinstance(item, dict)
|
||||
]
|
||||
|
||||
|
||||
async def get_info(id: str) -> dict[str, Any]:
|
||||
"""歌手信息:歌曲数与专辑数并发取,任一失败整体失败(与原文 Promise.all 一致)。"""
|
||||
song_body, album_body = await asyncio.gather(
|
||||
create_musicu_fetch(
|
||||
{
|
||||
"req": {
|
||||
"module": SONG_LIST_MODULE,
|
||||
"method": "GetSingerSongList",
|
||||
"param": {"singerMid": id, "order": 1, "begin": 0, "num": 1},
|
||||
}
|
||||
}
|
||||
),
|
||||
create_musicu_fetch(
|
||||
{
|
||||
"req": {
|
||||
"module": ALBUM_LIST_MODULE,
|
||||
"method": "GetAlbumList",
|
||||
"param": {"singerMid": id, "order": 0, "begin": 0, "num": 1, "songNumTag": 0, "singerID": 0},
|
||||
}
|
||||
}
|
||||
),
|
||||
)
|
||||
song_req = song_body.get("req") or {}
|
||||
album_req = album_body.get("req") or {}
|
||||
if song_req.get("code") != 0 or album_req.get("code") != 0:
|
||||
raise RuntimeError("get singer info faild.")
|
||||
|
||||
song_data = song_req.get("data") or {}
|
||||
album_data = album_req.get("data") or {}
|
||||
# 详情接口停用:歌手名从歌曲条目反查
|
||||
first_song = (song_data.get("songList") or [{}])[0] or {}
|
||||
singers = (first_song.get("songInfo") or {}).get("singer") or []
|
||||
self_info = next((s for s in singers if isinstance(s, dict) and s.get("mid") == id), None)
|
||||
if self_info is None:
|
||||
self_info = singers[0] if singers and isinstance(singers[0], dict) else {}
|
||||
return {
|
||||
"source": "tx",
|
||||
"id": id,
|
||||
"info": {
|
||||
"name": self_info.get("name") or "",
|
||||
"desc": None,
|
||||
"avatar": f"https://y.gtimg.cn/music/photo_new/T001R500x500M000{id}.jpg",
|
||||
"gender": None,
|
||||
},
|
||||
"count": {
|
||||
"music": song_data.get("totalNum"),
|
||||
"album": album_data.get("total"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def get_song_list(id: str, page: int = 1, limit: int = 100) -> dict[str, Any]:
|
||||
body = await create_musicu_fetch(
|
||||
{
|
||||
"req": {
|
||||
"module": SONG_LIST_MODULE,
|
||||
"method": "GetSingerSongList",
|
||||
"param": {"singerMid": id, "order": 1, "begin": (page - 1) * limit, "num": limit},
|
||||
}
|
||||
}
|
||||
)
|
||||
req = body.get("req") or {}
|
||||
if req.get("code") != 0:
|
||||
raise RuntimeError("get singer song list faild.")
|
||||
data = req.get("data") or {}
|
||||
return {
|
||||
"source": "tx",
|
||||
"list": filter_song_list(data.get("songList")),
|
||||
"limit": limit,
|
||||
"page": page,
|
||||
"total": data.get("totalNum"),
|
||||
}
|
||||
|
||||
|
||||
async def get_album_list(id: str, page: int = 1, limit: int = 10) -> dict[str, Any]:
|
||||
body = await create_musicu_fetch(
|
||||
{
|
||||
"req": {
|
||||
"module": ALBUM_LIST_MODULE,
|
||||
"method": "GetAlbumList",
|
||||
"param": {
|
||||
"singerMid": id,
|
||||
"order": 0,
|
||||
"begin": (page - 1) * limit,
|
||||
"num": limit,
|
||||
"songNumTag": 0,
|
||||
"singerID": 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
req = body.get("req") or {}
|
||||
if req.get("code") != 0:
|
||||
raise RuntimeError("get singer album faild.")
|
||||
data = req.get("data") or {}
|
||||
return {
|
||||
"source": "tx",
|
||||
"list": filter_album_list(data.get("albumList")),
|
||||
"limit": limit,
|
||||
"page": page,
|
||||
"total": data.get("total"),
|
||||
}
|
||||
Reference in new issue
Block a user