Enhanced Google Drive PDF Downloader

Download protected PDF files from Google Drive with quality options

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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();
})();