feat(login): kg/tx 登录态自动续期(移植 lx-music-api-server 方案)
- 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 测试
This commit is contained in:
1 parent
9d7cb6f39a
commit
aca7928019
8 files changed
+300
-9
No files matched your search
+23
-5
@@ -4,6 +4,7 @@
|
||||
import * as wyAuth from '../sdk/auth/wy.ts'
|
||||
import * as kgAuth from '../sdk/auth/kg.ts'
|
||||
import { clearCredentials, getCredentials, getTxAuth, setCredentials } from '../sdk/auth/store.ts'
|
||||
import { refreshAll } from './refresh.ts'
|
||||
|
||||
const fail = (reply, err) => {
|
||||
const status = err.status ?? 502
|
||||
@@ -107,12 +108,17 @@ export const loginRoutes = async(fastify) => {
|
||||
fastify.post('/login/tx', {
|
||||
schema: {
|
||||
summary: '绑定 QQ 音乐 cookie',
|
||||
description: 'cookie 从已登录的 y.qq.com 浏览器请求头复制(需含 uin 与 qm_keyst 字段),用于解锁 tx 取流的 320k/无损音质。',
|
||||
description: 'cookie 从已登录的 y.qq.com 浏览器请求头复制(需含 uin 与 qm_keyst 字段),用于解锁 tx 取流的 320k/无损音质。' +
|
||||
'可选提供 App 抓包的 openId/accessToken/refreshToken/refreshKey,供定时任务自动续期登录态。',
|
||||
tags: ['登录'],
|
||||
body: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
cookie: { type: 'string' },
|
||||
openId: { type: 'string' },
|
||||
accessToken: { type: 'string' },
|
||||
refreshToken: { type: 'string' },
|
||||
refreshKey: { type: 'string' },
|
||||
},
|
||||
required: ['cookie'],
|
||||
},
|
||||
@@ -123,7 +129,8 @@ export const loginRoutes = async(fastify) => {
|
||||
if (!uin || !/qm_keyst=\S+/.test(cookie)) {
|
||||
return reply.code(422).send({ code: 422, msg: 'cookie 需包含 uin 与 qm_keyst 字段' })
|
||||
}
|
||||
setCredentials('tx', { cookie })
|
||||
const { openId, accessToken, refreshToken, refreshKey } = request.body
|
||||
setCredentials('tx', { cookie, uin, openId, accessToken, refreshToken, refreshKey })
|
||||
return { logged: true, uin }
|
||||
})
|
||||
|
||||
@@ -136,19 +143,30 @@ export const loginRoutes = async(fastify) => {
|
||||
|
||||
// ---------- 通用 ----------
|
||||
|
||||
fastify.post('/login/refresh', {
|
||||
schema: {
|
||||
summary: '手动触发登录态续期',
|
||||
description: '对已登录的 kg/tx 执行 token/cookie 续期(与自动任务同一逻辑)。' +
|
||||
'未登录的平台跳过;失败的平台在对应字段返回 error,不影响其他平台。',
|
||||
tags: ['登录'],
|
||||
},
|
||||
}, async() => refreshAll())
|
||||
|
||||
fastify.get('/login/status', {
|
||||
schema: {
|
||||
summary: '查看登录状态',
|
||||
description: 'wy 为实时校验(未登录或 cookie 失效返回 logged: false),kg/tx 为已存凭据。',
|
||||
description: 'wy 为实时校验(未登录或 cookie 失效返回 logged: false),kg/tx 为已存凭据(含 VIP 等级与最近更新时间)。',
|
||||
tags: ['登录'],
|
||||
},
|
||||
}, async() => {
|
||||
const [wy, kg] = await Promise.all([wyAuth.status().catch(() => null), Promise.resolve(kgAuth.status())])
|
||||
const tx = getTxAuth()
|
||||
const txCred = getCredentials('tx') as { vipType?: string; updatedAt?: string } | null
|
||||
const kgCred = getCredentials('kg') as { updatedAt?: string } | null
|
||||
return {
|
||||
wy: wy ? { logged: true, ...wy } : { logged: false },
|
||||
kg: kg ? { logged: true, ...kg } : { logged: false },
|
||||
tx: tx ? { logged: true, uin: tx.uin } : { logged: false },
|
||||
kg: kg ? { logged: true, ...kg, updatedAt: kgCred?.updatedAt } : { logged: false },
|
||||
tx: tx ? { logged: true, uin: tx.uin, vipType: txCred?.vipType ?? null, updatedAt: txCred?.updatedAt } : { logged: false },
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user