万年漫画
https://www.wnian001.com
zpccool (13551) 1天前 下载:425
漫画 免费 漫画 韩漫
万年漫画(wnian001.com)。Cloudflare 保护站点,首次使用需通过浏览器验证。
// @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;
}