【学习通刷课助手】||防止鼠标移出暂停|章节结束自动跳转|章节测试[调用AI接口答题,正确率待验证]

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

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