平台注册表由 9 个扩到 15 个,同时补齐 sdk/types.ts 的类型与 OpenAPI 文档里的能力位声明。 - migu:命中 Z3D/I3D 加密候选时不返回直链,改由服务端解密代理出流, SDK 侧只负责登记票据,避免客户端拿到无法播放的密文 - apple:只提供 preview 试听片段(完整曲为 DRM),标 noAggregate 排除在跨平台精确匹配之外,防止用它顶掉其它平台的可播结果 - 其余平台按既有 PlatformModule 约定实现搜索/专辑/歌单/歌词 为什么合并成一次提交:六平台共用同一份注册表与能力位声明, 拆开会产生互相引用、单独无法通过类型检查的中间态。
78 lines
2.8 KiB
TypeScript
78 lines
2.8 KiB
TypeScript
// Apple Music 专辑(上游 apple.go 的 SearchAlbum / ParseAlbum / GetAlbumSongs)
|
||
// 详情:amp-api /v1/catalog/<sf>/albums/<id>?extend=extendedAssetUrls
|
||
// → data[0].relationships.tracks.data[](>300 首时分页,next 由 fetchAllTrackResources 跟进)
|
||
// 链接:extractAppleID(link, 'album'),支持裸数字 id 与 /us/album/<slug>/<id> 链接。
|
||
import {
|
||
appleAmpGet,
|
||
appleAlbumToPlaylist,
|
||
appleSongFromCatalog,
|
||
extractAppleID,
|
||
fetchAllTrackResources,
|
||
getAppleConfig,
|
||
} from './util.ts'
|
||
|
||
export { searchAlbum } from './musicSearch.ts'
|
||
|
||
import { searchAlbum } from './musicSearch.ts'
|
||
|
||
/** 专辑详情(page/limit 在完整曲目上切片,与其它平台 getAlbumDetail 契约一致) */
|
||
export const getAlbumDetail = async(rawId: string, page = 1, limit = 50) => {
|
||
const albumId = extractAppleID(String(rawId ?? ''), 'album')
|
||
if (!albumId) throw new Error(`invalid apple music album id/link: ${rawId}`)
|
||
|
||
const cfg = getAppleConfig()
|
||
const body: any = await appleAmpGet(`/v1/catalog/${cfg.storefront}/albums/${albumId}`, { extend: 'extendedAssetUrls' })
|
||
const album = (body?.data ?? [])[0]
|
||
if (!album) throw new Error(`apple album not found: ${albumId}`)
|
||
|
||
const tracks = await fetchAllTrackResources(album?.relationships?.tracks)
|
||
const songs = tracks.map((track: any) => appleSongFromCatalog(track, albumId))
|
||
|
||
const p = Math.max(1, Number(page) || 1)
|
||
const l = Math.max(1, Number(limit) || 50)
|
||
const summary = appleAlbumToPlaylist(album)
|
||
// 上游 fetchPlaylistDetail 的做法:接口未给 trackCount 时以实际曲目数补齐
|
||
if (!summary.trackCount || songs.length > summary.trackCount) summary.trackCount = songs.length
|
||
|
||
return {
|
||
source: 'apple' as const,
|
||
info: {
|
||
id: albumId,
|
||
name: summary.name,
|
||
img: summary.cover,
|
||
desc: summary.description,
|
||
author: summary.creator,
|
||
link: summary.link,
|
||
trackCount: summary.trackCount,
|
||
releaseDate: summary.extra?.release_date || '',
|
||
recordLabel: summary.extra?.record_label || '',
|
||
},
|
||
list: songs.slice((p - 1) * l, (p - 1) * l + l),
|
||
page: p,
|
||
limit: l,
|
||
total: songs.length,
|
||
}
|
||
}
|
||
|
||
/** 上游 GetAlbumSongs:只返回曲目数组 */
|
||
export const getAlbumSongs = async(id: string): Promise<any[]> => {
|
||
const { list } = await getAlbumDetail(id, 1, 100000)
|
||
return list
|
||
}
|
||
|
||
/** 上游 ParseAlbum:专辑链接 → 详情(曲目全量,不分页) */
|
||
export const parseAlbumLink = async(link: string) => {
|
||
const detail = await getAlbumDetail(link, 1, 100000)
|
||
return {
|
||
source: 'apple' as const,
|
||
id: detail.info.id,
|
||
info: detail.info,
|
||
list: detail.list,
|
||
page: 1,
|
||
limit: Math.max(detail.total, 1),
|
||
total: detail.total,
|
||
}
|
||
}
|
||
|
||
export default { getAlbumDetail, getAlbumSongs, searchAlbum, parseAlbumLink }
|