159 lines
3.6 KiB
TypeScript
159 lines
3.6 KiB
TypeScript
/**
|
|
* Bilibili 搜索接口
|
|
* 代码参考 https://github.com/ljk743121/Sound-of-experiment/blob/v4/server/utils/plugins/bilibili.ts
|
|
*/
|
|
import { defineEventHandler, getQuery, createError } from 'h3'
|
|
import xss from 'xss'
|
|
|
|
interface SongInfo {
|
|
id: number
|
|
bvid: string
|
|
title: string
|
|
author: string
|
|
pic: string
|
|
duration: string
|
|
}
|
|
|
|
interface VideoPage {
|
|
cid: number
|
|
page: number
|
|
part: string
|
|
duration: number
|
|
}
|
|
|
|
interface VideoInfoRes {
|
|
code: number
|
|
message: string
|
|
data: {
|
|
pages: VideoPage[]
|
|
}
|
|
}
|
|
|
|
interface SearchRes {
|
|
code: number
|
|
message: string
|
|
data: {
|
|
result: [
|
|
{
|
|
id: number
|
|
bvid: string
|
|
title: string
|
|
author: string
|
|
pic: string
|
|
duration: string
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
function htmlToPlainText(value: string) {
|
|
return xss(value, {
|
|
whiteList: {},
|
|
stripIgnoreTag: true,
|
|
stripIgnoreTagBody: ['script', 'style']
|
|
})
|
|
}
|
|
|
|
function bi_convert_song(song_info: SongInfo, pages?: VideoPage[]) {
|
|
let imgUrl = song_info.pic
|
|
const durationStr = song_info.duration
|
|
.split(':')
|
|
.map((x) => Number.parseInt(x))
|
|
.reverse()
|
|
let duration = durationStr[0] + durationStr[1] * 60
|
|
if (durationStr.length === 3) {
|
|
duration += durationStr[2] * 60 * 60
|
|
}
|
|
if (imgUrl.startsWith('//')) {
|
|
imgUrl = `https:${imgUrl}`
|
|
}
|
|
const track = {
|
|
id: song_info.bvid,
|
|
title: htmlToPlainText(song_info.title),
|
|
artist: htmlToPlainText(song_info.author),
|
|
source: 'bilibili',
|
|
musicPlatform: 'bilibili',
|
|
cover: imgUrl,
|
|
duration,
|
|
album: 'Bilibili Video',
|
|
pages: pages || []
|
|
}
|
|
return track
|
|
}
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const query = getQuery(event)
|
|
const keyword = query.keyword as string
|
|
|
|
if (!keyword) {
|
|
return []
|
|
}
|
|
|
|
const target_url = `https://api.bilibili.com/x/web-interface/search/type`
|
|
|
|
try {
|
|
const resp = await $fetch<SearchRes>(target_url, {
|
|
method: 'GET',
|
|
params: {
|
|
__refresh__: true,
|
|
page: 1,
|
|
page_size: 15,
|
|
platform: 'pc',
|
|
highlight: 1,
|
|
single_column: 0,
|
|
keyword,
|
|
search_type: 'video',
|
|
dynamic_offset: 0,
|
|
preload: true,
|
|
com2co: true
|
|
},
|
|
headers: {
|
|
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 (!resp.data?.result) {
|
|
return []
|
|
}
|
|
|
|
const results = await Promise.all(
|
|
resp.data.result.map(async (song) => {
|
|
try {
|
|
const videoInfoResp = await $fetch<VideoInfoRes>(
|
|
`https://api.bilibili.com/x/web-interface/view`,
|
|
{
|
|
method: 'GET',
|
|
params: {
|
|
bvid: song.bvid
|
|
},
|
|
headers: {
|
|
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'
|
|
}
|
|
}
|
|
)
|
|
|
|
const pages = videoInfoResp.data?.pages || []
|
|
return bi_convert_song(song, pages)
|
|
} catch (error) {
|
|
console.error(`Failed to fetch video info for ${song.bvid}:`, error)
|
|
return bi_convert_song(song)
|
|
}
|
|
})
|
|
)
|
|
|
|
return results
|
|
} catch (error: any) {
|
|
console.error('Bilibili search error:', error)
|
|
throw createError({
|
|
statusCode: 500,
|
|
message: error.message || 'Bilibili search failed'
|
|
})
|
|
}
|
|
})
|