zhyDaDa_超星网课助手

[个人向] 刷超星尔雅的网课, 现在支持pdf/ppt/视频/音频的处理, 提供了设置面板可以调节, 解决了鼠标移开视频暂停的问题

  1. // ==UserScript==
  2. // @name zhyDaDa_超星网课助手
  3. // @version 1.20.3
  4. // @description [个人向] 刷超星尔雅的网课, 现在支持pdf/ppt/视频/音频的处理, 提供了设置面板可以调节, 解决了鼠标移开视频暂停的问题
  5. // @author zhyDaDa
  6. // @namespace http://zhydada.github.io/
  7. // @updateurl https://greasyfork.org/scripts/461448-zhydada-%E8%B6%85%E6%98%9F%E7%BD%91%E8%AF%BE%E5%8A%A9%E6%89%8B/code/zhyDaDa_%E8%B6%85%E6%98%9F%E7%BD%91%E8%AF%BE%E5%8A%A9%E6%89%8B.user.js
  8. // @homepage http://zhydada.github.io/scripts/4.user.js
  9. // @license personal - use only
  10.  
  11. // @match *://mooc.s.ecust.edu.cn/*
  12. // @match *://mooc1.chaoxing.com/*
  13.  
  14. // @require https://code.jquery.com/jquery-3.6.0.min.js
  15. // @icon data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAkACQAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9yL68aMTFZP3i7toZ9q55xk9hWLovi+6vtRjhurdrdZQQrbzywGcc+wP5Vf1U/Jc/Vv5msK/t5JfIaFo98Mqy/McA4zx+tfP0uG8HTcqNZt8+0m2nHfVWaT6bpnPnGbPBYulS05Grt/Mv634vubDUHhtbdrlYwN7bjwxGcfkQfxrb0+4kkWBnb5m27grZXPfB71ytlbyRtcNKybppmk+Vs4zjj8MVv6VJu+yjPRl/mK58Zw3RqSp0cJpyfFO7bloul7Xv2svuDJ82ljcZVpK3IldP5ianGzpdemW/ma53Wb99PtlePaxZ9vzDpwT/AErsZ4CzyK0bFSTxtrI8ReFTrFnHHCogZJA5JjPIwRj9R+VZ4HiinVxyWNhy0722bSXn1fyX+YZ1k8sbiIV7rlirNdWYujag+oWztJtVlfaNo68A+/rXRaT9+25/jX+Yqv4d8KnR7OSOdVuGeQuCIzwMAY/T9a1rW18uWMLCyhWGPl6V0YjijCU8ZNYenJwvZNKy6d9fvDJcnlgq9Su2uWSskt1sf//Z
  16. // @grant unsafeWindow
  17. // @grant GM_registerMenuCommand
  18. // @grant GM_unregisterMenuCommand
  19. // @grant GM_getValue
  20. // @grant GM_setValue
  21. // @run-at document-end
  22. // ==/UserScript==
  23.  
  24. (function() {
  25. 'use strict';
  26.  
  27. /**
  28. + 1.0 视频基本操作
  29. + 1.1 寻找猎物多次更新
  30. + 1.2 确保在看到绿色勾勾出现再跳
  31. + 1.3 自动判断视频有没有放过, 只挑没放过的视频看
  32. + 1.4 克服浏览器禁止自动播放的限制, 尽力剔除bug
  33. + 1.5 可以完成ppt和pdf的任务了
  34. + 1.6 提供设置面板(保存设置完善成功)
  35. + 1.7 第二种pdf完善, 全局变量问题解决
  36. + 1.8 解决了多开和节流问题
  37. + 1.9 可以处理audio音频任务了
  38. + 1.10 面板最小化
  39. + 1.11 读书任务
  40. + 1.12 记忆面板位置
  41. + 1.13 保持窗口聚焦, 击败onblur的监视
  42. + 1.14 采用creatWorker维持后台运行
  43. + 1.15 和视频一样, 到阅读任务, 自动打开阅读窗口
  44. + 1.16 持续阅读(看完了再刷新重头来过)
  45. + 1.17 修复了一个bug: 部分界面的iframe采用data传递一个JSON来表示任务类型
  46. + 1.18 能够找到答案并展示
  47. + 1.19 修复了一个bug: 有时候会出现iframe的data属性报错的情况
  48. + 1.19.1 加一个类型wmv
  49. + 1.20 修复了无法自动寻找猎物的问题(原因是对于一些iframeNode.parentNode不包含icon节点, 届时就resolve不了)
  50. + 1.20.1 加一个类型book, 但仅做受理, 由于跨域问题, 无法直接操作, 所幸一般没有任务点, 直接先留坑
  51. + 1.20.2 加了一个类型flv
  52. + 1.20.3 遇到了神奇的"讨论板块", 为了以后遇到神奇物种都不出问题, 一律直接跳过
  53. */
  54.  
  55. //#region 防止多次启动
  56. if (!(window === window.top) || typeof zhy_settings !== 'undefined') return;
  57. //#endregion
  58. //#region /*======================div:函数定义==========================*/
  59. unsafeWindow.zhy_settings = GM_getValue("zhy_settings", {
  60. skipGreen: true,
  61. playbackRate: '1',
  62. loc_x: '50px',
  63. loc_y: '50px',
  64. scrollInterval: 500,
  65. setMin: 0,
  66. });
  67. zhy_settings.done = false;
  68. unsafeWindow.zhyDaDa = {};
  69. unsafeWindow.lastCallTime = 0;
  70. /**
  71. * 醒目的控制台输出
  72. * @param {"string"} log 要打印到控制台的话
  73. * @param {"报错"|"警告"|"启动"|"提示"|"幽灵白"} color 字体颜色 可选项为["报错"|"警告"|"启动"|"提示"|"幽灵白"] 默认绿色
  74. * @param {"int"} fontSize 字体大小, 默认24
  75. */
  76. zhyDaDa.sendLog = (log, color, fontSize) => {
  77. switch (color) {
  78. case "报错":
  79. color = "red";
  80. break;
  81. case "警告":
  82. color = "#F2AB26";
  83. break;
  84. case "启动":
  85. color = "#A162F7";
  86. break;
  87. case "提示":
  88. color = "#35D4C7";
  89. break;
  90. case "幽灵白":
  91. color = "ghostwhite";
  92. break;
  93. default:
  94. color = color || "#43bb88";
  95. break;
  96. }
  97.  
  98. fontSize = fontSize || 24;
  99. console.log('%c' + log, 'color: ' + color + ';font-size: ' + fontSize + 'px;font-weight: bold;'); //text-decoration: underline;
  100. }
  101.  
  102. zhyDaDa.getBaseDocument = () => {
  103. var currentWindow = window;
  104.  
  105. while (currentWindow !== currentWindow.parent) {
  106. currentWindow = currentWindow.parent;
  107. }
  108.  
  109. return currentWindow.document;
  110. }
  111.  
  112. /**
  113. * 模拟cmd中的sleep函数
  114. * @param {"number"} d deltaTime 即要等待的时间差 照旧以毫秒为单位
  115. */
  116. function sleep(d) {
  117. return new Promise((success, fail) => {
  118. setTimeout(success, d);
  119. });
  120. }
  121. //#endregion
  122.  
  123. function create(f) {
  124. var blob = new Blob(['(' + f + ')()']);
  125. var url = window.URL.createObjectURL(blob);
  126. var worker = new Worker(url);
  127. return worker;
  128. }
  129.  
  130. const createWorker = (callback, time) => {
  131. var pollingWorker = create(`function (e) {
  132. setInterval(function () {
  133. this.postMessage(null)
  134. }, ${time})
  135. }`);
  136. pollingWorker.onmessage = callback
  137. return pollingWorker;
  138. }
  139.  
  140. const stopWorker = (vm) => {
  141. try {
  142. vm && vm.terminate()
  143. } catch (err) {
  144. console.log(err)
  145. }
  146. }
  147.  
  148. /*======================div:全局效果==========================*/
  149.  
  150.  
  151.  
  152. zhyDaDa.findPray = (skipGreen = false) => {
  153. // 节流
  154. let currentTime = Date.now();
  155. if (currentTime - unsafeWindow.lastCallTime < 3600) return false;
  156. unsafeWindow.lastCallTime = currentTime;
  157. try {
  158. let points = $("#coursetree .roundpoint,.roundpointStudent", zhyDaDa.getBaseDocument()).get();
  159. // 0有任务 1任务完成 2无任务 3任务上锁
  160. let currentPointIndex = 0;
  161. let pointsCataList = points.map((e, i) => {
  162. if (e.parentNode.className == "currents") {
  163. currentPointIndex = i;
  164. }
  165. if (e.className.indexOf("jobCount") >= 0) {
  166. return 0;
  167. } else if (e.className.indexOf("blue") >= 0) {
  168. return 1;
  169. } else if (e.className.indexOf("noJob") >= 0) {
  170. return 2;
  171. } else if (e.className.indexOf("lock") >= 0) {
  172. return 3;
  173. }
  174. });
  175.  
  176.  
  177. if (skipGreen || !unsafeWindow.zhy_settings.skipGreen) {
  178. let next = points[currentPointIndex + 1];
  179. next.parentNode.querySelector('a').click();
  180. unsafeWindow.setTimeout(() => { zhyDaDa.main(); }, 4800);
  181. } else if (points.length > 0) {
  182. let i = currentPointIndex;
  183. while (pointsCataList[++i] != 0 && i < points.length);
  184. if (i == points.length && pointsCataList[i] != 0) {
  185. i = 0;
  186. while (pointsCataList[++i] != 0 && i < currentPointIndex);
  187. if (i == currentPointIndex) zhy_settings.done = true;
  188. }
  189. let next = points[i];
  190. next.parentNode.querySelector('a').click();
  191. unsafeWindow.setTimeout(() => { zhyDaDa.main(); }, 4800);
  192. }
  193. } catch {
  194. zhyDaDa.sendLog("找不到猎物了", "报错");
  195. }
  196. }
  197.  
  198. zhyDaDa.turnToNextPage = () => {
  199. let focus = $(".currents", zhyDaDa.getBaseDocument()).get()[0];
  200. let neighbour = focus.parentNode.nextElementSibling;
  201. neighbour.firstElementChild.querySelector('a').click();
  202. }
  203.  
  204. zhyDaDa.getIframes = () => {
  205. let aaa = $("iframe", zhyDaDa.getBaseDocument()).get();
  206. if (aaa.length < 1) {
  207. return false;
  208. } else if (aaa[0].id == "iframe") {
  209. aaa = $("iframe", aaa[0].contentWindow.document).get();
  210. }
  211. return aaa;
  212. }
  213.  
  214. zhyDaDa.classifyTasks = (taskIframes) => {
  215. let tasks = []; // [$iframe,"catagory"]
  216. tasks = taskIframes.map((iframe) => {
  217. let className = iframe.className;
  218. let catagory;
  219. try {
  220. let data = iframe.getAttribute('data');
  221. if (!data) {
  222. // 说明没有data, 判断class
  223. if (className.indexOf("video") > 0) catagory = "video";
  224. else if (className.indexOf("pdf") > 0) catagory = "pdf";
  225. else if (className.indexOf("ppt") > 0) catagory = "ppt";
  226. else if (className.indexOf("audio") > 0) catagory = "audio";
  227.  
  228. } else {
  229. let json_data = JSON.parse(iframe.getAttribute('data'));
  230. let bookname = json_data.bookname; // 图书包含图书出版信息, 需要另外判断(判断依据"bookname")
  231. let type = json_data.type;
  232. let job_id = json_data.jobid;
  233. if (!type && !job_id && !bookname) throw new ReferenceError("该任务data属性既无type也无jobid, 还不是图书");
  234. else if (bookname) catagory = "book";
  235. else if (type) {
  236. switch (type) {
  237. case ".mp4":
  238. case ".wmv":
  239. case ".flv":
  240. catagory = "video";
  241. break;
  242. case ".ppt":
  243. case ".pptx":
  244. catagory = "ppt";
  245. break;
  246. case ".pdf":
  247. case ".doc":
  248. case ".docx":
  249. catagory = "pdf";
  250. break;
  251. default:
  252. throw new ReferenceError("该任务data属性给出的type不存在\n现在的type: " + type);
  253. }
  254. } else if (job_id) {
  255. if (JSON.parse(iframe.getAttribute('data')).jobid.indexOf('work') == 0) catagory = "work";
  256. else if (JSON.parse(iframe.getAttribute('data')).jobid.indexOf('read') == 0) catagory = "read";
  257. else throw new ReferenceError("该任务data属性给出的jobid不存在");
  258. }
  259. }
  260. } catch (e) {
  261. console.log(e);
  262. zhyDaDa.sendLog("任务分类时出现未知错误", "报错");
  263. // throw new ReferenceError("任务分类时出现未知错误");
  264. } finally {
  265. catagory = catagory || "unknown";
  266. }
  267.  
  268. return [iframe, catagory];
  269. });
  270. return tasks;
  271. }
  272.  
  273. zhyDaDa.dealTasks = (tasksList) => {
  274. return new Promise((resolve, reject) => {
  275. if (tasksList.length < 1) {
  276. resolve();
  277. } else {
  278. let iframeNode = tasksList[0][0];
  279. let iframeCata = tasksList[0][1];
  280. switch (iframeCata) {
  281. case "video":
  282. zhyDaDa.dealVideo(iframeNode).then(() => {
  283. tasksList.shift();
  284. zhyDaDa.dealTasks(tasksList).then(resolve);
  285. });
  286. break;
  287.  
  288. case "pdf":
  289. zhyDaDa.dealPdf(iframeNode).then(() => {
  290. tasksList.shift();
  291. zhyDaDa.dealTasks(tasksList).then(resolve);
  292. });
  293. break;
  294.  
  295. case "ppt":
  296. zhyDaDa.dealPpt(iframeNode).then(() => {
  297. tasksList.shift();
  298. zhyDaDa.dealTasks(tasksList).then(resolve);
  299. });
  300. break;
  301. case "audio":
  302. zhyDaDa.dealAudio(iframeNode).then(() => {
  303. tasksList.shift();
  304. zhyDaDa.dealTasks(tasksList).then(resolve);
  305. });
  306. break;
  307.  
  308. case "read":
  309. zhyDaDa.dealRead(iframeNode).then(() => {
  310. resolve();
  311. });
  312. break;
  313.  
  314. case "book":
  315. zhyDaDa.dealBook(iframeNode).then(() => {
  316. tasksList.shift();
  317. zhyDaDa.dealTasks(tasksList).then(resolve);
  318. });
  319. break;
  320.  
  321. case "work":
  322. zhyDaDa.dealWork(iframeNode);
  323. resolve("等用户做作业");
  324. break;
  325.  
  326. default:
  327. zhyDaDa.sendLog("该任务类型暂时未开发, 直接跳过咯", "警告");
  328. tasksList.shift();
  329. zhyDaDa.dealTasks(tasksList).then(resolve);
  330. break;
  331. }
  332. }
  333.  
  334. })
  335. }
  336.  
  337. //#region div: Video
  338. zhyDaDa.dealVideo = (iframeNode) => {
  339. return new Promise((resolve, reject) => {
  340. let iconNode = iframeNode.parentNode;
  341. if (iconNode.classList.length > 1 && zhy_settings.skipGreen) resolve();
  342. else {
  343. let videoNode = iframeNode.contentWindow.document.querySelector("video");
  344. zhyDaDa.watchVideo(videoNode).then(resolve);
  345. }
  346.  
  347. })
  348.  
  349. }
  350.  
  351. zhyDaDa.watchVideo = (video) => {
  352. return new Promise((resolve, reject) => {
  353. video.addEventListener('ended', onEnded); // 添加 ended 事件处理程序
  354.  
  355. video.volume = 0;
  356. video.play(); // 开始播放视频
  357. video.playbackRate = Number(zhy_settings.playbackRate);
  358. video.pause = () => { return true }
  359.  
  360. // 1.21新增: 针对视频中出现的问题枚举解答
  361. try {
  362. // let video = $0;
  363. let tkTopic = video.parentElement.querySelector(".tkTopic");
  364. let type = tkTopic.querySelector(".tkTopic_title").innerText;
  365. let choices = tkTopic.querySelector(".tkItem_ul").children;
  366. let submit = tkTopic.querySelector("#videoquiz-submit");
  367.  
  368. switch (type) {
  369. case "[单选题]":
  370. case "[判断题]":
  371. for (let i = 0; i < choices.length; i++) {
  372. const e = choices[i];
  373. e.firstChild.click();
  374. submit.click();
  375. }
  376. case "[多选题]":
  377. let len = choices.length;
  378. let dp = (x) => {
  379. if (x == len) {
  380. submit.click();
  381. return;
  382. }
  383. const e = choices[x];
  384. e.firstChild.click();
  385. dp(x + 1);
  386. e.firstChild.click();
  387. dp(x + 1);
  388. }
  389. dp(0);
  390. default:
  391. throw new ReferenceError("视频内的问题, 出现了新的题型: " + type);
  392. }
  393.  
  394. } catch (e) {}
  395.  
  396. function onEnded() {
  397. video.removeEventListener('ended', onEnded); // 删除 ended 事件处理程序
  398. resolve(); // 视频播放完成,设置 Promise 状态为 fulfilled
  399. }
  400. });
  401. }
  402. //#endregion
  403.  
  404. //#region div: Pdf
  405. zhyDaDa.dealPdf = (iframeNode) => {
  406. return new Promise((resolve, reject) => {
  407. let iconNode = iframeNode.parentNode;
  408. let img = iframeNode.contentWindow.document.querySelector("#img");
  409. if (iconNode.classList.length > 1 && zhy_settings.skipGreen) resolve();
  410. else {
  411. img.scrollTop = img.scrollHeight;
  412. let btn = iframeNode.contentWindow.document.querySelector(".mkeRbtn");
  413. let num = iframeNode.contentWindow.document.querySelector(".all").innerText;
  414. num = Number(num);
  415. for (let i = 0; i < num + 2; i++) {
  416. btn.click();
  417. }
  418. if (iconNode.firstChild.className.indexOf("icon") == -1) resolve();
  419. }
  420. });
  421. }
  422. //#endregion
  423.  
  424. //#region div: Ppt
  425. zhyDaDa.dealPpt = (iframeNode) => {
  426. return new Promise((resolve, reject) => {
  427. try {
  428. let iconNode = iframeNode.parentNode;
  429. if (iconNode.classList.length > 1 && zhy_settings.skipGreen) resolve();
  430. else {
  431. let btn = iframeNode.contentWindow.document.querySelector(".nextBtn");
  432. if (!btn) {
  433. zhyDaDa.sendLog("这个PPT无预览, 非任务点, 直接跳过", "提示");
  434. resolve();
  435. }
  436. let num = iframeNode.contentWindow.document.querySelector(".all").innerText;
  437. num = Number(num);
  438. for (let i = 0; i < num + 2; i++) {
  439. btn.click();
  440. }
  441. if (iconNode.firstChild.className.indexOf("icon") == -1) resolve();
  442. }
  443. } catch (e) {
  444. zhyDaDa.sendLog("遇到了异种ppt, 报错信息见下方, 请手动处理, 本次跳过", "警告");
  445. console.log(e);
  446. resolve();
  447. }
  448. })
  449.  
  450. }
  451. //#endregion
  452.  
  453. //#region div: Audio
  454. zhyDaDa.dealAudio = (iframeNode) => {
  455. return new Promise((resolve, reject) => {
  456. let iconNode = iframeNode.parentNode;
  457. if (iconNode.classList.length > 1 && zhy_settings.skipGreen) resolve();
  458. else {
  459. let btn = iframeNode.contentWindow.document.querySelector(".vjs-play-control");
  460. let audioElement = iframeNode.contentWindow.document.querySelector("audio");
  461.  
  462. audioElement.addEventListener('ended', onEnded); // 添加 ended 事件处理程序
  463.  
  464. audioElement.volume = 0;
  465. audioElement.playbackRate = Number(zhy_settings.playbackRate);
  466. audioElement.pause = () => { return true }
  467. audioElement.play(); // 开始播放音频
  468.  
  469. function onEnded() {
  470. audioElement.removeEventListener('ended', onEnded); // 删除 ended 事件处理程序
  471. resolve(); // 音频播放完成,设置 Promise 状态为 fulfilled
  472. }
  473. }
  474.  
  475. })
  476.  
  477. }
  478. //#endregion
  479.  
  480. //#region div: Read
  481. zhyDaDa.dealRead = (iframeNode) => {
  482. return new Promise((resolve, reject) => {
  483. // 搜索所有的span, 找出其中innerText为"去阅读"的span, 点击它
  484. let toReadSpan;
  485. let frame_content = iframeNode.contentWindow.document.querySelector("#frame_content");
  486. let spans = frame_content.contentWindow.document.getElementsByTagName("span");
  487. for (let i = 0; i < spans.length; i++) {
  488. if (spans[i].innerText == "去阅读") {
  489. toReadSpan = spans[i];
  490. break;
  491. }
  492. }
  493. toReadSpan.click();
  494. resolve();
  495. });
  496. }
  497. //#endregion
  498.  
  499. //#region div: Book
  500. zhyDaDa.dealBook = (iframeNode) => {
  501. return new Promise((resolve, reject) => {
  502. let frame_content = iframeNode.contentWindow.document.querySelector("[name='bookifame']");
  503. // todo: 以下内容涉及跨域问题, 这本图书是另一个主机地址的资源, 无法直接操作, 所幸一般没有任务点, 直接先留坑
  504. // let readWeb = frame_content.contentWindow.document.querySelector("#Readweb");
  505. // step = readWeb.scrollHeight / 10;
  506. // for (let i = 0; i < 10; i++) {
  507. // readWeb.scrollTo(0, step * i);
  508. // }
  509. resolve();
  510. });
  511. }
  512.  
  513. //#endregion
  514.  
  515. //#region div: Work
  516. zhyDaDa.dealWork = (iframeNode) => {
  517. let frame_content = iframeNode.contentWindow.document.querySelector("#frame_content");
  518. let form = frame_content.contentWindow.document.querySelector("#form1");
  519. let sss = form.serialize();
  520. let pat = /answer([0-9]*?)=(.*?)&answertype([0-9]*?)=([0-9]+?)&/g
  521. let counts = 0;
  522. let answerArea = zhyDaDa.getBaseDocument().querySelector("#answerArea");
  523. let answerString = "";
  524. while ((result = pat.exec(sss)) != null) {
  525. let answer = result[2];
  526. let answertype = result[4];
  527. answerString += `第${++counts}题: ${answer}\n`;
  528. }
  529. answerArea.value = answerString;
  530. }
  531. //#endregion
  532.  
  533. zhyDaDa.main = () => {
  534.  
  535. // 节流
  536. let currentTime = Date.now();
  537. if (currentTime - unsafeWindow.lastCallTime < 3600) return false;
  538. unsafeWindow.lastCallTime = currentTime;
  539. if (zhy_settings.done) return true;
  540. let taskIframes = zhyDaDa.getIframes();
  541. if (!taskIframes) return false;
  542. let classifiedTasksList = zhyDaDa.classifyTasks(taskIframes);
  543. zhyDaDa.dealTasks(classifiedTasksList).then(() => {
  544. zhyDaDa.sendLog("本次任务已完成, 正在寻找下一个猎物", "提示");
  545. unsafeWindow.setTimeout(() => { zhyDaDa.findPray(zhyDaDa.skipGreen); }, 4500);
  546. });
  547. }
  548. // 插入控制面板浮窗
  549. zhyDaDa.insertPanel = () => {
  550. let _style = `
  551. <style>
  552. #zhy_settings_panel_4 {
  553. position: fixed;
  554. top: ${zhy_settings.loc_y || "50px"};
  555. left: ${zhy_settings.loc_x || "50px"};
  556. background-color: #fff;
  557. border: 1px solid #ccc;
  558. border-radius: 5px;
  559. padding: 10px;
  560. font-size: 12px;
  561. z-index: 99999;
  562. width: 180px;
  563. }
  564.  
  565. #zhy_settings_panel_4 h3 {
  566. margin-top: 0;
  567. margin-bottom: 10px;
  568. }
  569. #drag-handle {
  570. width: 100%;
  571. height: 18px;
  572. cursor: move;
  573. background: grey;
  574. border-radius: 10px;
  575. margin-bottom: 10px;
  576. text-align: center;
  577. color: #bbb;
  578. user-select: none;
  579. min-width: 80px;
  580. }
  581.  
  582. #zhy_settings_panel_4 .zhy_settings_item {
  583. margin-bottom: 10px;
  584. }
  585.  
  586. #zhy_settings_panel_4 label {
  587. display: inline-block;
  588. width: 120px;
  589. }
  590.  
  591. #playbackRateContainer {
  592. display: grid;
  593. }
  594.  
  595. #playbackRateContainer input[type="radio"] {
  596. display: none;
  597. }
  598.  
  599. #playbackRateContainer label {
  600. flex: 1;
  601. text-align: center;
  602. padding: 5px 10px;
  603. border: 1px solid #ccc;
  604. border-radius: 5px;
  605. cursor: pointer;
  606. }
  607.  
  608. #playbackRateContainer input[type="radio"]:checked + label {
  609. background-color: #ccc;
  610. }
  611.  
  612. #scrollInterval{
  613. width: 120px;
  614. }
  615.  
  616. </style>
  617. `;
  618. let _html = `
  619. <div id="zhy_settings_panel_4">
  620. <div id="drag-handle" flag="0">双击最小化</div>
  621. <h3>设置面板</h3>
  622. <div class="zhy_settings_item" id="config">
  623. <label for="setMin">默认最小化面板</label>
  624. <input type="checkbox" id="setMin" checked>
  625. </div>
  626. <div class="zhy_settings_item">
  627. <label for="skipGreenToggle">跳过已经看完的任务点:</label>
  628. <input type="checkbox" id="skipGreenToggle" checked>
  629. </div>
  630. <div class="zhy_settings_item">
  631. <label for="startWork">启动刷课:</label>
  632. <button id="startWork">启动</button>
  633. </div>
  634. <div class="zhy_settings_item">
  635. <label for="goToNextPrey">直接前往下一课:</label>
  636. <button id="goToNextPrey">启动</button>
  637. </div>
  638. <div class="zhy_settings_item">
  639. <label>视频播放速率:</label>
  640. <div id="playbackRateContainer">
  641. <input type="radio" id="rate1" name="playbackRate" value="1" checked>
  642. <label for="rate1">1</label>
  643.  
  644. <input type="radio" id="rate1.25" name="playbackRate" value="1.25">
  645. <label for="rate1.25">1.25</label>
  646.  
  647. <input type="radio" id="rate1.5" name="playbackRate" value="1.5">
  648. <label for="rate1.5">1.5</label>
  649.  
  650. <input type="radio" id="rate2" name="playbackRate" value="2">
  651. <label for="rate2">2</label>
  652. </div>
  653. </div>
  654. <hr>
  655. <div class="zhy_settings_item">
  656. <label for="scrollInterval">滚动间隔(毫秒):</label>
  657. <input type="number" id="scrollInterval" value="500">
  658. </div>
  659. <hr>
  660. <div class="zhy_settings_item">
  661. <label for="answerArea">章节测验答案: </label>
  662. <input type="textarea" id="answerArea" value="">
  663. </div>
  664.  
  665. </div>
  666. `;
  667.  
  668. document.documentElement.insertAdjacentHTML('beforeend', _style + _html);
  669.  
  670.  
  671. // 获取设置面板元素
  672. var panel = document.getElementById("zhy_settings_panel_4");
  673.  
  674. // 获取浮窗标题栏元素
  675. let panelHeader = document.getElementById("drag-handle");
  676.  
  677. // 定义变量记录鼠标按下时的坐标和面板的初始位置
  678. let startX, startY, panelX, panelY;
  679.  
  680. // 给标题栏添加鼠标按下事件监听器
  681. panelHeader.addEventListener("mousedown", function(e) {
  682. // 记录鼠标按下时的坐标和面板的初始位置
  683. startX = e.clientX;
  684. startY = e.clientY;
  685. panelX = panel.offsetLeft;
  686. panelY = panel.offsetTop;
  687.  
  688. // 给 document 添加鼠标移动和松开事件监听器
  689. document.addEventListener("mousemove", movePanel);
  690. document.addEventListener("mouseup", stopPanel);
  691. });
  692.  
  693. // 双击标题栏最小化面板
  694. panelHeader.addEventListener("dblclick", function() {
  695. // 判断面板是否已经最小化
  696. // 如果panel的flag属性值不为"1", 说明面板没有最小化
  697. if (panel.getAttribute("flag") != "1") {
  698. // 将panel中除了标题栏之外的元素隐藏
  699. for (let i = 1; i < panel.children.length; i++) {
  700. panel.children[i].style.display = "none";
  701. }
  702. panelHeader.innerHTML = "双击恢复";
  703. // 修改panel的flag属性为"1"
  704. panel.setAttribute("flag", "1");
  705. } else {
  706. for (let i = 1; i < panel.children.length; i++) {
  707. panel.children[i].style.display = "block";
  708. }
  709. panelHeader.innerHTML = "双击最小化";
  710. panelHeader.style.width = "100%";
  711. // 修改panel的flag属性为"0"
  712. panel.setAttribute("flag", "0");
  713. }
  714. });
  715.  
  716. // 移动浮窗的函数
  717. function movePanel(e) {
  718. // 计算鼠标移动的距离
  719. var deltaX = e.clientX - startX;
  720. var deltaY = e.clientY - startY;
  721.  
  722. // 更新面板的位置
  723. panel.style.left = panelX + deltaX + "px";
  724. panel.style.top = panelY + deltaY + "px";
  725. }
  726.  
  727. // 停止移动浮窗的函数
  728. function stopPanel() {
  729. // 记录位置
  730. zhy_settings.loc_x = panel.style.left;
  731. zhy_settings.loc_y = panel.style.top;
  732. GM_setValue("zhy_settings", zhy_settings);
  733. // 移除鼠标移动和松开事件监听器
  734. document.removeEventListener("mousemove", movePanel);
  735. document.removeEventListener("mouseup", stopPanel);
  736. }
  737.  
  738.  
  739. // 获取设置面板中的元素
  740. let skipGreenToggle = document.getElementById("skipGreenToggle");
  741. let startWork = document.getElementById("startWork");
  742. let goToNextPrey = document.getElementById("goToNextPrey");
  743. let playbackRateRadios = document.getElementsByName("playbackRate");
  744. let setMin = document.getElementById("setMin");
  745.  
  746. skipGreenToggle.checked = zhy_settings.skipGreen;
  747. let inputElement = $(`input[name='playbackRate'][value='${zhy_settings.playbackRate}']`).get();
  748. (inputElement.length > 0) && (inputElement[0].checked = true);
  749. setMin.checked = zhy_settings.setMin === 1 ? true : false;
  750. // 如果设置了默认最小化面板, 则最小化面板(即模拟双击标题栏)
  751. if (zhy_settings.setMin === 1) {
  752. panelHeader.dispatchEvent(new MouseEvent("dblclick"));
  753. }
  754.  
  755. // 定义回调函数
  756. function settingsChanged(ratio) {
  757. zhy_settings.setMin = zhyDaDa.getBaseDocument().getElementById("setMin").checked ? 1 : 0;
  758. zhy_settings.skipGreen = zhyDaDa.getBaseDocument().getElementById("skipGreenToggle").checked ? 1 : 0;
  759. zhy_settings.playbackRate = ratio || zhy_settings.playbackRate;
  760. zhy_settings.scrollInterval = zhyDaDa.getBaseDocument().getElementById("scrollInterval").value;
  761. GM_setValue("zhy_settings", zhy_settings);
  762. }
  763.  
  764. // 给setMin添加change事件监听器,当状态发生改变时调用回调函数
  765. setMin.addEventListener("change", () => { settingsChanged(); });
  766.  
  767. // 给skipGreenToggle添加change事件监听器,当状态发生改变时调用回调函数
  768. skipGreenToggle.addEventListener("change", () => { settingsChanged(); });
  769.  
  770. // 给startWork添加click事件监听器,当点击时调用回调函数
  771. startWork.addEventListener("click", () => { zhyDaDa.main(); });
  772.  
  773. // 给goToNextPrey添加click事件监听器,当点击时调用回调函数
  774. goToNextPrey.addEventListener("click", () => { zhyDaDa.findPray(true); });
  775.  
  776. // 给playbackRateRadios中的每个单选按钮添加click事件监听器,当点击时调用回调函数
  777. playbackRateRadios.forEach(function(radio) {
  778. radio.addEventListener("click", (event) => { settingsChanged(event.target.value); });
  779. });
  780.  
  781. // 给scrollInterval添加change事件监听器,当状态发生改变时调用回调函数
  782. document.getElementById("scrollInterval").addEventListener("change", (event) => {
  783. zhy_settings.scrollInterval = event.target.value;
  784. GM_setValue("zhy_settings", zhy_settings);
  785. });
  786. }
  787.  
  788. /**
  789. * 读书功能实现
  790. */
  791. zhyDaDa.readBook = () => {
  792. //缓慢滚动页面到底部, 每2秒滚动一次, 滚动距离为屏幕高度的1/40
  793. let scrollInterval = createWorker(() => {
  794. // 滚动body
  795. document.body.scrollBy(0, window.innerHeight / 40);
  796. if (window.innerHeight + document.body.scrollTop >= document.body.children[0].offsetHeight) {
  797. // 找到id为loadbutton的a标签, 点击它, 找不到就clearInterval
  798. let loadButton = document.getElementById("loadbutton");
  799. if (loadButton) {
  800. loadButton.click();
  801. } else {
  802. location.reload();
  803. }
  804. }
  805. }, zhy_settings.scrollInterval || 500);
  806.  
  807. }
  808.  
  809. /**
  810. * 保持屏幕焦点
  811. */
  812. zhyDaDa.keepFocus = () => {
  813. const videoDom = document.createElement('video');
  814. const hiddenCanvas = document.createElement('canvas');
  815. videoDom.setAttribute('style', 'display:none');
  816. videoDom.setAttribute('muted', '');
  817. videoDom.muted = true;
  818. videoDom.setAttribute('autoplay', '');
  819. videoDom.autoplay = true;
  820. videoDom.setAttribute('playsinline', '');
  821. hiddenCanvas.setAttribute('style', 'display:none');
  822. hiddenCanvas.setAttribute('width', '1');
  823. hiddenCanvas.setAttribute('height', '1');
  824. hiddenCanvas.getContext('2d').fillRect(0, 0, 1, 1);
  825. videoDom.srcObject = hiddenCanvas.captureStream();
  826. try {
  827. unsafeWindow.onblur = () => {
  828. return true;
  829. }
  830. window.onblur = () => {
  831. return true;
  832. }
  833. unsafeWindow.focus();
  834. window.focus();
  835. } catch (e) {
  836. return true;
  837. }
  838. }
  839.  
  840. zhyDaDa.sendLog("\n###################\n##zhyDaDa 网课助手##\n###################", "启动", 32);
  841. unsafeWindow.setTimeout(() => { zhyDaDa.insertPanel() }, 1000);
  842. if (document.title.indexOf("全屏显示") > -1) {
  843. unsafeWindow.setTimeout(() => { zhyDaDa.readBook() }, 2400);
  844. } else {
  845. unsafeWindow.setTimeout(() => { zhyDaDa.main() }, 4800);
  846. createWorker(() => { zhyDaDa.main() }, 15000);
  847. }
  848. createWorker(() => { zhyDaDa.keepFocus() }, 5000);
  849. })();