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

Unblur Scribd content, auto scrape visible pages, and print/save as PDF. Always show UI. Includes dark mode and persistent settings. Works on all /document/ and /read/ URLs. Built by Eliminater74.

当前为 2025-06-03 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Scribd Enhancer All-in-One (v2.0)
  3. // @namespace https://greasyfork.org/users/Eliminater74
  4. // @version 2.0
  5. // @description Unblur Scribd content, auto scrape visible pages, and print/save as PDF. Always show UI. Includes dark mode and persistent settings. Works on all /document/ and /read/ URLs. Built by Eliminater74.
  6. // @author Eliminater74
  7. // @license MIT
  8. // @match *://*.scribd.com/*
  9. // @icon https://s-f.scribdassets.com/favicon.ico
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15.  
  16. const LS_KEY = 'scribdEnhancerSettings';
  17. const defaultSettings = {
  18. unblur: true,
  19. printableView: true,
  20. autoScrape: true,
  21. darkMode: false,
  22. debug: false
  23. };
  24.  
  25. const settings = JSON.parse(localStorage.getItem(LS_KEY)) || defaultSettings;
  26. const saveSettings = () => localStorage.setItem(LS_KEY, JSON.stringify(settings));
  27.  
  28. // -------- UI Styles --------
  29. const style = document.createElement('style');
  30. style.textContent = `
  31. #se-floating-gear {
  32. position: fixed; bottom: 20px; right: 20px; z-index: 9999;
  33. width: 40px; height: 40px; border-radius: 50%; background: #333;
  34. color: white; font-size: 24px; text-align: center; line-height: 40px;
  35. cursor: pointer; box-shadow: 0 0 6px rgba(0,0,0,0.4);
  36. }
  37. #se-menu {
  38. position: fixed; bottom: 70px; right: 20px; z-index: 9999;
  39. background: #fff; border: 1px solid #ccc; padding: 10px; border-radius: 10px;
  40. display: none; font-family: sans-serif; box-shadow: 0 0 10px rgba(0,0,0,0.3);
  41. }
  42. #se-menu label {
  43. display: block; margin: 5px 0; color: #000; font-size: 14px;
  44. }
  45. div[style*="rgb(244, 221, 221)"],
  46. div[style*="#f4dddd"],
  47. div[style*="rgb(255, 244, 211)"],
  48. div[style*="#fff4d3"] {
  49. display: none !important;
  50. }
  51. body.dark-mode, html.dark-mode {
  52. background: #121212 !important;
  53. color: #e0e0e0 !important;
  54. }
  55. .dark-mode * {
  56. background: transparent !important;
  57. color: #e0e0e0 !important;
  58. border-color: #444 !important;
  59. }
  60. .dark-mode a { color: #66c0ff !important; }
  61. .dark-mode .promo_div, .dark-mode .blurred_page {
  62. display: none !important;
  63. }
  64. `;
  65. document.head.appendChild(style);
  66.  
  67. // -------- Floating Gear UI --------
  68. const gear = document.createElement('div');
  69. gear.id = 'se-floating-gear';
  70. gear.textContent = '⚙';
  71. document.body.appendChild(gear);
  72.  
  73. const menu = document.createElement('div');
  74. menu.id = 'se-menu';
  75. menu.innerHTML = Object.keys(settings).map(key => {
  76. const checked = settings[key] ? 'checked' : '';
  77. const label = key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase());
  78. return `<label><input type="checkbox" data-key="${key}" ${checked}> ${label}</label>`;
  79. }).join('');
  80. document.body.appendChild(menu);
  81.  
  82. gear.addEventListener('click', () => {
  83. menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
  84. });
  85.  
  86. menu.addEventListener('change', e => {
  87. const key = e.target.dataset.key;
  88. settings[key] = e.target.checked;
  89. saveSettings();
  90. applyDarkMode();
  91. location.reload();
  92. });
  93.  
  94. function applyDarkMode() {
  95. if (settings.darkMode) {
  96. document.documentElement.classList.add('dark-mode');
  97. document.body.classList.add('dark-mode');
  98. } else {
  99. document.documentElement.classList.remove('dark-mode');
  100. document.body.classList.remove('dark-mode');
  101. }
  102. }
  103.  
  104. // -------- Feature Functions --------
  105.  
  106. function unblurContent() {
  107. if (!settings.unblur) return;
  108. const clean = () => {
  109. document.querySelectorAll('.blurred_page, .promo_div').forEach(el => el.remove());
  110. document.querySelectorAll('[unselectable="on"]').forEach(el => el.removeAttribute('unselectable'));
  111. document.querySelectorAll('*').forEach(el => {
  112. const cs = getComputedStyle(el);
  113. if (cs.color === 'transparent') el.style.color = '#111';
  114. if (cs.textShadow && cs.textShadow.includes('white')) el.style.textShadow = 'none';
  115. el.removeAttribute('data-initial-color');
  116. el.removeAttribute('data-initial-text-shadow');
  117. });
  118. };
  119. clean();
  120. const observer = new MutationObserver(clean);
  121. observer.observe(document.body, { childList: true, subtree: true });
  122. }
  123.  
  124. function enablePrintableView() {
  125. if (!settings.printableView) return;
  126. document.querySelectorAll('script').forEach(script => {
  127. const idMatch = /"id":(\d{6,})/.exec(script.textContent);
  128. const keyMatch = /"access_key":"(key[-\w\d]*)"/.exec(script.textContent);
  129. if (idMatch && keyMatch) {
  130. const swfUrl = `http://d1.scribdassets.com/ScribdViewer.swf?document_id=${idMatch[1]}&access_key=${keyMatch[1]}`;
  131. document.querySelectorAll('div').forEach(div => {
  132. div.innerHTML = div.innerHTML.replace(/https?:\/\/www\.scribd\.com\/upload-document/gi, swfUrl);
  133. });
  134. }
  135. });
  136. }
  137.  
  138. function injectScrapeUI() {
  139. const container = document.createElement('div');
  140. container.id = 'scraped-book';
  141. container.style = 'display:none;';
  142. document.body.appendChild(container);
  143.  
  144. const scrapeBtn = document.createElement('button');
  145. scrapeBtn.textContent = '📖 Start Scraping Book';
  146. scrapeBtn.style = 'position:fixed;top:50px;left:10px;z-index:9999;padding:10px;background:#2196F3;color:#fff;border:none;border-radius:5px;';
  147. scrapeBtn.onclick = () => scrapePages(container);
  148. document.body.appendChild(scrapeBtn);
  149.  
  150. const printBtn = document.createElement('button');
  151. printBtn.textContent = '🖨️ Print to PDF';
  152. printBtn.style = 'position:fixed;top:90px;left:10px;z-index:9999;padding:10px;background:#4CAF50;color:#fff;border:none;border-radius:5px;';
  153. printBtn.onclick = () => {
  154. const win = window.open('', 'PrintBook');
  155. win.document.write(`<html><head><title>Book</title></head><body>${container.innerHTML}</body></html>`);
  156. win.document.close();
  157. win.focus();
  158. setTimeout(() => win.print(), 600);
  159. };
  160. document.body.appendChild(printBtn);
  161. }
  162.  
  163. function scrapePages(container) {
  164. window.scrollTo(0, 0); // Ensure visibility for lazy-loaded elements
  165. const pages = document.querySelectorAll('.text_layer, .page, .reader_column, [id^="page_container"]');
  166. let count = 0;
  167.  
  168. pages.forEach(page => {
  169. const clone = page.cloneNode(true);
  170. clone.style.marginBottom = '30px';
  171. container.appendChild(clone);
  172. count++;
  173. });
  174.  
  175. if (count > 0) {
  176. alert(`✅ Scraped ${count} page elements. Ready to Print.`);
  177. } else {
  178. alert(`❌ No readable page content found. Try scrolling through the book or toggling debug mode.`);
  179. }
  180. }
  181.  
  182. // -------- Init --------
  183. window.addEventListener('load', () => {
  184. applyDarkMode();
  185. unblurContent();
  186. enablePrintableView();
  187. if (settings.autoScrape) injectScrapeUI();
  188. });
  189. })();