151 lines
4.8 KiB
TypeScript
151 lines
4.8 KiB
TypeScript
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 音乐播放链接解析失败'
|
||
})
|
||
})
|