万年漫画

https://www.wnian001.com

zpccool (13551) 1天前 下载:425

漫画 免费 漫画 韩漫
万年漫画(wnian001.com)。Cloudflare 保护站点,首次使用需通过浏览器验证。
二维码导入(APP尚未完成该功能)
// @name        万年漫画
// @version     0.3.0
// @uuid        wannianmanhua
// @author      Ai
// @url         https://www.wnian001.com
// @logo        https://www.wnian001.com/wn.ico
// @enabled     true
// @tags        免费,漫画,韩漫
// @type        comic
// @description 万年漫画(wnian001.com)。Cloudflare 保护站点,首次使用需通过浏览器验证。

// ─── 内置测试 ─────────────────────────────────────────────────────────────

async function TEST(type) {
  if (type === "__list__") return ["search", "bookInfo", "chapterList", "chapterContent", "explore"];

  if (type === "search") {
    var results = await search("社团学姐", 1);
    if (!results || results.length < 1) return { passed: false, message: "搜索结果为空" };
    return { passed: true, message: "搜索返回 " + results.length + " 条结果 ✓" };
  }
  if (type === "bookInfo") {
    var info = await bookInfo("https://www.wnian001.com/post/100895481.html");
    if (!info || !info.name) return { passed: false, message: "bookInfo 返回为空" };
    return { passed: true, message: "name=" + info.name + " ✓" };
  }
  if (type === "chapterList") {
    var chs = await chapterList("https://www.wnian001.com/post/100895481.html");
    if (!chs || chs.length < 1) return { passed: false, message: "章节列表为空" };
    return { passed: true, message: "共 " + chs.length + " 章 ✓" };
  }
  if (type === "chapterContent") {
    var content = await chapterContent("https://www.wnian001.com/post/100895481/page-2.html");
    if (!content) return { passed: false, message: "正文为空" };
    return { passed: true, message: "正文长度=" + content.length + " ✓" };
  }
  if (type === "explore") {
    var cats = await explore(1, "GETALL");
    if (!cats || cats.length < 1) return { passed: false, message: "分类列表为空" };
    var items = await explore(1, cats[0]);
    if (!items || items.length < 1) return { passed: false, message: cats[0] + " 分类结果为空" };
    return { passed: true, message: cats.length + " 个分类,首分类返回 " + items.length + " 条 ✓" };
  }

  return { passed: false, message: "未知测试类型: " + type };
}

// ─── 配置 ─────────────────────────────────────────────────────────────────

var BASE = "https://www.wnian001.com";

// 发现页分类(首页导航 + 热搜)
var EXPLORE_CATEGORIES = [
  { name: "🔥 热搜", path: "/", mode: "hot" },
  { name: "短篇漫画", path: "/item/短篇漫画" },
  { name: "连载漫画", path: "/item/连载" },
  { name: "完结漫画", path: "/item/完结" },
  { name: "爱情漫画", path: "/mh" },
  { name: "美女写真", path: "/meinv" },
];

// ─── CF 保护检测 ──────────────────────────────────────────────────────────

/**
 * 检测 HTML 是否被 Cloudflare 拦截。
 * 覆盖 CF 经典 5 秒盾、Turnstile 人机验证、JS Challenge 等多种变体。
 * @param {string} html - HTTP 或浏览器页面 HTML
 * @returns {boolean}
 */
function isCfBlocked(html) {
  if (!html || html.length < 200) {
    legado.log("[CF:detect] blocked: html too short (" + (html ? html.length : 0) + ")");
    return true;
  }
  // 仅匹配 CF 挑战/拦截页特有的标记,排除正常页面也会出现的(如 ray-id、/_cf/ 资源路径)
  var markers = [
    "Just a moment",
    "cf-browser-verification",
    "Checking your browser",
    // "challenge-platform",
    "cf-challenge-running",
    "managed_checking_msg",
    "cf-please-wait",
    "cf-turnstile-wrapper",
  ];
  for (var i = 0; i < markers.length; i++) {
    if (html.indexOf(markers[i]) !== -1) {
      legado.log("[CF:detect] blocked: matched '" + markers[i] + "' (html=" + html.length + ")");
      return true;
    }
  }
  return false;
}

/**
 * 使用新版浏览器探测 API 完成 Cloudflare 验证。
 * 通过 acquire() 复用同名会话,验证完成后 Cookie 自动双向同步到 HTTP 客户端。
 * @param {string} url  - 要访问的 URL
 * @param {string} html - HTTP 响应内容
 * @returns {string} - 若通过验证返回新 HTML,否则返回原 html
 */
async function ensureCfPassed(url, html) {
  if (!isCfBlocked(html)) return html;

  legado.log("[CF] 检测到 Cloudflare 拦截(html=" + html.length + "),启动浏览器探测...");
  legado.toast("需要完成 Cloudflare 验证,请在弹出窗口中操作");

  // 获取/复用名为 'cf' 的浏览器会话(同一书源内共享)
  var sessionId = await legado.browser.acquire("cf", { visible: true });
  legado.log("[CF] 会话 id=" + sessionId);

  var httpCookiesBefore = legado.http.cookies(url);
  legado.log("[CF:debug] HTTP cookies(before) = " + httpCookiesBefore);

  // 导航到被拦截的 URL,等待加载
  await legado.browser.navigate(sessionId, url, { waitFor: "networkidle" });

  // ── 调试:对比浏览器 UA 与 HTTP 客户端 UA(必须在 navigate 之后,about:blank 无 IPC bridge)──
  var browserUA = await legado.browser.eval(sessionId, "return navigator.userAgent");
  legado.log("[CF:debug] 浏览器 UA = " + browserUA);

  // 等待 CF 验证完成:轮询检测页面内容,最多 60 秒
  var maxAttempts = 60;
  var passed = false;
  for (var i = 0; i < maxAttempts; i++) {
    var pageHtml = await legado.browser.html(sessionId);
    legado.log("[CF] 轮询 #" + (i + 1) + " html=" + (pageHtml ? pageHtml.length : 0));
    if (pageHtml && pageHtml.length > 500 && !isCfBlocked(pageHtml)) {
      passed = true;
      legado.log("[CF] 浏览器页面验证通过 (尝试 " + (i + 1) + " 次)");
      break;
    }
    // 等待 1 秒后重试
    legado.sleep(1000);
  }

  if (!passed) {
    legado.log("[CF] 浏览器页面验证超时,隐藏窗口");
    await legado.browser.hide(sessionId);
    return html;
  }

  // ── 调试:验证通过后对比 Cookie ──
  var browserCookies = await legado.browser.cookies(url);
  var httpCookiesAfter = legado.http.cookies(url);
  legado.log("[CF:debug] 浏览器 cookies = " + JSON.stringify(browserCookies));
  legado.log("[CF:debug] HTTP cookies(after) = " + httpCookiesAfter);

  // Cookie 已通过浏览器探测自动同步到全局 Cookie Store,
  // 用 HTTP 请求验证 Cookie 是否真正可用。
  legado.log("[CF] 用 HTTP 验证 Cookie...");
  var retryHtml = await legado.http.get(url);
  legado.log("[CF:debug] HTTP 重试 html=" + (retryHtml ? retryHtml.length : 0) + " blocked=" + isCfBlocked(retryHtml));
  if (!isCfBlocked(retryHtml) && retryHtml.length > 500) {
    legado.log("[CF] HTTP 验证通过 (长度=" + retryHtml.length + "),隐藏窗口");
    await legado.browser.hide(sessionId);
    return retryHtml;
  }

  // HTTP 请求仍被拦截,说明 Cookie 还没完全同步或需要更久,继续等待
  legado.log("[CF] HTTP 仍被拦截(html=" + (retryHtml ? retryHtml.length : 0) + "),等待 Cookie 同步...");
  // 输出被拦截时的 HTML 前 500 字符帮助诊断
  if (retryHtml) legado.log("[CF:debug] 被拦截HTML片段: " + retryHtml.substring(0, 500));
  for (var j = 0; j < 10; j++) {
    legado.sleep(2000);
    retryHtml = await legado.http.get(url);
    var httpCookiesRetry = legado.http.cookies(url);
    legado.log("[CF:debug] 重试#" + (j + 1) + " html=" + (retryHtml ? retryHtml.length : 0) + " cookies=" + httpCookiesRetry);
    if (!isCfBlocked(retryHtml) && retryHtml.length > 500) {
      legado.log("[CF] HTTP 验证通过 (重试 " + (j + 1) + " 次)");
      await legado.browser.hide(sessionId);
      return retryHtml;
    }
  }

  legado.log("[CF] 验证最终超时");
  await legado.browser.hide(sessionId);
  return html;
}

// ─── 工具 ────────────────────────────────────────────────────────────────

function toAbs(href) {
  if (!href) return "";
  if (href.indexOf("http") === 0) return href;
  if (href.indexOf("//") === 0) return "https:" + href;
  if (href.indexOf("/") === 0) return BASE + href;
  return BASE + "/" + href;
}

// ─── 解析文章列表(搜索 & 发现共用) ─────────────────────────────────────

function parseArticleList(doc) {
  var items = legado.dom.selectAll(doc, "article.excerpt");
  if (!items || items.length === 0) return [];

  var results = [];
  for (var i = 0; i < items.length; i++) {
    var item = items[i];
    try {
      var nameEl = legado.dom.select(item, "h2 a");
      var name = nameEl ? legado.dom.text(nameEl).trim() : "";
      var href = nameEl ? legado.dom.attr(nameEl, "href") : "";
      var bookUrl = toAbs(href);

      var catEl = legado.dom.select(item, "p.cat");
      var kind = catEl ? legado.dom.text(catEl).trim() : "";

      var statusEl = legado.dom.select(item, "span.subtitle");
      var status = statusEl ? legado.dom.text(statusEl).trim() : "";
      if (status && kind) kind = kind + " · " + status;
      else if (status) kind = status;

      var coverEl = legado.dom.select(item, ".thumbnail img, .focus img");
      var coverUrl = "";
      if (coverEl) {
        coverUrl = legado.dom.attr(coverEl, "data-src") || legado.dom.attr(coverEl, "src") || "";
        coverUrl = toAbs(coverUrl);
      }

      var introEl = legado.dom.select(item, "p.note");
      var intro = introEl ? legado.dom.text(introEl).trim() : "";

      if (name && bookUrl) {
        results.push({
          name: name,
          author: "",
          bookUrl: bookUrl,
          coverUrl: coverUrl,
          kind: kind,
          intro: intro,
        });
      }
    } catch (e) {
      legado.log("[parseArticleList] 解析条目异常: " + e);
    }
  }
  return results;
}

// ─── 搜索 ────────────────────────────────────────────────────────────────

async function search(keyword, page) {
  // WordPress 搜索:/?s=keyword(不支持分页参数,直接返回全部匹配结果)
  var url = BASE + "/?s=" + legado.urlEncode(keyword);
  legado.log("[search] keyword=" + keyword + " url=" + url);

  var html = await legado.http.get(url);
  html = await ensureCfPassed(url, html);

  var doc = legado.dom.parse(html);

  var results = parseArticleList(doc);
  legado.dom.free(doc);
  legado.log("[search] 返回 " + results.length + " 条结果");
  return results;
}

// ─── 详情 ────────────────────────────────────────────────────────────────

async function bookInfo(bookUrl) {
  legado.log("[bookInfo] url=" + bookUrl);

  var html = await legado.http.get(bookUrl);
  html = await ensureCfPassed(bookUrl, html);

  var doc = legado.dom.parse(html);

  // 标题:h1.article-title 内的 <a> 文本(不含 span.subtitle)
  var titleEl = legado.dom.select(doc, "h1.article-title a");
  var name = titleEl ? legado.dom.text(titleEl).trim() : "";

  // 连载状态
  var statusEl = legado.dom.select(doc, "h1.article-title span.subtitle");
  var status = statusEl ? legado.dom.text(statusEl).trim() : "";

  // 封面:.c-img img
  var coverEl = legado.dom.select(doc, ".c-img img");
  var coverUrl = "";
  if (coverEl) {
    coverUrl = legado.dom.attr(coverEl, "src") || "";
    coverUrl = toAbs(coverUrl);
  }

  // 简介:span.dis
  var introEl = legado.dom.select(doc, "span.dis");
  var intro = introEl ? legado.dom.text(introEl).trim() : "";
  // 去除开头的 "==>" 标记
  intro = intro.replace(/^=+>/, "").trim();

  // 分类:.article-meta a[rel="category tag"]
  var catEl = legado.dom.select(doc, '.article-meta a[rel="category tag"]');
  var kind = catEl ? legado.dom.text(catEl).trim() : "";
  if (status) kind = kind ? kind + " · " + status : status;

  // 标签
  var tagEls = legado.dom.selectAll(doc, ".post-tags a");
  var tagList = [];
  if (tagEls) {
    for (var i = 0; i < tagEls.length; i++) {
      var tagText = legado.dom.text(tagEls[i]).trim();
      if (tagText) tagList.push(tagText);
    }
  }
  if (tagList.length > 0) kind = kind ? kind + " | " + tagList.join(", ") : tagList.join(", ");

  // 最新章节:取最后一个 chapter-link
  var chLinks = legado.dom.selectAll(doc, "a.chapter-link");
  var lastChapter = "";
  if (chLinks && chLinks.length > 0) {
    lastChapter = legado.dom.text(chLinks[chLinks.length - 1]).trim();
  }

  legado.dom.free(doc);
  legado.log("[bookInfo] name=" + name + " kind=" + kind + " chapters=" + (chLinks ? chLinks.length : 0));

  return {
    name: name,
    author: "",
    coverUrl: coverUrl,
    intro: intro,
    kind: kind,
    lastChapter: lastChapter,
    bookUrl: bookUrl,
    tocUrl: bookUrl,
  };
}

// ─── 目录 ────────────────────────────────────────────────────────────────

async function chapterList(tocUrl) {
  legado.log("[chapterList] url=" + tocUrl);

  var html = await legado.http.get(tocUrl);
  html = await ensureCfPassed(tocUrl, html);

  var doc = legado.dom.parse(html);
  var chapters = [];

  // 章节链接:a.chapter-link
  var links = legado.dom.selectAll(doc, "a.chapter-link");
  legado.log("[chapterList] a.chapter-link 命中=" + (links ? links.length : 0));

  if (links) {
    for (var i = 0; i < links.length; i++) {
      var a = links[i];
      var href = legado.dom.attr(a, "href");
      var chName = legado.dom.text(a).trim();
      if (!href || !chName) continue;
      chapters.push({ name: chName, url: toAbs(href) });
    }
  }

  legado.dom.free(doc);
  legado.log("[chapterList] 返回 " + chapters.length + " 章");
  return chapters;
}

// ─── 正文(漫画图片) ────────────────────────────────────────────────────

async function chapterContent(chapterUrl) {
  legado.log("[chapterContent] url=" + chapterUrl);

  var html = await legado.http.get(chapterUrl);
  html = await ensureCfPassed(chapterUrl, html);

  var doc = legado.dom.parse(html);

  // 漫画图片在 .article-content img 中
  var contentEl = legado.dom.select(doc, ".article-content");
  if (!contentEl) {
    legado.log("[chapterContent] 未找到 .article-content");
    legado.dom.free(doc);
    return "";
  }

  var imgs = legado.dom.selectAll(contentEl, "img");
  var imageUrls = [];
  if (imgs) {
    for (var i = 0; i < imgs.length; i++) {
      var src = legado.dom.attr(imgs[i], "data-src") || legado.dom.attr(imgs[i], "src") || "";
      if (!src) continue;
      src = toAbs(src);
      // 跳过封面图(在 .c-img 中的封面已被包含在 content 区域,过滤掉)
      if (src.indexOf("/manga-cover/") !== -1) continue;
      imageUrls.push(src);
    }
  }

  legado.dom.free(doc);
  legado.log("[chapterContent] 获取 " + imageUrls.length + " 张图片");

  // 返回 <img> 标签拼接(漫画模式下由阅读器渲染)
  // var result = "";
  // for (var j = 0; j < imageUrls.length; j++) {
  //   result += '<img src="' + imageUrls[j] + '">\n';
  // }
  return JSON.stringify(imageUrls);
}

// ─── 发现页 ──────────────────────────────────────────────────────────────

async function explore(page, category) {
  legado.log("[explore] page=" + page + " category=" + category);

  // 返回分类列表
  if (!category || category === "GETALL") {
    var names = [];
    for (var i = 0; i < EXPLORE_CATEGORIES.length; i++) {
      names.push(EXPLORE_CATEGORIES[i].name);
    }
    return names;
  }

  // 查找分类配置
  var cat = null;
  for (var j = 0; j < EXPLORE_CATEGORIES.length; j++) {
    if (EXPLORE_CATEGORIES[j].name === category) {
      cat = EXPLORE_CATEGORIES[j];
      break;
    }
  }
  if (!cat) {
    legado.log("[explore] 未知分类: " + category);
    return [];
  }

  // 热搜模式:从首页提取 a.hot-keyword 链接列表
  if (cat.mode === "hot") {
    if (page > 1) return []; // 热搜仅一页
    var homeUrl = BASE + cat.path;
    var homeHtml = await legado.http.get(homeUrl);
    homeHtml = await ensureCfPassed(homeUrl, homeHtml);

    var homeDoc = legado.dom.parse(homeHtml);
    var hotLinks = legado.dom.selectAll(homeDoc, "a.hot-keyword");
    var hotResults = [];
    if (hotLinks) {
      for (var h = 0; h < hotLinks.length; h++) {
        var hotName = legado.dom.text(hotLinks[h]).trim();
        var hotHref = legado.dom.attr(hotLinks[h], "href");
        if (hotName && hotHref) {
          hotResults.push({
            name: hotName,
            author: "",
            bookUrl: toAbs(hotHref),
            coverUrl: "",
            kind: "热搜",
            intro: "",
          });
        }
      }
    }
    legado.dom.free(homeDoc);
    legado.log("[explore] 热搜返回 " + hotResults.length + " 条");
    return hotResults;
  }

  // 标准分类:请求分类页,解析 article.excerpt 列表
  // 分类页分页:/item/xxx/page/N 或 /mh/page/N
  var catUrl = BASE + cat.path;
  if (page > 1) catUrl += "/page/" + page;
  legado.log("[explore] url=" + catUrl);

  var catHtml = await legado.http.get(catUrl);
  catHtml = await ensureCfPassed(catUrl, catHtml);

  var catDoc = legado.dom.parse(catHtml);
  var catResults = parseArticleList(catDoc);
  legado.dom.free(catDoc);
  legado.log("[explore] " + category + " 返回 " + catResults.length + " 条");
  return catResults;
}
广告