// ==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网站小说的工具,支持TXT和HTML格式下载,多线程下载等功能。
作者github:wenmoux:
新增功能:
使用方法:
注意:需要先登录PO18网站才能下载已购买的章节。
${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 = `