Greasy Fork is available in English.
专门针对清华大学图书馆、学位论文等 PDF.js 访问限制场景。直接从浏览器内存中提取 PDF 原始流并下载,支持页面标题重命名。
// ==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);
})();