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 规范化,
与新增平台的注册表改动分开,出问题时便于单独回滚。
This commit is contained in:
snowy-yang committed 2026-10-03 00:26:51 +08:00
1 parent 33b96f92c4
commit 68e071b166
20 files changed
+2218 -33

No files matched your search

+8 -2
View File
@@ -15,6 +15,7 @@
// 不匹配视为未命中、沿降级链换档重试;
// - 此前「mobi.s 吐 11 秒蜜罐假 MP3」的结论仅适用于其它 type/source 参数组合,
// 本组合已验证为真音频。
// - 返回 url 的 query 用 `$` 顶替 `=`,统一还原后对外返回(见 normalizeUrl)。
// 2) www.kuwo.cn/api/v1/www/music/playUrl(回退通道,web 端官方接口,需 Secret 签名头)
// 签名:GET https://www.kuwo.cn/ 首页拿动态 cookie(Hm_Iuvt_*,名字与值均随机下发),
// 以「cookie 值为消息、cookie 名为密钥」跑异或哈希得 Secret 头(酷我 web 端算法,
@@ -48,6 +49,11 @@ const fallbackChain: Record<string, string[]> = {
// 明文音频格式白名单;mgg/mflac/mmp4/zp 等为加密格式,拿到也不能播
const plainFormats = new Set(['mp3', 'flac', 'ogg', 'aac', 'm4a'])
// 酷我 CDN 回吐的 query 用 `$` 顶替 `=`(`?bitrate$320&format$mp3&…&loginUid$`,实为酷我客户端
// 自有写法,签名 token 在 path 里、query 无 sign 参数),`$` 非标准分隔符,播放器与 URL
// 解析器普遍不认,统一还原成 `=` 再对外返回
const normalizeUrl = (url: string) => url.replace(/\$/g, '=')
// 匿名通道实际可用音质(sdk/index.ts 汇总为 supportQuality)
export const supportQualitys = ['128k', '320k', 'flac', 'flac24bit']
@@ -68,7 +74,7 @@ const getUrlByMobi = (songmid: string, tier: { br: string, bitrate: number }): P
const data = body?.data
if (statusCode != 200 || !data?.url) return null
if (Number(data.bitrate) != tier.bitrate || !plainFormats.has(String(data.format))) return null
return { url: data.url }
return { url: normalizeUrl(data.url) }
}).catch(() => null)
}
@@ -156,7 +162,7 @@ const getUrlByPlayUrl = (songmid: string, t: KwToken, retryNum = 0) => {
return Promise.reject(new Error(msg || '获取失败'))
}
// 服务器无视 br,匿名实际只回 128k,如实上报
return { type: '128k', url: body.data.url }
return { type: '128k', url: normalizeUrl(body.data.url) }
})
return requestObj
}