69书吧

https://www.69shuba.com

PC6live (13656) 05/18 17:51 下载:4308

小说
69书吧,手动过cf验证
二维码导入(APP尚未完成该功能)
// @uuid        019e3a7f-429f-7ce4-9b20-424a9a93f971
// @name        69书吧
// @version     1.0.0
// @author      PC6live
// @url         https://www.69shuba.com
// @logo        https://cdn.cdnshu.com/favicon.ico
// @enabled     true
// @description 69书吧

const BASE = "https://www.69shuba.com";

function isCfBlocked(html) {
  if (!html || html.length < 200) return true;
  const markers = [
    "Just a moment",
    "cf-browser-verification",
    "Checking your browser",
    "cf-challenge-running",
    "managed_checking_msg",
    "cf-please-wait",
    "cf-turnstile-wrapper",
    "正在进行安全验证",
    "cloudflare.com/turnstile",
  ];
  for (let i = 0; i < markers.length; i++) {
    if (html.indexOf(markers[i]) !== -1) return true;
  }
  return false;
}

function ensureCfPassed(url, html) {
  if (!isCfBlocked(html)) return html;
  const sessionId = legado.browser.acquire("cf", { visible: false });
  legado.browser.navigate(sessionId, url, { waitFor: "load" });
  const maxAttempts = 60;
  let passed = false;
  let realHtml = null;
  let browserShown = false;
  for (let i = 0; i < maxAttempts; i++) {
    const pageHtml = legado.browser.html(sessionId);
    if (pageHtml && pageHtml.length > 500 && !isCfBlocked(pageHtml)) {
      passed = true;
      realHtml = pageHtml;
      break;
    }
    if (!browserShown) {
      legado.toast("需要完成 Cloudflare 验证,请在弹出窗口中操作");
      legado.browser.show(sessionId);
      browserShown = true;
    }
    legado.sleep(1000);
  }
  if (!passed) {
    legado.browser.hide(sessionId);
    return html;
  }
  legado.browser.cookies(url);
  legado.browser.hide(sessionId);
  return realHtml;
}

function ensureCfPassedWithSearch(url, html, params) {
  if (!isCfBlocked(html)) return html;
  const sessionId = legado.browser.acquire("cf", { visible: false });
  legado.browser.navigate(sessionId, BASE, { waitFor: "load" });
  legado.browser.eval(
    sessionId,
    `
      const params = ${JSON.stringify(params)};
      const form = document.createElement('form');
      form.method = 'POST';
      form.action = ${JSON.stringify(url)};
      Object.entries(params).forEach(([key, value]) => {
        const input = document.createElement('input');
        input.type = 'hidden';
        input.name = key;
        input.value = value ?? '';
        form.appendChild(input);
      });
      document.body.appendChild(form);
      HTMLFormElement.prototype.submit.call(form);
    `,
  );
  legado.sleep(1000);
  const maxAttempts = 60;
  let passed = false;
  let realHtml = null;
  let browserShown = false;
  for (let i = 0; i < maxAttempts; i++) {
    const pageHtml = legado.browser.html(sessionId);
    if (
      pageHtml &&
      pageHtml.length > 500 &&
      !isCfBlocked(pageHtml) &&
      pageHtml.indexOf("newbox") !== -1
    ) {
      passed = true;
      realHtml = pageHtml;
      break;
    }
    if (!browserShown) {
      legado.toast("需要完成 Cloudflare 验证,请在弹出窗口中操作");
      legado.browser.show(sessionId);
      browserShown = true;
    }
    legado.sleep(1000);
  }
  if (!passed) {
    legado.browser.hide(sessionId);
    return html;
  }
  legado.browser.cookies(url);
  legado.browser.hide(sessionId);
  return realHtml;
}

// ── 搜索 ─────────────────────────────────────────────────────
// 返回 BookItem[]
async function search(key, page) {
  legado.log("search: keyword=" + key + ", page=" + page);
  const encoded = await legado.urlEncodeCharset(key, "gbk");
  const body = `searchkey=${encoded}&submit=Search`;
  const url = BASE + "/modules/article/search.php";
  let html = await legado.http.post(url, body);
  legado.log("search: isCfBlocked=" + isCfBlocked(html));
  const html = ensureCfPassedWithSearch(url, html, {
    searchkey: encoded,
    submit: "Search",
  });
  const doc = legado.dom.parse(html);
  legado.log("search: doc=" + html);
  const books = [];
  const items = legado.dom.selectAll(doc, ".newbox > ul > li");
  for (let i = 0; i < items.length; i++) {
    const el = items[i];
    books.push({
      name: legado.dom.selectText(el, "div.newnav > h3 > a:nth-child(2)"),
      author: legado.dom.selectText(
        el,
        "div.newnav > div.labelbox > label:nth-child(1)",
      ),
      coverUrl: legado.dom.selectAttr(el, ".imgbox img", "src"),
      intro: legado.dom.selectText(el, "ol.ellipsis_2"),
      bookUrl: legado.dom.selectAttr(el, "a.btn.btn-tp", "href"),
    });
  }
  legado.dom.free(doc);
  return books;
}

// ── 书籍详情 ──────────────────────────────────────────────────
// 返回 BookItem(含 tocUrl)
async function bookInfo(bookUrl) {
  let html = await legado.http.get(bookUrl);
  html = ensureCfPassed(bookUrl, html);
  const doc = legado.dom.parse(html);
  const info = {
    name: legado.dom.selectAttr(doc, '[property="og:title"]', "content"),
    author: legado.dom.selectAttr(
      doc,
      '[property="og:novel:author"]',
      "content",
    ),
    coverUrl: legado.dom.selectAttr(doc, '[property="og:image"]', "content"),
    intro: legado.dom.selectText(doc, "div.navtxt > p:nth-child(1)"),
    bookUrl,
    tocUrl: legado.dom.selectAttr(doc, ".btn.more-btn", "href"),
    latestChapter: legado.dom.selectAttr(
      doc,
      '[property="og:novel:latest_chapter_name"]',
      "content",
    ),
    latestChapterUrl: legado.dom.selectAttr(
      doc,
      ".qustime > ul > li:nth-child(1) > a",
      "href",
    ),
    wordCount: legado.dom.selectText(doc, "div.booknav2 > p:nth-child(4)"),
    updateTime: legado.dom.selectAttr(
      doc,
      '[property="og:novel:update_time"]',
      "content",
    ),
    status: legado.dom.selectAttr(
      doc,
      '[property="og:novel:status"]',
      "content",
    ),
    kind: legado.dom.selectText(doc, "#tagul"),
  };
  legado.dom.free(doc);
  return info;
}

// ── 章节目录 ──────────────────────────────────────────────────
// 返回 ChapterInfo[]
async function chapterList(tocUrl) {
  let html = await legado.http.get(tocUrl);
  html = ensureCfPassed(tocUrl, html);
  const doc = legado.dom.parse(html);
  const items = legado.dom.selectAll(doc, "#catalog > ul > li");
  const chapters = items.map((node) => ({
    name: legado.dom.selectText(node, "a"),
    url: legado.dom.selectAttr(node, "a", "href"),
  }));
  legado.dom.free(doc);
  return chapters.reverse();
}

// ── 章节正文 ──────────────────────────────────────────────────
// 返回纯文本字符串
async function chapterContent(chapterUrl) {
  let html = await legado.http.get(chapterUrl);
  html = ensureCfPassed(chapterUrl, html);
  const doc = legado.dom.parse(html);
  const title = legado.dom.selectText(doc, "h1");
  const cleanHtml = legado.dom.remove(
    doc,
    "h1, .txtinfo, script, .bottom-ad, #txtright",
  );
  legado.dom.free(doc);

  const cleanDoc = legado.dom.parse(cleanHtml);
  let content = legado.dom.selectText(cleanDoc, ".txtnav");
  legado.dom.free(cleanDoc);

  const noise = /\(本章完\)/g;
  content = content
    .replace(noise, "")
    .replace(new RegExp(`^\\s*${title}\\s*\\n?`), "")
    .replace(/\n{3,}/g, "\n\n");
  return content ?? "";
}

// ── 发现页(可选) ────────────────────────────────────────────
// 返回 ExploreItem[],不需要时可删除此函数
// async function explore(page) { ... }
广告