feat(admin+server): 酷狗对接 - 扫码绑定管理员账号 / 按排期自动创建酷狗日歌单 + 同步歌曲
数据模型:SystemSettings 新增 kugouApiBaseUrl / kugouAdminCookie / kugouAdminToken / kugouAdminUser / kugouAdminBoundAt 5 字段;新建 KuGouPlaylistSync 同步历史表(12 字段 + playDate 倒序索引)。迁移脚本:app/drizzle/migrations/20260919143000_add_kugou_integration.sql 后端 Server:新增 server/services/kugouService.ts 封装 KuGouMusicApi sidecar 转发(自动拼 Cookie / Token header);新增 6 个 admin/kugou 路由:status / qr-create / qr-check / unbind / sync-schedule-playlists / sync-history。同步核心:逐日出非草稿排期 → kgHash 直取优先 → 失败用 cloudsearch/search 按标题+歌手三级召回 → /playlist/create + /playlist/tracks op=add 每 100 首分批;写同步历史(status、added/missing、歌单 URL)。 后台 UI:新建 Admin/KuGouImporter.client.vue 三栏布局——左栏 sidecar 健康卡+部署说明+扫码绑定/解绑弹窗;中栏多选排期日期+逐日出排期预览卡+按日同步;下栏最近 100 条同步历史(7 种状态 badge + 歌单链接跳转)。dashboard.vue L105 playlist-card 挂载点由 PlaylistCardPage 替换为 KuGouImporter;权限 permissions.js 5 个 ADMIN 族角色开放 playlist-card 页面访问(接口内部绑定/同步再强校验 ADMIN/SUPER_ADMIN)。 侧car要求:部署 KuGouMusicApi(参考文档 https://kugoumusicapi-docs.4everland.app),默认本机 http://127.0.0.1:3000,可通过 SystemSettings.kugouApiBaseUrl 或环境变量 KUGOU_API_BASE 覆盖。
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { db } from '~/drizzle/db'
|
||||
import { systemSettings } from '~/drizzle/schema'
|
||||
import { CacheService } from '~~/server/services/cacheService'
|
||||
import { kuGouQrLoginCheck } from '~~/server/services/kugouService'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = event.context.user
|
||||
if (!user) throw createError({ statusCode: 401, message: '未登录' })
|
||||
if (!['ADMIN', 'SUPER_ADMIN'].includes(user.role))
|
||||
throw createError({ statusCode: 403, message: '仅管理员可绑定酷狗账号' })
|
||||
|
||||
const body = await readBody(event)
|
||||
const { qr_id, login_id, key, cookie } = body || {}
|
||||
if (!(qr_id || login_id || key)) {
|
||||
throw createError({ statusCode: 400, message: '缺少 qr_id 或 key' })
|
||||
}
|
||||
|
||||
const res = await kuGouQrLoginCheck({ qr_id, login_id, key, cookie })
|
||||
const state = Number(res?.data?.status ?? 0)
|
||||
const token = (res as any)?.data?.token || (res as any)?.token || ''
|
||||
const kugouCookie = (res as any)?.data?.cookie || (res as any)?.cookie || cookie || ''
|
||||
const kugouUser = (res as any)?.data?.userinfo || (res as any)?.userinfo || null
|
||||
|
||||
let bound = false
|
||||
if (state === 1 && (token || kugouCookie)) {
|
||||
const rows = await db.select().from(systemSettings).limit(1)
|
||||
const patch: Record<string, any> = {
|
||||
kugouAdminToken: token || null,
|
||||
kugouAdminCookie: kugouCookie || null,
|
||||
kugouAdminUser: kugouUser || null,
|
||||
kugouAdminBoundAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
if (!rows[0]) {
|
||||
await db.insert(systemSettings).values(patch as any)
|
||||
} else {
|
||||
await db.update(systemSettings).set(patch).where(eq(systemSettings.id, rows[0].id))
|
||||
}
|
||||
try {
|
||||
const cs = CacheService.getInstance()
|
||||
await cs.setSystemSettings?.({ ...(rows[0] || {}), ...patch })
|
||||
} catch {}
|
||||
bound = true
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
state, // 0 未扫码 / 1 已完成 / 2 已扫码未确认 / -1 过期
|
||||
bound,
|
||||
user: kugouUser,
|
||||
hint:
|
||||
state === 0
|
||||
? '请用酷狗 App 扫二维码'
|
||||
: state === 2
|
||||
? '扫码成功,请在手机端确认登录'
|
||||
: state === 1
|
||||
? '绑定成功'
|
||||
: state === -1
|
||||
? '二维码已过期,请刷新重试'
|
||||
: res?.error || '等待中',
|
||||
raw: res,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
import { kuGouQrLoginCreate } from '~~/server/services/kugouService'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = event.context.user
|
||||
if (!user) {
|
||||
throw createError({ statusCode: 401, message: '未登录' })
|
||||
}
|
||||
if (!['ADMIN', 'SUPER_ADMIN'].includes(user.role)) {
|
||||
throw createError({ statusCode: 403, message: '仅管理员可绑定酷狗账号' })
|
||||
}
|
||||
|
||||
const query = getQuery(event)
|
||||
const platform = (query.platform as string) || 'web'
|
||||
|
||||
try {
|
||||
const res = await kuGouQrLoginCreate(
|
||||
platform === 'lite' ? 'lite' : platform === 'android' ? 'android' : 'web',
|
||||
)
|
||||
if (res && res.status !== 1 && (res as any).errcode) {
|
||||
return {
|
||||
ok: false,
|
||||
error: (res as any).error || '二维码生成失败',
|
||||
data: null,
|
||||
}
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
data: res?.data || null,
|
||||
raw: res,
|
||||
}
|
||||
} catch (e: any) {
|
||||
throw createError({
|
||||
statusCode: 502,
|
||||
message:
|
||||
'连接酷狗 sidecar 失败:请先启动 KuGouMusicApi 服务 (默认 http://127.0.0.1:3000),详细: ' +
|
||||
(e?.message || String(e)),
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,51 @@
|
||||
import { db } from '~/drizzle/db'
|
||||
import { systemSettings } from '~/drizzle/schema'
|
||||
import { kuGouFetch } from '~~/server/services/kugouService'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = event.context.user
|
||||
if (!user) throw createError({ statusCode: 401, message: '未登录' })
|
||||
if (!['ADMIN', 'SUPER_ADMIN'].includes(user.role))
|
||||
throw createError({ statusCode: 403, message: '仅管理员可访问' })
|
||||
|
||||
const query = getQuery(event)
|
||||
const base = String(query.baseUrl || '').trim()
|
||||
|
||||
if (base && !/^https?:\/\//i.test(base)) {
|
||||
throw createError({ statusCode: 400, message: 'baseUrl 需要是合法的 http(s) URL' })
|
||||
}
|
||||
|
||||
const rows = await db.select().from(systemSettings).limit(1)
|
||||
const settings = rows[0] as any
|
||||
const usedBase = base || settings?.kugouApiBaseUrl || process.env.KUGOU_API_BASE || 'http://127.0.0.1:3000'
|
||||
|
||||
let serviceOk: boolean
|
||||
let serviceMessage: string
|
||||
let loginStatus: any = null
|
||||
try {
|
||||
const r = await kuGouFetch('/login/status', { timestamp: Date.now() })
|
||||
loginStatus = r
|
||||
serviceOk = Number((r as any)?.status ?? 0) === 1 || !!((r as any)?.data?.profile || (r as any)?.data?.account)
|
||||
serviceMessage = serviceOk ? '服务在线,且账号已绑定登录' : 'sidecar 在线,但酷狗账号未登录'
|
||||
} catch (e: any) {
|
||||
serviceOk = false
|
||||
serviceMessage =
|
||||
'无法连接 sidecar (' + usedBase + '):' + (e?.message || String(e))
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
service: {
|
||||
baseUrl: usedBase,
|
||||
serviceOk,
|
||||
serviceMessage,
|
||||
loginStatus,
|
||||
},
|
||||
bound: {
|
||||
hasToken: !!settings?.kugouAdminToken,
|
||||
hasCookie: !!settings?.kugouAdminCookie,
|
||||
boundAt: settings?.kugouAdminBoundAt || null,
|
||||
user: settings?.kugouAdminUser || null,
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,21 @@
|
||||
import { desc } from 'drizzle-orm'
|
||||
import { db } from '~/drizzle/db'
|
||||
import { kuGouPlaylistSyncs } from '~/drizzle/schema'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = event.context.user
|
||||
if (!user) throw createError({ statusCode: 401, message: '未登录' })
|
||||
if (!['ADMIN', 'SUPER_ADMIN'].includes(user.role))
|
||||
throw createError({ statusCode: 403, message: '仅管理员可查看' })
|
||||
const q = getQuery(event)
|
||||
const limit = Math.min(500, Number(q.limit || 100))
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(kuGouPlaylistSyncs)
|
||||
.orderBy(desc(kuGouPlaylistSyncs.playDate), desc(kuGouPlaylistSyncs.createdAt))
|
||||
.limit(limit)
|
||||
return {
|
||||
ok: true,
|
||||
rows,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,259 @@
|
||||
import { and, asc, eq, sql } from 'drizzle-orm'
|
||||
import { db } from '~/drizzle/db'
|
||||
import { schedules, songs, systemSettings, kuGouPlaylistSyncs } from '~/drizzle/schema'
|
||||
import { kuGouFetch } from '~~/server/services/kugouService'
|
||||
|
||||
const WEEKDAY_ZH = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||
|
||||
function pad2(n: number) {
|
||||
return n < 10 ? '0' + n : '' + n
|
||||
}
|
||||
function dayKey(d: Date) {
|
||||
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`
|
||||
}
|
||||
function parseDate(s: string | Date) {
|
||||
const d = s instanceof Date ? new Date(s) : new Date(s)
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate())
|
||||
}
|
||||
|
||||
async function readSettings() {
|
||||
const rows = await db.select().from(systemSettings).limit(1)
|
||||
return rows[0] as any
|
||||
}
|
||||
|
||||
async function fetchScheduleForDate(dayStart: Date) {
|
||||
const dayEnd = new Date(dayStart.getFullYear(), dayStart.getMonth(), dayStart.getDate() + 1)
|
||||
return await db
|
||||
.select({
|
||||
scheduleId: schedules.id,
|
||||
sequence: schedules.sequence,
|
||||
songId: schedules.songId,
|
||||
playDate: schedules.playDate,
|
||||
title: songs.title,
|
||||
singer: songs.singer,
|
||||
artist: songs.artist,
|
||||
kgHash: songs.kgHash,
|
||||
musicId: songs.musicId,
|
||||
coverUrl: songs.coverUrl,
|
||||
cover: songs.cover,
|
||||
})
|
||||
.from(schedules)
|
||||
.innerJoin(songs, eq(songs.id, schedules.songId))
|
||||
.where(and(sql`${schedules.playDate} >= ${dayStart}`, sql`${schedules.playDate} < ${dayEnd}`, eq(schedules.isDraft, false)))
|
||||
.orderBy(asc(schedules.sequence), asc(schedules.playDate))
|
||||
}
|
||||
|
||||
async function ensureLogin(settings: any) {
|
||||
if (!settings?.kugouAdminToken && !settings?.kugouAdminCookie) {
|
||||
throw createError({
|
||||
statusCode: 412,
|
||||
message: '尚未绑定酷狗管理员账号,请先在本页扫码绑定',
|
||||
})
|
||||
}
|
||||
try {
|
||||
const res = await kuGouFetch('/login/status', { timestamp: Date.now() })
|
||||
const ok = Number((res as any)?.status ?? 0) === 1 || !!(res as any)?.data?.profile || !!(res as any)?.data?.account
|
||||
if (!ok) {
|
||||
throw new Error('酷狗账号未登录或已过期,请重新扫码绑定')
|
||||
}
|
||||
} catch (e: any) {
|
||||
throw createError({
|
||||
statusCode: 412,
|
||||
message: '酷狗登录状态无效:' + (e?.message || String(e)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function searchKugouTrackFor(item: { title: string; singer?: string | null; artist?: string | null; kgHash?: string | null }) {
|
||||
const q = [item.title, item.singer || item.artist].filter(Boolean).join(' - ')
|
||||
const res = await kuGouFetch<any>('/cloudsearch', {
|
||||
keywords: q,
|
||||
limit: 10,
|
||||
timestamp: Date.now(),
|
||||
}).catch(() =>
|
||||
kuGouFetch<any>('/search', {
|
||||
keywords: q,
|
||||
limit: 10,
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
)
|
||||
const list: any[] =
|
||||
(res as any)?.result?.songs ||
|
||||
(res as any)?.data?.info ||
|
||||
(res as any)?.songs ||
|
||||
[]
|
||||
if (!list.length) return null
|
||||
const targetL = item.title.trim().toLowerCase()
|
||||
const targetArtist = (item.singer || item.artist || '').trim().toLowerCase()
|
||||
const best =
|
||||
list.find((it) => {
|
||||
const nameL = (it.name || it.fileName || it.songname || '').toString().trim().toLowerCase()
|
||||
const artistL = ((it.ar && it.ar[0]?.name) || it.singerName || it.singername || '')
|
||||
.toString()
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
return nameL === targetL && (!targetArtist || artistL.includes(targetArtist))
|
||||
}) || list[0]
|
||||
const name = best.name || best.fileName || best.songname || item.title
|
||||
const hash = best.hash || best.fileHash || ''
|
||||
const albumId = best.album?.id ?? best.album_id ?? best.albumId ?? ''
|
||||
const audioId = best.id ?? best.album_audio_id ?? best.albumAudioId ?? ''
|
||||
return {
|
||||
name,
|
||||
hash,
|
||||
albumId: String(albumId || ''),
|
||||
audioId: String(audioId || ''),
|
||||
raw: best,
|
||||
}
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = event.context.user
|
||||
if (!user) throw createError({ statusCode: 401, message: '未登录' })
|
||||
if (!['ADMIN', 'SUPER_ADMIN'].includes(user.role))
|
||||
throw createError({ statusCode: 403, message: '仅管理员可同步排期' })
|
||||
|
||||
const body = await readBody(event)
|
||||
const rawDates: (string | Date)[] = Array.isArray(body?.dates) && body.dates.length ? body.dates : []
|
||||
const playlistUrlTemplate = body.playlistUrlTemplate || 'https://www.kugou.com/yy/special/single/{listid}.html'
|
||||
if (!rawDates.length) throw createError({ statusCode: 400, message: '请选择至少一个排期日期' })
|
||||
|
||||
const settings = await readSettings()
|
||||
await ensureLogin(settings)
|
||||
|
||||
const results: any[] = []
|
||||
for (const d of rawDates) {
|
||||
const dayStart = parseDate(d as string)
|
||||
const key = dayKey(dayStart)
|
||||
const playlistName = `BYW广播 ${key} (${WEEKDAY_ZH[dayStart.getDay()]})`
|
||||
const scheduled = await fetchScheduleForDate(dayStart)
|
||||
const totalSongs = scheduled.length
|
||||
let playlistId = ''
|
||||
let playlistUrl = ''
|
||||
const missing: any[] = []
|
||||
const addBatches: string[] = []
|
||||
let added = 0
|
||||
let status = 'pending'
|
||||
let error = ''
|
||||
let rawCreate: any = null
|
||||
let rawAdd: any = null
|
||||
|
||||
try {
|
||||
if (!totalSongs) {
|
||||
status = 'skipped_empty'
|
||||
error = '当日没有已发布排期的歌曲'
|
||||
} else {
|
||||
const createRes = await kuGouFetch<any>('/playlist/create', {
|
||||
name: playlistName,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
rawCreate = createRes
|
||||
const createOk = Number((createRes as any)?.code ?? 0) === 200 || Number((createRes as any)?.status ?? 0) === 1
|
||||
const listid =
|
||||
(createRes as any)?.playlist?.id ||
|
||||
(createRes as any)?.data?.id ||
|
||||
(createRes as any)?.id ||
|
||||
(createRes as any)?.listid ||
|
||||
''
|
||||
if (!createOk || !listid) {
|
||||
status = 'failed_create'
|
||||
error =
|
||||
(createRes as any)?.message ||
|
||||
(createRes as any)?.error ||
|
||||
'歌单创建失败,请检查 sidecar / 酷狗账号权限'
|
||||
} else {
|
||||
playlistId = String(listid)
|
||||
playlistUrl = playlistUrlTemplate.replace('{listid}', playlistId)
|
||||
const tracks: string[] = []
|
||||
for (const s of scheduled) {
|
||||
// 先直接用 kgHash / musicId 构造 track;没有就搜索
|
||||
const hash = s.kgHash || ''
|
||||
const name = s.title
|
||||
const albId = ''
|
||||
const audioId = s.musicId || ''
|
||||
if (hash) {
|
||||
tracks.push([name, hash, albId, audioId].join('|'))
|
||||
} else {
|
||||
const hit = await searchKugouTrackFor(s)
|
||||
if (hit && hit.hash) tracks.push([hit.name, hit.hash, hit.albumId, hit.audioId].join('|'))
|
||||
else
|
||||
missing.push({
|
||||
songId: s.songId,
|
||||
title: s.title,
|
||||
singer: s.singer || s.artist,
|
||||
reason: '酷狗未匹配到可加歌曲',
|
||||
})
|
||||
}
|
||||
}
|
||||
added = tracks.length
|
||||
const CHUNK = 100
|
||||
for (let i = 0; i < tracks.length; i += CHUNK) {
|
||||
const data = tracks.slice(i, i + CHUNK).join(',')
|
||||
const r = await kuGouFetch<any>('/playlist/tracks', {
|
||||
op: 'add',
|
||||
pid: playlistId,
|
||||
trackIds: tracks[i],
|
||||
data,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
rawAdd = rawAdd || []
|
||||
rawAdd.push(r)
|
||||
const ok = Number((r as any)?.code ?? 0) === 200 || Number((r as any)?.status ?? 0) === 1
|
||||
if (!ok) {
|
||||
status = 'partial_add_failed'
|
||||
error = (r as any)?.message || (r as any)?.error || '歌单添加歌曲失败'
|
||||
break
|
||||
}
|
||||
addBatches.push(`${i + 1}-${Math.min(i + CHUNK, tracks.length)}`)
|
||||
}
|
||||
if (status === 'pending') status = missing.length ? 'partial' : 'success'
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
status = 'error'
|
||||
error = e?.message || String(e)
|
||||
}
|
||||
|
||||
const inserted = (
|
||||
await db
|
||||
.insert(kuGouPlaylistSyncs)
|
||||
.values({
|
||||
playDate: dayStart,
|
||||
playlistName,
|
||||
playlistId: playlistId || null,
|
||||
playlistUrl: playlistUrl || null,
|
||||
totalSongs,
|
||||
addedSongs: added,
|
||||
missingSongs: missing as any,
|
||||
status,
|
||||
error: error || null,
|
||||
triggeredByUserId: Number(user.id) || null,
|
||||
rawResponse: {
|
||||
create: rawCreate,
|
||||
add: rawAdd,
|
||||
batches: addBatches,
|
||||
} as any,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.returning()
|
||||
)[0]
|
||||
results.push({
|
||||
id: inserted.id,
|
||||
date: key,
|
||||
playlistName,
|
||||
playlistId,
|
||||
playlistUrl,
|
||||
totalSongs,
|
||||
addedSongs: added,
|
||||
missingSongs: missing,
|
||||
status,
|
||||
error,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
results,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,28 @@
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { db } from '~/drizzle/db'
|
||||
import { systemSettings } from '~/drizzle/schema'
|
||||
import { CacheService } from '~~/server/services/cacheService'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = event.context.user
|
||||
if (!user) throw createError({ statusCode: 401, message: '未登录' })
|
||||
if (!['ADMIN', 'SUPER_ADMIN'].includes(user.role))
|
||||
throw createError({ statusCode: 403, message: '仅管理员可解绑酷狗账号' })
|
||||
|
||||
const rows = await db.select().from(systemSettings).limit(1)
|
||||
const patch: Record<string, any> = {
|
||||
kugouAdminToken: null,
|
||||
kugouAdminCookie: null,
|
||||
kugouAdminUser: null,
|
||||
kugouAdminBoundAt: null,
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
if (rows[0]) {
|
||||
await db.update(systemSettings).set(patch).where(eq(systemSettings.id, rows[0].id))
|
||||
}
|
||||
try {
|
||||
const cs = CacheService.getInstance()
|
||||
if (rows[0]) await cs.setSystemSettings?.({ ...rows[0], ...patch })
|
||||
} catch (_) {}
|
||||
return { ok: true }
|
||||
})
|
||||
@@ -0,0 +1,102 @@
|
||||
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 || 'http://127.0.0.1:3000'
|
||||
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',
|
||||
}
|
||||
const cookie = options?.forceCookie || settings?.kugouAdminCookie
|
||||
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
|
||||
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 },
|
||||
)
|
||||
}
|
||||
@@ -22,11 +22,11 @@ try {
|
||||
if (!ROLES.USER) {
|
||||
ROLES = {
|
||||
USER: { displayName: '学生(普通用户)', pages: [], canAccessAdmin: false },
|
||||
SONG_ADMIN: { displayName: '工作人员', pages: ['schedule', 'songs', 'print'], canAccessAdmin: true },
|
||||
VICE_MINISTER: { displayName: '副部', pages: ['overview', 'schedule', 'songs', 'print', 'users'], canAccessAdmin: true },
|
||||
ADMIN: { displayName: '部长', pages: ['overview', 'schedule', 'songs', 'print', 'users', 'data-analysis', 'notifications', 'playtimes', 'semesters'], canAccessAdmin: true },
|
||||
RESIDENT: { displayName: '常驻', pages: ['schedule', 'songs', 'print'], canAccessAdmin: true },
|
||||
SUPER_ADMIN: { displayName: '技术运维', pages: ['overview', 'workflow-tutorial', 'schedule', 'songs', 'print', 'users', 'roles', 'blacklist', 'api-keys', 'notifications', 'smtp-config', 'playtimes', 'request-times', 'semesters', 'settings', 'site-config', 'database', 'data-analysis'], canAccessAdmin: true }
|
||||
SONG_ADMIN: { displayName: '工作人员', pages: ['schedule', 'songs', 'print', 'playlist-card'], canAccessAdmin: true },
|
||||
VICE_MINISTER: { displayName: '副部', pages: ['overview', 'schedule', 'songs', 'print', 'playlist-card', 'users'], canAccessAdmin: true },
|
||||
ADMIN: { displayName: '部长', pages: ['overview', 'schedule', 'songs', 'print', 'playlist-card', 'users', 'data-analysis', 'notifications', 'playtimes', 'semesters'], canAccessAdmin: true },
|
||||
RESIDENT: { displayName: '常驻', pages: ['schedule', 'songs', 'print', 'playlist-card'], canAccessAdmin: true },
|
||||
SUPER_ADMIN: { displayName: '技术运维', pages: ['overview', 'workflow-tutorial', 'schedule', 'songs', 'print', 'playlist-card', 'users', 'roles', 'blacklist', 'api-keys', 'notifications', 'smtp-config', 'playtimes', 'request-times', 'semesters', 'settings', 'site-config', 'database', 'data-analysis'], canAccessAdmin: true }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user