Scribd Enhancer All-in-One (v2.3)

Unblur Scribd, scrape visible text/images with OCR, optional live preview, export to TXT/HTML, and dark mode. Built by Eliminater74 with full UI toggle control and persistent settings.

目前為 2025-06-05 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Scribd Enhancer All-in-One (v2.3)
  3. // @namespace https://greasyfork.org/users/Eliminater74
  4. // @version 2.3
  5. // @description Unblur Scribd, scrape visible text/images with OCR, optional live preview, export to TXT/HTML, and dark mode. Built by Eliminater74 with full UI toggle control and persistent settings.
  6. // @author Eliminater74
  7. // @license MIT
  8. // @match *://*.scribd.com/*
  9. // @grant none
  10. // @icon https://s-f.scribdassets.com/favicon.ico
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15.  
  16. // Load Tesseract.js for OCR
  17. const script = document.createElement('script');
  18. script.src = 'https://cdn.jsdelivr.net/npm/tesseract.js@4.0.2/dist/tesseract.min.js';
  19. document.head.appendChild(script);
  20.  
  21. const LS_KEY = 'scribdEnhancerSettings';
  22. const defaultSettings = {
  23. unblur: true,
  24. printableView: true,
  25. autoScrape: true,
  26. darkMode: false,
  27. debug: false,
  28. showPreview: true
  29. };
  30. const settings = { ...defaultSettings, ...JSON.parse(localStorage.getItem(LS_KEY) || '{}') };
  31. const saveSettings = () => localStorage.setItem(LS_KEY, JSON.stringify(settings));
  32.  
  33. // Styles
  34. const style = document.createElement('style');
  35. style.textContent = `
  36. #se-floating-gear {
  37. position: fixed; bottom: 20px; right: 20px; z-index: 9999;
  38. width: 40px; height: 40px; border-radius: 50%; background: #333;
  39. color: white; font-size: 24px; text-align: center; line-height: 40px;
  40. cursor: pointer; box-shadow: 0 0 6px rgba(0,0,0,0.4);
  41. }
  42. #se-menu {
  43. position: fixed; bottom: 70px; right: 20px; z-index: 9999;
  44. background: #fff; border: 1px solid #ccc; padding: 10px; border-radius: 10px;
  45. display: none; font-family: sans-serif; box-shadow: 0 0 10px rgba(0,0,0,0.3);
  46. }
  47. #se-menu label {
  48. display: block; margin: 5px 0; color: #000; font-size: 14px;
  49. }
  50. #se-preview-box {
  51. position: fixed; top: 10px; right: 20px; bottom: 150px;
  52. width: 300px; background: #fff; color: #000; overflow: auto;
  53. z-index: 9998; border: 1px solid #999; padding: 10px; font-family: monospace;
  54. font-size: 12px; white-space: pre-wrap; border-radius: 10px;
  55. }
  56. body.dark-mode #se-preview-box {
  57. background: #222; color: #eee; border-color: #555;
  58. }
  59. div[style*="rgb(244, 221, 221)"],
  60. div[style*="#f4dddd"],
  61. div[style*="rgb(255, 244, 211)"],
  62. div[style*="#fff4d3"] {
  63. display: none !important;
  64. }
  65. body.dark-mode, html.dark-mode {
  66. background: #121212 !important;
  67. color: #e0e0e0 !important;
  68. }
  69. .dark-mode * {
  70. background: transparent !important;
  71. color: #e0e0e0 !important;
  72. border-color: #444 !important;
  73. }
  74. .dark-mode a { color: #66c0ff !important; }
  75. `;
  76. document.head.appendChild(style);
  77.  
  78. // Gear Icon & Settings Menu
  79. const gear = document.createElement('div');
  80. gear.id = 'se-floating-gear';
  81. gear.textContent = '⚙';
  82. document.body.appendChild(gear);
  83.  
  84. const menu = document.createElement('div');
  85. menu.id = 'se-menu';
  86. menu.innerHTML = Object.keys(defaultSettings).map(key => {
  87. const checked = settings[key] ? 'checked' : '';
  88. const label = key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase());
  89. return `<label><input type="checkbox" data-key="${key}" ${checked}> ${label}</label>`;
  90. }).join('');
  91. document.body.appendChild(menu);
  92.  
  93. gear.addEventListener('click', () => {
  94. menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
  95. });
  96.  
  97. menu.addEventListener('change', e => {
  98. const key = e.target.dataset.key;
  99. settings[key] = e.target.checked;
  100. saveSettings();
  101. applyDarkMode();
  102. location.reload();
  103. });
  104.  
  105. function applyDarkMode() {
  106. if (settings.darkMode) {
  107. document.documentElement.classList.add('dark-mode');
  108. document.body.classList.add('dark-mode');
  109. } else {
  110. document.documentElement.classList.remove('dark-mode');
  111. document.body.classList.remove('dark-mode');
  112. }
  113. }
  114.  
  115. function unblurContent() {
  116. if (!settings.unblur) return;
  117. const clean = () => {
  118. document.querySelectorAll('.blurred_page, .promo_div').forEach(el => el.remove());
  119. document.querySelectorAll('[unselectable="on"]').forEach(el => el.removeAttribute('unselectable'));
  120. document.querySelectorAll('*').forEach(el => {
  121. const cs = getComputedStyle(el);
  122. if (cs.color === 'transparent') el.style.color = '#111';
  123. if (cs.textShadow && cs.textShadow.includes('white')) el.style.textShadow = 'none';
  124. el.removeAttribute('data-initial-color');
  125. el.removeAttribute('data-initial-text-shadow');
  126. });
  127. };
  128. clean();
  129. new MutationObserver(clean).observe(document.body, { childList: true, subtree: true });
  130. }
  131.  
  132. function injectScrapeUI() {
  133. const container = document.createElement('div');
  134. container.id = 'scraped-book';
  135. container.style = 'display:none;';
  136. document.body.appendChild(container);
  137.  
  138. const preview = document.createElement('div');
  139. preview.id = 'se-preview-box';
  140. preview.textContent = '[Preview Ready]\n';
  141. if (settings.showPreview) document.body.appendChild(preview);
  142.  
  143. const addButton = (label, top, color, handler) => {
  144. const btn = document.createElement('button');
  145. btn.textContent = label;
  146. btn.style = `position:fixed;top:${top}px;left:10px;z-index:9999;padding:10px;background:${color};color:#fff;border:none;border-radius:5px;`;
  147. btn.onclick = handler;
  148. document.body.appendChild(btn);
  149. };
  150.  
  151. addButton('📖 Start Scraping Book', 50, '#2196F3', () => scrapePages(container, preview));
  152. addButton('🖨️ Print to PDF', 90, '#4CAF50', () => {
  153. const win = window.open('', 'PrintBook');
  154. win.document.write(`<html><head><title>Book</title></head><body>${container.innerHTML}</body></html>`);
  155. win.document.close(); win.focus(); setTimeout(() => win.print(), 600);
  156. });
  157. addButton('📄 Export as TXT', 130, '#6A1B9A', () => {
  158. const blob = new Blob([preview.textContent], { type: 'text/plain' });
  159. const link = document.createElement('a');
  160. link.href = URL.createObjectURL(blob);
  161. link.download = 'scribd_extracted.txt';
  162. link.click();
  163. });
  164. addButton('🧾 Export as HTML', 170, '#00796B', () => {
  165. const blob = new Blob([`<html><body><pre>${preview.textContent}</pre></body></html>`], { type: 'text/html' });
  166. const link = document.createElement('a');
  167. link.href = URL.createObjectURL(blob);
  168. link.download = 'scribd_extracted.html';
  169. link.click();
  170. });
  171. }
  172.  
  173. function scrapePages(container, preview) {
  174. window.scrollTo(0, 0);
  175. document.querySelectorAll('img[data-src], img[data-lazy], img.lazyload').forEach(img => {
  176. const src = img.getAttribute('data-src') || img.getAttribute('data-lazy');
  177. if (src) img.setAttribute('src', src);
  178. });
  179.  
  180. const pages = document.querySelectorAll('.text_layer, .page, .reader_column, [id^="page_container"]');
  181. let count = 0;
  182.  
  183. pages.forEach(page => {
  184. const clone = page.cloneNode(true);
  185.  
  186. clone.querySelectorAll('img').forEach(img => {
  187. const src = img.getAttribute('src') || img.getAttribute('data-src') || img.getAttribute('data-lazy');
  188. if (src) img.setAttribute('src', src);
  189. });
  190.  
  191. clone.querySelectorAll('*').forEach(el => {
  192. const bg = window.getComputedStyle(el).backgroundImage;
  193. if (bg && bg !== 'none') el.style.backgroundImage = bg;
  194. });
  195.  
  196. page.querySelectorAll('img').forEach(img => {
  197. if (window.Tesseract) {
  198. window.Tesseract.recognize(img.src, 'eng').then(result => {
  199. const text = result.data.text.trim();
  200. if (text && settings.showPreview) {
  201. const p = document.createElement('p');
  202. p.textContent = '[OCR] ' + text;
  203. preview.textContent += p.textContent + '\n';
  204. container.appendChild(p);
  205. }
  206. });
  207. }
  208. });
  209.  
  210. const plainText = page.innerText.trim();
  211. if (plainText) {
  212. const textNode = document.createElement('p');
  213. textNode.textContent = plainText;
  214. if (settings.showPreview) preview.textContent += plainText + '\n';
  215. container.appendChild(textNode);
  216. }
  217.  
  218. container.appendChild(clone);
  219. count++;
  220. });
  221.  
  222. alert(count > 0
  223. ? `✅ Scraped ${count} pages (text + images + OCR).`
  224. : `❌ No readable content found.`);
  225. }
  226.  
  227. window.addEventListener('load', () => {
  228. applyDarkMode();
  229. unblurContent();
  230. if (settings.autoScrape) injectScrapeUI();
  231. });
  232. })();