166看书小说
http://www.16kbook.co
ethereal-essence (13554) 16小时前 下载:310
小说 免费 小说 免费小说
166看书小说(16kbook.co),免费网文小说站,以玄幻/都市/言情题材为主。
// @uuid 019e120c-c6a3-75eb-b43a-ad55c4db19c5
// @name 166看书小说
// @version 2.0.1
// @author Ethereal
// @url http://www.16kbook.co
// @logo http://www.16kbook.co/favicon.ico
// @enabled true
// @tags 免费,小说,免费小说
// @description 166看书小说(16kbook.co),免费网文小说站,以玄幻/都市/言情题材为主。
// ─── 内置测试 ─────────────────────────────────────────────────────────────
async function TEST(type) {
if (type === '__list__') return ['search', 'explore', 'bookInfo', 'chapterList', 'chapterContent'];
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 === 'explore') {
var books = await explore(1, '玄幻');
if (!books || books.length < 1) return { passed: false, message: '发现页 [玄幻] 返回为空' };
return { passed: true, message: '发现页 [玄幻]: ' + books.length + ' 条结果 ✓' };
}
if (type === 'bookInfo') {
var r = await bookInfo('http://www.16kbook.co/81_81408/');
return { passed: !!r.name, message: 'bookInfo name=' + r.name };
}
if (type === 'chapterList') {
var r = await chapterList('http://www.16kbook.co/81_81408/');
return { passed: r.length > 0, message: 'chapterList cnt=' + r.length + ' first=' + (r[0] ? r[0].name : 'N/A') };
}
if (type === 'chapterContent') {
var r = await chapterContent('http://www.16kbook.co/81_81408/43271175.html');
return { passed: r.length > 100, message: 'chapterContent len=' + r.length + ' first=' + r.substring(0, 40) };
}
return { passed: false, message: '未知测试类型: ' + type };
}
// ─── 配置 ────────────────────────────────────────────────────────────────
var BASE = 'http://www.16kbook.co';
/** 章节 URL 正则:/数字_数字/数字.html 或 /数字_数字/数字_数字.html(多页) */
var CHAPTER_URL_PATTERN = /\/\d+_\d+\/\d+(?:_\d+)?\.html/;
// ─── 工具 ────────────────────────────────────────────────────────────────
function toAbs(href) {
if (!href) return '';
if (href.indexOf('http') === 0) return href;
return BASE + (href.charAt(0) === '/' ? href : '/' + href);
}
/** 将相对 href 解析为绝对 URL(基于 baseUrl 的目录) */
function resolveUrl(href, baseUrl) {
if (!href) return '';
if (href.indexOf('http') === 0) return href;
if (href.charAt(0) === '/') return BASE + href;
var dir = baseUrl.replace(/[^\/]*$/, '');
return dir + href;
}
function extractCover(el, selector) {
return legado.dom.selectAttr(el, selector, 'data-original')
|| legado.dom.selectAttr(el, selector, 'data-src')
|| legado.dom.selectAttr(el, selector, 'src')
|| '';
}
/** 与搜索路径解析共用同一页 HTML,只请求一次分类列表 */
var _cookieReady = false;
var _probeHtml = '';
/** 列表页 URL(用于 Referer;勿用 /rank/ 后立刻打搜索接口,易触发站点频控) */
var SEARCH_PATH_PROBE = '/class/xuanhuan/1/';
/** 站点对非浏览器栈较敏感,请求头贴近真实浏览器 */
var HTML_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
/** 与常见桌面 Chrome 一致;站点对 Legado 默认 UA 会返回「搜索间隔」脚本 */
var CHROME_UA =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
/** 列表/详情/章节与探测页统一请求头;鸿蒙等端裸 http.get 常拿到空壳页,DOM 无 .item */
function httpGetHtml(url, referer) {
if (!referer) referer = BASE + '/';
return legado.http.get(url, {
Accept: HTML_ACCEPT,
'Accept-Language': 'zh-CN,zh;q=0.9',
'User-Agent': CHROME_UA,
Referer: referer,
});
}
async function ensureCookie() {
if (_cookieReady) return;
await resolveSearchPath();
_cookieReady = true;
}
var _searchPath = '';
async function resolveSearchPath() {
if (_searchPath) return _searchPath;
var probeUrl = BASE + SEARCH_PATH_PROBE;
legado.log('[searchPath] probe=' + probeUrl);
if (!_probeHtml) {
_probeHtml = await httpGetHtml(probeUrl);
}
var doc = legado.dom.parse(_probeHtml);
var form = legado.dom.select(doc, 'form#search');
if (!form) form = legado.dom.select(doc, 'form[name="search"]');
if (!form) {
legado.dom.free(doc);
_probeHtml = '';
legado.log('[searchPath] form not found');
return '';
}
var act = (legado.dom.attr(form, 'action') || '').trim();
legado.dom.free(doc);
if (!act) {
_probeHtml = '';
legado.log('[searchPath] empty action');
return '';
}
if (act.indexOf('http://') === 0 || act.indexOf('https://') === 0) {
var slash = act.indexOf('/', act.indexOf('//') + 2);
_searchPath = slash >= 0 ? act.substring(slash) : '';
} else {
_searchPath = act.charAt(0) === '/' ? act : '/' + act;
}
legado.log('[searchPath] ' + _searchPath);
_probeHtml = '';
return _searchPath;
}
// ─── 搜索 ─────────────────────────────────────────────────────────────────
async function search(keyword, page) {
legado.log('[search] keyword=' + keyword);
await ensureCookie();
var path = await resolveSearchPath();
if (!path) {
legado.log('[search] unresolved path');
return [];
}
var url = BASE + path + '?searchkey=' + encodeURIComponent(keyword);
var html = await httpGetHtml(url, BASE + SEARCH_PATH_PROBE);
if (html.indexOf('搜索间隔') !== -1) {
legado.log('[search] http blocked, browser extract');
var browserCode = [
'await new Promise(function(r){ setTimeout(r, 600); });',
'var nodes = document.querySelectorAll(".item");',
'var out = [];',
'for (var i = 0; i < nodes.length; i++) {',
' var el = nodes[i];',
' var na = el.querySelector("dt a");',
' if (!na) continue;',
' var href = na.getAttribute("href") || "";',
' if (!href) continue;',
' var name = (na.textContent || "").trim();',
' if (!name) continue;',
' var img = el.querySelector("img");',
' var coverUrl = "";',
' if (img) {',
' coverUrl = img.getAttribute("data-original") || img.getAttribute("data-src") || img.getAttribute("src") || "";',
' }',
' var ba = el.querySelector(".btm a");',
' var author = ba ? (ba.textContent || "").trim() : "";',
' var dd = el.querySelector("dd");',
' var intro = dd ? (dd.textContent || "").trim() : "";',
' out.push({ name: name, author: author, bookUrl: href, coverUrl: coverUrl, intro: intro });',
'}',
'return JSON.stringify(out);',
].join('');
try {
var json = await legado.browser.run(url, browserCode, {
visible: false,
waitUntil: 'domcontentloaded',
timeoutSecs: 25,
});
if (!json || json.length < 5) return [];
var arr = JSON.parse(json);
for (var j = 0; j < arr.length; j++) {
if (arr[j].bookUrl) arr[j].bookUrl = toAbs(arr[j].bookUrl);
if (arr[j].coverUrl) arr[j].coverUrl = toAbs(arr[j].coverUrl);
}
legado.log('[search] found=' + arr.length + ' (browser)');
return arr;
} catch (e) {
legado.log('[search] browser failed: ' + e.message);
return [];
}
}
if (!html || html.length < 200) {
legado.log('[search] empty body');
return [];
}
var doc = legado.dom.parse(html);
var books = [];
var items = legado.dom.selectAll(doc, '.item');
for (var i = 0; i < items.length; i++) {
var el = items[i];
var nameLink = legado.dom.select(el, 'dt a');
if (!nameLink) continue;
var bookUrl = legado.dom.attr(nameLink, 'href') || '';
if (!bookUrl) continue;
bookUrl = toAbs(bookUrl);
var name = (legado.dom.text(nameLink) || '').trim();
if (!name) continue;
var coverUrl = extractCover(el, 'img');
// 作者在 .btm a 中
var author = (legado.dom.selectText(el, '.btm a') || '').trim();
var intro = (legado.dom.selectText(el, 'dd') || '').trim();
books.push({
name: name,
author: author,
bookUrl: bookUrl,
coverUrl: coverUrl,
intro: intro,
});
}
legado.dom.free(doc);
legado.log('[search] found=' + books.length);
return books;
}
// ─── 书籍详情 ─────────────────────────────────────────────────────────────
async function bookInfo(bookUrl) {
legado.log('[bookInfo] url=' + bookUrl);
await ensureCookie();
var html = await httpGetHtml(bookUrl, BASE + '/');
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:lastest_chapter_name"]', 'content')
|| legado.dom.selectAttr(doc, '[property="og:novel:latest_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);
await ensureCookie();
var seenUrls = {};
var html = await httpGetHtml(tocUrl, BASE + '/');
var doc = legado.dom.parse(html);
// 结构: <a href="..." rel="chapter"><dd>章节名</dd></a>(a 包裹 dd)
// 页面先有"最新章节"(倒序),再有"全部章节"(正序),需去重并按章节 ID 排序
var links = legado.dom.selectAll(doc, '#list a');
var chapters = [];
for (var i = 0; i < links.length; i++) {
var href = legado.dom.attr(links[i], 'href') || '';
if (!CHAPTER_URL_PATTERN.test(href)) continue;
var chUrl = toAbs(href);
var chName = (legado.dom.text(links[i]) || '').trim();
if (chName && chUrl && !seenUrls[chUrl]) {
seenUrls[chUrl] = 1;
// 提取章节数字 ID 用于排序(URL 格式 /数字_数字/章节ID.html)
var idMatch = href.match(/\/(\d+)\.html/);
var chId = idMatch ? parseInt(idMatch[1]) : i;
chapters.push({ name: chName, url: chUrl, _id: chId });
}
}
// 按章节 ID 升序排列,保证从第一章到最新章
chapters.sort(function(a, b) { return a._id - b._id; });
for (var j = 0; j < chapters.length; j++) {
delete chapters[j]._id;
}
legado.dom.free(doc);
legado.log('[chapterList] total=' + chapters.length);
return chapters;
}
// ─── 正文 ─────────────────────────────────────────────────────────────────
async function chapterContent(chapterUrl) {
legado.log('[content] url=' + chapterUrl);
await ensureCookie();
var paragraphs = [];
var url = chapterUrl;
var MAX_PAGES = 10;
for (var p = 0; p < MAX_PAGES; p++) {
var html = await httpGetHtml(url, BASE + '/');
var doc = legado.dom.parse(html);
var contentEl = legado.dom.select(doc, '#booktxt');
if (!contentEl) {
legado.dom.free(doc);
legado.log('[content] #booktxt not found at page ' + (p + 1));
break;
}
var contentHtml = legado.dom.html(contentEl) || '';
var lines = contentHtml.split(/<br\s*\/?>|<\/?p[^>]*>/i);
for (var i = 0; i < lines.length; i++) {
var text = lines[i].replace(/<[^>]+>/g, '').replace(/ /g, ' ').replace(/<[^;]*>/g, '').replace(/lt;[^;]*gt;/g, '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\s+/g, ' ').trim();
if (text && !/本章未完|加入书签|章节报错|最近转码严重|退出阅读模式|一秒记住|最快更新|16kbook/.test(text)) {
paragraphs.push(text);
}
}
// 翻页
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 = resolveUrl(nextHref, url);
if (nextUrl === url) break;
url = nextUrl;
}
return paragraphs.join('\n\n');
}
// ─── 发现页 ──────────────────────────────────────────────────────────────
var EXPLORE_CATEGORIES = [
{ name: '排行', path: '/rank/' },
{ name: '玄幻', path: '/class/xuanhuan/1/' },
{ name: '武侠', path: '/class/wuxia/1/' },
{ name: '都市', path: '/class/dushi/1/' },
{ name: '历史', path: '/class/lishi/1/' },
{ name: '科幻', path: '/class/kehuan/1/' },
{ name: '游戏', path: '/class/youxi/1/' },
{ name: '女生', path: '/class/nvsheng/1/' },
{ name: '其他', path: '/class/qita/1/' },
{ name: '全本', path: '/quanben/class/1/' },
];
async function explore(page, category) {
var cat = null;
for (var i = 0; i < EXPLORE_CATEGORIES.length; i++) {
if (EXPLORE_CATEGORIES[i].name === category) {
cat = EXPLORE_CATEGORIES[i];
break;
}
}
if (!cat) return EXPLORE_CATEGORIES.map(function(c) { return c.name; });
await ensureCookie();
var url = BASE + cat.path;
var html = await httpGetHtml(url, BASE + '/');
var doc = legado.dom.parse(html);
var books = [];
var items = legado.dom.selectAll(doc, '.item');
for (var i = 0; i < items.length; i++) {
var el = items[i];
var nameLink = legado.dom.select(el, 'dt a');
if (!nameLink) continue;
var bookUrl = legado.dom.attr(nameLink, 'href') || '';
if (!bookUrl) continue;
bookUrl = toAbs(bookUrl);
var name = (legado.dom.text(nameLink) || '').trim();
if (!name) continue;
var coverUrl = extractCover(el, 'img');
var author = (legado.dom.selectText(el, '.btm a') || '').trim();
books.push({ name: name, author: author, bookUrl: bookUrl, coverUrl: coverUrl });
}
legado.dom.free(doc);
return books;
}