您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
可知网导出页面到PDF,仅对PDF预览有效
当前为
// ==UserScript== // @name keledge-helper // @namespace http://tampermonkey.net/ // @version 0.1 // @description 可知网导出页面到PDF,仅对PDF预览有效 // @author [email protected] // @match https://www.keledge.com/pdfReader?* // @require https://cdn.staticfile.org/pdf-lib/1.17.1/pdf-lib.min.js // @icon https://www.google.com/s2/favicons?sz=64&domain=keledge.com // @grant none // @run-at document-start // @license GPL-3.0-only // ==/UserScript== (function() { 'use strict'; // 全局常量 const GUI = `<div><style class="keledge-style">.keledge-fold-btn{position:fixed;left:151px;top:36%;user-select:none;font-size:large;z-index:1001}.keledge-fold-btn::after{content:"🐵"}.keledge-fold-btn.folded{left:20px}.keledge-fold-btn.folded::after{content:"🙈"}.keledge-box{position:fixed;width:154px;left:10px;top:32%;z-index:1000}.btns-sec{background:#e7f1ff;border:2px solid #1676ff;padding:0 0 10px 0;font-weight:600;border-radius:2px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei','Helvetica Neue',Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol'}.btns-sec.folded{display:none}.logo-title{width:100%;background:#1676ff;text-align:center;font-size:large;color:#e7f1ff;line-height:40px;height:40px;margin:0 0 16px 0}.keledge-box button{display:block;width:128px;height:28px;border-radius:4px;color:#fff;font-size:12px;border:none;outline:0;margin:8px auto;font-weight:700;cursor:pointer;opacity:.9}.keledge-box button.folded{display:none}.keledge-box .btn-1{background:linear-gradient(180deg,#00e7f7 0,#feb800 .01%,#ff8700 100%)}.keledge-box .btn-1:hover,.keledge-box .btn-2:hover{opacity:.8}.keledge-box .btn-1:active,.keledge-box .btn-2:active{opacity:1}</style><div class="keledge-box"><section class="btns-sec"><p class="logo-title">keledge-helper</p><button class="btn-1" onclick="btn1_fn(this)">{{btn1_desc}}</button></section><p class="keledge-fold-btn" onclick="[this, this.parentElement.querySelector('.btns-sec')].forEach(elem => elem.classList.toggle('folded'))"></p></div></div>`; // 全局变量 window.pdf_data_list = []; window.log = console.log.bind(console); window.error = console.error.bind(console); /** * @param {number} delay */ function sleep(delay) { return new Promise(resolve => setTimeout(resolve, delay)); } async function wait_for_pdfjs() { while (!window.pdfjsLib) { await sleep(200); } } function hooked_get_doc(pdf_data) { pdf_data_list.push(pdf_data.data); log(`page collected: ${pdf_data_list.length}`); return getDocument(pdf_data); } function hook_pdfjs() { window.getDocument = pdfjsLib.getDocument.bind(pdfjsLib); pdfjsLib.getDocument = hooked_get_doc; } /** * 加载CDN脚本 * @param {string} url */ async function load_web_script(url) { try { // xhr+eval方式 const resp = await fetch(url); const code = await resp.text(); Function(code)(); } catch(e) { error(e); // 嵌入<script>方式 return new Promise((resolve) => { const script = document.createElement("script"); script.src = url; script.onload = resolve; document.body.append(script); }); } } /** * 返回一个包含计数器的迭代器, 其每次迭代值为 [index, value] * @param {Iterable} iterable * @returns */ function* enumerate(iterable) { let i = 0; for (let value of iterable) { yield [i, value]; i++; } } /** * 合并多个PDF * @param {Array<ArrayBuffer | Uint8Array>} pdfs * @returns {Promise<Uint8Array>} */ async function join_pdfs(pdfs) { if (!window.PDFLib) { const url = "https://cdn.staticfile.org/pdf-lib/1.17.1/pdf-lib.min.js"; await load_web_script(url); } const combined = await PDFLib.PDFDocument.create(); for (const [i, buffer] of enumerate(pdfs)) { const pdf = await PDFLib.PDFDocument.load(buffer); const pages = await combined.copyPages( pdf, pdf.getPageIndices() ); for (const page of pages) { combined.addPage(page); } log(`已经合并 ${i + 1} 组`); } return combined.save(); } /** * 创建并下载文件 * @param {string} file_name 文件名 * @param {ArrayBuffer | ArrayBufferView | Blob | string} content 内容 * @param {string} type 媒体类型,需要符合 MIME 标准 */ function save(file_name, content, type="") { const blob = new Blob( [content], { type } ); const size = (blob.size / 1024).toFixed(1); log(`blob saved, size: ${size} kb, type: ${blob.type}`); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.download = file_name || "未命名文件"; a.href = url; a.click(); URL.revokeObjectURL(url); } async function export_pdf() { const combined = await join_pdfs(pdf_data_list); save(document.title + ".pdf", combined, "application/pdf"); } /** * @param {string} selectors * @returns {HTMLElement} */ function $(selectors) { const self = this?.querySelector ? this : document; return self.querySelector(selectors); } /** * 等待直到函数返回true * @param {Function} is_ready 判断条件达成与否的函数 * @param {number} timeout 最大等待秒数, 默认5000毫秒 * @returns {Promise<boolean>} 是否在超时前返回 */ async function until(is_ready, timeout=5000) { const gap = 200; let chances = parseInt(timeout / gap); chances = chances < 1 ? 1 : chances; while (!is_ready()) { await sleep(200); chances -= 1; if (!chances) { break; } } if (chances === 0) { error(`超时!(${timeout} ms);超时函数: `, is_ready); return false; } return true; } /** * 判断指定页码的页面是否加载完成 * @param {number} page_no * @returns */ function is_page_loaded(page_no) { return !!$(`[id*="pdf-page-${page_no}"] [data-loaded="true"]`); } /** * @param {HTMLElement} element * @returns {Promise<boolean>} 是否被封禁 */ async function on_page_loaded(element) { const success = await until(() => $.call(element, `[data-loaded="true"]`)); return !success; } async function main() { log("进入 keledge-helper 脚本"); await wait_for_pdfjs(); hook_pdfjs(); window.btn1_fn = export_pdf; const gui = GUI.replace("{{btn1_desc}}", "导出PDF"); document.body.insertAdjacentHTML("beforeend", gui); } main(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址