您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Scribd Enhancer with OCR, TXT/HTML export, Snapshot PDF (pixel-perfect), Rich HTML (images inlined), page-range + quality controls. Draggable/collapsible panel + floating gear with position memory. Rich HTML now de-duplicates layered text/image to avoid doubled content. By Eliminater74.
// ==UserScript== // @name Scribd Enhancer All-in-One (v3.0.1) // @namespace https://gf.qytechs.cn/users/Eliminater74 // @version 3.0.1 // @description Scribd Enhancer with OCR, TXT/HTML export, Snapshot PDF (pixel-perfect), Rich HTML (images inlined), page-range + quality controls. Draggable/collapsible panel + floating gear with position memory. Rich HTML now de-duplicates layered text/image to avoid doubled content. By Eliminater74. // @author Eliminater74 // @license MIT // @match *://*.scribd.com/* // @grant none // @icon https://s-f.scribdassets.com/favicon.ico // ==/UserScript== (function () { 'use strict'; // ---------- KEYS ---------- const SETTINGS_KEY = 'scribdEnhancerSettings'; const UI_MENU_KEY = 'scribdEnhancer_ui_menu'; const UI_GEAR_KEY = 'scribdEnhancer_ui_gear'; // ---------- SETTINGS ---------- const defaultSettings = { unblur: true, autoScrape: false, darkMode: false, showPreview: true, enableOCR: true, ocrLang: 'auto', splitEvery: 0, // Snapshot controls pageRange: 'all', // 'all' | '1-25' | '5,7,10-12' snapshotScale: 2, // 1..4 snapshotQuality: 0.92, // 0.8 | 0.92 | 1.0 // NEW: Rich HTML layer preference: 'auto' | 'preferText' | 'preferImage' richPref: 'auto' }; const settings = { ...defaultSettings, ...JSON.parse(localStorage.getItem(SETTINGS_KEY) || '{}') }; const saveSettings = () => localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); // ---------- LIBS ---------- const loadScript = (src) => { const s = document.createElement('script'); s.src = src; document.head.appendChild(s); return s; }; loadScript('https://cdn.jsdelivr.net/npm/[email protected]/dist/tesseract.min.js'); loadScript('https://cdn.jsdelivr.net/npm/[email protected]/dist/html2canvas.min.js'); loadScript('https://cdn.jsdelivr.net/npm/[email protected]/dist/jspdf.umd.min.js'); // ---------- STYLES ---------- const style = document.createElement('style'); style.textContent = ` #se-gear { position: fixed; width: 40px; height: 40px; line-height: 40px; text-align: center; background:#2b2b2b; color:#fff; border-radius: 50%; cursor: pointer; box-shadow: 0 2px 10px rgba(0,0,0,.45); z-index: 2147483647; user-select:none; font-size: 20px; } #se-panel { position: fixed; background:#1e1f22; color:#f1f1f1; width: 320px; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,.6); z-index: 2147483646; font-family: system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif; display: none; } #se-header { display:flex; align-items:center; justify-content:space-between; padding:8px 10px; cursor:move; background:#2a2b2f; border-top-left-radius:12px; border-top-right-radius:12px; font-weight:600; } #se-header .controls { display:flex; gap:6px; } #se-header .btn { width:24px; height:24px; line-height:24px; text-align:center; border-radius:6px; background:#3a3b41; cursor:pointer; user-select:none; } #se-body { padding:8px 10px 10px; max-height: 70vh; overflow:auto; } #se-body label { display:flex; align-items:center; gap:6px; font-size:13px; margin:4px 0; } #se-body .row { display:flex; gap:8px; } #se-body .row > * { flex:1; } #se-body input[type="text"], #se-body select { width:100%; padding:6px; border-radius:6px; border:1px solid #444; background:#121316; color:#eee; font-size:13px; } #se-body button { width:100%; padding:8px; margin-top:6px; border:none; border-radius:8px; background:#3b3d45; color:#fff; cursor:pointer; font-size:13px; } #se-body button:hover { filter:brightness(1.08); } #se-preview { position: fixed; right: 20px; bottom: 80px; width: 380px; top: 12px; background:#111; color:#eee; border:1px solid #444; border-radius:10px; padding:10px; font-family: ui-monospace,Menlo,Consolas,monospace; font-size:12px; white-space:pre-wrap; overflow:auto; z-index: 2147483645; } .se-dark #se-preview { background:#222; color:#eee; border-color:#555; } `; document.head.appendChild(style); // ---------- HELPERS ---------- const sleep = (ms) => new Promise(r => setTimeout(r, ms)); const clamp = (v, min, max) => Math.max(min, Math.min(max, v)); const safe = (s) => (s || '').toString(); function applyDarkMode() { document.documentElement.classList.toggle('se-dark', settings.darkMode); document.body.classList.toggle('se-dark', settings.darkMode); } function unblurContent() { if (!settings.unblur) return; const cleanup = () => { document.querySelectorAll('.blurred_page, .promo_div, [unselectable="on"]').forEach(el => el.remove()); document.querySelectorAll('*').forEach(el => { const cs = getComputedStyle(el); if (cs.color === 'transparent') el.style.color = '#111'; if (cs.textShadow && cs.textShadow.includes('white')) el.style.textShadow = 'none'; }); }; cleanup(); new MutationObserver(cleanup).observe(document.body, { childList: true, subtree: true }); } function cleanOCRText(text) { return text.split('\n').map(t => t.trim()) .filter(line => line.length >= 3 && /[a-zA-Z]/.test(line) && !/^[^a-zA-Z0-9]{3,}$/.test(line)) .join('\n'); } function detectLanguage(text) { const map = { spa:/[ñáéíóúü]/i, fra:/[éèêëàâôûùç]/i, deu:/[äöüß]/i, ron:/[șțăîâ]/i }; for (const [k,re] of Object.entries(map)) if (re.test(text)) return k; return 'eng'; } async function preprocessImage(src) { return new Promise(resolve => { const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = () => { if (img.naturalWidth < 100 || img.naturalHeight < 100 || /logo|icon|watermark/i.test(src)) return resolve(null); const c = document.createElement('canvas'); c.width = img.width; c.height = img.height; const ctx = c.getContext('2d'); ctx.drawImage(img, 0, 0); const d = ctx.getImageData(0,0,c.width,c.height); for (let i=0; i<d.data.length; i+=4) { const avg = (d.data[i]+d.data[i+1]+d.data[i+2])/3; d.data[i]=d.data[i+1]=d.data[i+2]=avg; } ctx.putImageData(d,0,0); resolve(c.toDataURL('image/png')); }; img.onerror = () => resolve(null); img.src = src; }); } function getScribdPages() { return [...document.querySelectorAll( '.page, .reader_column, [id^="page_container"], .outer_page, .abs_page, .scribd_page, .text_layer' )]; } function parsePageRange(rangeText, totalPages) { const txt = safe(rangeText).trim().toLowerCase(); if (!txt || txt === 'all') return Array.from({length: totalPages}, (_,i)=>i); const set = new Set(); for (const part of txt.split(/[,;]\s*/)) { const m = part.match(/^(\d+)\s*-\s*(\d+)$/); if (m) { let a = clamp(+m[1],1,totalPages), b = clamp(+m[2],1,totalPages); if (a>b) [a,b]=[b,a]; for (let p=a; p<=b; p++) set.add(p-1); } else { const n = clamp(parseInt(part,10),1,totalPages); if (!isNaN(n)) set.add(n-1); } } return [...set].sort((x,y)=>x-y); } // ---------- EXPORTS ---------- function exportOutput(content, ext) { const split = settings.splitEvery | 0; const parts = content.split(/(?=\[Page \d+])/); if (!split || split < 1) { const blob = new Blob([content], { type: ext==='html' ? 'text/html' : 'text/plain' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `scribd_output.${ext}`; a.click(); return; } for (let i=0; i<parts.length; i+=split) { const chunk = parts.slice(i,i+split).join('\n'); const blob = new Blob([chunk], { type: ext==='html' ? 'text/html' : 'text/plain' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `scribd_part${Math.floor(i/split)+1}.${ext}`; a.click(); } } function printToPDF(content) { const win = window.open('', 'PrintView'); win.document.write(`<html><head><title>Scribd Print</title></head><body><pre>${content}</pre></body></html>`); win.document.close(); win.focus(); setTimeout(() => win.print(), 600); } async function exportSnapshotPDF(allPages) { await new Promise(r => { const chk = () => (window.html2canvas && window.jspdf) ? r() : setTimeout(chk,100); chk(); }); const pages = getPagesInRange(allPages); if (!pages.length) return alert('No pages selected.'); const scale = clamp(+settings.snapshotScale || 2, 1, 4); const quality = +settings.snapshotQuality || 0.92; const { jsPDF } = window.jspdf; const pdf = new jsPDF({ unit:'pt', format:'a4', compress:true }); const pageW = pdf.internal.pageSize.getWidth(); const pageH = pdf.internal.pageSize.getHeight(); for (let i=0; i<pages.length; i++) { const node = pages[i]; node.scrollIntoView({block:'center'}); await sleep(220); const canvas = await window.html2canvas(node, { useCORS:true, allowTaint:true, backgroundColor:'#ffffff', scale }); const imgData = canvas.toDataURL('image/jpeg', quality); const imgW = pageW, imgH = (canvas.height/canvas.width) * imgW; if (i>0) pdf.addPage(); const finalH = imgH > pageH ? pageH : imgH; const finalW = imgH > pageH ? (pageH/imgH)*imgW : imgW; pdf.addImage(imgData, 'JPEG', 0, 0, finalW, finalH); if (i % 10 === 0) await sleep(40); } pdf.save('scribd_snapshot.pdf'); } function getPagesInRange(allPages) { const idxs = parsePageRange(settings.pageRange, allPages.length); return idxs.map(i => allPages[i]).filter(Boolean); } // --- Rich HTML (DOM clone + images inlined) with layer de-dup --- async function exportRichHTML(allPages) { const pages = getPagesInRange(allPages); if (!pages.length) return alert('No pages selected.'); const sections = []; for (let i=0; i<pages.length; i++) { const clone = pages[i].cloneNode(true); // Remove hidden bits that can become visible offline clone.querySelectorAll('[aria-hidden="true"], [style*="opacity:0"], [style*="opacity: 0"], [style*="visibility:hidden"]').forEach(n => n.remove()); // Decide which layer to keep const hasTextLayer = !!clone.querySelector('.text_layer, [class*="textLayer"]'); const preferText = settings.richPref === 'preferText' || (settings.richPref === 'auto' && hasTextLayer); if (preferText) { // Keep selectable text: drop canvases and likely page-wide images clone.querySelectorAll('canvas').forEach(n => n.remove()); clone.querySelectorAll('img').forEach(img => { const cls = img.className || ''; const w = (img.getAttribute('width') || '') + (img.style?.width || ''); const h = (img.getAttribute('height') || '') + (img.style?.height || ''); if (/page|render|canvas|background/i.test(cls) || /100%/.test(w+h)) img.remove(); }); } else { // Keep raster layer: drop absolutely-positioned text clone.querySelectorAll('.text_layer, [class*="textLayer"]').forEach(n => n.remove()); } // Inline images (best effort) const imgs = [...clone.querySelectorAll('img')]; await Promise.all(imgs.map(async (img) => { try { const src = img.getAttribute('src') || img.src; if (!src) return; img.setAttribute('src', await imageToDataURL(src)); } catch { /* keep original src */ } })); // Strip scripts/styles inside clone clone.querySelectorAll('script, link[rel="stylesheet"]').forEach(n => n.remove()); sections.push(`<section style="page-break-after:always">${clone.outerHTML}</section>`); if (i % 20 === 0) await sleep(15); } const html = `<!doctype html> <html> <head> <meta charset="utf-8"> <title>Scribd Rich Export</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> /* keep it readable offline */ *{transform:none !important} body{margin:16px;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;} section{margin:0 auto; max-width:900px;} img{max-width:100%; height:auto;} </style> </head> <body> ${sections.join('\n')} </body> </html>`; const blob = new Blob([html], { type: 'text/html' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'scribd_rich.html'; a.click(); } function imageToDataURL(src) { return new Promise(resolve => { const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = () => { try { const c = document.createElement('canvas'); c.width = img.naturalWidth; c.height = img.naturalHeight; const ctx = c.getContext('2d'); ctx.drawImage(img,0,0); resolve(c.toDataURL('image/png')); } catch { resolve(src); } }; img.onerror = () => resolve(src); const bust = src.includes('?') ? '&' : '?'; img.src = src + bust + 'x=' + Date.now(); }); } // ---------- SCRAPER ---------- async function scrapePages(pages, preview) { const concurrency = 4; let index = 0; const firstText = []; async function scrape(page, i) { page.scrollIntoView(); await sleep(300); let found = false; const text = page.innerText.trim(); if (text) { preview.textContent += `[Page ${i+1}] ✅\n${text}\n\n`; firstText.push(text); found = true; } if (settings.enableOCR && window.Tesseract) { const imgs = page.querySelectorAll('img'); for (let img of imgs) { const src = img.src || ''; const processed = await preprocessImage(src); if (!processed) continue; const lang = settings.ocrLang === 'auto' ? detectLanguage(firstText.join(' ')) : settings.ocrLang; try { const res = await window.Tesseract.recognize(processed, lang); const ocrText = cleanOCRText(res.data.text || ''); if (ocrText) { preview.textContent += `[OCR] ${ocrText}\n\n`; found = true; } } catch {} } } if (!found) preview.textContent += `[Page ${i+1}] ❌ No content\n\n`; } const workers = Array(concurrency).fill().map(async ()=>{ while (index < pages.length) { const i = index++; await scrape(pages[i], i); }}); await Promise.all(workers); alert(`✅ Scraped ${pages.length} pages.`); } // ---------- DRAGGABLE + UI ---------- function makeDraggable(el, storageKey, fallbackPos) { el.style.position = 'fixed'; el.style.touchAction = 'none'; try { const saved = JSON.parse(localStorage.getItem(storageKey) || 'null'); if (saved && Number.isFinite(saved.x) && Number.isFinite(saved.y)) { el.style.left = saved.x + 'px'; el.style.top = saved.y + 'px'; } else if (fallbackPos) { const {x,y} = fallbackPos(); el.style.left = x + 'px'; el.style.top = y + 'px'; } } catch {} let startX, startY, startL, startT, moved=false; const onDown = (e) => { moved = false; const p = e.touches ? e.touches[0] : e; startX=p.clientX; startY=p.clientY; const r = el.getBoundingClientRect(); startL=r.left; startT=r.top; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); document.addEventListener('touchmove', onMove, {passive:false}); document.addEventListener('touchend', onUp); }; const onMove = (e) => { const p = e.touches ? e.touches[0] : e; if (e.cancelable) e.preventDefault(); moved = true; const nx = clamp(startL + (p.clientX-startX), 0, window.innerWidth - el.offsetWidth); const ny = clamp(startT + (p.clientY-startY), 0, window.innerHeight - el.offsetHeight); el.style.left = nx + 'px'; el.style.top = ny + 'px'; }; const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); document.removeEventListener('touchmove', onMove); document.removeEventListener('touchend', onUp); const r = el.getBoundingClientRect(); localStorage.setItem(storageKey, JSON.stringify({x:r.left, y:r.top})); if (moved) { el.dataset.justDragged = '1'; setTimeout(()=>delete el.dataset.justDragged,150); } }; el.addEventListener('mousedown', onDown); el.addEventListener('touchstart', onDown, {passive:false}); } function buildUI() { // Gear const gear = document.createElement('div'); gear.id = 'se-gear'; gear.textContent = '⚙️'; document.body.appendChild(gear); makeDraggable(gear, UI_GEAR_KEY, () => ({ x: window.innerWidth - 70, y: window.innerHeight - 70 })); // Panel const panel = document.createElement('div'); panel.id = 'se-panel'; panel.innerHTML = ` <div id="se-header"> <div>📚 Scribd Enhancer</div> <div class="controls"> <div id="se-min" class="btn" title="Collapse">–</div> <div id="se-close" class="btn" title="Close">✕</div> </div> </div> <div id="se-body"> <label><input type="checkbox" id="opt-unblur"> Unblur</label> <label><input type="checkbox" id="opt-autoscrape"> Auto Scrape</label> <label><input type="checkbox" id="opt-dark"> Dark Mode</label> <label><input type="checkbox" id="opt-preview"> Show Preview</label> <div class="row"> <label style="flex:1">OCR <select id="opt-lang"> <option value="auto">Auto</option> <option value="eng">English</option> <option value="spa">Spanish</option> <option value="fra">French</option> <option value="deu">German</option> </select> </label> <label style="flex:1">Split <select id="opt-split"> <option value="0">Off</option> <option value="100">100</option> <option value="250">250</option> <option value="500">500</option> </select> </label> </div> <label>Export Page Range <input id="opt-range" type="text" placeholder="all | 1-25 | 5,7,10-12"> </label> <div class="row"> <label>Scale <select id="opt-scale"> <option value="1">1x</option> <option value="2">2x</option> <option value="3">3x</option> <option value="4">4x</option> </select> </label> <label>JPEG <select id="opt-quality"> <option value="0.8">0.80</option> <option value="0.92">0.92</option> <option value="1.0">1.00</option> </select> </label> </div> <label>Rich Export Preference <select id="opt-richpref"> <option value="auto">Auto (prefer text layer if present)</option> <option value="preferText">Keep Text (remove page images)</option> <option value="preferImage">Keep Images (remove text layer)</option> </select> </label> <button id="btn-scrape">📖 Scrape Pages (Text/OCR)</button> <button id="btn-export">💾 Export TXT</button> <button id="btn-html">🧾 Export Plain HTML</button> <button id="btn-print">🖨️ Print (Text)</button> <button id="btn-snapshot-pdf">📸 Export Snapshot PDF</button> <button id="btn-rich-html">🖼️ Export Rich HTML</button> </div> `; document.body.appendChild(panel); makeDraggable(panel, UI_MENU_KEY, () => ({ x: window.innerWidth - 360, y: window.innerHeight - 360 })); // Open/Close & collapse const togglePanel = () => { if (gear.dataset.justDragged) return; panel.style.display = (panel.style.display === 'none' || !panel.style.display) ? 'block' : 'none'; }; gear.addEventListener('click', togglePanel); panel.querySelector('#se-close').addEventListener('click', () => panel.style.display = 'none'); const body = panel.querySelector('#se-body'); let collapsed = false; panel.querySelector('#se-min').addEventListener('click', () => { collapsed = !collapsed; body.style.display = collapsed ? 'none' : 'block'; panel.querySelector('#se-min').textContent = collapsed ? '+' : '–'; }); // Keyboard shortcuts document.addEventListener('keydown', (e) => { if (e.key.toLowerCase() === 'g') togglePanel(); if (e.key === 'Escape') panel.style.display = 'none'; }); // Bind controls const bind = (sel, prop, parser = v=>v) => { const el = panel.querySelector(sel); el.value = (prop in settings) ? settings[prop] : el.value; if (el.type === 'checkbox') el.checked = !!settings[prop]; el.addEventListener('change', () => { settings[prop] = el.type === 'checkbox' ? el.checked : parser(el.value); saveSettings(); applyDarkMode(); if (prop === 'showPreview') { if (settings.showPreview && !document.getElementById('se-preview')) document.body.appendChild(preview); if (!settings.showPreview && document.getElementById('se-preview')) preview.remove(); } }); return el; }; bind('#opt-unblur', 'unblur'); bind('#opt-autoscrape','autoScrape'); bind('#opt-dark', 'darkMode'); bind('#opt-preview', 'showPreview'); bind('#opt-lang', 'ocrLang'); bind('#opt-split', 'splitEvery', v=>parseInt(v,10)||0); bind('#opt-range', 'pageRange', v=>safe(v)||'all'); bind('#opt-scale', 'snapshotScale', v=>clamp(parseInt(v,10)||2,1,4)); bind('#opt-quality', 'snapshotQuality', v=>Number(v)||0.92); bind('#opt-richpref', 'richPref'); // Actions panel.querySelector('#btn-scrape').onclick = () => { const pages = getScribdPages(); if (!pages.length) return alert('❌ No pages found.'); if (settings.showPreview && !document.getElementById('se-preview')) document.body.appendChild(preview); scrapePages(pages, preview); }; panel.querySelector('#btn-export').onclick = () => exportOutput(preview.textContent, 'txt'); panel.querySelector('#btn-html').onclick = () => exportOutput(`<html><body><pre>${preview.textContent}</pre></body></html>`, 'html'); panel.querySelector('#btn-print').onclick = () => printToPDF(preview.textContent); panel.querySelector('#btn-snapshot-pdf').onclick = async () => { const pages = getScribdPages(); if (!pages.length) return alert('❌ No pages found.'); try { await exportSnapshotPDF(pages); } catch (e) { console.error(e); alert('Snapshot export failed. Try Rich HTML.'); } }; panel.querySelector('#btn-rich-html').onclick = async () => { const pages = getScribdPages(); if (!pages.length) return alert('❌ No pages found.'); try { await exportRichHTML(pages); } catch (e) { console.error(e); alert('Rich HTML export failed.'); } }; return { gear, panel }; } // Preview box function createPreview() { const preview = document.createElement('div'); preview.id = 'se-preview'; if (settings.showPreview) { preview.textContent = '[Preview Initialized]\n'; document.body.appendChild(preview); } return preview; } // ---------- BOOT ---------- applyDarkMode(); unblurContent(); const preview = createPreview(); const { gear } = buildUI(); makeDraggable(gear, UI_GEAR_KEY, () => ({ x: window.innerWidth - 70, y: window.innerHeight - 70 })); // Auto-scrape if desired if (settings.autoScrape) { const pages = getScribdPages(); if (pages.length && settings.showPreview && !document.getElementById('se-preview')) document.body.appendChild(preview); if (pages.length) scrapePages(pages, preview); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址