希赛

希赛页面优化

目前为 2024-04-10 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name EduCity
  3. // @name:zh-CN 希赛
  4. // @description Optimize the website of educity.cn.
  5. // @description:zh-CN 希赛页面优化
  6. // @namespace https://github.com/HaleShaw
  7. // @version 1.4.5
  8. // @author HaleShaw
  9. // @copyright 2021+, HaleShaw (https://github.com/HaleShaw)
  10. // @license AGPL-3.0-or-later
  11. // @homepage https://github.com/HaleShaw/TM-EduCity
  12. // @supportURL https://github.com/HaleShaw/TM-EduCity/issues
  13. // @contributionURL https://www.jianwudao.com/
  14. // @icon https://wangxiao.xisaiwang.com/favicon.ico
  15. // @match https://www.educity.cn/wangxiao2/*
  16. // @match http://www.educity.cn/wangxiao2/*
  17. // @match https://uc.educity.cn/personalCenter/studyCenter.html
  18. // @match https://uc.educity.cn/tiku/testReport.html*
  19. // @match https://uc.educity.cn/tiku/examinationMode.html*
  20. // @match https://uc.educity.cn/tiku/examinationModeCopy.html*
  21. // @match https://wangxiao.xisaiwang.com/*
  22. // @compatible Chrome
  23. // @grant GM_addStyle
  24. // @grant GM_info
  25. // ==/UserScript==
  26.  
  27. // ==OpenUserJS==
  28. // @author HaleShaw
  29. // @collaborator HaleShaw
  30. // ==/OpenUserJS==
  31.  
  32. (function () {
  33. "use strict";
  34.  
  35. const mainStyle = `
  36. /* 顶部菜单 */
  37. div.ecv2_header_tools > div.fl > ul > li:not(:first-child),
  38. div.ecv2_header_tools > div.fr > :not(:last-child),
  39.  
  40. /* Footer */
  41. .ecv2_footer {
  42. display: none !important;
  43. }
  44. `;
  45.  
  46. const zhiBoStyle = `
  47. /* 左上角标题 */
  48. #mainVid > div.vid_head > div.vid_hleft > a:not(:last-child),
  49.  
  50. /* 右上角“返回旧版、视频课程” */
  51. #mainVid > div.vid_head > div.vid_hright > a,
  52.  
  53. /* 离线观看 */
  54. #mainVid > div.vid_mid > div.vid_midL > div.vid_midL_top,
  55.  
  56. /* 右侧笔记、提问 */
  57. #mainVid > div.vid_mid > div.vid_midR > div.vid_midR_tab > div > a:nth-child(2),
  58. #mainVid > div.vid_mid > div.vid_midR > div.vid_midR_tab > div > a:nth-child(3),
  59.  
  60. /* 鼠标混入视频时的浮标按钮“新建笔记” */
  61. .vid_bj_new,
  62.  
  63. /* 顶部横条 */
  64. .vid_head {
  65. display: none !important;
  66. }
  67.  
  68. .vid_head .vid_hright {
  69. padding-top: 0 !important;
  70. }
  71.  
  72. .vid_mid .vid_midL {
  73. padding: 0 !important;
  74. }
  75.  
  76. .vid_midR_tab,
  77. .vid_midR_tab > a,
  78. .vid_midR_tab > a > i,
  79. .vid_midR_tab > .vid_midR_ul,
  80. .vid_midR_tab > .vid_midR_ul > a,
  81. .vid_midR_tab > .vid_midR_ul > a > i,
  82. .tabhide .vid_midR{
  83. width: 28px !important;
  84. }
  85.  
  86. .tabhide .vid_mid {
  87. padding-right: 28px;
  88. }
  89. .vid_mid {
  90. padding-right: 439px;
  91. }
  92.  
  93. .vid_midR {
  94. width: 439px;
  95. }
  96.  
  97. .vid_tab_content {
  98. padding-bottom: 0;
  99. }
  100.  
  101. /* 进度条上的时间 */
  102. .time-span {
  103. margin: 0 10px;
  104. }
  105.  
  106. /* 视频播放窗口上的遮罩元素 */
  107. #videoCoverTop {
  108. width: 100%;
  109. height: 17.4%;
  110. position: absolute;
  111. left: 0;
  112. top: 0;
  113. text-align: center;
  114. padding-top: 1.5rem;
  115. font-size: 1.75rem;
  116. z-index: 1;
  117. background-color: black;
  118. }
  119.  
  120. #videoCoverBottom {
  121. position: absolute;
  122. width: 100%;
  123. height: 14%;
  124. background-color: black;
  125. z-index: 2;
  126. left: 0;
  127. bottom: 0;
  128. display: flex;
  129. align-items: flex-end;
  130. justify-content: right;
  131. padding-bottom: 63px;
  132. }
  133.  
  134. #videoCoverLeft {
  135. position: absolute;
  136. width: 25.4%;
  137. height: 43%;
  138. background-color: white;
  139. z-index: 0;
  140. left: 0;
  141. bottom: 14%;
  142. }
  143.  
  144. /* 视频播放窗口进度条上的时间 */
  145. .pv-time-remaining-real.time-span,
  146. .pv-time-over.time-span {
  147. color: yellow;
  148. }
  149. `;
  150.  
  151. // 测试报告页面样式
  152. const reportStyle = `
  153. /* 顶部广告 */
  154. #accountSettingsHeader,
  155.  
  156. /* 专家建议 */
  157. div.col-md-7.testportSty > p,
  158. div.col-md-7.testportSty > a,
  159.  
  160. /* 笔记、提问 */
  161. div.doPane.note,
  162. div.doPane.question,
  163.  
  164. /* 参考答案,你的答案 */
  165. .answerEnd,
  166.  
  167. /* 选项前的radio */
  168. .answerContentList.mgt10 > .cbox,
  169.  
  170. /* 查看解析,收藏*/
  171. #dataListWarp > div.dajx > div.pull-right.clearfix {
  172. display: none !important;
  173. }
  174. `;
  175.  
  176. // 考试或练习页面
  177. const examStyle = `
  178. /* 顶部图片Header */
  179. #accountSettingsHeader,
  180.  
  181. /* 右上角二维码 */
  182. div.zt_top_right,
  183.  
  184. /* 正确答案与错误答案选项 */
  185. .analysisAnswer>div.bg-fff.box-shadow.mgt10:first-child,
  186.  
  187. /* 答案解析中的“笔记”和“提问” */
  188. div.tknew.doPane.note,
  189. div.tknew.doPane.question,
  190.  
  191. div.col-md-12>div>div.zt_top_right,
  192. .lh2>span,
  193. #accountSettingsHeader,
  194. .center.answerCard,
  195. .pull-right>span:not(:first-child),
  196. .answerTitle {
  197. display: none !important;
  198. }
  199.  
  200. /* 右侧题目编号列表 */
  201. .dtklist.item {
  202. height: 500px;
  203. }
  204.  
  205. /* 选项前的CheckBox */
  206. div.answerContentList>span.cbox {
  207. display: inline-block !important;
  208. }
  209.  
  210. /* 标题 */
  211. .zt_top_zong {
  212. height: 50px;
  213. background: none !important;
  214. }
  215.  
  216. .right-title {
  217. padding: 0px 20px !important;
  218. margin-bottom: 0px !important;
  219. }
  220.  
  221. /* 进度条 */
  222. .jindu_div {
  223. margin: 5px 0 !important;
  224. }
  225.  
  226. .jindu_div .jindu_line {
  227. width: calc(100% - 160px);
  228. }
  229.  
  230. .bp20 {
  231. padding: 5px !important;
  232. }
  233.  
  234. .ISpan {
  235. margin-bottom: 2px !important;
  236. margin-right: 0 !important;
  237. }
  238.  
  239. div.answerList.mgb20 {
  240. padding: 0 15px 0 25px !important;
  241. margin-bottom: 0px !important;
  242. }
  243.  
  244. /* 题干 */
  245. .subject-content {
  246. padding: 0px 30px !important;
  247. background: none !important;
  248. min-height: 320px !important;
  249. }
  250.  
  251. .single-content {
  252. padding: 5px 10px !important;
  253. }
  254.  
  255. .lh2 {
  256. margin-bottom: 5px !important;
  257. }
  258.  
  259. .examTigan {
  260. max-height: 600px;
  261. overflow-y: auto;
  262. display: block;
  263. }
  264.  
  265. .examTigan > p {
  266. font-size: 2.3rem;
  267. }
  268.  
  269. /* 选项列表 */
  270. .answerContentList > label {
  271. font-size: 2.2rem;
  272. color: #666;
  273. }
  274.  
  275. /* 参考解析 */
  276. div.analysisAnswer>div {
  277. padding-bottom: 0px !important;
  278. }
  279.  
  280. .answerEnd {
  281. padding: 0 !important;
  282. margin-top: 0 !important;
  283. }
  284.  
  285. .answerList {
  286. padding: 0 15px 5px 25px !important;
  287. }
  288.  
  289. .shitiDesp.pdt15 {
  290. padding-top: 0 !important;
  291. }
  292.  
  293. .jiexinew {
  294. padding: 10px 30px 0 30px !important;
  295. }
  296.  
  297. #jiexispan>p {
  298. margin-bottom: 0 !important;
  299. }
  300.  
  301. .countTime {
  302. padding: 0 !important;
  303. }
  304.  
  305. #ztsetWrap>div.bg-fff.box-shadow {
  306. margin-bottom: 0 !important;
  307. }
  308.  
  309. /* 顶部标题高度 */
  310. .mgt10 {
  311. margin-top: 0px !important;
  312. }
  313.  
  314. .lh2 {
  315. font-weight: bolder !important;
  316. color: #337ab7 !important;
  317. font-size: 1.125em !important;
  318. }
  319.  
  320. .spanExplain {
  321. padding: 5px 20px !important;
  322. }
  323.  
  324. .exBtn {
  325. margin-top: 5px !important;
  326. }
  327.  
  328. .answerWrap {
  329. padding: 0px 30px !important;
  330. }
  331.  
  332. .mgb20 {
  333. margin-bottom: 0px !important;
  334. }
  335.  
  336. .singleR {
  337. font-size: 16px;
  338. }
  339. `;
  340.  
  341. // 个人中心页面
  342. const personalStyle = `
  343. /* 班主任微信 */
  344. #baomingDiv > .kcgw_weixin,
  345.  
  346. /* 用户ID,我的报名 */
  347. div.xpc_top_info > div.xpc_top_V2,
  348.  
  349. /* 左侧边栏菜单栏 */
  350. div.xpc-menu-box > dl:last-child,
  351. div.xpc-menu-box > dl:nth-last-child(2):nth-child(odd),
  352. div.xpc-menu-box > dl:nth-child(2) > dd:last-child,
  353.  
  354. /* 右侧悬浮工具 */
  355. .ecv2_right_tools {
  356. display: none !important;
  357. }
  358.  
  359. #cHeadImg {
  360. height: 48px;
  361. width: 48px;
  362. }
  363.  
  364. .xpc_top,
  365. .xpc_top .xpc_top_con,
  366. .xpc_top .xpc-top-message li a {
  367. height: 60px !important;
  368. }
  369.  
  370. /* 播放列表 */
  371. .ecv2_live_taggleTitles {
  372. padding: 5px 20px 5px 20px !important;
  373. }
  374. .ecv2_live_taggleHide li {
  375. padding: 5px 0 5px 68px !important;
  376. }
  377.  
  378. .xpc_zbmulu {
  379. padding: 0 20px !important;
  380. }
  381.  
  382. /* 主窗口 */
  383. .xpc_main .xpc_main_con {
  384. padding: 0 !important;
  385. }
  386. `;
  387.  
  388. const ANSWER_LIST = ["A", "B", "C", "D"];
  389.  
  390. main();
  391.  
  392. function main() {
  393. logInfo(GM_info.script.name, GM_info.script.version);
  394. GM_addStyle(mainStyle);
  395.  
  396. let url = window.location.href;
  397. // 个人中心页面
  398. if (url.startsWith("https://wangxiao.xisaiwang.com/ucenter2/")) {
  399. GM_addStyle(personalStyle);
  400. setTimeout(() => {
  401. updatePlayButton();
  402. }, 1500);
  403. } else if (
  404. url.startsWith("https://www.educity.cn/wangxiao2") ||
  405. url.startsWith("http://www.educity.cn/wangxiao2") ||
  406. url.startsWith("https://wangxiao.xisaiwang.com/wangxiao2/")
  407. ) {
  408. // 直播回放调节播放速度
  409. setTimeout(() => {
  410. addCover();
  411. updateSpeed();
  412. }, 1500);
  413. } else if (url.startsWith("https://wangxiao.xisaiwang.com/tiku2/ctjx")) {
  414. // 独立的错题解析页面,添加键盘事件
  415. GM_addStyle(examStyle);
  416. addLeftRightKeyListener();
  417. } else if (url.startsWith("https://wangxiao.xisaiwang.com/tiku2/sectionReport")) {
  418. // 测试报告页面
  419. GM_addStyle(reportStyle);
  420. showWrongTopics();
  421. } else if (
  422. url.startsWith("https://uc.educity.cn/tiku/examinationModeCopy.html") ||
  423. url.startsWith("https://uc.educity.cn/tiku/examinationMode.html") ||
  424. url.startsWith("https://wangxiao.xisaiwang.com/tiku2/exam")
  425. ) {
  426. // 添加键盘监听事件,按键答题
  427. GM_addStyle(examStyle);
  428. addKeyListener();
  429. }
  430. }
  431.  
  432. /**
  433. * 更新播放按钮事件
  434. * 将原有事件移除,在新页面打开播放页面。
  435. */
  436. function updatePlayButton() {
  437. let buttons = document.querySelectorAll(
  438. 'div.detail-course-top > a.detail-course-btnC.buyProductDetail[data-type="Video"]'
  439. );
  440. for (let i = 0; i < buttons.length; i++) {
  441. buttons[i].removeAttribute("onclick");
  442. buttons[i].setAttribute("target", "_blank");
  443. const uri =
  444. "/wangxiao2/c" +
  445. $(buttons[i]).attr("data-cid") +
  446. "/sp" +
  447. $(buttons[i]).attr("data-id") +
  448. ".html";
  449. buttons[i].setAttribute("href", uri);
  450. }
  451. }
  452.  
  453. function updateSpeed() {
  454. GM_addStyle(zhiBoStyle);
  455.  
  456. addRemainingTime();
  457. updateVideoTitle();
  458. addRateButton();
  459. addRateListener();
  460. updateSideHeight();
  461. addPersonalCenter();
  462. }
  463.  
  464. /**
  465. * 添加视频播放窗口上的遮罩元素
  466. */
  467. function addCover() {
  468. let parentEle = document.querySelector(".pv-video-wrap");
  469. let videoCoverTop = document.getElementById("videoCoverTop");
  470. let videoCoverBottom = document.getElementById("videoCoverBottom");
  471. let videoCoverLeft = document.getElementById("videoCoverLeft");
  472. let title = document.querySelector("a.log.pointer.video-player.act").textContent.trim();
  473. if (parentEle && !videoCoverTop && !videoCoverBottom && !videoCoverLeft) {
  474. parentEle.appendChild(
  475. $(
  476. `<div id="videoCoverTop">${title} &nbsp;&nbsp;&nbsp;&nbsp;剩余课时:${getRemainingClass()}</div>`
  477. )[0]
  478. );
  479. parentEle.appendChild(
  480. $(`<div id="videoCoverBottom"><span class="videoCoverBottom"></span></div>`)[0]
  481. );
  482. parentEle.appendChild($(`<div id="videoCoverLeft"></div>`)[0]);
  483. }
  484. }
  485.  
  486. /**
  487. * 添加倍速按钮
  488. */
  489. function addRateButton() {
  490. if (!$(".pv-rate-select") || $(".pv-rate-select").length == 0) {
  491. return;
  492. }
  493. $(
  494. '<div data-rate="4">4x</div><div data-rate="3.5">3.5x</div><div data-rate="3">3x</div><div data-rate="2.5">2.5x</div>'
  495. ).insertBefore($(".pv-rate-select").children().eq(0));
  496. }
  497.  
  498. // 获取当前倍速
  499. function getCurrentRate() {
  500. let rate = 1.0;
  501. let rateEle = document.querySelector("button.pv-rate-btn>span");
  502. if (!rateEle) {
  503. return rate;
  504. }
  505. rate = rateEle.textContent.replace("x", "");
  506.  
  507. return rate;
  508. }
  509.  
  510. /**
  511. * 添加控制播放速度的监听事件,仅用于更新显示当前速度,速度控制通过其他通用三方脚本实现。
  512. */
  513. function addRateListener() {
  514. let rateEle = document.querySelector("button.pv-rate-btn>span");
  515.  
  516. document.onkeyup = function (e) {
  517. var theEvent = e || window.event;
  518. var code = theEvent.keyCode || theEvent.which || theEvent.charCode;
  519. if (code == 88) {
  520. // X,减速
  521. let rate = getCurrentRate();
  522. let newRate = (new Number(rate) - new Number(0.1)).toFixed(1);
  523. let rateStr = newRate + "x";
  524. rateEle.textContent = rateStr;
  525. updateRate(rateStr);
  526. }
  527. if (code == 67) {
  528. // C,加速
  529. let rate = getCurrentRate();
  530. let newRate = (new Number(rate) + new Number(0.1)).toFixed(1);
  531. let rateStr = newRate + "x";
  532. rateEle.textContent = rateStr;
  533. updateRate(rateStr);
  534. }
  535. if (code == 90) {
  536. // Z,恢复正常速度
  537. rateEle.textContent = "1x";
  538. updateRate("1x");
  539. }
  540. };
  541. }
  542.  
  543. /**
  544. * 更新右侧边栏上的播放倍率
  545. * @param {String} rate 播放倍率
  546. */
  547. function updateRate(rate) {
  548. let rateEle = document.querySelector("span.rateRight");
  549. if (rateEle) {
  550. rateEle.textContent = rate;
  551. } else {
  552. document
  553. .querySelector(".vid_midR_tab")
  554. .appendChild($(`<span class="rateRight">${rate}</span>`)[0]);
  555. }
  556. }
  557.  
  558. /**
  559. * 更新右侧侧边栏高度
  560. */
  561. function updateSideHeight() {
  562. let sideBar = document.querySelector(".vid_midR_tab");
  563. if (!sideBar) {
  564. return;
  565. }
  566. sideBar.style.height = sideBar.parentElement.previousElementSibling.offsetHeight + "px";
  567. document.querySelector(".vid_midR_wrap").style.height =
  568. sideBar.parentElement.previousElementSibling.offsetHeight + "px";
  569. }
  570.  
  571. /**
  572. * 添加个人中心按钮
  573. */
  574. function addPersonalCenter() {
  575. $('<a href="/ucenter2/personal/index.html" target="_blank">个人中心</a>').insertBefore(
  576. $(".vid_midR_tab").children().eq(0)
  577. );
  578. }
  579.  
  580. // 添加键盘监听事件,按键答题
  581. function addKeyListener() {
  582. document.onkeyup = function (e) {
  583. var theEvent = e || window.event;
  584. var code = theEvent.keyCode || theEvent.which || theEvent.charCode;
  585. // Spacebar. 查看答案解析
  586. if (code == 32 && document.getElementsByClassName("col-md-4 center bottomCenter bp20")[0]) {
  587. document.getElementsByClassName("col-md-4 center bottomCenter bp20")[0].click();
  588. scrollToBottom();
  589. }
  590. // Left Arrow.
  591. if (code == 37 && document.getElementsByClassName("col-md-4 center bp20 bLeftWrap")[0]) {
  592. document.getElementsByClassName("col-md-4 center bp20 bLeftWrap")[0].click();
  593. setTimeout(() => {
  594. let tiGan = document.querySelector("div.examTigan");
  595. scrollElementToBottom(tiGan);
  596. }, 200);
  597. scrollToBottom();
  598. }
  599. // Right Arrow.
  600. if (code == 39 && document.getElementsByClassName("col-md-4 center bp20 bRightWrap")[0]) {
  601. document.getElementsByClassName("col-md-4 center bp20 bRightWrap")[0].click();
  602. setTimeout(() => {
  603. let tiGan = document.querySelector("div.examTigan");
  604. scrollElementToBottom(tiGan);
  605. }, 200);
  606. scrollToBottom();
  607. }
  608. // A,1.
  609. if (code == 49 || code == 65 || code == 97) {
  610. document.getElementById("slec0A").click();
  611. scrollToBottom();
  612. }
  613. // B,2.
  614. if (code == 50 || code == 66 || code == 98) {
  615. document.getElementById("slec0B").click();
  616. scrollToBottom();
  617. }
  618. // C,3.
  619. if (code == 51 || code == 67 || code == 99) {
  620. document.getElementById("slec0C").click();
  621. scrollToBottom();
  622. }
  623. // D,4.
  624. if (code == 52 || code == 68 || code == 100) {
  625. document.getElementById("slec0D").click();
  626. scrollToBottom();
  627. }
  628. // J. 标记
  629. if (code == 74 && document.getElementsByClassName("bj_icon addBiaoji")[0]) {
  630. document.getElementsByClassName("bj_icon addBiaoji")[0].click();
  631. setTimeout(function () {
  632. if (document.getElementsByClassName("swal-button swal-button--confirm")[0]) {
  633. document.getElementsByClassName("swal-button swal-button--confirm")[0].click();
  634. document.getElementsByClassName("col-md-4 center bp20 bRightWrap")[0].click();
  635. }
  636. }, 300);
  637. }
  638. // J. 取消标记
  639. if (code == 74 && document.getElementsByClassName("bj_icon cancelBiaoji")[0]) {
  640. document.getElementsByClassName("bj_icon cancelBiaoji")[0].click();
  641. setTimeout(function () {
  642. if (document.getElementsByClassName("swal-button swal-button--confirm")[0]) {
  643. document.getElementsByClassName("swal-button swal-button--confirm")[0].click();
  644. }
  645. }, 300);
  646. }
  647. // P.暂停
  648. if (code == 80 && document.getElementsByClassName("inline-block zanTing")[0]) {
  649. document.getElementsByClassName("inline-block zanTing")[0].click();
  650. }
  651. // P.继续做题
  652. if (
  653. code == 80 &&
  654. document.getElementsByClassName("swal-button swal-button--confirm")[0] &&
  655. document.getElementsByClassName("swal-button swal-button--confirm")[0].textContent ==
  656. "继续做题"
  657. ) {
  658. document.getElementsByClassName("swal-button swal-button--confirm")[0].click();
  659. }
  660. };
  661. }
  662.  
  663. // 添加左右方向键监听事件
  664. function addLeftRightKeyListener() {
  665. document.onkeyup = function (e) {
  666. var theEvent = e || window.event;
  667. var code = theEvent.keyCode || theEvent.which || theEvent.charCode;
  668. // Left Arrow.
  669. if (code == 37 && document.getElementsByClassName("col-md-4 center bp20 bLeftWrap")[0]) {
  670. document.getElementsByClassName("col-md-4 center bp20 bLeftWrap")[0].click();
  671. }
  672. // Right Arrow.
  673. if (code == 39 && document.getElementsByClassName("col-md-4 center bp20 bRightWrap")[0]) {
  674. document.getElementsByClassName("col-md-4 center bp20 bRightWrap")[0].click();
  675. }
  676. };
  677. }
  678.  
  679. // 只看错题
  680. function showWrongTopics() {
  681. loadErrData();
  682. setTimeout(() => {
  683. let showButtons = document.querySelectorAll("#dataListWarp>ul>li>h4.chak.zhank");
  684. for (let i = 0; i < showButtons.length; i++) {
  685. showButtons[i].click();
  686. }
  687. }, 800);
  688. setTimeout(() => {
  689. let explainButtons = document.querySelectorAll(
  690. "#dataListWarp>div.dajx>div.pull-right.clearfix>a.ckjx"
  691. );
  692. for (let i = 0; i < explainButtons.length; i++) {
  693. explainButtons[i].click();
  694. }
  695. }, 1500);
  696. setTimeout(() => {
  697. autoFillAnswer();
  698. }, 2000);
  699. }
  700.  
  701. // 自动填充答案
  702. function autoFillAnswer() {
  703. const answers = document.getElementsByClassName("answerEnd");
  704. for (let i = 0; i < answers.length; i++) {
  705. let ans = answers[i].children[0].innerText.replace("参考答案:", "").replace(/\s+/g, "");
  706. let your = answers[i].children[1].innerText.replace("你的答案:", "").replace(/\s+/g, "");
  707. if (ans != your) {
  708. let ansId = ANSWER_LIST.indexOf(ans);
  709. let yourId = ANSWER_LIST.indexOf(your);
  710. let ansList = answers[i].parentElement.parentElement.querySelectorAll(
  711. ".answerContentList.mgt10"
  712. );
  713. ansList[ansId].style.fontWeight = "bold";
  714. ansList[ansId].style.color = "#51cb65";
  715. ansList[ansId].children[1].style.fontWeight = "bold";
  716. // ansList[ansId].children[0].children[0].checked = true;
  717. if (yourId != undefined) {
  718. ansList[yourId].style.color = "rgba(128, 128, 145,0.7)";
  719. ansList[yourId].style.textDecoration = "line-through";
  720. }
  721. }
  722. }
  723. }
  724.  
  725. // ---------------------------------------------------
  726. // 更新播放页面标题
  727. function updateVideoTitle() {
  728. const config = { attributes: true };
  729. const callback = function (mutationsList, observer) {
  730. for (let mutation of mutationsList) {
  731. if (mutation.type === "attributes" && mutation.attributeName === "class") {
  732. let title = document.querySelector("a.log.pointer.video-player.act").textContent.trim();
  733. document.getElementById(
  734. "videoCoverTop"
  735. ).innerHTML = `${title} &nbsp;&nbsp;&nbsp;&nbsp;剩余课时:${getRemainingClass()}`;
  736. }
  737. }
  738. };
  739. const observer = new MutationObserver(callback);
  740. document.querySelectorAll("a.log.pointer.video-player").forEach(element => {
  741. observer.observe(element, config);
  742. });
  743. }
  744.  
  745. // 获取剩余课时数量
  746. function getRemainingClass() {
  747. let classList = document.querySelectorAll("a.log.pointer.video-player");
  748. let activeClass = document.querySelector("a.log.pointer.video-player.act").textContent.trim();
  749. let index = 0;
  750. let flag = false;
  751. for (let i = 0; i < classList.length; i++) {
  752. const className = classList[i].textContent.trim();
  753. if (flag) {
  754. index++;
  755. }
  756. if (activeClass == className) {
  757. flag = true;
  758. }
  759. }
  760. return index;
  761. }
  762.  
  763. // 添加剩余时间
  764. function addRemainingTime() {
  765. document.querySelector(".pv-time-current").addEventListener(
  766. "DOMSubtreeModified",
  767. function () {
  768. let remainingSeconds = getRemainingSeconds();
  769. let remainingTime = remainingSeconds > 0 ? formatSeconds(remainingSeconds) : "";
  770. let realRemainingSeconds = (
  771. new Number(remainingSeconds) / new Number(getCurrentRate())
  772. ).toFixed(0);
  773. let realRemainingTime = formatSeconds(realRemainingSeconds);
  774. let overTime = getOverTime(realRemainingSeconds);
  775.  
  776. let currentEle = document.querySelector(".pv-time-current");
  777. if (currentEle) {
  778. let parent = currentEle.parentElement;
  779.  
  780. let remainingTimeSpan = document.querySelector(".pv-time-remaining.time-span");
  781. if (!remainingTimeSpan) {
  782. remainingTimeSpan = document.createElement("span");
  783. remainingTimeSpan.setAttribute("class", "pv-time-remaining time-span");
  784. parent.append(remainingTimeSpan);
  785. }
  786. remainingTimeSpan.textContent = "剩余时间:" + remainingTime;
  787.  
  788. let realRemainingTimeSpan = document.querySelector(".pv-time-remaining-real.time-span");
  789. if (!realRemainingTimeSpan) {
  790. realRemainingTimeSpan = document.createElement("span");
  791. realRemainingTimeSpan.setAttribute("class", "pv-time-remaining-real time-span");
  792. parent.append(realRemainingTimeSpan);
  793. }
  794. realRemainingTimeSpan.textContent = "真实剩余时间:" + realRemainingTime;
  795.  
  796. let overTimeSpan = document.querySelector(".pv-time-over.time-span");
  797. if (!overTimeSpan) {
  798. overTimeSpan = document.createElement("span");
  799. overTimeSpan.setAttribute("class", "pv-time-over time-span");
  800. parent.append(overTimeSpan);
  801. }
  802. overTimeSpan.textContent = "结束时间:" + overTime;
  803.  
  804. let nowTimeSpan = document.querySelector(".pv-time-now.time-span");
  805. if (!nowTimeSpan) {
  806. nowTimeSpan = document.createElement("span");
  807. nowTimeSpan.setAttribute("class", "pv-time-now time-span");
  808. document.getElementById("videoCoverBottom").append(nowTimeSpan);
  809. }
  810. nowTimeSpan.textContent = "北京时间:" + dateFormat("HH:MM:SS", new Date());
  811. }
  812. document
  813. .querySelector(".pv-video-wrap")
  814. .nextElementSibling.setAttribute(
  815. "class",
  816. "pv-skin-blue pv-video-bottom pv-subtitle-hide pv-show-fullscreen-page pv-base-control pv-first-h pv-first-hh"
  817. );
  818. },
  819. false
  820. );
  821. }
  822.  
  823. // 获取当前时间
  824. function getNowSeconds() {
  825. let nowSeconds = 0;
  826. let currentEle = document.querySelector(".pv-time-current");
  827. if (!currentEle) {
  828. return nowSeconds;
  829. }
  830.  
  831. let nowTime = currentEle.textContent;
  832. let nowArr = nowTime.split(":");
  833. if (nowArr.length == 2) {
  834. nowSeconds = parseInt(nowArr[0]) * 60 + parseInt(nowArr[1]);
  835. } else if (nowArr.length == 3) {
  836. nowSeconds = parseInt(nowArr[0]) * 60 * 60 + parseInt(nowArr[1]) * 60 + parseInt(nowArr[2]);
  837. }
  838.  
  839. return nowSeconds;
  840. }
  841.  
  842. // 获取总时长
  843. function getAllSeconds() {
  844. let allSeconds = 0;
  845. let durationEle = document.querySelector(".pv-time-duration");
  846. if (!durationEle) {
  847. return allSeconds;
  848. }
  849.  
  850. let allTime = durationEle.textContent;
  851. let allArr = allTime.split(":");
  852. if (allArr.length == 2) {
  853. allSeconds = parseInt(allArr[0]) * 60 + parseInt(allArr[1]);
  854. } else if (allArr.length == 3) {
  855. allSeconds = parseInt(allArr[0] * 60 * 60) + parseInt(allArr[1]) * 60 + parseInt(allArr[2]);
  856. }
  857.  
  858. return allSeconds;
  859. }
  860.  
  861. // 获取剩余时间
  862. function getRemainingSeconds() {
  863. let allSeconds = getAllSeconds();
  864. let nowSeconds = getNowSeconds();
  865. return allSeconds - nowSeconds;
  866. }
  867.  
  868. // 获取结束时间
  869. function getOverTime(seconds) {
  870. let timestamp = new Date().getTime() + seconds * 1000;
  871. return dateFormat("HH:MM:SS", new Date(timestamp));
  872. }
  873.  
  874. // 窗口滚动到底部
  875. function scrollToBottom() {
  876. setTimeout("window.scrollTo(0, document.body.scrollHeight)", 200);
  877. setTimeout("window.scrollTo(0, document.body.scrollHeight)", 600);
  878. }
  879.  
  880. // 元素滚动到底部
  881. function scrollElementToBottom(element) {
  882. element.scrollTop = element.scrollHeight - element.clientHeight;
  883. }
  884.  
  885. // 将秒格式化为时间格式
  886. function formatSeconds(value) {
  887. let result = parseInt(value);
  888. let h =
  889. Math.floor(result / 3600) < 10 ? "0" + Math.floor(result / 3600) : Math.floor(result / 3600);
  890. let m =
  891. Math.floor((result / 60) % 60) < 10
  892. ? "0" + Math.floor((result / 60) % 60)
  893. : Math.floor((result / 60) % 60);
  894. let s = Math.floor(result % 60) < 10 ? "0" + Math.floor(result % 60) : Math.floor(result % 60);
  895.  
  896. let res = "";
  897. if (h !== "00") res += `${h}:`;
  898. if (m !== "00") res += `${m}:`;
  899. res += `${s}`;
  900. return res;
  901. }
  902.  
  903. /**
  904. * Format date.
  905. * @param fmt format standard.
  906. * @param date date.
  907. * @returns {Time formatted string}
  908. */
  909. function dateFormat(fmt, date) {
  910. let ret;
  911. let opt = {
  912. "Y+": date.getFullYear().toString(),
  913. "m+": (date.getMonth() + 1).toString(),
  914. "d+": date.getDate().toString(),
  915. "H+": date.getHours().toString(),
  916. "M+": date.getMinutes().toString(),
  917. "S+": date.getSeconds().toString(),
  918. };
  919. for (let k in opt) {
  920. ret = new RegExp("(" + k + ")").exec(fmt);
  921. if (ret) {
  922. fmt = fmt.replace(
  923. ret[1],
  924. ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0")
  925. );
  926. }
  927. }
  928. return fmt;
  929. }
  930.  
  931. /**
  932. * Log the title and version at the front of the console.
  933. * @param {String} title title.
  934. * @param {String} version script version.
  935. */
  936. function logInfo(title, version) {
  937. console.clear();
  938. const titleStyle = "color:white;background-color:#606060";
  939. const versionStyle = "color:white;background-color:#1475b2";
  940. const logTitle = " " + title + " ";
  941. const logVersion = " " + version + " ";
  942. console.log("%c" + logTitle + "%c" + logVersion, titleStyle, versionStyle);
  943. }
  944. })();