站长问「能不能跳转到酷狗 App 登录」—— 平台不允许(小程序无法启动任意原生 App),
而且即使跳过去,App 内的登录态也只在那台手机上,不会回传给服务端。
所以真正的便利做法是「凭证配一次、长期复用」:
- kugouService.kuGouFetch:cookie/token 增加 KUGOU_ADMIN_COOKIE / KUGOU_ADMIN_TOKEN
兜底读环境变量;优先级 显式传入 > 库里绑定 > 环境变量
(库里绑定的优先,所以之后想临时换一份 Cookie 仍可在后台粘一次覆盖)
- status.get.ts:bound 增加 source: 'db' | 'env' | 'none',界面能说清凭证从哪来
- deploy.yml:KUGOU_ADMIN_COOKIE / KUGOU_ADMIN_TOKEN 三处齐全
(step env: 映射 + ENV_PAYLOAD export + upsert_env_key),并用 ${VAR:-} 兜底 ——
少任何一处都会像上次那样 unbound variable 直接把部署打挂
已用脚本校验:payload 占位符/参数 23/23 对应、远端脚本 30 个变量都有来源、
两个新变量三处齐全。
111 lines
3.1 KiB
TypeScript
111 lines
3.1 KiB
TypeScript
import { ofetch } from 'ofetch'
|
|
import { db } from '~/drizzle/db'
|
|
import { systemSettings } from '~/drizzle/schema'
|
|
import { CacheService } from './cacheService'
|
|
|
|
export interface KuGouQrLoginStep1 {
|
|
status?: number
|
|
data?: {
|
|
qr_id?: string
|
|
login_id?: string
|
|
qrimg?: string
|
|
img?: string
|
|
extra?: any
|
|
}
|
|
error?: string
|
|
errcode?: number
|
|
}
|
|
|
|
export interface KuGouQrLoginStep2 {
|
|
status?: number
|
|
data?: {
|
|
status?: number
|
|
token?: string
|
|
cookie?: string
|
|
userinfo?: any
|
|
auth_params?: any
|
|
}
|
|
error?: string
|
|
errcode?: number
|
|
message?: string
|
|
}
|
|
|
|
let cacheService: CacheService | null = null
|
|
function getCache() {
|
|
if (!cacheService) cacheService = CacheService.getInstance()
|
|
return cacheService
|
|
}
|
|
|
|
async function readAdminSettings() {
|
|
try {
|
|
const c = getCache()
|
|
const cached = await c.getSystemSettings?.()
|
|
if (cached && (cached.kugouApiBaseUrl || cached.kugouAdminToken || cached.kugouAdminCookie)) {
|
|
return cached
|
|
}
|
|
} catch (_) {}
|
|
const rows = await db.select().from(systemSettings).limit(1)
|
|
return rows[0] || null
|
|
}
|
|
|
|
export interface KuGouCallOptions {
|
|
forceCookie?: string
|
|
forceToken?: string
|
|
}
|
|
|
|
export async function kuGouFetch<T = any>(
|
|
path: string,
|
|
query?: Record<string, any>,
|
|
options?: KuGouCallOptions,
|
|
): Promise<T> {
|
|
const settings = (await readAdminSettings()) as any
|
|
const base = (options?.forceToken ? null : settings?.kugouApiBaseUrl) || process.env.KUGOU_API_BASE || ''
|
|
const headers: Record<string, string> = {
|
|
'User-Agent':
|
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128 Safari/537.36',
|
|
}
|
|
/*
|
|
* 凭证优先级:显式传入(bind-cookie 校验用) → 库里绑定的 → **环境变量兜底**。
|
|
*
|
|
* 加环境变量这一层是为了"配一次永久用":站长可以只在服务器 .env(或 Gitea Secrets)里
|
|
* 放一次 KUGOU_ADMIN_COOKIE,就不必再进小程序粘贴;库里绑定的值优先,所以
|
|
* 之后想临时换一份 Cookie,仍然可以在后台粘一次覆盖。
|
|
*/
|
|
const cookie = options?.forceCookie || settings?.kugouAdminCookie || process.env.KUGOU_ADMIN_COOKIE || ''
|
|
if (cookie) headers.Cookie = cookie
|
|
const url = base + path
|
|
return await ofetch<T>(url, {
|
|
query,
|
|
headers,
|
|
retry: 1,
|
|
timeout: 20000,
|
|
onRequest({ options }) {
|
|
const token =
|
|
options?.forceToken || settings?.kugouAdminToken || process.env.KUGOU_ADMIN_TOKEN || ''
|
|
if (token && options.query) {
|
|
;(options.query as any).token = token
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
export async function kuGouQrLoginCreate(platform: 'web' | 'android' | 'lite' = 'web') {
|
|
return await kuGouFetch<KuGouQrLoginStep1>(
|
|
platform === 'lite' ? '/login/qr/lite/create' : '/login/qr/create',
|
|
{ timestamp: Date.now() },
|
|
)
|
|
}
|
|
|
|
export async function kuGouQrLoginCheck(params: { qr_id?: string; login_id?: string; key?: string; cookie?: string }) {
|
|
return await kuGouFetch<KuGouQrLoginStep2>(
|
|
'/login/qr/check',
|
|
{
|
|
qr_id: params.qr_id,
|
|
login_id: params.login_id,
|
|
key: params.key,
|
|
timestamp: Date.now(),
|
|
},
|
|
{ forceCookie: params.cookie },
|
|
)
|
|
}
|