69书吧
https://www.69shuba.com
PC6live (13656) 05/18 17:51 下载:4308
小说
69书吧,手动过cf验证
// @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) { ... }