refactor(qs)!: 手机端链路彻底清除——PC 签名成为唯一供给
安卓自签 crypto/ 整目录(11 文件)与 device.ts 设备注册删除,audiodecrypt 上移 qs/ 根,getDeviceIds 并入 passport(_qs_passport_device 持久化); musicSearch 去指纹搜索(需 PC 签名,匿名搜索下线);mssdk 签名供给改三级: QS_SIGN_SERVER 显式 → 本机 16689 /healthz 自动探测(qs-signer 的 mssdk-sign) → data/qs-sign.json 固定值兜底。
This commit is contained in:
1 parent
cec63f864e
commit
6b05fde64b
19 files changed
+111
-1271
No files matched your search
+59
-14
@@ -1,28 +1,66 @@
|
||||
// 汽水 PC 通道签名(X-Helios / X-Medusa)—— 两种供给方式,按优先级:
|
||||
// 1) 签名服务器:环境变量 QS_SIGN_SERVER 指向签名服务(如 http://127.0.0.1:8899,
|
||||
// 汽水 PC 通道签名(X-Helios / X-Medusa)—— 三种供给方式,按优先级:
|
||||
// 1) 签名服务器(显式):环境变量 QS_SIGN_SERVER 指向签名服务(如 http://127.0.0.1:16889,
|
||||
// 可选 QS_SIGN_TOKEN 配 Bearer 鉴权),HTTP POST /sign。
|
||||
// 契约同时兼容独立仓库 qs-signer(gitea.4i.hk/hhhge/qs-signer,本地与 MusicServer 同级)
|
||||
// 的两个服务(请求 {url, headers, body?},响应 {ok, headers, added?}):
|
||||
// - server.mjs (Wine + bdms.node)
|
||||
// - mssdk-sign/ (metasecml.dll 独立进程,见其 README)
|
||||
// 2) 固定值兜底:无签名服务器时,两个加密头直接用抓包固定值 ——
|
||||
// 契约兼容独立仓库 qs-signer(gitea.4i.hk/hhhge/qs-signer,本地与 MusicServer 同级)
|
||||
// 的 mssdk-sign 服务(请求 {url, headers, body?},响应 {ok, headers, added?})。
|
||||
// 2) 签名服务器(自动探测):QS_SIGN_SERVER 未配置时,进程启动探测本机 16689 端口——
|
||||
// GET /healthz 应答 ok:true 且 service:"mssdk-sign" 才认定为汽水签名服务并自动接入
|
||||
// (16689 上是别的服务则忽略,端口没人听静默跳过,均回落到 3)。
|
||||
// 3) 固定值兜底:无签名服务器时,两个加密头直接用抓包固定值 ——
|
||||
// 环境变量 QS_HELIOS / QS_MEDUSA,或 data/qs-sign.json:
|
||||
// { "helios": "…", "medusa": "…", "device_id": "…", "install_id": "…" }
|
||||
// 签名与设备/URL 绑定:固定值必须搭配抓包时的同一套 device_id/install_id
|
||||
// (json 里带上即自动对齐 URL 参数),时效自负,被风控时走安卓通道兜底。
|
||||
// 两者都没有:PC 通道不可用,搜索/取流自动走安卓自签通道。
|
||||
// (json 里带上即自动对齐 URL 参数),时效自负。
|
||||
// 两者都没有:PC 签名不可用 —— 搜索/取流走 track_v2 的通道直接报错(匿名试听不受影响)。
|
||||
// 官方只对 qishui.com 且 **非** /passport/、非 /ttwid/ 的请求签名 —— 登录链路用不到本模块。
|
||||
// 签名覆盖 URL + 全部请求头;POST 的 x-ss-stub(body 的 MD5 大写)既是签名输入也要随请求发出。
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { getDeviceIds } from './device.ts'
|
||||
import { getDeviceIds } from './passport.ts'
|
||||
|
||||
// 仓库根(本文件在 sdk/qs/ 下)——不依赖 process.cwd(),从任何目录启动都能找到
|
||||
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..')
|
||||
const FIXED_SIGN_FILE = join(REPO_ROOT, 'data', 'qs-sign.json')
|
||||
const SIGN_TIMEOUT = 5000
|
||||
|
||||
// —— 端口探测:QS_SIGN_SERVER 未配置时,启动探测本机 16689 是不是汽水签名服务 ——
|
||||
const PROBE_BASE = 'http://127.0.0.1:16689'
|
||||
const PROBE_TIMEOUT = 2000
|
||||
|
||||
// undefined=探测未完成;null=探测过、不是签名服务;字符串=探测到的签名服务地址
|
||||
let probedServer: string | null | undefined
|
||||
let probing: Promise<void> | null = null
|
||||
|
||||
/** healthz 应答 ok:true 且 service:"mssdk-sign" 才认——16689 上可能是无关服务 */
|
||||
const isSignService = (json: any): boolean => json?.ok === true && json?.service === 'mssdk-sign'
|
||||
|
||||
const probeOnce = async (): Promise<void> => {
|
||||
try {
|
||||
const res = await fetch(`${PROBE_BASE}/healthz`, { signal: AbortSignal.timeout(PROBE_TIMEOUT) })
|
||||
const json = await res.json().catch(() => null) as any
|
||||
if (res.ok && isSignService(json)) {
|
||||
probedServer = PROBE_BASE
|
||||
console.log(`[qs/mssdk] 探测到本机 16689 是汽水签名服务(mssdk-sign v${json.version ?? '?'},dll ${json.dllVersion ?? '?'}),自动接入`)
|
||||
} else {
|
||||
probedServer = null
|
||||
console.log(`[qs/mssdk] 本机 16689 有服务但不是汽水签名服务(healthz service=${json?.service ?? '非 JSON'}),忽略`)
|
||||
}
|
||||
} catch {
|
||||
probedServer = null // 端口没人监听是常态,静默
|
||||
}
|
||||
}
|
||||
|
||||
/** 确保探测完成(进程内只探一次);显式配置了 QS_SIGN_SERVER 则永不探测 */
|
||||
const ensureProbe = async (): Promise<void> => {
|
||||
if (probedServer !== undefined || process.env.QS_SIGN_SERVER?.trim()) return
|
||||
probing ??= probeOnce()
|
||||
await probing
|
||||
}
|
||||
|
||||
// 启动即探测一次(异步进行,不阻塞导入);探测期间进来的签名调用会等它出结果
|
||||
void ensureProbe()
|
||||
|
||||
/** 固定值配置(helios/medusa 必填;device_id/install_id 应与抓包时同一套) */
|
||||
interface FixedSignConfig {
|
||||
helios: string
|
||||
@@ -75,16 +113,22 @@ type SignMode =
|
||||
| { kind: 'server'; server: string; token: string }
|
||||
| { kind: 'fixed'; fixed: FixedSignConfig }
|
||||
|
||||
/** 当前签名供给方式:签名服务器 > 固定值 > 不可用(null) */
|
||||
/** 当前签名供给方式:显式服务器 > 探测接入的服务器 > 固定值 > 不可用(null) */
|
||||
const loadSignMode = (): SignMode | null => {
|
||||
const server = process.env.QS_SIGN_SERVER?.trim().replace(/\/+$/, '')
|
||||
if (server) return { kind: 'server', server, token: process.env.QS_SIGN_TOKEN?.trim() ?? '' }
|
||||
if (probedServer) {
|
||||
// 探测接入;签名服务开了鉴权时同样吃 QS_SIGN_TOKEN
|
||||
return { kind: 'server', server: probedServer, token: process.env.QS_SIGN_TOKEN?.trim() ?? '' }
|
||||
}
|
||||
const fixed = readFixedConfig()
|
||||
return fixed ? { kind: 'fixed', fixed } : null
|
||||
}
|
||||
|
||||
/** PC 签名是否可用(只看配置;服务器宕机等运行期失败由签名调用返回 null、调用方降级处理) */
|
||||
export const mssdkAvailable = (): boolean => Boolean(loadSignMode())
|
||||
/** PC 签名是否可用(只看配置与探测结果;服务器宕机等运行期失败由签名调用返回 null、调用方降级处理) */
|
||||
export const mssdkAvailable = (): boolean =>
|
||||
// 探测未完成时先放行——紧随其后的 signQishuiRequest 会等探测结果再定供给
|
||||
Boolean(loadSignMode()) || probedServer === undefined
|
||||
|
||||
const signViaServer = async (
|
||||
server: string,
|
||||
@@ -125,13 +169,14 @@ const signViaServer = async (
|
||||
* 给一个 qishui.com 请求签名
|
||||
* @param headers 必须是「真实要发出去的那一份」(含 cookie / x-ss-stub)
|
||||
* @param body POST body(给了且 headers 没带 x-ss-stub 时服务端会自动补)
|
||||
* @returns 需追加的头;不可用时返回 null(调用方走安卓通道)
|
||||
* @returns 需追加的头;不可用时返回 null(调用方自行降级处理)
|
||||
*/
|
||||
export const signQishuiRequest = async (
|
||||
url: string,
|
||||
headers: Record<string, string>,
|
||||
body?: string,
|
||||
): Promise<Record<string, string> | null> => {
|
||||
await ensureProbe() // 探测未完成时先等它出结果,再决定供给方式
|
||||
const mode = loadSignMode()
|
||||
if (!mode) return null
|
||||
if (mode.kind === 'fixed') return { 'X-Helios': mode.fixed.helios, 'X-Medusa': mode.fixed.medusa }
|
||||
|
||||
Reference in new issue
Block a user