From 7f3d6d31ce4a1d87b8a3ed77e1f52429072ea15f Mon Sep 17 00:00:00 2001
From: XC-Pro <19840+xc-pro@noreply.example.org>
Date: Thu, 13 Aug 2026 18:03:44 +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=8Capi=E3=80=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
api/咖啡直播.py | 375 +++++++++++++++++
api/极影.js | 1068 +++++++++++++++++++++++++++++++++++++++++++++++
api/荐片.js | 152 +++++++
api/金牌影院.js | 169 ++++++++
4 files changed, 1764 insertions(+)
create mode 100644 api/咖啡直播.py
create mode 100644 api/极影.js
create mode 100644 api/荐片.js
create mode 100644 api/金牌影院.js
diff --git a/api/咖啡直播.py b/api/咖啡直播.py
new file mode 100644
index 0000000..b0c181b
--- /dev/null
+++ b/api/咖啡直播.py
@@ -0,0 +1,375 @@
+# coding=utf-8
+#!/usr/bin/env python3
+# @name 咖啡直播
+# @author 转自 OmniBox JS
+# @description 体育赛事录像回放 + 直播(足球/篮球/NBA)
+# @version 2.0.0
+
+import json
+import requests
+
+
+class Spider:
+
+ def getName(self):
+ return "咖啡直播"
+
+ def getDependence(self):
+ return []
+
+ def init(self, extend=""):
+ self.host = "https://kafeizhibo.cc"
+ self.headers = {
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
+ "Accept": "application/json, text/plain, */*",
+ "Referer": "https://kafeizhibo.com/live/all"
+ }
+
+ def log(self, msg):
+ print("[咖啡直播] " + str(msg))
+
+ # =========================================================
+ # 工具
+ # =========================================================
+
+ def _normalize_url(self, path):
+ if not path:
+ return ""
+ if path.startswith("http"):
+ return path
+ if path.startswith("//"):
+ return "https:" + path
+ return self.host + ("" if path.startswith("/") else "/") + path
+
+ def _get(self, path, params=None, referer=None):
+ h = dict(self.headers)
+ if referer:
+ h["Referer"] = referer
+ resp = requests.get(self.host + path, headers=h, params=params, timeout=10)
+ return resp.json()
+
+ # =========================================================
+ # 直播部分
+ # =========================================================
+
+ def _fetch_live_all(self):
+ """GET /api/v1/archor — 返回所有正在直播的频道"""
+ return self._get("/api/v1/archor", referer=self.host + "/live/all")
+
+ def _parse_live_list(self, items, category_filter=None):
+ """
+ category: 1=足球, 2=篮球, None=全部
+ 每个 archor 代表一个独立直播频道(同一场球可能有多个频道)
+ 合并同 match_id 的频道到一个 vod,多线路在 detail 里处理
+ """
+ # 按 room_id 去重(同一 room_id 只取第一个,避免重复)
+ seen_rooms = set()
+ result = []
+ for item in items:
+ if category_filter and item.get("category") != category_filter:
+ continue
+ room_id = str(item.get("room_id", ""))
+ if room_id in seen_rooms:
+ continue
+ seen_rooms.add(room_id)
+
+ home = item.get("home_team", "")
+ away = item.get("away_team", "")
+ league = item.get("league_name", "")
+ h_score = item.get("home_score", 0)
+ a_score = item.get("away_score", 0)
+ title = "{} vs {} ({})".format(home, away, league)
+
+ pic = self._normalize_url(item.get("screenshot", ""))
+ if not pic or "default" in pic:
+ mi = item.get("match_info") or {}
+ pic = mi.get("home_team_logo", "")
+
+ result.append({
+ "vod_id": "live_{}".format(room_id),
+ "vod_name": "🔴 " + title,
+ "vod_pic": pic,
+ "vod_remarks": "{} - {} | {}".format(h_score, a_score, item.get("name", "")),
+ })
+ return result
+
+ def _detail_live(self, room_id):
+ """GET /api/v1/room/{room_id} — 获取直播间多线路"""
+ try:
+ data = self._get(
+ "/api/v1/room/{}".format(room_id),
+ referer=self.host + "/room/{}".format(room_id)
+ )
+ if data.get("code") != 200 or not data.get("data"):
+ return {"list": []}
+
+ d = data["data"]
+ room_info = d.get("room_info", {})
+ signals = d.get("signals", [])
+
+ home = room_info.get("home_team", "")
+ away = room_info.get("away_team", "")
+ league = room_info.get("league", "")
+ h_score = room_info.get("home_score", 0)
+ a_score = room_info.get("away_score", 0)
+ title = "{} vs {} ({})".format(home, away, league)
+
+ teams = d.get("teams", {})
+ pic = (teams.get("home") or {}).get("logo", "")
+
+ # 每条 signal 是一个线路(官方直播/原声直播)
+ episodes = []
+ for sig in signals:
+ url = sig.get("stream_url", "")
+ if url:
+ name = sig.get("name", "线路")
+ episodes.append("{}${}".format(name, url))
+
+ # 如果 signals 为空,fallback 到 archor
+ if not episodes:
+ archor = d.get("archor", {})
+ url = archor.get("stream_url", "")
+ if url:
+ episodes.append("{}${}".format(archor.get("name", "直播"), url))
+
+ vod = {
+ "vod_id": "live_{}".format(room_id),
+ "vod_name": "🔴 " + title,
+ "vod_pic": pic,
+ "vod_content": "{} {} vs {},比分 {} - {}".format(
+ league, home, away, h_score, a_score
+ ),
+ "vod_play_from": "直播线路",
+ "vod_play_url": "#".join(episodes),
+ }
+ return {"list": [vod]}
+ except Exception as e:
+ self.log("直播详情失败: " + str(e))
+ return {"list": []}
+
+ # =========================================================
+ # 录像部分
+ # =========================================================
+
+ def _fetch_recordings(self, page=1, size=30, league=None, type_id=None):
+ params = {"page": page, "size": size}
+ if league:
+ params["league"] = league
+ elif type_id and type_id not in ("all", "nba", "live_all", "live_1", "live_2"):
+ params["type"] = type_id
+ h = dict(self.headers)
+ h["Referer"] = self.host + "/pc/replay"
+ resp = requests.get(self.host + "/api/v1/recordings", headers=h, params=params, timeout=10)
+ return resp.json()
+
+ def _parse_video_list(self, items):
+ result = []
+ for item in items:
+ title = "{} vs {} ({})".format(
+ item["home_team"], item["away_team"], item["league_name"]
+ )
+ score = "{} - {}".format(item["home_score"], item["away_score"])
+ pic = item.get("cover_image", "")
+ if pic and not pic.startswith("http"):
+ pic = self._normalize_url(pic)
+ if not pic or "default_cover" in pic:
+ pic = item.get("home_team_logo", "")
+ remarks = "{} | {} | {}个录像".format(
+ score, item["start_time"], item.get("recording_count", 0)
+ )
+ result.append({
+ "vod_id": str(item["match_id"]),
+ "vod_name": title,
+ "vod_pic": pic,
+ "vod_remarks": remarks,
+ })
+ return result
+
+ def _detail_recording(self, vid):
+ try:
+ h = dict(self.headers)
+ h["Referer"] = self.host + "/pc/replay"
+ resp = requests.get(
+ "{}/api/v1/match/{}/recordings".format(self.host, vid),
+ headers=h,
+ timeout=10
+ )
+ data = resp.json()
+ if data.get("code") != 200 or not data.get("data"):
+ return {"list": []}
+
+ match = data["data"]["match"]
+ replays = data["data"].get("replays", [])
+ highlights = data["data"].get("highlights", [])
+
+ title = "{} vs {} ({})".format(
+ match["home_team"], match["away_team"], match["league_name"]
+ )
+ pic = match.get("home_team_logo") or match.get("away_team_logo") or ""
+
+ episodes = []
+ for idx, rec in enumerate(replays):
+ if rec.get("video_url"):
+ name = rec.get("title") or "录像{}".format(idx + 1)
+ episodes.append("{}${}".format(name, rec["video_url"]))
+ for idx, rec in enumerate(highlights):
+ if rec.get("video_url"):
+ name = rec.get("title") or "集锦{}".format(idx + 1)
+ episodes.append("{}${}".format(name, rec["video_url"]))
+
+ vod = {
+ "vod_id": str(vid),
+ "vod_name": title,
+ "vod_pic": pic,
+ "vod_content": "{} {} {} vs {},比分 {} - {},比赛时间:{}".format(
+ match["league_name"], match.get("match_round", ""),
+ match["home_team"], match["away_team"],
+ match["home_score"], match["away_score"],
+ match["start_time"]
+ ),
+ "vod_play_from": "录像源",
+ "vod_play_url": "#".join(episodes),
+ }
+ return {"list": [vod]}
+ except Exception as e:
+ self.log("录像详情失败: " + str(e))
+ return {"list": []}
+
+ # =========================================================
+ # FongMi 接口
+ # =========================================================
+
+ def homeContent(self, filter):
+ categories = [
+ # 直播分类
+ {"type_id": "live_all", "type_name": "🔴 直播全部"},
+ {"type_id": "live_1", "type_name": "🔴 直播足球"},
+ {"type_id": "live_2", "type_name": "🔴 直播篮球"},
+ # 录像分类
+ {"type_id": "all", "type_name": "录像全部"},
+ {"type_id": "1", "type_name": "录像足球"},
+ {"type_id": "2", "type_name": "录像篮球"},
+ {"type_id": "nba", "type_name": "录像NBA"},
+ ]
+ # 首页展示直播列表
+ try:
+ data = self._fetch_live_all()
+ vod_list = self._parse_live_list(data.get("data", [])) if data.get("code") == 200 else []
+ except Exception as e:
+ self.log("首页失败: " + str(e))
+ vod_list = []
+ return {"class": categories, "list": vod_list}
+
+ def homeVideoContent(self):
+ return {"list": []}
+
+ def categoryContent(self, tid, pg, filter, extend):
+ pg = int(pg) if pg else 1
+
+ # ---- 直播分类 ----
+ if tid in ("live_all", "live_1", "live_2"):
+ try:
+ data = self._fetch_live_all()
+ if data.get("code") == 200:
+ cat = None if tid == "live_all" else int(tid.split("_")[1])
+ vod_list = self._parse_live_list(data.get("data", []), category_filter=cat)
+ else:
+ vod_list = []
+ except Exception as e:
+ self.log("直播分类失败: " + str(e))
+ vod_list = []
+ return {"list": vod_list, "page": 1, "pagecount": 1, "limit": 100, "total": len(vod_list)}
+
+ # ---- 录像分类 ----
+ try:
+ if tid == "nba":
+ data = self._fetch_recordings(pg, 20, league="NBA")
+ size = 20
+ elif tid == "all":
+ data = self._fetch_recordings(pg, 30)
+ size = 30
+ else:
+ data = self._fetch_recordings(pg, 30, type_id=tid)
+ size = 30
+
+ vod_list = []
+ pagecount = 1
+ if data.get("code") == 200 and data.get("data"):
+ vod_list = self._parse_video_list(data["data"])
+ pagecount = pg + 1 if len(data["data"]) == size else pg
+ except Exception as e:
+ self.log("录像分类失败: " + str(e))
+ vod_list = []
+ pagecount = 1
+
+ return {"list": vod_list, "page": pg, "pagecount": pagecount, "limit": 30, "total": len(vod_list)}
+
+ def detailContent(self, ids):
+ vid = ids[0] if isinstance(ids, list) and ids else str(ids)
+ if vid.startswith("live_"):
+ room_id = vid[5:] # 去掉 "live_" 前缀
+ return self._detail_live(room_id)
+ else:
+ return self._detail_recording(vid)
+
+ def searchContent(self, key, quick, pg=1):
+ if not key:
+ return {"list": []}
+ keyword = key.lower()
+ result = []
+
+ # 搜索直播
+ try:
+ data = self._fetch_live_all()
+ if data.get("code") == 200:
+ for item in data.get("data", []):
+ if (keyword in item.get("home_team", "").lower()
+ or keyword in item.get("away_team", "").lower()
+ or keyword in item.get("league_name", "").lower()
+ or keyword in item.get("title", "").lower()):
+ room_id = str(item.get("room_id", ""))
+ home = item.get("home_team", "")
+ away = item.get("away_team", "")
+ league = item.get("league_name", "")
+ result.append({
+ "vod_id": "live_{}".format(room_id),
+ "vod_name": "🔴 {} vs {} ({})".format(home, away, league),
+ "vod_pic": "",
+ "vod_remarks": "直播中",
+ })
+ except Exception as e:
+ self.log("搜索直播失败: " + str(e))
+
+ # 搜索录像
+ try:
+ data = self._fetch_recordings(1, 100)
+ if data.get("code") == 200:
+ for item in data["data"]:
+ if (keyword in item["home_team"].lower()
+ or keyword in item["away_team"].lower()
+ or keyword in item["league_name"].lower()):
+ title = "{} vs {} ({})".format(
+ item["home_team"], item["away_team"], item["league_name"]
+ )
+ result.append({
+ "vod_id": str(item["match_id"]),
+ "vod_name": title,
+ "vod_pic": "",
+ "vod_remarks": "{} - {}".format(item["home_score"], item["away_score"]),
+ })
+ except Exception as e:
+ self.log("搜索录像失败: " + str(e))
+
+ return {"list": result, "page": 1, "pagecount": 1}
+
+ def playerContent(self, flag, id, vipFlags):
+ return {
+ "parse": 0,
+ "playUrl": "",
+ "url": id,
+ "header": json.dumps({
+ "User-Agent": self.headers["User-Agent"],
+ "Referer": self.host,
+ "Origin": self.host,
+ })
+ }
diff --git a/api/极影.js b/api/极影.js
new file mode 100644
index 0000000..efc5234
--- /dev/null
+++ b/api/极影.js
@@ -0,0 +1,1068 @@
+import { Crypto, _ } from 'assets://js/lib/cat.js'
+
+let host = '';
+let header = {
+ 'User-Agent': 'okhttp/3.12.11'
+};
+let siteKey = '';
+let siteType = '';
+let siteJx = '';
+
+const urlPattern1 = /api\.php\/.*?\/vod/;
+const urlPattern2 = /api\.php\/.+?\.vod/;
+const parsePattern = /\/.+\\?.+=/;
+const parsePattern1 = /.*(url|v|vid|php\?id)=/;
+const parsePattern2 = /https?:\/\/[^\/]*/;
+
+const htmlVideoKeyMatch = [
+ /player=new/,
+ /
0) {
+ let episodeName = segment.substring(0, dollarInSegment);
+ let urlPart = segment.substring(dollarInSegment);
+ episodeName = episodeName.replace(/奇迹云/g, '');
+ result += episodeName + urlPart;
+ } else {
+ result += segment.replace(/奇迹云/g, '');
+ }
+ result += sep;
+ i = found + sep.length;
+ } else {
+ // 最后一段
+ let segment = playUrl.substring(i);
+ let dollarInSegment = segment.indexOf('$');
+ if (dollarInSegment > 0) {
+ let episodeName = segment.substring(0, dollarInSegment);
+ let urlPart = segment.substring(dollarInSegment);
+ episodeName = episodeName.replace(/奇迹云/g, '');
+ result += episodeName + urlPart;
+ } else {
+ result += segment.replace(/奇迹云/g, '');
+ }
+ break;
+ }
+ }
+ return result;
+}
+
+async function detail(ids) {
+ try {
+ // 苹果CMS V10模式检测
+ if (host.includes('/vod') || host.includes('/provide/vod')) {
+ const url = `${host}?ac=detail&ids=${ids}`;
+ const json = await request(url, getHeaders(url));
+ const obj = JSON.parse(json);
+ const result = { list: [] };
+ const vod = {};
+
+ const data = obj.list && obj.list[0] ? obj.list[0] : {};
+ vod.vod_id = data.vod_id || ids;
+ vod.vod_name = (data.vod_name || "").replace(/奇迹云/g, '');
+ vod.vod_pic = data.vod_pic || "";
+ vod.type_name = (data.type_name || "").replace(/奇迹云/g, '');
+ vod.vod_year = (data.vod_year || "").replace(/奇迹云/g, '');
+ vod.vod_area = (data.vod_area || "").replace(/奇迹云/g, '');
+ vod.vod_remarks = (data.vod_remarks || "").replace(/奇迹云/g, '');
+ vod.vod_actor = (data.vod_actor || "").replace(/奇迹云/g, '');
+ vod.vod_director = (data.vod_director || "").replace(/奇迹云/g, '');
+ vod.vod_content = (data.vod_content || "").replace(/奇迹云/g, '');
+
+ // 处理线路名称
+ let playFrom = data.vod_play_from || "";
+ if (playFrom) {
+ let lines = playFrom.split('$$$');
+ let newLines = [];
+ for (let line of lines) {
+ if (line && line.toLowerCase().includes('qijiyun4k')) {
+ newLines.push('拒绝收费');
+ } else {
+ newLines.push((line || "").replace(/奇迹云/g, ''));
+ }
+ }
+ vod.vod_play_from = newLines.join('$$$');
+ } else {
+ vod.vod_play_from = "";
+ }
+
+ // 处理选集:只替换文字,保留原始分隔符和结构
+ vod.vod_play_url = replacePlayUrlText(data.vod_play_url || "");
+
+ result.list.push(vod);
+ return JSON.stringify(result);
+ } else {
+ // 原有AppYsV2模式
+ const apiUrl = host;
+ const url = getPlayUrlPrefix(apiUrl) + ids;
+
+ const json = await request(url, getHeaders(url));
+ const obj = JSON.parse(json);
+ const result = {
+ list: [],
+ };
+ const vod = {};
+ genPlayList(apiUrl, obj, json, vod, ids);
+
+ // 替换详情中的所有文字
+ if (vod.vod_name) vod.vod_name = vod.vod_name.replace(/奇迹云/g, '');
+ if (vod.type_name) vod.type_name = vod.type_name.replace(/奇迹云/g, '');
+ if (vod.vod_year) vod.vod_year = vod.vod_year.replace(/奇迹云/g, '');
+ if (vod.vod_area) vod.vod_area = vod.vod_area.replace(/奇迹云/g, '');
+ if (vod.vod_remarks) vod.vod_remarks = vod.vod_remarks.replace(/奇迹云/g, '');
+ if (vod.vod_actor) vod.vod_actor = vod.vod_actor.replace(/奇迹云/g, '');
+ if (vod.vod_director) vod.vod_director = vod.vod_director.replace(/奇迹云/g, '');
+ if (vod.vod_content) vod.vod_content = vod.vod_content.replace(/奇迹云/g, '');
+
+ // 替换线路名称
+ if (vod.vod_play_from) {
+ let lines = vod.vod_play_from.split('$$$');
+ let newLines = [];
+ for (let line of lines) {
+ if (line && line.toLowerCase().includes('qijiyun4k')) {
+ newLines.push('拒绝收费');
+ } else {
+ newLines.push((line || "").replace(/奇迹云/g, ''));
+ }
+ }
+ vod.vod_play_from = newLines.join('$$$');
+ }
+
+ // 处理选集:只替换文字,保留原始分隔符和结构
+ vod.vod_play_url = replacePlayUrlText(vod.vod_play_url || "");
+
+ result.list.push(vod);
+ return JSON.stringify(result);
+ }
+ } catch (e) {
+ SpiderDebug.log(e);
+ }
+ return "";
+}
+
+async function play(flag, id, vipFlags) {
+ try {
+ let parseUrls = siteJx[flag];
+ if (!parseUrls) {
+ if (siteJx.hasOwnProperty('*')) {
+ parseUrls = siteJx['*'];
+ } else {
+ parseUrls = [];
+ }
+ }
+
+ if (parseUrls.length > 0) {
+ const result = await getFinalVideo(flag, parseUrls, id);
+ if (result !== null) {
+ return JSON.stringify(result);
+ }
+ }
+
+ if (isVideoFormat(id)) {
+ const result = {
+ parse: 0,
+ playUrl: "",
+ url: id
+ };
+ return JSON.stringify(result);
+ } else {
+ const result = {
+ parse: 1,
+ jx: "1",
+ url: id
+ };
+ return JSON.stringify(result);
+ }
+ } catch (e) {
+ SpiderDebug.log(e);
+ }
+ return "";
+}
+
+async function search(key, quick) {
+ try {
+ // 苹果CMS V10模式检测
+ if (host.includes('/vod') || host.includes('/provide/vod')) {
+ const url = `${host}?ac=videolist&wd=${encodeURIComponent(key)}&pg=1`;
+ const json = await request(url, getHeaders(url));
+ const obj = JSON.parse(json);
+ const videos = [];
+
+ if (obj.list && Array.isArray(obj.list)) {
+ for (const item of obj.list) {
+ videos.push({
+ vod_id: item.vod_id,
+ vod_name: (item.vod_name || "").replace(/奇迹云/g, ''),
+ vod_pic: item.vod_pic || "",
+ vod_remarks: (item.vod_remarks || "").replace(/奇迹云/g, '')
+ });
+ }
+ }
+ return JSON.stringify({ list: videos });
+ } else {
+ // 原有AppYsV2模式
+ const apiUrl = host;
+ const url = getSearchUrl(apiUrl, encodeURIComponent(key));
+ const json = await request(url, getHeaders(url));
+ const obj = JSON.parse(json);
+ let jsonArray = null;
+ const videos = [];
+
+ if (obj.list instanceof Array) {
+ jsonArray = obj.list;
+ } else if (obj.data instanceof Object && obj.data.list instanceof Array) {
+ jsonArray = obj.data.list;
+ } else if (obj.data instanceof Array) {
+ jsonArray = obj.data;
+ }
+
+ if (jsonArray !== null) {
+ for (const vObj of jsonArray) {
+ if (vObj.vod_id) {
+ const v = {
+ vod_id: vObj.vod_id,
+ vod_name: (vObj.vod_name || "").replace(/奇迹云/g, ''),
+ vod_pic: vObj.vod_pic,
+ vod_remarks: (vObj.vod_remarks || "").replace(/奇迹云/g, '')
+ };
+ videos.push(v);
+ } else {
+ const v = {
+ vod_id: vObj.nextlink,
+ vod_name: (vObj.title || "").replace(/奇迹云/g, ''),
+ vod_pic: vObj.pic,
+ vod_remarks: (vObj.state || "").replace(/奇迹云/g, '')
+ };
+ videos.push(v);
+ }
+ }
+ }
+
+ const result = { list: videos };
+ return JSON.stringify(result);
+ }
+ } catch (error) {
+ SpiderDebug.log(error);
+ }
+ return "";
+}
+
+// 辅助函数
+async function getFinalVideo(flag, parseUrls, url) {
+ let htmlPlayUrl = "";
+ for (const parseUrl of parseUrls) {
+ if (parseUrl === "" || parseUrl === "null") {
+ continue;
+ }
+ const playUrl = parseUrl + url;
+ const content = await request(playUrl, null, 10000);
+ let tryJson = null;
+ try {
+ tryJson = jsonParse(url, content);
+ } catch (error) { }
+
+ if (tryJson !== null && tryJson.hasOwnProperty("url") && tryJson.hasOwnProperty("header")) {
+ tryJson.header = JSON.stringify(tryJson.header);
+ return tryJson;
+ }
+
+ if (content.includes(" {
+ if (purl && !parseUrls.includes(purl)) {
+ parseUrls.push(purl);
+ }
+ });
+ }
+ if (from.player_info?.parse2) {
+ const parse2 = from.player_info.parse2.split(",");
+ parse2.forEach(purl => {
+ if (purl && !parseUrls.includes(purl)) {
+ parseUrls.push(purl);
+ }
+ });
+ }
+ parseUrlMap.set(flag, parseUrls);
+ } catch (e) {
+ SpiderDebug.log(e);
+ }
+ }
+ }
+ } else if (urlPattern1.test(URL)) {
+ const data = object.list && object.list[0] ? object.list[0] : {};
+ vod.vod_id = data.vod_id || vid;
+ vod.vod_name = data.vod_name || "";
+ vod.vod_pic = data.vod_pic || "";
+ vod.type_name = data.type_name || "";
+ vod.vod_year = data.vod_year || "";
+ vod.vod_area = data.vod_area || "";
+ vod.vod_remarks = data.vod_remarks || "";
+ vod.vod_actor = data.vod_actor || "";
+ vod.vod_director = data.vod_director || "";
+ vod.vod_content = data.vod_content || "";
+
+ vod.vod_play_from = data.vod_play_from || "";
+ vod.vod_play_url = data.vod_play_url || "";
+ }
+
+ // 合并播放源
+ if (playFlags.length > 0 && playUrls.length > 0) {
+ vod.vod_play_from = playFlags.join("$$$");
+ vod.vod_play_url = playUrls.join("$$$");
+ }
+}
+
+function jsonParse(input, json) {
+ try {
+ let jsonPlayData = JSON.parse(json);
+ if (jsonPlayData.hasOwnProperty("data") && typeof jsonPlayData.data === "object" && !jsonPlayData.hasOwnProperty("url")) {
+ jsonPlayData = jsonPlayData.data;
+ }
+
+ let url = jsonPlayData.url;
+
+ if (url.startsWith("//")) {
+ url = "https:" + url;
+ }
+ if (!url.trim().startsWith("http")) {
+ return null;
+ }
+ if (url === input) {
+ if (isVip(url) || !isVideoFormat(url)) {
+ return null;
+ }
+ }
+ if (isBlackVodUrl(input, url)) {
+ return null;
+ }
+
+ let headers = {};
+ if (jsonPlayData.hasOwnProperty("header")) {
+ headers = jsonPlayData.header;
+ } else if (jsonPlayData.hasOwnProperty("Header")) {
+ headers = jsonPlayData.Header;
+ } else if (jsonPlayData.hasOwnProperty("headers")) {
+ headers = jsonPlayData.headers;
+ } else if (jsonPlayData.hasOwnProperty("Headers")) {
+ headers = jsonPlayData.Headers;
+ }
+
+ let ua = "";
+ if (jsonPlayData.hasOwnProperty("user-agent")) {
+ ua = jsonPlayData["user-agent"];
+ } else if (jsonPlayData.hasOwnProperty("User-Agent")) {
+ ua = jsonPlayData["User-Agent"];
+ }
+ if (ua.trim().length > 0) {
+ headers["User-Agent"] = " " + ua;
+ }
+
+ let referer = "";
+ if (jsonPlayData.hasOwnProperty("referer")) {
+ referer = jsonPlayData.referer;
+ } else if (jsonPlayData.hasOwnProperty("Referer")) {
+ referer = jsonPlayData.Referer;
+ }
+ if (referer.trim().length > 0) {
+ headers["Referer"] = " " + referer;
+ }
+
+ headers = fixJsonVodHeader(headers, input, url);
+
+ const taskResult = {
+ header: headers,
+ url: url,
+ parse: "0"
+ };
+
+ return taskResult;
+ } catch (error) {
+ SpiderDebug.log(error);
+ }
+ return null;
+}
+
+function isVip(url) {
+ try {
+ let isVip = false;
+ const host = new URL(url).hostname;
+ const vipWebsites = ["iqiyi.com", "v.qq.com", "youku.com", "le.com", "tudou.com", "mgtv.com", "sohu.com", "acfun.cn", "bilibili.com", "baofeng.com", "pptv.com"];
+ for (let b = 0; b < vipWebsites.length; b++) {
+ if (host.includes(vipWebsites[b])) {
+ if (vipWebsites[b] === "iqiyi.com") {
+ if (url.includes("iqiyi.com/a_") || url.includes("iqiyi.com/w_") || url.includes("iqiyi.com/v_")) {
+ isVip = true;
+ break;
+ }
+ } else {
+ isVip = true;
+ break;
+ }
+ }
+ }
+ return isVip;
+ } catch (e) {
+ SpiderDebug.log(e);
+ }
+ return false;
+}
+
+function isBlackVodUrl(input, url) {
+ return url.includes("973973.xyz") || url.includes(".fit:");
+}
+
+function fixJsonVodHeader(headers, input, url) {
+ if (headers === null) {
+ headers = {};
+ }
+
+ if (input.includes("www.mgtv.com")) {
+ headers["Referer"] = " ";
+ headers["User-Agent"] = " Mozilla/5.0";
+ } else if (url.includes("titan.mgtv")) {
+ headers["Referer"] = " ";
+ headers["User-Agent"] = " Mozilla/5.0";
+ } else if (input.includes("bilibili")) {
+ headers["Referer"] = " https://www.bilibili.com/";
+ headers["User-Agent"] = " " + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36";
+ }
+
+ return headers;
+}
+
+const snifferMatch = /http((?!http).){26,}?\.(m3u8|mp4|flv|avi|mkv|rm|wmv|mpg)\?.*|http((?!http).){26,}\.(m3u8|mp4|flv|avi|mkv|rm|wmv|mpg)|http((?!http).){26,}\/m3u8\?pt=m3u8.*|http((?!http).)*?default\.ixigua\.com\/.*|http((?!http).)*?cdn-tos[^\?]*|http((?!http).)*?\/obj\/tos[^\?]*|http.*?\/player\/m3u8play\.php\?url=.*|http.*?\/player\/.*?[pP]lay\.php\?url=.*|http.*?\/playlist\/m3u8\/\?vid=.*|http.*?\.php\?type=m3u8&.*|http.*?\/download.aspx\?.*|http.*?\/api\/up_api.php\?.*|https.*?\.66yk\.cn.*|http((?!http).)*?netease\.com\/file\/.*/;
+
+function isVideoFormat(url) {
+ if (snifferMatch.test(url)) {
+ return !url.includes("cdn-tos") || !url.includes(".js");
+ }
+ return false;
+}
+
+function isVideo(url) {
+ return !url.includes(".mp4") && !url.includes(".m3u8");
+}
+
+function UA(url) {
+ if (url.includes(".vod")) {
+ return "okhttp/4.1.0";
+ }
+ return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36";
+}
+
+function getCateUrl(URL) {
+ if (URL.includes("api.php/app") || URL.includes("xgapp")) {
+ return URL + "nav?token=";
+ } else if (URL.includes(".vod")) {
+ return URL + "/types";
+ } else {
+ return "";
+ }
+}
+
+function getPlayUrlPrefix(URL) {
+ if (URL.includes("api.php/app") || URL.includes("xgapp")) {
+ return URL + "video_detail?id=";
+ } else if (URL.includes(".vod")) {
+ return URL + "/detail?vod_id=";
+ } else {
+ return "";
+ }
+}
+
+function getRecommendUrl(URL) {
+ if (URL.includes("api.php/app") || URL.includes("xgapp")) {
+ return URL + "index_video?token=";
+ } else if (URL.includes(".vod")) {
+ return URL + "/vodPhbAll";
+ } else {
+ return "";
+ }
+}
+
+function getFilterTypes(URL, typeExtend) {
+ let str = "";
+
+ if (typeExtend !== null) {
+ for (let key in typeExtend) {
+ if (key === "class" || key === "area" || key === "lang" || key === "year") {
+ try {
+ str += "筛选" + key + "+全部=+" + typeExtend[key].replace(/,/g, "+") + "\n";
+ } catch (e) { }
+ }
+ }
+ }
+
+ if (URL.includes(".vod")) {
+ str += "\n" + "排序+全部=+最新=time+最热=hits+评分=score";
+ } else if (URL.includes("api.php/app") || URL.includes("xgapp")) {
+ // Do nothing, leave the string as it is.
+ } else {
+ str = "分类+全部=+电影=movie+连续剧=tvplay+综艺=tvshow+动漫=comic+4K=movie_4k+体育=tiyu\n筛选class+全部=+喜剧+爱情+恐怖+动作+科幻+剧情+战争+警匪+犯罪+动画+奇幻+武侠+冒险+枪战+恐怖+悬疑+惊悚+经典+青春+文艺+微电影+古装+历史+运动+农村+惊悚+惊悚+伦理+情色+福利+三级+儿童+网络电影\n筛选area+全部=+大陆+香港+台湾+美国+英国+法国+日本+韩国+德国+泰国+印度+西班牙+加拿大+其他\n筛选year+全部=+2025+2024+2023+2022+2021+2020+2019+2018+2017+2016+2015+2014+2013+2012+2011+2010+2009+2008+2007+2006+2005+2004+2003+2002+2001+2000";
+ }
+
+ return str;
+}
+
+function getCateFilterUrlSuffix(URL) {
+ if (URL.includes("api.php/app") || URL.includes("xgapp")) {
+ return "&class=筛选class&area=筛选area&lang=筛选lang&year=筛选year&limit=18&pg=#PN#";
+ } else if (URL.includes(".vod")) {
+ return "&class=筛选class&area=筛选area&lang=筛选lang&year=筛选year&by=排序&limit=18&page=#PN#";
+ } else {
+ return "&page=#PN#&area=筛选area&type=筛选class&start=筛选year";
+ }
+}
+
+function getCateFilterUrlPrefix(URL) {
+ if (URL.includes("api.php/app") || URL.includes("xgapp")) {
+ return URL + "video?tid=";
+ } else if (URL.includes(".vod")) {
+ return URL + "?type=";
+ } else {
+ return URL + "?ac=list&class=";
+ }
+}
+
+function isBan(key) {
+ return key === "伦理" || key === "情色" || key === "福利";
+}
+
+function getSearchUrl(URL, KEY) {
+ if (URL.includes(".vod")) {
+ return URL + "?wd=" + KEY + "&page=";
+ } else if (URL.includes("api.php/app") || URL.includes("xgapp")) {
+ return URL + "search?text=" + KEY + "&pg=";
+ } else if (urlPattern1.test(URL)) {
+ return URL + "?ac=list&zm=" + KEY + "&page=";
+ }
+ return "";
+}
+
+function findJsonArray(obj, match, result) {
+ Object.keys(obj).forEach((k) => {
+ try {
+ const o = obj[k];
+ if (k === match && Array.isArray(o)) {
+ result.push(o);
+ }
+ if (typeof o === "object" && o !== null) {
+ if (Array.isArray(o)) {
+ o.forEach((item) => {
+ if (typeof item === "object" && item !== null) {
+ findJsonArray(item, match, result);
+ }
+ });
+ } else {
+ findJsonArray(o, match, result);
+ }
+ }
+ } catch (e) {
+ SpiderDebug.log(e);
+ }
+ });
+}
+
+function jsonArr2Str(array) {
+ const strings = [];
+ for (let i = 0; i < array.length; i++) {
+ try {
+ strings.push(array[i]);
+ } catch (e) {
+ SpiderDebug.log(e);
+ }
+ }
+ return strings.join(",");
+}
+
+function getHeaders(URL) {
+ const headers = {};
+ headers["User-Agent"] = UA(URL);
+ return headers;
+}
+
+function isJsonString(str) {
+ try {
+ JSON.parse(str);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
+
+export function __jsEvalReturn() {
+ return {
+ init: init,
+ home: home,
+ homeVod: homeVod,
+ category: category,
+ detail: detail,
+ play: play,
+ search: search,
+ };
+}
\ No newline at end of file
diff --git a/api/荐片.js b/api/荐片.js
new file mode 100644
index 0000000..e29984c
--- /dev/null
+++ b/api/荐片.js
@@ -0,0 +1,152 @@
+/**
+ * 荐片 新式 JS0 接口源
+ * 适配 FongMi TVBox 最新规范
+ */
+
+let host = 'https://api.ztcgi.com';
+let UA = 'Mozilla/5.0 (Linux; Android 9; V2196A Build/PQ3A.190705.08211809; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Mobile Safari/537.36;webank/h5face;webank/1.0;netType:NETWORK_WIFI;appVersion:416;packageName:com.jp3.xg3';
+let imghost = '';
+
+/**
+ * 初始化配置
+ */
+async function init(cfg) {
+ try {
+ let res = await req(`${host}/api/appAuthConfig`, { headers: { 'User-Agent': UA } });
+ let config = JSON.parse(res.content);
+ imghost = `https://${config.data.imgDomain}`;
+ } catch (e) {
+ imghost = 'https://img.jianpian.com';
+ }
+}
+
+/**
+ * 首页分类与筛选
+ */
+async function home(filter) {
+ let classes = [
+ {type_id: '1', type_name: '电影'},
+ {type_id: '2', type_name: '电视剧'},
+ {type_id: '3', type_name: '动漫'},
+ {type_id: '4', type_name: '综艺'}
+ ];
+
+ // 筛选数据模板
+ const filterItem = [
+ {"key": "cateId", "name": "分类", "value": [{"v": "1", "n": "剧情"}, {"v": "2", "n": "爱情"}, {"v": "3", "n": "动画"}, {"v": "4", "n": "喜剧"}, {"v": "5", "n": "战争"}, {"v": "6", "n": "歌舞"}, {"v": "7", "n": "古装"}, {"v": "8", "n": "奇幻"}, {"v": "9", "n": "冒险"}, {"v": "10", "n": "动作"}, {"v": "11", "n": "科幻"}, {"v": "12", "n": "悬疑"}, {"v": "13", "n": "犯罪"}, {"v": "14", "n": "家庭"}, {"v": "15", "n": "传记"}, {"v": "16", "n": "运动"}, {"v": "18", "n": "惊悚"}, {"v": "20", "n": "短片"}, {"v": "21", "n": "历史"}, {"v": "22", "n": "音乐"}, {"v": "23", "n": "西部"}, {"v": "24", "n": "武侠"}, {"v": "25", "n": "恐怖"}]},
+ {"key": "area", "name": "地區", "value": [{"v": "1", "n": "国产"}, {"v": "3", "n": "中国香港"}, {"v": "6", "n": "中国台湾"}, {"v": "5", "n": "美国"}, {"v": "18", "n": "韩国"}, {"v": "2", "n": "日本"}]},
+ {"key": "year", "name": "年代", "value": [{"v": "107", "n": "2025"}, {"v": "119", "n": "2024"}, {"v": "153", "n": "2023"}, {"v": "101", "n": "2022"}, {"v": "118", "n": "2021"}, {"v": "16", "n": "2020"}, {"v": "7", "n": "2019"}, {"v": "22", "n": "2016"}, {"v": "2015", "n": "2015以前"}]},
+ {"key": "sort", "name": "排序", "value": [{"v": "update", "n": "最新"}, {"v": "hot", "n": "最热"}, {"v": "rating", "n": "评分"}]}
+ ];
+
+ let filterObj = {"1": filterItem, "2": filterItem, "3": filterItem, "4": filterItem};
+
+ return JSON.stringify({
+ class: classes,
+ filters: filterObj
+ });
+}
+
+/**
+ * 首页推荐
+ */
+async function homeVod() {
+ let html = await req(`${host}/api/slide/list?pos_id=88`, { headers: { 'User-Agent': UA, 'Referer': host } });
+ let res = JSON.parse(html.content);
+ let videos = res.data.map(item => ({
+ vod_id: item.jump_id,
+ vod_name: item.title,
+ vod_pic: item.thumbnail.includes('http') ? item.thumbnail : `${imghost}${item.thumbnail}`,
+ vod_remarks: ""
+ }));
+ return JSON.stringify({ list: videos });
+}
+
+/**
+ * 分类列表
+ */
+async function category(tid, pg, filter, extend) {
+ let url = `${host}/api/crumb/list?fcate_pid=${tid}&category_id=&area=${extend.area || ''}&year=${extend.year || ''}&type=${extend.cateId || ''}&sort=${extend.sort || ''}&page=${pg}`;
+ let html = await req(url, { headers: { 'User-Agent': UA, 'Referer': host } });
+ let res = JSON.parse(html.content);
+ let videos = res.data.map(item => ({
+ vod_id: item.id,
+ vod_name: item.title,
+ vod_pic: item.path.includes('http') ? item.path : `${imghost}${item.path}`,
+ vod_remarks: item.mask
+ }));
+ return JSON.stringify({
+ page: pg,
+ list: videos
+ });
+}
+
+/**
+ * 详情页
+ */
+async function detail(id) {
+ let html = await req(`${host}/api/video/detailv2?id=${id}`, { headers: { 'User-Agent': UA, 'Referer': host } });
+ let data = JSON.parse(html.content).data;
+
+ // 线路处理:将“常规线路”显示为“边下边播”
+ let play_from = data.source_list_source.map(item => item.name).join('$$$').replace(/常规线路/g, '边下边播');
+ let play_url = data.source_list_source.map(play =>
+ play.source_list.map(({source_name, url}) => `${source_name}$${url}`).join('#')
+ ).join('$$$');
+
+ let vod = {
+ vod_id: data.id,
+ vod_name: data.title,
+ vod_year: data.year,
+ vod_area: data.area,
+ vod_remarks: data.mask,
+ vod_content: data.description,
+ vod_play_from: play_from,
+ vod_play_url: play_url,
+ vod_pic: data.thumbnail.includes('http') ? data.thumbnail : `${imghost}${data.thumbnail}`
+ };
+
+ return JSON.stringify({ list: [vod] });
+}
+
+/**
+ * 搜索功能
+ */
+async function search(wd, quick) {
+ let url = `${host}/api/v2/search/videoV2?key=${encodeURIComponent(wd)}&category_id=88&page=1&pageSize=20`;
+ let html = await req(url, { headers: { 'User-Agent': UA, 'Referer': host } });
+ let res = JSON.parse(html.content);
+ let videos = res.data.map(item => ({
+ vod_id: item.id,
+ vod_name: item.title,
+ vod_pic: item.thumbnail.includes('http') ? item.thumbnail : `${imghost}${item.thumbnail}`,
+ vod_remarks: item.mask
+ }));
+ return JSON.stringify({ list: videos });
+}
+
+/**
+ * 播放解析
+ */
+async function play(flag, id, flags) {
+ let playUrl = id;
+ // 判断是否需要添加专用协议前缀
+ if (!id.includes(".m3u8") && !id.includes(".mp4")) {
+ playUrl = `tvbox-xg:${id}`;
+ }
+ return JSON.stringify({
+ parse: 0,
+ url: playUrl
+ });
+}
+
+// 导出标准接口对象
+export default {
+ init,
+ home,
+ homeVod,
+ category,
+ detail,
+ search,
+ play
+};
diff --git a/api/金牌影院.js b/api/金牌影院.js
new file mode 100644
index 0000000..74784cc
--- /dev/null
+++ b/api/金牌影院.js
@@ -0,0 +1,169 @@
+import 'assets://js/lib/crypto-js.js';
+const { HOSTS, KEY, USER_AGENT } = {
+ HOSTS: ["https://hnytxj.com","https://www.hkybqufgh.com","https://www.sizhengxt.com","https://www.sdzhgt.com","https://www.jiabaide.cn","https://m.9zhoukj.com","https://m.cqzuoer.com","https://www.hellosht52bwb.com"],
+ KEY: "cb808529bae6b6be45ecfab29a4889bc",
+ USER_AGENT: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.61 Safari/537.36"
+};
+let currentHost = '';
+const guid = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
+ const r = Math.random() * 16 | 0;
+ return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
+});
+const md5 = s => CryptoJS.MD5(s).toString();
+const sha1 = s => CryptoJS.SHA1(s).toString();
+const toQueryString = obj => Object.keys(obj).filter(k => obj[k] != null && obj[k] !== '').map(k => `${k}=${obj[k]}`).join('&');
+const getHeaders = (params = {}) => {
+ const t = Date.now().toString();
+ const sign = sha1(md5(toQueryString({ ...params, key: KEY, t })));
+ return {
+ 'User-Agent': USER_AGENT,
+ 'Accept': 'application/json, text/plain, */*',
+ 'sign': sign,
+ 't': t,
+ 'deviceid': guid()
+ };
+};
+
+const normalizeFieldName = k => {
+ const l = k.toLowerCase();
+ if (l.startsWith('vod') && l.length > 3) return 'vod_' + l.slice(3);
+ if (l.startsWith('type') && l.length > 4) return 'type_' + l.slice(4);
+ return l;
+};
+
+const normalizeVodList = list => (list || []).map(item => {
+ const res = {};
+ for (const [k, v] of Object.entries(item || {})) if (v != null) res[normalizeFieldName(k)] = v;
+ return res;
+});
+
+async function reqSafe(url, options = {}) {
+ try {
+ const res = await req(url, options);
+ return JSON.parse(res.content);
+ } catch (e) {
+ return {};
+ }
+}
+
+async function init() {
+ currentHost = HOSTS[Math.floor(Math.random() * HOSTS.length)];
+ return true;
+}
+
+async function home() {
+ const [cRes, fRes] = await Promise.all([
+ reqSafe(`${currentHost}/api/mw-movie/anonymous/get/filer/type`, { headers: getHeaders() }),
+ reqSafe(`${currentHost}/api/mw-movie/anonymous/v1/get/filer/list`, { headers: getHeaders() })
+ ]);
+
+ const classes = (cRes.data || []).map(k => ({ type_name: k.typeName, type_id: k.typeId.toString() }));
+ const fData = fRes.data || {};
+ const baseSort = [{ n: "最近更新", v: "2" }, { n: "人气高低", v: "3" }, { n: "评分高低", v: "4" }];
+ const filters = {};
+ for (const [tid, d] of Object.entries(fData)) {
+ const sortValues = tid === '1' ? baseSort.slice(1) : baseSort;
+ const arr = [
+ { key: "type", name: "类型", value: (d.typeList || []).map(i => ({ n: i.itemText, v: i.itemValue })) },
+ { key: "area", name: "地区", value: (d.districtList || []).map(i => ({ n: i.itemText, v: i.itemText })) },
+ { key: "year", name: "年份", value: (d.yearList || []).map(i => ({ n: i.itemText, v: i.itemText })) },
+ { key: "lang", name: "语言", value: (d.languageList || []).map(i => ({ n: i.itemText, v: i.itemText })) },
+ { key: "sort", name: "排序", value: sortValues }
+ ];
+ if (d.plotList?.length) arr.splice(1, 0, { key: "v_class", name: "剧情", value: d.plotList.map(i => ({ n: i.itemText, v: i.itemText })) });
+ filters[tid] = arr;
+ }
+ return JSON.stringify({ class: classes, filters });
+}
+
+async function homeVod() {
+ const [r1, r2] = await Promise.all([
+ reqSafe(`${currentHost}/api/mw-movie/anonymous/v1/home/all/list`, { headers: getHeaders() }),
+ reqSafe(`${currentHost}/api/mw-movie/anonymous/home/hotSearch`, { headers: getHeaders() })
+ ]);
+ let list = [];
+ const data1 = r1.data || {};
+ for (const k in data1) if (data1[k]?.list) list.push(...data1[k].list);
+ if (Array.isArray(r2.data)) list.push(...r2.data);
+ return JSON.stringify({ list: normalizeVodList(list) });
+}
+
+async function category(tid, pg, _, ext = {}) {
+ const params = {
+ area: ext.area || '',
+ filterStatus: "1",
+ lang: ext.lang || '',
+ pageNum: pg,
+ pageSize: "30",
+ sort: ext.sort || '1',
+ sortBy: "1",
+ type: ext.type || '',
+ type1: tid,
+ v_class: ext.v_class || '',
+ year: ext.year || ''
+ };
+
+ const url = `${currentHost}/api/mw-movie/anonymous/video/list?${toQueryString(params)}`;
+ const res = await reqSafe(url, { headers: getHeaders(params) });
+ const vodList = normalizeVodList(res.data?.list || []);
+ return JSON.stringify({
+ list: vodList,
+ page: +pg,
+ pagecount: 9999,
+ limit: 90,
+ total: 999999
+ });
+}
+
+async function detail(id) {
+ const res = await reqSafe(`${currentHost}/api/mw-movie/anonymous/video/detail?id=${id}`, {
+ headers: getHeaders({ id })
+ });
+ const vod = normalizeVodList([res.data])[0];
+ if (!vod) {
+ return JSON.stringify({ list: [{ vod_id: id, vod_name: '加载失败', vod_play_url: '' }] });
+ }
+
+ vod.vod_play_from = '金牌影院';
+ if (vod.episodelist?.length) {
+ const name = vod.episodelist.length > 1 ? vod.episodelist[0].name : vod.vod_name;
+ vod.vod_play_url = vod.episodelist.map(ep => `${name}$${id}@@${ep.nid}`).join('#');
+ delete vod.episodelist;
+ }
+ return JSON.stringify({ list: [vod] });
+}
+
+async function play(_, id) {
+ const [vid, nid] = id.split('@@');
+ const url = `${currentHost}/api/mw-movie/anonymous/v2/video/episode/url?clientType=1&id=${vid}&nid=${nid}`;
+ const res = await reqSafe(url, { headers: getHeaders({ clientType: '1', id: vid, nid: nid }) });
+ const urls = [];
+ for (const item of res.data?.list || []) {
+ urls.push(item.resolutionName, item.url);
+ }
+ return JSON.stringify({
+ parse: 0,
+ url: urls,
+ header: {
+ 'User-Agent': USER_AGENT,
+ 'sec-ch-ua-platform': '"Windows"',
+ 'DNT': '1',
+ 'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
+ 'sec-ch-ua-mobile': '?0',
+ 'Origin': currentHost,
+ 'Referer': currentHost + '/'
+ }
+ });
+}
+
+async function search(wd, _, pg = "1") {
+ const params = { keyword: wd, pageNum: pg, pageSize: "8", sourceCode: "1" };
+ const url = `${currentHost}/api/mw-movie/anonymous/video/searchByWord?${toQueryString(params)}`;
+ const res = await reqSafe(url, { headers: getHeaders(params) });
+ const list = normalizeVodList(res.data?.result?.list || []);
+ return JSON.stringify({ list, page: +pg });
+}
+
+export function __jsEvalReturn() {
+ return { init, home, homeVod, category, detail, play, proxy: null, search };
+}