合肥工业大学图书馆PDF下载

适用于【合肥工业大学图书馆-纸电一体化读者服务平台】,对文档截图,合并为PDF。

目前为 2023-08-17 提交的版本。查看 最新版本

// ==UserScript==
// @name         合肥工业大学图书馆PDF下载
// @namespace    http://tampermonkey.net/
// @version      0.0.1
// @description  适用于【合肥工业大学图书馆-纸电一体化读者服务平台】,对文档截图,合并为PDF。
// @author       [email protected]
// @match        https://hfut.zhitongda.cn/opac/readBook
// @require      https://gf.qytechs.cn/scripts/445312-wk-full-cli/code/wk-full-cli.user.js
// @connect      cdn.staticfile.org
// @icon         https://hfut.zhitongda.cn/opac/favicon.ico
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// @run-at       document-idle
// @license      GPL-3.0-only
// ==/UserScript==


// import { utils } from "../utils";
(function() {
    "use strict";


    // 全局常量
    const API = "getPdfUrl";  // 取得 PDF 数据的全局函数名称
    const DL_BTN = 1;  // 按钮序号,此按钮用于下载 PDF
    const PDF_LIB_URL = "https://cdn.staticfile.org/pdf-lib/1.17.1/pdf-lib.min.js";
    const window = unsafeWindow;
    const utils = wk$;
    
    // 全局变量
    let iframe = null;


    /**
     * 输出 [info] 级别日志到控制台
     * @param  {...any} args 
     */
    function print(...args) {
        console.info("[wk]:", ...args);
    }


    /**
     * 初始化进度计数器
     */
    function init_counter() {
        let _counter = 0;
        let _text = "未初始化进度:{}";

        /**
         * 设置进度文本,必须正好包含一对大括号,用于填充 counter
         * @param {string} text 
         */
        function set_text(text) {
            const [left, right] = [_text.indexOf("{}"), _text.lastIndexOf("{}")];
            if (left === -1) {
                throw new Error(`进度文本中必须包含一对大括号`);
            }
            if (left !== right) {
                throw new Error(`进度文本中能且仅能包含一对大括号`);
            }

            _text = text;
        }

        /**
         * counter + 1,且输出进度
         */
        function count() {
            _counter += 1;
            const progress = _text.replace("{}", `${_counter}`);

            try {
                print("<进度>", progress);
                utils.btn(DL_BTN).textContent = progress;
            } catch (err) {
                console.error(err);
            }
        }

        /**
         * 重置 counter
         */
        function reset_counter() {
            _counter = 0;
            _text = "未初始化进度:{}";
        }

        return { set_text, count, reset_counter };
    }

    const { set_text, count, reset_counter } = init_counter();


    /**
     * iframe 内单元素选择器
     * @param {string} selectors 
     * @returns {HTMLElement | undefined}
     */
    function iframe$(selectors) {
        if (iframe instanceof HTMLIFrameElement) {
            return iframe.contentDocument.querySelector(selectors);
        }

        const _iframe = document.querySelector("iframe");
        if (_iframe) {
            if (!iframe) {
                iframe = _iframe;
            }
            return iframe.contentDocument.querySelector(selectors);
        }
    }


    /**
     * 请求第 pn 页 PDF
     * @param {number} pn 
     * @returns {Promise<undefined | Uint8Array>}
     */
    async function get_data_by_pn(pn) {
        const result = await window[API](pn);
        count();  // 更新进度

        if (!result.data) {
            print(`第 ${pn} 页请求得到空响应`);
            return;
        }
        if (!result.success) {
            print(`第 ${pn} 页请求失败`);
            return;
        }

        return utils.b64_to_bytes(result.data);
    }


    /**
     * 下载并返回全部 PDF 数据
     * @param {number} max_pn
     * @returns {Promise<Array<Uint8Array>>} 
     */
    async function fetch_pdfs(max_pn) {
        // 准备请求
        reset_counter();
        set_text(`已下载 {}/${max_pn} 页`);
        print(`最大页码:${max_pn}`);
        print("开始下载 PDF");
        const tasks = [];
        
        // 请求数据
        for (let i = 0; i < max_pn; i++) {
            tasks[i] = get_data_by_pn(i);
        }

        // 完成请求完成
        const pdfs = await utils.gather(tasks);
        reset_counter();
        print("全部 PDF 下载完成");
        return pdfs;
    }


    /**
     * 合并全部 PDF 为一个
     * @param {Array<Uint8Array>} pdfs 
     * @returns {Promise<Uint8Array>}
     */
    async function merge_pdfs(pdfs) {
        reset_counter();
        set_text(`已合并 {}/${pdfs.length} 页`);
        print("开始合并 PDF");
        
        const combined = await utils.join_pdfs(pdfs, count);
        reset_counter();
        print("全部 PDF 合并完成");
        return combined;
    }


    async function make_pdf() {
        // 确认继续
        if (!confirm("开始下载后会导致文档预览消失,是否继续?")) {
            return;
        }

        // 环境检测
        if (!window.PDFLib) {
            utils.raise("window.PDFLib 不存在,无法生成 PDF");
        }

        if (!window[API] || !(window[API] instanceof Function)) {
            utils.raise(`window.${API} 不存在,无法请求 PDF 数据`);
        }

        const title = iframe$("title").textContent;
        const max_pn = parseInt(iframe$("#pageNumber").max);
        if (max_pn < 1) {
            utils.raise(`不正常的最大页码:${max_pn}`);
        }

        // 移除PDF预览
        iframe.remove();

        const pdfs = await fetch_pdfs(max_pn);
        const size = pdfs.map(bytes => bytes.length).reduce((sum, len) => sum + len);

        // 大于 100 MB 的文件下载原始数据
        if (size > 100 * 1024 * 1024) {
            alert("超过 100 MB,只能下载原始数据集,请用 pdfs-merger 转换为 PDF");
            const type = "application/octet-stream";
            const blob = new Blob(pdfs, { type });
            utils.save(`${title}.dat`, blob, type);
        }
        // 小于的下载PDF
        else {
            const merged_pdf = await merge_pdfs(pdfs);
            utils.save(`${title}.pdf`, merged_pdf, "application/pdf");
        }
    }


    /**
     * @param {(event: PointerEvent) => Promise<void>} btn_fn 
     * @returns {(event: PointerEvent) => Promise<void>}
     */
    function wrap_btn_fn(btn_fn) {
        async function inner(event) {
            const btn = event.target;
            const text = btn.textContent;
            btn.disabled = true;
            try {
                await btn_fn(event);
            } catch(err) {
                console.error(err);
            }
            btn.textContent = text;
            btn.disabled = false;
        }
        return inner;
    }


    /**
     * 合肥工业大学图书馆-纸电一体化读者服务平台-文档下载策略
     */
    function zhitongda() {
        GM_xmlhttpRequest({
            method: "GET",
            url: PDF_LIB_URL,
            onload: function(resp) {
                const code = resp.responseText;
                Function(code)();
                print("pdf-lib 加载完成");
            }
        });
        utils.create_btns();
        utils.onclick(wrap_btn_fn(make_pdf), DL_BTN, "下载PDF");
    }
    

    zhitongda();


    /**
     * 更新日志
     * ---
     * (v0.0.1) [2023-08-17]
     * - 发布初版
     */
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址