diff --git a/lib/routes/bilibili/cache.ts b/lib/routes/bilibili/cache.ts index cd8502cb00..b69dc73d08 100644 --- a/lib/routes/bilibili/cache.ts +++ b/lib/routes/bilibili/cache.ts @@ -204,6 +204,73 @@ const getCidFromId = (aid, pid, bvid) => { }); }; +interface SubtitleEntry { + from: number; + to: number; + sid: number; + content: string; + music: number; +} + +function secondsToSrtTime(seconds: number): string { + const date = new Date(seconds * 1000); + const hh = String(date.getUTCHours()).padStart(2, '0'); + const mm = String(date.getUTCMinutes()).padStart(2, '0'); + const ss = String(date.getUTCSeconds()).padStart(2, '0'); + const ms = String(date.getUTCMilliseconds()).padStart(3, '0'); + return `${hh}:${mm}:${ss},${ms}`; +} + +function convertJsonToSrt(body: SubtitleEntry[]): string { + return body + .map((item, index) => { + const start = secondsToSrtTime(item.from); + const end = secondsToSrtTime(item.to); + return `${index + 1}\n${start} --> ${end}\n${item.content}\n`; + }) + .join('\n'); +} + +const getVideoSubtitle = async ( + bvid: string +): Promise< + Array<{ + content: string; + lan_doc: string; + }> +> => { + if (!bvid) { + return []; + } + + const cid = await getCidFromId(undefined, 1, bvid); + const cookie = await getCookie(); + return cache.tryGet(`bili-video-subtitle-${bvid}`, async () => { + const response = await got(`https://api.bilibili.com/x/player/wbi/v2?bvid=${bvid}&cid=${cid}`, { + headers: { + Referer: `https://www.bilibili.com/video/${bvid}`, + Cookie: cookie, + }, + }); + + const subtitles = response?.data?.data?.subtitle?.subtitles || []; + + return await Promise.all( + subtitles.map(async (subtitle) => { + const url = `https:${subtitle.subtitle_url}`; + const subtitleData = await cache.tryGet(url, async () => { + const subtitleResponse = await got(url); + return convertJsonToSrt(subtitleResponse?.data?.body || []); + }); + return { + content: subtitleData, + lan_doc: subtitle.lan_doc, + }; + }) + ); + }); +}; + const getAidFromBvid = async (bvid) => { const key = `bili-cid-from-bvid-${bvid}`; let aid = await cache.get(key); @@ -285,4 +352,5 @@ export default { getAidFromBvid, getArticleDataFromCvid, getRenderData, + getVideoSubtitle, }; diff --git a/lib/routes/bilibili/video.ts b/lib/routes/bilibili/video.ts index 80d9425a63..edb71460c4 100644 --- a/lib/routes/bilibili/video.ts +++ b/lib/routes/bilibili/video.ts @@ -1,13 +1,9 @@ -import type { Context } from 'hono'; -import JSONbig from 'json-bigint'; -import { DataItem, Route, ViewType } from '@/types'; +import { Route, ViewType } from '@/types'; import got from '@/utils/got'; -import { parseDate } from '@/utils/parse-date'; +import logger from '@/utils/logger'; +import type { Context } from 'hono'; import cache from './cache'; import utils, { getVideoUrl } from './utils'; -import logger from '@/utils/logger'; -import type { BilibiliWebDynamicResponse } from './api-interface'; -import { parseDuration } from '@/utils/helpers'; export const route: Route = { path: '/user/video/:uid/:embed?', @@ -55,53 +51,9 @@ async function handler(ctx: Context) { }, }); const data = response.data; - let fallbackItem: DataItem[] | undefined; if (data.code) { - try { - // Fallback to get video from dynamic API - const params = utils.addDmVerifyInfo(`host_mid=${uid}&platform=web&features=itemOpusStyle,listOnlyfans,opusBigCover,onlyfansVote`, utils.getDmImgList()); - const response = await got(`https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space?${params}`, { - headers: { - Referer: `https://space.bilibili.com/${uid}/`, - Cookie: cookie, - }, - }); - const body = JSONbig.parse(response.body); - if (body?.code === -352) { - throw new Error('Request failed, please try again.'); - } - const items = (body as BilibiliWebDynamicResponse)?.data?.items.filter((item) => item.modules.module_dynamic?.major?.type === 'MAJOR_TYPE_ARCHIVE'); - if (items && items.length > 0) { - fallbackItem = items.map((item) => { - const dynamic = item?.modules?.module_dynamic?.major?.archive; - const author = item?.modules?.module_author; - - const aid = dynamic?.aid; - const bvid = dynamic?.bvid; - - const data: DataItem = { - title: dynamic?.title ?? '', - description: !aid && !bvid ? '' : utils.renderUGCDescription(embed, dynamic.cover, dynamic.desc, aid, undefined, bvid), - pubDate: author?.pub_ts ? parseDate(author.pub_ts, 'X') : undefined, - link: author?.pub_ts && author.pub_ts > utils.bvidTime && bvid ? `https://www.bilibili.com/video/${bvid}` : `https://www.bilibili.com/video/av${aid}`, - author: name ?? undefined, - attachments: bvid - ? [ - { - url: getVideoUrl(bvid), - mime_type: 'text/html', - duration_in_seconds: parseDuration(dynamic?.duration_text), - }, - ] - : undefined, - }; - return data; - }); - } - } catch { - logger.error(JSON.stringify(data.data)); - throw new Error(`Got error code ${data.code} while fetching: ${data.message}`); - } + logger.error(JSON.stringify(data.data)); + throw new Error(`Got error code ${data.code} while fetching: ${data.message}`); } return { @@ -112,25 +64,34 @@ async function handler(ctx: Context) { logo: face ?? undefined, icon: face ?? undefined, item: - fallbackItem ?? - (data.data && - data.data.list && - data.data.list.vlist && - data.data.list.vlist.map((item) => ({ - title: item.title, - description: utils.renderUGCDescription(embed, item.pic, item.description, item.aid, undefined, item.bvid), - pubDate: new Date(item.created * 1000).toUTCString(), - link: item.created > utils.bvidTime && item.bvid ? `https://www.bilibili.com/video/${item.bvid}` : `https://www.bilibili.com/video/av${item.aid}`, - author: name, - comments: item.comment, - attachments: item.bvid - ? [ - { - url: getVideoUrl(item.bvid), - mime_type: 'text/html', - }, - ] - : undefined, - }))), + data.data && + data.data.list && + data.data.list.vlist && + (await Promise.all( + data.data.list.vlist.map(async (item) => { + const subtitles = await cache.getVideoSubtitle(item.bvid); + return { + title: item.title, + description: utils.renderUGCDescription(embed, item.pic, item.description, item.aid, undefined, item.bvid), + pubDate: new Date(item.created * 1000).toUTCString(), + link: item.created > utils.bvidTime && item.bvid ? `https://www.bilibili.com/video/${item.bvid}` : `https://www.bilibili.com/video/av${item.aid}`, + author: name, + comments: item.comment, + attachments: item.bvid + ? [ + { + url: getVideoUrl(item.bvid), + mime_type: 'text/html', + }, + ...subtitles.map((subtitle) => ({ + url: `data:text/plain;charset=utf-8,${encodeURIComponent(subtitle.content)}`, + mime_type: 'text/srt', + title: `字幕 - ${subtitle.lan_doc}`, + })), + ] + : undefined, + }; + }) + )), }; }