55读书小说网
https://www.changduzw.com
zpccool (13551) 1天前 下载:377
小说 免费小说 玄幻 仙侠 都市 完本
55读书小说网 - 全分类免费小说阅读,支持书名/作者搜索
// ==UserScript==
// @name 55读书小说网
// @uuid 55dushuxiaoshuowang
// @version 2.1.0
// @author Ai
// @url https://www.changduzw.com
// @logo https://www.changduzw.com/qiji/images/logo.png
// @type novel
// @enabled true
// @tags 免费小说,玄幻,仙侠,都市,完本
// @description 55读书小说网 - 全分类免费小说阅读,支持书名/作者搜索
// ==/UserScript==
// 基础URL
const BASE = 'https://www.changduzw.com';
// 工具函数
function absUrl(path) {
if (!path) return '';
if (path.startsWith('http')) return path;
if (path.startsWith('//')) return 'https:' + path;
return BASE + (path.startsWith('/') ? path : '/' + path);
}
function trimStr(str) {
return str ? str.replace(/[\s\n\r]+/g, ' ').trim() : '';
}
function normalizeBookUrl(url) {
var full = absUrl(url);
var m1 = full.match(/\/books\/(\d+)\/?/);
if (m1) return BASE + '/books/' + m1[1] + '/';
var m2 = full.match(/\/book\/\d+\/(\d+)\/?/);
if (m2) return BASE + '/books/' + m2[1] + '/';
return full;
}
function parseCoverFromNode(node, selectors) {
var i;
for (i = 0; i < selectors.length; i++) {
var img = legado.dom.select(node, selectors[i]);
if (img) {
var src = legado.dom.attr(img, 'src') ||
legado.dom.attr(img, 'data-src') ||
legado.dom.attr(img, 'data-original') ||
legado.dom.attr(img, 'data-lazy-src') ||
legado.dom.attr(img, 'data-url');
if (src) return absUrl(src);
}
}
return '';
}
function normalizeCoverUrl(url) {
var cover = absUrl(url || '');
if (!cover) return '';
if (cover.indexOf('/qiji/images/lazy.gif') > -1) return '';
if (cover.indexOf('/modules/article/images/nocover.jpg') > -1) return '';
if (cover.indexOf('/qiji/images/logo.png') > -1) return '';
return cover;
}
function guessCoverFromBookUrl(bookUrl) {
var full = absUrl(bookUrl || '');
var idMatch = full.match(/\/books\/(\d+)\/?/) || full.match(/\/book\/\d+\/(\d+)\/?/);
if (!idMatch) return '';
var id = idMatch[1];
var idNum = parseInt(id, 10);
if (!idNum || idNum < 1) return '';
// 站点封面目录通常按 articleid / 1000 分组,例如 65114 -> 65,215296 -> 215
var prefix = String(Math.floor(idNum / 1000));
if (!prefix || prefix === '0') {
prefix = id.length > 2 ? id.slice(0, 2) : id.slice(0, 1);
}
return 'https://www.changduzw.com/files/article/image/' + prefix + '/' + id + '/' + id + 's.jpg';
}
function extractCoverFromHtml(html) {
if (!html) return '';
var m =
html.match(/<div class=["']imgbox["'][\s\S]*?<img[^>]+data-src=["']([^"']+)["']/i) ||
html.match(/<div class=["']shu_img["'][\s\S]*?<img[^>]+data-src=["']([^"']+)["']/i) ||
html.match(/<img[^>]+data-src=["']([^"']+\/\d+s\.jpg)["']/i) ||
html.match(/<img[^>]+src=["']([^"']+\/\d+s\.jpg)["']/i);
if (!m) return '';
return normalizeCoverUrl(m[1]);
}
async function fetchCoverFromBookPage(bookUrl) {
try {
var html = await legado.http.get(absUrl(bookUrl));
var byRegex = extractCoverFromHtml(html);
if (byRegex) return byRegex;
var doc = legado.dom.parse(html);
var cover = parseCoverFromNode(doc, ['div.imgbox img', '.bookimg img', '.book-info img', '.info img', 'img']);
cover = normalizeCoverUrl(cover);
if (cover) return cover;
return normalizeCoverUrl(guessCoverFromBookUrl(bookUrl));
} catch (e) {
return normalizeCoverUrl(guessCoverFromBookUrl(bookUrl));
}
}
// 搜索功能
async function search(keyword, page = 1) {
legado.log('[55读书] search: ' + keyword + ', page=' + page);
const formData = new URLSearchParams();
formData.append('searchkey', keyword);
formData.append('searchtype', 'articlename');
if (page > 1) formData.append('page', page);
const html = await legado.http.post(
BASE + '/modules/article/search.php',
formData.toString(),
{
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': BASE + '/'
}
);
const doc = legado.dom.parse(html);
const books = [];
const seen = new Set();
const rows = legado.dom.selectAll(doc, 'table.grid > tbody > tr');
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const nameLink = legado.dom.select(row, 'td:first-child a');
if (nameLink) {
const name = trimStr(legado.dom.text(nameLink));
const author = trimStr(legado.dom.selectText(row, 'td:nth-child(3)'));
const bookUrl = normalizeBookUrl(legado.dom.attr(nameLink, 'href'));
let coverUrl = normalizeCoverUrl(parseCoverFromNode(row, ['img', 'td:first-child img', 'td:nth-child(1) img']));
// 搜索阶段不再请求详情页,避免 N 次串行请求拖慢速度
if (!coverUrl) {
coverUrl = normalizeCoverUrl(guessCoverFromBookUrl(bookUrl));
}
const lastChapterLink = legado.dom.select(row, 'td:nth-child(2) a');
const lastChapter = lastChapterLink ? trimStr(legado.dom.text(lastChapterLink)) : '';
const lastChapterUrl = lastChapterLink ? absUrl(legado.dom.attr(lastChapterLink, 'href')) : '';
const updateTime = trimStr(legado.dom.selectText(row, 'td:nth-child(5)'));
const wordCount = trimStr(legado.dom.selectText(row, 'td:nth-child(4)'));
const status = trimStr(legado.dom.selectText(row, 'td:last-child'));
const dedupeKey = (bookUrl || '') + '|' + (name || '') + '|' + (author || '');
if (seen.has(dedupeKey)) {
continue;
}
seen.add(dedupeKey);
books.push({
name,
author,
bookUrl,
coverUrl,
kind: '',
lastChapter,
lastChapterUrl,
updateTime,
wordCount,
status
});
}
}
return books;
}
// 书籍详情 - 优化封面获取
async function bookInfo(bookUrl) {
legado.log('[55读书] bookInfo: ' + bookUrl);
const html = await legado.http.get(absUrl(bookUrl));
const doc = legado.dom.parse(html);
// 从meta标签获取信息
function getMeta(prop) {
const el = legado.dom.select(doc, `meta[property="${prop}"]`);
return el ? legado.dom.attr(el, 'content') || '' : '';
}
const name = getMeta('og:novel:book_name') || getMeta('og:title') || trimStr(legado.dom.selectText(doc, 'div.status h1')) || '未知书名';
const author = getMeta('og:novel:author') || trimStr(legado.dom.selectText(doc, 'div.status p.author')).replace('作者:', '') || '未知作者';
const intro = getMeta('og:description') || trimStr(legado.dom.selectText(doc, 'div.jianjie.tabpanel p'));
// 优化封面获取:尝试多种方式获取封面图片
let coverUrl = '';
// 1. 从meta标签获取
const metaCover = getMeta('og:image');
if (metaCover) {
coverUrl = normalizeCoverUrl(metaCover);
}
// 2. 从imgbox获取
if (!coverUrl) coverUrl = extractCoverFromHtml(html);
if (!coverUrl) coverUrl = normalizeCoverUrl(parseCoverFromNode(doc, ['div.imgbox img', '.bookimg img', '.book-info img', '.info img', 'img']));
if (!coverUrl) coverUrl = normalizeCoverUrl(guessCoverFromBookUrl(bookUrl));
// 3. 不做默认封面兜底,取不到就返回空字符串
// 获取分类信息
let kind = getMeta('og:novel:category');
if (!kind) {
const statusDiv = legado.dom.select(doc, 'div.status');
if (statusDiv) {
const paragraphs = legado.dom.selectAll(statusDiv, 'p');
for (let i = 0; i < paragraphs.length; i++) {
const text = legado.dom.text(paragraphs[i]);
if (text && text.includes('分 类')) {
const link = legado.dom.select(paragraphs[i], 'a');
if (link) {
kind = trimStr(legado.dom.text(link));
}
break;
}
}
}
}
// 获取其他信息
let wordCount = '';
let updateTime = '';
let lastChapter = '';
let status = '';
let lastChapterUrl = '';
const statusDiv = legado.dom.select(doc, 'div.status');
if (statusDiv) {
const paragraphs = legado.dom.selectAll(statusDiv, 'p');
for (let i = 0; i < paragraphs.length; i++) {
const text = legado.dom.text(paragraphs[i]);
if (text) {
if (text.includes('总字数')) {
wordCount = trimStr(text.replace('总字数:', ''));
} else if (text.includes('更 新')) {
const redSpan = legado.dom.select(paragraphs[i], 'span.red');
updateTime = redSpan ? trimStr(legado.dom.text(redSpan)) : '';
} else if (text.includes('最新章节')) {
const link = legado.dom.select(paragraphs[i], 'a');
if (link) {
lastChapterUrl = absUrl(legado.dom.attr(link, 'href'));
const redSpan = legado.dom.select(link, 'span.red');
lastChapter = redSpan ? trimStr(legado.dom.text(redSpan)) : '';
}
}
}
}
// 获取状态
const serialSpan = legado.dom.select(statusDiv, 'span.serial');
status = serialSpan ? trimStr(legado.dom.text(serialSpan)) : '';
}
// 如果没有从meta获取到最后章节,尝试其他方式
if (!lastChapter) {
lastChapter = getMeta('og:novel:latest_chapter_name');
}
return {
name,
author,
bookUrl: absUrl(bookUrl),
tocUrl: absUrl(bookUrl),
coverUrl,
intro,
kind,
lastChapter,
lastChapterUrl,
wordCount,
updateTime,
status
};
}
// 章节目录 - 完全修复版:自动跳转到真正的章节列表页
async function chapterList(tocUrl) {
try {
legado.log('[55读书] chapterList: ' + tocUrl);
// 首先获取书籍详情页
const html = await legado.http.get(absUrl(tocUrl));
const doc = legado.dom.parse(html);
// 查找"点击阅读"按钮 - 根据用户提供的源码结构
let chapterListUrl = '';
// 方法1:查找class为"button read"的阅读按钮(精确匹配)
const readButton = legado.dom.select(doc, 'a.button.read');
if (readButton) {
chapterListUrl = absUrl(legado.dom.attr(readButton, 'href'));
}
// 方法2:如果没有找到特定class的按钮,查找包含"点击阅读"文本的链接
if (!chapterListUrl) {
const allLinks = legado.dom.selectAll(doc, 'a');
for (let i = 0; i < allLinks.length; i++) {
const link = allLinks[i];
const text = legado.dom.text(link);
if (text && text.includes('点击阅读')) {
chapterListUrl = absUrl(legado.dom.attr(link, 'href'));
break;
}
}
}
// 方法3:如果还是没有找到,尝试构建章节列表URL
if (!chapterListUrl) {
// 从详情页URL提取书籍ID,然后构建章节列表URL
// 详情页URL格式: /books/65114/
// 章节列表URL格式: /book/65/65114/
const bookIdMatch = tocUrl.match(/books?\/(\d+)/);
if (bookIdMatch) {
const bookId = bookIdMatch[1];
const prefix = bookId.length > 2 ? bookId.slice(0, 2) : bookId.slice(0, 1);
chapterListUrl = `${BASE}/book/${prefix}/${bookId}/`;
} else {
// 如果无法提取,使用原URL
chapterListUrl = tocUrl;
}
}
// 获取真正的章节列表页面
const chapterListHtml = await legado.http.get(chapterListUrl);
const chapterDoc = legado.dom.parse(chapterListHtml);
const chapters = [];
// 从章节列表页面提取章节
// 首先尝试网站的标准结构
const items = legado.dom.selectAll(chapterDoc, 'ul.listtile li');
if (items.length > 0) {
for (let i = 0; i < items.length; i++) {
const item = items[i];
const link = legado.dom.select(item, 'span.zj a') || legado.dom.select(item, 'a');
if (link) {
const name = trimStr(legado.dom.text(link));
const url = absUrl(legado.dom.attr(link, 'href'));
// 过滤非章节链接
if (name && url && !/^(月票|抽奖|中奖|活动|新春|十更|公告)/.test(name)) {
const timeEl = legado.dom.select(item, 'span.time');
const updateTime = timeEl ? trimStr(legado.dom.text(timeEl).replace(/[()]/g, '')) : '';
chapters.push({ name, url, updateTime });
}
}
}
} else {
// 备用选择器 - 尝试其他可能的结构
const altSelectors = [
'div.chapter-list li',
'table.chapter tbody tr',
'ul#chapterList li',
'div#chapterList li',
'div.reader-chapter-list li',
'ul.reader-chapter-list li'
];
for (const selector of altSelectors) {
const altItems = legado.dom.selectAll(chapterDoc, selector);
if (altItems.length > 0) {
for (let i = 0; i < altItems.length; i++) {
const item = altItems[i];
const link = legado.dom.select(item, 'a');
if (link) {
const name = trimStr(legado.dom.text(link));
const url = absUrl(legado.dom.attr(link, 'href'));
chapters.push({ name, url });
}
}
break;
}
}
}
// 如果以上方法都失败,从所有链接中提取
if (chapters.length === 0) {
const allLinks = legado.dom.selectAll(chapterDoc, 'a');
const seenUrls = new Set();
for (let i = 0; i < allLinks.length; i++) {
const link = allLinks[i];
const href = legado.dom.attr(link, 'href');
const name = trimStr(legado.dom.text(link));
if (href && href.includes('.html') && name && name.length > 2) {
const url = absUrl(href);
if (!seenUrls.has(url)) {
seenUrls.add(url);
chapters.push({ name, url });
}
}
}
}
// 检查分页
const paginationLinks = legado.dom.selectAll(chapterDoc, 'a');
let hasNextPage = false;
for (let i = 0; i < paginationLinks.length; i++) {
const text = legado.dom.text(paginationLinks[i]);
if (text && (text.includes('下一页') || text.includes('下页'))) {
hasNextPage = true;
break;
}
}
// 如果有分页,加载后续页面
if (hasNextPage && chapters.length > 0) {
let page = 2;
const maxPages = 10;
while (page <= maxPages) {
const pageUrl = `${chapterListUrl}?page=${page}`;
try {
const pageHtml = await legado.http.get(pageUrl);
const pageDoc = legado.dom.parse(pageHtml);
const pageItems = legado.dom.selectAll(pageDoc, 'ul.listtile li');
let pageChaptersCount = 0;
for (let i = 0; i < pageItems.length; i++) {
const item = pageItems[i];
const link = legado.dom.select(item, 'span.zj a') || legado.dom.select(item, 'a');
if (link) {
const name = trimStr(legado.dom.text(link));
const url = absUrl(legado.dom.attr(link, 'href'));
chapters.push({ name, url });
pageChaptersCount++;
}
}
if (pageChaptersCount === 0) {
break;
}
page++;
await new Promise(function(resolve) { setTimeout(resolve, 300); });
} catch (error) {
console.error(`[55读书] 加载第${page}页失败:`, error);
break;
}
}
}
return chapters;
} catch (error) {
console.error('[55读书] 获取章节目录失败:', error);
return [];
}
}
// 章节正文 - 完全修复版:解决[object object]问题
async function chapterContent(chapterUrl) {
try {
legado.log('[55读书] chapterContent: ' + chapterUrl);
const html = await legado.http.get(absUrl(chapterUrl));
const doc = legado.dom.parse(html);
let contentEl = legado.dom.select(doc, '#htmlContent');
if (!contentEl) {
// 尝试其他常见的内容选择器
const altSelectors = [
'div.content',
'div#content',
'div.chapter-content',
'div.read-content'
];
for (const selector of altSelectors) {
const el = legado.dom.select(doc, selector);
if (el) {
contentEl = el;
break;
}
}
}
if (!contentEl) {
return '';
}
// 移除不需要的元素
legado.dom.remove(contentEl, 'script, style, div, a');
// 确保获取的是文本内容
let content = '';
try {
// 使用textContent而不是text(),确保获取纯文本
content = legado.dom.text(contentEl) || '';
// 如果legado.dom.text返回的是对象,使用toString()转换
if (typeof content !== 'string') {
content = String(content);
}
} catch (error) {
console.error('[55读书] 提取文本内容失败:', error);
content = '';
}
// 清理内容
content = content
.replace(/ /g, ' ')
.replace(/最新网址[^\n]*/g, '')
.replace(/请牢记本站域名[^\n]*/g, '')
.replace(/天才一秒记住[^\n]*/g, '')
.replace(/\.\.\.\.\.\.\.\.\.\./g, '')
.replace(/本章未完.*/g, '')
.replace(/继续阅读.*/g, '')
.trim();
// 确保content是字符串
if (typeof content !== 'string') {
content = JSON.stringify(content) || '';
}
return content || '';
} catch (error) {
console.error('[55读书] 获取章节内容失败:', error);
return '';
}
}
// 发现页
async function explore(page = 1, category = '') {
legado.log('[55读书] explore: category=' + category + ', page=' + page);
const categories = {
'仙侠修真': `${BASE}/fenlei/2_${page}/`,
'玄幻魔法': `${BASE}/fenlei/1_${page}/`,
'都市言情': `${BASE}/fenlei/3_${page}/`,
'历史军事': `${BASE}/fenlei/4_${page}/`,
'网游竞技': `${BASE}/fenlei/5_${page}/`,
'科幻灵异': `${BASE}/fenlei/6_${page}/`,
'完本小说': `${BASE}/quanben/${page > 1 ? page + '.html' : ''}`
};
if (category === 'GETALL' || !category) {
return Object.keys(categories);
}
if (!categories[category]) return [];
const html = await legado.http.get(categories[category]);
const doc = legado.dom.parse(html);
const books = [];
const seen = new Set();
const items = legado.dom.selectAll(doc, 'div.shu_box');
for (let i = 0; i < items.length; i++) {
const item = items[i];
const nameEl = legado.dom.select(item, 'div.shu_xinxi h4 a') || legado.dom.select(item, 'a');
if (nameEl) {
const name = trimStr(legado.dom.text(nameEl));
const author = '';
const bookUrl = normalizeBookUrl(legado.dom.attr(nameEl, 'href'));
let coverUrl = normalizeCoverUrl(parseCoverFromNode(item, [
'img',
'div.shu_img img',
'.bookimg img',
'a img'
]));
if (!coverUrl) {
coverUrl = normalizeCoverUrl(guessCoverFromBookUrl(bookUrl));
}
const updateTime = '';
const wordCount = '';
let status = '';
const dedupeKey = (bookUrl || '') + '|' + (name || '') + '|' + (author || '');
if (seen.has(dedupeKey)) {
continue;
}
seen.add(dedupeKey);
books.push({
name,
author,
bookUrl,
coverUrl,
kind: category,
lastChapter: '',
updateTime,
wordCount,
status
});
}
}
return books;
}
// 测试函数
async function TEST(type) {
if (type === '__list__') return ['search', 'bookInfo', 'chapterList', 'chapterContent', 'explore'];
if (type === 'search') {
const r = await search('斗破苍穹');
return r.length > 0 ?
{ passed: true, message: `搜索成功:${r.length}条结果` } :
{ passed: false, message: '搜索失败' };
}
if (type === 'bookInfo') {
const i = await bookInfo('https://www.changduzw.com/books/65114/');
return i.name !== '未知书名' ?
{ passed: true, message: `详情成功:《${i.name}》封面:${i.coverUrl ? '有' : '无'}` } :
{ passed: false, message: '详情失败' };
}
if (type === 'chapterList') {
const c = await chapterList('https://www.changduzw.com/book/65/65114/');
return c.length > 0 ?
{ passed: true, message: `目录成功:${c.length}章` } :
{ passed: false, message: '目录失败' };
}
if (type === 'chapterContent') {
const c = await chapterContent('https://www.changduzw.com/book/65/65114/9898692.html');
// 小说类型 chapterContent 直接返回字符串
if (typeof c === 'string' && c.length > 100) {
return {
passed: true,
message: `正文成功:${c.length}字,类型:${typeof c}`
};
} else {
return {
passed: false,
message: `正文失败:类型为${typeof c},长度:${c ? c.length : 0}`
};
}
}
if (type === 'explore') {
const e = await explore(1, '仙侠修真');
return e.length > 0 ?
{ passed: true, message: `发现成功:${e.length}本` } :
{ passed: false, message: '发现失败' };
}
return { passed: false, message: `未知类型:${type}` };
}
// 书源导出
({
name: '55读书小说网',
url: BASE,
version: '2.1.0',
logo: 'https://www.changduzw.com/qiji/images/logo.png',
language: 'zh-CN',
enableSearch: true,
search,
bookInfo,
chapterList,
chapterContent,
explore,
TEST
})