平台注册表由 9 个扩到 15 个,同时补齐 sdk/types.ts 的类型与 OpenAPI 文档里的能力位声明。 - migu:命中 Z3D/I3D 加密候选时不返回直链,改由服务端解密代理出流, SDK 侧只负责登记票据,避免客户端拿到无法播放的密文 - apple:只提供 preview 试听片段(完整曲为 DRM),标 noAggregate 排除在跨平台精确匹配之外,防止用它顶掉其它平台的可播结果 - 其余平台按既有 PlatformModule 约定实现搜索/专辑/歌单/歌词 为什么合并成一次提交:六平台共用同一份注册表与能力位声明, 拆开会产生互相引用、单独无法通过类型检查的中间态。
64 lines
2.5 KiB
TypeScript
64 lines
2.5 KiB
TypeScript
// 咪咕音乐平台模块 —— 搜索 / 取流(多档候选 + Z3D 解密标注)/ 歌词 / 封面 / 专辑 / 歌单 / 链接解析
|
||
// 上游对应:music-lib/migu/(migu.go、song.go、lyric.go、album.go、playlist.go、download.go)
|
||
// 匿名面(2026-10-02 实测):搜索、PQ(128k mp3) 取流、歌词、封面、专辑曲目、歌单曲目均可用;
|
||
// HQ/SQ/ZQ 各档需登录 Cookie(接口返回「登录后免费试听」)。
|
||
import miguApi from '../apis/migu.ts'
|
||
import musicSearch from './musicSearch.ts'
|
||
import lyric, { contentIdOf } from './lyric.ts'
|
||
import album from './album.ts'
|
||
import playlist from './playlist.ts'
|
||
import { fetchSongDetail, miguSongLink } from './util.ts'
|
||
|
||
const migu = {
|
||
musicSearch,
|
||
getMusicUrl(songInfo: any, type: string) {
|
||
return miguApi.getMusicUrl(songInfo, type)
|
||
},
|
||
getLyric(songInfo: any) {
|
||
return lyric.getLyric(songInfo)
|
||
},
|
||
getPic(songInfo: any) {
|
||
return (async() => {
|
||
if (songInfo?.img) return songInfo.img
|
||
const contentID = contentIdOf(songInfo)
|
||
if (!contentID) return null
|
||
const item = await fetchSongDetail(contentID)
|
||
const { pickMiguImage, normalizeMiguImageURL, firstNonEmpty } = await import('./util.ts')
|
||
return normalizeMiguImageURL(firstNonEmpty(
|
||
pickMiguImage(item.imgItems), pickMiguImage(item.albumImgs), item.img1, item.img2, item.img3,
|
||
)) || null
|
||
})()
|
||
},
|
||
getMusicInfo(songInfo: any) {
|
||
const contentID = contentIdOf(songInfo)
|
||
if (!contentID) return Promise.reject(new Error('migu 歌曲 id 无效'))
|
||
return fetchSongDetail(contentID)
|
||
},
|
||
album,
|
||
playlist,
|
||
parse: {
|
||
parseSong: async(link: string) => parseSong(link),
|
||
parseAlbum: album.parseAlbumLink,
|
||
parsePlaylist: playlist.parsePlaylistLink,
|
||
},
|
||
getMusicDetailPageUrl(songInfo: any) {
|
||
const extra = songInfo?.extra ?? {}
|
||
return String(extra.link ?? '') || miguSongLink(contentIdOf(songInfo))
|
||
},
|
||
}
|
||
|
||
/** 歌曲链接解析:https://music.migu.cn/v3/music/song/<contentId>(含裸 contentId) */
|
||
export const parseSong = async(link: string) => {
|
||
const raw = String(link ?? '')
|
||
const m = raw.match(/music\.migu\.cn\/v3\/music\/song\/(\d+)/) || raw.match(/^(\d{6,})$/)
|
||
if (!m) throw new Error('无效的咪咕歌曲链接')
|
||
const contentID = m[1]!
|
||
const item = await fetchSongDetail(contentID)
|
||
const { convertItemToSong } = await import('./util.ts')
|
||
const song = convertItemToSong(item, true)
|
||
if (!song) throw new Error('该歌曲没有可用格式')
|
||
return song
|
||
}
|
||
|
||
export default migu
|