NSFC conclusion downloader

帮助你直接下载国自然结题报告(userscript版)

目前为 2021-02-02 提交的版本。查看 最新版本

// ==UserScript==
// @name         NSFC conclusion downloader 
// @namespace    https://blog.rhilip.info/
// @version      0.1
// @description  帮助你直接下载国自然结题报告(userscript版)
// @author       Rhilip
// @match        http://output.nsfc.gov.cn/conclusionProject/*
// @require      https://unpkg.com/jspdf@latest/dist/jspdf.umd.min.js
// @require      https://unpkg.com/axios/dist/axios.min.js
// ==/UserScript==

(function() {
    'use strict';
    // 从 jspdf 库中获得jsPDF
    const { jsPDF } = jspdf

    function asyncImageLoader(url){
        return new Promise((resolve, reject) => {
            var image = new Image()
            image.src = url
            image.onload = () => resolve(image)
            image.onerror = () => reject(new Error('could not load image'))
        })
    }

    function cleanFilename(filename) {
        return filename.replace(/[:\/]/ig, '_').replace(/\x00/ig,'_').replace(/[\n\\\*><?\"|\t]/ig,'')
    }

    /**
     * @param obj - {
     *  sel: 'a',                    // the selector you want to wait for (optional)
     *  context: document.body,      // scope of search for selector or mutations (optional, default document.body)
     *  stop: true,                  // stop waiting after first result (optional, default false)
     *  mode: 'M',                   // M to use mutation observer, S to use setInterval (optional, default M)
     *  onchange: func,              // if using mode 'M' this function will be called whenever mutation handler triggers
     *  onmatch: func,               // if selector is specified function will be called for each match with element as parameter
     *  config: { attributes: true } // if using mode 'M' this object will override settings passed to mutation observer
     * }
     */
    function waitForElems(obj) {
        var tick;
        var id = 'fke' + Math.floor(Math.random() * 12345);
        var type = window.MutationObserver ? (obj.mode || 'M') : 'S';
        var lastMutation = Date.now();
        var lastCall = Date.now();
        var context = obj.context || document.body;
        var sel = obj.sel;
        var config = obj.config || {
            subtree: true,
            childList: true
        };
        var onChange = obj.onchange;
        var queuedCall;

        function throttle(func) {
            var now = Date.now();
            clearTimeout(queuedCall);
            // less than 100ms since last mutation
            if (now - lastMutation < 100) {
                // 500ms or more since last query
                if (now - lastCall >= 500) {
                    func();
                } else {
                    queuedCall = setTimeout(func, 100);
                }
            } else {
                func();
            }
            lastMutation = now;
        }

        function findElem(sel) {
            lastCall = Date.now();
            var found = [].filter.call(context.querySelectorAll(sel), function(elem) {
                return elem.dataset[id] !== 'y';
            });
            if (found.length > 0) {
                if (obj.stop) {
                    type === 'M' ? tick.disconnect() : clearInterval(tick);
                }
                found.forEach(function(elem) {
                    elem.dataset[id] = 'y';
                    obj.onmatch(elem);
                });
            }
        }
        if (type === 'M') {
            tick = new MutationObserver(function() {
                if (sel) throttle.call(null, findElem.bind(null, sel));
                if (onChange) onChange.apply(this, arguments);
            });
            tick.observe(context, config);
        } else {
            tick = setInterval(findElem.bind(null, sel), 300);
        }
        if (sel) findElem(sel);
        return {
            type: type,
            stop: function() {
                if (type === 'M') {
                    tick.disconnect();
                } else {
                    clearInterval(tick);
                }
            },
            resume: function() {
                if (type === 'M') {
                    tick.observe(context, config);
                } else {
                    tick = setInterval(findElem.bind(null, sel), 300);
                }
            }
        };
    }

    // 由于页面是动态加载的,所以我们需要等我们需要的元素加载出来再添加交互逻辑
    waitForElems({
        sel: '#conclusion-report-tab > h3',
        onmatch: () => {
            $('#conclusion-report-tab > h3').before('<button id="download_report" type="button" class="btn btn-link pull-right">(下载全文PDF)</button>')
            const downloadBtn = $('#download_report')
            downloadBtn.click((async () => {
                downloadBtn.prop('disabled', true)
                // 获得项目信息: 编号(加密后)、批准号、项目名称
                const dependUintID = location.pathname.match(/conclusionProject\/(.+)/)[1]
                const projectID = $('#basic-tab > div:nth-child(1) > div.col-md-10').text()
                const projectName = $('#basic-tab > div:nth-child(2) > div.col-md-10').text()

                // 准备 jsPDF 对象
                const doc = new jsPDF();
                doc.deletePage(1);   // 删除第一页

                let i = 1;
                let should_loop = true;
                while (should_loop) {
                    downloadBtn.text(`正在下载第 ${i} 页`)
                    const postData = new URLSearchParams({id: dependUintID, index: i});
                    const req = await axios.post('http://output.nsfc.gov.cn/baseQuery/data/completeProjectReport', postData);
                    const image = await asyncImageLoader(req.data.data.url)
                    doc.addPage([image.width, image.height], image.width < image.height ? 'p' : 'l');
                    doc.addImage(image, "JPEG", 0, 0, image.width, image.height);

                    should_loop = req.data.data.hasnext !== false;
                    i++;
                }

                downloadBtn.text('正在生成PDF')
                doc.save(`${projectID} ${projectName}.pdf`)

                downloadBtn.text('下载完成')
                downloadBtn.prop('disabled', false)
            }))
        }
    })
})();

QingJ © 2025

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