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(/