网飞猫

https://www.ncat22.com

zpccool (13551) 16小时前 下载:435

视频
网飞猫影视 - 电影/连续剧/动漫/综艺纪录/短剧
二维码导入(APP尚未完成该功能)
// @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;
}
广告