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

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

目前為 2024-04-25 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

      // ==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);