// ==UserScript== // @name 番茄小说批量下载器 // @namespace http://tampermonkey.net/ // @version 2.0 // @description 在番茄小说搜索页面添加批量下载功能,支持高并发下载 // @author You // @match https://fanqienovel.com/search* // @match https://fanqienovel.com/page/* // @match https://fanqienovel.com/reader/* // @grant GM_xmlhttpRequest // @grant GM_download // @grant GM_setValue // @grant GM_getValue // @connect tt.sjmyzq.cn // @connect fanqienovel.com // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 样式定义 const styles = ` .fanqie-downloader-panel { position: fixed; top: 100px; right: 20px; width: 360px; background: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); z-index: 9999; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; overflow: hidden; } .fanqie-downloader-header { background: linear-gradient(135deg, #ff6b6b, #ff8e8e); color: white; padding: 15px 20px; font-size: 16px; font-weight: 600; display: flex; justify-content: space-between; align-items: center; } .fanqie-downloader-header button { background: rgba(255,255,255,0.2); border: none; color: white; width: 28px; height: 28px; border-radius: 50%; cursor: pointer; font-size: 14px; } .fanqie-downloader-body { padding: 15px 20px; max-height: 500px; overflow-y: auto; } .fanqie-downloader-body.hidden { display: none; } .fanqie-input-group { margin-bottom: 12px; } .fanqie-input-group label { display: block; font-size: 13px; color: #666; margin-bottom: 5px; } .fanqie-input-group input, .fanqie-input-group select { width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; } .fanqie-input-row { display: flex; gap: 10px; } .fanqie-input-row .fanqie-input-group { flex: 1; } .fanqie-btn { width: 100%; padding: 10px; border: none; border-radius: 6px; font-size: 14px; cursor: pointer; margin-bottom: 8px; transition: all 0.3s; } .fanqie-btn-primary { background: #ff6b6b; color: white; } .fanqie-btn-primary:hover { background: #ff5252; } .fanqie-btn-primary:disabled { background: #ccc; cursor: not-allowed; } .fanqie-btn-secondary { background: #f0f0f0; color: #333; } .fanqie-btn-secondary:hover { background: #e0e0e0; } .fanqie-progress { margin-top: 15px; padding: 10px; background: #f8f8f8; border-radius: 6px; } .fanqie-progress-bar { height: 6px; background: #e0e0e0; border-radius: 3px; overflow: hidden; margin-bottom: 8px; } .fanqie-progress-fill { height: 100%; background: linear-gradient(90deg, #ff6b6b, #ff8e8e); transition: width 0.3s; width: 0%; } .fanqie-progress-text { font-size: 12px; color: #666; display: flex; justify-content: space-between; } .fanqie-log { margin-top: 10px; max-height: 150px; overflow-y: auto; background: #1e1e1e; color: #0f0; padding: 10px; border-radius: 6px; font-size: 12px; font-family: monospace; } .fanqie-log-entry { margin-bottom: 3px; } .fanqie-log-error { color: #ff6b6b; } .fanqie-log-success { color: #69f0ae; } .fanqie-log-info { color: #64b5f6; } .fanqie-book-list { margin-top: 10px; max-height: 200px; overflow-y: auto; } .fanqie-book-item { display: flex; align-items: center; padding: 8px; border-bottom: 1px solid #eee; cursor: pointer; } .fanqie-book-item:hover { background: #f5f5f5; } .fanqie-book-item input { margin-right: 8px; } .fanqie-book-item img { width: 40px; height: 56px; object-fit: cover; border-radius: 4px; margin-right: 10px; } .fanqie-book-info { flex: 1; } .fanqie-book-title { font-size: 13px; font-weight: 500; color: #333; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .fanqie-book-author { font-size: 11px; color: #999; } .fanqie-download-btn { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; background: #ff6b6b; color: white; border: none; border-radius: 4px; font-size: 12px; cursor: pointer; margin-left: 10px; } .fanqie-download-btn:hover { background: #ff5252; } .fanqie-current-book { background: #fff3f3; border: 1px solid #ff6b6b; border-radius: 6px; padding: 10px; margin-bottom: 12px; } .fanqie-current-book-title { font-weight: 600; color: #ff6b6b; margin-bottom: 5px; } .fanqie-current-book-id { font-size: 12px; color: #666; } .fanqie-stats { display: flex; justify-content: space-between; font-size: 12px; color: #666; margin-top: 5px; } `; // 添加样式 function addStyles() { const styleEl = document.createElement('style'); styleEl.textContent = styles; document.head.appendChild(styleEl); } // 创建面板 function createPanel() { const panel = document.createElement('div'); panel.className = 'fanqie-downloader-panel'; panel.innerHTML = `
]*>.*?<\/p>/g) ?.map(p => p.replace(/<[^>]*>/g, '').trim()) .join('\n') || data.data.content; resolve({ success: true, content }); } else { resolve({ success: false, content: null }); } } catch (e) { resolve({ success: false, content: null }); } }, onerror: function() { resolve({ success: false, content: null }); } }); }); } // 开始下载 - 支持高并发 async function startDownload(bookId, bookName, volumes, totalChapters) { const startIndex = parseInt(document.getElementById('fanqieStart').value) - 1; let endIndex = parseInt(document.getElementById('fanqieEnd').value) - 1; if (isNaN(endIndex) || endIndex < 0) endIndex = totalChapters - 1; const concurrency = parseInt(document.getElementById('fanqieConcurrency').value) || 10; const delayTime = parseInt(document.getElementById('fanqieDelay').value) || 0; // 收集需要下载的章节 const chaptersToDownload = []; for (const volume of volumes) { for (const ch of volume.chapters) { if (ch.globalIndex >= startIndex && ch.globalIndex <= endIndex) { chaptersToDownload.push({ ...ch, volumeIndex: volume.volumeIndex, volumeTitle: volume.volumeTitle }); } } } const total = chaptersToDownload.length; const results = []; let successCount = 0; let failCount = 0; const maxFail = 10; let consecutiveFails = 0; const downloadStartTime = Date.now(); log(`📥 开始下载《${bookName}》: 共${total}章, 并发${concurrency}, 延迟${delayTime}ms`); // 并发下载函数 async function downloadBatch(batch) { const promises = batch.map(async (ch) => { const result = await fetchChapterContent(ch.itemId); return { ch, result }; }); return await Promise.all(promises); } // 分批次下载 for (let i = 0; i < chaptersToDownload.length; i += concurrency) { const batch = chaptersToDownload.slice(i, i + concurrency); const batchResults = await downloadBatch(batch); for (const { ch, result } of batchResults) { if (result.success) { results.push({ volumeIndex: ch.volumeIndex, volumeTitle: ch.volumeTitle, chapterOrder: ch.realChapterOrder, title: ch.title, content: result.content, globalIndex: ch.globalIndex }); successCount++; consecutiveFails = 0; log(`✅ [卷${ch.volumeIndex}] ${ch.title}`, 'success'); } else { failCount++; consecutiveFails++; log(`❌ [卷${ch.volumeIndex}] ${ch.title} 获取失败`, 'error'); if (consecutiveFails >= maxFail) { log(`⚠️ 连续${maxFail}章失败,停止下载`, 'error'); break; } } } // 更新进度 const progress = Math.round(((i + batch.length) / total) * 100); const elapsed = (Date.now() - downloadStartTime) / 1000; const speed = (successCount + failCount) / elapsed; updateProgress(progress, `下载中...`, successCount, failCount, speed); if (consecutiveFails >= maxFail) break; // 延迟 if (delayTime > 0 && i + concurrency < chaptersToDownload.length) { await new Promise(resolve => setTimeout(resolve, delayTime)); } } updateProgress(100, '下载完成', successCount, failCount, 0); log(`🎉 下载完成! 成功 ${successCount}/${total} 章`, 'success'); if (results.length > 0) { // 按卷和章节顺序排序 results.sort((a, b) => { if (a.volumeIndex !== b.volumeIndex) { return a.volumeIndex - b.volumeIndex; } return a.chapterOrder - b.chapterOrder; }); downloadTxt(results, bookName, bookId); } document.getElementById('fanqieFetchBtn').disabled = false; document.getElementById('fanqieFetchBtn').textContent = '获取章节列表'; } // 下载TXT文件 - 简洁格式,不过度修改 function downloadTxt(results, bookName, bookId) { const lines = []; // 简单的文件标题 lines.push(`《${bookName}》`); lines.push(''); let currentVolume = 0; for (const item of results) { // 如果有卷名且换了新卷,添加卷标题 if (item.volumeTitle && item.volumeIndex !== currentVolume) { currentVolume = item.volumeIndex; lines.push(''); lines.push(`=== ${item.volumeTitle} ===`); lines.push(''); } // 使用原始章节标题,不做修改 lines.push(''); lines.push(item.originalTitle || item.title); lines.push(''); // 章节内容 lines.push(item.content); lines.push(''); } const content = lines.join('\n'); const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); // 清理书名中的非法字符 const safeBookName = bookName.replace(/[\\/:*?"<>|]/g, '_'); const a = document.createElement('a'); a.href = url; a.download = `${safeBookName}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); log('💾 文件已下载: ' + `${safeBookName}.txt`, 'success'); } // 初始化 function init() { addStyles(); createPanel(); log('番茄小说下载器 v2.0 已加载', 'success'); log('支持并发下载(最大50)、自动检测页面小说', 'info'); } // 等待页面加载完成 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();