From 1df1c70e5e8cf46237db8424240028bc2e2c51cd Mon Sep 17 00:00:00 2001 From: XC-Pro <19840+xc-pro@noreply.example.org> Date: Fri, 14 Aug 2026 11:21:48 +0200 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=E3=80=8Cjs=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/985短剧网.js | 228 +++++++++++++++++++ js/AGE动漫.js | 446 +++++++++++++++++++++++++++++++++++++ js/cat_123TV.js | 149 +++++++++++++ js/cat_哔哩.js | 538 +++++++++++++++++++++++++++++++++++++++++++++ js/cat_哔哩影视.js | 362 ++++++++++++++++++++++++++++++ 5 files changed, 1723 insertions(+) create mode 100644 js/985短剧网.js create mode 100644 js/AGE动漫.js create mode 100644 js/cat_123TV.js create mode 100644 js/cat_哔哩.js create mode 100644 js/cat_哔哩影视.js diff --git a/js/985短剧网.js b/js/985短剧网.js new file mode 100644 index 0000000..a6249eb --- /dev/null +++ b/js/985短剧网.js @@ -0,0 +1,228 @@ +//本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。 +//严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。 +import { + load as t +} from "assets://js/lib/cat.js"; +let e, a = "https://www.985djw.com", + i = "", + n = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36", + Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + "accept-language": "zh-CN,zh;q=0.9", + "cache-control": "no-cache", + pragma: "no-cache", + priority: "u=0, i", + "sec-ch-ua": '"Google Chrome";v="149", "Chromium";v="149", "Not)A;Brand";v="24"', + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": '"Windows"', + "sec-fetch-dest": "document", + "sec-fetch-mode": "navigate", + "upgrade-insecure-requests": "1" + }; +async function r(t) { + "function" == typeof js2Proxy && "function" == typeof desX && "function" != typeof getProxy && (e = !0); + let n = t.ext; + if ("object" == typeof n && null !== n) { + let t = n.host || ""; + t && t.startsWith("http") && (a = t), n.picProxy && n.picProxy.startsWith("http") && (i = n.picProxy) + } else "string" == typeof n && n.startsWith("http") && (a = n); + a.endsWith("/") && (a = a.slice(0, -1)) +} +async function s(i) { + let n = [], + r = {}, + s = await u(a + "/"), + o = t(s); + if (o(".stui-header__menu ul li a").each((t, e) => { + let a = o(e).attr("href"), + i = o(e).text().trim(); + if (a && "/" !== a && -1 === a.indexOf(".html")) { + let t = a.match(/\/nl\/([^\/]+)\//); + t && n.push({ + type_id: t[1], + type_name: i + }) + } + }), e && n.unshift({ + type_id: "home", + type_name: "首页" + }), n.length > 0) { + let i = e && n.length > 1 ? n[1].type_id : n[0].type_id, + s = await u(a + "/nl/" + i + "/", a + "/"), + o = t(s), + l = { + "类型": "class", + "地区": "area", + "年份": "year", + "语言": "lang", + "排序": "by" + }, + c = { + class: 3, + area: 1, + year: 11, + lang: 4, + by: 2 + }, + p = []; + o(".stui-screen__list").each((t, e) => { + let a = o(e).find("li:first-child span.text-muted").text().replace("按", "").trim(), + i = l[a]; + if (i) { + let t = [], + n = ""; + o(e).find("a").each((e, a) => { + let r = o(a).text().trim(), + s = (o(a).attr("href") || "").split("-")[c[i]] || ""; + "" === r && "" === s || ("全部" === r && (n = s), t.push({ + n: r, + v: s + })) + }), t.length > 1 && p.push({ + key: i, + name: a, + init: n, + value: t + }) + } + }); + for (let t = 0; t < n.length; t++) "home" !== n[t].type_id && (r[n[t].type_id] = p) + } + return JSON.stringify({ + class: n, + filters: r + }) +} +async function o() { + return await l("home", 1, null, {}) +} +async function l(e, i, n, r) { + let s = i || 1; + if ("home" === e) { + let e = await u(a + "/"), + i = h(t(e)); + return JSON.stringify({ + page: 1, + pagecount: 1, + list: i + }) + } + let o = r || {}, + l = o.area || "", + c = o.by || "", + p = o.class || "", + d = o.lang || "", + m = o.letter || "", + f = o.year || "", + _ = a + `/vodshow/${e}-${l}-${c}-${p}-${d}-${m}---${s}---${f}/`, + y = await u(_, a + "/nl/" + e + "/"), + g = t(y), + v = h(g), + x = s, + w = g(".stui-page .num").text(); + return w && w.includes("/") && (x = parseInt(w.split("/")[1])), JSON.stringify({ + page: parseInt(s), + pagecount: parseInt(x), + list: v + }) +} +async function c(e, i, n) { + let r = n || 1, + s = ""; + s = "1" === String(r) ? a + "/vodsearch/-------------/?wd=" + encodeURIComponent(e) + "&submit=" : a + `/vodsearch/${encodeURIComponent(e)}----------${r}---/`; + let o = await u(s, a + "/"), + l = t(o), + c = h(l), + p = r, + d = l(".stui-page .num").text(); + return d && d.includes("/") && (p = parseInt(d.split("/")[1])), JSON.stringify({ + page: parseInt(r), + pagecount: parseInt(p), + list: c + }) +} +async function p(e) { + let i = e.startsWith("http") ? e : a + e, + n = await u(i, a + "/"), + r = t(n), + s = { + vod_id: e, + vod_name: r("h1.title a").text().trim(), + vod_pic: m(r(".stui-content__thumb img").attr("data-original")), + type_name: "", + vod_year: "", + vod_area: "", + vod_remarks: "", + vod_actor: "", + vod_director: "", + vod_content: r(".stui-content__desc").text().trim(), + vod_play_from: "", + vod_play_url: "" + }; + r(".stui-content__detail p.data").each((t, e) => { + let a = r(e).text(); + a.includes("类型:") && (s.type_name = r(e).find("a").text().trim()), a.includes("地区:") && (s.vod_area = r(e).find("a").text().trim()), a.includes("年份:") && (s.vod_year = r(e).find("a").text().trim()), a.includes("主演:") && (s.vod_actor = a.replace("主演:", "").trim()), a.includes("导演:") && (s.vod_director = a.replace("导演:", "").trim()) + }); + let o = []; + return r(".stui-pannel").each((t, e) => { + r(e).find(".stui-pannel__head h3.title").text().includes("手机在线播放器") && (s.vod_play_from = "碎光在线", r(e).find("ul.stui-content__playlist li a").each((t, e) => { + let a = r(e).attr("href"), + i = r(e).text().trim(); + a && o.push(i + "$" + a) + })) + }), s.vod_play_url = o.join("#"), JSON.stringify({ + list: [s] + }) +} +async function d(t, e, i) { + let r = e.startsWith("http") ? e : a + e, + s = "", + o = (await u(r, a + "/")).match(/player_aaaa\s*=\s*(\{[\s\S]*?\})/); + if (o) try { + s = JSON.parse(o[1]).url || "" + } catch (t) { + console.error("解析 player_aaaa 核心数组失败: " + t.message) + } + return JSON.stringify({ + parse: 0, + url: s, + header: { + "User-Agent": n["User-Agent"] + } + }) +} +async function u(t, e) { + let a = Object.assign({}, n); + return e ? (a.referer = e, a["sec-fetch-site"] = "same-origin") : a["sec-fetch-site"] = "none", (await req(t, { + headers: a + })).content +} + +function h(t) { + let e = []; + return t(".stui-vodlist__item").each((a, i) => { + let n = t(i).find("a.stui-vodlist__thumb"), + r = n.attr("href"); + r && e.push({ + vod_id: r, + vod_name: n.attr("title") || "", + vod_pic: m(n.attr("data-original")), + vod_remarks: n.find(".pic-text").text().trim() || "" + }) + }), e +} + +function m(t) { + return t ? (t.startsWith("//") ? t = "http:" + t : t.startsWith("/") && (t = a + t), i ? i + t : t) : "" +} +export function __jsEvalReturn() { + return { + init: r, + home: s, + homeVod: o, + category: l, + search: c, + detail: p, + play: d + } +} \ No newline at end of file diff --git a/js/AGE动漫.js b/js/AGE动漫.js new file mode 100644 index 0000000..2db40f8 --- /dev/null +++ b/js/AGE动漫.js @@ -0,0 +1,446 @@ +import { load, Crypto } from 'assets://js/lib/cat.js'; + +var HOST; +const PC_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36'; +const DefHeader = { 'User-Agent': PC_UA }; +const KParams = { + headers: { 'User-Agent': PC_UA }, + timeout: 5000 +}; + +async function init(cfg) { + try { + let host = cfg.ext?.host?.trim() || 'https://www.agedm.io'; + HOST = host.replace(/\/$/, ''); + KParams.headers.Referer = HOST; + const parseTimeout = parseInt(cfg.ext?.timeout?.trim(), 10); + KParams.timeout = parseTimeout > 0 ? parseTimeout : 5000; + KParams.resHtml = await request(HOST); + } catch (e) { + console.error('初始化参数失败:', e.message); + } +} + +async function home(filter) { + try { + const classes = [ + { type_id: 'all', type_name: '全部' }, + { type_id: 'TV', type_name: 'TV' }, + { type_id: '剧场版', type_name: '剧场版' }, + { type_id: 'OVA', type_name: 'OVA' } + ]; + const filterConfig = [ + { + key: "area", name: "地区", + value: [ + { n: "全部", v: "all" }, { n: "日本", v: "日本" }, + { n: "中国", v: "中国" }, { n: "欧美", v: "欧美" } + ] + }, + { + key: "letter", name: "首字母", + value: [ + { n: "全部", v: "all" }, + { n: "A", v: "A" }, { n: "B", v: "B" }, { n: "C", v: "C" }, { n: "D", v: "D" }, + { n: "E", v: "E" }, { n: "F", v: "F" }, { n: "H", v: "H" }, { n: "I", v: "I" }, + { n: "J", v: "J" }, { n: "K", v: "K" }, { n: "L", v: "L" }, { n: "M", v: "M" }, + { n: "N", v: "N" }, { n: "O", v: "O" }, { n: "P", v: "P" }, { n: "Q", v: "Q" }, + { n: "R", v: "R" }, { n: "S", v: "S" }, { n: "T", v: "T" }, { n: "U", v: "U" }, + { n: "V", v: "V" }, { n: "W", v: "W" }, { n: "X", v: "X" }, { n: "Y", v: "Y" }, + { n: "Z", v: "Z" } + ] + }, + { + key: "year", name: "年份", + value: [ + { n: "全部", v: "all" }, + { n: "2026", v: "2026" }, { n: "2025", v: "2025" }, { n: "2024", v: "2024" }, + { n: "2023", v: "2023" }, { n: "2022", v: "2022" }, { n: "2021", v: "2021" }, + { n: "2020", v: "2020" }, { n: "2019", v: "2019" }, { n: "2018", v: "2018" }, + { n: "2017", v: "2017" }, { n: "2016", v: "2016" }, { n: "2015", v: "2015" }, + { n: "2014", v: "2014" }, { n: "2013", v: "2013" }, { n: "2012", v: "2012" }, + { n: "2011", v: "2011" }, { n: "2010", v: "2010" }, { n: "2009", v: "2009" }, + { n: "2008", v: "2008" }, { n: "2007", v: "2007" }, { n: "2006", v: "2006" }, + { n: "2005", v: "2005" }, { n: "2004", v: "2004" }, { n: "2003", v: "2003" }, + { n: "2002", v: "2002" }, { n: "2001", v: "2001" }, { n: "2000以前", v: "2000以前" } + ] + }, + { + key: "quarter", name: "季度", + value: [ + { n: "全部", v: "all" }, + { n: "1月", v: "1" }, { n: "4月", v: "4" }, + { n: "7月", v: "7" }, { n: "10月", v: "10" } + ] + }, + { + key: "status", name: "状态", + value: [ + { n: "全部", v: "all" }, + { n: "连载", v: "连载" }, { n: "完结", v: "完结" }, { n: "未播放", v: "未播放" } + ] + }, + { + key: "type", name: "类型", + value: [ + { n: "全部", v: "all" }, + { n: "搞笑", v: "搞笑" },{ n: "运动", v: "运动" },{ n: "励志", v: "励志" }, + { n: "热血", v: "热血" },{ n: "战斗", v: "战斗" },{ n: "竞技", v: "竞技" }, + { n: "校园", v: "校园" },{ n: "青春", v: "青春" },{ n: "爱情", v: "爱情" }, + { n: "恋爱", v: "恋爱" },{ n: "冒险", v: "冒险" },{ n: "后宫", v: "后宫" }, + { n: "百合", v: "百合" },{ n: "治愈", v: "治愈" },{ n: "萝莉", v: "萝莉" }, + { n: "魔法", v: "魔法" },{ n: "悬疑", v: "悬疑" },{ n: "推理", v: "推理" }, + { n: "奇幻", v: "奇幻" },{ n: "科幻", v: "科幻" },{ n: "游戏", v: "游戏" }, + { n: "神魔", v: "神魔" },{ n: "恐怖", v: "恐怖" },{ n: "血腥", v: "血腥" }, + { n: "机战", v: "机战" },{ n: "战争", v: "战争" },{ n: "犯罪", v: "犯罪" }, + { n: "历史", v: "历史" },{ n: "社会", v: "社会" },{ n: "职场", v: "职场" }, + { n: "剧情", v: "剧情" },{ n: "伪娘", v: "伪娘" },{ n: "耽美", v: "耽美" }, + { n: "童年", v: "童年" },{ n: "教育", v: "教育" },{ n: "亲子", v: "亲子" }, + { n: "真人", v: "真人" },{ n: "歌舞", v: "歌舞" },{ n: "肉番", v: "肉番" }, + { n: "美少女", v: "美少女" },{ n: "轻小说", v: "轻小说" },{ n: "吸血鬼", v: "吸血鬼" }, + { n: "女性向", v: "女性向" },{ n: "泡面番", v: "泡面番" },{ n: "欢乐向", v: "欢乐向" } + ] + }, + { + key: "source", name: "资源", + value: [ + { n: "全部", v: "all" }, + { n: "BDRIP", v: "BDRIP" }, { n: "AGE-RIP", v: "AGE-RIP" } + ] + }, + { + key: "sort", name: "排序", + value: [ + { n: "全部", v: "time" }, + { n: "时间", v: "time" }, { n: "点击量", v: "点击量" } + ] + } + ]; + const filters = { all: filterConfig, TV: filterConfig, "剧场版": filterConfig, OVA: filterConfig }; + return JSON.stringify({ class: classes, filters }); + } catch (e) { + console.error('获取分类失败:', e.message); + return JSON.stringify({ class: [], filters: {} }); + } +} + +async function homeVod() { + try { + const VODS = getVodList(KParams.resHtml, true); + return JSON.stringify({ list: VODS }); + } catch (e) { + console.error('推荐页获取失败:', e.message); + return JSON.stringify({ list: [] }); + } +} + +async function category(tid, pg, filter, extend) { + try { + pg = parseInt(pg, 10) || 1; + const year = extend?.year ? encodeURIComponent(extend.year) : 'all'; + const letter = extend?.letter ? encodeURIComponent(extend.letter) : 'all'; + const type = extend?.type ? encodeURIComponent(extend.type) : 'all'; + const source = extend?.source ? encodeURIComponent(extend.source) : 'all'; + const sort = extend?.sort ? encodeURIComponent(extend.sort) : 'time'; + const area = extend?.area ? encodeURIComponent(extend.area) : 'all'; + const quarter = extend?.quarter ? encodeURIComponent(extend.quarter) : 'all'; + const status = extend?.status ? encodeURIComponent(extend.status) : 'all'; + const cateUrl = `${HOST}/catalog/${tid}-${year}-${letter}-${type}-${source}-${sort}-${pg}-${area}-${quarter}-${status}`; + const resHtml = await request(cateUrl); + const VODS = getVodList(resHtml); + return JSON.stringify({ list: VODS, page: pg, pagecount: 999, limit: 30, total: 30 * 999 }); + } catch (e) { + console.error('类别页获取失败:', e.message); + return JSON.stringify({ list: [], page: 1, pagecount: 0, limit: 30, total: 0 }); + } +} + +async function search(wd, quick, pg) { + try { + pg = parseInt(pg, 10) || 1; + const searchUrl = `${HOST}/search?query=${encodeURIComponent(wd)}&page=${pg}`; + const resHtml = await request(searchUrl); + const VODS = getVodList(resHtml); + return JSON.stringify({ list: VODS, page: pg, pagecount: 10, limit: 30, total: 300 }); + } catch (e) { + console.error('搜索页获取失败:', e.message); + return JSON.stringify({ list: [], page: 1, pagecount: 0, limit: 30, total: 0 }); + } +} + +function getVodList(khtml, hm = false) { + try { + if (!khtml) throw new Error('源码为空'); + const kvods = []; + const $ = load(khtml); + const listArr = hm ? $('.video_item').get() : $('.video_cover').get(); + for (const it of listArr) { + const $it = $(it); + const a = $it.find('a'); + const kname = dealStr(a.text().trim() || a.attr('title'), '名称'); + const kurl = dealStr(a.attr('href'), 'href'); + const poster = $it.find('img'); + let pic = poster.attr('data-original') || poster.attr('src'); + if (pic && pic.startsWith('/')) pic = HOST + pic; + const kpic = dealStr(pic, '图片'); + const kremarks = dealStr($it.find('span').text(), '状态'); + kvods.push({ + vod_name: kname, + vod_pic: kpic, + vod_remarks: kremarks, + vod_id: `${kurl}@${kname}@${kpic}@${kremarks}` + }); + } + return kvods; + } catch (e) { + console.error(`生成视频列表失败:`, e.message); + return []; + } +} + +async function detail(ids) { + try { + const [id, kname, kpic, kremarks] = ids.split('@'); + const rawUrl = /^http/.test(id) ? id : `${HOST}${id}`; + const detailUrl = rawUrl.replace(/https?:\/\/[^\/]+/, HOST); + const resHtml = await request(detailUrl); + if (!resHtml) throw new Error('源码请求失败'); + const $ = load(resHtml); + const intros = $('.detail_imform_list').html() || ''; + const ktabs = $('.nav-item:has(button)').get().map((it, idx) => { + const btn = $(it).find('button'); + return dealStr(`${btn.text()}(${btn.attr('data-bs-target').replace('#playlist-source-','')})`, `线-${idx+1}`); + }); + const kurls = $('.video_detail_episode').get().map(item => { + const arr = $(item).find('li a').get().map(it => { + const $it = $(it); + return dealStr($it.text(), 'noEpi') + '$' + dealStr($it.attr('href'), 'noUrl'); + }); + return arr.join('#'); + }); + const VOD = { + vod_id: detailUrl, + vod_name: kname, + vod_pic: kpic, + type_name: cutStr(intros, '剧情类型:', '', '', '简介'), + vod_play_from: ktabs.join('$$$'), + vod_play_url: kurls.join('$$$') + }; + return JSON.stringify({ list: [VOD] }); + } catch (e) { + console.error('详情页获取失败:', e.message); + return JSON.stringify({ list: [] }); + } +} + +function uuid() { + const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + const arr = []; + arr[8] = arr[13] = arr[18] = arr[23] = "-"; + arr[14] = "4"; + for (let i = 0; i < 36; i++) { + if (!arr[i]) { + const r = Math.random() * 16 | 0; + arr[i] = chars[(i === 19 ? (r & 0x3 | 0x8) : r)]; + } + } + return arr.join(""); +} + +async function play(flag, ids, flags) { + try { + const playUrl = /^http/.test(ids) ? ids : `${HOST}${ids}`; + let kurl = ''; + const resHtml = await request(playUrl); + const $ = load(resHtml); + const iframesrc = $('#iframeForVideo').attr('src') || ''; + if (iframesrc) { + const iframeresHtml = await request(iframesrc); + let rawVurl = cutStr(iframeresHtml, 'Vurl = ', ';'); + let Vurl = rawVurl.replace(/^["']|["']$/g, '').trim(); + if (!/m3u8|mp4|mkv/.test(Vurl)) { + const Ref = cutStr(iframeresHtml, 'Ref = "', '";'); + const Time = cutStr(iframeresHtml, 'Time = "', '";'); + const Version = cutStr(iframeresHtml, 'Version = "', '";'); + const idList = cutStr(iframeresHtml, 'id="', '"', 'null', true, 1, true); + const mockHtmlMeta = { contentTypeId: idList[0], viewportId: idList[1] }; + const apiUrl = 'https://jx.wuzhoupai.com:8443/vip/Api.php'; + const jxhost = 'jx.wuzhoupai.com'; + const currentUuid = uuid(); + const reqData = { url: Vurl, wap: '0', ios: '0', host: jxhost, referer: Ref, time: Time }; + const encryptParams = hxm_encrypt(JSON.stringify(reqData)).toUpperCase(); + const signRaw = `${jxhost} | ${currentUuid} | ${Time} | ${Version} | ${encryptParams}`; + const sign = hxm_encrypt(signRaw); + const apiRes = await request(apiUrl, { + method: 'post', + postType: 'json', + headers: { + 'x-requested-with': 'XMLHttpRequest', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'Accept': 'application/json, text/javascript, */*; q=0.01', + 'Video-Parse-Uuid': currentUuid, + 'Video-Parse-Time': Time, + 'Video-Parse-Version': Version, + 'Video-Parse-Sign': sign + }, + data: { Params: encryptParams } + }); + const apiResjson = safeParseJSON(apiRes); + const result = decryptData(apiResjson, mockHtmlMeta); + kurl = result.url; + } else { + kurl = Vurl; + } + } + return JSON.stringify({ jx: 0, parse: 0, url: kurl, header: DefHeader }); + } catch (e) { + console.error('播放失败:', e.message); + return JSON.stringify({ jx: 0, parse: 0, url: '', header: {} }); + } +} + +// 工具函数 +function dealStr(str, defaultValue = '') { + if (str === null || typeof str === 'undefined' || String(str).trim() === '') return defaultValue; + return String(str).replace(/( |\u00A0|\s)+/g, ' ').trim().replace(/\s+/g, ' ') || defaultValue; +} + +function cutStr(str, prefix = '', suffix = '', defaultVal = 'cutFaile', clean = true, i = 1, all = false) { + try { + if (typeof str !== 'string' || !str) throw new Error('被截取对象需为非空字符串'); + const cleanStr = cs => String(cs).replace(/<[^>]*?>/g, ' ').replace(/( |\u00A0|\s)+/g, ' ').trim().replace(/\s+/g, ' '); + const esc = s => String(s).replace(/[.*+?${}()|[\]\\/^]/g, '\\$&'); + const pre = esc(prefix).replace(/£/g, '[^]*?'); + const end = esc(suffix); + const regex = new RegExp(`${pre ? pre : '^'}([^]*?)${end ? end : '$'}`, 'g'); + const matchIterator = str.matchAll(regex); + if (all) { + const matchArr = [...matchIterator]; + return matchArr.length ? matchArr.map(it => { + const val = it[1] ?? defaultVal; + return clean && val !== defaultVal ? cleanStr(val) : val; + }) : [defaultVal]; + } + i = parseInt(i, 10); + if (isNaN(i) || i < 1) throw new Error('序号必须为正整数'); + let tgIdx = i - 1, matchIdx = 0; + for (const match of matchIterator) { + if (matchIdx++ === tgIdx) { + const result = match[1] ?? defaultVal; + return clean && result !== defaultVal ? cleanStr(result) : result; + } + } + return defaultVal; + } catch (e) { + console.error(`字符串截取失败:`, e.message); + return all ? ['cutErr'] : 'cutErr'; + } +} + +function safeParseJSON(jStr) { + try { return JSON.parse(jStr); } catch (e) { return null; } +} + +async function request(reqUrl, options = {}) { + try { + if (typeof reqUrl !== 'string' || !reqUrl.trim()) throw new Error('reqUrl需为字符串且非空'); + if (typeof options !== 'object' || Array.isArray(options) || !options) throw new Error('options类型需为非null对象'); + options.method = options.method?.toLowerCase() || 'get'; + if (['get', 'head'].includes(options.method)) { + delete options.data; + delete options.postType; + } else { + options.data = options.data ?? ''; + options.postType = options.postType?.toLowerCase() || 'form'; + } + let { headers, timeout, toBase64 = false, ...restOpts } = options; + const optObj = { + headers: (headers && typeof headers === 'object' && !Array.isArray(headers)) ? headers : KParams.headers, + timeout: parseInt(timeout, 10) > 0 ? parseInt(timeout, 10) : KParams.timeout, + buffer: toBase64 ? 2 : 0, + ...restOpts + }; + const res = await req(reqUrl, optObj); + if (options.withHeaders) { + const resHeaders = res.headers && typeof res.headers === 'object' && !Array.isArray(res.headers) ? res.headers : {}; + return JSON.stringify({ ...resHeaders, body: res?.content ?? '' }); + } + return res?.content ?? ''; + } catch (e) { + console.error(`${reqUrl}→请求失败:`, e.message); + return options?.withHeaders ? JSON.stringify({ body: '' }) : ''; + } +} + +// AES解密工具 +function decryptOuterAesCbc(hexCipherText, md5String) { + const hexWords = Crypto.enc.Hex.parse(hexCipherText); + const base64CipherText = Crypto.enc.Base64.stringify(hexWords); + const rawKey = md5String.substring(0, 16); + const rawIv = md5String.substring(16, 32); + const cryptoKey = Crypto.enc.Latin1.parse(rawKey); + const cryptoIv = Crypto.enc.Latin1.parse(rawIv); + const decryptedWords = Crypto.AES.decrypt(base64CipherText, cryptoKey, { iv: cryptoIv, mode: Crypto.mode.CBC, padding: Crypto.pad.Pkcs7 }); + return Crypto.enc.Utf8.stringify(decryptedWords); +} + +function hxm_decrypt(encryptedUrl) { + if (!encryptedUrl) return ""; + const secretStr = "ni po jie ni ** "; + const cryptoKey = Crypto.enc.Utf8.parse(secretStr); + const cryptoIv = Crypto.enc.Utf8.parse(secretStr); + let finalCipherText = encryptedUrl; + if (/^[0-9a-fA-F]+$/.test(encryptedUrl)) { + const hexWords = Crypto.enc.Hex.parse(encryptedUrl); + finalCipherText = Crypto.enc.Base64.stringify(hexWords); + } + const decryptedWords = Crypto.AES.decrypt(finalCipherText, cryptoKey, { iv: cryptoIv, mode: Crypto.mode.CBC, padding: Crypto.pad.Pkcs7 }); + return Crypto.enc.Utf8.stringify(decryptedWords); +} + +function hxm_encrypt(encryptedUrl) { + if (!encryptedUrl) return ""; + const secretStr = "ni po jie ni ** "; + const cryptoKey = Crypto.enc.Utf8.parse(secretStr); + const cryptoIv = Crypto.enc.Utf8.parse(secretStr); + let finalCipherText = encryptedUrl; + if (/^[0-9a-fA-F]+$/.test(encryptedUrl)) { + const hexWords = Crypto.enc.Hex.parse(encryptedUrl); + finalCipherText = Crypto.enc.Base64.stringify(hexWords); + } + const encrypted = Crypto.AES.encrypt(finalCipherText, cryptoKey, { iv: cryptoIv, mode: Crypto.mode.CBC, padding: Crypto.pad.Pkcs7 }); + return Crypto.enc.Hex.stringify(encrypted.ciphertext).toUpperCase(); +} + +function decryptData(responseData, htmlMeta = { contentTypeId: '', viewportId: '' }) { + let decryptedInfo = {}; + if (responseData.Status !== 1) throw new Error("Status 不为 1,数据异常"); + if (responseData.Code === 10) { + let metaFeature = (htmlMeta.contentTypeId + htmlMeta.viewportId).replace("viewport", ""); + const rawSaltKey = responseData.Code + metaFeature + responseData.Appkey + responseData.Version; + const md5String = md5X(rawSaltKey); + const decryptedStr = decryptOuterAesCbc(responseData.Data, md5String); + decryptedInfo = JSON.parse(decryptedStr); + if (!decryptedInfo.url.startsWith('http')) decryptedInfo.url = hxm_decrypt(decryptedInfo.url); + decryptedInfo.url = decodeURIComponent(decryptedInfo.url); + } else { + const rawSaltKey = responseData.Code + responseData.Appkey + responseData.Version; + const md5String = md5X(rawSaltKey); + const decryptedStr = decryptOuterAesCbc(responseData.Data, md5String); + decryptedInfo = JSON.parse(decryptedStr); + if (!decryptedInfo.url.startsWith('http')) decryptedInfo.url = hxm_decrypt(decryptedInfo.url); + decryptedInfo.url = decodeURIComponent(decryptedInfo.url); + } + return decryptedInfo; +} + +export function __jsEvalReturn() { + return { init, home, homeVod, category, search, detail, play, proxy: null }; +} \ No newline at end of file diff --git a/js/cat_123TV.js b/js/cat_123TV.js new file mode 100644 index 0000000..b6ffc92 --- /dev/null +++ b/js/cat_123TV.js @@ -0,0 +1,149 @@ +let host = 'https://a123tv.com'; +let headers = { + 'User-Agent': 'Mozilla/5.0 (Linux; Android 13; M2102J2SC Build/TKQ1.221114.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/144.0.7559.31 Mobile Safari/537.36', + 'Referer': host +}; + +function getList(html) { + let videos = []; + pdfa(html, '.w4-item-wrap').forEach(it => { + const id = pdfh(it, 'a.w4-item&&href') || '', + name = pdfh(it, 'img&&alt') || '', + pic = pdfh(it, 'img&&data-src') || pdfh(it, 'img&&src') || '', + remark = pdfh(it, '.w4-item-cover .s span&&Text') || ''; + if (id && name) { + let vod_pic = pic.startsWith('//') ? `https:${pic}` : pic.startsWith('/') ? `${host}${pic}` : pic; + vod_pic = vod_pic || 'data:image/gif;base64,R0lGODdhAQABAPAAAMPDwwAAACwAAAAAAQABAAACAkQBADs='; + videos.push({ + vod_id: id.replace(host, '').replace(/^\//, ''), + vod_name: name.trim(), + vod_pic, + vod_remarks: remark.trim() + }); + } + }); + return videos; +} + +async function init(cfg) {} +async function home(filter) { + return JSON.stringify({ + class: [{ "type_id": "10", "type_name": "电影" }, { "type_id": "11", "type_name": "连续剧" }, + { "type_id": "12", "type_name": "综艺" }, { "type_id": "13", "type_name": "动漫" }, + { "type_id": "15", "type_name": "福利" }], + filters: {} + }); +} +async function homeVod() { + let resp = await req(host, { headers }); + return JSON.stringify({ list: getList(resp.content) }); +} +async function category(tid, pg, filter, extend) { + let p = parseInt(pg) || 1; + let resp = await req(`${host}/t/${tid}/p${p}.html`, { headers }); + return JSON.stringify({ list: getList(resp.content), page: p, pagecount: p + 5, limit: 20 }); +} + +async function detail(id) { + const playFrom = [], playList = []; + let detailUrl = id.startsWith('http') ? id : `${host}/${id.replace(/^\//, '')}`; + let { content: html = '' } = await req(detailUrl, { headers }); + + // 基础信息 + let vod_name = (pdfh(html, 'li.on h1&&Text') || pdfh(html, 'h1.title&&Text') || '未知片名').trim(); + let vod_pic = pdfh(html, '#awp1&&data-poster') || ''; + vod_pic = vod_pic.startsWith('//') ? `https:${vod_pic}` : vod_pic.startsWith('/') ? `${host}${vod_pic}` : vod_pic; + let vod_remarks = pdfh(html, '.w4-line-cover .i&&Text')?.split(' / ')[1] || '720p'; + + // 解析meta中的导演/演员/地区/年代/类型/简介 + let vod_area = '', vod_year = '', type_name = '', vod_actor = '', vod_director = '', vod_content = ''; + // 提取description + const descMatch = html.match(/]*>/i); + const descText = descMatch ? descMatch[1] : ''; + // 从标题提取类型和年代 + const titleMatch = html.match(/《[^》]+》\s*-\s*([^-]+)\s*-\s*(\d+)年/i); + if (titleMatch) { + type_name = titleMatch[1].trim(); + vod_year = titleMatch[2].trim(); + } + // 解析描述中的地区、导演、演员、剧情 + if (descText) { + const areaMatch = descText.match(/地区:([^。]+)。/); + const dirMatch = descText.match(/导演:([^。]+)。/); + const actorMatch = descText.match(/演员:([^。]+)。/); + const contentMatch = descText.match(/剧情:([\s\S]+)/); + + vod_area = areaMatch ? areaMatch[1].trim() : ''; + vod_director = dirMatch ? dirMatch[1].trim().replace(/、/g, '/') : ''; + vod_actor = actorMatch ? actorMatch[1].trim().replace(/、/g, '/') : ''; + vod_content = contentMatch ? contentMatch[1].trim() : descText; + } + + const ppMatch = html.match(/var\s+pp\s*=\s*({[\s\S]*?});/); + if (ppMatch) { + const { la = [] } = JSON.parse(ppMatch[1]); + const episodeNames = pdfa(html, '.w4-episode-list .w a').map((a, i) => + (pdfh(a, 'a&&Text') || pdfh(a, 'a&&title') || `第${i+1}集`).trim() + ); + + la.forEach((item, index) => { + if (Array.isArray(item) && item.length >= 5) { + const [, , episodeNum, , m3u8] = item; + const realEpNum = parseInt(episodeNum) || episodeNames.length; + const lineEpisodes = []; + for (let i = 0; i < realEpNum; i++) { + let epName = episodeNames[i] || `第${i+1}集`; + let realM3u8 = m3u8.startsWith('//') ? `https:${m3u8}` : m3u8; + lineEpisodes.push(`${epName}$${realM3u8}`); + } + playFrom.push(`✨推荐✨${index + 1}`); + playList.push(lineEpisodes.join('#')); + } + }); + } + + return JSON.stringify({ + list: [{ + vod_id: id, vod_name, vod_pic, vod_remarks, vod_year, vod_area, + type_name, vod_actor, vod_director, vod_content, + vod_play_from: playFrom.join('$$$'), vod_play_url: playList.join('$$$') + }] + }); +} + +async function search(wd, quick, pg) { + let p = parseInt(pg) || 1; + let resp = await req(`${host}/s/${encodeURIComponent(wd)}/p${p}.html`, { headers }); + return JSON.stringify({ list: getList(resp.content), page: p, pagecount: p + 5, limit: 20 }); +} + +async function play(flag, id, flags) { + let url = id.startsWith('http') ? id : `${host}/${id.replace(/^\//, '')}`; + let { content: html = '' } = await req(url, { headers }); + + const playerVarMatch = html.match(/var\s+player_aaaa\s*=\s*({[^;]+});/); + if (playerVarMatch) { + let jsonStr = playerVarMatch[1].replace(/(\w+):/g, '"$1":').replace(/\\/g, ''); + let { url: playUrl } = JSON.parse(jsonStr); + if (playUrl) return JSON.stringify({ parse: playUrl.includes('.m3u8') ? 0 : 1, url: playUrl, header: headers }); + } + + let m3u8Match = html.match(/"url":"([^"]+\.m3u8(\?[^"]*)?)"/) || html.match(/'url':'([^']+\.m3u8(\?[^']*)?)'/) || + html.match(/"(https?:\/\/[^"]+\.m3u8[^"]*)"/) || html.match(/'(https?:\/\/[^']+\.m3u8[^']*)'/) || + html.match(/https?:\/\/[^\s"]+\.m3u8[^\s"]*/); + if (m3u8Match) { + let playUrl = m3u8Match[1] || m3u8Match[0]; + return JSON.stringify({ parse: 0, url: playUrl.replace(/\\/g, ""), header: headers }); + } + + let iframeMatch = html.match(/]*src="([^"]+)"[^>]*>/i) || html.match(/]*src="([^"]+)"[^>]*>/i); + if (iframeMatch) { + let iframeUrl = iframeMatch[1]; + iframeUrl = iframeUrl.startsWith('//') ? `https:${iframeUrl}` : !iframeUrl.startsWith('http') ? `${host}${iframeUrl.startsWith('/') ? '' : '/'}${iframeUrl}` : iframeUrl; + return JSON.stringify({ parse: 1, url: iframeUrl, header: headers }); + } + + return JSON.stringify({ parse: 1, url: url, header: headers }); +} + +export default { init, home, homeVod, category, detail, search, play }; diff --git a/js/cat_哔哩.js b/js/cat_哔哩.js new file mode 100644 index 0000000..6f9bd10 --- /dev/null +++ b/js/cat_哔哩.js @@ -0,0 +1,538 @@ +import { load } from 'assets://js/lib/cat.js'; +import 'assets://js/lib/crypto-js.js'; + +var BILI_COOKIE = ''; +var BILI_GUEST_COOKIE = ''; // 自动获取的访客 Cookie +var extConfig = {}; + +const PLACEHOLDER = 'https://i0.hdslb.com/bfs/static/jinkela/video/asserts/nocover.png'; +const DEFAULT_TYPE = '音乐#新闻#白噪音#沙雕动漫#虾仁#短剧#电影#歌曲#演唱会#鬼畜#搞笑#脑洞乌托邦#相声#小品#戏曲#音乐#MV#舞曲#舞蹈#纪录片#健身#帕梅拉#武术#太极拳#广场舞#体育#球星#世界杯#UP主#小姐姐#女优#美食#食谱#荒野求生#旅游#风景#游戏#解说#演讲#考公考试#平面设计#软件教学'; // 分类兜底 + +function decodeHtml(str) { + if (!str) return ''; + return str.replace(/"/g, '"') + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/'/g, "'") + .replace(/ /g, ' '); +} + +function fmtDuration(str) { + if (!str) return ''; + let parts = str.split(':').map(Number); + let minutes = 0; + if (parts.length === 3) { + minutes = parts[0] * 60 + parts[1] + Math.round(parts[2] / 60); + } else if (parts.length === 2) { + minutes = parts[0] + Math.round(parts[1] / 60); + } + return minutes > 0 ? minutes + '分钟' : ''; +} + +function formatCount(num) { + if (!num && num !== 0) return ''; + let n = Number(num); + if (n >= 10000) { + let w = n / 10000; + return (w % 1 === 0 ? w.toFixed(0) : w.toFixed(1)) + '万'; + } + return n.toString(); +} + +const mixinKeyEncTab = [ + 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 +]; + +let wbiKeyCache = { key: '', hour: -1 }; + +function getMixinKey(orig) { + let temp = ''; + for (let i = 0; i < mixinKeyEncTab.length; i++) { + if (mixinKeyEncTab[i] < orig.length) temp += orig[mixinKeyEncTab[i]]; + } + return temp.slice(0, 32); +} + +async function updateWbiKey() { + const now = new Date(); + const hour = now.getHours(); + if (wbiKeyCache.key && wbiKeyCache.hour === hour) return; + + // 用当前有效的 Cookie 请求 wbi 密钥 + const ck = BILI_COOKIE || BILI_GUEST_COOKIE || 'buvid3=84B0395D-C9F2-C490-E92E-A09AB48FE26E71636infoc'; + try { + const resp = await req('https://api.bilibili.com/x/web-interface/nav', { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + 'Referer': 'https://www.bilibili.com', + 'Origin': 'https://www.bilibili.com', + 'Cookie': ck + } + }); + const data = JSON.parse(resp.content); + const imgUrl = data.data.wbi_img.img_url; + const subUrl = data.data.wbi_img.sub_url; + const imgKey = imgUrl.split('/').pop().split('.')[0]; + const subKey = subUrl.split('/').pop().split('.')[0]; + wbiKeyCache.key = getMixinKey(imgKey + subKey); + wbiKeyCache.hour = hour; + } catch (e) { + wbiKeyCache.key = ''; + } +} + +function encWbi(params, key) { + if (!key) return ''; + const sortedKeys = Object.keys(params).sort(); + const query = sortedKeys.map(k => { + const v = params[k]; + return encodeURIComponent(k) + '=' + encodeURIComponent(v).replace(/[!'()*]/g, ''); + }).join('&'); + const w_rid = CryptoJS.MD5(query + key).toString(); + return query + '&w_rid=' + w_rid; +} + +async function fetchGuestCookie() { + try { + const resp = await req('https://space.bilibili.com/2/video', { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + }, + timeout: 8000 + }); + if (resp && resp.headers) { + let cookies = resp.headers['set-cookie'] || []; + if (typeof cookies === 'string') cookies = [cookies]; + const parts = cookies.map(c => c.split(';')[0].trim()).filter(c => c); + if (parts.length > 0) BILI_GUEST_COOKIE = parts.join('; '); + } + } catch (e) { + } +} + +function getHeaders(useCookie) { + let ck = BILI_COOKIE || BILI_GUEST_COOKIE || 'buvid3=84B0395D-C9F2-C490-E92E-A09AB48FE26E71636infoc'; + return { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36', + 'Referer': 'https://www.bilibili.com', + 'Origin': 'https://www.bilibili.com', + 'Cookie': useCookie ? ck : 'buvid3=84B0395D-C9F2-C490-E92E-A09AB48FE26E71636infoc' + }; +} + +function searchHeaders() { + let ck = BILI_COOKIE || BILI_GUEST_COOKIE || 'buvid3=84B0395D-C9F2-C490-E92E-A09AB48FE26E71636infoc'; + return { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36', + 'Referer': 'https://www.bilibili.com', + 'Origin': 'https://www.bilibili.com', + 'Cookie': ck + }; +} + +async function init(config) { + let cfg = typeof config === 'string' ? JSON.parse(config) : (config || {}); + extConfig = cfg; + let cookieSource = cfg.cookie || ''; // 这里填cookie或外链URL + if (cookieSource) { + if (cookieSource.startsWith('http://') || cookieSource.startsWith('https://')) { + try { + let resp = await req(cookieSource, { headers: { 'User-Agent': 'Mozilla/5.0' }, timeout: 8000 }); + if (resp && resp.content) BILI_COOKIE = resp.content.trim(); + } catch (e) {} + } else { + BILI_COOKIE = cookieSource; + } + } + + if (!BILI_COOKIE) { + await fetchGuestCookie(); + } + + await updateWbiKey(); + return '{}'; +} + +async function home(filter) { + let typeStr = extConfig.type || ''; // 这里填分类URL + for (let retry = 0; retry < 2; retry++) { + if (typeStr.startsWith('http')) { + try { + let resp = await req(typeStr, { headers: { 'User-Agent': 'Mozilla/5.0' }, timeout: 8000 }); + if (resp && resp.content) { + let data = JSON.parse(resp.content); + if (data && data.class) return JSON.stringify(data); + } + } catch (e) { + if (retry === 1) print('网络分类加载失败,使用内置分类'); + else await new Promise(resolve => setTimeout(resolve, 1000)); + } + } else break; + } + let names = (typeStr.indexOf('#') > -1 ? typeStr : DEFAULT_TYPE).split('#').filter(n => n.trim()); + let classes = names.map(n => ({ type_id: n, type_name: n })); + + let filters = {}; + names.forEach(n => { + filters[n] = [ + { + key: 'order', + name: '排序', + value: [ + { n: '综合排序', v: 'totalrank' }, + { n: '最多点击', v: 'click' }, + { n: '最新发布', v: 'pubdate' }, + { n: '最多弹幕', v: 'dm' }, + { n: '最多收藏', v: 'stow' } + ] + }, + { + key: 'duration', + name: '时长', + value: [ + { n: '全部时长', v: '0' }, + { n: '60分钟以上', v: '4' }, + { n: '30~60分钟', v: '3' }, + { n: '10~30分钟', v: '2' }, + { n: '10分钟以下', v: '1' } + ] + } + ]; + }); + + return JSON.stringify({ class: classes, filters: filters }); +} + +async function homeVod() { return JSON.stringify({ list: [] }); } + +async function category(tid, pg, filterParams, extendParams) { + if (typeof tid === 'string' && tid.endsWith('_clicklink')) tid = tid.split('_')[0]; + let page = parseInt(pg) || 1; + let sel = {}; + if (filterParams) Object.assign(sel, filterParams); + if (extendParams) Object.assign(sel, extendParams); + let order = sel.order || 'totalrank'; + let duration = sel.duration || '0'; + let keyword = tid; + if (sel.tid) keyword += ' ' + sel.tid; + + await updateWbiKey(); + const params = { + search_type: 'video', + keyword: keyword, + order: order, + duration: duration, + page: page + }; + const query = encWbi(params, wbiKeyCache.key); + const url = 'https://api.bilibili.com/x/web-interface/wbi/search/type?' + query; + + for (let retry = 0; retry < 3; retry++) { + try { + let resp = await req(url, { headers: searchHeaders(), timeout: 8000 }); + if (!resp || !resp.content) continue; + let data = JSON.parse(resp.content); + let items = (data.data && data.data.result) || []; + let list = items.map(v => { + let img = v.pic || ''; + if (img.startsWith('//')) img = 'https:' + img; + if (!img) img = PLACEHOLDER; + let durationStr = fmtDuration(v.duration); + let playStr = formatCount(v.play); + let danmuStr = formatCount(v.danmaku); + let remarkParts = []; + if (durationStr) remarkParts.push(durationStr); + if (playStr) remarkParts.push(playStr + '播放'); + if (danmuStr) remarkParts.push(danmuStr + '弹幕'); + return { + vod_id: v.bvid + '@' + v.aid, + vod_name: decodeHtml(v.title.replace(/<[^>]*>/g, '')), + vod_pic: img, + vod_remarks: remarkParts.join(' | ') + }; + }); + let hasMore = list.length >= 20; + if (!list.length) list.push({ vod_id: '0', vod_name: '无结果', vod_pic: PLACEHOLDER }); + return JSON.stringify({ list, page, pagecount: hasMore ? page + 1 : page, limit: 20, total: list.length }); + } catch (e) { + if (retry === 2) { + return JSON.stringify({ list: [{ vod_id: '0', vod_name: '请求失败', vod_pic: PLACEHOLDER }], page: 1, pagecount: 1 }); + } + await new Promise(resolve => setTimeout(resolve, 1500)); + } + } +} + +async function detail(ids) { + let idStr = Array.isArray(ids) ? ids[0] : ids; + if (!idStr || idStr.indexOf('@') === -1) return JSON.stringify({ list: [] }); + let parts = idStr.split('@'), aid = parts[1]; + try { + let viewUrl = 'https://api.bilibili.com/x/web-interface/view?aid=' + aid; + let resp = await req(viewUrl, { headers: getHeaders(true) }); + let info = JSON.parse(resp.content).data; + if (!info) return JSON.stringify({ list: [] }); + + let title = decodeHtml((info.title || '').replace(/<[^>]*>/g, '')); + let pic = info.pic || PLACEHOLDER; + if (pic.startsWith('//')) pic = 'https:' + pic; + let desc = info.desc || ''; + let duration = info.duration || 0; + let owner = info.owner || {}; + let stat = info.stat || {}; + let tname = info.tname || ''; + let pubdate = info.pubdate || 0; + let upName = owner.name || '未知'; + + let follower = ''; + try { + let statUrl = 'https://api.bilibili.com/x/relation/stat?vmid=' + owner.mid; + let statResp = await req(statUrl, { headers: getHeaders(false) }); + let statData = JSON.parse(statResp.content).data; + if (statData && statData.follower > 0) { + follower = formatCount(statData.follower); + } + } catch (e) {} + + let director = '🆙 ' + '[a=cr:' + JSON.stringify({id: upName + '_clicklink', name: upName}) + '/]' + upName + '[/a]'; + if (follower) director += ' 👥 ' + follower; + + let actor = '▶' + (stat.view || 0) + ' 💬' + (stat.danmaku || 0) + ' 👍' + (stat.like || 0) + ' 💰' + (stat.coin || 0) + ' ⭐' + (stat.favorite || 0); + let year = pubdate ? new Date(pubdate * 1000).getFullYear().toString() : ''; + + let pages = info.pages || []; + let bilibiliPlayUrls = pages.map(p => { + let epTitle = (p.part || 'P' + p.page).replaceAll('#', '﹟').replaceAll('$', '﹩'); + return epTitle + '$' + aid + '+' + p.cid; + }); + + let relatedPlayUrls = []; + try { + let relatedUrl = 'https://api.bilibili.com/x/web-interface/archive/related?aid=' + aid; + let relResp = await req(relatedUrl, { headers: getHeaders(true) }); + let relData = JSON.parse(relResp.content).data; + if (relData && Array.isArray(relData)) { + relData.forEach(rd => { + let cid = rd.cid; + let rdTitle = decodeHtml((rd.title || '').replaceAll('#', '﹟').replaceAll('$', '﹩')); + let aaid = rd.aid; + relatedPlayUrls.push(rdTitle + '$' + aaid + '+' + cid); + }); + } + } catch (e) {} + + let playFrom = 'B站'; + let playUrl = bilibiliPlayUrls.join('#'); + if (relatedPlayUrls.length > 0) { + playFrom += '$$$相关推荐'; + playUrl += '$$$' + relatedPlayUrls.join('#'); + } + + return JSON.stringify({ + list: [{ + vod_id: idStr, + vod_name: title, + vod_pic: pic, + vod_remarks: Math.floor(duration / 60) + '分钟', + vod_content: desc, + vod_play_from: playFrom, + vod_play_url: playUrl, + vod_director: director, + vod_actor: actor, + type_name: tname, + vod_year: year + }] + }); + } catch (e) { + return JSON.stringify({ list: [] }); + } +} + +async function play(flag, id) { + if (!id || id.indexOf('+') === -1) return JSON.stringify({ parse: 0, url: '' }); + let parts = id.split('+'), aid = parts[0], cid = parts[1]; + try { + let dashUrl = 'https://api.bilibili.com/x/player/playurl?avid=' + aid + '&cid=' + cid + '&qn=127&fnval=4048&fourk=1'; + let resp = await req(dashUrl, { headers: getHeaders(true) }); + let data = JSON.parse(resp.content); + if (!data.data || !data.data.dash) return JSON.stringify({ parse: 0, url: '' }); + + let dash = data.data.dash; + let videos = dash.video || []; + let duration = dash.duration || 0; + let minBuf = dash.min_buffer_time || 1.5; + + let rawAudios = dash.audio ? [...dash.audio] : []; + if (dash.flac && dash.flac.audio) { + if (Array.isArray(dash.flac.audio)) rawAudios.push(...dash.flac.audio); + else rawAudios.push(dash.flac.audio); + } + if (dash.dolby && dash.dolby.audio) { + if (Array.isArray(dash.dolby.audio)) rawAudios.push(...dash.dolby.audio); + else rawAudios.push(dash.dolby.audio); + } + + let audioList = []; + let seenAudio = new Set(); + + const audioPriority = { 30251: 1, 30250: 2, 30280: 3, 30232: 4, 30216: 5 }; + rawAudios.sort((a, b) => { + let pA = audioPriority[a.id] || 99; + let pB = audioPriority[b.id] || 99; + return pA - pB; + }); + + for (let a of rawAudios) { + if (a && a.id && !seenAudio.has(a.id)) { + seenAudio.add(a.id); + audioList.push(a); + } + } + + let formats = data.data.support_formats || []; + if (formats.length === 0) { + let aq = data.data.accept_quality || []; + let ad = data.data.accept_description || []; + formats = aq.map((q, i) => ({ quality: q, display_desc: ad[i] || q.toString() })); + } + let qnMap = {}; + formats.forEach(f => { qnMap[f.quality] = f.display_desc || f.new_description || f.quality.toString(); }); + + let urlsArray = []; + let seenStreams = new Set(); + + videos.sort((a, b) => b.id - a.id); + for (let v of videos) { + let qn = v.id; + + let codecName = ''; + if (v.codecs) { + if (v.codecs.includes('avc')) codecName = 'AVC'; + else if (v.codecs.includes('hev') || v.codecs.includes('hvc')) codecName = 'HEVC'; + else if (v.codecs.includes('av01')) codecName = 'AV1'; + else if (v.codecs.includes('dvh') || v.codecs.includes('dve')) codecName = '杜比'; + } + + let streamKey; + if (qn <= 80) { + streamKey = qn.toString(); + codecName = ''; + } else { + streamKey = qn + '_' + codecName; + } + + if (!seenStreams.has(streamKey)) { + seenStreams.add(streamKey); + + let baseName = qnMap[qn] || ('画质' + qn); + if (qn === 126 || codecName === '杜比') { + baseName = '杜比视界'; + } else if (qn === 125) { + baseName = 'HDR真彩色(' + (codecName || 'HEVC') + ')'; + } else { + if (codecName) baseName += '(' + codecName + ')'; + } + + let mpd = buildMpd(v, audioList, duration, minBuf); + let dataUri = 'data:application/dash+xml;base64,' + base64Encode(mpd); + urlsArray.push({ name: baseName, url: dataUri, qn: qn, bw: v.bandwidth || 0 }); + } + } + + if (urlsArray.length === 0) return JSON.stringify({ parse: 0, url: '' }); + + urlsArray.sort((a, b) => { + if (b.qn !== a.qn) return b.qn - a.qn; + return b.bw - a.bw; + }); + + let arr = []; + urlsArray.forEach(q => { + arr.push(q.name); + arr.push(q.url); + }); + + return JSON.stringify({ + parse: 0, + jx: 0, + url: arr, + danmaku: 'https://api.bilibili.com/x/v1/dm/list.so?oid=' + cid, + header: JSON.stringify(getHeaders(true)) + }); + } catch (e) { return JSON.stringify({ parse: 0, url: '' }); } +} + +function buildMpd(video, audioList, duration, minBuf) { + let mpd = '\n'; + mpd += '\n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' ' + (video.baseUrl || video.base_url).replace(/&/g, '&') + '\n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' \n'; + + if (audioList && audioList.length > 0) { + for (let audio of audioList) { + let label = ''; + if (audio.id === 30251) label = '无损Hi-Res'; + else if (audio.id === 30250) label = '杜比全景声'; + else if (audio.id === 30280) label = '192k高音质'; + else if (audio.id === 30232) label = '132k标准'; + else if (audio.id === 30216) label = '64k流畅'; + else label = '音质_' + audio.id; + + mpd += ' \n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' ' + (audio.baseUrl || audio.base_url).replace(/&/g, '&') + '\n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' \n'; + } + } + + mpd += ' \n'; + return mpd; +} + +function base64Encode(str) { + let bytes = []; + for (let i = 0; i < str.length; i++) { + let c = str.charCodeAt(i); + if (c < 0x80) bytes.push(c); + else if (c < 0x800) { + bytes.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)); + } else if (c < 0x10000) { + bytes.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); + } else { + bytes.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); + } + } + let base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + let result = ''; + for (let i = 0; i < bytes.length; i += 3) { + let b1 = bytes[i], b2 = bytes[i + 1] || 0, b3 = bytes[i + 2] || 0; + result += base64.charAt(b1 >> 2); + result += base64.charAt(((b1 & 0x3) << 4) | (b2 >> 4)); + result += (i + 1 < bytes.length) ? base64.charAt(((b2 & 0xf) << 2) | (b3 >> 6)) : '='; + result += (i + 2 < bytes.length) ? base64.charAt(b3 & 0x3f) : '='; + } + return result; +} + +async function search(keyword, quick, pg) { return category(keyword, pg || '1', {}, {}); } +async function searchContent(keyword, quick, page) { return search(keyword, quick, page); } + +export function __jsEvalReturn() { + return { init, home, homeVod, category, detail, play, search, searchContent }; +} \ No newline at end of file diff --git a/js/cat_哔哩影视.js b/js/cat_哔哩影视.js new file mode 100644 index 0000000..564bfce --- /dev/null +++ b/js/cat_哔哩影视.js @@ -0,0 +1,362 @@ +import { load } from 'assets://js/lib/cat.js'; + +var BILI_COOKIE = ''; +var isLogin = false; +var isVip = false; + +function getHeaders(needCookie) { + var cookie = BILI_COOKIE || 'buvid3=84B0395D-C9F2-C490-E92E-A09AB48FE26E71636infoc'; + return { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0', + 'Referer': 'https://www.bilibili.com', + 'Cookie': needCookie ? cookie.replace(/,/g, '%2C') : 'buvid3=84B0395D-C9F2-C490-E92E-A09AB48FE26E71636infoc' + }; +} + +var FIXED_FILTERS = [ + { name: '排序', key: 'order', value: [ + { v: '0', n: '更新时间' }, { v: '1', n: '弹幕数量' }, { v: '2', n: '播放数量' }, + { v: '3', n: '追看人数' }, { v: '4', n: '最高评分' }, { v: '5', n: '开播时间' }, { v: '6', n: '上映时间' } + ]}, + { name: '付费', key: 'season_status', value: [ + { v: '-1', n: '全部' }, { v: '1', n: '免费' }, { v: '2%2C6', n: '付费' }, { v: '4%2C6', n: '大会员' } + ]} +]; +var ALL_FILTERS = {}; +['1','3','4','2','7','5'].forEach(function(t) { ALL_FILTERS[t] = FIXED_FILTERS; }); + +async function init(config) { + var json = {}; + try { json = JSON.parse(config); } catch (e) {} + + var cookieSource = json.cookie || ''; // 这里填cookie或外链URL + if (cookieSource) { + if (cookieSource.startsWith('http://') || cookieSource.startsWith('https://')) { + try { + var resp = await req(cookieSource, { headers: { 'User-Agent': 'Mozilla/5.0' }, timeout: 8000 }); + if (resp && resp.content) { + BILI_COOKIE = resp.content.trim(); + } + } catch (e) { BILI_COOKIE = ''; } + } else { + BILI_COOKIE = cookieSource; + } + } + + try { + var navResp = await req('https://api.bilibili.com/x/web-interface/nav', { headers: getHeaders(true) }); + var navData = JSON.parse(navResp.content); + if (navData.data) { + isLogin = navData.data.isLogin || false; + isVip = navData.data.vipStatus === 1; + } + } catch (e) {} + return '{}'; +} + +async function home(filter) { + var classes = [ + { type_id: '1', type_name: '番剧' }, + { type_id: '4', type_name: '国创' }, + { type_id: '2', type_name: '电影' }, + { type_id: '5', type_name: '电视剧' }, + { type_id: '3', type_name: '纪录' }, + { type_id: '7', type_name: '综艺' } + ]; + return JSON.stringify({ class: classes, filters: ALL_FILTERS }); +} + +async function homeVod() { return JSON.stringify({ list: [] }); } + +async function category(tid, pg, filterParams, extendParams) { + var page = parseInt(pg) || 1; + var typeId = tid; + var params = { order: '0', season_status: '-1', style_id: -1, sort: -1, area: -1 }; + var sel = {}; + if (typeof filterParams === 'object') Object.assign(sel, filterParams); + if (typeof extendParams === 'object') Object.assign(sel, extendParams); + if (sel.order) params.order = sel.order; + if (sel.season_status) params.season_status = sel.season_status; + var query = 'order=' + params.order + '&season_status=' + params.season_status + + '&style_id=' + params.style_id + '&sort=' + params.sort + '&area=' + params.area + + '&pagesize=20&type=1&season_type=' + typeId + '&page=' + page; + var url = 'https://api.bilibili.com/pgc/season/index/result?' + query; + var resp = await req(url, { headers: getHeaders(false) }); + if (!resp || !resp.content) return JSON.stringify({ list: [] }); + var data = JSON.parse(resp.content); + var list = []; + var seasons = (data.data && data.data.list) ? data.data.list : []; + seasons.forEach(function(item) { + list.push({ + vod_id: item.season_id, + vod_name: item.title.replace(/<[^>]*>/g, ''), + vod_pic: item.cover, + vod_remarks: item.index_show || '' + }); + }); + var hasMore = data.data && data.data.has_next === 1; + return JSON.stringify({ list: list, page: page, pagecount: hasMore ? page + 1 : page, limit: 20, total: hasMore ? (page+1)*20 : page*20 }); +} + +async function detail(ids) { + var seasonId = Array.isArray(ids) ? ids[0] : ids; + if (!seasonId) return JSON.stringify({ list: [] }); + var detailUrl = 'https://api.bilibili.com/pgc/view/web/season?season_id=' + seasonId; + var resp = await req(detailUrl, { headers: getHeaders(true) }); + if (!resp || !resp.content) return JSON.stringify({ list: [] }); + var data = JSON.parse(resp.content); + var season = data.result; + if (!season) return JSON.stringify({ list: [] }); + + // 基础信息 + var title = season.title || '未知剧名'; + var cover = season.cover || ''; + var desc = season.evaluate || ''; + var actors = season.actors || ''; + var episodes = season.episodes || []; + var typeName = season.share_sub_title || ''; + var areas = (season.areas && season.areas[0]) ? season.areas[0].name : ''; + var pubDate = (season.publish && season.publish.pub_time) ? season.publish.pub_time.substring(0, 4) : ''; + var newEpDesc = (season.new_ep && season.new_ep.desc) ? season.new_ep.desc : ''; + var stat = season.stat || {}; + var danmakus = stat.danmakus || 0; + var likes = stat.likes || 0; + var coins = stat.coins || 0; + var favorites = stat.favorites || 0; + + var scoreText = ''; + if (season.rating && season.rating.score) { + scoreText = '评分: ' + season.rating.score + ' ' + (season.subtitle || ''); + } else { + scoreText = '暂无评分' + ' ' + (season.subtitle || ''); + } + + function zh(num) { + if (Number(num) > 1e8) return (num / 1e8).toFixed(2) + '亿'; + if (Number(num) > 1e4) return (num / 1e4).toFixed(2) + '万'; + return num.toString(); + } + + var statusText = '弹幕: ' + zh(danmakus) + ' 点赞: ' + zh(likes) + ' 投币: ' + zh(coins) + ' 追番追剧: ' + zh(favorites); + + var playUrls = episodes.map(function(ep) { + var part = (ep.share_copy || ep.title || '剧集').replace('#', '\uff03').replace('$', '\uff04'); + return part + '$' + ep.aid + '+' + ep.cid; + }).join('#'); + + return JSON.stringify({ + list: [{ + vod_id: seasonId, + vod_name: title, + vod_pic: cover, + vod_remarks: episodes.length + '集 ' + (newEpDesc || ''), + vod_content: desc, + vod_actor: actors, + vod_director: scoreText, + vod_area: areas, + type_name: typeName, + vod_year: pubDate, + vod_play_from: '哔哩影视', + vod_play_url: playUrls + }] + }); +} + +async function search(keyword, quick, pg) { + var page = pg || '1'; + var keywordEnc = encodeURIComponent(keyword); + var ftUrl = 'https://api.bilibili.com/x/web-interface/search/type?search_type=media_ft&keyword=' + keywordEnc + '&page=' + page; + var bangumiUrl = 'https://api.bilibili.com/x/web-interface/search/type?search_type=media_bangumi&keyword=' + keywordEnc + '&page=' + page; + var results = []; + try { + var [ftResp, bangumiResp] = await Promise.all([ + req(ftUrl, { headers: getHeaders(false) }), + req(bangumiUrl, { headers: getHeaders(false) }) + ]); + [ftResp, bangumiResp].forEach(function(resp) { + if (!resp || !resp.content) return; + var data = JSON.parse(resp.content); + var items = (data.data && data.data.result) ? data.data.result : []; + items.forEach(function(item) { + results.push({ + vod_id: item.season_id, + vod_name: item.title.replace(/<[^>]*>/g, ''), + vod_pic: item.cover, + vod_remarks: item.index_show || '' + }); + }); + }); + } catch (e) {} + var seen = new Set(); + var finalList = []; + results.forEach(function(item) { if (!seen.has(item.vod_id)) { seen.add(item.vod_id); finalList.push(item); } }); + return JSON.stringify({ list: finalList, page: parseInt(page), pagecount: 1, limit: finalList.length, total: finalList.length }); +} + +async function play(flag, id) { + if (!id || id.indexOf('+') === -1) return JSON.stringify({ parse: 0, url: '' }); + var parts = id.split('+'); + var aid = parts[0], cid = parts[1]; + + // ===== 优先 DASH ===== + try { + var dashUrl = 'https://api.bilibili.com/x/player/playurl?avid=' + aid + '&cid=' + cid + '&qn=127&fnval=4048&fnver=0&fourk=1&platform=pc'; + var resp = await req(dashUrl, { headers: getHeaders(true), timeout: 10000 }); + var json = JSON.parse(resp.content); + var result = json.result || json.data; + if (result && result.dash) { + var dash = result.dash; + var videos = dash.video || []; + var audios = dash.audio || []; + var duration = dash.duration || 0; + var minBuf = dash.min_buffer_time || 1.5; + + var formats = result.support_formats || []; + if (formats.length === 0) { + var aq = result.accept_quality || []; + var ad = result.accept_description || []; + formats = aq.map(function(q, i) { return { quality: q, display_desc: ad[i] || q.toString() }; }); + } + var qnMap = {}; + formats.forEach(function(f) { qnMap[f.quality] = f.display_desc || f.new_description || f.quality.toString(); }); + + var audio = null; + for (var i = 0; i < audios.length; i++) { + if (!audio || audios[i].id > audio.id) audio = audios[i]; + } + + var urlList = []; + var seenQn = new Set(); + videos.sort(function(a, b) { return b.id - a.id; }); + for (var j = 0; j < videos.length; j++) { + var v = videos[j]; + var qn = v.id; + if (qnMap[qn] && !seenQn.has(qn)) { + seenQn.add(qn); + var mpd = buildMpd(v, audio, duration, minBuf); + var dataUri = 'data:application/dash+xml;base64,' + base64Encode(mpd); + urlList.push(qnMap[qn], dataUri); + } + } + if (urlList.length > 0) { + return JSON.stringify({ + parse: 0, jx: 0, + url: urlList, + danmaku: 'https://api.bilibili.com/x/v1/dm/list.so?oid=' + cid, + header: JSON.stringify(getHeaders(true)) + }); + } + } + } catch (e) {} + + // ===== FLV 后备:利用真实 quality 去重 ===== + try { + var infoUrl = 'https://api.bilibili.com/pgc/player/web/playurl?avid=' + aid + '&cid=' + cid + '&qn=0&fnval=1&fourk=1'; + var infoResp = await req(infoUrl, { headers: getHeaders(true), timeout: 5000 }); + var infoJson = JSON.parse(infoResp.content); + var infoResult = infoJson.result || infoJson.data; + var acceptQuality = (infoResult && infoResult.accept_quality) ? infoResult.accept_quality : [80, 64, 32, 16]; + var acceptDesc = (infoResult && infoResult.accept_description) ? infoResult.accept_description : []; + + var descMap = {}; + for (var i = 0; i < acceptQuality.length; i++) { + descMap[acceptQuality[i]] = acceptDesc[i] || acceptQuality[i].toString(); + } + + var collected = {}; // key: 真实qn, value: {url, desc} + // 从高到低遍历 + acceptQuality.sort(function(a,b){ return b - a; }); + for (var i = 0; i < acceptQuality.length; i++) { + var qn = acceptQuality[i]; + try { + var flvUrl = 'https://api.bilibili.com/pgc/player/web/playurl?avid=' + aid + '&cid=' + cid + '&qn=' + qn + '&fnval=1&fourk=1'; + var flvResp = await req(flvUrl, { headers: getHeaders(true), timeout: 5000 }); + var flvJson = JSON.parse(flvResp.content); + var flvResult = flvJson.result || flvJson.data; + if (flvResult && flvResult.durl && flvResult.durl.length > 0) { + // 以服务器返回的实际质量为准 + var realQn = flvResult.quality !== undefined ? flvResult.quality : qn; + if (!collected[realQn]) { + var name = descMap[realQn] || flvResult.format || realQn.toString(); + collected[realQn] = { url: flvResult.durl[0].url, desc: name }; + } + } + } catch (e2) {} + } + + var names = [], urls = []; + // 按真实画质从高到低输出 + var keys = Object.keys(collected).sort(function(a,b){ return b - a; }); + for (var k = 0; k < keys.length; k++) { + var item = collected[keys[k]]; + names.push(item.desc); + urls.push(item.url); + } + + if (urls.length > 0) { + var urlList = []; + for (var m = 0; m < names.length; m++) { + urlList.push(names[m], urls[m]); + } + return JSON.stringify({ + parse: 0, jx: 0, + url: urlList, + danmaku: 'https://api.bilibili.com/x/v1/dm/list.so?oid=' + cid, + header: JSON.stringify(getHeaders(true)) + }); + } + } catch (e3) {} + + return JSON.stringify({ parse: 0, url: '' }); +} + +function buildMpd(video, audio, duration, minBuf) { + var mpd = '\n'; + mpd += '\n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' ' + (video.baseUrl || video.base_url || '').replace(/&/g, '&') + '\n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' \n'; + if (audio) { + mpd += ' \n'; + mpd += ' \n'; + mpd += ' ' + (audio.baseUrl || audio.base_url || '').replace(/&/g, '&') + '\n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' \n'; + mpd += ' \n'; + } + mpd += ' \n'; + return mpd; +} + +function base64Encode(str) { + var bytes = []; + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + if (c < 0x80) bytes.push(c); + else if (c < 0x800) bytes.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)); + else if (c < 0x10000) bytes.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); + else bytes.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); + } + var base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + var result = ''; + for (var i = 0; i < bytes.length; i += 3) { + var b1 = bytes[i], b2 = bytes[i + 1] || 0, b3 = bytes[i + 2] || 0; + result += base64.charAt(b1 >> 2); + result += base64.charAt(((b1 & 0x3) << 4) | (b2 >> 4)); + result += (i + 1 < bytes.length) ? base64.charAt(((b2 & 0xf) << 2) | (b3 >> 6)) : '='; + result += (i + 2 < bytes.length) ? base64.charAt(b3 & 0x3f) : '='; + } + return result; +} + +export function __jsEvalReturn() { + return { init, home, homeVod, category, detail, search, play }; +} \ No newline at end of file