refactor: 架构整固——分层单向化、全量 TypeScript、直接运行

- 移除 bd/mg 两平台及磁盘残留产物;清理 api-test 死脚本、musicSdk/message+options 死模块、infSign vendor
- musicSdk/ 改名 sdk/;src/common、src/auth、src/apis 整体移入 sdk/,依赖方向定为 src → sdk 严格单向
- src/ 手写 JS 全量转 .ts,删除 JS 类型垫片 modules.d.ts;源码 import 统一 .ts 后缀
- Node 原生类型剥离直接运行 .ts(engines >=22.18),取消 tsc 发射产物,tsc 仅类型检查(pnpm typecheck)
- 删除 kwdes 备用实现与 src/index.ts 转发垫片;测试移至 test/smoke-test.ts
- common/request.ts 补显式导出类型;修复 set-cookie 单值形态、ApiError 类字段等隐患

验证:tsc 零错误,smoke-test 41/41
This commit is contained in:
snowy-yang committed 2026-09-25 23:06:23 +08:00
1 parent c8c939aa32
commit 354df2e739
126 files changed
+413 -2942

No files matched your search

+86
View File
@@ -0,0 +1,86 @@
// 酷狗音乐 URL 获取(手机客户端通道)
// 接口: https://gateway.kugou.com/v5/url(x-router: trackercdn.kugou.com,android 签名)
// 登录后自动携带 token/userid(见 /login/kg);匿名仅能获取部分免费资源,
// VIP/受限资源会返回 status:2。quality: 128k→128, 320k→320, flac→flac, flac24bit→high
// 统一接口约定:apis('kg').getMusicUrl(songInfo, type) → { promise, cancelHttp }
// promise resolve → { type, url }
import crypto from 'node:crypto'
import { httpFetch } from '../common/request.ts'
import { getKgAuth } from '../auth/store.ts'
import { androidSign, getDevice } from '../auth/kg.ts'
const md5 = s => crypto.createHash('md5').update(s).digest('hex')
const SALT_KEY = '57ae12eb6890223e355ccfcb74edf70d'
const APPID = 1005
const CLIENTVER = 11430
const qualityMap = { '128k': '128', '320k': '320', flac: 'flac', flac24bit: 'high' }
export const getMusicUrl = (songInfo, type) => {
const requestObj = { cancelHttp() {}, promise: null as any }
const hash = (songInfo._types?.[type]?.hash || songInfo.hash || '').toLowerCase()
if (!hash) {
requestObj.promise = Promise.reject(new Error('缺少 hash'))
return requestObj
}
requestObj.cancelHttp = () => {} // 单次 GET,无需取消
requestObj.promise = (async() => {
const dev = getDevice()
const auth = getKgAuth()
const userid = Number(auth?.userid || 0)
const params: Record<string, any> = {
dfid: dev.dfid, mid: dev.mid, uuid: '-', appid: APPID, clientver: CLIENTVER,
clienttime: Math.floor(Date.now() / 1000),
album_id: Number(songInfo.albumId ?? 0),
area_code: 1,
hash,
ssa_flag: 'is_fromtrack',
version: CLIENTVER,
page_id: 151369488,
quality: qualityMap[type] ?? '128',
album_audio_id: Number(songInfo.songmid ?? 0),
behavior: 'play',
pid: 2,
cmd: 26,
pidversion: 3001,
IsFreePart: 0,
ppage_id: '463467626,350369493,788954147',
cdnBackup: 1,
module: '',
}
if (auth) {
params.token = auth.token
params.userid = userid
}
params.key = md5(`${hash}${SALT_KEY}${APPID}${dev.mid}${userid}`)
params.signature = androidSign(params)
const qs = Object.keys(params).map(k => `${k}=${encodeURIComponent(params[k])}`).join('&')
const r = await httpFetch(`https://gateway.kugou.com/v5/url?${qs}`, {
method: 'get',
headers: {
dfid: dev.dfid, mid: dev.mid, clienttime: String(params.clienttime),
'kg-rc': '1', 'kg-thash': '5d816a0', 'kg-rec': '1', 'kg-rf': 'B9EDA08A64250DEFFBCADDEE00F8F25F',
'x-router': 'trackercdn.kugou.com',
'User-Agent': 'Android15-1070-11083-46-0-DiscoveryDRADProtocol-wifi',
},
timeout: 15000,
}).promise
const body = r.body && typeof r.body === 'object' ? r.body : {}
if (body.status !== 1 || !Array.isArray(body.data)) {
throw new Error(body.error || (auth ? '获取失败' : '获取失败(该资源需要酷狗登录)'))
}
const item = body.data.find(d => d.url) ?? body.data[0]
const url = item?.url || item?.fallback
if (!url) throw new Error('获取失败')
return { type, url }
})()
return requestObj
}
export default { getMusicUrl }