- kg: login_by_token 续期(AES-256-CBC + RSA 裸加密,参考其 refresh/kg.py),新 token 回写 data/auth.json - tx: 新增 sdk/auth/tx.ts —— cookie 解析、VIP 检测(vip_login_base)、 music.login.LoginServer 续期(走既有 musicu.fcg 未签名通道,最佳努力) - 调度:启动 10s 后首刷 + 每 24h 一次(对齐参考项目 86400s + next_run_time=now) - /login/tx 支持可选 App 抓包凭据(openId/accessToken/refreshToken/refreshKey) - 新增 POST /login/refresh 手动续期;/login/status 增补 tx 字段与更新时间 - 补充 /login/tx 文档行与 smoke 测试
99 lines
2.9 KiB
TypeScript
99 lines
2.9 KiB
TypeScript
// 登录态存储 —— 各平台凭据持久化(data/auth.json,不入库)
|
|
// 结构:{ wy: {...}, kg: {...}, tx: {...}, _kg_device: {...} }
|
|
// 注意:store.js 为 tsc 就地发射产物(不入库);改逻辑只改本文件,然后 pnpm build
|
|
|
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
import { dirname, join } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const DATA_FILE = join(dirname(fileURLToPath(import.meta.url)), '../../data/auth.json')
|
|
|
|
export interface WyCredentials {
|
|
cookie: string
|
|
musicU: string
|
|
userid?: number
|
|
nickname?: string | null
|
|
avatar?: string | null
|
|
vipType?: number | null
|
|
updatedAt?: string
|
|
}
|
|
|
|
export interface KgCredentials {
|
|
token: string
|
|
userid: number | string
|
|
nickname?: string | null
|
|
updatedAt?: string
|
|
}
|
|
|
|
export interface KgDevice {
|
|
guid: string
|
|
mid: string
|
|
dfid: string
|
|
updatedAt?: string
|
|
}
|
|
|
|
export interface TxCredentials {
|
|
cookie: string
|
|
// App 抓包的完整凭据(可选):提供后可自动续期(music.login.LoginServer),
|
|
// 仅浏览器 cookie 时 qm_keyst 无法主动续期,只能做 VIP 检测
|
|
openId?: string
|
|
accessToken?: string
|
|
refreshToken?: string
|
|
refreshKey?: string
|
|
uin?: string
|
|
vipType?: 'svip' | 'vip' | 'normal' | null
|
|
updatedAt?: string
|
|
}
|
|
|
|
let cache: Record<string, any> = {}
|
|
let loaded = false
|
|
|
|
const load = (): Record<string, any> => {
|
|
if (loaded) return cache
|
|
try {
|
|
cache = existsSync(DATA_FILE) ? JSON.parse(readFileSync(DATA_FILE, 'utf8')) : {}
|
|
} catch {
|
|
cache = {}
|
|
}
|
|
loaded = true
|
|
return cache
|
|
}
|
|
|
|
const save = (data: Record<string, any>): void => {
|
|
cache = data
|
|
loaded = true
|
|
mkdirSync(dirname(DATA_FILE), { recursive: true })
|
|
writeFileSync(DATA_FILE, JSON.stringify(data, null, 2))
|
|
}
|
|
|
|
export const getCredentials = (platform: string): any => load()[platform] ?? null
|
|
|
|
export const setCredentials = (platform: string, credentials: Record<string, any>): void => {
|
|
const data = load()
|
|
data[platform] = { ...data[platform], ...credentials, updatedAt: new Date().toISOString() }
|
|
save(data)
|
|
}
|
|
|
|
export const clearCredentials = (platform: string): void => {
|
|
const data = load()
|
|
delete data[platform]
|
|
save(data)
|
|
}
|
|
|
|
/** wy 登录 cookie 字符串(未登录返回空串) */
|
|
export const getWyCookie = (): string => (getCredentials('wy') as WyCredentials | null)?.cookie || ''
|
|
|
|
/** kg 登录态(未登录返回 null) */
|
|
export const getKgAuth = (): { token: string; userid: number | string } | null => {
|
|
const cred = getCredentials('kg') as KgCredentials | null
|
|
return cred?.token ? { token: cred.token, userid: cred.userid || 0 } : null
|
|
}
|
|
|
|
/** tx(QQ) 登录态:cookie 与其中的 uin(未登录返回 null) */
|
|
export const getTxAuth = (): { cookie: string; uin: string } | null => {
|
|
const cred = getCredentials('tx') as TxCredentials | null
|
|
if (!cred?.cookie) return null
|
|
const uin = cred.cookie.match(/uin=(\d+)/)?.[1] ?? '0'
|
|
return { cookie: cred.cookie, uin }
|
|
}
|