This commit is contained in:
Developer
2026-03-11 20:53:18 +08:00
parent 4e8beae209
commit 9347c8752d
12 changed files with 836 additions and 261 deletions

View File

@@ -1,13 +1,14 @@
import axios from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Platform } from 'react-native';
import pako from 'pako';
import type { VideoItem, Comment, PlayUrlResponse, QRCodeInfo, VideoShotData, HeatmapResponse, DanmakuItem } from './types';
import { signWbi } from '../utils/wbi';
import { parseDanmakuXml } from '../utils/danmaku';
const isWeb = Platform.OS === 'web';
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
const BASE = isWeb ? 'http://localhost:3001/bilibili-api' : 'https://api.bilibili.com';
const BASE = isWeb ? 'http://localhost:3001/bilibili-api' : 'https://api.bilibili.com';
const PASSPORT = isWeb ? 'http://localhost:3001/bilibili-passport' : 'https://passport.bilibili.com';
const COMMENT_BASE = isWeb
? 'http://localhost:3001/bilibili-comment'
@@ -32,13 +33,13 @@ const api = axios.create({
baseURL: BASE,
timeout: 10000,
headers: isWeb ? {
'Accept': 'application/json, text/plain, */*',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9',
} : {
'User-Agent': UA,
'Referer': 'https://www.bilibili.com',
'Origin': 'https://www.bilibili.com',
'Accept': 'application/json, text/plain, */*',
'User-Agent': UA,
'Referer': 'https://www.bilibili.com',
'Origin': 'https://www.bilibili.com',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9',
},
});
@@ -50,7 +51,7 @@ api.interceptors.request.use(async (config) => {
]);
if (isWeb) {
// Browsers block Cookie/Referer/Origin headers; relay via custom headers to proxy
if (buvid3) config.headers['X-Buvid3'] = buvid3;
if (buvid3) config.headers['X-Buvid3'] = buvid3;
if (sessdata) config.headers['X-Sessdata'] = sessdata;
} else {
const cookies: string[] = [`buvid3=${buvid3}`];
@@ -75,7 +76,7 @@ async function getWbiKeys(): Promise<{ imgKey: string; subKey: string }> {
export async function getRecommendFeed(freshIdx = 0): Promise<VideoItem[]> {
const { imgKey, subKey } = await getWbiKeys();
const signed = signWbi(
{ fresh_type: 3, fresh_idx: freshIdx, fresh_idx_1h: freshIdx, ps: 12, feed_version: 'V8' },
{ fresh_type: 3, fresh_idx: freshIdx, fresh_idx_1h: freshIdx, ps: 21, feed_version: 'V8' },
imgKey,
subKey,
);
@@ -87,8 +88,7 @@ export async function getRecommendFeed(freshIdx = 0): Promise<VideoItem[]> {
aid: item.id ?? item.aid,
pic: item.pic ?? item.cover,
owner: item.owner ?? { mid: 0, name: item.owner_info?.name ?? '', face: item.owner_info?.face ?? '' },
}))
.filter((item: any) => item.bvid && (item.pic || item.cover) && item.duration > 0) as VideoItem[];
})) as VideoItem[];
}
export async function getPopularVideos(pn = 1): Promise<VideoItem[]> {
@@ -172,12 +172,44 @@ export async function pollQRCode(qrcode_key: string): Promise<{ code: number; co
return { code, cookie };
}
export async function getDanmaku(cid: number): Promise<DanmakuItem[]> {
try {
if (isWeb) {
// web 走代理,代理已解压,直接拿文本
const res = await axios.get(`${COMMENT_BASE}/${cid}.xml`, {
headers: {},
responseType: 'text',
});
return parseDanmakuXml(res.data);
}
// Native 策略 1responseType: 'text',依赖 OkHttp 自动解压
const res = await axios.get(`${COMMENT_BASE}/${cid}.xml`, {
headers: isWeb ? {} : { Referer: 'https://www.bilibili.com', 'User-Agent': UA },
headers: { Referer: 'https://www.bilibili.com', 'User-Agent': UA },
responseType: 'text',
});
return parseDanmakuXml(res.data as string);
} catch { return []; }
if (typeof res.data === 'string' && res.data.includes('<d ')) {
return parseDanmakuXml(res.data);
}
// 策略 2 回退arraybuffer + pako 手动解压
const res2 = await axios.get(`${COMMENT_BASE}/${cid}.xml`, {
headers: { Referer: 'https://www.bilibili.com', 'User-Agent': UA },
responseType: 'arraybuffer',
});
const bytes = new Uint8Array(res2.data as ArrayBuffer);
let xmlText: string;
if (bytes[0] === 0x1f && bytes[1] === 0x8b) {
xmlText = pako.inflate(bytes, { to: 'string' });
} else {
xmlText = new TextDecoder('utf-8').decode(bytes);
}
return parseDanmakuXml(xmlText);
} catch (e) {
console.warn('getDanmaku failed:', e);
return [];
}
}