知网下载助手

知网学术下载解析工具,免登录即可下载,完全免费使用。

// ==UserScript==
// @name         知网下载助手
// @namespace    cnki
// @version      0.0.4
// @antifeature  membership
// @description  知网学术下载解析工具,免登录即可下载,完全免费使用。
// @author       千千软件
// @icon         https://img03.mifile.cn/v1/MI_542ED8B1722DC/8a3d67192d85999be1bc1cda5c4d3528.png
// @match        https://*.cnki.net/kcms2/*
// @require      https://lib.baomitu.com/sweetalert/2.1.2/sweetalert.min.js
// @require      https://lib.baomitu.com/clipboard.js/2.0.6/clipboard.min.js
// @run-at       document-idle
// @grant        unsafeWindow
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_openInTab
// @grant        GM_notification
// @grant        GM_xmlhttpRequest
// @connect      localhost
// @connect      127.0.0.1
// @connect      qianqian.club
// @connect      cnki.net
// @connect      baidu.com
// ==/UserScript==

(function () {
    'use strict';

    let globalData = {
        scriptVersion: '0.0.4',
        downloading: 0,
        storageNamePrefix: 'qq_storageName', // 本地储存名称前缀
    }

    let getAppSettingData = function () {
        return {
            scriptVersion: globalData.scriptVersion,
            apiUrl: `/cnki.php`,
            storageNamePrefix: globalData.storageNamePrefix,
        }
    }

    let getConfig = function () {
        return {
            code: getStorage.getLastUse('code'),
        }
    }
    let getStorage = {
        getAppConfig: (key) => {
            return GM_getValue(getAppSettingData().storageNamePrefix + '_app_' + key) || '';
        },
        setAppConfig: (key, value) => {
            GM_setValue(getAppSettingData().storageNamePrefix + '_app_' + key, value || '');
        },
        getLastUse: (key) => {
            return GM_getValue(getAppSettingData().storageNamePrefix + '_last_' + key) || '';
        },
        setLastUse: (key, value) => {
            GM_setValue(getAppSettingData().storageNamePrefix + '_last_' + key, value || '');
        },
        getCommonValue: (key) => {
            return GM_getValue(getAppSettingData().storageNamePrefix + '_common_' + key) || '';
        },
        setCommonValue: (key, value) => {
            GM_setValue(getAppSettingData().storageNamePrefix + '_common_' + key, value || '');
        }
    }

    //下载面板
    let showDownloadDialog = function (response) {
        let content = `
            <div id="downloadDialog">
                <div id="dialogTop">
                    <div id="dialogErr">${response.err}</div>
                </div>
                <div id="dialogMiddle">
                    <div id="dialogLeft">
                        <div id="dialogQr">
                            <img id="dialogQrImg" src="https://img06.mifile.cn/v1/MI_542ED8B1722DC/118fd536b55197b20597d81d5974159f.jpg" />
                        </div>
                    </div>
                    <div id="dialogRight">
                        <div id="dialogContent">
                            <div id="dialogVaptchaCode">
                                <div id="dialogVaptchaCodeInput">
                                    <span id="dialogVaptchaCodeTips">填写验证码:</span>
                                    <input id="dialogCode" type="text" value="${getConfig().code}" />
                                </div>
                                <div id="dialogCodeRemark"></div>
                            </div>
                            <input id="dialogBtnGetUrl" type="button" value="点击继续下载" class="btnInterface" />
                        </div>
                    </div>
                </div>
                <div id="dialogClear"></div>
                <div id="dialogBottom"></div><!--众所周知的原因,脚本不可能常在,但作者常在,关注才能不迷路!!!-->
            </div>
        `;
        showSwal(content, {
            button: '关 闭',
            closeOnClickOutside: false
        });
    }

    //请求直链成功后,tips
    let showQrTips = function (res) {
        let qrImg = $.trim(res.qrImg);
        let qrTips = $.trim(res.qrTips);
        let codeTips = $.trim(res.codeTips);
        let codeRemark = $.trim(res.codeRemark);
        //console.log(qrImg, qrTips);
        if (qrImg.length > 0) {
            $("#dialogQrImg").attr('src', qrImg);
        }
        if (qrTips.length > 0) {
            // $("#dialogBottom").html(qrTips);
        }
        if (codeTips.length > 0) {
            $("#dialogVaptchaCodeTips").html(codeTips).show();
        }
        if (codeRemark.length > 0) {
            $("#dialogCodeRemark").html(codeRemark).show();
        }
    }

    //存储验证码
    let saveCode = function () {
        getStorage.setLastUse('code', $("#dialogCode").val());
    }

    //=========================================

    //查询接口地址-->发起服务器请求
    let getDownloadUrl = function (dlType) {
        if (globalData.downloading === 1) {
            return false;
        }
        setDownloadingState(dlType, '正在查询接口地址...');
        let bdUrl = "https://pan.baidu.com/pcloud/user/getinfo?query_uk=1070525283";
        let details = {
            method: 'GET',
            timeout: 10000, // 10秒超时
            url: bdUrl + '&' + new Date().getTime(),
            responseType: 'json',
            onload: function (res) {
                // console.log(res);
                if (res.status === 200) {
                    if (res.response.errno == 0) {
                        let ifDomain = res.response.user_info.intro;
                        // console.log(ifDomain);
                        // ifDomain = 'http://localhost:48818/cnki'
                        getDownloadUrlReal(dlType, ifDomain);
                    } else {
                        throw res;
                    }
                } else {
                    throw res;
                }
            }
        };
        try {
            GM_xmlhttpRequest(details);
        } catch (error) {
            console.error(error);
            setDownloadCompleteState();
            showTipError('获取接口地址时报错:' + error.message)
        }
    }

    let getDownloadUrlReal = function (dlType, ifDomain) {
        setDownloadingState(dlType, '正在请求下载地址...');
        let downloadUrl = `${ifDomain}${getAppSettingData().apiUrl}?version=${getAppSettingData().scriptVersion}&t=8888` + new Date().getTime();
        // let code = $('#dialogCode').val() || getConfig().code;
        let code = getConfig().code;
        let fileUrl = window.location.href;
        let fileName = $('.wx-tit h1').text();
        let dlUrl = $('#' + dlType + 'Down').attr('href');
        //官网“学位论文”栏目的bug,两个按钮的id都是cajDown,暂时就这样来修复
        if (!dlUrl && dlType == 'pdf') {
            let pdfBtn = $('li.btn-dlpdf>a').first();
            if (pdfBtn) {
                dlUrl = pdfBtn.attr('href');
            }
        }

        let params = new FormData();
        params.append('code', $.trim(code));
        params.append('fileUrl', fileUrl);
        params.append('fileName', fileName);
        params.append('dlUrl', dlUrl);
        params.append('dlType', dlType);
        //远程请求下载地址
        let details = {
            method: 'POST',
            responseType: 'json',
            timeout: 30000, // 30秒超时
            url: downloadUrl,
            data: params,
            onload: function (res) {
                // params.forEach((value, key) => {
                //      console.log("%s --> %s", key, value);
                // })
                setDownloadCompleteState();
                console.log('远程请求直链地址,返回:', res);
                if (res.status === 200) {
                    switch (res.response.errno) {
                        case 0: // 正常返回
                            console.log('正常返回');
                            downFile(res.response.downloadUrl);
                            break;
                        case 100: // 版本太旧
                            showTipErrorSwal(res.response.err);
                            break;
                        case 101: // 验证码不正确
                            showDownloadDialog(res.response);
                            showQrTips(res.response);
                            //绑定按钮点击(点击继续下载)
                            $("#dialogBtnGetUrl").click(function () {
                                saveCode();
                                swal.close();
                                getDownloadUrl();
                            });
                            break;
                        default: // 其它错误
                            showTipError(res.response.err);
                            break;
                    }
                } else {
                    console.error(res);
                    showTipError('请求下载地址失败!服务器返回:' + res.status);
                }
            },
            ontimeout: (res) => {
                console.error(res);
                setDownloadCompleteState();
                showTipError('请求下载地址时连接服务器接口超时,请重试!');
            },
            onerror: (res) => {
                console.error(res);
                setDownloadCompleteState();
                showTipError('请求下载地址时连接服务器接口出错,请重试!');
            }
        };
        try {
            GM_xmlhttpRequest(details);
        } catch (error) {
            showTipError('远程请求未知错误,请重试!');
            setDownloadCompleteState();
            console.error(error);
        }
    }

    let setDownloadingState = function (dlType, tips) {
        globalData.downloading = 1;
        $('#' + dlType + 'BtnName').text(tips);
    }
    let setDownloadCompleteState = function () {
        globalData.downloading = 0;
        $('#pdfBtnName').text('知网助手下载PDF');
        $('#cajBtnName').text('知网助手下载CAJ');
    }

    let showTipErrorSwal = function (err) {
        showSwal(err, {icon: 'error'});
    }

    let showSwal = function (content, option) {
        divTips.innerHTML = content;
        option.content = divTips;
        if (!option.hasOwnProperty('button')) {
            option.button = '朕 知 道 了'
        }
        swal(option);
    }

    let showTipError = function (err) {
        // showSwal(err,{icon: 'error'});
        alert(err);
    }

    //========================================= 公共函数

    let downFile = function (url) {
        const a = document.createElement('a');
        a.style.display = 'none';
        a.href = url;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
    };

    // 延迟执行,否则找不到对应的按钮
    let sleep = function (time) {
        return new Promise((resolve) => setTimeout(resolve, time));
    };

    //========================================= css
    GM_addStyle(`
        .operate-btn .btn-dl-helper a {
            background-color: #ff6988
        }
        .operate-btn .btn-dl-helper a:hover {
            background-color: #dd2014
        }
        .operate-btn .btn-dlpdf-helper i {
            background-position: -75px 0
        }
        .operate-btn .btn-dlcaj-helper i {
            background-position: -50px 0
        }
        .operate-btn li .btn-dl-helper i {
            display: inline-block;
            vertical-align: middle;
            width: 25px;
            height: 25px;
            background-image: url(https://piccache.cnki.net/nxgp/kcms/2023121114r/images/gb/kcms8/ndetail/icon-dllinkbtns3.png);
            background-repeat: no-repeat;
            *height: 20px;
        }
        .operate-left {
            padding-right: 0px;
        }
        a.aDownHelper {
            font-size: 16px;
            font-weight: bold;
        }
        
        /****************************************/
        .swal-modal {
            width: auto;
            min-width: 730px;
        }
        .swal-modal input {
            border: 1px grey solid;
        }
        #downloadDialog{
            width: 730px;
            font-size: 16px;
        }

        #dialogTop{
            margin: 20px 0;
        }
        #dialogErr{
            background: #f4c758;
            margin: 15px -20px 0 -20px;
            padding: 10px 0 10px 25px;
            color: #4c4433;
        }
        #dialogLeftTips{
            text-align: left;
            margin: 0 0 10px 0px;
            color: #4c4433;
            font-size: 13px;
        }
        #dialogRight{
            width: 56%;
            float: left;
            margin-left: 10px;
        }
        #dialogContent input{
            vertical-align: middle;
        }
        #dialogRemark{
            text-align: left;
            font-size: 12px;
            margin-top: 5px;
        }
        #dialogVaptchaCode{
            text-align: left;
            font-size: 14px;
            /* border: 2px solid #EDD; */
            line-height: 28px;
        }
        #dialogVaptchaCodeInput{
            font-size: 18px;
        }
        #dialogCode{
            width: 65%;
            font-size: 18px;
            padding: 3px;
        }
        #dialogCodeRemark{
            margin: 10px 0px;
            padding: 3px;
        }
        #dialogQr{
            width: 265px;
            height: 265px;
            text-align: center;
        }
        #dialogQr img{
            width: 100%;
            margin-left: 27px;
        }
        #dialogClear{
            clear: both;
        }
        #dialogBottom{
            text-align: left;
            margin: 20px -20px -5px -20px;
            background: #f4c758;
            color: #4c4433;
            height: 1px;
        }
        .btnInterface {
            width: 100%;
            height: 50px;
            background: #f00 !important;
            border-radius: 4px;
            transition: .3s;
            font-size: 25px !important;
            border: 0;
            color: #fff;
            cursor: pointer;
            text-decoration: none;
            font-family: Microsoft YaHei,SimHei,Tahoma;
            font-weight: 100;
            letter-spacing: 2px;
        }
        #dialogLeft{
            float: left;
            width: 42%;
        }
        .swal-footer{
            margin-top: 5px;
        }
        .div-dl-helper{
            margin-bottom: 10px;
            text-align: center;
        }
    `);
    // ==================================== 逻辑代码开始
    console.log('脚本开始');

    const divTips = document.createElement('div');
    divTips.id = "divTips";//用于提示脚本版本

    let btnDownload = {
        id: 'divCnkiHelper',
        html: function () {
            return `
                <li id="liPdfBtn" title="使用知网下载助手进行下载" class="btn-dl-helper btn-dlpdf-helper" style="margin-right: 5px;">
                    <a id="pdfDownHelper" class="aDownHelper" href="#">
                        <i></i>
                        <text id="pdfBtnName"></text>
                    </a>
                </li>
                <li id="liCajBtn" title="使用知网下载助手进行下载" class="btn-dl-helper btn-dlcaj-helper">
                    <a id="cajDownHelper" class="aDownHelper" href="#">
                        <i></i>
                        <text id="cajBtnName"></text>
                    </a>
                </li>
            `
        },
        class: function () {
            return 'div-dl-helper';
        }
    }

    let start = function () {//迭代调用
        if (!$('#pdfDown')[0]) {
            if (!$('li.btn-dlpdf>a').first()) {
                //官网“学位论文”栏目的bug,两个按钮的id都是cajDown,暂时就这样来修复
                console.log('找不到【PDF下载】按钮,该文档不可下载!');
                return;
            }
        }
        if (!$('#cajDown')[0]) {
            console.log('找不到【CAJ下载】按钮!');
            // return;
        }
        let operateBtn = $('ul.operate-btn');

        // 创建按钮 START
        let divCnkiHelper = document.createElement('div');
        divCnkiHelper.id = btnDownload.id;
        divCnkiHelper.innerHTML = btnDownload.html();
        // divCnkiHelper.style.cssText = btnDownload.style();
        divCnkiHelper.className = btnDownload.class();
        // 创建按钮 END

        // 添加按钮 START
        operateBtn.children().first().before(divCnkiHelper);
        if (!$('#cajDown')[0]) $('#liCajBtn').hide();//如果无caj下载按钮
        setDownloadCompleteState();
        // 添加按钮 END

        //绑定点击事件
        $('.aDownHelper').click(function (e) {
            let dlType = $(this).attr('id').slice(0, 3);
            console.log('#' + dlType + 'DownHelper 点击事件');
            e.preventDefault();
            // getDownloadUrlReal();
            getDownloadUrl(dlType);
        });
    }

    sleep(500).then(() => {
        start();
        // GM_deleteValue('qq_storageName_last_code');//测试
    })
})();
//###########################################