EnaeaAssistant-学习公社全自动4X速

Automate course learning on enaea.edu.cn with 4X video speed control

  1. // ==UserScript==
  2. // @name EnaeaAssistant-学习公社全自动4X速
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.8.4
  5. // @license MIT
  6. // @description Automate course learning on enaea.edu.cn with 4X video speed control
  7. // @author beabaed@gmail.com, KKG&DS&CL
  8. // @match https://study.enaea.edu.cn/circleIndexRedirect.do*
  9. // @match https://study.enaea.edu.cn/viewerforccvideo.do*
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_addStyle
  13. // @run-at document-end
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. 'use strict';
  18. const SCRIPT_VERSION = '1.8.2'; // Script version
  19. let currentPagePath = window.location.pathname;
  20. let continuePlay = setInterval(continuePlayVideo, 3000);
  21. let continueChapter = setInterval(findAndStartChapter, 3000);
  22. let continueLesson = setInterval(clickNextPageButton, 3000);
  23. let pageLoading = false;
  24. let currentPageComplete = true;
  25. let playbackSpeed = 4; // Set to 4X for video speed control, it cannot Faster more.
  26.  
  27. // Add status display with semi-transparent background
  28. GM_addStyle(`
  29. #enaeaHelperStatus {
  30. position: fixed;
  31. top: 10px;
  32. right: 10px;
  33. background: rgba(0, 0, 0, 0.6); /* Semi-transparent background */
  34. color: white;
  35. padding: 10px;
  36. border-radius: 5px;
  37. font-family: Arial, sans-serif;
  38. font-size: 14px;
  39. z-index: 10000;
  40. display: block;
  41. }
  42. `);
  43.  
  44. // Wait for the DOM to be fully loaded before creating the status bar
  45. window.addEventListener('load', () => {
  46. let statusDiv = document.createElement('div');
  47. statusDiv.id = 'enaeaHelperStatus';
  48. document.body.appendChild(statusDiv);
  49. updateStatus(`Script loaded (v${SCRIPT_VERSION})`);
  50. });
  51.  
  52. function updateStatus(message) {
  53. let statusDiv = document.getElementById('enaeaHelperStatus');
  54. if (statusDiv) {
  55. statusDiv.textContent = `EnaeaHelper v${SCRIPT_VERSION}: ${message}`;
  56. }
  57. }
  58.  
  59. if (isContentsPage()) {
  60. continuePlayVideo();
  61. sleep(2000).then(() => {
  62. pageLoading = false;
  63. GM_setValue("baseUrl", window.location.href);
  64. findAndStartLesson();
  65. });
  66. }
  67.  
  68. if (isLessonPage()) {
  69. sleep(2000).then(() => {
  70. pageLoading = false;
  71. findAndStartChapter();
  72. });
  73. }
  74.  
  75. function findAndStartLesson() {
  76. let incompleteLessonIndex = findIncompleteLesson();
  77. if (incompleteLessonIndex >= 0) {
  78. updateStatus('Starting next lesson');
  79. startNextLesson(incompleteLessonIndex);
  80. clearInterval(continueLesson);
  81. } else {
  82. currentPageComplete = true;
  83. updateStatus('All lessons on this page completed');
  84. }
  85. }
  86.  
  87. function findIncompleteLesson() {
  88. let progressValueList = document.querySelectorAll(".progressvalue");
  89. for (let i = 0; i < progressValueList.length; i++) {
  90. if (progressValueList[i].innerText !== '100%') {
  91. return i;
  92. }
  93. }
  94. return -1;
  95. }
  96.  
  97. function startNextLesson(index) {
  98. pageLoading = true;
  99. currentPageComplete = false;
  100. let goLearnList = document.querySelectorAll(".golearn.ablesky-colortip.saveStuCourse");
  101. if (goLearnList[index]) {
  102. sleep(1000).then(() => {
  103. window.location.href = 'https://study.enaea.edu.cn' + getLessonUrl(goLearnList[index].getAttribute("data-vurl"));
  104. });
  105. }
  106. }
  107.  
  108. function getLessonUrl(dataUrl) {
  109. if (dataUrl.startsWith("/")) {
  110. return dataUrl;
  111. } else {
  112. return "/" + dataUrl;
  113. }
  114. }
  115.  
  116. function findAndStartChapter() {
  117. let currentChapter = document.querySelector(".current.cvtb-MCK-course-content");
  118. if (currentChapter) {
  119. let currentChapterProgress = currentChapter.querySelector(".cvtb-MCK-CsCt-studyProgress");
  120. if (currentChapterProgress && currentChapterProgress.innerText === "100%") {
  121. let incompleteChapterIndex = findIncompleteChapter();
  122. if (incompleteChapterIndex >= 0) {
  123. updateStatus('Starting next chapter');
  124. startNextChapter(incompleteChapterIndex);
  125. } else {
  126. updateStatus('All chapters in this lesson completed');
  127. completeCurrentLesson();
  128. }
  129. }
  130. }
  131. }
  132.  
  133. function findIncompleteChapter() {
  134. let progressValueList = document.querySelectorAll(".cvtb-MCK-CsCt-studyProgress");
  135. for (let i = 0; i < progressValueList.length; i++) {
  136. if (progressValueList[i].innerText !== '100%') {
  137. return i;
  138. }
  139. }
  140. return -1;
  141. }
  142.  
  143. function startNextChapter(index) {
  144. let courseInfoList = document.querySelectorAll(".cvtb-MCK-CsCt-info.clearfix");
  145. if (courseInfoList[index]) {
  146. courseInfoList[index].click();
  147. let chapterTitle = document.querySelectorAll(".cvtb-MCK-CsCt-title.cvtb-text-ellipsis")[index]?.innerText;
  148. continuePlayVideo();
  149. sleep(300).then(() => {
  150. console.log('Start learn: ' + chapterTitle);
  151. let continueButton = document.getElementById("ccH5jumpInto");
  152. if (continueButton) {
  153. continueButton.click();
  154. }
  155. });
  156. }
  157. }
  158.  
  159. function completeCurrentLesson() {
  160. let lessonTitle = document.querySelector(".cvtb-top-link.cvtb-text-ellipsis")?.innerHTML;
  161. console.log('This lesson is completed: ' + lessonTitle);
  162. let baseUrl = GM_getValue('baseUrl', '');
  163. if (baseUrl) {
  164. pageLoading = true;
  165. window.location.href = baseUrl;
  166. clearInterval(continueChapter);
  167. clearInterval(continuePlay);
  168. }
  169. }
  170.  
  171. function continuePlayVideo() {
  172. if (isLessonPage()) {
  173. let video = document.querySelector('video');
  174. if (video) {
  175. // Restore video playback position based on progress percentage
  176. let progress = getCurrentChapterProgress();
  177. if (progress > 0) {
  178. let targetTime = (progress / 100) * video.duration;
  179. if (video.currentTime < targetTime) {
  180. video.currentTime = targetTime;
  181. updateStatus(`Resuming video from ${progress}% progress`);
  182. }
  183. }
  184.  
  185. // Set video playback speed to 4X
  186. if (video.playbackRate !== playbackSpeed) {
  187. video.playbackRate = playbackSpeed;
  188. updateStatus(`Video speed set to ${playbackSpeed}x`);
  189. }
  190.  
  191. video.muted = true; // Ensure video is muted
  192. if (video.paused) {
  193. video.play(); // Ensure video is always playing
  194. updateStatus('Video is playing');
  195. }
  196.  
  197. // Save playback position periodically
  198. setInterval(() => {
  199. let progress = (video.currentTime / video.duration) * 100;
  200. GM_setValue('lastPlaybackProgress', progress);
  201. }, 5000);
  202.  
  203. // Automatically play next video when current video ends
  204. video.onended = () => {
  205. GM_setValue('lastPlaybackProgress', 0); // Reset progress
  206. updateStatus('Video ended, starting next chapter');
  207. findAndStartChapter();
  208. };
  209. }
  210. closePausePopup();
  211. }
  212. }
  213.  
  214. function getCurrentChapterProgress() {
  215. let currentChapter = document.querySelector(".current.cvtb-MCK-course-content");
  216. if (currentChapter) {
  217. let progressElement = currentChapter.querySelector(".cvtb-MCK-CsCt-studyProgress");
  218. if (progressElement) {
  219. let progressText = progressElement.innerText;
  220. let progress = parseFloat(progressText);
  221. if (!isNaN(progress)) {
  222. return progress;
  223. }
  224. }
  225. }
  226. return 0;
  227. }
  228.  
  229. function clickNextPageButton() {
  230. if (isContentsPage() && !isLastPage() && currentPageComplete && !pageLoading) {
  231. updateStatus('Current page complete, going to next page');
  232. nextPage();
  233. sleep(2000).then(() => {
  234. findAndStartLesson();
  235. });
  236. }
  237. }
  238.  
  239. function isContentsPage() {
  240. return currentPagePath === '/circleIndexRedirect.do' && testURL('action', 'toNewMyClass') && testURL('type', 'course');
  241. }
  242.  
  243. function isLessonPage() {
  244. return currentPagePath === '/viewerforccvideo.do';
  245. }
  246.  
  247. function nextPage() {
  248. let nextButton = document.querySelector(".next.paginate_button:not(.paginate_button_disabled)");
  249. if (nextButton) {
  250. nextButton.click();
  251. }
  252. }
  253.  
  254. function isLastPage() {
  255. return document.querySelector(".next.paginate_button.paginate_button_disabled");
  256. }
  257.  
  258. function closePausePopup() {
  259. let pauseButton = document.querySelector(".td-content");
  260. if (pauseButton) {
  261. console.log('Pause button found');
  262. let continueButton = document.querySelector("button:contains('Continue Learning')");
  263. if (continueButton) {
  264. continueButton.click();
  265. }
  266. let answerOptions = document.querySelector(".dialog-content input");
  267. if (answerOptions) {
  268. answerOptions.click();
  269. }
  270. let dialogButton = document.querySelector(".dialog-button-container button");
  271. if (dialogButton) {
  272. dialogButton.click();
  273. }
  274. } else {
  275. console.log('Pause button not found');
  276. }
  277. }
  278.  
  279. function testURL(name, value) {
  280. let queryParams = window.location.search.substring(1);
  281. let variableList = queryParams.split("&");
  282. for (const element of variableList) {
  283. let parameterPair = element.split("=");
  284. if (parameterPair[0] === name) {
  285. return parameterPair[1] === value;
  286. }
  287. }
  288. return false;
  289. }
  290.  
  291. function sleep(time) {
  292. return new Promise((resolve) => setTimeout(resolve, time));
  293. }
  294. })();