Files
BYW-VoiceHub/server/api/bilibili/playurl.get.ts
T

115 lines
3.6 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.
/**
* Bilibili 播放链接获取接口
* 代码参考 https://github.com/rio4raki/Bilibili-audio-extraction 优化
* 采用 platform=html5 获取对浏览器 <audio> 标签兼容性最好的直链
* 同时转发客户端 IP,解决海外服务器获取到错误 CDN 节点导致访问慢的问题
*/
import { defineEventHandler, getQuery } from 'h3'
import { createApiError } from '~~/server/utils/apiError'
import { SERVER_ERROR_CODES } from '~~/server/config/constants'
import { getClientIP } from '~~/server/utils/ip-utils'
interface NoRefererPlayUrlRes {
code: number
message: string
data: {
durl: [
{
url: string
}
]
}
}
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const bvid = query.id as string
const cid = query.cid as string
if (!bvid) {
throw createApiError(400, SERVER_ERROR_CODES.COMMON_INVALID_PARAMS, '缺少 id 参数')
}
// 提取客户端真实 IP,用于转发给 Bilibili 接口,以便分配最快 CDN 节点
const clientIp = getClientIP(event)
const headers: Record<string, string> = {
Cookie: 'buvid3=0',
Referer: 'https://www.bilibili.com/',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
if (clientIp !== 'unknown') {
headers['X-Forwarded-For'] = clientIp
headers['X-Real-IP'] = clientIp
headers['Client-IP'] = clientIp
}
try {
let finalCid = cid
if (!finalCid) {
const target_url = 'https://api.bilibili.com/x/web-interface/view'
const resp1: any = await $fetch(target_url, {
method: 'GET',
params: { bvid },
headers,
timeout: 10000
})
if (!resp1?.data?.pages?.[0]?.cid) {
console.error('[Bilibili] CID 获取失败,响应:', JSON.stringify(resp1).slice(0, 300))
// 降级:去掉 Cookie 重试,部分场景 Cookie 反而触发风控
try {
const fallback: any = await $fetch(target_url, {
method: 'GET',
params: { bvid },
headers: {
Referer: 'https://www.bilibili.com/',
'User-Agent': headers['User-Agent']
},
timeout: 10000
})
if (fallback?.data?.pages?.[0]?.cid) {
finalCid = fallback.data.pages[0].cid
} else {
console.error('[Bilibili] 降级请求仍无 CID,响应:', JSON.stringify(fallback).slice(0, 300))
throw new Error('获取 CID 失败')
}
} catch (fallbackErr: any) {
throw new Error(`获取 CID 失败: ${fallbackErr?.message || '未知错误'}`)
}
} else {
finalCid = resp1.data.pages[0].cid
}
}
// 使用 platform=html5 参数绕过严格防盗链验证(允许前端使用 referrerpolicy="no-referrer")
const target_url2 = 'https://api.bilibili.com/x/player/playurl'
const resp2 = await $fetch<NoRefererPlayUrlRes>(target_url2, {
method: 'GET',
params: {
fnval: 1,
platform: 'html5',
high_quality: 1,
bvid,
cid: finalCid
},
headers,
timeout: 10000
})
if (resp2.data?.durl?.length > 0) {
const url = resp2.data.durl[0].url
return { url, pay: false }
} else {
throw new Error(`获取歌曲链接失败: ${resp2.message || '未知错误'}`)
}
} catch (error: any) {
console.error('Bilibili playurl error:', error)
throw createApiError(500, SERVER_ERROR_CODES.BILIBILI_PLAYURL_FAILED, error.message || '获取 Bilibili 音频链接失败')
}
})