refactor(api)!: 平台 id 收尾统一——migu→mg、apple→ap
补上最后两个多字母 id,至此 15 个平台对外 id 全部为两字母: kw kg tx wy qs xm me qt lr mg bd jx bl 5s ap **破坏性变更**:id 出现在所有 URL、/sources 返回以及 data/auth.json 的 凭据键里,咪咕与 Apple Music 已存的登录态会失效,需重新登录。 - sdk/index.ts:id 与 supportQuality 映射键(展示名 `咪咕音乐` / `Apple Music` 保持不动) - sdk/types.ts:Source 联合类型 - src/routes.ts:/stream 的 enum ['qs','migu'] → ['qs','mg'],以及 /url/mg、/stream/mg 等路径与 schema 描述 - src/http/migu-stream.ts、src/routes-login.ts、src/server.ts - test/smoke-test.ts、README.md、docs/API.md 同步 有意未改:域名与 API 路径(music.migu.cn、music.apple.com、 amp-api.music.apple.com、/v1/catalog/cn/apple-curators/…)、 标识符(registerMiguStream、appleAmpGet、APPLE_AMP_API_URL 等)、 sdk/migu 与 sdk/apple 的目录与文件名、错误文案里的平台名。 验证:typecheck ✓、pnpm test 112/112 ✓;另起临时实例实证 /sources (15/15 平台 id 与展示名全对)与 /login/status,旧 id 零残留。
This commit is contained in:
1 parent
3381e9b906
commit
1b972ec4dd
20 files changed
+106
-106
No files matched your search
+2
-2
@@ -35,7 +35,7 @@ export const getAlbumDetail = async(rawId: string, page = 1, limit = 50) => {
|
||||
if (!summary.trackCount || songs.length > summary.trackCount) summary.trackCount = songs.length
|
||||
|
||||
return {
|
||||
source: 'apple' as const,
|
||||
source: 'ap' as const,
|
||||
info: {
|
||||
id: albumId,
|
||||
name: summary.name,
|
||||
@@ -64,7 +64,7 @@ export const getAlbumSongs = async(id: string): Promise<any[]> => {
|
||||
export const parseAlbumLink = async(link: string) => {
|
||||
const detail = await getAlbumDetail(link, 1, 100000)
|
||||
return {
|
||||
source: 'apple' as const,
|
||||
source: 'ap' as const,
|
||||
id: detail.info.id,
|
||||
info: detail.info,
|
||||
list: detail.list,
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
// Apple Music 平台模块(上游 music-lib/apple/apple.go 的 Node/TS 移植,平台 id: apple)
|
||||
// Apple Music 平台模块(上游 music-lib/apple/apple.go 的 Node/TS 移植,平台 id: ap)
|
||||
// 能力:搜索 / 歌词(TTML,匿名 developer token 通常拿不到,见 lyric.ts)/ 封面 /
|
||||
// 专辑(搜索+详情+链接解析)/ 歌单(搜索+详情+分类+分类歌单+链接解析)/
|
||||
// 链接解析(song/album/playlist)/ 取流(**仅 preview 试听片段**,见 sdk/apis/apple.ts)。
|
||||
// 凭据:上游 New(cookie) → getCredentials('apple'),cookie 形如
|
||||
// 凭据:上游 New(cookie) → getCredentials('ap'),cookie 形如
|
||||
// "media-user-token=xxx;storefront=cn;token=xxx",也支持 storefront/mediaUserToken/token 直填字段。
|
||||
// 未实现(上游也没有):userPlaylist、榜单、热搜、歌手、评论、扫码登录。
|
||||
import appleApi from '../apis/apple.ts'
|
||||
@@ -72,7 +72,7 @@ const apple = {
|
||||
|
||||
playlist,
|
||||
|
||||
// 契约别名:Apple 的歌单分类即 genre curators,挂到 songList 键供 /song-list/apple 复用
|
||||
// 契约别名:Apple 的歌单分类即 genre curators,挂到 songList 键供 /song-list/ap 复用
|
||||
songList: {
|
||||
sortList: [{ name: '热门', id: '1526756058' }],
|
||||
getTags() {
|
||||
@@ -96,7 +96,7 @@ const apple = {
|
||||
|
||||
/** 登录状态(media-user-token 存在性判断;无在线校验接口) */
|
||||
export const status = () => {
|
||||
const cred = getCredentials('apple') as any
|
||||
const cred = getCredentials('ap') as any
|
||||
const cfg = getAppleConfig()
|
||||
return (cfg.mediaUserToken || cred?.cookie)
|
||||
? { logged: true, updatedAt: cred?.updatedAt }
|
||||
|
||||
@@ -39,7 +39,7 @@ export const search = async(keyword: string, page = 1, limit = 20) => {
|
||||
allPage: hasNext ? p + 1 : p,
|
||||
total: (p - 1) * l + list.length,
|
||||
limit: l,
|
||||
source: 'apple' as const,
|
||||
source: 'ap' as const,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export const searchAlbum = async(keyword: string, page = 1, limit = 20) => {
|
||||
total: (p - 1) * l + list.length,
|
||||
page: p,
|
||||
limit: l,
|
||||
source: 'apple' as const,
|
||||
source: 'ap' as const,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export const searchPlaylist = async(keyword: string, page = 1, limit = 20) => {
|
||||
total: (p - 1) * l + list.length,
|
||||
page: p,
|
||||
limit: l,
|
||||
source: 'apple' as const,
|
||||
source: 'ap' as const,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ export const appleCuratorCategories: Array<{ id: string, name: string }> = [
|
||||
|
||||
/** 上游 GetPlaylistCategories:静态返回 Apple Music genre curators */
|
||||
export const getCategories = async() => ({
|
||||
source: 'apple' as const,
|
||||
list: appleCuratorCategories.map(item => ({ id: item.id, name: item.name, group: 'Apple Music 分类', source: 'apple' as const })),
|
||||
source: 'ap' as const,
|
||||
list: appleCuratorCategories.map(item => ({ id: item.id, name: item.name, group: 'Apple Music 分类', source: 'ap' as const })),
|
||||
})
|
||||
|
||||
/** 上游 GetCategoryPlaylists:按 curator 分类取歌单(cn storefront,单请求上限 25,循环补齐) */
|
||||
@@ -88,7 +88,7 @@ export const getCategoryPlaylists = async(categoryID: string, page = 1, limit =
|
||||
}
|
||||
|
||||
return {
|
||||
source: 'apple' as const,
|
||||
source: 'ap' as const,
|
||||
list: all.slice(0, l),
|
||||
page: p,
|
||||
limit: l,
|
||||
@@ -118,7 +118,7 @@ export const getPlaylistDetail = async(rawId: string, page = 1, limit = 100) =>
|
||||
if (!summary.trackCount || songs.length > summary.trackCount) summary.trackCount = songs.length
|
||||
|
||||
return {
|
||||
source: 'apple' as const,
|
||||
source: 'ap' as const,
|
||||
info: {
|
||||
id: playlistId,
|
||||
name: summary.name,
|
||||
@@ -145,7 +145,7 @@ export const getPlaylistSongs = async(id: string): Promise<any[]> => {
|
||||
export const parsePlaylistLink = async(link: string) => {
|
||||
const detail = await getPlaylistDetail(link, 1, 100000)
|
||||
return {
|
||||
source: 'apple' as const,
|
||||
source: 'ap' as const,
|
||||
id: detail.info.id,
|
||||
info: detail.info,
|
||||
list: detail.list,
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
// Apple Music(music.apple.com / amp-api.music.apple.com)公共工具
|
||||
// 上游 music-lib/apple/apple.go 的移植基础层:
|
||||
// - New(cookie) → getCredentials('apple'):cookie 支持 "media-user-token=xxx;storefront=cn;token=xxx"
|
||||
// - New(cookie) → getCredentials('ap'):cookie 支持 "media-user-token=xxx;storefront=cn;token=xxx"
|
||||
// - fetchAppleToken():从 music.apple.com 首页 → index-legacy JS 抓 developer token(amp-api 的 Bearer)
|
||||
// 当前(2026-10)Apple 下发的 JWT 以 eyJ0eXAi({"typ":...})开头而非上游正则写的 eyJh,
|
||||
// 故这里泛化为任意 JWT(eyJ…)并优先选择 issuer=AMPWebPlay 的 token;带回退与失效重取见 getAppleToken/invalidateAppleToken。
|
||||
@@ -42,7 +42,7 @@ export const parseCookieParts = (cookie: string): Record<string, string> => {
|
||||
|
||||
/** 读取 apple 平台运行时配置(对应上游 New(cookie)) */
|
||||
export const getAppleConfig = (): AppleRuntimeConfig => {
|
||||
const cred = (getCredentials('apple') as any) || {}
|
||||
const cred = (getCredentials('ap') as any) || {}
|
||||
const parts = parseCookieParts(String(cred.cookie ?? ''))
|
||||
const storefront = String(cred.storefront || parts.storefront || DEFAULT_STOREFRONT).trim()
|
||||
return {
|
||||
@@ -297,7 +297,7 @@ export const appleSongFromCatalog = (res: any, defaultAlbumId = ''): any => {
|
||||
lrc: null,
|
||||
types: [],
|
||||
_types: {},
|
||||
source: 'apple' as const,
|
||||
source: 'ap' as const,
|
||||
link: attr.url || '', // music.apple.com 网页地址
|
||||
previewUrl: previewURL, // 30-90 秒试听片段直链(Apple 不提供完整曲目明文流)
|
||||
duration: durationSec,
|
||||
@@ -316,7 +316,7 @@ export const appleAlbumToPlaylist = (res: any): any => {
|
||||
creator: attr.artistName || '',
|
||||
description: attr.editorialNotes?.short || '',
|
||||
link: attr.url || '',
|
||||
source: 'apple' as const,
|
||||
source: 'ap' as const,
|
||||
extra: {
|
||||
release_date: attr.releaseDate || '',
|
||||
record_label: attr.recordLabel || '',
|
||||
@@ -340,7 +340,7 @@ export const applePlaylistToPlaylist = (res: any): any => {
|
||||
creator: attr.curatorName || '',
|
||||
description,
|
||||
link: attr.url || '',
|
||||
source: 'apple' as const,
|
||||
source: 'ap' as const,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user