Greasy Fork

Greasy Fork is available in English.

THU PDF Downloader - 清华大学图书馆/PDF.js 强制下载

专门针对清华大学图书馆、学位论文等 PDF.js 访问限制场景。直接从浏览器内存中提取 PDF 原始流并下载,支持页面标题重命名。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         THU PDF Downloader - 清华大学图书馆/PDF.js 强制下载
// @namespace    http://tampermonkey.net/
// @version      2.5
// @description  专门针对清华大学图书馆、学位论文等 PDF.js 访问限制场景。直接从浏览器内存中提取 PDF 原始流并下载,支持页面标题重命名。
// @author       王一航
// @match        *://newetds.lib.tsinghua.edu.cn/*
// @match        *://*/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // === UI 配置 ===
    const CONFIG = {
        mainColor: '#660874', // 清华紫
        hoverColor: '#8c259d',
        textColor: '#ffffff',
        position: { bottom: '20px', right: '20px' },
        zIndex: '2147483647'
    };

    // 定义按钮创建逻辑(封装成函数,稍后调用)
    function initButton(app) {
        // 防止重复添加
        if (document.getElementById('thu-pdf-downloader-container')) return;

        const container = document.createElement('div');
        container.id = 'thu-pdf-downloader-container';
        
        // 注入样式
        const style = document.createElement('style');
        style.innerHTML = `
            #thu-pdf-downloader-container {
                position: fixed;
                bottom: ${CONFIG.position.bottom};
                right: ${CONFIG.position.right};
                z-index: ${CONFIG.zIndex};
                font-family: sans-serif;
            }
            #thu-pdf-btn {
                display: flex;
                align-items: center;
                justify-content: center;
                background-color: ${CONFIG.mainColor};
                color: ${CONFIG.textColor};
                border: none;
                border-radius: 4px;
                padding: 8px 16px;
                font-size: 14px;
                font-weight: 600;
                cursor: pointer;
                box-shadow: 0 2px 5px rgba(0,0,0,0.2);
                transition: all 0.2s ease;
                user-select: none;
            }
            #thu-pdf-btn:hover {
                background-color: ${CONFIG.hoverColor};
                transform: translateY(-1px);
            }
            #thu-pdf-btn svg {
                width: 16px;
                height: 16px;
                margin-right: 8px;
                fill: currentColor;
            }
        `;
        document.head.appendChild(style);

        const btn = document.createElement('div');
        btn.id = 'thu-pdf-btn';
        btn.innerHTML = `
            <svg viewBox="0 0 24 24"><path d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" /></svg>
            <span>下载 PDF</span>
        `;
        btn.title = "检测到 PDF.js 实例,点击强制下载";

        // 点击事件
        btn.onclick = async () => {
            const updateState = (text, bg) => {
                btn.querySelector('span').innerText = text;
                if(bg) btn.style.backgroundColor = bg;
            };

            updateState('处理中...', '#555');

            try {
                // 直接使用传入的 app 实例,不再递归查找,确保 100% 准确
                const data = await app.pdfDocument.getData();
                const blob = new Blob([data], { type: 'application/pdf' });
                const url = URL.createObjectURL(blob);
                
                const rawTitle = document.title || 'downloaded_document';
                const safeTitle = rawTitle.replace(/[\\/:*?"<>|]/g, '_').trim();
                
                const a = document.createElement('a');
                a.href = url;
                a.download = `${safeTitle}.pdf`;
                document.body.appendChild(a);
                a.click();
                
                setTimeout(() => {
                    document.body.removeChild(a);
                    URL.revokeObjectURL(url);
                    updateState('成功!', '#28a745');
                    setTimeout(() => updateState('下载 PDF', CONFIG.mainColor), 2000);
                }, 100);

            } catch (err) {
                console.error("[THU-PDF] Error:", err);
                alert("下载出错,请检查控制台");
                updateState('出错', '#dc3545');
            }
        };

        container.appendChild(btn);
        document.body.appendChild(container);
        console.log("[THU-PDF] 按钮已注入 (针对当前 Context)");
    }

    // === 核心修改:轮询检测机制 ===
    // 不再默认显示按钮,而是查找是否存在 PDFViewerApplication
    const checkInterval = setInterval(() => {
        // 1. 检测全局对象是否存在
        if (window.PDFViewerApplication && window.PDFViewerApplication.pdfDocument) {
            clearInterval(checkInterval);
            initButton(window.PDFViewerApplication);
        }
    }, 1000); // 每秒检查一次

    // 10秒后停止检测,节省性能 (可根据网络情况调整)
    setTimeout(() => clearInterval(checkInterval), 15000);

})();