mirror of
https://gh-proxy.org/https://github.com/tiajinsha/JKVideo
synced 2026-07-07 23:18:38 +08:00
1
This commit is contained in:
@@ -29,9 +29,9 @@ export function VideoCard({ item, onPress }: Props) {
|
||||
<Text style={styles.title} numberOfLines={2}>{item.title}</Text>
|
||||
<View style={styles.meta}>
|
||||
<Ionicons name="play" size={11} color="#999" />
|
||||
<Text style={styles.metaText}>{formatCount(item.stat.view)}</Text>
|
||||
<Text style={styles.metaText}>{formatCount(item.stat?.view ?? 0)}</Text>
|
||||
</View>
|
||||
<Text style={styles.owner} numberOfLines={1}>{item.owner.name}</Text>
|
||||
<Text style={styles.owner} numberOfLines={1}>{item.owner?.name ?? ''}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
@@ -1,32 +1,31 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { getPopularVideos } from '../services/bilibili';
|
||||
import { getRecommendFeed } from '../services/bilibili';
|
||||
import type { VideoItem } from '../services/types';
|
||||
|
||||
export function useVideoList() {
|
||||
const [videos, setVideos] = useState<VideoItem[]>([]);
|
||||
const [page, setPage] = useState(1);
|
||||
const [freshIdx, setFreshIdx] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
const load = useCallback(async (reset = false) => {
|
||||
if (loading) return;
|
||||
const nextPage = reset ? 1 : page;
|
||||
const idx = reset ? 0 : freshIdx;
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await getPopularVideos(nextPage);
|
||||
const data = await getRecommendFeed(idx);
|
||||
setVideos(prev => reset ? data : [...prev, ...data]);
|
||||
setPage(nextPage + 1);
|
||||
setFreshIdx(idx + 1);
|
||||
} catch (e) {
|
||||
console.error('Failed to load videos', e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
}
|
||||
}, [loading, page]);
|
||||
}, [loading, freshIdx]);
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
setRefreshing(true);
|
||||
setPage(1);
|
||||
load(true);
|
||||
}, [load]);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import axios from 'axios';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { Platform } from 'react-native';
|
||||
import type { VideoItem, Comment, PlayUrlResponse, QRCodeInfo } from './types';
|
||||
import { signWbi } from '../utils/wbi';
|
||||
|
||||
const isWeb = Platform.OS === 'web';
|
||||
const BASE = isWeb ? 'http://localhost:3001/bilibili-api' : 'https://api.bilibili.com';
|
||||
@@ -54,6 +55,35 @@ api.interceptors.request.use(async (config) => {
|
||||
return config;
|
||||
});
|
||||
|
||||
// WBI key cache (rotates ~daily, reuse within process lifetime)
|
||||
let wbiKeys: { imgKey: string; subKey: string } | null = null;
|
||||
|
||||
async function getWbiKeys(): Promise<{ imgKey: string; subKey: string }> {
|
||||
if (wbiKeys) return wbiKeys;
|
||||
const res = await api.get('/x/web-interface/nav');
|
||||
const { img_url, sub_url } = res.data.data.wbi_img;
|
||||
const extract = (url: string) => url.split('/').pop()!.replace(/\.\w+$/, '');
|
||||
wbiKeys = { imgKey: extract(img_url), subKey: extract(sub_url) };
|
||||
return wbiKeys;
|
||||
}
|
||||
|
||||
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' },
|
||||
imgKey,
|
||||
subKey,
|
||||
);
|
||||
const res = await api.get('/x/web-interface/wbi/index/top/feed/rcmd', { params: signed });
|
||||
const items: any[] = res.data.data?.item ?? [];
|
||||
return items.map(item => ({
|
||||
...item,
|
||||
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 ?? '' },
|
||||
})) as VideoItem[];
|
||||
}
|
||||
|
||||
export async function getPopularVideos(pn = 1): Promise<VideoItem[]> {
|
||||
const res = await api.get('/x/web-interface/popular', { params: { pn, ps: 20 } });
|
||||
return res.data.data.list as VideoItem[];
|
||||
|
||||
@@ -8,17 +8,17 @@ export interface VideoItem {
|
||||
name: string;
|
||||
face: string;
|
||||
};
|
||||
stat: {
|
||||
stat?: {
|
||||
view: number;
|
||||
danmaku: number;
|
||||
reply: number;
|
||||
like: number;
|
||||
coin: number;
|
||||
favorite: number;
|
||||
};
|
||||
} | null;
|
||||
duration: number;
|
||||
desc: string;
|
||||
cid: number;
|
||||
cid?: number;
|
||||
pages?: Array<{ cid: number; part: string }>;
|
||||
}
|
||||
|
||||
|
||||
79
utils/wbi.ts
Normal file
79
utils/wbi.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
// WBI signature algorithm for Bilibili API
|
||||
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/sign/wbi.md
|
||||
|
||||
const MIXIN_KEY_ENC_TAB = [
|
||||
46,47,18,2,53,8,23,32,15,50,10,31,58,3,45,35,
|
||||
27,43,5,49,33,9,42,19,29,28,14,39,12,38,41,13,
|
||||
37,48,7,16,24,55,40,61,26,17,0,1,60,51,30,4,
|
||||
22,25,54,21,56,59,6,63,57,62,11,36,20,34,44,52,
|
||||
];
|
||||
|
||||
function getMixinKey(imgKey: string, subKey: string): string {
|
||||
const raw = imgKey + subKey;
|
||||
return MIXIN_KEY_ENC_TAB.slice(0, 32).map(i => raw[i]).join('');
|
||||
}
|
||||
|
||||
// Minimal MD5 implementation (no dependencies)
|
||||
function md5(input: string): string {
|
||||
function safeAdd(x: number, y: number) { const lsw=(x&0xffff)+(y&0xffff); const msw=(x>>16)+(y>>16)+(lsw>>16); return (msw<<16)|(lsw&0xffff); }
|
||||
function bitRotateLeft(num: number, cnt: number) { return (num<<cnt)|(num>>>(32-cnt)); }
|
||||
function md5cmn(q:number,a:number,b:number,x:number,s:number,t:number){return safeAdd(bitRotateLeft(safeAdd(safeAdd(a,q),safeAdd(x,t)),s),b);}
|
||||
function md5ff(a:number,b:number,c:number,d:number,x:number,s:number,t:number){return md5cmn((b&c)|(~b&d),a,b,x,s,t);}
|
||||
function md5gg(a:number,b:number,c:number,d:number,x:number,s:number,t:number){return md5cmn((b&d)|(c&~d),a,b,x,s,t);}
|
||||
function md5hh(a:number,b:number,c:number,d:number,x:number,s:number,t:number){return md5cmn(b^c^d,a,b,x,s,t);}
|
||||
function md5ii(a:number,b:number,c:number,d:number,x:number,s:number,t:number){return md5cmn(c^(b|~d),a,b,x,s,t);}
|
||||
|
||||
function md5blks(s: string): number[] {
|
||||
const nblk = ((s.length+8)>>6)+1;
|
||||
const blks = new Array(nblk*16).fill(0);
|
||||
for(let i=0;i<s.length;i++) blks[i>>2]|=s.charCodeAt(i)<<((i%4)*8);
|
||||
blks[s.length>>2]|=0x80<<((s.length%4)*8);
|
||||
blks[nblk*16-2]=s.length*8;
|
||||
return blks;
|
||||
}
|
||||
|
||||
const x = md5blks(input);
|
||||
let a=1732584193,b=-271733879,c=-1732584194,d=271733878;
|
||||
for(let i=0;i<x.length;i+=16){
|
||||
const [aa,bb,cc,dd]=[a,b,c,d];
|
||||
a=md5ff(a,b,c,d,x[i+0],7,-680876936);d=md5ff(d,a,b,c,x[i+1],12,-389564586);c=md5ff(c,d,a,b,x[i+2],17,606105819);b=md5ff(b,c,d,a,x[i+3],22,-1044525330);
|
||||
a=md5ff(a,b,c,d,x[i+4],7,-176418897);d=md5ff(d,a,b,c,x[i+5],12,1200080426);c=md5ff(c,d,a,b,x[i+6],17,-1473231341);b=md5ff(b,c,d,a,x[i+7],22,-45705983);
|
||||
a=md5ff(a,b,c,d,x[i+8],7,1770035416);d=md5ff(d,a,b,c,x[i+9],12,-1958414417);c=md5ff(c,d,a,b,x[i+10],17,-42063);b=md5ff(b,c,d,a,x[i+11],22,-1990404162);
|
||||
a=md5ff(a,b,c,d,x[i+12],7,1804603682);d=md5ff(d,a,b,c,x[i+13],12,-40341101);c=md5ff(c,d,a,b,x[i+14],17,-1502002290);b=md5ff(b,c,d,a,x[i+15],22,1236535329);
|
||||
a=md5gg(a,b,c,d,x[i+1],5,-165796510);d=md5gg(d,a,b,c,x[i+6],9,-1069501632);c=md5gg(c,d,a,b,x[i+11],14,643717713);b=md5gg(b,c,d,a,x[i+0],20,-373897302);
|
||||
a=md5gg(a,b,c,d,x[i+5],5,-701558691);d=md5gg(d,a,b,c,x[i+10],9,38016083);c=md5gg(c,d,a,b,x[i+15],14,-660478335);b=md5gg(b,c,d,a,x[i+4],20,-405537848);
|
||||
a=md5gg(a,b,c,d,x[i+9],5,568446438);d=md5gg(d,a,b,c,x[i+14],9,-1019803690);c=md5gg(c,d,a,b,x[i+3],14,-187363961);b=md5gg(b,c,d,a,x[i+8],20,1163531501);
|
||||
a=md5gg(a,b,c,d,x[i+13],5,-1444681467);d=md5gg(d,a,b,c,x[i+2],9,-51403784);c=md5gg(c,d,a,b,x[i+7],14,1735328473);b=md5gg(b,c,d,a,x[i+12],20,-1926607734);
|
||||
a=md5hh(a,b,c,d,x[i+5],4,-378558);d=md5hh(d,a,b,c,x[i+8],11,-2022574463);c=md5hh(c,d,a,b,x[i+11],16,1839030562);b=md5hh(b,c,d,a,x[i+14],23,-35309556);
|
||||
a=md5hh(a,b,c,d,x[i+1],4,-1530992060);d=md5hh(d,a,b,c,x[i+4],11,1272893353);c=md5hh(c,d,a,b,x[i+7],16,-155497632);b=md5hh(b,c,d,a,x[i+10],23,-1094730640);
|
||||
a=md5hh(a,b,c,d,x[i+13],4,681279174);d=md5hh(d,a,b,c,x[i+0],11,-358537222);c=md5hh(c,d,a,b,x[i+3],16,-722521979);b=md5hh(b,c,d,a,x[i+6],23,76029189);
|
||||
a=md5hh(a,b,c,d,x[i+9],4,-640364487);d=md5hh(d,a,b,c,x[i+12],11,-421815835);c=md5hh(c,d,a,b,x[i+15],16,530742520);b=md5hh(b,c,d,a,x[i+2],23,-995338651);
|
||||
a=md5ii(a,b,c,d,x[i+0],6,-198630844);d=md5ii(d,a,b,c,x[i+7],10,1126891415);c=md5ii(c,d,a,b,x[i+14],15,-1416354905);b=md5ii(b,c,d,a,x[i+5],21,-57434055);
|
||||
a=md5ii(a,b,c,d,x[i+12],6,1700485571);d=md5ii(d,a,b,c,x[i+3],10,-1894986606);c=md5ii(c,d,a,b,x[i+10],15,-1051523);b=md5ii(b,c,d,a,x[i+1],21,-2054922799);
|
||||
a=md5ii(a,b,c,d,x[i+8],6,1873313359);d=md5ii(d,a,b,c,x[i+15],10,-30611744);c=md5ii(c,d,a,b,x[i+6],15,-1560198380);b=md5ii(b,c,d,a,x[i+13],21,1309151649);
|
||||
a=md5ii(a,b,c,d,x[i+4],6,-145523070);d=md5ii(d,a,b,c,x[i+11],10,-1120210379);c=md5ii(c,d,a,b,x[i+2],15,718787259);b=md5ii(b,c,d,a,x[i+9],21,-343485551);
|
||||
a=safeAdd(a,aa);b=safeAdd(b,bb);c=safeAdd(c,cc);d=safeAdd(d,dd);
|
||||
}
|
||||
|
||||
function hex(n: number) { return ('0'+(n&0xff).toString(16)).slice(-2)+('0'+((n>>8)&0xff).toString(16)).slice(-2)+('0'+((n>>16)&0xff).toString(16)).slice(-2)+('0'+((n>>24)&0xff).toString(16)).slice(-2); }
|
||||
return hex(a)+hex(b)+hex(c)+hex(d);
|
||||
}
|
||||
|
||||
export function signWbi(
|
||||
params: Record<string, string | number>,
|
||||
imgKey: string,
|
||||
subKey: string,
|
||||
): Record<string, string | number> {
|
||||
const mixinKey = getMixinKey(imgKey, subKey);
|
||||
const wts = Math.floor(Date.now() / 1000);
|
||||
const all: Record<string, string | number> = { ...params, wts };
|
||||
const query = Object.keys(all)
|
||||
.sort()
|
||||
.map(k => {
|
||||
const v = String(all[k]).replace(/[!'()*]/g, '');
|
||||
return `${k}=${v}`;
|
||||
})
|
||||
.join('&');
|
||||
const w_rid = md5(query + mixinKey);
|
||||
return { ...all, w_rid };
|
||||
}
|
||||
Reference in New Issue
Block a user