22笔趣阁
https://m.22biqu.net
zpccool (13551) 1天前 下载:344
小说 笔趣阁 小说 免费小说 域名多
22笔趣阁(22biqu.net),笔趣阁系列镜像站,海量免费小说。
// @name 22笔趣阁
// @version 2.0.0
// @uuid 22biquge
// @author Ai
// @url https://m.22biqu.net
// @logo https://m.22biqu.net/favicon.ico
// @enabled false
// @tags 笔趣阁,小说,免费小说,域名多
// @description 22笔趣阁(22biqu.net),笔趣阁系列镜像站,海量免费小说。
// ─── 内置测试 ─────────────────────────────────────────────────────────────
async function TEST(type) {
if (type === '__list__') return ['search', 'explore'];
if (type === 'search') {
var results = await search('斗破苍穹', 1);
if (!results || results.length < 1) return { passed: false, message: '搜索结果为空' };
var found = false;
for (var i = 0; i < results.length; i++) {
if (results[i].author && results[i].author.indexOf('天蚕土豆') !== -1) { found = true; break; }
}
if (!found) return { passed: false, message: '搜索结果中未找到作者包含"天蚕土豆"的条目' };
return { passed: true, message: '搜索"斗破苍穹"返回 ' + results.length + ' 条结果 ✓' };
}
if (type === 'explore') {
var books = await explore(1, '总人气排行');
if (!books || books.length < 1) return { passed: false, message: '发现页 [总人气排行] 返回为空' };
return { passed: true, message: '发现页 [总人气排行]: ' + books.length + ' 条结果 ✓' };
}
return { passed: false, message: '未知测试类型: ' + type };
}
// ─── 配置 ────────────────────────────────────────────────────────────────
var BASE = 'https://m.22biqu.net';
// ─── 工具 ────────────────────────────────────────────────────────────────
/**
* 将相对 URL 补全为绝对 URL。
*/
function toAbs(href) {
if (!href) return '';
if (href.indexOf('http') === 0) return href;
return BASE + (href.charAt(0) === '/' ? href : '/' + href);
}
// ─── 搜索 ─────────────────────────────────────────────────────────────────
async function search(keyword, page) {
legado.log('[search] keyword=' + keyword);
var url = BASE + '/ss/?searchkey=' + encodeURIComponent(keyword) + '&submit=';
var html = await legado.http.get(url);
var doc = legado.dom.parse(html);
var books = [];
var items = legado.dom.selectAll(doc, '.bookbox');
for (var i = 0; i < items.length; i++) {
var el = items[i];
var bookUrl = legado.dom.selectAttr(el, 'a[href*="/biqu"]', 'href');
if (!bookUrl) continue;
bookUrl = toAbs(bookUrl);
var name = legado.dom.selectText(el, '.bookname a');
if (!name) continue;
var coverUrl = legado.dom.selectAttr(el, 'img', 'data-src')
|| legado.dom.selectAttr(el, 'img', 'data-original')
|| legado.dom.selectAttr(el, 'img', 'src')
|| '';
var authorBlock = legado.dom.selectText(el, '.author') || '';
var authorM = authorBlock.match(/作者[::]\s*(\S+)/);
var author = authorM ? authorM[1] : '';
var kindM = authorBlock.match(/类型[::]\s*(\S+)/);
var kind = kindM ? kindM[1] : '';
var lastChapter = legado.dom.selectText(el, '.update a') || '';
books.push({
name: name,
author: author,
bookUrl: bookUrl,
coverUrl: coverUrl,
lastChapter: lastChapter,
kind: kind,
});
}
legado.dom.free(doc);
legado.log('[search] found=' + books.length);
return books;
}
// ─── 书籍详情 ─────────────────────────────────────────────────────────────
async function bookInfo(bookUrl) {
legado.log('[bookInfo] url=' + bookUrl);
var html = await legado.http.get(bookUrl);
var doc = legado.dom.parse(html);
var result = {
name: legado.dom.selectAttr(doc, '[property="og:novel:book_name"]', 'content') || '',
author: legado.dom.selectAttr(doc, '[property="og:novel:author"]', 'content') || '',
coverUrl: legado.dom.selectAttr(doc, '[property="og:image"]', 'content') || '',
intro: legado.dom.selectAttr(doc, '[property="og:description"]', 'content') || '',
lastChapter: legado.dom.selectAttr(doc, '[property="og:novel:latest_chapter_name"]', 'content')
|| legado.dom.selectAttr(doc, '[property="og:novel:lastest_chapter_name"]', 'content')
|| '',
kind: legado.dom.selectAttr(doc, '[property="og:novel:category"]', 'content') || '',
tocUrl: bookUrl,
};
legado.dom.free(doc);
return result;
}
// ─── 章节列表 ─────────────────────────────────────────────────────────────
async function chapterList(tocUrl) {
legado.log('[chapterList] url=' + tocUrl);
var chapters = [];
var seenUrls = {};
// ── 步骤1:从第一页获取所有分页 URL ─────────────────────────────────────
// 22biqu.net 在目录底部有 <select id="indexselect">,每个 option value 是一个分页路径。
// 直接枚举比跟随"下一页"更可靠,不受超时截断影响。
var html0 = await legado.http.get(tocUrl);
var doc0 = legado.dom.parse(html0);
var optVals = legado.dom.selectAllAttrs(doc0, '#indexselect option', 'value');
legado.dom.free(doc0);
if (optVals && optVals.length > 0) {
// ── 模式一:下拉枚举所有分页(推荐)───────────────────────────────────
legado.log('[chapterList] select mode, pages=' + optVals.length);
// ── 并发批量抓取所有分页 ────────────────────────────────────────────────
var pageUrls = [];
for (var p = 0; p < optVals.length && p < 50; p++) {
pageUrls.push(toAbs(optVals[p]));
}
legado.log('[chapterList] batchGet ' + pageUrls.length + ' pages');
var batchResults = await legado.http.batchGet(pageUrls);
for (var p = 0; p < batchResults.length; p++) {
var res = batchResults[p];
if (!res.ok) { legado.log('[chapterList] page failed: ' + res.url); continue; }
var doc = legado.dom.parse(res.data);
var links = legado.dom.selectAll(doc, '.directoryArea:not(#chapterlist) a');
for (var i = 0; i < links.length; i++) {
var href = legado.dom.attr(links[i], 'href') || '';
if (!/\/biqu\d+\/\d+\.html/.test(href)) continue;
var chUrl = toAbs(href);
var chName = (legado.dom.text(links[i]) || '').trim();
if (chName && chUrl && !seenUrls[chUrl]) {
seenUrls[chUrl] = 1;
chapters.push({ name: chName, url: chUrl });
}
}
legado.dom.free(doc);
}
} else {
// ── 模式二:跟随"下一页"链接(备用)──────────────────────────────────
legado.log('[chapterList] nextpage mode');
var url = tocUrl;
for (var p = 0; p < 50; p++) {
var html = await legado.http.get(url);
var doc = legado.dom.parse(html);
var links = legado.dom.selectAll(doc, '.directoryArea:not(#chapterlist) a');
for (var i = 0; i < links.length; i++) {
var href = legado.dom.attr(links[i], 'href') || '';
if (!/\/biqu\d+\/\d+\.html/.test(href)) continue;
var chUrl = toAbs(href);
var chName = (legado.dom.text(links[i]) || '').trim();
if (chName && chUrl && !seenUrls[chUrl]) {
seenUrls[chUrl] = 1;
chapters.push({ name: chName, url: chUrl });
}
}
var nextLink = legado.dom.selectByText(doc, '下一页');
var nextHref = nextLink ? (legado.dom.attr(nextLink, 'href') || '') : '';
legado.dom.free(doc);
if (!nextHref || nextHref.indexOf('javascript') !== -1) break;
var nextUrl = toAbs(nextHref);
if (nextUrl === url) break;
url = nextUrl;
}
}
// 网站目录已为正序(第1章在前),直接返回
legado.log('[chapterList] total=' + chapters.length);
return chapters;
}
// ─── 正文 ─────────────────────────────────────────────────────────────────
async function chapterContent(chapterUrl) {
legado.log('[content] url=' + chapterUrl);
var paragraphs = [];
var url = chapterUrl;
var MAX_PAGES = 10;
for (var p = 0; p < MAX_PAGES; p++) {
var html = await legado.http.get(url);
var doc = legado.dom.parse(html);
var contentEl = legado.dom.select(doc, '#chaptercontent');
if (!contentEl) {
legado.dom.free(doc);
legado.log('[content] chaptercontent not found at page ' + (p + 1));
break;
}
// 提取每个 <p> 段落的文本
var pTags = legado.dom.selectAll(contentEl, 'p');
for (var i = 0; i < pTags.length; i++) {
var text = (legado.dom.text(pTags[i]) || '').trim();
if (text && !/本章未完|加入书签|章节报错|点击下一页|笔趣阁/.test(text)) {
paragraphs.push(text);
}
}
// 翻页:id="pt_next" 的链接
var nextHref = legado.dom.selectAttr(doc, '#pt_next', 'href');
legado.dom.free(doc);
if (!nextHref || nextHref.indexOf('javascript') !== -1) break;
var nextUrl = toAbs(nextHref);
if (nextUrl === url) break;
url = nextUrl;
}
return paragraphs.join('\n\n');
}
// ─── 发现页 ──────────────────────────────────────────────────────────────
var EXPLORE_CATEGORIES = [
{ name: '总人气排行', path: '/rank/allvisit/' },
{ name: '月排行榜', path: '/rank/monthvisit/' },
{ name: '周排行榜', path: '/rank/weekvisit/' },
{ name: '收藏榜', path: '/rank/goodnum/' },
{ name: '玄幻魔法', path: '/fenlei/1_1.html' },
{ name: '武侠修真', path: '/fenlei/2_1.html' },
{ name: '都市言情', path: '/fenlei/3_1.html' },
{ name: '历史军事', path: '/fenlei/4_1.html' },
{ name: '游戏竞技', path: '/fenlei/5_1.html' },
{ name: '科幻灵异', path: '/fenlei/6_1.html' },
{ name: '女生耽美', path: '/fenlei/7_1.html' },
];
/**
* 从排行/分类页 HTML 中提取书籍列表。
* 兼容 hot_sale(排行页)和 bookbox(分类页)两种卡片结构。
*/
function parseBooksFromHtml(html) {
var doc = legado.dom.parse(html);
var books = [];
// 先尝试 hot_sale 结构(排行页)
var items = legado.dom.selectAll(doc, '.hot_sale');
for (var i = 0; i < items.length; i++) {
var el = items[i];
var bookUrl = legado.dom.selectAttr(el, 'a[href*="/biqu"]', 'href');
if (!bookUrl) continue;
bookUrl = toAbs(bookUrl);
var name = legado.dom.selectText(el, '.title') || legado.dom.selectText(el, 'h4') || '';
name = name.replace(/^\d+\./, '').trim();
var coverUrl = legado.dom.selectAttr(el, 'img', 'data-src')
|| legado.dom.selectAttr(el, 'img', 'data-original')
|| legado.dom.selectAttr(el, 'img', 'src')
|| '';
var authorText = legado.dom.selectText(el, '.author') || '';
var author = authorText.replace(/作者[::]\s*/, '').replace(/\s*[((]\d{4}[\s\S]*$/, '').trim();
if (name && bookUrl) {
books.push({ name: name, author: author, bookUrl: bookUrl, coverUrl: coverUrl, kind: '' });
}
}
// 如果 hot_sale 没结果,尝试 bookbox 结构(分类页)
if (books.length === 0) {
items = legado.dom.selectAll(doc, '.bookbox');
for (var j = 0; j < items.length; j++) {
var el2 = items[j];
var bookUrl2 = legado.dom.selectAttr(el2, 'a[href*="/biqu"]', 'href');
if (!bookUrl2) continue;
bookUrl2 = toAbs(bookUrl2);
var name2 = legado.dom.selectText(el2, '.bookname a') || '';
var coverUrl2 = legado.dom.selectAttr(el2, 'img', 'data-src')
|| legado.dom.selectAttr(el2, 'img', 'data-original')
|| legado.dom.selectAttr(el2, 'img', 'src')
|| '';
var authorBlock2 = legado.dom.selectText(el2, '.author') || '';
var authorM2 = authorBlock2.match(/作者[::]\s*(\S+)/);
var author2 = authorM2 ? authorM2[1] : '';
var kindM2 = authorBlock2.match(/类型[::]\s*(\S+)/);
var kind2 = kindM2 ? kindM2[1] : '';
var lastChapter2 = legado.dom.selectText(el2, '.update a') || '';
if (name2 && bookUrl2) {
books.push({ name: name2, author: author2, bookUrl: bookUrl2, coverUrl: coverUrl2, lastChapter: lastChapter2, kind: kind2 });
}
}
}
legado.dom.free(doc);
return books;
}
async function explore(page, category) {
legado.log('[explore] page=' + page + ' category=' + category);
if (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 [];
}
var url = BASE + cat.path;
legado.log('[explore] url=' + url);
var html = await legado.http.get(url);
return parseBooksFromHtml(html);
}
// 设计目标是 发现功能 高级的发现功能 可以返回具体的html 代码让直接在界面内进行渲染, 然后内部也包含执行一些js 功能 ,js 功能 触发后也能转发回来