Greasy Fork is available in English.

中国知网CNKI硕博论文PDF下载

知网文献、硕博论文PDF批量下载,下载论文章节目录,阅读页面体验增强

// ==UserScript==
// @name         中国知网CNKI硕博论文PDF下载
// @version      3.0
// @namespace    https://greasyfork.org/users/244539
// @icon         https://www.cnki.net/favicon.ico
// @description  知网文献、硕博论文PDF批量下载,下载论文章节目录,阅读页面体验增强
// @author       爱与自由
// @match        http*://*.cnki.net
// @match        http*://*/kcms/detail/detail.aspx?*dbcode=*
// @match        http*://*/*/*/kcms/detail/detail.aspx?*dbcode=*
// @match        http*://*/*/*/kcms/detail*
// @match        http*://*/https/*/kcms*
// @match        http*://*/KCMS/detail/detail.aspx?*dbcode=*
// @match        http*://*/kns*/defaultresult/index*
// @match        http*://*/https/*/kns8/defaultresult/index
// @match        http*://*/https/*/kns8/DefaultResult/Index*
// @match        http*://*/kns8s/DefaultResult/Index
// @match        http*://*/https/*/kcms/detail/detail.aspx?
// @match        http*://*/KNS8/AdvSearch?*
// @match        http*://*/kns8/AdvSearch?*
// @match        http*://kns.cnki.net/kns8s/AdvSearch?crossids*
// @match        http*://kns.cnki.net/kns8s/AdvSearch?classid*
// @match        http*://kns.cnki.net/kns/advsearch?dbcode*
// @match        http*://*.*.*.*:*/kns8s/search
// @match        http*://*/kns/brief/*result*
// @match        http*://*/kcms/Detail/DownDetail.aspx*
// @match        http*://*/KNS8/DefaultResult/index*
// @match        http*://*/kns8/DefaultResult/Index*
// @match        http*://*/kns8/defaultresult/index*
// @match        http*://*/https/*/KNS8/DefaultResult/*
// @match        http*://*/kcms2/article/abstract?*
// @match        *://kns.cnki.net/KXReader/Detail?*
// @match        *://new.oversea.cnki.net/KXReader/Detail?*
// @match        *://new.big5.oversea.cnki.net/KXReader/Detail?*
// @match        *://new.gb.oversea.cnki.net/KXReader/Detail?*
// @match        *://*/KXReader/Detail?*
// @run-at       document-idle
// @grant        unsafeWindow
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const $ = unsafeWindow.jQuery;

    function loadCss(code) {
        const style = document.createElement('style');
        style.textContent = code;
        document.head.appendChild(style);
    }

    function createLoading(text, duration) {
        const loadingContent = document.createElement('div');
        loadingContent.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%, -50%);text-align:center;color:#333;font-size:16px;width:400px;height:40px;line-height:40px;z-index:9999;border:1px solid #0f5de5;padding:5px;background-color:#fff';
        loadingContent.textContent = text;
        document.body.appendChild(loadingContent);
        setTimeout(() => document.body.removeChild(loadingContent), duration);
        return loadingContent;
    }

    function createPopupButton() {
        const popupButton = document.createElement('button');
        popupButton.textContent = '批量下载PDF';
        popupButton.style.cssText = 'position:fixed;right:50px;top:50%;transform:translateY(-50%);z-index:9999;padding:10px;background-color:#0f5de5;color:#fff;border:none;cursor:pointer;border-radius:5px;';
        popupButton.addEventListener('click', createPopup);
        document.body.appendChild(popupButton);
    }

    function createPopup() {
        if (document.getElementById('popup')) return;

        const popup = document.createElement('div');
        popup.id = 'popup';
        popup.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,0.5);z-index:9999;';
        const content = document.createElement('div');
        content.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:80%;max-height:80%;overflow-y:auto;background-color:#fff;padding:20px;border-radius:10px;box-shadow:0 0 10px rgba(0,0,0,0.5);min-height:200px';

        const closeButton = createButton('关闭', () => document.body.removeChild(popup));
        closeButton.style.float = 'right';
        const linkButton = createButton('获取链接', getLinks);
        const selectAllButton = createButton('全选', selectAll);
        const deselectAllButton = createButton('取消全选', deselectAll);
        const downloadButton = createButton('下载', downloadSelected);
        downloadButton.title = "点击此处下载选中的文件";
        downloadButton.id = "dl_sel";
        const tooltip = document.getElementById('tooltip');

        function showTooltip(e) {
            tooltip.style.display = 'block';
            tooltip.style.left = e.pageX + 10 + 'px';
            tooltip.style.top = e.pageY + 10 + 'px';
        }

        function hideTooltip() {
            tooltip.style.display = 'none';
        }

        downloadButton.addEventListener('mouseenter', showTooltip);
        downloadButton.addEventListener('mousemove', showTooltip);
        downloadButton.addEventListener('mouseleave', hideTooltip);

        // const selectCount = document.createElement('span');
        // selectCount.textContent = '已选择: 0';
        // selectCount.className = 'diy-btn';

        const tips = createTips('获取链接后请不要关闭窗口,否则链接会消失!', 'red');
        const tips2 = createTips('如果获取失败或超时,请关闭窗口重新获取。', '#0b1f64');

        content.append(closeButton, linkButton, selectAllButton, deselectAllButton, downloadButton, tips, tips2);

        const table = document.createElement('table');
        table.id = 'my-table';
        table.style.cssText = 'width:100%;margin-top:10px;border-collapse:collapse;';
        table.innerHTML = '<thead><tr><th style="width:3%">多选</th><th style="width:3%">序号</th><th style="width:60%">名称</th><th style="width:7%">链接</th><th>关键词</th></tr></thead><tbody></tbody>';

        content.appendChild(table);
        popup.appendChild(content);
        document.body.appendChild(popup);
    }

    function createButton(text, onClick) {
        const button = document.createElement('button');
        button.textContent = text;
        button.className = 'diy-btn';
        button.addEventListener('click', onClick);
        return button;
    }

    function createTips(text, color) {
        const tips = document.createElement('span');
        tips.textContent = text;
        tips.className = 'diy-btn';
        tips.style.color = color;
        return tips;
    }

    function selectAll() {
        document.querySelectorAll('.selectItem').forEach(item => item.checked = true);
        updateSelectCount();
    }

    function deselectAll() {
        document.querySelectorAll('.selectItem').forEach(item => item.checked = false);
        updateSelectCount();
    }

    function downloadSelected() {
        const selectedItems = document.querySelectorAll('.selectItem:checked');
        if (selectedItems.length === 0) {
            createLoading('请选择要下载的项目!', 2000);
            return;
        }
        selectedItems.forEach(item => {
            const link = item.closest('tr').querySelector('a[href^="http"]');
            if (link) {
                window.open(link.href);
            };
        });
    }



    function updateSelectCount() {
        const count = document.querySelectorAll('.selectItem:checked').length;
        document.querySelector('#dl_sel').textContent = `下载 (${count})`;
    }

    async function getLinks() {
        const table = document.querySelector('#my-table tbody');
        if (table.children.length > 0) {
            createLoading('已有数据!关闭弹窗后打开可重新获取!', 2000);
            return;
        }

        const links = Array.from(document.querySelectorAll('.fz14')).map(link => link.href);
        const loading = createLoading('获取链接中(请耐心等待)……', 60000);

        try {
            const batchSize = 5; // 每批处理的链接数
            for (let i = 0; i < links.length; i += batchSize) {
                const batch = links.slice(i, i + batchSize);
                const responses = await Promise.all(batch.map(link =>
                    fetch(link, {
                        method: 'GET',
                        mode: 'same-origin',
                        referrerPolicy: 'unsafe-url',
                    }).then(response => response.text())
                ));

                responses.forEach((html, index) => {
                    const doc = new DOMParser().parseFromString(html, 'text/html');
                    const title = doc.querySelector('.wx-tit')?.firstElementChild?.textContent || '无标题';
                    const author = Array.from(doc.querySelectorAll('.author')).map(a => a.textContent).join(' ');
                    const pdfLink = doc.querySelector('#pdfDown')?.href || '无链接';
                    const keywords = Array.from(doc.querySelectorAll('.keywords a')).map(keywords => `<span class="diy-btn" style="font-size:12px">${keywords.textContent.replace(";","")}</span>`).join('');

                    const row = table.insertRow();
                    row.innerHTML = `
                        <td><input type="checkbox" class="selectItem"></td>
                        <td style="text-align:center">${i + index + 1}</td>
                        <td><span class="diy_title">${title}</span><span class="diy_author">${author}</span></td>
                        <td>${pdfLink !== '无链接' ? `<a href="${pdfLink}" target="_blank" class="diy-btn">PDF下载</a>` : '获取链接失败!'}</td>
                        <td>${keywords}</td>
                    `;

                    row.querySelector('input[type="checkbox"]').addEventListener('change', updateSelectCount);
                });

                // 更新加载提示
                loading.textContent = `正在获取链接...(${Math.min((i + batchSize) / links.length * 100, 100).toFixed(0)}%)`;
            }

            document.body.removeChild(loading);
            createLoading('获取完毕!', 2000);
        } catch (error) {
            console.error('Error fetching links:', error);
            document.body.removeChild(loading);
            createLoading(`获取链接出错:${error.message}`, 3000);
        }
    }

    // 目录下载功能
    function addCategoryDownloadButton() {
        const otherBtns = document.querySelector('.other-btns');
        if (!otherBtns) return;

        const li = document.createElement('li');
        li.className = 'btn-diy';
        li.style.cssText = 'width:auto;height:23px;line-height:22px;background-color:#3f8af0;border-radius:3px;';

        const a = document.createElement('a');
        a.textContent = '目录下载';
        a.className = 'a-diy';
        a.style.cssText = 'color:#ffffff;padding:2px 10px;';
        a.href = 'javascript:void(0)';
        a.addEventListener('click', downloadCategory);

        li.appendChild(a);
        otherBtns.appendChild(li);
    }

    async function downloadCategory() {
        const hrefLink = document.querySelector('.operate-btn li:nth-child(4) a')?.href;
        if (!hrefLink) {
            createLoading('无法获取目录链接', 2000);
            return;
        }

        try {
            const response = await fetch(hrefLink);
            const html = await response.text();
            const doc = new DOMParser().parseFromString(html, 'text/html');
            const title = doc.querySelector('.title')?.textContent || '未知标题';
            const chapters = Array.from(doc.querySelectorAll('.ls-chapters li'))
                .map(chapter => chapter.textContent.trim())
                .filter(Boolean)
                .map(text => {
                    const parts = text.split('-');
                    return `${parts[0].replace(/\n/g, '\t')} \n`;
                })
                .join('');
console.log(chapters);
            saveFile(title, chapters);
        } catch (error) {
            console.error('Error downloading category:', error);
            createLoading('下载目录失败', 2000);
        }
    }

    function saveFile(name, data) {
        const blob = new Blob([data], { type: 'text/plain' });
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = `${name}.txt`;
        link.click();
        URL.revokeObjectURL(link.href);
    }

    // 阅读模式调整功能
    function addReadingModeAdjustment() {
        const backtop = document.querySelector('.backtop');
        if (!backtop) return;

        const fontSizeIncrease = createReadingModeButton('增大字体', '字✚', () => changeFontSize('increase'));
        fontSizeIncrease.style.bottom = '-60px';
        const fontSizeDecrease = createReadingModeButton('减小字体', '字━', () => changeFontSize('decrease'));
        fontSizeDecrease.style.bottom = '-30px';

        const protectEyesSelect = document.createElement('select');
        protectEyesSelect.innerHTML = `
            <option value="none" selected>护眼模式</option>
            <option value="#FFFFFF">银河白</option>
            <option value="#FAF9DE">杏仁黄</option>
            <option value="#FFF2E2">秋叶褐</option>
            <option value="#FDE6E0">胭脂红</option>
            <option value="#E3EDCD">青草绿</option>
            <option value="#DCE2F1">海天蓝</option>
            <option value="#E9EBFE">葛巾紫</option>
            <option value="#EAEAEF">极光灰</option>
        `;
        protectEyesSelect.style.cssText = 'width:36px;font-size:12px;';
        protectEyesSelect.id = 'protect_eyes_select';
        protectEyesSelect.addEventListener('change', function() { changeMode(this.value); });

        backtop.append(protectEyesSelect, fontSizeIncrease, fontSizeDecrease);
        changeModeAuto();
    }

    function createReadingModeButton(title, text, action) {
        const button = document.createElement('span');
        button.title = title;
        button.textContent = text;
        button.className = 'font-size-button';
        button.addEventListener('click', action);
        return button;
    }

    function changeFontSize(action) {
        const main = document.querySelector('.main');
        if (!main) return;

        const currentSize = parseFloat(window.getComputedStyle(main).fontSize);
        const newSize = action === 'increase' ? currentSize + 1 : currentSize - 1;
        main.style.fontSize = `${newSize}px`;

        main.querySelectorAll('.p1').forEach(p => p.style.fontSize = `${newSize}px`);
    }

    function changeMode(color) {
        localStorage.bgc = color;
        document.body.style.backgroundColor = color;
        document.querySelectorAll('.ecp_top-nav, .content, .tips, .main, .refer, .brief').forEach(el => el.style.backgroundColor = color);
        document.querySelectorAll('dl, p').forEach(el => el.style.backgroundColor = color);
    }

    function changeModeAuto() {
        if (localStorage.bgc) {
            changeMode(localStorage.bgc);
            const select = document.getElementById('protect_eyes_select');
            if (select) {
                const option = Array.from(select.options).find(option => option.value === localStorage.bgc);
                if (option) option.selected = true;
            }
        }
    }


// 初始化函数
function init() {
    const url = window.location.href.toLowerCase();
    console.log(url);

    // 在所有页面上添加批量下载按钮
    // createPopupButton();
    if (url.includes('defaultresult') || url.includes('advsearch')) {
        createPopupButton();
    }

    // 在文献详情页面添加目录下载按钮和阅读模式调整功能
    if (url.includes('abstract') || url.includes('detail.aspx') || url.includes('detail') || url.includes('read')) {
        addCategoryDownloadButton();
        addReadingModeAdjustment();
    }

    // 加载自定义CSS
    loadCss(`
        .diy-btn {
            display: inline-block;
            vertical-align: middle;
            padding: 2px 8px;
            margin-left: 5px;
            line-height: 18px;
            color: #0f5de5;
            font-size: 14px;
            text-align: center;
            background-color: #e3ecfd;
            border: 1px solid #fff;
            cursor: pointer;
        }
        #popup table tr td,
        #popup table tr th {
            line-height: 20px;
            height: 20px;
            padding: 5px;
            border: 1px solid #eee;
        }
        .diy_title {
            display: block;
            color: #524d4d;
            font-size: 14px;
            font-weight: bold;
        }
        .diy_author {
            color: #666;
        }
        .font-size-button {
            font-size: 14px;
            display: block;
            line-height: 18px;
            border: 1px solid #e2e2e2;
            border-radius: 2px;
            background-color: #f5f5f5;
            color: #504f4f;
            float: left;
            padding: 3px;
            position: absolute;
            right: 0;
            width: 28px;
            cursor: pointer;
        }
    `);
}

// 在页面加载完成后运行初始化函数

    // 在页面加载完成后运行初始化函数
    window.addEventListener('load', init);

})();