Files
MusicServer/sdk/xm/musicInfo.ts
T
snowy-yang 8c02fb5c15 refactor(sdk): 审计 P2 卫生项清理——请求层收敛与死代码清除
- 删除旧 sdk/request.ts,全部 import 收敛到 sdk/common/request.ts(85 文件)
- 注释死代码 630+ 行、裸 console.log 吞错 6 处、未使用符号 26 处清零
- bili 状态校验去重;utils DNS 预解析失败日志改 warn 带上下文
- CODE-AUDIT.md:第四节 P2 清理记录 + 第八节线索逐条复核结论(kw/kg/tx/wy parse 骨架相似实质各异,不抽公共层)
2026-10-03 20:48:54 +08:00

65 lines
3.1 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.
// 喜马拉雅曲目信息(PC 桌面端 baseInfo 通道)
// 接口: https://www.ximalaya.com/mobile-playpage/track/v3/baseInfo/{ms_ts}?device=win&trackId=&trackQualityLevel=1
// 实测(2026-10-02)匿名免费曲目可用,但必须同时满足:
// 1) 携带 PC 设备 Cookie(install_id 与 1&_device 同一 uuid,channel=99&100001,客户端 4.0.14 抓包格式)
// 2) 携带 du_web_sdk 真实 xm-sign(hdaa 上报取 cadd/sid)
// 缺 Cookie 或换 device=www2 均返回 ret=1001 系统繁忙。
// 响应 trackInfo 顶层挂载(playUrlList/title/cover*/duration/isPaid/isAuthorized/isXimiUhqTrack),
// playUrlList[].url 为 AES-ECB 加密地址(key 取自官方客户端 4.0.14 asar),见 decoder.ts
import crypto from 'node:crypto'
import { httpFetch } from '../common/request.ts'
import { makeXmSign, XM_UA } from './sign.ts'
import { getCredentials } from '../auth/store.ts'
const BASE_INFO_URL = 'https://www.ximalaya.com/mobile-playpage/track/v3/baseInfo/'
// 进程级设备标识(install_id 与 1&_device 共用同一 uuid,稳定复用)
const deviceUuid = crypto.randomUUID()
const PC_DEVICE_COOKIE = `install_id=${deviceUuid}; channel=99&100001; 1&_device=win32&${deviceUuid}&4.0.14`
/** 登录态 Cookie(/login/xm 绑定,须含 1&_token);与 PC 设备 Cookie 合并后上送 */
export const buildRequestCookie = (): string => {
const loginCookie = (getCredentials('xm') as any)?.cookie || ''
return loginCookie ? `${loginCookie}; ${PC_DEVICE_COOKIE}` : PC_DEVICE_COOKIE
}
export const coverUrl = (trackInfo: any): string | null => {
const path = trackInfo?.coverMiddle || trackInfo?.coverLarge || trackInfo?.coverSmall
if (!path) return null
const s = String(path)
return s.startsWith('http') ? s : `https://imagev2.xmcdn.com/${s}`
}
/** 拉取曲目播放信息(免登免费曲可用;付费/VIP 曲 ret=1001 或 isAuthorized=false) */
export const getTrackInfo = async(trackId: string, trackQualityLevel = 1): Promise<any> => {
const xmSign = await makeXmSign()
const { statusCode, body } = await httpFetch(
`${BASE_INFO_URL}${Date.now()}?device=win&trackId=${trackId}&trackQualityLevel=${trackQualityLevel}`,
{
headers: {
'User-Agent': XM_UA,
Accept: 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
Referer: `https://www.ximalaya.com/sound/${trackId}`,
Origin: 'https://www.ximalaya.com',
Cookie: buildRequestCookie(),
'xm-sign': xmSign,
},
timeout: 20000,
},
).promise as any
if (statusCode != 200) throw new Error(`baseInfo HTTP ${statusCode}`)
const b = body ?? {}
if (b.ret != 0) {
// 1001:匿名访问付费内容与风控共用;未登录时优先提示绑定登录
const logged = Boolean((getCredentials('xm') as any)?.cookie)
if (b.ret == 1001 && !logged) throw new Error('该内容需要登录(POST /login/xm 绑定 cookie 后重试)')
throw new Error(b.msg || `baseInfo ret ${b.ret}`)
}
const trackInfo = b.trackInfo ?? b.data?.trackInfo
if (!trackInfo) throw new Error('baseInfo 未返回 trackInfo')
return trackInfo
}
export default { getTrackInfo, coverUrl }