feat: align with upstream 1.5.9.9
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
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 音乐播放链接解析失败'
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user