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

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

  1. // ==UserScript==
  2. // @name 国开自动刷课(不答题考试)
  3. // @namespace http://ibaiyu.top/
  4. // @version 1.0.0
  5. // @description 国开(国家开放大学)自动刷课(不答题考试) 支持自动访问线上链接、查看资料附件、观看视频、自动查看页面。
  6. // @author 南风依旧
  7. // @match *://lms.ouchn.cn/course/*
  8. // @original-author 南风依旧
  9. // @original-license GPL-3.0
  10. // @license GPL-3.0
  11. // ==/UserScript==
  12.  
  13.  
  14. // 设置视频播放速度 建议最大4-8倍速 不然可能会卡 没有最大值
  15. // 并且直接挂载到window上
  16. window.playbackRate = 4;
  17.  
  18. // 设置各种不同类型的课程任务之间的时间延迟,以便脚本在进行自动化学习时可以更好地模拟人类操作。
  19. const interval = {
  20. loadCourse: 3000, // 加载课程列表的延迟时间
  21. viewPage: 3000, // 查看页面类型课程的延迟时间
  22. onlineVideo: 3000, // 播放在线视频课程的延迟时间
  23. webLink: 3000, // 点击线上链接类型课程的延迟时间
  24. forum: 3000, // 发帖子给论坛课程的延迟时间
  25. material: 3000, // 查看附件类型课程的延迟时间
  26. other: 3000 // 处理其他未知类型课程的延迟时间
  27. };
  28.  
  29. (async function (window, document) {
  30.  
  31. // 使用正则表达式从当前 URL 中提取出课程 ID。
  32. const courseId = (await waitForElement("#courseId", interval.loadCourse)).value;
  33.  
  34. // 运行
  35. main();
  36.  
  37. // 保存值到本地存储
  38. function GM_setValue(name, value) {
  39. localStorage.setItem(name, JSON.stringify(value));
  40. }
  41.  
  42. //从本地存储获取值
  43. function GM_getValue(name, defaultValue) {
  44. const value = localStorage.getItem(name);
  45. if (value === null) {
  46. return defaultValue;
  47. }
  48. try {
  49. return JSON.parse(value);
  50. } catch (e) {
  51. console.error(`Error parsing stored value for ${name}:`, e);
  52. return defaultValue;
  53. }
  54. }
  55.  
  56. // 创建返回到课程列表页面的函数。
  57. async function returnCoursePage(waitTime = 500) {
  58. const backElement = await waitForElement("a.full-screen-mode-back", waitTime);
  59. if (backElement) {
  60. backElement?.click();
  61. } else {
  62. throw new Error("异常 无法获取到返回课程列表页面的元素!");
  63. }
  64. }
  65.  
  66. // 将中文类型名称转换为英文枚举值。
  67. function getTypeEum(type) {
  68. switch (type) {
  69. case "页面":
  70. return "page";
  71. case "音视频教材":
  72. return "online_video";
  73. case "线上链接":
  74. return "web_link";
  75. case "讨论":
  76. return "forum";
  77. case "参考资料":
  78. return "material";
  79. default:
  80. return null;
  81. }
  82. }
  83.  
  84. /**
  85. * 等待指定元素出现
  86. * 返回一个Promise对象,对document.querySelector封装了一下
  87. * @param selector dom选择器,像document.querySelector一样
  88. * @param waitTime 等待时间 单位: ms
  89. */
  90. async function waitForElement(selector, waitTime = 1000, maxCount = 10) {
  91. let count = 0;
  92. return new Promise(resolve => {
  93. let timeId = setInterval(() => {
  94. const element = document.querySelector(selector);
  95. if (element || count >= maxCount) {
  96. clearInterval(timeId);
  97. resolve(element || null);
  98. }
  99. count++;
  100. }, waitTime);
  101. });
  102. }
  103.  
  104. /**
  105. * 等待多个指定元素出现
  106. * 返回一个Promise对象,对document.querySelectorAll封装了一下
  107. * @param selector dom选择器,像document.querySelectorAll一样
  108. * @param waitTime 等待时间 单位: ms
  109. */
  110. async function waitForElements(selector, waitTime = 1000, maxCount = 10) {
  111. let count = 0;
  112. return new Promise(resolve => {
  113. let timeId = setInterval(() => {
  114. const element = document.querySelectorAll(selector);
  115. if (element || count >= maxCount) {
  116. clearInterval(timeId);
  117. resolve(element || null);
  118. }
  119. count++;
  120. }, waitTime);
  121. });
  122. }
  123.  
  124. // 等待指定时间
  125. function wait(ms) {
  126. return new Promise(resolve => { setTimeout(resolve, ms); });
  127. }
  128.  
  129. /**
  130. * 该函数用于添加学习行为时长
  131. */
  132. function addLearningBehavior(activity_id, activity_type) {
  133. const duration = Math.ceil(Math.random() * 300 + 40);
  134. const data = JSON.stringify({
  135. activity_id,
  136. activity_type,
  137. browser: 'chrome',
  138. course_id: globalData.course.id,
  139. course_code: globalData.course.courseCode,
  140. course_name: globalData.course.name,
  141. org_id: globalData.course.orgId,
  142. org_name: globalData.user.orgName,
  143. org_code: globalData.user.orgCode,
  144. dep_id: globalData.dept.id,
  145. dep_name: globalData.dept.name,
  146. dep_code: globalData.dept.code,
  147. user_agent: window.navigator.userAgent,
  148. user_id: globalData.user.id,
  149. user_name: globalData.user.name,
  150. user_no: globalData.user.userNo,
  151. visit_duration: duration
  152. });
  153. const url = 'https://lms.ouchn.cn/statistics/api/user-visits';
  154. return new Promise((resolve, reject) => {
  155. $.ajax({
  156. url,
  157. data,
  158. type: "POST",
  159. cache: false,
  160. contentType: "text/plain;charset=UTF-8",
  161. complete: resolve
  162. });
  163. });
  164. }
  165.  
  166. // 打开并播放在线视频课程。
  167. async function openOnlineVideo() {
  168. // 等待 video 或 audio 元素加载完成
  169. const videoElem = await waitForElement('video');
  170. let audioElem = null;
  171.  
  172. if (!videoElem) {
  173. audioElem = await waitForElement('audio');
  174. }
  175.  
  176. if (videoElem) {
  177. // 处理视频元素
  178. console.log("正在播放视频中...");
  179.  
  180. // 设置播放速率
  181. videoElem.playbackRate = playbackRate;
  182.  
  183. // 监听播放速率变化事件并重新设置播放速率
  184. videoElem.addEventListener('ratechange', function () {
  185. videoElem.playbackRate = playbackRate;
  186. });
  187.  
  188. // 监听视频播放结束事件
  189. videoElem.addEventListener('ended', returnCoursePage);
  190.  
  191. // 延迟一会儿以等待视频加载
  192. await wait(interval.onlineVideo);
  193.  
  194. // // 每隔一段时间检查是否暂停,并模拟点击继续播放并设置声音音量为0
  195. setInterval(() => {
  196. videoElem.volume = 0;
  197. if (document.querySelector("i.mvp-fonts.mvp-fonts-play")) {
  198. document.querySelector("i.mvp-fonts.mvp-fonts-play").click();
  199. }
  200. }, interval.onlineVideo);
  201.  
  202. } else if (audioElem) {
  203. // 处理音频元素
  204. console.log("正在播放音频中...");
  205.  
  206. // 监听音频播放结束事件
  207. audioElem.addEventListener("ended", returnCoursePage);
  208.  
  209. // 延迟一会儿以等待音频加载
  210. await wait(interval.onlineVideo);
  211.  
  212. // 每隔一段时间检查是否暂停,并模拟点击继续播放
  213. setInterval(() => {
  214. audioElem.volume = 0;
  215. if (document.querySelector("i.font.font-audio-play")) {
  216. document.querySelector("i.font.font-audio-play").click();
  217. }
  218. }, interval.onlineVideo);
  219. }
  220. }
  221.  
  222. // 打开并查看页面类型课程。
  223. function openViewPage() {
  224. // 当页面被加载完毕后延迟一会直接返回课程首页
  225. setTimeout(returnCoursePage, interval.viewPage);
  226. }
  227.  
  228. // 打开并点击线上链接类型课程。
  229. async function openWebLink() {
  230. // 等待获取open-link-button元素
  231. const ElementOpenLinkButton = await waitForElement(".open-link-button", interval.webLink);
  232.  
  233. // 设置元素属性让它不会弹出新标签并设置href为空并模拟点击
  234. ElementOpenLinkButton.target = "_self";
  235. ElementOpenLinkButton.href = "javascript:void(0);";
  236. ElementOpenLinkButton.click();
  237.  
  238. // 等待一段时间后执行returnCoursePage函数
  239. setTimeout(returnCoursePage, interval.webLink);
  240. }
  241. function openApiMaterial() { // 用API去完成查看附件
  242. const id = document.URL.match(/.*\/\/lms.ouchn.cn\/course\/[0-9]+\/learning-activity\/full-screen.+\/([0-9]+)/)[1];
  243. const res = new Promise((resolve, reject) => {
  244. $.ajax({
  245. url: `https://lms.ouchn.cn/api/activities/${id}`,
  246. type: "GET",
  247. success: resolve,
  248. error: reject
  249. })
  250. });
  251. res.then(async ({ uploads: uploadsModels }) => {
  252. uploadsModels.forEach(async ({ id: uploadId }) => {
  253. await wait(interval.material);
  254. await new Promise(resolve => $.ajax({
  255. url: `https://lms.ouchn.cn/api/course/activities-read/${id}`,
  256. type: "POST",
  257. data: JSON.stringify({ upload_id: uploadId }),
  258. contentType: "application/json",
  259. dataType: "JSON",
  260. success: resolve,
  261. error: resolve
  262. }));
  263. });
  264.  
  265. await wait(interval.material);
  266. returnCoursePage();
  267. });
  268. res.catch((xhr, status, error) => {
  269. console.log(`这里出现了一个异常 | status: ${status}`);
  270. console.dir(error, xhr, status);
  271. });
  272.  
  273. }
  274.  
  275. // 打开课程任务并发布帖子。
  276. async function openForum() {
  277. // 使用 waitForElement 函数等待 .embeded-new-topic>i 元素出现,并赋值给 topicElement 变量
  278. const topicElement = await waitForElement("button.ivu-btn.ivu-btn-primary i", interval.forum);
  279.  
  280. if (!topicElement) {
  281. throw new Error("无法找到发帖按钮的元素。");
  282. }
  283.  
  284. // 点击话题元素并等待一段时间
  285. topicElement.click();
  286. await wait(interval.forum);
  287.  
  288. // 获取标题、内容和提交按钮元素
  289. const titleElem = $("#add-topic-popup > div > div.topic-form-section.main-area > form > div:nth-child(1) > div.field > input");
  290. const contentElem = document.querySelector('#add-topic-popup > div > div.topic-form-section.main-area .simditor-body.needsclick[contenteditable]');
  291. const submitElem = document.querySelector("#add-topic-popup > div > div.popup-footer > div > button.button.button-green.medium");
  292.  
  293. // 设置标题和内容
  294. titleElem.val(`好好学习${Date.now()}`).trigger('change');
  295.  
  296. // 点击提交按钮并延迟一段时间后返回课程页面
  297. contentElem.innerHTML = `<p>好好学习,天天向上。${Date.now()}</p>`;
  298. submitElem.click();
  299.  
  300. // 等待一段时间后执行returnCoursePage函数
  301. setTimeout(returnCoursePage, interval.forum);
  302. }
  303.  
  304. // 课程首页处理
  305. async function courseIndex() {
  306. await new Promise(resolve => {
  307. console.log("正在展开所有课程任务");
  308. let timeId = setInterval(() => {
  309. const allCollapsedElement = document.querySelector("i.icon.font.font-toggle-all-collapsed");
  310. const allExpandedElement = document.querySelector("i.icon.font.font-toggle-all-expanded");
  311. if (!allExpandedElement) {
  312. if (allCollapsedElement) {
  313. allCollapsedElement.click();
  314. }
  315. }
  316. if (!allCollapsedElement && !allExpandedElement) { throw new Error("无法展开所有课程 可能是元素已更改,请联系作者更新。"); } {
  317. console.log("课程展开完成。");
  318. clearInterval(timeId);
  319. resolve();
  320. }
  321. }, interval.loadCourse);
  322. });
  323.  
  324.  
  325. console.log("正在获取加载的课程任务");
  326. const courseElements = await waitForElements('.learning-activity .clickable-area', interval.loadCourse);
  327.  
  328. const courseElement = Array.from(courseElements).find(elem => {
  329. const type = $(elem.querySelector('i.font[original-title]')).attr('original-title'); // 获取该课程任务的类型
  330. // const status = $(elem.querySelector('span.item-status')).text(); // 获取该课程任务是否进行中
  331. // 👆上行代码由于无法获取到课程任务是否已关闭,目前暂时注释掉
  332.  
  333. if(type=='讨论'){
  334. return false;
  335. }
  336. const typeEum = getTypeEum(type);
  337.  
  338. if (!typeEum) {
  339. return false;
  340. }
  341.  
  342. const completes = elem.querySelector('.ivu-tooltip-inner b').textContent === "已完成" ? true : false;
  343.  
  344. // const result = status === "进行中" && typeEum != null && completes === false;
  345. const result = typeEum != null && completes === false;
  346. if (result) {
  347. GM_setValue(`typeEum-${courseId}`, typeEum);
  348. }
  349. return result;
  350. });
  351. console.log(courseElement);
  352. if (courseElement) {
  353. console.log("发现未完成的课程");
  354. $(courseElement).click();
  355. } else {
  356. console.log("课程可能全部完成了");
  357. }
  358.  
  359. }
  360.  
  361. function main() {
  362. if (/https:\/\/lms.ouchn.cn\/course\/\d+\/ng.*#\//m.test(document.URL)) {
  363. // 判断是否在课程首页
  364. courseIndex();
  365. } else if (/http[s]?:\/\/lms.ouchn.cn\/course\/\d+\/learning-activity\/full-screen[#]?\//.test(window.location.href)) {
  366. let timeId = 0;
  367. const activity_id = /http[s]?:\/\/lms.ouchn.cn\/course\/\d+\/learning-activity\/full-screen[#]?\/(\d+)/.exec(window.location.href)[1];
  368. const typeEum = GM_getValue(`typeEum-${courseId}`, null);
  369. addLearningBehavior(activity_id, typeEum);
  370. switch (typeEum) {
  371. case "page":
  372. console.log("正在查看页面。");
  373. openViewPage();
  374. return;
  375. case "online_video":
  376. openOnlineVideo();
  377. return;
  378. case "web_link":
  379. console.log("正在点击外部链接~");
  380. openWebLink();
  381. return;
  382. case "forum":
  383. console.log("发帖中!");
  384. returnCoursePage()
  385. //openForum();
  386. return;
  387. case "material":
  388. console.log("正在给课件发送已阅读状态");
  389. openApiMaterial();
  390. return;
  391. default:
  392. setTimeout(returnCoursePage, interval.other);
  393. return;
  394. }
  395. }
  396. }
  397. })(window, document);

QingJ © 2025

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