小雅爬爬爬

爬取课件url

当前为 2024-04-20 提交的版本,查看 最新版本

// ==UserScript==
// @name        小雅爬爬爬
// @match      *://ccnu.ai-augmented.com/*
// @grant       none
// @description 爬取课件url
// @license MIT
// @author     Yi
// @version    1.0.9
// @namespace  https://gf.qytechs.cn/users/1268039
// ==/UserScript==

'use strict';

let course_resources;

// 附件下载实现细节
function parseContent() {
    console.oldLog("::parseContent");

    var download_url = 'https://ccnu.ai-augmented.com/api/jx-oresource/cloud/file_url/';
    var download_list = document.getElementById("download_list");
    download_list.innerHTML = '<h3 style="color:#fcbb34; font-weight:bold;">课程附件清单</h3>';
    for (let i in course_resources) {
        let file_name = course_resources[i].name;
        if (course_resources[i].mimetype) {
            fetch(download_url + course_resources[i].quote_id).then(function(response) {
                return response.json();
            }).then(function(data) {
                if (data.success) {
                    let file_url = data.data.url;

                    // 创建一个包含链接和勾选框的容器
                    var file_container = document.createElement('div');
                    file_container.style.display = 'flex';
                    file_container.style.alignItems = 'center';

                    // 创建勾选框
                    var checkbox = document.createElement('input');
                    checkbox.type = 'checkbox';
                    checkbox.style.marginRight = '10px';
                    checkbox.setAttribute('data-visible', 'true'); // 默认设置为可见

                    // 创建链接元素
                    var file_info = document.createElement('a');
                    file_info.innerHTML = file_name;
                    file_info.href = file_url;
                    file_info.target = "_blank";
                    file_info.className = 'link-style'; // 应用初始样式
                    file_info.style.textDecoration = 'none'; // 去掉下划线
                    file_info.addEventListener('mouseover', () => {
                        file_info.style.textDecoration = 'underline';
                        file_info.style.color = '#000';
                        file_info.style.fontWeight = "bold";
                    });
                    file_info.addEventListener('mouseout', function() {
                        file_info.style.textDecoration = 'none';
                        file_info.style.color = '';
                    });

                    // 将勾选框和链接添加到容器中
                    file_container.appendChild(checkbox);
                    file_container.appendChild(file_info);
                    file_container.style.borderBottom = '1px solid #eee'; // 每个项目间画一条分隔线
                    file_container.style.fontWeight = "bold";
                    file_container.style.padding = '5px 10px';
                    file_container.style.justifyContent = 'space-between';
                    file_container.style.alignItems = 'center';
                    file_container.style.display = 'flex';

                    console.oldLog('::parse', file_name, file_url);

                    // 将包含勾选框和链接的容器添加到下载列表
                    download_list.append(file_container);
                }
            }).catch(function(e) {
                console.oldLog('!!error', e);
            });
        }
    }
}

// 在文档中创建一个容器来放置所有下载进度条
let downloadsContainer = document.getElementById('downloadsContainer');
if (!downloadsContainer) {
    downloadsContainer = document.createElement('div');
    downloadsContainer.id = 'downloadsContainer';
    downloadsContainer.style.position = 'fixed';
    downloadsContainer.style.bottom = '10px';
    downloadsContainer.style.left = '50%';
    downloadsContainer.style.transform = 'translateX(-50%)';
    downloadsContainer.style.zIndex = '9999';
    downloadsContainer.style.backgroundColor = '#FFF3E0';
    downloadsContainer.style.boxShadow = '0 4px 6px rgba(255, 165, 0, 0.3)';
    downloadsContainer.style.borderRadius = '8px';
    downloadsContainer.style.padding = '15px';
    downloadsContainer.style.width = 'auto';
    downloadsContainer.style.maxWidth = '500px';
    downloadsContainer.style.boxSizing = 'border-box';
    downloadsContainer.style.margin = '0 auto';
    downloadsContainer.style.border = '1px solid #FFD180';
    downloadsContainer.style.display = 'none'; // 默认隐藏下载容器

    document.body.appendChild(downloadsContainer);
}

// 初始化拖动功能的变量
let isDragging = false;
let dragStartX = 0;
let dragStartY = 0;

// 函数:开始拖动
function dragStart(e) {
    // 记录初始拖动位置
    isDragging = true;
    dragStartX = e.clientX - downloadsContainer.getBoundingClientRect().left;
    dragStartY = e.clientY - downloadsContainer.getBoundingClientRect().top;
    document.addEventListener('mousemove', dragMove);
    document.addEventListener('mouseup', dragEnd);
}

// 函数:拖动时调用
function dragMove(e) {
    if (!isDragging) return; // 如果不是拖动状态,则退出
    // 计算容器新的位置
    let leftPos = e.clientX - dragStartX;
    let topPos = e.clientY - dragStartY;

    // 更新容器位置
    downloadsContainer.style.left = `${leftPos}px`;
    downloadsContainer.style.top = `${topPos}px`;
    downloadsContainer.style.bottom = 'auto'; // 当开始拖动时,移除bottom样式以允许自由定位
    downloadsContainer.style.transform = 'none'; // 移除transform样式,因为已经改为绝对定位
}

// 函数:停止拖动时调用
function dragEnd() {
    // 更新状态
    isDragging = false;
    document.removeEventListener('mousemove', dragMove);
    document.removeEventListener('mouseup', dragEnd);
}

// 添加鼠标按下时的事件监听器来初始化拖拽
downloadsContainer.addEventListener('mousedown', dragStart);

function updateDownloadsContainerVisibility() {
    downloadsContainer.style.display = downloadsContainer.children.length > 0 ? 'block' : 'none';
}

function courseDownload(file_url, file_name) {
    downloadsContainer.style.display = 'block'; // 新增下载前显示容器
    let progressText = document.createElement('span');
    let progressBar = document.createElement('progress');
    let progressContainer = document.createElement('div');

    progressContainer.style.marginBottom = '15px';

    progressBar.style.background = '#E0E0E0';
    progressBar.style.borderRadius = '8px';
    progressBar.style.height = '18px';
    progressBar.style.overflow = 'hidden';
    progressText.innerText = `正在下载: ${file_name}`;
    progressText.style.color = '#FF9800';
    progressText.style.fontWeight = 'bold';
    progressText.style.marginBottom = '10px';
    progressBar.value = 0;
    progressBar.max = 100;
    progressBar.style.width = '100%';

    progressContainer.appendChild(progressText);
    progressContainer.appendChild(progressBar);
    downloadsContainer.appendChild(progressContainer);

    // 创建一个 AbortController 和对应的信号
    const controller = new AbortController();
    const signal = controller.signal;
    window.abortControllers[file_name] = controller;

    // 动态创建一个停止按钮以取消下载
    let stopButton = document.createElement('button');
    stopButton.textContent = '停止';
    // 设置按钮基本样式
    stopButton.style.padding = '5px 10px';
    stopButton.style.border = 'none';
    stopButton.style.borderRadius = '5px';
    stopButton.style.backgroundColor = '#FF4136'; // 设置一个鲜艳的背景颜色
    stopButton.style.color = 'white';
    stopButton.style.cursor = 'pointer';
    stopButton.style.fontSize = '14px';
    stopButton.style.fontWeight = 'bold'; // 字体加粗
    stopButton.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.2)';
    stopButton.style.transition = 'transform 0.3s ease'; // 添加过渡效果使放大动画更平滑

    // 鼠标悬停时的放大效果
    stopButton.onmouseover = function() {
        stopButton.style.transform = 'scale(1.2)'; // 放大到原来的1.2倍
    };

    stopButton.onmouseout = function() {
        stopButton.style.transform = 'scale(1)'; // 回到原始大小
    };
    stopButton.onclick = function() {
        console.log(file_name + ' 的下载已经停止了');
        controller.abort(); // 取消下载

        // 移除进度条等相关元素
        progressContainer.remove();
        delete window.abortControllers[file_name];
        updateDownloadsContainerVisibility();
    };
    progressContainer.appendChild(stopButton);

    // 动态创建一个 <style> 元素,可以选择在这个函数内创建或者将其移到更全局的范围
    // 避免重复添加同样的 <style>
    if (!document.getElementById('progress-bar-styles')) {
        var style = document.createElement('style');
        style.id = 'progress-bar-styles';
        style.textContent = `
        progress[value]::-webkit-progress-bar {
            background-color: #E0E0E0; /* 进度条背景颜色 */
        }

        progress[value]::-webkit-progress-value {
            background-color: #FF9800; /* 已完成部分的颜色 */
        }

        progress[value]::-moz-progress-bar {
            background-color: #FF9800; /* Firefox 上的已完成部分颜色 */
        }
        `;
        document.head.appendChild(style);
    }

    // 开始下载文件,更新进度条
    fetch(file_url, { signal }).then(response => {
        const totalBytes = response.headers.get('Content-Length');
        if (!response.body || !totalBytes) {
            window.location = file_url;
            return;
        }

        const reader = response.body.getReader();
        let receivedBytes = 0;
        let chunks = [];

        function read() {
            reader.read().then(({ done, value }) => {
                if (done) {
                    // 当下载完成,从chunks创建一个Blob对象
                    const blob = new Blob(chunks, { type: 'application/octet-stream' });
                    // 创建一个下载URL
                    const downloadUrl = URL.createObjectURL(blob);
                    // 创建一个<a>标签用于触发下载
                    const a = document.createElement('a');
                    a.href = downloadUrl;
                    a.download = file_name; // 设置下载的文件名
                    document.body.appendChild(a); // 为了保证Firefox兼容性,将a元素添加到body
                    a.click(); // 模拟点击触发下载

                    // 清理资源
                    document.body.removeChild(a); // 移除刚刚添加的a元素
                    URL.revokeObjectURL(downloadUrl); // 释放由createObjectURL创建的URL对象
                    // 移除进度条的容器
                    downloadsContainer.removeChild(progressContainer);
                    // 当进度条被移除,检查downloadsContainer是否还有其他进度条,如果没有,隐藏容器
                    updateDownloadsContainerVisibility();
                    return;
                }
                chunks.push(value);
                receivedBytes += value.length;
                progressBar.value = (receivedBytes / totalBytes) * 100;

                read();
            }).catch(e => {
                if (e.name === 'AbortError') {
                    console.error(file_name + ' 的下载被用户取消了');
                    // 也许还要移除进度条或进行其他清理工作
                } else {
                    console.error(e); // 打印其他错误
                }
            });
        }

        read();
    });
}

window.showList = function () {
    var download_list = document.getElementById("download_list");

    // 检查是否已经存在搜索框
    var existingSearchInput = document.getElementById("searchInput");
    if (!existingSearchInput) {
        // 如果不存在,则创建搜索框
        var searchInput = document.createElement("input");
        searchInput.type = "text";
        searchInput.placeholder = "搜索文件名";
        searchInput.id = "searchInput"; // 设置唯一的ID
        searchInput.style.padding = '5px';
        searchInput.style.marginRight = '10px';
        searchInput.style.border = '1px solid #ddd';
        searchInput.style.borderRadius = '4px';
        searchInput.addEventListener("input", function () {
            filterList(this.value);
        });
        download_list.prepend(searchInput);
    }

    // 检查是否已存在全选复选框
    var existingSelectAllCheckbox = document.getElementById("selectAllCheckbox");
    if (!existingSelectAllCheckbox) {
        // 创建全选复选框
        var selectAllCheckbox = document.createElement('input');
        selectAllCheckbox.type = 'checkbox';
        selectAllCheckbox.id = 'selectAllCheckbox';
        selectAllCheckbox.style.marginRight = '10px';

        var selectAllLabel = document.createElement('label');
        selectAllLabel.htmlFor = 'selectAllCheckbox';
        selectAllLabel.textContent = '全选';
        selectAllLabel.style.userSelect = 'none'; // 防止点击标签时选中文字

        var checkboxContainer = document.createElement('div');
        checkboxContainer.appendChild(selectAllCheckbox);
        checkboxContainer.appendChild(selectAllLabel);
        download_list.prepend(checkboxContainer);
        checkboxContainer.style.display = 'flex';
        checkboxContainer.style.alignItems = 'center';
        checkboxContainer.style.justifyContent = 'space-between';
        checkboxContainer.style.padding = '5px 10px';
        checkboxContainer.style.borderBottom = '2px solid #fcbb34'; // 用主题颜色画分隔线
        checkboxContainer.style.marginBottom = '10px';

        // 给全选复选框添加事件监听器,当改变时,全选/取消全选所有当前可见的文件
        selectAllCheckbox.addEventListener('change', function() {
            var checkboxes = document.querySelectorAll("#download_list input[type='checkbox']:not(#selectAllCheckbox)");
            checkboxes.forEach(function(checkbox) {
                if (checkbox.getAttribute('data-visible') === 'true') {
                    checkbox.checked = selectAllCheckbox.checked;
                    // 手动触发 change 事件
                    var event = new Event('change', {
                        'bubbles': true, // 使事件冒泡
                        'cancelable': true // 使事件可以被取消
                    });
                    checkbox.dispatchEvent(event);
                }
            });
        });
    }

    if (download_list.style.display == "none") {
        download_list.style.display = "flex";
        download_list.style.overflowY = "auto"; // 添加垂直滚动条
        download_list.style.backgroundColor = "#ffffff";
        download_list.style.maxHeight = "300px"; // 设置最大高度,根据需要调整
    } else {
        download_list.style.display = "none";
    }

    // 检查是否已经创建了批量下载按钮
    var existingbulkDownloadButton = document.getElementById("bulkDownloadButton");
    if (!existingbulkDownloadButton) {
        // 检查是否已经创建了批量下载按钮
        var existingBulkDownloadButton = document.getElementById("bulkDownloadButton");
        if (!existingBulkDownloadButton) {
            // 添加批量下载按钮
            window.bulkDownloadButton = document.createElement('button');
            window.bulkDownloadButton.innerHTML = '批量下载';
            window.bulkDownloadButton.id = "bulkDownloadButton";
            window.bulkDownloadButton.title = '点击下载所选文件'; // 添加提示文本
            window.bulkDownloadButton.style = `
            position: fixed;
            top: 20px;
            right: 20px;
            background-color: #fcbb34;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 5px;
            cursor: pointer;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
            transition: background-color 0.3s, transform 0.3s;
        `;

            window.bulkDownloadButton.addEventListener('mouseover', function() {
                this.style.transform = 'scale(1.05)';
                this.style.backgroundColor = '#ffd564';
            });

            window.bulkDownloadButton.addEventListener('mouseout', function() {
                this.style.transform = 'scale(1)';
                this.style.backgroundColor = '#fcbb34';
            });
        }
        window.bulkDownloadButton.addEventListener('click', function() {
            var checkboxes = document.querySelectorAll("#download_list input[type='checkbox']:checked");
            checkboxes.forEach(function(checkbox) {
                // 检查是否真的勾选了复选框,并且复选框是可见的
                if (checkbox.checked && checkbox.getAttribute('data-visible') === 'true') {
                    var container = checkbox.closest('div');
                    var link = container.querySelector('a');
                    var file_name = link.textContent;
                    var file_url = link.href;

                    // 调用下载函数
                    courseDownload(file_url, file_name);
                }
            });
        });

        // 将批量下载按钮添加到下载列表
        download_list.appendChild(window.bulkDownloadButton);
    }
}

function filterList(keyword) {
    keyword = keyword.toLowerCase();
    var files = document.querySelectorAll("#download_list a");
    files.forEach(function(file) {
        var container = file.parentElement;
        var fileName = file.textContent.toLowerCase();
        var checkbox = container.querySelector("input[type='checkbox']");
        if (fileName.includes(keyword)) {
            container.style.display = "";
            checkbox.setAttribute('data-visible', 'true');
        } else {
            container.style.display = "none";
            checkbox.setAttribute('data-visible', 'false'); // 当文件被过滤掉时,设置复选框为不可见
        }
    });
}

function add_download_button() {
    // 全局变量用于保存批量下载按钮
    window.bulkDownloadButton = null;
    var down_button = document.createElement('div');
    down_button.innerHTML = `
<div id="download_list" style="z-index:999;backdrop-filter: blur(10px);border: 2px solid #fcbb34;border-radius: 5px;display: none;padding: 20px;flex-direction: column;align-items: flex-start;">
    <h3 style="color:#fcbb34; font-weight:bold;">课程附件清单</h3>
</div>
<svg onclick="showList()" class="icon download-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="60px" height="60px" fill="#fcbb34" title="双击展开列表">
    <title>展开列表</title>
    <path d="M19,9h-4V3H9v6H5l7,7L19,9z M5,18v2h14v-2H5z"/>
</svg>
`;

    // 添加下载图标的样式
    var downloadIconStyle = document.createElement('style');
    downloadIconStyle.innerHTML = `
  .download-icon {
  padding: 2px;
  margin: -20px;
  background-color: rgba(255, 255, 255, 0);
  border-radius: 10px;
}
    transition: transform 0.3s ease;
    cursor: pointer;
  }

  .download-icon:hover {
  background-color: rgba(255, 255, 255, 0.3);
    transform: scale(1.1);
  }
`;
    document.head.appendChild(downloadIconStyle);

    down_button.style.cssText = `
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 9000;
  cursor: pointer;
`;

    // 确保已经有这样的代码来设置图标的悬停动画
    var existingStyles = document.getElementById('download-icon-styles');
    if (!existingStyles) {
        document.head.appendChild(downloadIconStyle);
    }
    document.body.appendChild(down_button);
}

window.onload = ()=> {
    console.oldLog = console.log;
    console.log = (...data) =>{
        if (data[0] == 'nodesToData')
        {
            course_resources = data[1];
            console.oldLog('::', course_resources);
            parseContent();
        }
    };

    window.abortControllers = {};

    // 创建一个 MutationObserver 实例
    const observer = new MutationObserver(function(mutationsList, observer) {
        // 在每次发生 DOM 变化时触发这个回调函数
        for(let mutation of mutationsList) {
            if (mutation.type === 'childList' && mutation.target.id === 'download_list') {
                // 如果发生了子节点的变化,并且变化的目标是下载列表
                // 重新添加搜索框和批量下载按钮
                window.showList();
                break; // 处理完毕后退出循环
            }
        }
    });

    // 配置需要观察的目标节点和观察的类型
    observer.observe(document.body, { childList: true, subtree: true });

    // 添加下载按钮并延迟显示
    setTimeout(() => {
        add_download_button();
    }, 2000);
};


// 定义要抓取的后缀名
var extensions = [".doc", ".pdf", ".docx", ".ppt", ".pptx", ".xls", ".xlsx"];

// 创建一个元素,用于显示抓取到的 URL
var list = document.createElement("div");
initializeListStyles(list);


// 监听 xhr 请求,检查响应的 URL 是否符合条件
var open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
    this.addEventListener("load", function() {
        // 如果 URL 包含指定的后缀名之一
        for (var i = 0; i < extensions.length; i++) {
            if (url.includes(extensions[i])) {
                // 发送一个新的 xhr 请求,获取真正的下载地址
                handleXhrResponse(url);
                break;
            }
        }
    });
    open.call(this, method, url, async, user, pass);
};

// 初始化列表样式
function initializeListStyles(element) {
    element.style.position = "fixed";
    element.style.top = "10px";
    element.style.right = "0";
    element.style.width = "350px";
    element.style.height = "10%";
    element.style.overflow = "auto";
    element.style.zIndex = "9999";
    element.style.padding = "10px";
    // 为元素添加渐变背景色
    element.style.background = "linear-gradient(270deg, #ffc700, #ff8c00, #ff6500)";
    element.style.backgroundSize = "400% 400%";
    // 添加动态背景动画的 CSS 规则
    var animStyle = document.createElement("style");
    animStyle.textContent = `
@keyframes gradientBgAnimation {
    0% {
        background-position: 0% 50%;
    }
    50% {
        background-position: 100% 50%;
    }
    100% {
        background-position: 0% 50%;
    }
}

.dynamic-gradient {
    animation: gradientBgAnimation 15s ease infinite;
}
`;
    document.head.appendChild(animStyle);

    // 为元素添加动态渐变背景的类
    element.classList.add("dynamic-gradient");
    // 为元素添加阴影效果
    element.style.boxShadow = "0 4px 8px 0 rgba(0, 0, 0, 0.2)";
    // 为元素添加圆角效果
    element.style.borderRadius = "10px";
    // 为元素添加动画效果,悬停时放大
    element.style.transition = "transform 0.3s";
    element.addEventListener("mouseover", function() {
        element.style.transform = "scale(1.1)";
    });
    element.addEventListener("mouseout", function() {
        element.style.transform = "scale(1)";
    });
    element.innerHTML = "<h3><span style=\"font-family: '微软雅黑', 'Microsoft YaHei', sans-serif; font-weight: bold; font-style: italic; font-size: 16px;\">抓取到的课件</span></h3>";
    // 添加 draggable 属性,可拖动
    element.setAttribute("draggable", "true");
    // 添加 resize 属性,可调整大小
    element.style.resize = "both";
    // 添加拖动事件监听器
    element.addEventListener("dragstart", function(e) {
        // 设置拖动元素的透明度
        e.target.style.opacity = "0.5";
        // 设置拖动元素的 id
        e.dataTransfer.setData("text/plain", e.target.id);
        // 记录拖动元素的初始位置和鼠标的初始位置
        e.target.startX = e.clientX;
        e.target.startY = e.clientY;
        e.target.offsetX = e.target.offsetLeft;
        e.target.offsetY = e.target.offsetTop;
    });
    element.addEventListener("drag", function(e) {
        // 如果鼠标的位置有效,根据鼠标的移动距离,更新拖动元素的位置
        if (e.clientX > 0 && e.clientY > 0) {
            e.target.style.left = e.target.offsetX + e.clientX - e.target.startX + "px";
            e.target.style.top = e.target.offsetY + e.clientY - e.target.startY + "px";
        }
    });
    element.addEventListener("dragend", function(e) {
        // 恢复拖动元素的透明度
        e.target.style.opacity = "1";
    });
    document.body.appendChild(element);
}

// 全局变量,用于存储唯一的预览链接元素
var previewLink;
// 全局变量,用于标志是否有异步操作正在进行中
var isDownloading = false;

function handleXhrResponse(url) {
    if (isDownloading) {
        return; // 如果已经有下载在进行中,则跳过
    }

    isDownloading = true;

    // 清除之前的预览链接元素
    if (previewLink) {
        previewLink.parentNode.removeChild(previewLink);
        previewLink = null;
    }
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onload = function () {
        // 如果响应的文本中包含一个以 http 或 https 开头的 URL,将其添加到列表中
        // 在此之前,先将响应的文本中的 "}}" 和引号替换为空字符串,去掉多余的符号
        var text = xhr.responseText.replace("}}", "").replace(/"/g, "");
        var match = text.match(/(http|https):\/\/\S+/);
        var titleBannerElement = document.querySelector('.common_node_content_banner h5.title');
        var content;

        if (titleBannerElement && titleBannerElement.getAttribute('title')) {
            content = titleBannerElement.getAttribute('title').trim();
        } else {
            content = titleBannerElement.textContent.trim();
        }

        if (match) {
            // 如果预览链接不存在,则创建
            if (!previewLink) {
                previewLink = document.createElement("a");
                previewLink.style.background = "#06566f";
                // 使用背景剪裁
                previewLink.style.webkitBackgroundClip = "text";
                previewLink.style.backgroundClip = "text";
                // 设置文字颜色为透明以使渐变可见
                previewLink.style.color = "transparent";
                previewLink.style.fontFamily = "'微软雅黑', 'Microsoft YaHei', sans-serif";
                previewLink.style.fontWeight = "bold";
                // 悬停时的样式变化
                previewLink.addEventListener('mouseover', () => {
                    previewLink.style.backgroundColor = '#ffffff'; // 更改背景色
                });
                previewLink.addEventListener('mouseout', () => {
                    previewLink.style.backgroundColor = '#06566f'; // 恢复背景色

                });
                // 将预览链接添加到列表中
                list.appendChild(previewLink);
                list.appendChild(document.createElement("br"));
            }
            // 更新预览链接的属性
            previewLink.href = match[0];
            previewLink.target = "_blank";
            previewLink.textContent = content;
            // 添加点击事件监听器,在点击时进行下载
            previewLink.addEventListener("click", function (event) {
                event.preventDefault(); // 阻止默认的点击行为
                // 直接使用 courseDownload 函数进行下载
                courseDownload(match[0], content);
            });
        }
        isDownloading = false;

        // 将新创建的元素插入到列表的最前面
        var titleElement = list.querySelector("h3");
        if (titleElement) {
            list.insertBefore(previewLink, titleElement.nextSibling);
        } else {
            list.appendChild(previewLink);
        }
    }
    xhr.send();
}

QingJ © 2025

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