feat(sdk): 新增咪咕/千千/JOOX/哔哩哔哩/5sing/Apple Music 六平台

平台注册表由 9 个扩到 15 个,同时补齐 sdk/types.ts 的类型与
OpenAPI 文档里的能力位声明。

- migu:命中 Z3D/I3D 加密候选时不返回直链,改由服务端解密代理出流,
  SDK 侧只负责登记票据,避免客户端拿到无法播放的密文
- apple:只提供 preview 试听片段(完整曲为 DRM),标 noAggregate
  排除在跨平台精确匹配之外,防止用它顶掉其它平台的可播结果
- 其余平台按既有 PlatformModule 约定实现搜索/专辑/歌单/歌词

为什么合并成一次提交:六平台共用同一份注册表与能力位声明,
拆开会产生互相引用、单独无法通过类型检查的中间态。
This commit is contained in:
snowy-yang committed 2026-10-03 00:26:32 +08:00
1 parent 47ce651f46
commit 33b96f92c4
54 files changed
+6161 -5

No files matched your search

+74
View File
@@ -0,0 +1,74 @@
// Apple Music 搜索(上游 apple.go 的 Search / SearchAlbum / SearchPlaylist)
// 通道:amp-api /v1/catalog/<storefront>/search(Bearer developer token,见 util.ts)
// 单次请求上限 25 条,page/limit 用 offset 翻页;响应没有 total,
// 因此 total = 已翻过的条数 + 本页条数(下一页是否存在由 next 决定,allPage=页码+1)。
import {
appleAmpGet,
appleSongFromCatalog,
appleAlbumToPlaylist,
applePlaylistToPlaylist,
getAppleConfig,
} from './util.ts'
/** amp-api search 单页硬上限 */
export const SEARCH_PAGE_MAX = 25
const catalogSearch = async(keyword: string, types: string, page: number, limit: number) => {
const word = String(keyword ?? '').trim()
const p = Math.max(1, Number(page) || 1)
const l = Math.min(Math.max(1, Number(limit) || 20), 100)
if (!word) return { body: null, page: p, limit: l }
const cfg = getAppleConfig()
const body: any = await appleAmpGet(`/v1/catalog/${cfg.storefront}/search`, {
term: word,
types,
limit: Math.min(l, SEARCH_PAGE_MAX),
offset: (p - 1) * l,
})
return { body, page: p, limit: l }
}
/** 上游 Search:搜索歌曲 */
export const search = async(keyword: string, page = 1, limit = 20) => {
const { body, page: p, limit: l } = await catalogSearch(keyword, 'songs', page, limit)
const bucket = body?.results?.songs ?? {}
const list = (bucket.data ?? []).map((item: any) => appleSongFromCatalog(item))
const hasNext = Boolean(bucket.next)
return {
list,
allPage: hasNext ? p + 1 : p,
total: (p - 1) * l + list.length,
limit: l,
source: 'apple' as const,
}
}
/** 上游 SearchAlbum:搜索专辑(返回歌单摘要形状) */
export const searchAlbum = async(keyword: string, page = 1, limit = 20) => {
const { body, page: p, limit: l } = await catalogSearch(keyword, 'albums', page, limit)
const items = body?.results?.albums?.data ?? []
const list = items.map((item: any) => appleAlbumToPlaylist(item))
return {
list,
total: (p - 1) * l + list.length,
page: p,
limit: l,
source: 'apple' as const,
}
}
/** 上游 SearchPlaylist:搜索歌单 */
export const searchPlaylist = async(keyword: string, page = 1, limit = 20) => {
const { body, page: p, limit: l } = await catalogSearch(keyword, 'playlists', page, limit)
const items = body?.results?.playlists?.data ?? []
const list = items.map((item: any) => applePlaylistToPlaylist(item))
return {
list,
total: (p - 1) * l + list.length,
page: p,
limit: l,
source: 'apple' as const,
}
}
export default { search, searchAlbum, searchPlaylist }