Files
MusicServer/sdk/kg/album.ts
T
snowy-yang 68e071b166 feat(sdk): kg/kw/tx/wy 补齐专辑、歌单与 parse 能力
四个主流平台从「搜索 + 取流」补到能力对齐,新增 playlist/parse
(tx、wy 另加 userPlaylist),顺带修掉酷我直链的一个真实缺陷。

- kw:酷我 CDN 回吐的 query 用 `$` 顶替 `=`(`?bitrate$320&format$mp3…`),
  `$` 非 URL 标准分隔符,播放器与解析器普遍不认,导致拿到直链也放不出声;
  统一还原成 `=` 再对外返回
- wy:补齐 album/playlist/parse/userPlaylist,与 kg/tx 能力对齐
- kg/kw/tx:接入 parse(跨平台精确匹配的输入源)与歌单

为什么单独一次提交:这批改动不动架构,只做能力补齐与 URL 规范化,
与新增平台的注册表改动分开,出问题时便于单独回滚。
2026-10-03 00:26:51 +08:00

127 lines
4.9 KiB
TypeScript
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.
import { getMusicInfosByList } from './musicInfo.ts'
import { createHttpFetch } from './util.ts'
import { httpFetch } from '../request.ts'
const albumCover = (url: any): string => String(url ?? '').replace('{size}', '240')
export default {
/**
* 通过AlbumId获取专辑信息
* @param {*} id
*/
async getAlbumInfo(id) {
const albumInfoRequest = await createHttpFetch('http://kmrserviceretry.kugou.com/container/v1/album?dfid=1tT5He3kxrNC4D29ad1MMb6F&mid=22945702112173152889429073101964063697&userid=0&appid=1005&clientver=11589', {
method: 'POST',
body: {
appid: 1005,
clienttime: 1681833686,
clientver: 11589,
data: [{ album_id: id }],
fields: 'language,grade_count,intro,mix_intro,heat,category,sizable_cover,cover,album_name,type,quality,publish_company,grade,special_tag,author_name,publish_date,language_id,album_id,exclusive,is_publish,trans_param,authors,album_tag',
isBuy: 0,
key: 'e6f3306ff7e2afb494e89fbbda0becbf',
mid: '22945702112173152889429073101964063697',
show_album_tag: 0,
},
})
if (!albumInfoRequest) return Promise.reject(new Error('get album info failed.'))
const albumInfo = albumInfoRequest[0]
return {
name: albumInfo.album_name,
image: albumInfo.sizable_cover.replace('{size}', 240),
desc: albumInfo.intro,
authorName: albumInfo.author_name,
// play_count: this.formatPlayCount(info.count),
}
},
/**
* 通过AlbumId获取专辑
* @param {*} id
* @param {*} page
*/
async getAlbumDetail(id, page = 1, limit = 200) {
const albumList = await createHttpFetch(`http://mobiles.kugou.com/api/v3/album/song?version=9108&albumid=${id}&plat=0&pagesize=${limit}&area_code=0&page=${page}&with_res_tag=0`)
if (!albumList.info) return Promise.reject(new Error('Get album list failed.'))
let result = await getMusicInfosByList(albumList.info)
const info = await this.getAlbumInfo(id)
return {
list: result || [],
page,
limit,
total: albumList.total,
source: 'kg',
info: {
name: info.name,
img: info.image,
desc: info.desc,
author: info.authorName,
// play_count: this.formatPlayCount(info.count),
},
}
},
/**
* 专辑搜索(上游 kugou SearchAlbum:mobilecdn v3 search/album)
* 返回摘要统一形状(对齐契约:id/name/cover/trackCount/creator/description/link/source)
*/
async searchAlbum(keyword, page = 1, limit = 10) {
const word = String(keyword ?? '').trim()
if (!word) return { source: 'kg', list: [], page, limit, total: 0 }
const pageNo = Math.max(1, Number(page) || 1)
const pageSize = Math.max(1, Number(limit) || 10)
const apiURL = `http://mobilecdn.kugou.com/api/v3/search/album?keyword=${encodeURIComponent(word)}&format=json&page=${pageNo}&pagesize=${pageSize}`
const { statusCode, body } = await httpFetch(apiURL, {
headers: {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
Referer: 'http://m.kugou.com',
},
timeout: 15000,
}).promise
if (statusCode !== 200 || body?.status !== 1 || body?.errcode !== 0) {
throw new Error(body?.error || '酷狗专辑搜索失败')
}
const list = (body?.data?.info ?? []).map((item: any) => {
const albumID = String(item?.albumid ?? '').trim()
if (!albumID || albumID === '0') return null
return {
id: albumID,
name: String(item?.albumname ?? '').trim(),
cover: albumCover(item?.imgurl),
trackCount: Number(item?.songcount ?? 0) || 0,
creator: String(item?.singername ?? '').trim(),
description: String(item?.intro ?? '').trim(),
publishTime: String(item?.publishtime ?? '').trim(),
link: `https://www.kugou.com/album/${albumID}.html`,
source: 'kg',
type: 'album',
}
}).filter(Boolean)
return { source: 'kg', list, page: pageNo, limit: pageSize, total: Number(body?.data?.total ?? 0) || list.length }
},
/** 上游 GetAlbumSongs:复用 getAlbumDetail 拉全量曲目 */
async getAlbumSongs(id) {
const albumID = String(id ?? '').trim()
if (!albumID) throw new Error('酷狗专辑 id 为空')
const { list } = await this.getAlbumDetail(albumID, 1, 10000)
return list
},
/** 上游 ParseAlbum:album/single/<id>.html、album/<id>.html、albumid=<id> */
async parseAlbumLink(link) {
const raw = String(link ?? '')
const patterns = [
/album\/single\/(\d+)\.html/,
/yy\/album\/single\/(\d+)\.html/,
/album\/(\d+)\.html/,
/albumid=(\d+)/,
]
for (const pattern of patterns) {
const match = raw.match(pattern)
if (match) return this.getAlbumDetail(match[1], 1)
}
throw new Error('无效的酷狗专辑链接')
},
}