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