学习通刷课助手||防止鼠标移出暂停|章节结束自动跳转|章节测试[功能测试中,正确率待验证]

学习通课程自动挂机,当前脚本支持课程视频播放完成,自动跳转下一小节,章节测试自动跳过,后台播放防止视频暂停,章节测试刷题。

  1. // ==UserScript==
  2. // @name 学习通刷课助手||防止鼠标移出暂停|章节结束自动跳转|章节测试[功能测试中,正确率待验证]
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.0.3
  5. // @description 学习通课程自动挂机,当前脚本支持课程视频播放完成,自动跳转下一小节,章节测试自动跳过,后台播放防止视频暂停,章节测试刷题。
  6. // @author Sweek
  7. // @match *://*.chaoxing.com/*
  8. // @license GPLv3
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=csdn.net
  10. // @grant GM_addStyle
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @require https://code.jquery.com/jquery-2.1.4.min.js
  14. // ==/UserScript==
  15.  
  16.  
  17.  
  18. GM_setValue("playbackRate", GM_getValue("playbackRate") || 1);
  19. GM_setValue("volume", GM_getValue("volume") || 0);
  20. GM_setValue("courseImg", GM_getValue("courseImg") || '');
  21. GM_setValue("testType", GM_getValue("testType") || 'save');
  22. GM_setValue("ifTest", GM_getValue("ifTest") || 'skip');
  23. GM_setValue("taskInterval", GM_getValue("taskInterval") || '6000');
  24. GM_setValue("testInterval", GM_getValue("testInterval") || '4000');
  25. GM_setValue("ifReview", GM_getValue("ifReview") || '0');
  26. GM_setValue("savedEmail", GM_getValue("savedEmail") || '');
  27.  
  28.  
  29. // 定义全局变量
  30. let currentTime = null // 当前视频当前播放节点
  31. let duration = null // 当前视频总长度
  32. let progress = null // 当前视频播放进度
  33. let playbackRate = GM_getValue("playbackRate") // 当前视频播放倍速
  34. let volume = GM_getValue("volume") // 当前视频音量
  35.  
  36. // 课程章节相关数据
  37. let courseName = null // 当前课程名称
  38. let courseId = null // 当前课程id
  39. let courseImg = GM_getValue("courseImg") // 当前课程封面
  40. let chapterInfo = [] // 当前课程所有章节数据
  41. let currentChapterId = null // 当前所在章节id
  42. let currentChapterName = null // 当前所在章节名称
  43. let allChapterName = [] // 所有章节名称
  44. let currentChapterTabArr = [] // 当前页面所有的分栏
  45. let currentChapterTabName = '' // 当前页面激活的分栏名称
  46. let videoProgressId = '' // 定时监听页面内容监听事件
  47.  
  48. // 任务间隔
  49. let taskInterval = GM_getValue("taskInterval")
  50.  
  51. // 是否复习模式
  52. let ifReview = GM_getValue("ifReview")
  53.  
  54.  
  55. // 章节测试数据
  56. let testDom = null
  57. let ifTest = GM_getValue("ifTest")
  58. let testType = GM_getValue("testType")
  59. let testTasks = []
  60. let testBtnDocument = null
  61.  
  62. let testInterval = GM_getValue("testInterval")
  63.  
  64. // 获取当前页面的 URL
  65. url = ''
  66. chapterId = ''
  67.  
  68.  
  69. let arrayEchelon = []; // 顺序执行梯队,初始化为空数组
  70. const dealEvent = new Event("redeal", { bubbles: false, cancelable: false });
  71. let testArrayEchelon = []; // 顺序执行梯队,初始化为空数组
  72. const testDealEvent = new Event("testRedeal", { bubbles: false, cancelable: false });
  73.  
  74. // AI请求接口Url
  75. const fetch_url ='https://www.sweek.top/model/sse/data?text='
  76.  
  77. // 页面模板部分
  78. // 页面模板部分
  79. // 页面模板部分
  80.  
  81. // 页面样式
  82. var popCSs = `
  83. #my-window {
  84. position: fixed;
  85. top: 5px;
  86. left: 20px;
  87. width: 320px;
  88. height: auto;
  89. background-color: rgba(247, 247, 247, 1);
  90. border: 1px solid #fff;
  91. border-radius: 5px;
  92. z-index: 9999;
  93. overflow: hidden;
  94. padding: 0 5px 10px 0;
  95. font-family: fangsong;
  96. font-weight: bold;
  97. }
  98. #my-window .header {
  99. background-color: #4497fa;
  100. color: #fff;
  101. padding: 5px;
  102. font-size: 16px;
  103. font-family: 'fangsong';
  104. font-weight: bold;
  105. border-radius: 5px;
  106. cursor: move;
  107. height: 25px;
  108. width: 320px;
  109. }
  110.  
  111. #my-window .content {
  112. width: 320px;
  113. height: auto;
  114. }
  115.  
  116. #my-window .content .content-title {
  117. height: 22px;
  118. width: 300px;
  119. background-color: #dadada;
  120. line-height: 22px;
  121. padding-left: 5px;
  122. font-size: 12px;
  123. font-family: 'fangsong';
  124. font-weight: 600;
  125. boder-radius: 5px;
  126. border-left: 4px solid #2196f3;
  127. border-right: 4px solid #dadada;
  128. margin-top: 5px;
  129. border-radius: 3px;
  130. }
  131.  
  132. #my-window .content .content-notice {
  133. height: 80px;
  134. width: 300px;
  135. overflow: auto;
  136. border: 1px solid gray;
  137. border-radius: 3px;
  138. padding: 5px;
  139. margin-top: 5px;
  140. font-size: 12px;
  141. font-weight: bold;
  142. }
  143.  
  144. #my-window .content .content-process {
  145. height: 60px;
  146. width: 300px;
  147. overflow: auto;
  148. border: 1px solid gray;
  149. border-radius: 3px;
  150. padding: 5px;
  151. margin-top: 5px;
  152. }
  153.  
  154. #my-window .content .content-log {
  155. height: 120px;
  156. width: 300px;
  157. overflow: auto;
  158. border: 1px solid gray;
  159. border-radius: 3px;
  160. padding: 5px;
  161. margin-top: 5px;
  162. }
  163. #my-window .content .content-set {
  164. height: 240px;
  165. width: 300px;
  166. overflow-y: auto;
  167. overflow-x: hidden;
  168. border: 1px solid gray;
  169. border-radius: 3px;
  170. padding: 5px;
  171. margin-top: 5px;
  172. }
  173.  
  174. #my-window .content .content-set .content-set-content {
  175. width: 290px;
  176. display: flex;
  177. justify-content: space-between;
  178. border-top: 1px solid gray;
  179. border-bottom: 1px solid gray;
  180. padding: 5px 5px;
  181. }
  182.  
  183. #my-window .content .content-set .content-set-content .control {
  184. width: 190px;
  185. height: 20px;
  186. display: flex;
  187. justify-content: space-between;
  188. }
  189.  
  190. #my-window .content .content-set .content-set-content .control #ifReviewSelect {
  191. width: 190px;
  192. height: 20px;
  193. border-radius: 3px;
  194. border: 1px solid gray;
  195. font-size: 13px;
  196. font-family: fangsong;
  197. font-weight: bold;
  198. cursor: pointer;
  199. }
  200.  
  201. #my-window .content .content-set .content-set-content .control #playbackRateSelect {
  202. width: 190px;
  203. height: 20px;
  204. border-radius: 3px;
  205. border: 1px solid gray;
  206. font-size: 13px;
  207. font-family: fangsong;
  208. font-weight: bold;
  209. cursor: pointer;
  210. }
  211.  
  212. #my-window .content .content-set .content-set-content .control #volumeSelect {
  213. width: 190px;
  214. height: 20px;
  215. border-radius: 3px;
  216. border: 1px solid gray;
  217. font-size: 13px;
  218. font-family: fangsong;
  219. font-weight: bold;
  220. cursor: pointer;
  221. }
  222.  
  223. #my-window .content .content-set .content-set-content .control #ifTestSelect {
  224. width: 190px;
  225. height: 20px;
  226. border-radius: 3px;
  227. border: 1px solid gray;
  228. font-size: 13px;
  229. font-family: fangsong;
  230. font-weight: bold;
  231. cursor: pointer;
  232. }
  233.  
  234. #my-window .content .content-set .content-set-content .control #testTypeSelect {
  235. width: 190px;
  236. height: 20px;
  237. border-radius: 3px;
  238. border: 1px solid gray;
  239. font-size: 13px;
  240. font-family: fangsong;
  241. font-weight: bold;
  242. cursor: pointer;
  243. }
  244.  
  245. #my-window .content .content-set .content-set-content .control #taskIntervalSelect {
  246. width: 190px;
  247. height: 20px;
  248. border-radius: 3px;
  249. border: 1px solid gray;
  250. font-size: 13px;
  251. font-family: fangsong;
  252. font-weight: bold;
  253. cursor: pointer;
  254. }
  255.  
  256. #my-window .content .content-set .content-set-content .control #testIntervalSelect {
  257. width: 190px;
  258. height: 20px;
  259. border-radius: 3px;
  260. border: 1px solid gray;
  261. font-size: 13px;
  262. font-family: fangsong;
  263. font-weight: bold;
  264. cursor: pointer;
  265. }
  266.  
  267. #my-window .content .content-set .content-set-content #email-input {
  268. width: 145px;
  269. height: 20px;
  270. border-radius: 3px;
  271. border: 1px solid gray;
  272. font-size: 13px;
  273. font-family: fangsong;
  274. font-weight: bold;
  275. }
  276.  
  277.  
  278. #my-window .content .content-set .content-set-content #save-btn {
  279. color: gray;
  280. width: 40px;
  281. height: 22px;
  282. border-radius: 3px;
  283. border: 1px solid gray;
  284. font-size: 13px;
  285. font-family: fangsong;
  286. font-weight: bold;
  287. cursor: pointer;
  288. }
  289.  
  290. #save-btn:hover {
  291. background-color: #e3e3e3!important;
  292. }
  293.  
  294. #my-window .resizer {
  295. position: absolute;
  296. bottom: 0;
  297. right: 0;
  298. width: 20px;
  299. height: 20px;
  300. background-color: #2196f3;
  301. cursor: se-resize;
  302. border-radius: 0px;
  303. z-index: 1;
  304. }
  305. #hide-btn {
  306. height: 25px;
  307. width: auto;
  308. float: right;
  309. margin-right: 10px;
  310. background-color: #fff;
  311. border: 1px solid gray;
  312. border-radius: 5px;
  313. font-size: 12px;
  314. padding: 0 5px;
  315. font-family: 'fangsong';
  316. cursor: pointer;
  317. }
  318.  
  319. #hide-btn:hover {
  320. background-color: #4497fa;
  321. color: #fff;
  322. }
  323.  
  324. #hide-notice-btn {
  325. height: 20px;
  326. line-height: 20px;
  327. width: auto;
  328. background-color: #fff;
  329. border: 1px solid gray;
  330. border-radius: 3px;
  331. font-size: 12px;
  332. padding: 0 5px;
  333. font-family: 'fangsong';
  334. cursor: pointer;
  335. float: right;
  336. }
  337. #hide-notice-btn:hover {
  338. background-color: gray;
  339. color: #fff;
  340. }
  341.  
  342. #hide-process-btn {
  343. height: 20px;
  344. line-height: 20px;
  345. width: auto;
  346. background-color: #fff;
  347. border: 1px solid gray;
  348. border-radius: 3px;
  349. font-size: 12px;
  350. padding: 0 5px;
  351. font-family: 'fangsong';
  352. cursor: pointer;
  353. float: right;
  354. }
  355. #hide-process-btn:hover {
  356. background-color: gray;
  357. color: #fff;
  358. }
  359.  
  360. #hide-log-btn {
  361. height: 20px;
  362. line-height: 20px;
  363. width: auto;
  364. background-color: #fff;
  365. border: 1px solid gray;
  366. border-radius: 3px;
  367. font-size: 12px;
  368. padding: 0 5px;
  369. font-family: 'fangsong';
  370. cursor: pointer;
  371. float: right;
  372. }
  373. #hide-log-btn:hover {
  374. background-color: gray;
  375. color: #fff;
  376. }
  377.  
  378. #hide-set-btn {
  379. height: 20px;
  380. line-height: 20px;
  381. width: auto;
  382. background-color: #fff;
  383. border: 1px solid gray;
  384. border-radius: 3px;
  385. font-size: 12px;
  386. padding: 0 5px;
  387. font-family: 'fangsong';
  388. cursor: pointer;
  389. float: right;
  390. }
  391. #hide-set-btn:hover {
  392. background-color: gray;
  393. color: #fff;
  394. }
  395. `
  396.  
  397. // 页面Html
  398. var popHtml = `
  399. <div class="header">学习通助手[2.0.3]
  400. <button id="hide-btn">显示/隐藏</button>
  401. </div>
  402. <div class="content" id="my-window-content">
  403. <div class="resizer" style="display: none;"></div>
  404. <div class="row" style="border: 1px solid #ccc; padding: 5px;">
  405. <div class="content-title">公告
  406. <button id="hide-notice-btn">显示/隐藏</button>
  407. </div>
  408. <div class="content-notice" id="content-notice">
  409. </div>
  410. <div class="content-title">任务进度<span id="async-time"></span>
  411. <button id="hide-process-btn">显示/隐藏</button>
  412. </div>
  413. <div class="content-process" id="content-process"></div>
  414. <div class="content-title">执行日志
  415. <button id="hide-log-btn">显示/隐藏</button>
  416. </div>
  417. <div class="content-log" id="content-log"></div>
  418. <div class="content-title">配置
  419. <button id="hide-set-btn">显示/隐藏</button>
  420. </div>
  421. <div class="content-set" id="content-set">
  422. <div style="margin-bottom: 5px;font-family: 'fangsong';font-weight: bold;">
  423. <div>
  424. [该邮箱地址用来登录app网站,该邮箱需要和网站注册邮箱保持一致,不可为空,填写完,点击保存,可在网站查看课程进度。]
  425. </div>
  426. <div>[二维码:<a style="color: dodgerblue;cursor: pointer;" id="Qcode">点击查看,微信扫码</a>]</div>
  427. </div>
  428. <div class="content-set-content">
  429. <label for="email-input">[邮箱]</label>
  430. <div class="control">
  431. <input type="email" id="email-input" placeholder="请输入邮箱地址">
  432. <button id="save-btn">保存</button>
  433. </div>
  434. </div>
  435. <div class="content-set-content">
  436. <label for="email-input">[复习模式]</label>
  437. <div class="control">
  438. <select id="ifReviewSelect">
  439. <option value="1">启用</option>
  440. <option value="0">关闭</option>
  441. </select>
  442. </div>
  443. </div>
  444. <div class="content-set-content">
  445. <label for="email-input">[视频倍速]</label>
  446. <div class="control">
  447. <select id="playbackRateSelect">
  448. <option value="1">1x</option>
  449. <option value="2">2x[部分场景使用]</option>
  450. <option value="3">3x[暂时无法使用]</option>
  451. <option value="4">4x[暂时无法使用]</option>
  452. </select>
  453. </div>
  454. </div>
  455. <div class="content-set-content">
  456. <label for="email-input">[视频音量]</label>
  457. <div class="control">
  458. <select id="volumeSelect">
  459. <option value="0">静音</option>
  460. <option value="0.2">20</option>
  461. <option value="0.4">40</option>
  462. <option value="0.6">60</option>
  463. <option value="0.8">80</option>
  464. <option value="1">100</option>
  465. </select>
  466. </div>
  467. </div>
  468. <div class="content-set-content">
  469. <label for="email-input">[是否章节测试]</label>
  470. <div class="control">
  471. <select id="ifTestSelect">
  472. <option value="take">处理章节测试</option>
  473. <option value="skip">跳过章节测试</option>
  474. </select>
  475. </div>
  476. </div>
  477. <div class="content-set-content">
  478. <label for="email-input">[章节测试完成]</label>
  479. <div class="control">
  480. <select id="testTypeSelect">
  481. <option value="save">完成后暂时保存</option>
  482. <option value="submit">完成后提交</option>
  483. </select>
  484. </div>
  485. </div>
  486. <div class="content-set-content">
  487. <label for="email-input">[章节任务间隔]</label>
  488. <div class="control">
  489. <select id="taskIntervalSelect">
  490. <option value="4000">4s</option>
  491. <option value="6000">6s</option>
  492. <option value="8000">8s</option>
  493. <option value="10000">10s</option>
  494. </select>
  495. </div>
  496. </div>
  497. <div class="content-set-content">
  498. <label for="email-input">[章节测试间隔]</label>
  499. <div class="control">
  500. <select id="testIntervalSelect">
  501. <option value="4000">4s</option>
  502. <option value="6000">6s</option>
  503. <option value="8000">8s</option>
  504. <option value="10000">10s</option>
  505. </select>
  506. </div>
  507. </div>
  508. </div>
  509. </div>
  510. </div>
  511. `
  512. // 初始化添加页面弹窗以及悬浮球
  513. function initPopup() {
  514. // 添加CSS样式
  515. GM_addStyle(popCSs);
  516. // 创建窗口元素
  517. const myWindow = document.createElement("div");
  518. myWindow.id = "my-window";
  519. myWindow.innerHTML = popHtml;
  520. // 获取页面body元素
  521. const body = document.getElementsByTagName("body")[0];
  522. // 添加窗口和悬浮球到页面
  523. body.appendChild(myWindow);
  524. // 绑定隐藏窗口按钮的click事件
  525. const hideBtn = document.querySelector("#hide-btn");
  526. hideBtn.addEventListener("click", hideWindow);
  527. // 绑定隐藏公告按钮的click事件
  528. const hideNoticeBtn = document.querySelector("#hide-notice-btn");
  529. hideNoticeBtn.addEventListener("click", hideNotice);
  530. // 绑定隐藏播放进度按钮的click事件
  531. const hideProcessBtn = document.querySelector("#hide-process-btn");
  532. hideProcessBtn.addEventListener("click", hideProcess);
  533. // 绑定隐藏日志按钮的click事件
  534. const hideLogBtn = document.querySelector("#hide-log-btn");
  535. hideLogBtn.addEventListener("click", hideLog);
  536. // 绑定隐藏设置按钮的click事件
  537. const hideSetBtn = document.querySelector("#hide-set-btn");
  538. hideSetBtn.addEventListener("click", hideSet);
  539. // 获取弹窗内容
  540. var htmlContent = '<div style="width: 800px; height: 400px;display: flex;justify-content: center;align-items: center;"><img style="width: 400px; height: 400px;" src="https://www.sweek.top/api/preview/Qcode.png" alt="微信扫码"><img style="height: 400px;" src="https://www.sweek.top/api/preview/Qrcode.jpg" alt="微信扫码"></div>';
  541.  
  542. // 点击链接显示弹窗
  543. document.getElementById('Qcode').addEventListener('click', function() {
  544. showCustomPopup(htmlContent, 800, 400);
  545. });
  546.  
  547. // 获取头部元素
  548. const header = myWindow.querySelector('.header');
  549.  
  550. // 处理移动事件
  551. let isDragging = false;
  552. let mouseX = 0;
  553. let mouseY = 0;
  554.  
  555. header.addEventListener('mousedown', function (e) {
  556. e.preventDefault();
  557. isDragging = true;
  558. mouseX = e.clientX;
  559. mouseY = e.clientY;
  560. });
  561.  
  562. document.addEventListener('mousemove', function (e) {
  563. if (isDragging) {
  564. const deltaX = e.clientX - mouseX;
  565. const deltaY = e.clientY - mouseY;
  566. const newLeft = Math.min(
  567. Math.max(0, myWindow.offsetLeft + deltaX),
  568. window.innerWidth - myWindow.offsetWidth
  569. );
  570. const newTop = Math.min(
  571. Math.max(0, myWindow.offsetTop + deltaY),
  572. window.innerHeight - myWindow.offsetHeight
  573. );
  574. myWindow.style.left = newLeft + 'px';
  575. myWindow.style.top = newTop + 'px';
  576. mouseX = e.clientX;
  577. mouseY = e.clientY;
  578. }
  579. });
  580.  
  581. document.addEventListener('mouseup', function () {
  582. isDragging = false;
  583. });
  584.  
  585. document.getElementById('ifReviewSelect').addEventListener('change', function() {
  586. updateIfReview(this.value);
  587. });
  588. document.getElementById("ifReviewSelect").value = ifReview;
  589. document.getElementById('playbackRateSelect').addEventListener('change', function() {
  590. updatePlaybackRate(this.value);
  591. });
  592. document.getElementById("playbackRateSelect").value = playbackRate;
  593. document.getElementById('volumeSelect').addEventListener('change', function() {
  594. updateVolume(this.value);
  595. });
  596. document.getElementById("volumeSelect").value = volume;
  597. document.getElementById('testTypeSelect').addEventListener('change', function() {
  598. updateTestType(this.value);
  599. });
  600. document.getElementById("testTypeSelect").value = testType;
  601. document.getElementById('ifTestSelect').addEventListener('change', function() {
  602. updateIfTest(this.value);
  603. });
  604. document.getElementById("ifTestSelect").value = ifTest;
  605. document.getElementById('taskIntervalSelect').addEventListener('change', function() {
  606. updateTaskInterval(this.value);
  607. });
  608. document.getElementById("taskIntervalSelect").value = taskInterval;
  609. document.getElementById('testIntervalSelect').addEventListener('change', function() {
  610. updateTestInterval(this.value);
  611. });
  612. document.getElementById("testIntervalSelect").value = testInterval;
  613. }
  614.  
  615. // 当下拉框的值改变时,更新变量值
  616. function updateIfReview(val) {
  617. ifReview = val
  618. GM_setValue("ifReview", ifReview);
  619. }
  620. function updatePlaybackRate(val) {
  621. playbackRate = val
  622. GM_setValue("playbackRate", playbackRate);
  623. // 刷新页面
  624. location.reload();
  625. }
  626. function updateVolume(val) {
  627. volume = val
  628. GM_setValue("volume", volume);
  629. // 刷新页面
  630. location.reload();
  631. }
  632. function updateIfTest(val) {
  633. if(val == "take") {
  634. // const email = GM_getValue("savedEmail")
  635. // if(email) {
  636. // ifTest = val
  637. // GM_setValue("ifTest", ifTest);
  638. // notify(`已将章节测试设为${ifTest == 'take' ? '处理' : '跳过'}状态`, 2500)
  639. // } else {
  640. // notify('请先填写邮箱(邮箱账号需要扫码登录网站注册)', 5000)
  641. // }
  642. // 获取输入的邮箱地址
  643. const email = GM_getValue("savedEmail") || '';
  644. // 检查邮箱地址是否有效
  645. if (!isValidEmail(email)) {
  646. notify('请输入有效的邮箱地址!', 5000)
  647. document.getElementById('ifTestSelect').value = 'skip';
  648. return;
  649. }
  650. const url = 'https://www.sweek.top/api/checkEmailExists';
  651. const data = {
  652. email
  653. };
  654. if(email) {
  655. fetch(url, {
  656. method: 'POST',
  657. headers: {
  658. 'Content-Type': 'application/json', // 声明请求主体的内容类型为 JSON
  659. },
  660. body: JSON.stringify(data), // 将数据对象转换为 JSON 字符串并作为请求主体
  661. }).then(response => {
  662. if (!response.ok) {
  663. throw new Error('Network response was not ok');
  664. }
  665. return response.json(); // 解析 JSON 响应数据
  666. }).then(data => {
  667. if(data.exists){
  668. ifTest = val
  669. GM_setValue("ifTest", ifTest);
  670. notify(`已将章节测试设为${ifTest == 'take' ? '处理' : '跳过'}状态`, 2500)
  671. } else {
  672. document.getElementById('ifTestSelect').value = 'skip';
  673. notify('邮箱账号尚未注册,请扫码前往留言信箱网站注册,然后再尝试启用章节测试刷题功能', 5000)
  674. }
  675. }).catch(error => {
  676. // console.error('Error:', error);
  677. });
  678. }
  679. } else if (val == "skip"){
  680. ifTest = val
  681. GM_setValue("ifTest", ifTest);
  682. notify(`已将章节测试设为${ifTest == 'take' ? '处理' : '跳过'}状态`, 2500)
  683. }
  684. }
  685. function updateTestType(val) {
  686. testType = val
  687. GM_setValue("testType", testType);
  688. notify(`章节测试完成后将会${testType == 'save' ? '暂时保存' : '提交'}`, 2500)
  689. // 刷新页面
  690. // location.reload();
  691. }
  692. function updateTaskInterval(val) {
  693. taskInterval = val
  694. GM_setValue("taskInterval", taskInterval);
  695. notify(`章节任务执行间隔已设置为${taskInterval/1000}秒`, 2500)
  696. // 刷新页面
  697. // location.reload();
  698. }
  699. function updateTestInterval(val) {
  700. testInterval = val
  701. GM_setValue("testInterval", testInterval);
  702. notify(`章节测试执行间隔已设置为${testInterval/1000}秒`, 2500)
  703. // 刷新页面
  704. // location.reload();
  705. }
  706.  
  707.  
  708. // 隐藏窗口函数
  709. function hideWindow() {
  710. var myWindowContent = document.getElementById("my-window-content");
  711. var showPop = myWindowContent.style.display
  712. if (showPop == '' || showPop == 'block') {
  713. myWindowContent.style.display = "none";
  714. } else {
  715. myWindowContent.style.display = "block";
  716. }
  717. }
  718.  
  719. // 隐藏公告函数
  720. function hideNotice() {
  721. var contentNotice = document.getElementById("content-notice");
  722. var showPop = contentNotice.style.display
  723. if (showPop == '' || showPop == 'block') {
  724. contentNotice.style.display = "none";
  725. } else {
  726. contentNotice.style.display = "block";
  727. }
  728. }
  729.  
  730. // 隐藏播放进度函数
  731. function hideProcess() {
  732. var contentProcess = document.getElementById("content-process");
  733. var showPop = contentProcess.style.display
  734. if (showPop == '' || showPop == 'block') {
  735. contentProcess.style.display = "none";
  736. } else {
  737. contentProcess.style.display = "block";
  738. }
  739. }
  740.  
  741. // 隐藏日志函数
  742. function hideLog() {
  743. var contentLog = document.getElementById("content-log");
  744. var showPop = contentLog.style.display
  745. if (showPop == '' || showPop == 'block') {
  746. contentLog.style.display = "none";
  747. } else {
  748. contentLog.style.display = "block";
  749. }
  750. }
  751.  
  752. // 隐藏设置函数
  753. function hideSet() {
  754. var contentSet = document.getElementById("content-set");
  755. var showPop = contentSet.style.display
  756. if (showPop == '' || showPop == 'block') {
  757. contentSet.style.display = "none";
  758. } else {
  759. contentSet.style.display = "block";
  760. }
  761. }
  762.  
  763. function takeEmail() {
  764. // 获取邮箱输入框和保存按钮
  765. const emailInput = document.getElementById("email-input");
  766. const saveBtn = document.getElementById("save-btn");
  767.  
  768. // 当保存按钮被点击时
  769. saveBtn.addEventListener("click", function() {
  770. // 获取输入的邮箱地址
  771. const email = emailInput.value.trim();
  772. // 检查邮箱地址是否有效
  773. if (!isValidEmail(email)) {
  774. notify('请输入有效的邮箱地址!', 2500)
  775. return;
  776. }
  777. const url = 'https://www.sweek.top/api/checkEmailExists';
  778. const data = {
  779. email
  780. };
  781. if(email) {
  782. fetch(url, {
  783. method: 'POST',
  784. headers: {
  785. 'Content-Type': 'application/json', // 声明请求主体的内容类型为 JSON
  786. },
  787. body: JSON.stringify(data), // 将数据对象转换为 JSON 字符串并作为请求主体
  788. }).then(response => {
  789. if (!response.ok) {
  790. throw new Error('Network response was not ok');
  791. }
  792. return response.json(); // 解析 JSON 响应数据
  793. }).then(data => {
  794. if(data.exists){
  795. // 将邮箱地址存储到本地存储中
  796. GM_setValue("savedEmail", email);
  797. // 提示保存成功
  798. notify('保存成功!课程信息将同步到该邮箱账号', 2500)
  799. } else {
  800. notify('邮箱账号尚未注册,请扫码前往留言信箱网站注册', 2500)
  801. }
  802. }).catch(error => {
  803. // console.error('Error:', error);
  804. });
  805. }
  806.  
  807. });
  808.  
  809. // 页面加载时,尝试从本地存储中获取已保存的邮箱地址并反显到输入框中
  810. const savedEmail = GM_getValue("savedEmail") || '';
  811. if (savedEmail) {
  812. emailInput.value = savedEmail;
  813. }
  814. }
  815.  
  816. // 邮箱地址验证函数
  817. function isValidEmail(email) {
  818. // 此处可以使用正则表达式等方式进行邮箱地址的验证
  819. // 这里简单地判断邮箱是否包含 '@' 符号
  820. return email.includes("@");
  821. }
  822.  
  823.  
  824.  
  825.  
  826. // 封装页面组件方法
  827. // 封装页面组件方法
  828. // 封装页面组件方法
  829.  
  830. // 页面弹窗显示
  831. function showCustomPopup(htmlContent, width, height) {
  832. // 创建弹窗容器
  833. var popupContainer = document.createElement('div');
  834. popupContainer.className = 'custom-popup-container';
  835. // 设置弹窗容器样式
  836. popupContainer.style.position = 'fixed';
  837. popupContainer.style.top = '50px';
  838. popupContainer.style.left = '0';
  839. popupContainer.style.width = '100%';
  840. popupContainer.style.height = 'calc(100% - 50px)';
  841. popupContainer.style.display = 'flex';
  842. popupContainer.style.justifyContent = 'center';
  843. popupContainer.style.alignItems = 'center';
  844. popupContainer.style.zIndex = '999999';
  845.  
  846. // 创建弹窗内容容器
  847. var popupContent = document.createElement('div');
  848. popupContent.className = 'custom-popup-content';
  849. // 设置弹窗内容样式
  850. popupContent.style.width = width + 'px';
  851. popupContent.style.height = height + 'px';
  852. popupContent.style.backgroundColor = '#fff';
  853. popupContent.style.border = '1px solid #ccc';
  854. popupContent.style.borderRadius = '5px';
  855. popupContent.style.position = 'relative';
  856.  
  857. // 创建关闭按钮
  858. var closeButton = document.createElement('button');
  859. closeButton.innerHTML = '关闭';
  860. closeButton.style.position = 'absolute';
  861. closeButton.style.top = '5px';
  862. closeButton.style.right = '5px';
  863. closeButton.style.padding = '5px 10px';
  864. closeButton.style.backgroundColor = '#ccc';
  865. closeButton.style.border = 'none';
  866. closeButton.style.borderRadius = '3px';
  867. closeButton.style.cursor = 'pointer';
  868. closeButton.style.fontFamily = 'Arial, sans-serif';
  869.  
  870. // 关闭按钮点击事件处理
  871. closeButton.addEventListener('click', function() {
  872. popupContainer.remove();
  873. });
  874.  
  875. // 设置弹窗内容
  876. popupContent.innerHTML = htmlContent;
  877.  
  878. // 将关闭按钮和弹窗内容添加到弹窗容器中
  879. popupContent.appendChild(closeButton);
  880. popupContainer.appendChild(popupContent);
  881.  
  882. // 将弹窗容器添加到页面中
  883. document.body.appendChild(popupContainer);
  884. }
  885.  
  886. // 页面通知提示
  887. function notify(text, time) {
  888. // 创建通知元素
  889. var notification = document.createElement('div');
  890. notification.className = 'notification';
  891. // 设置通知内容
  892. notification.innerHTML = '<div class="notification-content"><h2 style="font-size: 16px;font-weight: bold;color: #307dff;font-family: fangsong;">' + '学习通助手提示' + '</h2><p style="font-family: fangsong;font-size: 13px;font-weight: bold;">' + text + '</p></div>';
  893. // 将通知添加到页面中
  894. document.body.appendChild(notification);
  895. // 设置通知样式
  896. notification.style.position = 'fixed';
  897. notification.style.top = '50px';
  898. notification.style.left = '-400px'; // 从左边弹出
  899. notification.style.transform = 'translateY(-50%)'; // 垂直居中
  900. notification.style.transition = 'left 0.5s ease-in-out'; // 添加过渡效果
  901. notification.style.zIndex = '999999';
  902. notification.style.backgroundColor = '#fff';
  903. notification.style.border = '1px solid #ccc';
  904. notification.style.padding = '10px';
  905. notification.style.borderRadius = '5px';
  906. notification.style.lineHeight = '25px';
  907.  
  908. // 等待一小段时间后,移动通知到左边
  909. setTimeout(function() {
  910. notification.style.left = '20px'; // 移动到左边
  911. }, 100);
  912.  
  913. // 设置定时器,在指定时间后移除通知
  914. setTimeout(function() {
  915. // 移动通知到左边以外
  916. notification.style.left = '-400px';
  917. // 等待过渡效果完成后,移除通知元素
  918. setTimeout(function() {
  919. notification.remove();
  920. }, 500);
  921. }, time);
  922. }
  923.  
  924. // 添加执行日志
  925. function addlog(str, color) {
  926. var _time = new Date().toLocaleTimeString()
  927. var contentLog = window.top.document.querySelector('.content-log');
  928. var newContent = '<p style="color: ' + color + ';">[' + _time + ']' + str + '</p><hr>';
  929. contentLog.innerHTML += newContent;
  930. // 将滚动条滚动到底部
  931. contentLog.scrollTop = contentLog.scrollHeight;
  932. }
  933.  
  934.  
  935. /** 监听事件-章节任务 */
  936. document.addEventListener("redeal", () => {
  937. dealAnsEchelon(arrayEchelon);
  938. });
  939.  
  940. /** 监听事件-章节测试 */
  941. document.addEventListener("testRedeal", () => {
  942. dealTestEchelon(testArrayEchelon);
  943. });
  944.  
  945.  
  946. // 章节任务处理
  947. // 章节任务处理
  948. // 章节任务处理
  949.  
  950. /** 初始化 */
  951. function initAll() {
  952. // console.log('location.pathname:::+ ', location.pathname)
  953. if(location.pathname == '/mooc-ans/knowledge/cards') {
  954. addlog('正在获取当前页面的任务...');
  955. const arrayAnsAll = document.querySelectorAll(".ans-attach-ct");
  956. addlog(`当前页面任务数量为: ${Array.from(arrayAnsAll).length}`);
  957. // 获取当前页面将会处理的任务-[根据是否启用复习模式]
  958. const arrayAns = ifReview == 1 ? document.querySelectorAll(".ans-attach-ct") : document.querySelectorAll('.ans-attach-ct:not(.ans-job-finished)')
  959. const taskCount = arrayAns.length;
  960. // addlog(`找到的任务数量: ${taskCount}`);
  961. if (arrayAns && taskCount > 0) {
  962. try {
  963. const arrayType = getIframesType(arrayAns);
  964. console.log('任务类型数组:', arrayType);
  965.  
  966. const arrayDocument = getAllIframesDocument(arrayAns);
  967. console.log('任务文档数组:', arrayDocument);
  968.  
  969. arrayEchelon = distributeAns(arrayType, arrayDocument);
  970. // addlog(`当前页面待办任务数量为: ${arrayEchelon.length}`);
  971.  
  972. // 触发处理任务的事件
  973. document.dispatchEvent(dealEvent);
  974. } catch (error) {
  975. // console.error("初始化过程中发生错误:", error);
  976. }
  977. } else {
  978. addlog("当前页面没有可处理的任务,直接跳过章节");
  979. // 如果没有任务,
  980. skipChapter();
  981. }
  982. }
  983.  
  984. }
  985.  
  986. // 获取任务属性
  987. function getIframesType(arrayAns) {
  988. return Array.from(arrayAns)
  989. .map(ans => {
  990. const jsonStr = ans.querySelector("iframe")?.getAttribute("data");
  991. if (!jsonStr) {
  992. // console.warn("未找到 data 属性或 data 属性为空。");
  993. return null;
  994. }
  995. try {
  996. const json = JSON.parse(jsonStr);
  997. // 根据需求获取任务类型
  998. // 课程类型
  999. if(json && json.type) {
  1000. return json.type;
  1001. }
  1002. // 测试类型
  1003. if(json && json.worktype) {
  1004. return json.worktype;
  1005. }
  1006. } catch (error) {
  1007. // console.warn("解析 JSON 失败:", error);
  1008. return null;
  1009. }
  1010. })
  1011. .filter(type => type !== null);
  1012. }
  1013.  
  1014. // 获取任务 document
  1015. function getAllIframesDocument(arrayAns) {
  1016. return Array.from(arrayAns)
  1017. .map(ans => ans.querySelector("iframe")?.contentWindow?.document)
  1018. .filter(doc => doc !== undefined && doc !== null);
  1019. }
  1020.  
  1021. // 按任务属性映射处理函数
  1022. const handlerMap = {
  1023. ".mp4": videoHandler,
  1024. ".wmv": videoHandler,
  1025. ".avi": videoHandler,
  1026. ".mkv": videoHandler,
  1027. ".flv": videoHandler,
  1028. ".mov": videoHandler,
  1029. ".doc": pptxHandler,
  1030. ".docx": pptxHandler,
  1031. ".pptx": pptxHandler,
  1032. ".pdf": pptxHandler,
  1033. ".ppt": pptxHandler,
  1034. ".mp3": audioHandler,
  1035. ".wav": audioHandler,
  1036. "workA": testHandler
  1037. };
  1038.  
  1039. // 按任务属性分配执行函数
  1040. function distributeAns(arrayType, arrayDocument) {
  1041. return arrayType.map((type, index) => {
  1042. const handler = handlerMap[type] || ignoreAns;
  1043. return { document: arrayDocument[index], handler };
  1044. });
  1045. }
  1046.  
  1047. // 处理单个任务
  1048. function dealSingleAns(singleAns) {
  1049. if (singleAns && singleAns.handler && typeof singleAns.handler === 'function') {
  1050. singleAns.handler(singleAns.document);
  1051. } else {
  1052. // console.warn("无效的任务或处理函数。");
  1053. document.dispatchEvent(dealEvent);
  1054. }
  1055. }
  1056.  
  1057.  
  1058. // 章节测试处理
  1059. // 章节测试处理
  1060. // 章节测试处理
  1061.  
  1062.  
  1063. // 按章节测试题目属性映射处理函数
  1064. const handlerTestMap = {
  1065. "单选题": choiceHandler,
  1066. "多选题": choiceHandler,
  1067. "判断题": choiceHandler
  1068. };
  1069.  
  1070. // 按任务属性分配执行函数
  1071. function distributeTest(arrayType) {
  1072. return arrayType.map((item, index) => {
  1073. const handler = handlerTestMap[item.type] || ignoreTest;
  1074. return { document: item.document, handler, testObj: item };
  1075. });
  1076. }
  1077.  
  1078. // 处理单个章节测试任务
  1079. function dealSingleTest(singleAns) {
  1080. if (singleAns && singleAns.handler && typeof singleAns.handler === 'function') {
  1081. singleAns.handler(singleAns.document, singleAns.testObj);
  1082. } else {
  1083. // console.warn("无效的任务或处理函数。");
  1084. document.dispatchEvent(testDealEvent);
  1085. }
  1086. }
  1087.  
  1088.  
  1089. /** 工具函数 */
  1090. /** 工具函数 */
  1091. /** 工具函数 */
  1092.  
  1093. // 章节测试执行方法
  1094. // 章节测试执行方法
  1095. // 章节测试执行方法
  1096.  
  1097.  
  1098. // 忽略章节测试题
  1099. function ignoreTest() {
  1100. addlog("无法处理, 忽略该章节测试题");
  1101. setTimeout(() => {
  1102. document.dispatchEvent(testDealEvent);
  1103. }, testInterval);
  1104. }
  1105.  
  1106. // 处理单选题/多选题/判断题
  1107. function choiceHandler(idocument, testObj) {
  1108. // console.log('idocument:::+ ', idocument)
  1109. // console.log('testObj:::+ ', testObj)
  1110. addlog(`处理${testObj.type}任务中...`);
  1111. fetch(fetch_url + testObj.question + '请你提供给我正确答案,不需要解析,答案需要精简,返回选项即可')
  1112. .then(response => {
  1113. let model_text = ''
  1114. const reader = response.body.getReader()
  1115. const decoder = new TextDecoder('utf-8')
  1116.  
  1117. const readChunk = () => {
  1118. reader.read().then(({ done, value }) => {
  1119. if (done) {
  1120. console.log('model_text:::+ ', model_text)
  1121. const answer_index = findABCDEFPositions(model_text)
  1122. console.log('answer_index:::+ ', answer_index)
  1123. const trueOptions = Array.from(idocument.querySelectorAll('li'));
  1124. trueOptions.forEach((option, index) => {
  1125. if (answer_index.includes(index)) {
  1126. option.click();
  1127. }
  1128. });
  1129. const answer_text = mapIndexToOption(answer_index)
  1130. console.log('answer_text:::+ ', answer_text)
  1131. const currentTestTask = testTasks.find((task) => task.data == testObj.data)
  1132. if (currentTestTask && answer_index.length > 0) {
  1133. currentTestTask.model_text = model_text
  1134. currentTestTask.answer_text = answer_text
  1135. currentTestTask.status = true
  1136. } else {
  1137. currentTestTask.status = false
  1138. }
  1139. // 使用 findIndex 获取当前任务的索引
  1140. const taskIndex = testTasks.findIndex((task) => task.data === testObj.data);
  1141. const process = (((taskIndex + 1)/testTasks.length)*100).toFixed(2) + '%'
  1142. setVideoProcess(process, taskIndex + 1, testTasks.length, 'test')
  1143. // 同步章节测试题目-AI模型生成
  1144. const email = GM_getValue("savedEmail") || ''
  1145. const course_name = GM_getValue("courseName") || '未获取课程名称';
  1146. const testList = [{ question: testObj.question, answer: answer_text, type: testObj.type, email: email, course_name: course_name }]
  1147. insertTestModel(testList)
  1148. setTimeout(() => {
  1149. document.dispatchEvent(testDealEvent);
  1150. }, testInterval);
  1151. return
  1152. }
  1153. const decodedValue = decoder.decode(value, { stream: true })
  1154. model_text += decodedValue
  1155. readChunk()
  1156. })
  1157. }
  1158. readChunk()
  1159. })
  1160. .catch(error => {
  1161. // 在这里处理错误
  1162. // console.error('Error:', error);
  1163. });
  1164. }
  1165.  
  1166. /** 章节测试任务梯队顺序处理 */
  1167. function dealTestEchelon(testArrayEchelon) {
  1168. const remainingTasks = testArrayEchelon.length;
  1169. addlog(`待处理章节测试任务数量为: ${remainingTasks}`);
  1170. if (remainingTasks === 0) {
  1171. // 获取章节测试题完成度
  1172. const test_process = getTrueStatusPercentage(testTasks)
  1173. const save_btn = testBtnDocument.querySelector('.btnSave')
  1174. const submit_btn = testBtnDocument.querySelector('.btnSubmit')
  1175. const mask_div = window.top.document.querySelector('.maskDiv')
  1176. const pop_ok = mask_div.querySelector('#popok')
  1177. if(test_process == 100) {
  1178. addlog('章节测试任务已全部完成');
  1179. switch (testType) {
  1180. case 'save':
  1181. save_btn.click()
  1182. addlog('章节测试答案已暂时保存');
  1183. break;
  1184. case 'submit':
  1185. submit_btn.click()
  1186. setTimeout(() => {
  1187. pop_ok.click()
  1188. setTimeout(() => {
  1189. const TiMus = getTestTopics()
  1190. // console.log('TiMus:::+ ', TiMus)
  1191. // 计算正确率
  1192. const rightRate = (TiMus.length/testTasks.length*100).toFixed(2) + '%';
  1193. notify(`章节测试答题情况[${TiMus.length}/${testTasks.length}],章节测试正确率[${rightRate}]`, 5000)
  1194. insertTest(TiMus)
  1195. }, 3000);
  1196. }, 1000);
  1197. addlog('章节测试答案已提交');
  1198. break;
  1199. default:
  1200. break;
  1201. }
  1202. } else {
  1203. addlog(`章节测试任务完成度为${test_process}%,不足100%,章节测试答案将暂时保存`);
  1204. save_btn.click()
  1205. }
  1206. setTimeout(() => {
  1207. if(testType == 'submit') {
  1208. }
  1209. addlog("章节测试任务已完成");
  1210. dealAnsEchelon(arrayEchelon);
  1211. }, 10000);
  1212. return;
  1213. }
  1214.  
  1215. const nextTask = testArrayEchelon.shift();
  1216. try {
  1217. dealSingleTest(nextTask);
  1218. } catch (error) {
  1219. // console.error("处理任务时发生错误:", error);
  1220. // 继续处理下一个任务
  1221. dealTestEchelon(testArrayEchelon);
  1222. }
  1223. }
  1224.  
  1225. // 课程任务执行方法
  1226. // 课程任务执行方法
  1227. // 课程任务执行方法
  1228.  
  1229. // 直接跳过
  1230. function skipChapter() {
  1231. addlog("跳过章节");
  1232. const chapterNext = window.top.document.querySelector("#prevNextFocusNext");
  1233. if (chapterNext) {
  1234. chapterNext.click();
  1235. } else {
  1236. // console.warn("未找到 #prevNextFocusNext 元素。");
  1237. }
  1238.  
  1239. setTimeout(() => {
  1240. const tip = document.querySelector(".maskDiv.jobFinishTip.maskFadeOut");
  1241. if (tip) {
  1242. const tipNextChapter = document.querySelector(".jb_btn.jb_btn_92.fr.fs14.nextChapter");
  1243. tipNextChapter?.click();
  1244. } else {
  1245. // console.warn("未找到完成提示或下一章节按钮。");
  1246. }
  1247. // 页面加载完成之后触发这个方法
  1248. window.onload = function() {
  1249. // 5秒后执行initAll
  1250. setTimeout(() => {
  1251. initAll();
  1252. }, 5000);
  1253. };
  1254. }, taskInterval);
  1255. }
  1256.  
  1257. // 忽略任务
  1258. function ignoreAns() {
  1259. addlog("无法处理, 忽略任务");
  1260. setTimeout(() => {
  1261. document.dispatchEvent(dealEvent);
  1262. }, taskInterval);
  1263. }
  1264.  
  1265. // 处理章节测试
  1266. function testHandler(idocument) {
  1267. testDom = idocument
  1268. addlog("处理章节测试任务中...");
  1269. if (ifTest == 'take') {
  1270. const iframe = idocument.querySelector("iframe");
  1271. // console.log('iframe:::+ ', iframe)
  1272. if (iframe) {
  1273. const sDocument = iframe.contentWindow?.document;
  1274. // console.log('sDocument:::+ ', sDocument)
  1275. if(sDocument) {
  1276. const form = sDocument.querySelector("form");
  1277. // 存储章节测试按钮DOM
  1278. testBtnDocument = sDocument.querySelector(".ZY_sub");
  1279. // console.log('form:::+ ', form)
  1280. if (form) {
  1281. const testTemps = Array.from(form.querySelectorAll('.singleQuesId, .newTestType'));
  1282. testTasks = testTemps.map((temp) => {
  1283. // console.log('temp:::+ ', temp)
  1284. return {
  1285. document: temp,
  1286. data: temp.getAttribute('data'),
  1287. class: temp.getAttribute('class'),
  1288. question: removeNewlines(temp.innerText),
  1289. type: extractText(temp.innerText)
  1290. }
  1291. }).filter(item => item.class == "singleQuesId")
  1292. console.log('testTasks:::+ ', testTasks)
  1293. testArrayEchelon = distributeTest(testTasks);
  1294. // 触发处理章节测试任务的事件
  1295. document.dispatchEvent(testDealEvent);
  1296. }
  1297. }
  1298. }
  1299. } else if (ifTest == 'skip') {
  1300. setTimeout(() => {
  1301. addlog("根据配置选项,跳过章节测试");
  1302. document.dispatchEvent(dealEvent);
  1303. }, taskInterval);
  1304. }
  1305. }
  1306.  
  1307. // 处理视频
  1308. function videoHandler(idocument) {
  1309. addlog("处理视频任务中...");
  1310. const video = idocument.querySelector("video");
  1311. const videoPlayButton = idocument.querySelector(".vjs-big-play-button");
  1312. const modalDialog = idocument.querySelector(".vjs-modal-dialog-content");
  1313. const closeButton = idocument.querySelector(".vjs-done-button");
  1314.  
  1315. // 检查视频和播放按钮是否存在
  1316. if (!video || !videoPlayButton) {
  1317. addlog("没有找到视频或播放按钮");
  1318. return;
  1319. }
  1320.  
  1321. const playVideo = () => {
  1322. if (video && typeof video.play === 'function') {
  1323. console.log('1video.play():::+ ')
  1324. video.muted = true; // 静音
  1325. video.play().catch((error) => {
  1326. console.error('视频播放失败:', error);
  1327. });
  1328. } else {
  1329. console.log('1videoPlayButton.click():::+ ')
  1330. videoPlayButton.click();
  1331. }
  1332. }
  1333. const clickPlayVideo = () => {
  1334. if (videoPlayButton && typeof videoPlayButton.click === 'function'){
  1335. console.log('2videoPlayButton.click():::+ ')
  1336. videoPlayButton.click();
  1337. } else {
  1338. console.log('2video.play():::+ ')
  1339. video.muted = true; // 静音
  1340. video.play().catch((error) => {
  1341. console.error('视频播放失败:', error);
  1342. });
  1343. }
  1344. }
  1345.  
  1346. const closeModalDialog = () => {
  1347. if (modalDialog && closeButton && modalDialog.getAttribute("aria-hidden") !== "true") {
  1348. setTimeout(() => {
  1349. closeButton.click();
  1350. }, 2000);
  1351. }
  1352. };
  1353.  
  1354. const onLoadedData = () => {
  1355. video.volume = volume;
  1356. addlog(`已将视频音量调节为${volume * 100}%`);
  1357.  
  1358. video.playbackRate = playbackRate;
  1359. addlog(`已将视频倍速调节为${playbackRate}X`);
  1360. };
  1361.  
  1362. const handlePause = () => {
  1363. clickPlayVideo();
  1364. closeModalDialog();
  1365. };
  1366.  
  1367. const updateProgress = () => {
  1368. const currentTime = video.currentTime;
  1369. const duration = video.duration;
  1370. const progress = (currentTime / duration) * 100;
  1371. setVideoProcess(progress.toFixed(2) + '%', currentTime.toFixed(0), duration.toFixed(0), 'video');
  1372. };
  1373.  
  1374. video.addEventListener("loadeddata", onLoadedData);
  1375. video.addEventListener("pause", handlePause);
  1376. video.addEventListener("timeupdate", updateProgress);
  1377.  
  1378. video.addEventListener("ended", () => {
  1379. addlog("视频任务已完成");
  1380. document.dispatchEvent(dealEvent);
  1381. video.removeEventListener("pause", handlePause);
  1382. video.removeEventListener("timeupdate", updateProgress);
  1383. }, { once: true });
  1384.  
  1385. setTimeout(() => {
  1386. if (video.paused && !video.ended) {
  1387. playVideo();
  1388. addlog('由于程序出错未自动播放,现重新模拟点击播放按钮...');
  1389. }
  1390. }, 10000);
  1391.  
  1392. playVideo();
  1393. }
  1394.  
  1395.  
  1396. // 处理音频
  1397. function audioHandler(idocument) {
  1398. addlog("处理音频任务中...");
  1399. const audio = idocument.querySelector("audio");
  1400. const audioPlayButton = idocument.querySelector(".vjs-play-control.vjs-control.vjs-button");
  1401.  
  1402. if (!audio) {
  1403. // console.warn("未找到音频元素。");
  1404. document.dispatchEvent(dealEvent);
  1405. return;
  1406. }
  1407.  
  1408. // 播放音频
  1409. audioPlayButton?.click();
  1410.  
  1411. // 监听音频播放结束事件
  1412. audio.addEventListener("ended", () => {
  1413. addlog("音频任务已完成");
  1414. document.dispatchEvent(dealEvent);
  1415. }, { once: true });
  1416.  
  1417. // 监听音频暂停事件并在暂停时继续播放
  1418. audio.addEventListener("pause", () => {
  1419. // console.log("音频暂停,自动恢复播放...");
  1420. audioPlayButton?.click();
  1421. });
  1422. // 监听音频进度变化
  1423. audio.addEventListener("timeupdate", () => {
  1424. const currentTime = audio.currentTime;
  1425. const duration = audio.duration;
  1426. const progress = (currentTime / duration) * 100;
  1427. setVideoProcess(progress.toFixed(2) + '%', currentTime.toFixed(0), duration.toFixed(0), 'audio');
  1428. });
  1429. }
  1430.  
  1431. // 处理 PPT & PDF
  1432. function pptxHandler(idocument) {
  1433. // 添加日志提示
  1434. addlog("处理 PPT/PDF 任务中...");
  1435.  
  1436. // 获取嵌入的 iframe 元素
  1437. const iframe = idocument.querySelector("iframe");
  1438. if (!iframe) {
  1439. console.warn("未找到嵌入的 iframe 元素。");
  1440. document.dispatchEvent(dealEvent);
  1441. return;
  1442. }
  1443.  
  1444. const sDocument = iframe.contentWindow?.document;
  1445. if (!sDocument) {
  1446. console.warn("无法获取 PPT/PDF 的文档内容。");
  1447. document.dispatchEvent(dealEvent);
  1448. return;
  1449. }
  1450.  
  1451. let finalHeight = sDocument.documentElement.scrollHeight;
  1452. let currentHeight = 0;
  1453.  
  1454. const scrollStep = 5; // 滚动的步长,较小的步长会让滚动更加平滑
  1455. const maxHeight = finalHeight; // 目标滚动的最大高度
  1456.  
  1457. // 平滑滚动函数
  1458. function smoothScroll() {
  1459. finalHeight = sDocument.documentElement.scrollHeight; // 动态更新最终高度
  1460. if (currentHeight >= maxHeight) {
  1461. addlog("PPT/PDF任务已完成");
  1462. document.dispatchEvent(dealEvent); // 完成滚动
  1463. return;
  1464. }
  1465.  
  1466. currentHeight += scrollStep; // 每次滚动小步长
  1467. sDocument.defaultView.scrollTo(0, currentHeight); // 执行滚动
  1468.  
  1469. // 计算滚动进度并更新
  1470. const progress = (currentHeight / maxHeight) * 100;
  1471. setVideoProcess(
  1472. progress.toFixed(2) + '%',
  1473. currentHeight.toFixed(0),
  1474. maxHeight.toFixed(0),
  1475. 'pdf'
  1476. );
  1477.  
  1478. // 请求下一帧
  1479. requestAnimationFrame(smoothScroll);
  1480. }
  1481.  
  1482. // 开始滚动动画
  1483. smoothScroll();
  1484. }
  1485.  
  1486.  
  1487.  
  1488.  
  1489. /** 任务梯队顺序处理 */
  1490. function dealAnsEchelon(arrayEchelon) {
  1491. const remainingTasks = arrayEchelon.length;
  1492. addlog(`待处理任务数量为: ${remainingTasks}`);
  1493. if (remainingTasks === 0) {
  1494. addlog('该章节任务已处理完成,即将跳转下一章节')
  1495. setTimeout(() => {
  1496. skipChapter();
  1497. }, taskInterval);
  1498. return;
  1499. }
  1500. const nextTask = arrayEchelon.shift();
  1501. try {
  1502. dealSingleAns(nextTask);
  1503. } catch (error) {
  1504. // console.error("处理任务时发生错误:", error);
  1505. // 继续处理下一个任务
  1506. dealAnsEchelon(arrayEchelon);
  1507. }
  1508. }
  1509.  
  1510.  
  1511. // 其他执行方法
  1512. // 其他执行方法
  1513. // 其他执行方法
  1514.  
  1515. // 获取章节测试题目-正确的
  1516. function getTestTopics() {
  1517.  
  1518. // 获取第一个符合条件的 iframe 并获取其中的子 iframe
  1519. const testIframe = testDom.querySelector("iframe")?.contentWindow?.document.querySelector('.CeYan');
  1520. console.log('testIframe:::+ ', testIframe);
  1521.  
  1522. // 如果没有找到目标元素,返回空数组
  1523. if (!testIframe) {
  1524. console.error('没有找到目标的测试 iframe');
  1525. return [];
  1526. }
  1527.  
  1528. // 获取题目元素
  1529. const TiMusDom = testIframe.querySelectorAll('.TiMu');
  1530. console.log('TiMusDom:::+ ', TiMusDom);
  1531.  
  1532. // 筛选出正确答案的题目
  1533. const rightTiMusDom = Array.from(TiMusDom).filter(TiMu => {
  1534. return TiMu.querySelector('.marking_dui') !== null;
  1535. });
  1536. console.log('rightTiMusDom:::+ ', rightTiMusDom);
  1537.  
  1538. // 提取题目和答案
  1539. const TiMus = Array.from(rightTiMusDom).map(TiMu => {
  1540. const filteredText = Array.from(TiMu.children)
  1541. .filter(child => !child.classList.contains('newAnswerBx') || !child.classList.contains('fl') ) // 过滤掉 class 为 newAnswerBx 的子元素
  1542. .map(child => child.innerText) // 获取剩余子元素的 innerText
  1543. .join(''); // 拼接所有文本内容
  1544.  
  1545. const question = removeNewlines(filteredText);
  1546. const answer = TiMu.querySelector('.answerCon')?.innerText || ''; // 使用 ? 确保 answerCon 存在
  1547. const type = extractText(question)
  1548. const email = GM_getValue("savedEmail") || ''
  1549. const course_name = GM_getValue("courseName") || '未获取课程名称';
  1550. return { question: question, answer: answer, type: type, email: email, course_name: course_name };
  1551. });
  1552.  
  1553. // console.log('TiMus:::+ ', TiMus);
  1554. return TiMus;
  1555. }
  1556.  
  1557.  
  1558.  
  1559. // 获取任务成功占比
  1560. function getTrueStatusPercentage(arr) {
  1561. // 过滤出status为true的元素
  1562. const trueCount = arr.filter(item => item.status === true).length;
  1563.  
  1564. // 计算占比
  1565. const percentage = (trueCount / arr.length) * 100;
  1566.  
  1567. return percentage;
  1568. }
  1569.  
  1570.  
  1571. // 索引转换选项
  1572. function mapIndexToOption(arr) {
  1573. arr = arr.map(item => {
  1574. switch (item) {
  1575. case 0:
  1576. return 'A';
  1577. case 1:
  1578. return 'B';
  1579. case 2:
  1580. return 'C';
  1581. case 3:
  1582. return 'D';
  1583. case 4:
  1584. return 'E';
  1585. case 5:
  1586. return 'F';
  1587. case 6:
  1588. return 'G';
  1589. default:
  1590. break
  1591. }
  1592. })
  1593. return arr.join('');
  1594. }
  1595.  
  1596. // 根据选项匹配索引
  1597. function findABCDEFPositions(str) {
  1598. const result = [];
  1599. const targetChars = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; // 目标字符集合
  1600. targetChars.forEach((text, index) => {
  1601. if(str.indexOf(text) !== -1) {
  1602. result.push(index); // 记录该字符的索引
  1603. }
  1604. })
  1605. return result;
  1606. }
  1607.  
  1608. function removeNewlines(str) {
  1609. // 使用正则表达式去除所有的换行符(\n)
  1610. return str.replace(/\n/g, '');
  1611. }
  1612.  
  1613. function extractText(str) {
  1614. // 使用正则表达式提取“【多选题】”中的“多选题”部分
  1615. const match = str.match(/【(.*?)】/);
  1616. return match ? match[1] : null;
  1617. }
  1618.  
  1619. // 获取当前时间,年月日时分秒
  1620. function getCurrentDateTime() {
  1621. var now = new Date();
  1622. var year = now.getFullYear();
  1623. var month = (now.getMonth() + 1).toString().padStart(2, '0'); // 月份从0开始,需要加1,并确保两位数格式
  1624. var day = now.getDate().toString().padStart(2, '0'); // 确保两位数格式
  1625. var hours = now.getHours().toString().padStart(2, '0'); // 确保两位数格式
  1626. var minutes = now.getMinutes().toString().padStart(2, '0'); // 确保两位数格式
  1627. var seconds = now.getSeconds().toString().padStart(2, '0'); // 确保两位数格式
  1628.  
  1629. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  1630. }
  1631.  
  1632. // 设置播放进度
  1633. function setVideoProcess(val1, val2, val3, type) {
  1634. if(type == 'video') {
  1635. var _process = window.top.document.querySelector('#content-process');
  1636. var _time = getCurrentDateTime()
  1637. var newContent = '<p style="color: #000;">[' + _time + ']' + '</p><hr><p>' + '播放进度:' + val1 + '</p><hr><p>' + '视频长度:' + val2 + 's' + '/' +val3 + 's' + '</p>';
  1638. _process.innerHTML = newContent;
  1639. } else if(type == 'audio') {
  1640. var _process = window.top.document.querySelector('#content-process');
  1641. var _time = getCurrentDateTime()
  1642. var newContent = '<p style="color: #000;">[' + _time + ']' + '</p><hr><p>' + '播放进度:' + val1 + '</p><hr><p>' + '音频长度:' + val2 + 's' + '/' +val3 + 's' + '</p>';
  1643. _process.innerHTML = newContent;
  1644. } else if(type == 'pdf') {
  1645. var _process = window.top.document.querySelector('#content-process');
  1646. var _time = getCurrentDateTime()
  1647. var newContent = '<p style="color: #000;">[' + _time + ']' + '</p><hr><p>' + '浏览进度:' + val1 + '</p><hr><p>' + 'pdf高度:' + val2 + 'px' + '/' +val3 + 'px' + '</p>';
  1648. _process.innerHTML = newContent;
  1649. } else if(type == 'test') {
  1650. var _process = window.top.document.querySelector('#content-process');
  1651. var _time = getCurrentDateTime()
  1652. var newContent = '<p style="color: #000;">[' + _time + ']' + '[' + '答题进度:' + val1 + ']' + '[' + val2 + '/' + val3 + ']' + '</p>';
  1653. testTasks.forEach((item,index) => {
  1654. const testItem = item.status ? `<p><span style="color: green">[${index + 1}]</span><span>${item.answer_text}</span></p>` : `<p><span style="color: red">[${index + 1}]</span><span>${item.answer_text || '暂无答案'}</span></p>`
  1655. newContent += testItem
  1656. })
  1657. _process.innerHTML = newContent;
  1658. }
  1659. }
  1660.  
  1661. // 获取视频播放进度-定时监听页面内容进行下一步处理
  1662. function getVideoProgress() {
  1663. // 同步课程进度
  1664. if(location.pathname == '/mycourse/studentstudy') {
  1665. const emailInput = document.getElementById("email-input");
  1666. const email = emailInput.value.trim();
  1667. const url = 'https://www.sweek.top/api/checkEmailExists';
  1668. const data = {
  1669. email
  1670. };
  1671. if(email) {
  1672. fetch(url, {
  1673. method: 'POST',
  1674. headers: {
  1675. 'Content-Type': 'application/json', // 声明请求主体的内容类型为 JSON
  1676. },
  1677. body: JSON.stringify(data), // 将数据对象转换为 JSON 字符串并作为请求主体
  1678. }).then(response => {
  1679. if (!response.ok) {
  1680. throw new Error('Network response was not ok');
  1681. }
  1682. return response.json(); // 解析 JSON 响应数据
  1683. }).then(data => {
  1684. if(data.exists){
  1685. syncCourseData()
  1686. } else {
  1687. var _async_time = window.top.document.querySelector('#async-time');
  1688. var newContent = `<span style="color: #000;">[同步课程信息失败,邮箱未注册]</span>`;
  1689. _async_time.innerHTML = newContent;
  1690. }
  1691. }).catch(error => {
  1692. // console.error('Error:', error);
  1693. });
  1694. }
  1695.  
  1696. // 脚本运行过程中如果弹出弹窗,发现后关闭-10s执行一次
  1697. const jobFinishTip = document.querySelector(".jobFinishTip");
  1698. const nextChapter = document.querySelector(".nextChapter");
  1699. if (jobFinishTip) {
  1700. const computedStyle = getComputedStyle(jobFinishTip);
  1701. if (computedStyle.display !== 'none') {
  1702. nextChapter.click()
  1703. }
  1704. }
  1705. }
  1706. }
  1707.  
  1708. // 获取页面url
  1709. function getURLInfo() {
  1710. if(location.pathname == '/mycourse/studentstudy') {
  1711. url = location.href
  1712. // 获取问号后面的部分
  1713. var queryString = url.split('?')[1];
  1714. // 将查询字符串拆分为参数对
  1715. var queryParams = queryString.split('&');
  1716. // 创建一个对象来存储参数
  1717. var params = {};
  1718. // 遍历参数对,将它们存储在对象中
  1719. queryParams.forEach(function(queryParam) {
  1720. var parts = queryParam.split('=');
  1721. var key = decodeURIComponent(parts[0]);
  1722. var value = decodeURIComponent(parts[1]);
  1723. params[key] = value;
  1724. });
  1725. chapterId = params['chapterId']
  1726. courseId = params['courseId']
  1727. GM_setValue("courseId", courseId);
  1728. }
  1729. }
  1730.  
  1731. // 获取当前页面章节小节的所有分栏
  1732. function getSubChapter() {
  1733. try {
  1734. if(location.pathname == '/mycourse/studentstudy') {
  1735. var subChapter = window.top.document.querySelector('#prev_tab');
  1736. if(subChapter) {
  1737. currentChapterTabArr = subChapter.querySelectorAll('li');
  1738. currentChapterTabArr.forEach(function(item) {
  1739. if(item.className === 'active') {
  1740. currentChapterTabName = item.innerText
  1741. }
  1742. })
  1743. }
  1744. }
  1745. } catch (error) {
  1746. // console.error('An error occurred:', error);
  1747. // location.reload(); // 刷新页面
  1748. }
  1749. }
  1750.  
  1751. // 获取课程所有章节节点数据
  1752. function getChapterCodeInfo() {
  1753. if(location.pathname === '/mooc-ans/knowledge/cards') {
  1754. var chapter = window.top.document.querySelectorAll('.posCatalog_select')
  1755. chapterInfo = chapter
  1756. allChapterName=[]
  1757.  
  1758. chapterInfo.forEach(function(item) {
  1759. allChapterName.push({
  1760. id: item.id,
  1761. title: item.innerText,
  1762. active: item.classList.contains('posCatalog_active') ? 1 : 0,
  1763. ifTitle: item.classList.contains('firstLayer') ? 1 : 0,
  1764. status: item.childNodes[3]?.className == 'icon_Completed prevTips' ? 1 : 0
  1765. })
  1766. if (item.classList.contains('posCatalog_active')) {
  1767. currentChapterId = item.id
  1768. GM_setValue("currentChapterId", currentChapterId);
  1769. currentChapterName = item.innerText
  1770. GM_setValue("currentChapterName", currentChapterName);
  1771. }
  1772. });
  1773. GM_setValue("chapterInfo", JSON.parse(JSON.stringify(allChapterName)));
  1774. addlog('当前章节为' + currentChapterName, 'green')
  1775. }
  1776. }
  1777.  
  1778. // 获取公告数据
  1779. function getBoard() {
  1780. fetch('https://www.sweek.top/api/board')
  1781. .then(response => response.json())
  1782. .then(data => {
  1783. // 在这里处理接收到的数据
  1784. var notice = document.querySelector('#content-notice');
  1785. notice.innerHTML = data.text
  1786. })
  1787. .catch(error => {
  1788. // 在这里处理错误
  1789. // console.error('Error:', error);
  1790. });
  1791. }
  1792.  
  1793. // 同步课程数据至数据库
  1794. async function syncCourseData() {
  1795. const url = 'https://www.sweek.top/api/insertOrUpdateCourse';
  1796. const email = GM_getValue("savedEmail") || '';
  1797. const course_name = GM_getValue("courseName") || '测试';
  1798. const course_id = GM_getValue("courseId") || '';
  1799. const course_img = GM_getValue("courseImg") || '';
  1800. const process = document.querySelector('#content-process').innerHTML;
  1801. const chapter_info = GM_getValue("chapterInfo");
  1802. const chapters = [...window.top.document.querySelectorAll('.posCatalog_select')];
  1803. const allChapterName = chapters.map(item => {
  1804. const isActive = item.classList.contains('posCatalog_active');
  1805. const isFirstLayer = item.classList.contains('firstLayer');
  1806. const isCompleted = item.childNodes[3]?.className === 'icon_Completed prevTips';
  1807.  
  1808. if (isActive) {
  1809. GM_setValue("currentChapterId", item.id);
  1810. GM_setValue("currentChapterName", item.innerText);
  1811. }
  1812.  
  1813. return {
  1814. id: item.id,
  1815. title: item.innerText,
  1816. active: isActive ? 1 : 0,
  1817. ifTitle: isFirstLayer ? 1 : 0,
  1818. status: isCompleted ? 1 : 0,
  1819. };
  1820. });
  1821.  
  1822. const data = {
  1823. email,
  1824. course_id,
  1825. course_name,
  1826. course_img,
  1827. chapter: JSON.stringify(allChapterName),
  1828. current_chapter: `${GM_getValue("currentChapterId") || ''},${GM_getValue("currentChapterName") || ''}`,
  1829. process,
  1830. };
  1831.  
  1832. // 播放异常刷新页面
  1833. // if(process.includes("NaN")) {
  1834. // location.reload(); // 刷新页面
  1835. // return;
  1836. // }
  1837.  
  1838. if (email && process) {
  1839. try {
  1840. const response = await fetch(url, {
  1841. method: 'POST',
  1842. headers: {
  1843. 'Content-Type': 'application/json',
  1844. },
  1845. body: JSON.stringify(data),
  1846. });
  1847.  
  1848. if (!response.ok) {
  1849. throw new Error('Network response was not ok');
  1850. }
  1851.  
  1852. const responseData = await response.json();
  1853. const _async_time = window.top.document.querySelector('#async-time');
  1854. const _time = getCurrentDateTime();
  1855. _async_time.innerHTML = `<span style="color: #000;">[同步时间:${_time}]</span>`;
  1856. } catch (error) {
  1857. console.error('Error:', error.message);
  1858. }
  1859. }
  1860. }
  1861.  
  1862. // 存储章节测试题目至数据库
  1863. async function insertTest(arr) {
  1864. const url = 'https://www.sweek.top/api/insertTest';
  1865. const data = {
  1866. testList: arr
  1867. }
  1868. if (arr.length > 0) {
  1869. try {
  1870. const response = await fetch(url, {
  1871. method: 'POST',
  1872. headers: {
  1873. 'Content-Type': 'application/json',
  1874. },
  1875. body: JSON.stringify(data),
  1876. });
  1877.  
  1878. if (!response.ok) {
  1879. throw new Error('Network response was not ok');
  1880. }
  1881. console.log('题目同步成功!:::+ ', arr)
  1882. } catch (error) {
  1883. console.error('Error:', error.message);
  1884. }
  1885. }
  1886. }
  1887. // 存储章节测试题目至数据库
  1888. async function insertTestModel(arr) {
  1889. const url = 'https://www.sweek.top/api/insertTestModel';
  1890. const data = {
  1891. testList: arr
  1892. }
  1893. if (arr.length > 0) {
  1894. try {
  1895. const response = await fetch(url, {
  1896. method: 'POST',
  1897. headers: {
  1898. 'Content-Type': 'application/json',
  1899. },
  1900. body: JSON.stringify(data),
  1901. });
  1902.  
  1903. if (!response.ok) {
  1904. throw new Error('Network response was not ok');
  1905. }
  1906. console.log('题目同步成功!:::+ ', arr)
  1907. } catch (error) {
  1908. console.error('Error:', error.message);
  1909. }
  1910. }
  1911. }
  1912.  
  1913.  
  1914. // 方法执行入口
  1915. (function () {
  1916. // 进入学习通弹出提示
  1917. if(location.pathname == '/base') {
  1918. notify('已进入学习通首页,请进入课程,选择需要学习的课程', 5000)
  1919. }
  1920. // 进入课程弹出提示
  1921. if (location.pathname == '/mooc2-ans/mycourse/stu') {
  1922. let courseName = window.top.document.querySelector('.classDl .colorDeep')?.getAttribute('title');
  1923. let courseImg = window.top.document.querySelector('.classDl').getElementsByTagName("img")[0]?.getAttribute('src');
  1924.  
  1925. if (courseName && courseImg) {
  1926. GM_setValue("courseName", courseName);
  1927. GM_setValue("courseImg", courseImg);
  1928. notify('已进入课程:' + courseName + ',请选择需要学习的章节', 5000);
  1929. } else {
  1930. console.error('课程信息未能获取');
  1931. }
  1932. }
  1933.  
  1934. // 初始化显示页面弹窗
  1935. if(location.pathname == '/mycourse/studentstudy') {
  1936. initPopup()
  1937. // 获取公告数据
  1938. getBoard()
  1939. // 邮箱操作
  1940. takeEmail()
  1941. }
  1942. // 获取页面章节节点数据
  1943. getChapterCodeInfo()
  1944. // 获取当前页面章节小节的所有分栏
  1945. getSubChapter()
  1946. // 获取页面url信息
  1947. getURLInfo()
  1948.  
  1949. // 定时打印视频播放进度-定时监听页面内容进行下一步处理
  1950. videoProgressId = setInterval(() => {
  1951. getVideoProgress()
  1952. }, 10000);
  1953.  
  1954. // 页面加载完成之后触发这个方法
  1955. window.onload = function() {
  1956. // 5秒后执行initAll
  1957. setTimeout(() => {
  1958. initAll();
  1959. }, 5000);
  1960. };
  1961. })();