365小说网
http://www.shukuge.com
ethereal-essence (13554) 16小时前 下载:305
小说 免费 小说 免费小说
365小说网(shukuge.com),免费全本小说在线阅读站。
// @uuid 019e120e-21b4-7377-8ec3-4d067a4350ac
// @name 365小说网
// @version 1.0.1
// @author Ethereal
// @url http://www.shukuge.com
// @type novel
// @logo http://www.shukuge.com/favicon.ico
// @enabled true
// @tags 免费,小说,免费小说
// @description 365小说网(shukuge.com),免费全本小说在线阅读站。
// ─── 内置测试 ─────────────────────────────────────────────────────────────
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: '搜索结果为空' };
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 || typeof books[0] === 'string') return { passed: false, message: '发现页 [玄幻] 返回为空或格式错误' };
return { passed: true, message: '发现页 [玄幻]: ' + books.length + ' 条结果 ✓' };
}
if (type === 'bookInfo') {
var r = await bookInfo('http://www.shukuge.com/book/166751/');
return { passed: !!r.name, message: 'bookInfo name=' + r.name };
}
if (type === 'chapterList') {
var r = await chapterList('http://www.shukuge.com/book/166751/index.html');
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.shukuge.com/book/166751/53009545.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.shukuge.com';
/** 章节 URL 正则:匹配 /book/数字/数字.html */
var CHAPTER_URL_PATTERN = /\/book\/\d+\/\d+\.html/;
// ─── 工具 ────────────────────────────────────────────────────────────────
function toAbs(href) {
if (!href) return '';
if (href.indexOf('http') === 0) return href;
return BASE + (href.charAt(0) === '/' ? href : '/' + href);
}
/** 分类/发现列表页:封面在封面链内 img,站点使用 src */
function listItemCoverUrl(el) {
return legado.dom.selectAttr(el, 'a.cover img', 'src') || '';
}
function parseListItemMeta(el) {
var author = '';
var kind = '';
var spans = legado.dom.selectAll(el, '.bookdesc .sp span');
if (spans && spans.length > 0) {
author = (legado.dom.text(spans[0]) || '').replace(/作者[::]\s*/, '').trim();
}
if (spans && spans.length > 1) {
kind = (legado.dom.text(spans[1]) || '').replace(/分类[::]\s*/, '').trim();
}
var lastChapter = legado.dom.selectText(el, '.bookdesc .desc a') || '';
return { author: author, kind: kind, lastChapter: lastChapter };
}
/** 分类页 URL:第 1 页为 /path/,第 n 页为 /path/n */
function categoryPageUrl(path, page) {
var p = Number(page) || 1;
if (p <= 1) {
return BASE + path;
}
var base = path.replace(/\/+$/, '');
return BASE + base + '/' + p;
}
// ─── 搜索 ─────────────────────────────────────────────────────────────────
async function search(keyword, page) {
legado.log('[search] keyword=' + keyword);
var url = BASE + '/Search?wd=' + encodeURIComponent(keyword);
var html = await legado.http.get(url);
var doc = legado.dom.parse(html);
var books = [];
// 搜索结果在 .listitem 中
var items = legado.dom.selectAll(doc, '.listitem');
for (var i = 0; i < items.length; i++) {
var el = items[i];
var bookUrl = legado.dom.selectAttr(el, '.bookdesc > a', 'href');
if (!bookUrl) continue;
bookUrl = toAbs(bookUrl);
var name = legado.dom.selectText(el, '.bookdesc h2');
if (!name) continue;
var coverUrl = listItemCoverUrl(el);
var meta = parseListItemMeta(el);
books.push({
name: name,
author: meta.author,
bookUrl: bookUrl,
coverUrl: coverUrl,
lastChapter: meta.lastChapter,
kind: meta.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 name = legado.dom.selectAttr(doc, '.bookdcover img', 'alt') || '';
var coverSrc = legado.dom.selectAttr(doc, '.bookdcover img', 'src') || '';
var coverUrl = coverSrc ? toAbs(coverSrc) : '';
var author = legado.dom.selectText(doc, '.bookdmore p:nth-child(3) a') || '';
var intro = legado.dom.selectText(doc, '.bookdtext p') || '';
intro = intro.replace(/下载后请在24小时之内删除[\s\S]*/i, '').trim();
var lastChapter = legado.dom.selectText(doc, '.bookdmore p:nth-child(7) a') || '';
var kind = legado.dom.selectText(doc, '.bookdmore p:nth-child(1) a') || '';
var tocHref = legado.dom.selectAttr(doc, '.bookdtext a.btn-primary[href*="/index.html"]', 'href');
if (!tocHref) {
throw new Error('[bookInfo] missing 在线阅读 toc link');
}
var tocUrl = toAbs(tocHref);
var result = {
name: name,
author: author,
coverUrl: coverUrl,
intro: intro,
lastChapter: lastChapter,
kind: kind,
tocUrl: tocUrl,
};
legado.dom.free(doc);
return result;
}
// ─── 章节列表 ─────────────────────────────────────────────────────────────
async function chapterList(tocUrl) {
legado.log('[chapterList] url=' + tocUrl);
var chapters = [];
var seenUrls = {};
// 365小说网目录可能分页,通过 <option> 枚举所有分页
var html0 = await legado.http.get(tocUrl);
var doc0 = legado.dom.parse(html0);
var optVals = legado.dom.selectAllAttrs(doc0, 'option', 'value');
legado.dom.free(doc0);
if (optVals && optVals.length > 1) {
// 分页模式
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]));
}
var batchResults = await legado.http.batchGet(pageUrls);
for (var p = 0; p < batchResults.length; p++) {
var res = batchResults[p];
if (!res.ok) {
throw new Error('[chapterList] batch page failed: ' + res.url);
}
var doc = legado.dom.parse(res.data);
var links = legado.dom.selectAll(doc, 'dl dd a');
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;
chapters.push({ name: chName, url: chUrl });
}
}
legado.dom.free(doc);
}
} else {
legado.log('[chapterList] single-page toc');
var html1 = await legado.http.get(tocUrl);
var doc1 = legado.dom.parse(html1);
var links1 = legado.dom.selectAll(doc1, 'dl dd a');
for (var j = 0; j < links1.length; j++) {
var href1 = legado.dom.attr(links1[j], 'href') || '';
if (!CHAPTER_URL_PATTERN.test(href1)) continue;
var chUrl1 = toAbs(href1);
var chName1 = (legado.dom.text(links1[j]) || '').trim();
if (chName1 && chUrl1 && !seenUrls[chUrl1]) {
seenUrls[chUrl1] = 1;
chapters.push({ name: chName1, url: chUrl1 });
}
}
legado.dom.free(doc1);
}
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, '#content #content');
if (!contentEl) {
legado.dom.free(doc);
legado.log('[content] #content #content not found at page ' + (p + 1));
break;
}
var nextLink = legado.dom.selectByText(doc, '下一页');
var nextHref = nextLink ? (legado.dom.attr(nextLink, 'href') || '') : '';
var contentHtml = legado.dom.html(contentEl) || '';
legado.dom.free(doc);
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(/\s+/g, ' ').trim();
if (text && !/本章未完|加入书签|章节报错|365小说网|shukuge\.com/.test(text)) {
paragraphs.push(text);
}
}
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: '/i-xuanhuan/' },
{ name: '言情', path: '/i-yanqing/' },
{ name: '穿越', path: '/i-chuanyue/' },
{ name: '重生', path: '/i-chongsheng/' },
{ name: '架空', path: '/i-jiakong/' },
{ name: '总裁', path: '/i-zongcai/' },
{ name: '武侠', path: '/i-wuxia/' },
{ name: '仙侠', path: '/i-xianxia/' },
{ name: '耽美', path: '/i-danmei/' },
{ name: '都市', path: '/i-dushi/' },
{ name: '军事', path: '/i-junshi/' },
{ name: '网游', path: '/i-wangyou/' },
{ name: '悬疑', path: '/i-xuanyi/' },
{ name: '文学', path: '/i-wenxue/' },
{ name: '科幻', path: '/i-kehuan/' },
{ name: '修真', path: '/i-xiuzhen/' },
{ name: '历史', path: '/i-lishi/' },
{ name: '其他', path: '/i-qita/' },
{ name: '最新小说', path: '/new/' },
{ name: '排行榜', path: '/top/' },
];
async function explore(page, category) {
legado.log('[explore] page=' + page + ' category=' + 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; });
var url = categoryPageUrl(cat.path, page);
var html = await legado.http.get(url);
var doc = legado.dom.parse(html);
var books = [];
var items = legado.dom.selectAll(doc, '.listitem');
for (var i = 0; i < items.length; i++) {
var el = items[i];
var bookUrl = legado.dom.selectAttr(el, '.bookdesc > a', 'href');
if (!bookUrl) continue;
bookUrl = toAbs(bookUrl);
var name = legado.dom.selectText(el, '.bookdesc h2');
if (!name) continue;
var coverUrl = listItemCoverUrl(el);
var meta = parseListItemMeta(el);
books.push({
name: name,
author: meta.author,
bookUrl: bookUrl,
coverUrl: coverUrl,
kind: meta.kind,
lastChapter: meta.lastChapter,
});
}
legado.dom.free(doc);
legado.log('[explore] found=' + books.length);
return books;
}