// ==UserScript== // @name PO18小说下载器 // @namespace http://tampermonkey.net/ // @version 1.3.4 // @description 下载PO18小说,支持TXT或HTML格式,多线程下载,记录下载历史,增强阅读体验,查看已购书架 // @author wenmoux // @license MIT // @match https://www.po18.tw/* // @icon https://www.po18.tw/favicon.ico // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant unsafeWindow // @connect www.po18.tw // @require https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js // @require https://code.jquery.com/ui/1.12.1/jquery-ui.min.js // @downloadURL https://update.greasyfork.icu/scripts/534737/PO18%E5%B0%8F%E8%AF%B4%E4%B8%8B%E8%BD%BD%E5%99%A8.user.js // @updateURL https://update.greasyfork.icu/scripts/534737/PO18%E5%B0%8F%E8%AF%B4%E4%B8%8B%E8%BD%BD%E5%99%A8.meta.js // ==/UserScript== (function() { 'use strict'; // ==== 辅助函数:HTML解析器(替代Cheerio) ==== const HTMLParser = { parse: function(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); return { querySelector: function(selector) { return doc.querySelector(selector); }, querySelectorAll: function(selector) { return Array.from(doc.querySelectorAll(selector)); }, getTextContent: function(selector) { const el = doc.querySelector(selector); return el ? el.textContent.trim() : ''; }, getAttributeValue: function(selector, attribute) { const el = doc.querySelector(selector); return el ? el.getAttribute(attribute) : null; }, getText: function() { return doc.body.textContent; }, getHTML: function() { return doc.body.innerHTML; }, remove: function(selector) { doc.querySelectorAll(selector).forEach(el => el.remove()); return this; } }; } }; // ==== 样式设置 - 修改为淡粉色主题 ==== GM_addStyle(` /* 粉色主题风格 */ :root { --primary-color: #FF8BA7; /* 主色调修改为淡粉色 */ --primary-light: #FFB2C0; /* 浅色调 */ --primary-dark: #D46A87; /* 深色调 */ --text-on-primary: #ffffff; --surface-color: #ffffff; --background-color: #FFF0F3; --error-color: #D32F2F; --box-shadow: 0 2px 4px rgba(0,0,0,.1), 0 3px 6px rgba(0,0,0,.05); --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); } .po18-downloader { font-family: 'Roboto', sans-serif; color: #333; } .po18-float-button { position: fixed; bottom: 30px; right: 30px; width: 56px; height: 56px; border-radius: 50%; background-color: var(--primary-color); color: var(--text-on-primary); display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 3px 5px rgba(0,0,0,0.3); z-index: 9999; user-select: none; transition: var(--transition); } .po18-float-button:hover { transform: scale(1.1);box-shadow: 0 5px 8px rgba(0,0,0,0.3); } .po18-panel { position: fixed; bottom: 100px; right: 30px; width: 360px; background-color: var(--surface-color); border-radius: 12px; box-shadow: var(--box-shadow); z-index: 9998; overflow: hidden; display: none; max-height: 600px; transition: var(--transition); } .po18-panel.active { display: block; } .po18-header { background-color: var(--primary-color); color: var(--text-on-primary); padding: 16px; font-weight: 500; font-size: 18px; display: flex; justify-content: space-between;align-items: center; } .po18-tabs { display: flex; background-color: var(--primary-light); color: var(--text-on-primary); } .po18-tab { flex: 1; text-align: center; padding: 12px 0; cursor: pointer; transition: var(--transition); border-bottom: 3px solid transparent;} .po18-tab.active { border-bottom: 3px solid white; background-color: var(--primary-color); } .po18-tab:hover:not(.active) { background-color: rgba(255,255,255,0.1); } .po18-tab-content { padding: 16px; max-height: 450px; overflow-y: auto; } .po18-tab-pane { display: none; } .po18-tab-pane.active { display: block; } .po18-card { background-color: white; border-radius: 12px; padding: 16px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } /* 书籍详情样式 */ .po18-book-info { display: flex; margin-bottom: 15px; } .po18-book-cover { width: 100px; height: 140px; object-fit: cover; border-radius: 6px; margin-right: 15px; } .po18-book-details { flex: 1;} .po18-book-title { font-size: 18px; font-weight: bold; margin-bottom: 6px; color: #333; } .po18-book-author { font-size: 14px; color: #666; margin-bottom: 10px; } .po18-book-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 5px; } .po18-book-tag { background-color: var(--primary-light); color: #333; padding: 2px 8px; border-radius: 10px; font-size: 12px;} .po18-form-group { margin-bottom: 12px; } .po18-form-group label { display: block; margin-bottom: 5px; font-weight:500; color: #666; } .po18-select { width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 8px; background-color: white; } .po18-button { padding: 10px 16px; border: none; border-radius: 8px; background-color: var(--primary-color); color: white; cursor: pointer; font-weight: 500; transition: var(--transition); } .po18-button:hover { background-color: var(--primary-dark); }.po18-button:disabled { background-color: #cccccc; cursor: not-allowed; } .po18-progress { height: 8px; background-color: #eee; border-radius: 4px; margin: 10px 0;overflow: hidden; } .po18-progress-bar { height: 100%; background-color: var(--primary-color); width: 0%;transition: width 0.3s ease; } .po18-log { font-family: monospace; background-color: #f8f8f8; padding: 10px; border-radius: 8px; max-height: 200px; overflow-y: auto; font-size: 12px; white-space: pre-wrap;} .po18-record-item { padding: 12px; border-left: 4px solid var(--primary-color); background-color: #f9f9f9; margin-bottom: 10px; border-radius: 08px 8px 0; } .po18-record-item h4 { margin: 0 0 8px 0;} .po18-record-info { display: flex; justify-content: space-between; font-size: 12px; color: #666; } /*拖动样式 */ .po18-draggable { cursor: move; } /* 书架相关样式 */ .po18-bookshelf-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .po18-bookshelf-header h3 { margin: 0; color: var(--primary-dark); } .po18-bookshelf-status { font-size: 14px; color: #666; margin-bottom: 15px; } .po18-book-item { border-bottom: 1px solid #eee; padding: 15px 0; } .po18-book-item:last-child { border-bottom: none; } .po18-book-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 10px; } .po18-button-small { padding: 5px 10px; font-size: 12px; } .po18-empty-message { text-align: center; padding: 30px 0; color: #666; } .po18-book-year { font-size: 12px; color: #888; margin-top: 5px; } `); // ==== 主要功能实现 ==== const Po18Downloader = { content: [], option: {}, logs: [], downloadRecords: GM_getValue('downloadRecords', []), currentTab: 'download', bid: null, downloadFormat: 'txt', threadCount: 3, isDownloading: false, totalChapters: 0, downloadedChapters: 0, startTime: 0, init() { this.createUI(); this.bindEvents(); this.loadSettings(); this.detectNovelPage(); // 检查登录状态 this.checkLoginStatus(); }, createUI() { // 创建悬浮按钮 const floatButton = document.createElement('div'); floatButton.className = 'po18-float-button'; floatButton.innerHTML = ''; document.body.appendChild(floatButton); // 创建主面板 const panel = document.createElement('div'); panel.className = 'po18-panel'; // 使用模板字符串确保HTML格式正确 panel.innerHTML = `
PO18小说下载器
下载
日志
记录
关于

我的书架

加载中...

PO18小说下载器增强版 v1.3.4

这是一款用于下载PO18网站小说的工具,支持TXT和HTML格式下载,多线程下载等功能。

作者github:wenmoux:

新增功能:

  1. 全新的粉色主题界面
  2. 显示小说封面、作者和标签
  3. 增强HTML输出,支持电子书式的左右翻页
  4. 阅读界面支持字体大小、颜色主题调整
  5. 新增行间距、字间距调整功能
  6. 优化正文排版和阅读舒适度
  7. 新增书架功能,便于管理已购买小说

使用方法:

  1. 在小说页面点击悬浮按钮
  2. 选择下载格式和线程数
  3. 点击"开始下载"按钮

注意:需要先登录PO18网站才能下载已购买的章节。

`; document.body.appendChild(panel); }, bindEvents() { // 点击悬浮按钮显示/隐藏面板 document.querySelector('.po18-float-button').addEventListener('click', () => { const panel = document.querySelector('.po18-panel'); panel.classList.toggle('active'); }); // 点击关闭按钮 document.getElementById('po18-close').addEventListener('click', () => { document.querySelector('.po18-panel').classList.remove('active'); }); // 标签页切换 document.querySelectorAll('.po18-tab').forEach(tab => { tab.addEventListener('click', (e) => { this.currentTab = e.target.dataset.tab; // 移除所有标签的active类 document.querySelectorAll('.po18-tab').forEach(t => { t.classList.remove('active'); }); // 移除所有面板的active类 document.querySelectorAll('.po18-tab-pane').forEach(p => { p.classList.remove('active'); }); // 添加当前标签和面板的active类 e.target.classList.add('active'); const pane = document.getElementById(`po18-tab-${this.currentTab}`); if (pane) { pane.classList.add('active'); } if (this.currentTab === 'records') { this.renderDownloadRecords(); } else if (this.currentTab === 'bookshelf') { this.renderBookshelf(); } }); }); // 下载按钮 document.getElementById('po18-start').addEventListener('click', () => { this.startDownload(); }); // 下载格式选择 document.getElementById('po18-format').addEventListener('change', (e) => { this.downloadFormat = e.target.value; GM_setValue('downloadFormat', this.downloadFormat); }); // 线程数选择 document.getElementById('po18-thread').addEventListener('change', (e) => { this.threadCount = parseInt(e.target.value); GM_setValue('threadCount', this.threadCount); }); // 书架刷新按钮事件 document.getElementById('po18-refresh-bookshelf')?.addEventListener('click', () => { this.log('正在刷新书架数据...'); this.fetchBookshelf().then(books => { this.getBookDetails(books).then(detailedBooks => { this.renderBookshelf(detailedBooks); }); }); }); // 实现悬浮按钮的拖动功能 this.makeDraggable(document.querySelector('.po18-float-button')); // 实现面板的拖动功能 this.makeDraggable(document.querySelector('.po18-panel'), document.querySelector('.po18-draggable')); }, makeDraggable(element, handle = null) { const dragElement = handle || element; let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; dragElement.addEventListener('mousedown', dragMouseDown); function dragMouseDown(e) { e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.addEventListener('mouseup', closeDragElement); document.addEventListener('mousemove', elementDrag); } function elementDrag(e) { e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; const newTop = element.offsetTop - pos2; const newLeft = element.offsetLeft - pos1; // 确保元素不会被拖出可视区域 if (newTop > 0 && newTop < window.innerHeight - element.offsetHeight) { element.style.top = newTop + "px"; } if (newLeft > 0 && newLeft < window.innerWidth - element.offsetWidth) { element.style.left = newLeft + "px"; } } function closeDragElement() { document.removeEventListener('mouseup', closeDragElement); document.removeEventListener('mousemove', elementDrag); } }, loadSettings() { this.downloadFormat = GM_getValue('downloadFormat', 'txt'); this.threadCount = GM_getValue('threadCount', 3); const formatSelect = document.getElementById('po18-format'); const threadSelect = document.getElementById('po18-thread'); if (formatSelect) formatSelect.value = this.downloadFormat; if (threadSelect) threadSelect.value = this.threadCount.toString(); }, detectNovelPage() { const url = window.location.href; const bidMatch = url.match(/\/books\/(\d+)/); if (bidMatch) { this.bid = bidMatch[1]; this.log(`检测到小说ID: ${this.bid}`); // 获取小说信息并显示 this.fetchBookDetails(this.bid); } else { this.log('未检测到小说页面'); } }, // 检查登录状态 checkLoginStatus() { // 检查页面中是否包含"登入"文字,如果没有则认为已登录 const pageContent = document.body.textContent || ''; const isLoggedIn = !pageContent.includes('登入'); // 显示或隐藏书架标签 const bookshelfTab = document.getElementById('po18-bookshelf-tab'); if (bookshelfTab) { bookshelfTab.style.display = isLoggedIn ? 'block' : 'none'; } return isLoggedIn; }, // 获取已购书架数据 async fetchBookshelf() { if (!this.checkLoginStatus()) { this.log('未登录,无法获取书架信息'); return []; } const allBooks = []; const currentYear = new Date().getFullYear(); // 获取最近5年的书籍 for (let year = currentYear; year >= currentYear - 5; year--) { try { const yearBooks = await this.fetchBookshelfByYear(year); if (yearBooks.length) { allBooks.push(...yearBooks); } } catch (error) { this.log(`获取${year}年书籍失败: ${error.message || '未知错误'}`); } } // 缓存书籍信息 GM_setValue('bookshelfData', { books: allBooks, timestamp: Date.now() }); return allBooks; }, async fetchBookshelfByYear(year) { return new Promise((resolve) => { const url = `https://www.po18.tw/panel/stock_manage/buyed_lists?sort=order&date_year=${year}`; GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'referer': 'https://www.po18.tw', }, onload: (response) => { try { const html = response.responseText; const $ = HTMLParser.parse(html); const books = []; $.querySelectorAll('tbody>.alt-row').forEach((book) => { const nameEl = book.querySelector('a'); if (!nameEl) return; const name = nameEl.textContent.trim(); const href = nameEl.getAttribute('href'); const authorEl = book.querySelector('.T_author'); // 从href中提取bid const bidMatch = href ? href.match(/\/books\/(\d+)/) : null; const bid = bidMatch ? bidMatch[1] : null; if (name && bid) { books.push({ title: name, bid: bid, author: authorEl ? authorEl.textContent.trim() : '未知作者', cover: null, // 稍后会通过详情获取 detail: `https://www.po18.tw${href}`, year: year }); } }); this.log(`获取到${year}年已购书籍 ${books.length} 本`); resolve(books); } catch (err) { this.log(`解析${year}年书籍列表失败: ${err.message || '未知错误'}`); resolve([]); } }, onerror: (error) => { this.log(`获取${year}年书籍列表请求失败: ${error.message || "未知错误"}`); resolve([]); } }); }); }, // 获取书籍详情并更新缓存 async getBookDetails(books) { const bookDetailsCache = GM_getValue('bookDetailsCache', {}); const now = Date.now(); const cacheExpiry = 7 * 24 * 60 * 60 * 1000; // 7天缓存过期 // 过滤出需要获取详情的书籍 const booksToFetch = books.filter(book => { const cachedBook = bookDetailsCache[book.bid]; return !cachedBook || (now - cachedBook.timestamp > cacheExpiry); }); if (booksToFetch.length === 0) { // 全部使用缓存 return books.map(book => { const cachedData = bookDetailsCache[book.bid]?.details; if (cachedData) { return { ...book, ...cachedData }; } return book; }); } // 分批获取详情,避免过多请求 const batchSize = 3; let processedCount = 0; for (let i = 0; i < booksToFetch.length; i += batchSize) { const batch = booksToFetch.slice(i, i + batchSize); await Promise.all(batch.map(async (book) => { try { const details = await this.getDetail(book.bid); if (details) { // 更新缓存 bookDetailsCache[book.bid] = { timestamp: now, details: { title: details.title, author: details.author, cover: details.cover, tags: details.tags } }; // 更新书籍数据 book.title = details.title; book.author = details.author; book.cover = details.cover; book.tags = details.tags; } processedCount++; this.log(`获取书籍详情 (${processedCount}/${booksToFetch.length}): ${book.title}`); // 更新界面 this.renderBookshelf(books); } catch (error) { this.log(`获取书籍 [${book.title}] 详情失败: ${error.message || '未知错误'}`); } })); // 短暂延迟,避免请求过快 if (i + batchSize < booksToFetch.length) { await new Promise(resolve => setTimeout(resolve, 1000)); } } // 保存缓存 GM_setValue('bookDetailsCache', bookDetailsCache); return books; }, // 渲染书架UI async renderBookshelf(books = null) { const container = document.getElementById('po18-bookshelf-container'); const statusEl = document.getElementById('po18-bookshelf-status'); if (!container) return; // 如果没有提供书籍列表,尝试从缓存加载 if (!books) { const cachedData = GM_getValue('bookshelfData', null); if (cachedData && Date.now() - cachedData.timestamp < 24 * 60 * 60 * 1000) { // 缓存不超过24小时 books = cachedData.books; this.log('从缓存加载书架数据'); } else { // 缓存过期或不存在,重新获取 if (statusEl) statusEl.textContent = '正在获取书架数据...'; books = await this.fetchBookshelf(); } // 获取书籍详情 books = await this.getBookDetails(books); } // 更新状态信息 if (statusEl) { statusEl.textContent = `共 ${books.length} 本已购书籍`; } // 渲染书架 let html = ''; if (books.length === 0) { html = '
没有找到已购书籍,请确认已登录PO18网站
'; } else { books.forEach((book) => { // 默认封面图 const coverUrl = book.cover || 'https://imgfzone.tooopen.com/20201106/tooopen_v11011311323157.jpg'; // 标签HTML let tagsHTML = ''; if (book.tags) { const tagsList = book.tags.split('·'); tagsList.forEach(tag => { if (tag.trim()) { tagsHTML += `${tag.trim()}`; } }); } html += `
${book.title}封面

${book.title}

作者: ${book.author}
${tagsHTML}
购买年份: ${book.year}
查看
`; }); } container.innerHTML = html; // 绑定下载按钮事件 document.querySelectorAll('.po18-download-book').forEach(button => { button.addEventListener('click', (e) => { const bid = e.target.dataset.bid; const title = e.target.dataset.title; if (bid) { this.bid = bid; this.log(`选择下载书籍: ${title} (${bid})`); // 切换到下载标签页document.querySelector('.po18-tab[data-tab="download"]').click(); // 获取书籍详情 this.fetchBookDetails(bid); } }); }); }, // 获取并显示小说详情 async fetchBookDetails(bid) { try { const detail = await this.getDetail(bid); if (detail) { this.renderBookDetails(detail); } } catch (err) { this.log(`获取小说详情失败: ${err.message || '未知错误'}`); } }, // 渲染小说详情 renderBookDetails(detail) { const container = document.getElementById('po18-book-details-container'); if (!container) return; // 标签HTML let tagsHTML = ''; if (detail.tags) { const tagsList = detail.tags.split('·'); tagsList.forEach(tag => { if (tag.trim()) { tagsHTML += `${tag.trim()}`; } }); } // 构造小说详情HTML const html = `
${detail.title}封面

${detail.title}

作者: ${detail.author}
${tagsHTML}
`; container.innerHTML = html; }, log(message) { const timestamp = new Date().toLocaleTimeString(); const logMessage = `[${timestamp}] ${message}`; this.logs.unshift(logMessage); // 限制日志数量 if (this.logs.length > 100) { this.logs.pop(); } // 更新日志显示 const logElement = document.getElementById('po18-logs'); if (logElement) { logElement.innerText = this.logs.join('\n'); } console.log(`[PO18下载器] ${message}`); }, updateProgress(current, total) { this.downloadedChapters = current; this.totalChapters = total; const percent = total > 0 ? Math.floor((current / total) * 100) : 0; const progressBar = document.getElementById('po18-progress'); const progressText = document.getElementById('po18-progress-text'); const downloadTime = document.getElementById('po18-download-time'); if (progressBar) progressBar.style.width = `${percent}%`; if (progressText) progressText.innerText = `${current}/${total} 章节 (${percent}%)`; const elapsedTime = Math.floor((Date.now() - this.startTime) / 1000); if (downloadTime) downloadTime.innerText = `已用时间: ${elapsedTime}秒`; }, async startDownload() { if (this.isDownloading) { this.log('下载任务正在进行中,请等待完成'); return; } if (!this.bid) { this.log('未检测到小说ID,请在小说页面使用此功能'); return; } this.isDownloading = true; this.content = []; this.option = {}; this.downloadedChapters = 0; this.totalChapters = 0; this.startTime = Date.now(); const downloadStatus = document.getElementById('po18-download-status'); if (downloadStatus) downloadStatus.style.display = 'block'; const startBtn = document.getElementById('po18-start'); if (startBtn) { startBtn.disabled = true; startBtn.textContent = '下载中...'; } this.log(`开始下载小说 (BID: ${this.bid}, 格式: ${this.downloadFormat}, 线程数: ${this.threadCount})`); try { await this.downloadNovel(); } catch (err) { this.log(`下载失败: ${err.message || '未知错误'}`); } finally { this.isDownloading = false; if (startBtn) { startBtn.disabled = false; startBtn.textContent = '开始下载'; } } }, async downloadNovel() { // 获取小说详情 this.log('正在获取小说详情...'); const detail = await this.getDetail(this.bid); if (!detail) { this.log('获取小说详情失败'); return; } this.option = Object.assign({}, detail); this.log(`小说信息: ${detail.title} - ${detail.author} (共${detail.pageNum}页)`); // 获取章节列表 this.log('正在获取章节列表...'); const chapters = await this.getChapterList(detail); if (!chapters || chapters.length === 0) { this.log('获取章节列表失败或没有可下载的章节'); return; } this.totalChapters = chapters.length; this.log(`共找到 ${chapters.length} 个可下载章节`); // 下载所有章节内容 this.log('开始下载章节内容...'); const startTime = Date.now(); // 根据线程数分配任务 const batchSize = this.threadCount; for (let i = 0; i < chapters.length; i += batchSize) { const batch = chapters.slice(i, i + batchSize); await Promise.all(batch.map(chapter => this.getChapterContent(chapter))); // 更新进度 this.updateProgress(Math.min(i + batchSize, chapters.length), chapters.length); } const endTime = Date.now(); const duration = (endTime - startTime) / 1000; this.log(`章节内容下载完成,耗时 ${duration.toFixed(2)} 秒`); // 按顺序排序内容 this.content.sort((a, b) => a.index - b.index); // 生成完整内容 this.log('正在生成最终文件...'); // 整理内容格式 const fileContent = this.formatContent(); // 下载文件 const fileName = `${detail.title}.${this.downloadFormat}`; const fileSize = this.getByteSize(fileContent); const fileSizeText = this.formatFileSize(fileSize); // 使用FileSaver.js保存文件 try { const blob = new Blob([fileContent], { type: this.downloadFormat === 'txt' ? 'text/plain;charset=utf-8' : 'text/html;charset=utf-8' }); window.saveAs(blob, fileName); // 记录下载信息 const record = { title: detail.title, author: detail.author, format: this.downloadFormat, size: fileSizeText, time: new Date().toLocaleString(), duration: duration.toFixed(2), chapterCount: chapters.length, cover: detail.cover, tags: detail.tags }; this.downloadRecords.unshift(record); if (this.downloadRecords.length > 50) { this.downloadRecords.pop(); } GM_setValue('downloadRecords', this.downloadRecords); this.log(`下载完成! 文件名: ${fileName}, 大小: ${fileSizeText}, 耗时: ${duration.toFixed(2)}秒`); } catch (e) { this.log(`保存文件失败: ${e.message || '未知错误'}`); } }, async getDetail(bid) { return new Promise((resolve) => { this.log('正在获取小说详情...'); GM_xmlhttpRequest({ method: 'GET', url: `https://www.po18.tw/books/${bid}`, headers: { 'referer': 'https://www.po18.tw', }, onload: (response) => { try { const html = response.responseText; const $ = HTMLParser.parse(html); // 使用自定义的HTML解析替代cheerio let zhText = $.getTextContent("dd.statu"); let zh = zhText.match(/\d+/); // 获取标签 const tags = []; $.querySelectorAll(".book_intro_tags>a").forEach(tag => { tags.push(tag.textContent.trim()); }); // 处理描述 let descContent = $.getTextContent(".B_I_content"); let paragraphs = descContent.split(/\s{2,}/); let desc = paragraphs.map(para => `

${para.trim()}

`).join("\n"); // 构建详情对象 const bookTitle = $.getTextContent("h1.book_name"); const title = bookTitle.split(/(|【|\(/)[0].trim(); const detail = { title: title, author: $.getTextContent("a.book_author"), cover: $.getAttributeValue(".book_cover>img", "src"), description: desc, content: [], tags: tags.join("·"), bid, pub: "po18脸红心跳", pageNum: Math.ceil(zh / 100) || 1 // 确保至少有一页 }; this.log(`获取到小说: ${detail.title} - ${detail.author}`); resolve(detail); } catch (err) { this.log(`解析小说详情失败: ${err.message || '未知错误'}`); resolve(null); } }, onerror: (error) => { this.log(`获取小说详情请求失败: ${error.message || "未知错误"}`); resolve(null); } }); }); }, async getChapterList(detail) { const chapters = []; for (let page = 1; page <= detail.pageNum; page++) { this.log(`正在获取第${page}/${detail.pageNum} 页章节列表...`); const url = `https://www.po18.tw/books/${detail.bid}/articles?page=${page}`; const pageChapters = await this.getPageChapters(url); if (pageChapters && pageChapters.length > 0) { chapters.push(...pageChapters); } } return chapters; }, async getPageChapters(url) { return new Promise((resolve) => { GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'referer': 'https://www.po18.tw', }, onload: (response) => { try { const html = response.responseText; const $ = HTMLParser.parse(html); const chapterItems = []; $.querySelectorAll("#w0>div").forEach((element) => { const chaptNameEl = element.querySelector(".l_chaptname"); if (!chaptNameEl) return; const name = chaptNameEl.textContent.trim(); const isPurchased = !element.textContent.includes('訂購'); if (isPurchased) {const btnLink = element.querySelector(".l_btn>a"); if (!btnLink) return; const href = btnLink.getAttribute("href"); if (!href) return; const id = href.split("/"); if (id.length < 5) return; chapterItems.push({ title: name, bid: id[2], pid: id[4], index: chapterItems.length }); } else { this.log(`章节 "${name}" 需要购买,已跳过`); } }); resolve(chapterItems); } catch (err) { this.log(`解析章节列表失败: ${err.message || '未知错误'}`); resolve([]); } }, onerror: (error) => { this.log(`获取章节列表请求失败: ${error.message || "未知错误"}`); resolve([]); } }); }); }, async getChapterContent(chapter) { return new Promise((resolve) => { const { bid, pid, index, title } = chapter; GM_xmlhttpRequest({ method: 'GET', url: `https://www.po18.tw/books/${bid}/articlescontent/${pid}`, headers: { 'referer': `https://www.po18.tw/books/${bid}/articles/${pid}`, 'x-requested-with': 'XMLHttpRequest' }, onload: (response) => { try { let content = response.responseText.replace(/   /g, ""); const $ = HTMLParser.parse(content); // 移除引用块 $.remove("blockquote"); // 获取标题和内容 let name = $.getTextContent("h1"); // 将章节内容存储到数组 this.content[index] = { title: name || title, data: $.getHTML().replace(/ /g, ""), rawText: $.getText(), index: index }; this.log(`已下载章节: ${name || title}`); this.downloadedChapters++; this.updateProgress(this.downloadedChapters, this.totalChapters); resolve(); } catch (err) { this.log(`下载章节 "${title}" 失败: ${err.message || '未知错误'}`); resolve(); } }, onerror: (error) => { this.log(`下载章节 "${title}" 请求失败: ${error.message || "未知错误"}`); resolve(); } }); }); }, // 增强的内容格式化方法 formatContent() { if (this.downloadFormat === 'txt') { // TXT格式增强,加入简介和标签 let content = `${this.option.title}\n作者: ${this.option.author}\n\n`; // 加入标签 if (this.option.tags) { content += `标签: ${this.option.tags}\n\n`; } // 加入简介 if (this.option.description) { const description = this.option.description.replace(/<[^>]+>/g, ''); // 移除HTML标签 content += `【简介】\n${description}\n\n`; } // 加入正文内容 content += `【正文】\n`; this.content.forEach(chapter => { if (chapter) { // 确保章节存在 content += `\n\n${chapter.title}\n\n`;content += chapter.rawText.replace(/\s+/g, '\n\n'); } }); return content; } else { // HTML格式 - 增强为阅读器风格 // 创建一个精美的HTML电子书阅读界面 let content = ` ${this.option.title} - ${this.option.author}
${this.option.title}封面

${this.option.title}

作者:${this.option.author}

${this.option.tags ? this.option.tags.split('·').map(tag => `${tag.trim()}`).join('') : ''}
${this.option.description}
`; // 添加章节页面 this.content.forEach((chapter, index) => { if (chapter) { content += `

${chapter.title}

${chapter.data}
`; } }); // 添加导航按钮和侧边栏 content += `

阅读设置

背景颜色

字体大小

特大

行间距

紧凑
适中
宽松
超宽

字间距

正常
略宽
宽松
超宽

字体选择

黑体
宋体
楷体
行书
`; return content; } }, getByteSize(string) { return new Blob([string]).size; }, formatFileSize(bytes) { if (bytes < 1024) { return bytes + ' B'; } else if (bytes < 1024 * 1024) { return (bytes / 1024).toFixed(2) + ' KB'; } else { return (bytes / (1024 * 1024)).toFixed(2) + ' MB'; } }, renderDownloadRecords() { const container = document.getElementById('po18-records-container'); if (!container) { return; } if (this.downloadRecords.length === 0) { container.innerHTML = '
暂无下载记录
'; return; } let html = ''; this.downloadRecords.forEach((record) => { // 添加封面显示 const coverHtml = record.cover ? `${record.title}封面` : ''; // 添加标签显示 let tagsHtml = ''; if (record.tags) { const tagsList = record.tags.split('·'); tagsHtml = '
'; tagsList.forEach(tag => { if (tag.trim()) { tagsHtml += `${tag.trim()} `; } }); tagsHtml += '
'; } html += `
${coverHtml}

${record.title || "未知标题"}

作者: ${record.author || "未知作者"} 格式: ${record.format ? record.format.toUpperCase() : "未知格式"}
大小: ${record.size || "未知大小"} 章节数: ${record.chapterCount || "未知"}
时间: ${record.time || "未知时间"} 耗时: ${record.duration || "0"}秒
${tagsHtml}
`; }); container.innerHTML = html; } }; // 初始化下载器 Po18Downloader.init(); })();