Files
BYW-VoiceHub/server/api/music/resolve-url.post.ts
T

151 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { upgradeTxAudioUrl } from '~~/server/utils/native_tx'
import {
getQqCookieDiagnostic,
normalizeQqCookie,
resolveQqOfficialPlayUrl,
resolveQqSdkPlayUrl
} from '~~/server/utils/qq_music_sdk'
const HYW_TX_URL = 'http://103.79.184.97/api/music/url'
const HYW_CARD_KEY = 'PYPW-QFRL-3DBF-95O6'
const resolveTxWithHyw = async (songmid: string, quality: unknown) => {
const qualityMap: Record<string, string> = {
'4': '128k',
'8': '320k',
'10': 'flac',
'11': 'master',
'14': 'master'
}
const query = new URLSearchParams({
source: 'tx',
songId: songmid,
songmid,
platform: 'tx',
quality: qualityMap[String(quality)] || String(quality || '128k'),
key: HYW_CARD_KEY
})
const response = await fetch(`${HYW_TX_URL}?${query.toString()}`, {
signal: AbortSignal.timeout(8000),
headers: { Accept: 'application/json', 'X-Card-Key': HYW_CARD_KEY }
})
if (!response.ok) throw new Error(`HYW 返回 ${response.status}`)
const data: any = await response.json()
const url = typeof data?.url === 'string' ? data.url.trim() : ''
if (data?.code !== 200 || !/^https?:\/\//i.test(url)) {
throw new Error(data?.message || 'HYW 未返回播放链接')
}
return upgradeTxAudioUrl(url)
}
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const platform = String(body?.platform || '').trim()
const musicId = body?.musicId
const playUrl = String(body?.playUrl || '').trim()
const cookie = normalizeQqCookie(String(body?.cookie || '').trim())
// official-first:QQ 官方 Cookie 链路;fallback:第三方兜底链路
const strategy = body?.strategy === 'official-first' ? 'official-first' : 'fallback'
const excludedSources = new Set(
Array.isArray(body?.excludeSources)
? body.excludeSources.map((item: unknown) => String(item || '').trim()).filter(Boolean)
: []
)
if (playUrl) {
return {
success: true,
url: platform === 'tencent' ? upgradeTxAudioUrl(playUrl) : playUrl,
source: 'play-url',
normalizedMusicId: musicId ? String(musicId).trim() : '',
idType: 'provided-url',
authUsed: Boolean(cookie),
authDiagnostic: getQqCookieDiagnostic(cookie)
}
}
if (platform !== 'tencent') {
throw createError({ statusCode: 400, message: '暂不支持的平台' })
}
const normalizedMusicId = String(musicId || '').trim()
// 官方 Cookie 链路:带用户登录态走 CgiGetVkey,可获取 VIP/高音质直链
if (strategy === 'official-first') {
if (excludedSources.has('qq-official')) {
throw createError({ statusCode: 502, message: 'QQ 音乐播放链接解析失败' })
}
try {
const url = await resolveQqSdkPlayUrl(
normalizedMusicId,
body?.quality,
cookie || undefined,
String(body?.mediaId || '').trim() || undefined
)
return {
success: true,
url,
source: 'qq-official',
normalizedMusicId,
idType: 'songmid',
authUsed: Boolean(cookie),
authDiagnostic: getQqCookieDiagnostic(cookie)
}
} catch (error: any) {
const sdkError = error?.message || String(error)
console.warn('[music/resolve-url] QQ 官方 SDK 解析失败,尝试原生直连:', sdkError)
// 原生直连:单文件名组合 + zzcSign 签名请求,与 SDK 的文件名策略互为校验
try {
const url = await resolveQqOfficialPlayUrl({
songmid: normalizedMusicId,
quality: body?.quality,
cookie: cookie || undefined,
mediaId: String(body?.mediaId || '').trim() || undefined
})
return {
success: true,
url,
source: 'qq-official',
normalizedMusicId,
idType: 'songmid',
authUsed: Boolean(cookie),
authDiagnostic: getQqCookieDiagnostic(cookie)
}
} catch (nativeError: any) {
const nativeErrorText = nativeError?.message || String(nativeError)
console.warn('[music/resolve-url] QQ 原生直连解析失败:', nativeErrorText)
throw createError({
statusCode: 502,
message: `QQ 官方链路解析失败:${sdkError};原生直连:${nativeErrorText}`
})
}
}
}
if (excludedSources.has('hyw-tx')) {
throw createError({ statusCode: 502, message: 'QQ 音乐播放链接解析失败' })
}
try {
const url = await resolveTxWithHyw(normalizedMusicId, body?.quality)
return {
success: true,
url,
source: 'hyw-tx',
normalizedMusicId,
idType: 'songmid',
authUsed: Boolean(cookie),
authDiagnostic: getQqCookieDiagnostic(cookie)
}
} catch (error: any) {
console.warn('[music/resolve-url] HYW QQ 回退失败:', error?.message || error)
}
throw createError({
statusCode: 502,
message: 'QQ 音乐播放链接解析失败'
})
})