55读书小说网

https://www.changduzw.com

zpccool (13551) 1天前 下载:377

小说 免费小说 玄幻 仙侠 都市 完本
55读书小说网 - 全分类免费小说阅读,支持书名/作者搜索
二维码导入(APP尚未完成该功能)
// ==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(/&nbsp;/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
})
广告