Files
MusicServer/app/sdk/platforms/apple/music_search.py
T
snowy-yang dfc06bf4de 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 路由验证通过
2026-10-23 21:55:45 +08:00

91 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Apple Music 搜索(对应 sdk/apple/musicSearch.ts,上游 apple.go 的 Search / SearchAlbum / SearchPlaylist)。
通道:amp-api /v1/catalog/<storefront>/search(Bearer developer token,见 util.py)
单次请求上限 25 条,page/limit 用 offset 翻页;响应没有 total,
因此 total = 已翻过的条数 + 本页条数(下一页是否存在由 next 决定,allPage=页码+1)。
"""
from __future__ import annotations
from typing import Any
from app.sdk.common.utils import as_dict, as_list
from app.sdk.platforms.apple.util import (
apple_album_to_playlist,
apple_amp_get,
apple_playlist_to_playlist,
apple_song_from_catalog,
get_apple_config,
)
#: amp-api search 单页硬上限
SEARCH_PAGE_MAX = 25
async def _catalog_search(keyword: str, types: str, page: int, limit: int) -> dict[str, Any]:
word = str(keyword or "").strip()
p = max(1, int(page or 1))
size = min(max(1, int(limit or 20)), 100)
if not word:
return {"body": None, "page": p, "limit": size}
cfg = get_apple_config()
body = await apple_amp_get(
f"/v1/catalog/{cfg['storefront']}/search",
{
"term": word,
"types": types,
"limit": min(size, SEARCH_PAGE_MAX),
"offset": (p - 1) * size,
},
)
return {"body": body, "page": p, "limit": size}
async def search(keyword: str, page: int = 1, limit: int = 20, retry_num: int = 0) -> dict[str, Any]:
del retry_num # 契约参数,amp-api 无重试语义
result = await _catalog_search(keyword, "songs", page, limit)
p, size = result["page"], result["limit"]
results = as_dict(as_dict(result["body"]).get("results"))
bucket = as_dict(results.get("songs"))
songs = [apple_song_from_catalog(item) for item in as_list(bucket.get("data"))]
has_next = bool(bucket.get("next"))
return {
"list": songs,
"allPage": p + 1 if has_next else p,
"total": (p - 1) * size + len(songs),
"limit": size,
"source": "apple",
}
async def search_album(keyword: str, page: int = 1, limit: int = 20) -> dict[str, Any]:
"""上游 SearchAlbum:搜索专辑(返回歌单摘要形状)。"""
result = await _catalog_search(keyword, "albums", page, limit)
p, size = result["page"], result["limit"]
results = as_dict(as_dict(result["body"]).get("results"))
items = as_list(as_dict(results.get("albums")).get("data"))
songs = [apple_album_to_playlist(item) for item in items]
return {
"list": songs,
"total": (p - 1) * size + len(songs),
"page": p,
"limit": size,
"source": "apple",
}
async def search_playlist(keyword: str, page: int = 1, limit: int = 20) -> dict[str, Any]:
"""上游 SearchPlaylist:搜索歌单。"""
result = await _catalog_search(keyword, "playlists", page, limit)
p, size = result["page"], result["limit"]
results = as_dict(as_dict(result["body"]).get("results"))
items = as_list(as_dict(results.get("playlists")).get("data"))
playlists = [apple_playlist_to_playlist(item) for item in items]
return {
"list": playlists,
"total": (p - 1) * size + len(playlists),
"page": p,
"limit": size,
"source": "apple",
}