国开自动刷课(不答题考试)

国开(国家开放大学)自动刷课(不答题考试) 支持自动访问线上链接、查看资料附件、观看视频、自动查看页面。

      // ==UserScript==
        // @name             国开自动刷课(不答题考试)
        // @namespace        http://ibaiyu.top/
        // @version          1.0.0
        // @description      国开(国家开放大学)自动刷课(不答题考试) 支持自动访问线上链接、查看资料附件、观看视频、自动查看页面。
        // @author           南风依旧
        // @match          *://lms.ouchn.cn/course/*
        // @original-author  南风依旧
        // @original-license GPL-3.0
        // @license          GPL-3.0
        // ==/UserScript==


        // 设置视频播放速度 建议最大4-8倍速 不然可能会卡 没有最大值
        // 并且直接挂载到window上
        window.playbackRate = 4;

        // 设置各种不同类型的课程任务之间的时间延迟,以便脚本在进行自动化学习时可以更好地模拟人类操作。
        const interval = {
            loadCourse: 3000, // 加载课程列表的延迟时间
            viewPage: 3000, // 查看页面类型课程的延迟时间
            onlineVideo: 3000, // 播放在线视频课程的延迟时间
            webLink: 3000, // 点击线上链接类型课程的延迟时间
            forum: 3000, // 发帖子给论坛课程的延迟时间
            material: 3000, // 查看附件类型课程的延迟时间
            other: 3000 // 处理其他未知类型课程的延迟时间
        };

        (async function (window, document) {

            // 使用正则表达式从当前 URL 中提取出课程 ID。
            const courseId = (await waitForElement("#courseId", interval.loadCourse)).value;

            // 运行
            main();

            // 保存值到本地存储
            function GM_setValue(name, value) {
                localStorage.setItem(name, JSON.stringify(value));
            }

            //从本地存储获取值
            function GM_getValue(name, defaultValue) {
                const value = localStorage.getItem(name);
                if (value === null) {
                    return defaultValue;
                }
                try {
                    return JSON.parse(value);
                } catch (e) {
                    console.error(`Error parsing stored value for ${name}:`, e);
                    return defaultValue;
                }
            }

            // 创建返回到课程列表页面的函数。
            async function returnCoursePage(waitTime = 500) {
                const backElement = await waitForElement("a.full-screen-mode-back", waitTime);
                if (backElement) {
                    backElement?.click();
                } else {
                    throw new Error("异常 无法获取到返回课程列表页面的元素!");
                }
            }

            // 将中文类型名称转换为英文枚举值。
            function getTypeEum(type) {
                switch (type) {
                    case "页面":
                        return "page";
                    case "音视频教材":
                        return "online_video";
                    case "线上链接":
                        return "web_link";
                    case "讨论":
                        return "forum";
                    case "参考资料":
                        return "material";
                    default:
                        return null;
                }
            }

            /**
             * 等待指定元素出现
             * 返回一个Promise对象,对document.querySelector封装了一下
             * @param selector dom选择器,像document.querySelector一样
             * @param waitTime 等待时间 单位: ms
             */
            async function waitForElement(selector, waitTime = 1000, maxCount = 10) {
                let count = 0;
                return new Promise(resolve => {
                    let timeId = setInterval(() => {
                        const element = document.querySelector(selector);
                        if (element || count >= maxCount) {
                            clearInterval(timeId);
                            resolve(element || null);
                        }
                        count++;
                    }, waitTime);
                });
            }

            /**
             * 等待多个指定元素出现
             * 返回一个Promise对象,对document.querySelectorAll封装了一下
             * @param selector dom选择器,像document.querySelectorAll一样
             * @param waitTime 等待时间 单位: ms
             */
            async function waitForElements(selector, waitTime = 1000, maxCount = 10) {
                let count = 0;
                return new Promise(resolve => {
                    let timeId = setInterval(() => {
                        const element = document.querySelectorAll(selector);
                        if (element || count >= maxCount) {
                            clearInterval(timeId);
                            resolve(element || null);
                        }
                        count++;
                    }, waitTime);
                });
            }

            // 等待指定时间
            function wait(ms) {
                return new Promise(resolve => { setTimeout(resolve, ms); });
            }

            /**
             * 该函数用于添加学习行为时长
             */
            function addLearningBehavior(activity_id, activity_type) {
                const duration = Math.ceil(Math.random() * 300 + 40);
                const data = JSON.stringify({
                    activity_id,
                    activity_type,
                    browser: 'chrome',
                    course_id: globalData.course.id,
                    course_code: globalData.course.courseCode,
                    course_name: globalData.course.name,
                    org_id: globalData.course.orgId,
                    org_name: globalData.user.orgName,
                    org_code: globalData.user.orgCode,
                    dep_id: globalData.dept.id,
                    dep_name: globalData.dept.name,
                    dep_code: globalData.dept.code,
                    user_agent: window.navigator.userAgent,
                    user_id: globalData.user.id,
                    user_name: globalData.user.name,
                    user_no: globalData.user.userNo,
                    visit_duration: duration
                });
                const url = 'https://lms.ouchn.cn/statistics/api/user-visits';
                return new Promise((resolve, reject) => {
                    $.ajax({
                        url,
                        data,
                        type: "POST",
                        cache: false,
                        contentType: "text/plain;charset=UTF-8",
                        complete: resolve
                    });
                });
            }

            // 打开并播放在线视频课程。
            async function openOnlineVideo() {
                // 等待 video 或 audio 元素加载完成
                const videoElem = await waitForElement('video');
                let audioElem = null;

                if (!videoElem) {
                    audioElem = await waitForElement('audio');
                }

                if (videoElem) {
                    // 处理视频元素
                    console.log("正在播放视频中...");

                    // 设置播放速率
                    videoElem.playbackRate = playbackRate;

                    // 监听播放速率变化事件并重新设置播放速率
                    videoElem.addEventListener('ratechange', function () {
                        videoElem.playbackRate = playbackRate;
                    });

                    // 监听视频播放结束事件
                    videoElem.addEventListener('ended', returnCoursePage);

                    // 延迟一会儿以等待视频加载
                    await wait(interval.onlineVideo);

                    // // 每隔一段时间检查是否暂停,并模拟点击继续播放并设置声音音量为0
                    setInterval(() => {
                        videoElem.volume = 0;
                        if (document.querySelector("i.mvp-fonts.mvp-fonts-play")) {
                            document.querySelector("i.mvp-fonts.mvp-fonts-play").click();
                        }
                    }, interval.onlineVideo);

                } else if (audioElem) {
                    // 处理音频元素
                    console.log("正在播放音频中...");

                    // 监听音频播放结束事件
                    audioElem.addEventListener("ended", returnCoursePage);

                    // 延迟一会儿以等待音频加载
                    await wait(interval.onlineVideo);

                    // 每隔一段时间检查是否暂停,并模拟点击继续播放
                    setInterval(() => {
                        audioElem.volume = 0;
                        if (document.querySelector("i.font.font-audio-play")) {
                            document.querySelector("i.font.font-audio-play").click();
                        }
                    }, interval.onlineVideo);
                }
            }

            // 打开并查看页面类型课程。
            function openViewPage() {
                // 当页面被加载完毕后延迟一会直接返回课程首页
                setTimeout(returnCoursePage, interval.viewPage);
            }

            // 打开并点击线上链接类型课程。
            async function openWebLink() {
                // 等待获取open-link-button元素
                const ElementOpenLinkButton = await waitForElement(".open-link-button", interval.webLink);

                // 设置元素属性让它不会弹出新标签并设置href为空并模拟点击
                ElementOpenLinkButton.target = "_self";
                ElementOpenLinkButton.href = "javascript:void(0);";
                ElementOpenLinkButton.click();

                // 等待一段时间后执行returnCoursePage函数
                setTimeout(returnCoursePage, interval.webLink);
            }
            function openApiMaterial() { // 用API去完成查看附件
                const id = document.URL.match(/.*\/\/lms.ouchn.cn\/course\/[0-9]+\/learning-activity\/full-screen.+\/([0-9]+)/)[1];
                const res = new Promise((resolve, reject) => {
                    $.ajax({
                        url: `https://lms.ouchn.cn/api/activities/${id}`,
                        type: "GET",
                        success: resolve,
                        error: reject
                    })
                });
                res.then(async ({ uploads: uploadsModels }) => {
                    uploadsModels.forEach(async ({ id: uploadId }) => {
                        await wait(interval.material);
                        await new Promise(resolve => $.ajax({
                            url: `https://lms.ouchn.cn/api/course/activities-read/${id}`,
                            type: "POST",
                            data: JSON.stringify({ upload_id: uploadId }),
                            contentType: "application/json",
                            dataType: "JSON",
                            success: resolve,
                            error: resolve
                        }));
                    });

                    await wait(interval.material);
                    returnCoursePage();
                });
                res.catch((xhr, status, error) => {
                    console.log(`这里出现了一个异常 | status: ${status}`);
                    console.dir(error, xhr, status);
                });

            }

            // 打开课程任务并发布帖子。
            async function openForum() {
                // 使用 waitForElement 函数等待 .embeded-new-topic>i 元素出现,并赋值给 topicElement 变量
                const topicElement = await waitForElement("button.ivu-btn.ivu-btn-primary i", interval.forum);

                if (!topicElement) {
                    throw new Error("无法找到发帖按钮的元素。");
                }

                // 点击话题元素并等待一段时间
                topicElement.click();
                await wait(interval.forum);

                // 获取标题、内容和提交按钮元素
                const titleElem = $("#add-topic-popup > div > div.topic-form-section.main-area > form > div:nth-child(1) > div.field > input");
                const contentElem = document.querySelector('#add-topic-popup > div > div.topic-form-section.main-area .simditor-body.needsclick[contenteditable]');
                const submitElem = document.querySelector("#add-topic-popup > div > div.popup-footer > div > button.button.button-green.medium");

                // 设置标题和内容
                titleElem.val(`好好学习${Date.now()}`).trigger('change');

                // 点击提交按钮并延迟一段时间后返回课程页面
                contentElem.innerHTML = `<p>好好学习,天天向上。${Date.now()}</p>`;
                submitElem.click();

                // 等待一段时间后执行returnCoursePage函数
                setTimeout(returnCoursePage, interval.forum);
            }

            // 课程首页处理
            async function courseIndex() {
                await new Promise(resolve => {
                    console.log("正在展开所有课程任务");
                    let timeId = setInterval(() => {
                        const allCollapsedElement = document.querySelector("i.icon.font.font-toggle-all-collapsed");
                        const allExpandedElement = document.querySelector("i.icon.font.font-toggle-all-expanded");
                        if (!allExpandedElement) {
                            if (allCollapsedElement) {
                                allCollapsedElement.click();
                            }
                        }
                        if (!allCollapsedElement && !allExpandedElement) { throw new Error("无法展开所有课程 可能是元素已更改,请联系作者更新。"); } {
                            console.log("课程展开完成。");
                            clearInterval(timeId);
                            resolve();
                        }
                    }, interval.loadCourse);
                });


                console.log("正在获取加载的课程任务");
                const courseElements = await waitForElements('.learning-activity .clickable-area', interval.loadCourse);

                const courseElement = Array.from(courseElements).find(elem => {
                    const type = $(elem.querySelector('i.font[original-title]')).attr('original-title'); // 获取该课程任务的类型
                    // const status = $(elem.querySelector('span.item-status')).text(); // 获取该课程任务是否进行中
                    // 👆上行代码由于无法获取到课程任务是否已关闭,目前暂时注释掉

                    if(type=='讨论'){
                        return false;
                    }
                    const typeEum = getTypeEum(type);

                    if (!typeEum) {
                        return false;
                    }

                    const completes = elem.querySelector('.ivu-tooltip-inner b').textContent === "已完成" ? true : false;

                    // const result = status === "进行中" && typeEum != null && completes === false;
                    const result = typeEum != null && completes === false;
                    if (result) {
                        GM_setValue(`typeEum-${courseId}`, typeEum);
                    }
                    return result;
                });
                
                console.log(courseElement);
                if (courseElement) {
                    console.log("发现未完成的课程");
                    $(courseElement).click();
                } else {
                    console.log("课程可能全部完成了");
                }

            }

            function main() {
                if (/https:\/\/lms.ouchn.cn\/course\/\d+\/ng.*#\//m.test(document.URL)) {
                    // 判断是否在课程首页
                    courseIndex();
                } else if (/http[s]?:\/\/lms.ouchn.cn\/course\/\d+\/learning-activity\/full-screen[#]?\//.test(window.location.href)) {
                    let timeId = 0;
                    const activity_id = /http[s]?:\/\/lms.ouchn.cn\/course\/\d+\/learning-activity\/full-screen[#]?\/(\d+)/.exec(window.location.href)[1];
                    const typeEum = GM_getValue(`typeEum-${courseId}`, null);
                    addLearningBehavior(activity_id, typeEum);
                    switch (typeEum) {
                        case "page":
                            console.log("正在查看页面。");
                            openViewPage();
                            return;
                        case "online_video":
                            openOnlineVideo();
                            return;
                        case "web_link":
                            console.log("正在点击外部链接~");
                            openWebLink();
                            return;
                        case "forum":
                            console.log("发帖中!");
                            returnCoursePage()
                            //openForum();
                            return;
                        case "material":
                            console.log("正在给课件发送已阅读状态");
                            openApiMaterial();
                            return;
                        default:
                            setTimeout(returnCoursePage, interval.other);
                            return;
                    }
                }
            }
        })(window, document);