网飞猫
https://www.ncat22.com
zpccool (13551) 16小时前 下载:435
视频
网飞猫影视 - 电影/连续剧/动漫/综艺纪录/短剧
// @name 网飞猫
// @uuid wangfeimao
// @version 1.0.0
// @author AI
// @url https://www.ncat22.com
// @type video
// @enabled true
// @description 网飞猫影视 - 电影/连续剧/动漫/综艺纪录/短剧
var BASE = "https://www.ncat22.com";
var CATEGORIES = {
电影: 1,
连续剧: 2,
动漫: 3,
综艺纪录: 4,
短剧: 6,
};
// ── 浏览器会话(书源级复用)───────────────────────────────
var _bid = null;
function getBrowser() {
if (!_bid) {
_bid = legado.browser.create({ visible: false });
legado.log("[browser] created session: " + _bid);
}
return _bid;
}
async function fetchHtml(url) {
legado.log("[fetch] " + url);
var id = getBrowser();
legado.browser.navigate(id, url, { waitUntil: "networkidle", timeoutSecs: 25 });
return legado.browser.html(id);
}
function absUrl(href) {
if (!href) return "";
if (href.indexOf("http") === 0) return href;
return BASE + href;
}
// ── explore ────────────────────────────────────────────
async function explore(page, category) {
legado.log("[explore] category=" + category + " page=" + page);
if (category === "GETALL") {
return Object.keys(CATEGORIES);
}
var ch = CATEGORIES[category] || 1;
var url = BASE + "/show/" + ch + "------" + page + ".html";
legado.log("[fetch] " + url);
var id = getBrowser();
legado.browser.navigate(id, url, { waitUntil: "networkidle", timeoutSecs: 25 });
var data = legado.browser.eval(
id,
"return (function(){" +
" var items = document.querySelectorAll(\"a.v-item[href*='/detail/']\");" +
" var r = [];" +
" for (var i=0; i<items.length; i++) {" +
" var a = items[i];" +
' var titles = a.querySelectorAll(".v-item-title");' +
' var name = "";' +
" for (var j=0; j<titles.length; j++) {" +
" var t = titles[j];" +
' if (t.style.display !== "none" && t.innerText.trim()) { name = t.innerText.trim(); break; }' +
" }" +
' var imgs = a.querySelectorAll("img.lazy");' +
' var cover = "";' +
" for (var k=0; k<imgs.length; k++) {" +
' if (!imgs[k].id || imgs[k].id !== "noneCoverImg") { var raw=imgs[k].getAttribute("data-original")||imgs[k].src||""; cover=raw.indexOf("http")===0?raw:(raw?"https://vres.bavdxfg.cn"+raw:""); break; }' +
" }" +
' var badge = a.querySelector(".v-item-bottom span");' +
' r.push({url:a.href, name:name, cover:cover, badge:badge?badge.innerText.trim():""});' +
" }" +
" return JSON.stringify(r);" +
"})()",
);
var books = [];
try {
var items = JSON.parse(data || "[]");
for (var j = 0; j < items.length; j++) {
var item = items[j];
if (!item.name || !item.url) continue;
books.push({
name: item.name,
bookUrl: absUrl(item.url),
coverUrl: item.cover || "",
latestChapter: item.badge || "",
tocUrl: absUrl(item.url),
});
}
} catch (e) {
legado.log("[explore] parse error: " + e.message);
}
legado.log("[explore] items: " + books.length);
return books;
}
// ── bookInfo ───────────────────────────────────────────
async function bookInfo(bookUrl) {
legado.log("[bookInfo] url=" + bookUrl);
var id = getBrowser();
legado.browser.navigate(id, bookUrl, { waitUntil: "networkidle", timeoutSecs: 25 });
// 用 eval 获取已渲染的标题(跳过 CSS display:none 的水印 strong)
var name = legado.browser.eval(id, 'return (document.querySelector(".detail-title") || {}).innerText || "";');
var coverUrl = legado.browser.eval(
id,
'return (function(){var box=document.querySelector("#itemCoverImg");if(!box)return"";var imgs=box.querySelectorAll("img");for(var i=0;i<imgs.length;i++){if(!imgs[i].id||imgs[i].id!=="noneCoverImg"){var raw=imgs[i].getAttribute("data-original")||imgs[i].src||"";return raw.indexOf("http")===0?raw:(raw?"https://vres.bavdxfg.cn"+raw:"");}}return"";})()',
);
var remark = legado.browser.eval(
id,
'(function(){ var rows = document.querySelectorAll(".detail-info-row"); for(var i=0;i<rows.length;i++){ var side = rows[i].querySelector(".detail-info-row-side"); if(side && side.innerText.indexOf("备注")>=0){ var main=rows[i].querySelector(".detail-info-row-main"); return main?main.innerText.trim():""; } } return ""; })()',
);
var author = legado.browser.eval(
id,
'(function(){ var rows = document.querySelectorAll(".detail-info-row"); for(var i=0;i<rows.length;i++){ var side = rows[i].querySelector(".detail-info-row-side"); if(!side) continue; var sideText = side.innerText; if(sideText.indexOf("导演")>=0||sideText.indexOf("主演")>=0||sideText.indexOf("作者")>=0){ var main=rows[i].querySelector(".detail-info-row-main"); return main?main.innerText.trim():""; } } return ""; })()',
);
var updateTime = legado.browser.eval(
id,
'(function(){ var rows = document.querySelectorAll(".detail-info-row"); for(var i=0;i<rows.length;i++){ var side = rows[i].querySelector(".detail-info-row-side"); if(side && side.innerText.indexOf("首映")>=0){ var main=rows[i].querySelector(".detail-info-row-main"); return main?main.innerText.trim():""; } } return ""; })()',
);
/** @type {BookItem} */
var book = {
name: (name || "").trim(),
author: (author || "").trim(),
bookUrl: bookUrl,
coverUrl: coverUrl || "",
latestChapter: remark || "",
updateTime: updateTime || "",
tocUrl: bookUrl,
};
return book;
}
// ── chapterList ────────────────────────────────────────
async function chapterList(tocUrl) {
legado.log("[chapterList] url=" + tocUrl);
var id = getBrowser();
legado.browser.navigate(id, tocUrl, { waitUntil: "networkidle", timeoutSecs: 25 });
// 只取第一个片源的集数列表
var data = legado.browser.eval(
id,
'return (function(){var box=document.querySelector(".episode-list-box-main");if(!box)return"[]";var first=box.querySelector(".episode-list");if(!first)return"[]";var links=first.querySelectorAll("a.episode-item");var r=[];for(var i=0;i<links.length;i++){r.push({name:links[i].innerText.trim(),url:links[i].href});}return JSON.stringify(r);})()',
);
var chapters = [];
try {
var items = JSON.parse(data || "[]");
for (var j = 0; j < items.length; j++) {
chapters.push({ name: items[j].name || "第" + (j + 1) + "集", url: items[j].url });
}
} catch (e) {
legado.log("[chapterList] parse error: " + e.message);
}
legado.log("[chapterList] chapters: " + chapters.length);
return chapters;
}
// ── chapterContent ─────────────────────────────────────
async function chapterContent(chapterUrl) {
legado.log("[chapterContent] url=" + chapterUrl);
var id = getBrowser();
function startsWithExtM3u(text) {
var value = String(text || "");
if (value.charCodeAt(0) === 0xfeff) value = value.substring(1);
value = value.replace(/^\s+/, "").toUpperCase();
return value.indexOf("#EXTM3U") === 0;
}
function previewText(text, limit) {
var value = String(text || "");
value = value.replace(/\r/g, "\\r").replace(/\n/g, "\\n");
if (value.length > limit) return value.substring(0, limit) + "...";
return value;
}
function logResponseTrace(tag, evt, body) {
var headers = evt.responseHeaders || {};
var contentType = headers["content-type"] || headers["Content-Type"] || "";
legado.log("[chapterContent] trace[" + tag + "] status=" + evt.status + " ct=" + contentType + " url=" + (evt.url || ""));
legado.log("[chapterContent] trace[" + tag + "] bodyHead=" + previewText(body, 160));
}
function shouldTraceFallback(url, contentType, body) {
var lurl = String(url || "").toLowerCase();
var lct = String(contentType || "").toLowerCase();
if (!body) return false;
if (lurl.indexOf("data:") === 0) return false;
if (lurl.indexOf("ipc.localhost") !== -1) return false;
if (lurl.indexOf(".js") !== -1) return false;
if (lct.indexOf("javascript") !== -1 || lct.indexOf("text/css") !== -1 || lct.indexOf("text/html") !== -1) return false;
if (lct.indexOf("image/") === 0 || lct.indexOf("font/") === 0) return false;
if (lct.indexOf("application/octet-stream") === 0) return false;
return true;
}
// 通过网络请求回调捕获真实 m3u8 地址(使用原生 WebView2 COM 拦截):
// captureBody:true 让底层读取 m3u8 / 文本类型响应体,供 #EXTM3U 检测使用
var playUrl = "";
var _evtTotal = 0;
legado.browser.onRequest(
id,
function (evt) {
_evtTotal++;
if (playUrl) return;
if (evt.type !== "response") return;
var url = evt.url || "";
var lurl = url.toLowerCase();
// m3u8 URL 优先路径:只接受当前播放环境已经确认可读取的播放列表。
// 只有响应体首部就是 #EXTM3U 才算真正的播放列表。
if (lurl.indexOf("m3u8") !== -1) {
var s = evt.status;
var responseBody = evt.responseBody || "";
legado.log("[chapterContent] ### onRequest[m3u8] ###: " + url);
logResponseTrace("m3u8", evt, responseBody);
if ((s === 200 || s === 206) && startsWithExtM3u(responseBody)) {
playUrl = url;
legado.log("[chapterContent] m3u8 found(url status=" + s + "): " + url);
}
return;
}
// 非 m3u8:只处理 200 响应,并排除 JS/CSS/HTML 资源
if (evt.status !== 200) return;
var body = evt.responseBody || "";
var ct = ((evt.responseHeaders && evt.responseHeaders["content-type"]) || "").toLowerCase();
if (!shouldTraceFallback(url, ct, body)) return;
legado.log("[chapterContent] ### onRequest[fallback] ###: " + url);
logResponseTrace("fallback", evt, body);
// body 首部含 #EXTM3U(兜底:内容类型非标准时通过 body 判断)
if (startsWithExtM3u(body)) {
playUrl = url;
legado.log("[chapterContent] m3u8 found(body): " + url);
}
},
{ captureBody: true },
);
// 用 networkidle 等待播放器初始化并发起 m3u8 请求(约 3-5s),超时后 wrapper 仍会 drain 缓冲区
legado.browser.navigate(id, chapterUrl, { waitUntil: "networkidle", timeoutSecs: 12 });
// 注意:此时不立即 offRequest —— WebView2 原生 HLS 不走 fetch/XHR,m3u8 响应可能在
// networkidle 之后才到达 COM 层;navigate(BASE) 内部会再次 drain,届时才调用 offRequest。
legado.log("[chapterContent] events after nav=" + _evtTotal + " playUrl=" + (playUrl || "NONE"));
// eval 兜底:若网络拦截未捕获到,尝试从播放器全局变量 / video 元素提取
if (!playUrl) {
var evalRaw = legado.browser.eval(
id,
"(function(){" +
"var r=[];" +
'try{var v=document.querySelector("video");if(v&&v.src&&v.src.indexOf("http")===0)r.push(v.src);}catch(e){}' +
'try{if(typeof config!=="undefined"&&config&&config.url)r.push(String(config.url));}catch(e){}' +
'try{if(typeof playerConfig!=="undefined"&&playerConfig&&playerConfig.url)r.push(String(playerConfig.url));}catch(e){}' +
'try{if(typeof player_data!=="undefined"&&player_data&&player_data.url)r.push(String(player_data.url));}catch(e){}' +
'try{if(typeof MacPlayerConfig!=="undefined"&&MacPlayerConfig&&MacPlayerConfig.url)r.push(String(MacPlayerConfig.url));}catch(e){}' +
// iframe 同源
'try{var ifs=document.querySelectorAll("iframe");for(var i=0;i<ifs.length;i++){try{var cw=ifs[i].contentWindow;if(cw&&cw.config&&cw.config.url)r.push(String(cw.config.url));}catch(e2){}}}catch(e){}' +
"return JSON.stringify(r);" +
"})()",
);
try {
var evalUrls = JSON.parse(evalRaw || "[]");
for (var ei = 0; ei < evalUrls.length; ei++) {
if (evalUrls[ei] && evalUrls[ei].length > 10) {
playUrl = evalUrls[ei];
legado.log("[chapterContent] eval fallback: " + playUrl);
break;
}
}
} catch (e3) {}
}
// 立即停止媒体播放,回到首页(避免后台出声/持续消耗流量)
// navigate(BASE) 内部会再次 drain REQUEST_EVENTS,捕获在 networkidle 之后才到达的 m3u8 事件
legado.browser.eval(
id,
'try{document.querySelectorAll("video,audio").forEach(function(el){try{el.pause();el.removeAttribute("src");el.load();}catch(e){}});}catch(e){}',
);
legado.browser.navigate(id, BASE, { waitUntil: "load", timeoutSecs: 10 });
legado.log("[chapterContent] events after backNav=" + _evtTotal + " playUrl=" + (playUrl || "NONE"));
// 回到首页后 handler 不再需要
legado.browser.offRequest(id);
if (playUrl && playUrl.length > 10) {
legado.log("[chapterContent] url=" + playUrl);
return playUrl;
}
legado.log("[chapterContent] no playable m3u8 found");
return "";
}
// ── search ─────────────────────────────────────────────
async function search(keyword, page) {
legado.log("[search] keyword=" + keyword + " page=" + page);
var id = getBrowser();
legado.browser.navigate(id, BASE, { waitUntil: "networkidle", timeoutSecs: 20 });
var token = legado.browser.eval(id, 'return (function(){var i=document.querySelector("input[name=t]");return i?i.value:"";})()');
legado.log("[search] token=" + (token ? "ok" : "missing"));
var searchUrl = BASE + "/search?t=" + encodeURIComponent(token) + "&k=" + encodeURIComponent(keyword) + "&page=" + page;
legado.log("[search] url=" + searchUrl);
legado.browser.navigate(id, searchUrl, { waitUntil: "networkidle", timeoutSecs: 25 });
var data = legado.browser.eval(
id,
"return (function(){" +
' var list = document.querySelector(".search-result-list");' +
' if (!list) return "[]";' +
' var items = list.querySelectorAll("a.search-result-item");' +
" var r = [];" +
" for (var i=0; i<items.length; i++) {" +
" var a = items[i];" +
' var titleEl = a.querySelector(".title");' +
' var img = a.querySelector("img.lazy");' +
' r.push({url:a.href, name:titleEl?titleEl.innerText.trim():"", cover:img?(img.src||""):""});' +
" }" +
" return JSON.stringify(r);" +
"})()",
);
var books = [];
try {
var items = JSON.parse(data || "[]");
for (var j = 0; j < items.length; j++) {
var item = items[j];
if (!item.name || !item.url) continue;
books.push({ name: item.name, bookUrl: absUrl(item.url), coverUrl: item.cover || "", latestChapter: "", tocUrl: absUrl(item.url) });
}
} catch (e) {
legado.log("[search] parse error: " + e.message);
}
legado.log("[search] results: " + books.length);
return books;
}