UNPKG

rsshub

Version:
322 lines (294 loc) 9.81 kB
import { config } from '@/config'; import md5 from '@/utils/md5'; import ofetch from '@/utils/ofetch'; import { art } from '@/utils/render'; import CryptoJS from 'crypto-js'; import path from 'node:path'; import { MediaResult, ResultResponse, SeasonResult } from './types'; // a function randomHexStr(length) { let string = ''; for (let r = 0; r < length; r++) { string += dec2HexUpper(16 * Math.random()); } return padStringWithZeros(string, length); } // o function dec2HexUpper(e) { return Math.ceil(e).toString(16).toUpperCase(); } // s function padStringWithZeros(string, length) { let padding = ''; if (string.length < length) { for (let n = 0; n < length - string.length; n++) { padding += '0'; } } return padding + string; } function lsid() { const e = Date.now().toString(16).toUpperCase(); const lsid = randomHexStr(8) + '_' + e; return lsid; } function _uuid() { const e = randomHexStr(8); const t = randomHexStr(4); const r = randomHexStr(4); const n = randomHexStr(4); const o = randomHexStr(12); const i = Date.now(); return e + '-' + t + '-' + r + '-' + n + '-' + o + padStringWithZeros((i % 100000).toString(), 5) + 'infoc'; } // P function shiftCharByOne(string) { let shiftedStr = ''; for (let n = 0; n < string.length; n++) { shiftedStr += String.fromCharCode(string.charCodeAt(n) - 1); } return shiftedStr; } // o function hexsign(e) { const n = 'YhxToH[2q'; const r = CryptoJS.HmacSHA256('ts'.concat(e), shiftCharByOne(n)); const o = CryptoJS.enc.Hex.stringify(r); return o; } function addRenderData(params, renderData) { return `${params}&w_webid=${encodeURIComponent(renderData)}`; } function addWbiVerifyInfo(params, wbiVerifyString) { const searchParams = new URLSearchParams(params); searchParams.sort(); const verifyParam = searchParams.toString(); const wts = Math.round(Date.now() / 1000); const w_rid = md5(`${verifyParam}&wts=${wts}${wbiVerifyString}`); return `${params}&w_rid=${w_rid}&wts=${wts}`; } // https://github.com/errcw/gaussian/blob/master/lib/box-muller.js function generateGaussianInteger(mean, std) { const _2PI = Math.PI * 2; const u1 = Math.random(); const u2 = Math.random(); const z0 = Math.sqrt(-2 * Math.log(u1)) * Math.cos(_2PI * u2); return Math.round(z0 * std + mean); } function getDmImgList() { if (config.bilibili.dmImgList !== undefined) { const dmImgList = JSON.parse(config.bilibili.dmImgList); return JSON.stringify([dmImgList[Math.floor(Math.random() * dmImgList.length)]]); } const x = Math.max(generateGaussianInteger(1245, 5), 0); const y = Math.max(generateGaussianInteger(1285, 5), 0); const path = [ { x: 3 * x + 2 * y, y: 4 * x - 5 * y, z: 0, timestamp: Math.max(generateGaussianInteger(30, 5), 0), type: 0, }, ]; return JSON.stringify(path); } function getDmImgInter() { if (config.bilibili.dmImgInter !== undefined) { const dmImgInter = JSON.parse(config.bilibili.dmImgInter); return JSON.stringify([dmImgInter[Math.floor(Math.random() * dmImgInter.length)]]); } const p1 = getDmImgInterWh(274, 601); const s1 = getDmImgInterOf(134, 30); const p2 = getDmImgInterWh(332, 64); const s2 = getDmImgInterOf(1101, 338); const of = getDmImgInterOf(0, 0); const wh = getDmImgInterWh(1245, 1285); const ds = [ { t: getDmImgInterT('div'), c: getDmImgInterC('clearfix g-search search-container'), p: [p1[0], p1[2], p1[1]], s: [s1[2], s1[0], s1[1]], }, { t: getDmImgInterT('div'), c: getDmImgInterC('wrapper'), p: [p2[0], p2[2], p2[1]], s: [s2[2], s2[0], s2[1]], }, ]; return JSON.stringify({ ds, wh, of }); } function getDmImgInterT(tag: string) { return { a: 4, article: 29, button: 7, div: 2, em: 27, form: 17, h1: 11, h2: 12, h3: 13, h4: 14, h5: 15, h6: 16, img: 5, input: 6, label: 25, li: 10, ol: 9, option: 20, p: 3, section: 28, select: 19, span: 1, strong: 26, table: 21, td: 23, textarea: 18, th: 24, tr: 22, ul: 8, }[tag]; } function getDmImgInterC(className: string) { return Buffer.from(className).toString('base64').slice(0, -2); } function getDmImgInterOf(top: number, left: number) { const seed = Math.floor(514 * Math.random()); return [3 * top + 2 * left + seed, 4 * top - 4 * left + 2 * seed, seed]; } function getDmImgInterWh(width: number, height: number) { const seed = Math.floor(114 * Math.random()); return [2 * width + 2 * height + 3 * seed, 4 * width - height + seed, seed]; } function addDmVerifyInfo(params: string, dmImgList: string) { const dmImgStr = Buffer.from('no webgl').toString('base64').slice(0, -2); const dmCoverImgStr = Buffer.from('no webgl').toString('base64').slice(0, -2); return `${params}&dm_img_list=${dmImgList}&dm_img_str=${dmImgStr}&dm_cover_img_str=${dmCoverImgStr}`; } function addDmVerifyInfoWithInter(params: string, dmImgList: string, dmImgInter: string) { return `${addDmVerifyInfo(params, dmImgList)}&dm_img_inter=${dmImgInter}`; } const bvidTime = 1_589_990_400; /** * 获取番剧信息并缓存 * * @param {string} id - 番剧 ID。 * @param cache - 缓存 module。 * @returns {Promise<MediaResult>} 番剧信息。 */ export const getBangumi = (id: string, cache): Promise<MediaResult> => cache.tryGet( `bilibili:getBangumi:${id}`, async () => { const res = await ofetch<ResultResponse<MediaResult>>('https://api.bilibili.com/pgc/view/web/media', { query: { media_id: id, }, }); if (res.result.share_url === undefined) { // reference: https://api.bilibili.com/pgc/review/user?media_id=${id} res.result.share_url = `https://www.bilibili.com/bangumi/media/md${res.result.media_id}`; } return res.result; }, config.cache.routeExpire, false ) as Promise<MediaResult>; /** * 获取番剧分集信息并缓存 * * @param {string} id - 番剧 ID。 * @param cache - 缓存 module。 * @returns {Promise<SeasonResult>} 番剧分集信息。 */ export const getBangumiItems = (id: string, cache): Promise<SeasonResult> => cache.tryGet( `bilibili:getBangumiItems:${id}`, async () => { const res = await ofetch<ResultResponse<SeasonResult>>('https://api.bilibili.com/pgc/web/season/section', { query: { season_id: id, }, }); return res.result; }, config.cache.routeExpire, false ) as Promise<SeasonResult>; /** * 使用模板渲染 UGC(用户生成内容)描述。 * * @param {boolean} embed - 是否嵌入视频。 * @param {string} img - 要包含在描述中的图片 URL。 * @param {string} description - UGC 的文本描述。 * @param {string} [aid] - 可选。UGC 的 aid。 * @param {string} [cid] - 可选。UGC 的 cid。 * @param {string} [bvid] - 可选。UGC 的 bvid。 * @returns {string} 渲染的 UGC 描述。 * * @see https://player.bilibili.com/ 获取更多信息。 */ export const renderUGCDescription = (embed: boolean, img: string, description: string, aid?: string, cid?: string, bvid?: string): string => { // docs: https://player.bilibili.com/ const rendered = art(path.join(__dirname, 'templates/description.art'), { embed, ugc: true, aid, cid, bvid, img: img.replace('http://', 'https://'), description, }); return rendered; }; /** * 使用模板渲染 OGV(原创视频)描述。 * * @param {boolean} embed - 是否嵌入视频。 * @param {string} img - 要包含在描述中的图片 URL。 * @param {string} description - OGV 的文本描述。 * @param {string} [seasonId] - 可选。OGV 的季 ID。 * @param {string} [episodeId] - 可选。OGV 的集 ID。 * @returns {string} 渲染的 OGV 描述。 * * @see https://player.bilibili.com/ 获取更多信息。 */ export const renderOGVDescription = (embed: boolean, img: string, description: string, seasonId?: string, episodeId?: string): string => { // docs: https://player.bilibili.com/ const rendered = art(path.join(__dirname, 'templates/description.art'), { embed, ogv: true, seasonId, episodeId, img: img.replace('http://', 'https://'), description, }); return rendered; }; export function getVideoUrl(bvid: string): string; export function getVideoUrl(bvid?: string): string | undefined; export function getVideoUrl(bvid?: string): string | undefined { return bvid ? `https://www.bilibili.com/blackboard/newplayer.html?isOutside=true&autoplay=true&danmaku=true&muted=false&highQuality=true&bvid=${bvid}` : undefined; } export const getLiveUrl = (roomId?: string) => (roomId ? `https://www.bilibili.com/blackboard/live/live-activity-player.html?cid=${roomId}` : undefined); export default { lsid, _uuid, hexsign, addWbiVerifyInfo, getDmImgList, getDmImgInter, addDmVerifyInfo, addDmVerifyInfoWithInter, bvidTime, addRenderData, getBangumi, getBangumiItems, renderUGCDescription, renderOGVDescription, getVideoUrl, };