feat(xm): 喜马拉雅平台接入——匿名搜索/专辑/免费曲直链(hdaa xm-sign+AES-ECB 解密)、cookie 绑定解锁付费

This commit is contained in:
snowy-yang committed 2026-10-02 19:43:09 +08:00
1 parent 44852864e2
commit 5a9915b74c
8 files changed
+717

No files matched your search

+53
View File
@@ -0,0 +1,53 @@
// 喜马拉雅 xm-sign 生成 —— 移植 Ximalaya-Downloader-Next / easy-sign 的 du_web_sdk 签名链
// 协议:device_info → JSON → URL 编码字节 → zlib(level 6) → AES-128-ECB(PKCS7, KEY) →
// POST https://hdaa.shuzilm.cn/report?v=1.2.0&e=1&c=1&r=<uuid>(octet-stream)→
// Base64 响应 → AES-ECB 解密 → JSON 取 cadd/sid → xm-sign = "{cadd}&&{sid}"
// KEY 为 du_web_sdk _getDeviceKey(0) 的硬编码密钥;每次签名都重新上报(不缓存)。
// 模板为纯 ASCII(Base64/十六进制字段),latin1 取字节与参考实现的 charCode 语义一致。
import zlib from 'node:zlib'
import crypto from 'node:crypto'
import { httpFetch } from '../request.ts'
import { DEVICE_INFO } from './deviceInfo.ts'
const SIGN_KEY = 'm9ZtRrz:qujT8@da'
const REPORT_URL = 'https://hdaa.shuzilm.cn/report?v=1.2.0&e=1&c=1&r='
// 与 device_info.ew1.yV2(Chrome 125 模板)保持一致
export const XM_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
const aesEcb = (data: Buffer, decrypt: boolean) => {
const key = Buffer.from(SIGN_KEY, 'utf8')
const cipher = decrypt
? crypto.createDecipheriv('aes-128-ecb', key, null)
: crypto.createCipheriv('aes-128-ecb', key, null)
return Buffer.concat([cipher.update(data), cipher.final()])
}
/** 生成 xm-sign(形如 "cadd&&sid");每次调用重新上报设备指纹 */
export const makeXmSign = async(): Promise<string> => {
const info = { ...DEVICE_INFO, Zf5: Date.now() }
const compressed = zlib.deflateSync(Buffer.from(JSON.stringify(info), 'latin1'), { level: 6 })
const payload = aesEcb(compressed, false)
const { statusCode, body, raw } = await httpFetch(`${REPORT_URL}${crypto.randomUUID()}`, {
method: 'post',
headers: {
'Content-Type': 'application/octet-stream',
'User-Agent': XM_UA,
Host: 'hdaa.shuzilm.cn',
},
body: payload,
timeout: 15000,
}).promise as any
const respText = raw ? raw.toString('utf8') : String(body ?? '')
if (statusCode != 200) throw new Error(`xm-sign 上报失败: HTTP ${statusCode}`)
const decrypted = aesEcb(Buffer.from(respText, 'base64'), true).toString('utf8')
const obj = JSON.parse(decrypted)
const cadd = String(obj.cadd || '')
const sid = String(obj.sid || '')
if (!cadd || !sid) throw new Error('xm-sign 上报响应缺少 cadd/sid')
return `${cadd}&&${sid}`
}
export default { makeXmSign, XM_UA }