您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Download protected PDF files from Google Drive with quality options
// ==UserScript== // @name Enhanced Google Drive PDF Downloader // @namespace GoogleDrivePDFDownloader // @version 2 // @description Download protected PDF files from Google Drive with quality options // @author akvabhi // @match https://drive.google.com/* // @grant none // @homepage https://github.com/Akv2021/Enhanced-Google-Drive-PDF-Downloader // @license MIT // ==/UserScript== (function () { 'use strict'; const COLORS = { fast: '#2ecc71', // Green for fast mode slow: '#e74c3c', // Red for slow mode hover: '#3367d6', default: '#4285f4' }; // Utility functions const log = (message, type = 'info') => { const timestamp = new Date().toLocaleTimeString(); const logMethod = type === 'error' ? console.error : console.log; logMethod(`[PDF Downloader ${timestamp}] ${message}`); }; const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); // Global quality setting window.pdfQualityMode = 'FAST'; // Default to fast mode const progressIndicator = { element: null, create() { const indicator = document.createElement('div'); indicator.style.cssText = ` position: fixed; top: 65px; right: 20px; z-index: 9999; padding: 8px 16px; background-color: #4285f4; color: white; border-radius: 4px; font-family: Arial, sans-serif; font-size: 14px; display: none; box-shadow: 0 2px 5px rgba(0,0,0,0.2); line-height: 20px; min-height: 20px; display: flex; align-items: center; transition: all 0.3s ease; `; document.body.appendChild(indicator); this.element = indicator; }, show(message) { if (!this.element) this.create(); this.element.style.display = 'block'; this.element.style.opacity = '0'; setTimeout(() => { this.element.style.opacity = '1'; this.element.textContent = message; }, 10); const downloadContainer = document.querySelector('#pdfDownloadContainer'); if (downloadContainer) downloadContainer.style.display = 'none'; }, hide() { if (this.element) { this.element.style.opacity = '0'; setTimeout(() => { this.element.style.display = 'none'; const downloadContainer = document.querySelector('#pdfDownloadContainer'); if (downloadContainer) { downloadContainer.style.display = 'flex'; downloadContainer.style.opacity = '1'; } }, 300); } }, updateProgress(current, total) { const percentage = Math.floor((current / total) * 100); this.show(`Processing: ${percentage}% (${current}/${total} pages)`); } }; async function loadJsPDF() { return new Promise((resolve, reject) => { log('Loading jsPDF library...'); progressIndicator.show('Loading PDF library...'); const script = document.createElement('script'); const scriptURL = 'https://unpkg.com/jspdf@latest/dist/jspdf.umd.min.js'; if (window.trustedTypes && trustedTypes.createPolicy) { const policy = trustedTypes.createPolicy('pdfDownloaderPolicy', { createScriptURL: (input) => input }); script.src = policy.createScriptURL(scriptURL); } else { script.src = scriptURL; } script.onload = () => { log('jsPDF library loaded successfully'); resolve(); }; script.onerror = (error) => { log('Failed to load jsPDF library', 'error'); reject(error); }; document.body.appendChild(script); }); } async function convertImageToBase64(img) { try { const canvas = document.createElement('canvas'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight); return canvas.toDataURL('image/png', 1.0); } catch (error) { log(`Error converting image to base64: ${error.message}`, 'error'); throw error; } } function getValidPdfPages() { const images = Array.from(document.getElementsByTagName('img')); return images.filter(img => img.src.startsWith('blob:https://drive.google.com/') && img.naturalWidth > 0 && img.naturalHeight > 0 ); } async function generatePDF(images) { log(`Starting PDF generation with ${images.length} pages`); const { jsPDF } = window.jspdf; let pdf = null; for (let i = 0; i < images.length; i++) { const img = images[i]; const orientation = img.naturalWidth > img.naturalHeight ? 'l' : 'p'; if (!pdf) { pdf = new jsPDF({ orientation: orientation, unit: 'px', format: [img.naturalWidth, img.naturalHeight], hotfixes: ['px_scaling'] }); } if (i > 0) { pdf.addPage([img.naturalWidth, img.naturalHeight], orientation); } progressIndicator.show(`Converting page ${i + 1}/${images.length}...`); const imgData = await convertImageToBase64(img); pdf.addImage(imgData, 'PNG', 0, 0, img.naturalWidth, img.naturalHeight, '', window.pdfQualityMode); progressIndicator.updateProgress(i + 1, images.length); await delay(50); } return pdf; } async function downloadPDF() { try { const button = document.querySelector('#pdfDownloadButton'); button.disabled = true; button.textContent = '⏳ Processing...'; log('Starting PDF download process...'); progressIndicator.show('Initializing...'); await loadJsPDF(); const validPages = getValidPdfPages(); if (validPages.length === 0) { throw new Error('No valid PDF pages found. Please scroll through the document first.'); } log(`Found ${validPages.length} valid pages`); const pdf = await generatePDF(validPages); const fileName = document.querySelector('meta[itemprop="name"]')?.content || 'download.pdf'; const finalFileName = fileName.toLowerCase().endsWith('.pdf') ? fileName : `${fileName}.pdf`; progressIndicator.show(`Saving as ${finalFileName}...`); await pdf.save(finalFileName, { returnPromise: true }); log('PDF downloaded successfully!'); progressIndicator.show('Download complete!'); button.disabled = false; button.textContent = window.pdfQualityMode === 'SLOW' ? 'Download PDF (Best Quality)' : 'Download PDF (Fast)'; setTimeout(() => progressIndicator.hide(), 3000); } catch (error) { log(`Failed to generate PDF: ${error.message}`, 'error'); progressIndicator.show(`Error: ${error.message}`); const button = document.querySelector('#pdfDownloadButton'); button.disabled = false; button.textContent = window.pdfQualityMode === 'SLOW' ? 'Download PDF (Best Quality)' : 'Download PDF (Fast)'; alert(`Failed to generate PDF: ${error.message}`); } } function updateDownloadButtonText(isHighQuality) { const button = document.querySelector('#pdfDownloadButton'); if (button) { const newText = isHighQuality ? 'Download PDF (Best Quality)' : 'Download PDF (Fast)'; button.style.opacity = '0'; setTimeout(() => { button.textContent = newText; button.style.opacity = '1'; }, 150); } } function createToggleSwitch() { const toggleContainer = document.createElement('div'); toggleContainer.style.cssText = ` display: flex; flex-direction: column; align-items: center; margin: 12px 0; width: 100%; `; const switchLabel = document.createElement('div'); switchLabel.textContent = 'Quality Mode:'; switchLabel.style.marginBottom = '8px'; const switchControl = document.createElement('div'); switchControl.style.cssText = ` display: flex; flex-direction: column; align-items: center; width: 100%; `; const toggleSwitch = document.createElement('label'); toggleSwitch.style.cssText = ` position: relative; display: inline-block; width: 46px; height: 24px; `; const toggleInput = document.createElement('input'); toggleInput.type = 'checkbox'; toggleInput.checked = false; // Default to fast mode toggleInput.style.cssText = ` opacity: 0; width: 0; height: 0; `; const toggleSlider = document.createElement('span'); toggleSlider.style.cssText = ` position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: ${COLORS.fast}; transition: .4s; border-radius: 24px; `; const sliderBall = document.createElement('span'); sliderBall.style.cssText = ` position: absolute; content: ''; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; transform: translateX(0); `; const labelsContainer = document.createElement('div'); labelsContainer.style.cssText = ` display: flex; justify-content: space-between; width: 200px; margin-top: 8px; font-size: 12px; color: #888; `; const lowLabel = document.createElement('span'); lowLabel.textContent = 'Low (Fast)'; lowLabel.style.cssText = ` transition: color 0.3s ease; color: #4285f4; `; const highLabel = document.createElement('span'); highLabel.textContent = 'High (Slow)'; highLabel.style.cssText = ` transition: color 0.3s ease; color: #888; `; toggleInput.addEventListener('change', (e) => { const isHighQuality = e.target.checked; sliderBall.style.transform = isHighQuality ? 'translateX(22px)' : 'translateX(0)'; toggleSlider.style.backgroundColor = isHighQuality ? COLORS.slow : COLORS.fast; window.pdfQualityMode = isHighQuality ? 'SLOW' : 'FAST'; lowLabel.style.color = isHighQuality ? '#888' : '#fff'; highLabel.style.color = isHighQuality ? '#fff' : '#888'; updateDownloadButtonText(isHighQuality); }); toggleSlider.appendChild(sliderBall); toggleSwitch.appendChild(toggleInput); toggleSwitch.appendChild(toggleSlider); labelsContainer.appendChild(lowLabel); labelsContainer.appendChild(highLabel); switchControl.appendChild(toggleSwitch); switchControl.appendChild(labelsContainer); toggleContainer.appendChild(switchLabel); toggleContainer.appendChild(switchControl); return toggleContainer; } function addDownloadButton() { const container = document.createElement('div'); container.id = 'pdfDownloadContainer'; container.style.cssText = ` position: fixed; top: 65px; right: 20px; z-index: 9999; display: flex; align-items: center; gap: 8px; transition: opacity 0.3s ease; `; const button = document.createElement('button'); button.id = 'pdfDownloadButton'; button.textContent = 'Download PDF (Fast)'; button.style.cssText = ` padding: 8px 16px; background-color: #4285f4; color: white; border: none; border-radius: 4px; cursor: pointer; font-family: Arial, sans-serif; font-size: 14px; box-shadow: 0 2px 5px rgba(0,0,0,0.2); height: 36px; display: flex; align-items: center; transition: all 0.3s ease; opacity: 1; `; const infoIcon = document.createElement('div'); infoIcon.id = 'pdfInfoIcon'; // Add ID for click-outside handling infoIcon.innerHTML = 'ℹ️'; infoIcon.style.cssText = ` cursor: help; font-size: 16px; position: relative; width: 36px; height: 36px; background-color: #4285f4; border-radius: 4px; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 5px rgba(0,0,0,0.2); transition: background-color 0.3s ease; `; const tooltip = document.createElement('div'); tooltip.id = 'pdfDownloadTooltip'; // Add ID for click-outside handling tooltip.style.cssText = ` position: absolute; top: calc(100% + 8px); right: 0; background-color: #333; color: white; padding: 16px; border-radius: 4px; font-size: 13px; width: 280px; display: none; z-index: 10000; font-family: Arial, sans-serif; box-shadow: 0 2px 10px rgba(0,0,0,0.2); transition: opacity 0.3s ease; `; // Info content const scrollInfo = document.createElement('div'); scrollInfo.textContent = '⚠️ If some pages are missing, scroll to bottom to load all pages and retry.'; scrollInfo.style.marginBottom = '8px'; const qualityInfo = document.createElement('div'); qualityInfo.style.cssText = ` margin-bottom: 2px; padding: 8px; background-color: rgba(255,255,255,0.1); border-radius: 4px; `; qualityInfo.innerHTML = ` <div style="margin-bottom: 8px; font-weight: bold;">Processing Modes:</div> <div style="font-size: 12px; line-height: 1.4;"> • Fast: Quick processing (few seconds)<br> • Slow: Detailed processing (may take longer) </div> `; // Add toggle switch const toggleSwitch = createToggleSwitch(); // Footer with author and GitHub link const footer = document.createElement('div'); footer.style.cssText = ` display: flex; align-items: center; justify-content: center; margin-top: 4px; padding-top: 12px; border-top: 1px solid rgba(255,255,255,0.1); `; const authorText = document.createElement('span'); authorText.textContent = 'Track Issues '; authorText.style.marginRight = '8px'; const githubLink = document.createElement('a'); githubLink.href = 'https://github.com/Akv2021/Enhanced-Google-Drive-PDF-Downloader/issues'; githubLink.target = '_blank'; githubLink.style.cssText = ` color: white; text-decoration: none; display: flex; align-items: center; transition: opacity 0.3s ease; `; githubLink.innerHTML = ` <svg height="20" width="20" viewBox="0 0 16 16" style="fill: white;"> <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/> </svg> `; // Tooltip persistence let tooltipTimer = null; let isTooltipHovered = false; function startTooltipTimer() { if (tooltipTimer) { clearTimeout(tooltipTimer); } tooltipTimer = setTimeout(() => { if (!isTooltipHovered) { tooltip.style.opacity = '0'; setTimeout(() => tooltip.style.display = 'none', 300); infoIcon.style.backgroundColor = '#4285f4'; } }, 60000); // 60 second timeout } tooltip.addEventListener('mouseenter', () => { isTooltipHovered = true; if (tooltipTimer) { clearTimeout(tooltipTimer); } }); tooltip.addEventListener('mouseleave', () => { isTooltipHovered = false; startTooltipTimer(); }); infoIcon.addEventListener('mouseenter', () => { tooltip.style.display = 'block'; tooltip.style.opacity = '0'; setTimeout(() => tooltip.style.opacity = '1', 10); infoIcon.style.backgroundColor = '#3367d6'; startTooltipTimer(); }); infoIcon.addEventListener('mouseleave', () => { if (!isTooltipHovered) { startTooltipTimer(); } }); button.addEventListener('mouseover', () => { if (!button.disabled) button.style.backgroundColor = '#3367d6'; }); button.addEventListener('mouseout', () => { if (!button.disabled) button.style.backgroundColor = '#4285f4'; }); button.addEventListener('click', downloadPDF); // Assemble tooltip tooltip.appendChild(scrollInfo); tooltip.appendChild(qualityInfo); tooltip.appendChild(toggleSwitch); footer.appendChild(authorText); footer.appendChild(githubLink); tooltip.appendChild(footer); // Assemble final container infoIcon.appendChild(tooltip); container.appendChild(button); container.appendChild(infoIcon); document.body.appendChild(container); } function initialize() { log('Initializing PDF downloader...'); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { addDownloadButton(); setupClickOutside(); }); } else { addDownloadButton(); setupClickOutside(); } } // Add this new function for click-outside handling function setupClickOutside() { document.addEventListener('click', (event) => { const tooltip = document.querySelector('#pdfDownloadTooltip'); const infoIcon = document.querySelector('#pdfInfoIcon'); if (tooltip && !tooltip.contains(event.target) && !infoIcon.contains(event.target)) { tooltip.style.opacity = '0'; setTimeout(() => tooltip.style.display = 'none', 300); infoIcon.style.backgroundColor = COLORS.default; } }); } initialize(); })();