Trends Hub

by baranwang
Verified
import { createHash } from 'node:crypto'; import { URLSearchParams } from 'node:url'; import { z } from 'zod'; import { http, cacheStorage, dayjs, defineToolConfig, handleSuccessResult, logger } from '../utils'; const bilibiliRequestSchema = z.object({ type: z .union([ z.literal(0).describe('全站'), z.literal(1).describe('动画'), z.literal(3).describe('音乐'), z.literal(4).describe('游戏'), z.literal(5).describe('娱乐'), z.literal(188).describe('科技'), z.literal(119).describe('鬼畜'), z.literal(129).describe('舞蹈'), z.literal(155).describe('时尚'), z.literal(160).describe('生活'), z.literal(168).describe('国创相关'), z.literal(181).describe('影视'), ]) .optional() .default(0) .describe('排行榜分区'), }); const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'; const encodeWbi = (params: Record<string, string>, imgKey: string, subKey: string): string => { // 过滤特殊字符 const chrFilter = /[!'()*]/g; // 创建URL参数对象 const search = new URLSearchParams(); // 添加时间戳参数 const paramsWithTs: Record<string, string> = { ...params, wts: dayjs().unix().toString(), }; // 按照字典序遍历参数 const sortedKeys = Object.keys(paramsWithTs).sort(); for (const key of sortedKeys) { const value = paramsWithTs[key].toString().replace(chrFilter, ''); search.set(key, value); } const mixinKey = [ 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, ] .map((n) => `${imgKey}${subKey}`[n]) .join('') .slice(0, 32); // 计算wbi签名 const wbiSign = createHash('md5') .update(search.toString() + mixinKey) .digest('hex'); search.set('w_rid', wbiSign); return search.toString(); }; const getWbiKeys = async () => { const resp = await http.get('https://api.bilibili.com/x/web-interface/nav', { headers: { Cookie: 'SESSDATA=xxxxxx', 'User-Agent': UA, Referer: 'https://www.bilibili.com/', }, }); const { img_url: imgUrl = '', sub_url: subUrl = '' } = resp.data.data.wbi_img; const getFileNameFromUrl = (url: string) => url.slice(url.lastIndexOf('/') + 1, url.lastIndexOf('.')); return { imgKey: getFileNameFromUrl(imgUrl), subKey: getFileNameFromUrl(subUrl), }; }; const getBiliWbi = async (): Promise<string> => { const CACHE_KEY = 'bilibili-wbi'; const cachedData = cacheStorage.getItem(CACHE_KEY); if (cachedData) { return cachedData; } const { imgKey, subKey } = await getWbiKeys(); const params = { foo: '114', bar: '514', baz: '1919810' }; const query = encodeWbi(params, imgKey, subKey); cacheStorage.setItem(CACHE_KEY, query); return query; }; const mainGetBilibiliRank = async (type: number) => { const wbiData = await getBiliWbi(); const resp = await http.get<{ code: number; data: { list: any[]; }; message: string; }>(`https://api.bilibili.com/x/web-interface/ranking/v2?rid=${type}&type=all&${wbiData}`, { headers: { Referer: 'https://www.bilibili.com/ranking/all', 'User-Agent': UA, Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Sec-Ch-Ua': '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"', 'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Platform': '"Windows"', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-User': '?1', 'Upgrade-Insecure-Requests': '1', }, }); if (resp.data.code !== 0) { throw new Error(resp.data.message); } return resp.data.data.list.map((item) => ({ title: item.title, description: item.desc || '该视频暂无简介', cover: item.pic, author: item.owner?.name, publishTime: dayjs.unix(item.pubdate).toISOString(), view: item.stat?.view || 0, link: item.short_link_v2 || `https://www.bilibili.com/video/${item.bvid}`, })); }; const backupGetBilibiliRank = async (type: number) => { const resp = await http.get<{ code: number; data: { list: any[]; }; message: string; }>(`https://api.bilibili.com/x/web-interface/ranking?jsonp=jsonp?rid=${type}&type=all&callback=__jp0`, { headers: { Referer: 'https://www.bilibili.com/ranking/all', 'User-Agent': UA, }, }); if (resp.data.code !== 0) { throw new Error(resp.data.message); } return resp.data.data.list.map((item) => ({ title: item.title, description: item.desc || '该视频暂无简介', cover: item.pic, author: item.author, view: item.video_review, link: `https://www.bilibili.com/video/${item.bvid}`, })); }; const getBilibiliRank = async (type: number) => { try { return await mainGetBilibiliRank(type); } catch (error) { logger.error(error); return await backupGetBilibiliRank(type); } }; export default defineToolConfig({ name: 'get-bilibili-rank', description: '获取哔哩哔哩视频排行榜,包含全站、动画、音乐、游戏等多个分区的热门视频,反映当下年轻人的内容消费趋势', zodSchema: bilibiliRequestSchema, func: async (args: unknown) => { const { type } = bilibiliRequestSchema.parse(args); return getBilibiliRank(type); }, });