UOOC assistant

【使用前先看介绍/有问题可反馈】【欢迎一键三连(好评+打赏+收藏),你的支持是作者维护下去的最大动力!】UOOC优课联盟助手(UOOC assistant):可选是否倍速(若取消勾选则一倍速播放),可选是否静音(若取消勾选则恢复原音量),可选是否播放(若取消勾选则暂停播放),可选是否连播(若取消勾选则循环播放),离开页面保持状态,自动回答视频中途弹出问题;键盘左右方向键可以控制视频快进/快退,上下方向键可以控制音量增大/减小,空格键可以控制播放/暂停;自动签到慕课;如果视频标题下面出现 倍速/静音/播放/连播 选项说明脚本正常启动运行。

当前为 2020-11-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name UOOC assistant
  3. // @name:en UOOC assistant
  4. // @namespace http://tampermonkey.net/
  5. // @version 0.9.5
  6. // @description 【使用前先看介绍/有问题可反馈】【欢迎一键三连(好评+打赏+收藏),你的支持是作者维护下去的最大动力!】UOOC优课联盟助手(UOOC assistant):可选是否倍速(若取消勾选则一倍速播放),可选是否静音(若取消勾选则恢复原音量),可选是否播放(若取消勾选则暂停播放),可选是否连播(若取消勾选则循环播放),离开页面保持状态,自动回答视频中途弹出问题;键盘左右方向键可以控制视频快进/快退,上下方向键可以控制音量增大/减小,空格键可以控制播放/暂停;自动签到慕课;如果视频标题下面出现 倍速/静音/播放/连播 选项说明脚本正常启动运行。
  7. // @description:en 【使用前先看介绍/有问题可反馈】【欢迎一键三连(好评+打赏+收藏),你的支持是作者维护下去的最大动力!】UOOC优课联盟助手(UOOC assistant):可选是否倍速(若取消勾选则一倍速播放),可选是否静音(若取消勾选则恢复原音量),可选是否播放(若取消勾选则暂停播放),可选是否连播(若取消勾选则循环播放),离开页面保持状态,自动回答视频中途弹出问题;键盘左右方向键可以控制视频快进/快退,上下方向键可以控制音量增大/减小,空格键可以控制播放/暂停;自动签到慕课;如果视频标题下面出现 倍速/静音/播放/连播 选项说明脚本正常启动运行。
  8. // @author cc
  9. // @include http://www.uooc.net.cn/*
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15. const jsName = 'UOOC-assistant.js';
  16. const RECURSION_DURATION = 500;
  17. if (location.host == 'www.uooc.net.cn') {
  18. console.log(`excute ${jsName}`);
  19. let recursive = () => {
  20. let extraTime = 0;
  21. try {
  22. let done = false;
  23. let video = document.querySelector('#player_html5_api');
  24. if (video) {
  25. if (document.getElementById('rate').checked)
  26. video.playbackRate = 2;
  27. else
  28. video.playbackRate = 1;
  29. if (document.getElementById('volume').checked)
  30. video.muted = true;
  31. else
  32. video.muted = false;
  33. if (document.getElementById('play').checked && !video.ended)
  34. video.play();
  35. else
  36. video.pause();
  37. if (video.ended) {
  38. done = true;
  39. };
  40. let quizLayer = document.querySelector('#quizLayer');
  41. if (quizLayer && quizLayer.style.display != 'none') {
  42. if (done) {
  43. setTimeout(() => {
  44. document.querySelectorAll('.layui-layer-shade').forEach(e => e.style.display = 'none');
  45. }, RECURSION_DURATION << 1);
  46. };
  47. let source = JSON.parse(document.querySelector('div[uooc-video]').getAttribute('source'));
  48. let quizList = source.quiz;
  49. let quizIndex = 0;
  50. let quizQuestion = document.querySelector('.smallTest-view .ti-q-c').innerHTML;
  51. for (let i = 0; i < quizList.length; i++) {
  52. if (quizList[i].question == quizQuestion) {
  53. quizIndex = i;
  54. break;
  55. };
  56. };
  57. let quizAnswer = eval(quizList[quizIndex].answer);
  58. let quizOptions = quizLayer.querySelector('div.ti-alist');
  59. for (let ans of quizAnswer) {
  60. let labelIndex = ans.charCodeAt() - 'A'.charCodeAt();
  61. quizOptions.children[labelIndex].click();
  62. }; // end for
  63. quizLayer.querySelector('button').click();
  64. extraTime = 1000;
  65. }; // end if
  66. if (!done) {
  67. if (video.paused && document.getElementById('play').checked) {
  68. video.play();
  69. } else {
  70. document.querySelectorAll('.layui-layer-shade, #quizLayer').forEach(e => e.style.display = 'none');
  71. };
  72. };
  73. }; // end if (video)
  74. if (!done) {
  75. setTimeout(recursive, RECURSION_DURATION + extraTime);
  76. } else if (video) {
  77. if (!document.getElementById('continue').checked) {
  78. video.currentTime = 0;
  79. // video.ended = false;
  80. setTimeout(recursive, RECURSION_DURATION + extraTime);
  81. } else {
  82. let current_video = document.querySelector('.basic.active');
  83. let next_part = current_video.parentNode;
  84. let next_video = current_video;
  85. // 定义判断是否视频的函数
  86. let isVideo = node => Boolean(node.querySelector('span.icon-video'));
  87. // 定义是否可返回上一级目录的函数
  88. let canBack = () => {
  89. return Boolean(next_part.parentNode.parentNode.tagName == 'LI');
  90. };
  91. // 定义更新至后续视频的函数
  92. let toNextVideo = () => {
  93. next_video = next_video.nextElementSibling;
  94. while (next_video && !isVideo(next_video)) {
  95. next_video = next_video.nextElementSibling;
  96. };
  97. };
  98. // 定义判断是否存在视频的函数
  99. let isExistsVideo = () => {
  100. let _video = next_part.firstElementChild;
  101. while (_video && !isVideo(_video)) {
  102. _video = _video.nextElementSibling;
  103. };
  104. return Boolean(_video && isVideo(_video));
  105. };
  106. // 定义判断是否存在后续视频的函数
  107. let isExistsNextVideo = () => {
  108. let _video = current_video.nextElementSibling;
  109. while (_video && !isVideo(_video)) {
  110. _video = _video.nextElementSibling;
  111. };
  112. return Boolean(_video && isVideo(_video));
  113. };
  114. // 定义检查文件后是否存在后续目录的函数
  115. let isExistsNextListAfterFile = () => {
  116. let part = current_video.parentNode.nextElementSibling;
  117. return Boolean(part && part.childElementCount > 0);
  118. };
  119. // 定义更新文件后的后续目录的函数
  120. let toNextListAfterFile = () => {
  121. next_part = next_part.nextElementSibling;
  122. };
  123. // 定义返回上一级的函数
  124. let toOuterList = () => {
  125. next_part = next_part.parentNode.parentNode;
  126. };
  127. // 定义返回主条目的函数
  128. let toOuterItem = () => {
  129. next_part = next_part.parentNode;
  130. };
  131. // 定义检查列表后是否存在后续目录的函数
  132. let isExistsNextListAfterList = () => {
  133. return Boolean(next_part.nextElementSibling);
  134. };
  135. // 定义进入列表后的后续目录的函数
  136. let toNextListAfterList = () => {
  137. next_part = next_part.nextElementSibling;
  138. };
  139. // 定义展开目录的函数
  140. let expandList = () => {
  141. next_part.firstElementChild.click();
  142. };
  143. // 定义进入展开目录的第一个块级元素的函数
  144. let toExpandListFirstElement = () => {
  145. next_part = next_part.firstElementChild.nextElementSibling;
  146. if (next_part.classList.contains('unfoldInfo')) {
  147. next_part = next_part.nextElementSibling;
  148. };
  149. };
  150. // 定义判断块级元素是否目录列表的函数
  151. let isList = () => {
  152. return Boolean(next_part.tagName == 'UL');
  153. };
  154. // 定义目录列表的第一个目录的函数
  155. let toInnerList = () => {
  156. next_part = next_part.firstElementChild;
  157. };
  158. // 定义进入文件列表的第一个视频的函数
  159. let toFirstVideo = () => {
  160. next_video = next_part.firstElementChild;
  161. while (next_video && !isVideo(next_video)) {
  162. next_video = next_video.nextElementSibling;
  163. };
  164. };
  165. // 定义模式
  166. let mode = {
  167. FIRST_VIDEO: 'FIRST_VIDEO',
  168. NEXT_VIDEO: 'NEXT_VIDEO',
  169. LAST_LIST: 'LAST_LIST',
  170. NEXT_LIST: 'NEXT_LIST',
  171. INNER_LIST: 'INNER_LIST',
  172. OUTER_LIST: 'OUTER_LIST',
  173. OUTER_ITEM: 'OUTER_ITEM',
  174. }
  175. // 定义搜索函数
  176. let search = (_mode) => {
  177. switch (_mode) {
  178. case mode.FIRST_VIDEO: // mode = 0
  179. if (isExistsVideo()) {
  180. toFirstVideo();
  181. next_video.click();
  182. setTimeout(recursive, RECURSION_DURATION);
  183. } else {
  184. // perhaps there is an exam, end recursion
  185. };
  186. break;
  187. case mode.NEXT_VIDEO: // mode == 1
  188. if (isExistsNextVideo()) {
  189. toNextVideo();
  190. next_video.click();
  191. setTimeout(recursive, RECURSION_DURATION);
  192. } else if (isExistsNextListAfterFile()) {
  193. search(mode.LAST_LIST);
  194. } else {
  195. search(mode.OUTER_ITEM);
  196. };
  197. break;
  198. case mode.LAST_LIST: // mode == 2
  199. toNextListAfterFile();
  200. toInnerList();
  201. search(mode.INNER_LIST);
  202. break;
  203. case mode.NEXT_LIST: // mode == 3
  204. toNextListAfterList();
  205. search(mode.INNER_LIST);
  206. break;
  207. case mode.INNER_LIST: // mode == 4
  208. expandList();
  209. (function waitForExpand () {
  210. if (next_part.firstElementChild.nextElementSibling) {
  211. toExpandListFirstElement();
  212. if (isList()) {
  213. toInnerList();
  214. search(mode.INNER_LIST);
  215. } else {
  216. search(mode.FIRST_VIDEO);
  217. };
  218. } else {
  219. setTimeout(waitForExpand, RECURSION_DURATION);
  220. };
  221. })();
  222. break;
  223. case mode.OUTER_LIST: // mode == 5
  224. toOuterList();
  225. if (isExistsNextListAfterList()) {
  226. search(mode.NEXT_LIST);
  227. } else if (canBack()) {
  228. search(mode.OUTER_LIST);
  229. } else {
  230. // perhaps there is no next list
  231. };
  232. break;
  233. case mode.OUTER_ITEM: // mode == 6
  234. toOuterItem();
  235. if (isExistsNextListAfterList()) {
  236. toNextListAfterList();
  237. search(mode.INNER_LIST);
  238. } else if (canBack()){
  239. search(mode.OUTER_LIST);
  240. } else {
  241. // perhaps there is no list
  242. };
  243. break;
  244. default:
  245. break;
  246. };
  247. };
  248. try {
  249. search(mode.NEXT_VIDEO);
  250. } catch (e) {
  251. console.log(e);
  252. };
  253. };
  254. };
  255. } catch (e) {
  256. console.log(e);
  257. };
  258. }; // end recursive
  259. let wait = () => {
  260. if (document.readyState == 'complete') {
  261. console.log('ready to set checkboxes.');
  262. let getCheckbox = (name, text) => {
  263. let p = document.createElement('p');
  264. p.style.color = '#cccccc';
  265. let checkbox = document.createElement('input');
  266. checkbox.id = name;
  267. checkbox.type = 'checkbox';
  268. checkbox.checked = true;
  269. checkbox.name = name;
  270. checkbox.value = name;
  271. checkbox.style.marginLeft = '25px';
  272. p.append(checkbox);
  273. let label = document.createElement('label');
  274. label.setAttribute('for', name);
  275. label.innerText = text;
  276. label.style.marginLeft = '15px';
  277. p.append(label);
  278. p.style.margin = '5px';
  279. return p;
  280. };
  281. let rateCheckbox = getCheckbox('rate', '倍速');
  282. let volumeCheckbox = getCheckbox('volume', '静音');
  283. let playCheckbox = getCheckbox('play', '播放');
  284. let continueCheckbox = getCheckbox('continue', '连播');
  285. let head = document.querySelector('.learn-head');
  286. let container = document.createElement('div');
  287. container.id = 'container';
  288. container.style.display = 'flex';
  289. container.style.flexDirection = 'row';
  290. container.style.alignItems = 'center';
  291. container.append(rateCheckbox);
  292. container.append(volumeCheckbox);
  293. container.append(playCheckbox);
  294. container.append(continueCheckbox);
  295. let div = document.createElement('div');
  296. div.innerHTML = '( 提示:键盘的 \u2190 和 \u2192 可以控制快进/快退,\u2191 和 \u2193 可以控制音量增大/减小,空格键可以控制播放/暂停 )';
  297. div.style.color = '#cccccc';
  298. div.style.fontWeight = 'bold';
  299. div.style.height = 'min-height';
  300. div.style.margin = '0px 20px 0px';
  301. div.style.padding = '2px 5px 2px';
  302. div.style.bordRadius = '5px';
  303. div.style.fontSize = '13px';
  304. container.appendChild(div);
  305. let a = document.createElement('a');
  306. a.href = 'https://s1.ax1x.com/2020/11/08/BTeRqe.png';
  307. a.target = '_blank';
  308. a.innerHTML = '<u>本脚本使用完全免费,您的打赏是作者维护下去的最大动力!点此打赏作者😊</u>';
  309. a.style.color = '#cccccc';
  310. a.style.fontWeight = 'bold';
  311. a.style.height = 'min-height';
  312. a.style.margin = '0px 20px 0px';
  313. a.style.padding = '2px 5px 2px';
  314. a.style.bordRadius = '5px';
  315. a.style.fontSize = '13px';
  316. container.appendChild(a);
  317. head.append(container);
  318. console.log('checkboxes have been set.');
  319. let cid = location.href.match(/\d+/g)[0];
  320. let httpRequest = new XMLHttpRequest();
  321. httpRequest.open('GET', `http://www.uooc.net.cn/home/course/info?cid=${cid}`, true);
  322. httpRequest.send();
  323. httpRequest.onreadystatechange = function () {
  324. if (httpRequest.readyState == 4 && httpRequest.status == 200) {
  325. console.log('Automatic sign-in succeeded.');
  326. };
  327. };
  328. document.onkeydown = (event) => {
  329. let k = event.key;
  330. let complete = false;
  331. let div = document.querySelector('div.basic.active');
  332. if (div && div.classList.contains('complete'))
  333. complete = true;
  334. let video = document.getElementById('player_html5_api');
  335. if (video) {
  336. switch (k) {
  337. case 'ArrowLeft':
  338. video.currentTime -= 10;
  339. break;
  340. case 'ArrowRight':
  341. if (complete)
  342. video.currentTime += 10;
  343. break;
  344. case 'ArrowUp':
  345. if (video.volume + 0.1 <= 1.0)
  346. video.volume += 0.1;
  347. else
  348. video.volume = 1.0;
  349. break;
  350. case 'ArrowDown':
  351. if (video.volume - 0.1 >= 0.0)
  352. video.volume -= 0.1;
  353. else
  354. video.volume = 0.0;
  355. break;
  356. case ' ':
  357. let continueCheckbox = document.getElementById('play');
  358. continueCheckbox.checked = !continueCheckbox.checked;
  359. break;
  360. };
  361. };
  362. };
  363. recursive();
  364. } else {
  365. setTimeout(wait, RECURSION_DURATION);
  366. };
  367. }; // end wait
  368. wait();
  369. }
  370. })();