雨课堂刷课助手

针对雨课堂视频进行自动播放

  1. // ==UserScript==
  2. // @name 雨课堂刷课助手
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.4.14
  5. // @description 针对雨课堂视频进行自动播放
  6. // @author 风之子
  7. // @license GPL3
  8. // @match *://*.yuketang.cn/*
  9. // @match *://*.gdufemooc.cn/*
  10. // @run-at document-start
  11. // @icon http://yuketang.cn/favicon.ico
  12. // @grant unsafeWindow
  13. // ==/UserScript==
  14. // 雨课堂刷课脚本
  15. /*
  16. 已适配雨课堂学校及网址:
  17. 学校:中原工学院,河南大学研究院,辽宁大学,河北大学,中南大学,电子科技大学,华北电力大学,上海理工大学研究生院及其他院校...
  18. 网址:changjiang.yuketang.cn,yuketang.cn ...
  19. */
  20.  
  21. const basicConf = {
  22. version: '2.4.14',
  23. rate: 2, //用户可改 视频播放速率,可选值[1,1.25,1.5,2,3,16],默认为2倍速,实测4倍速往上有可能出现 bug,3倍速暂时未出现bug,推荐二倍/一倍。
  24. pptTime: 3000, // 用户可改 ppt播放时间,单位毫秒
  25. }
  26.  
  27. const $ = { // 开发脚本的工具对象
  28. panel: "", // panel节点,后期赋值
  29. observer: "", // 保存observer观察对象
  30. userInfo: { // 实时同步刷课记录,避免每次都从头开始检测
  31. allInfo: {}, // 刷课记录,运行时赋值
  32. getProgress(classUrl) { // 参数:classUrl:课程地址
  33. if (!localStorage.getItem("[雨课堂脚本]刷课进度信息")) // 第一次初始化这个localStorage
  34. this.setProgress(classUrl, 0, 0);
  35. this.allInfo = JSON.parse(localStorage.getItem("[雨课堂脚本]刷课进度信息")); // 将信息保存到本地
  36. if (!this.allInfo[classUrl]) // 第一次初始化这个课程
  37. this.setProgress(classUrl, 0, 0);
  38. console.log(this.allInfo);
  39. return this.allInfo[classUrl]; // 返回课程记录对象{outside:外边第几集,inside:里面第几集}
  40. },
  41. setProgress(classUrl, outside, inside = 0) { // 参数:classUrl:课程地址,outside为最外层集数,inside为最内层集数
  42. this.allInfo[classUrl] = {
  43. outside,
  44. inside
  45. }
  46. localStorage.setItem("[雨课堂脚本]刷课进度信息", JSON.stringify(this.allInfo)); // localstorage只能保存字符串,需要先格式化为字符串
  47. },
  48. removeProgress(classUrl) { // 移除课程刷课信息,用在课程刷完的情况
  49. delete this.allInfo[classUrl];
  50. localStorage.setItem("[雨课堂脚本]刷课进度信息", JSON.stringify(this.allInfo));
  51. }
  52. },
  53. alertMessage(message) { // 向页面中添加信息
  54. const li = document.createElement("li");
  55. li.innerText = message;
  56. $.panel.querySelector('.n_infoAlert').appendChild(li);
  57. },
  58. ykt_speed() { // 视频加速
  59. const rate = basicConf.rate || 2;
  60. let speedwrap = document.getElementsByTagName("xt-speedbutton")[0];
  61. let speedlist = document.getElementsByTagName("xt-speedlist")[0];
  62. let speedlistBtn = speedlist.firstElementChild.firstElementChild;
  63.  
  64. speedlistBtn.setAttribute('data-speed', rate);
  65. speedlistBtn.setAttribute('keyt', rate + '.00');
  66. speedlistBtn.innerText = rate + '.00X';
  67. $.alertMessage('已开启' + rate + '倍速');
  68.  
  69. // 模拟点击
  70. let mousemove = document.createEvent("MouseEvent");
  71. mousemove.initMouseEvent("mousemove", true, true, unsafeWindow, 0, 10, 10, 10, 10, 0, 0, 0, 0, 0, null);
  72. speedwrap.dispatchEvent(mousemove);
  73. speedlistBtn.click();
  74. },
  75. claim() { // 视频静音
  76. document.querySelector("#video-box > div > xt-wrap > xt-controls > xt-inner > xt-volumebutton > xt-icon").click();
  77. $.alertMessage('已开启静音');
  78. },
  79. videoDetail() { // 不用鼠标模拟操作就能实现的一般视频加速静音方法
  80. document.querySelector('video').play();
  81. document.querySelector('video').volume = 0;
  82. document.querySelector('video').playbackRate = basicConf.rate;
  83. $.alertMessage(`实际上已默认静音和${basicConf.rate}倍速`);
  84. },
  85. audioDetail() { // 音频处理
  86. document.querySelector('audio').play();
  87. document.querySelector('audio').volume = 0;
  88. document.querySelector('audio').playbackRate = basicConf.rate;
  89. $.alertMessage(`实际上已默认静音和${basicConf.rate}倍速`);
  90. },
  91. observePause() { // 视频意外暂停,自动播放 duck123ducker贡献
  92. var targetElement = document.getElementsByClassName('play-btn-tip')[0]; // 要监听的dom元素
  93. if (document.getElementsByClassName('play-btn-tip').length === 0) { // 还未加载出来视频dom时,开启轮回扫描
  94. setTimeout(observePause, 100);
  95. } else {
  96. $.observer = new MutationObserver(function (mutationsList) {
  97. for (var mutation of mutationsList) {
  98. if (mutation.type === 'childList' && mutation.target === targetElement && targetElement.innerText === '播放') { // 被监视的元素状态
  99. console.log('视频意外暂停了,已恢复播放');
  100. document.getElementsByTagName('video')[0].play();
  101. $.alertMessage('视频意外暂停了,已恢复播放');
  102. }
  103. }
  104. });
  105. var config = { childList: true };
  106. $.observer.observe(targetElement, config);
  107. document.querySelector("video").play(); //防止进入下一章时由于鼠标离开窗口而在视频开始时就暂停导致永远无法触发监听器
  108. }
  109. },
  110. preventScreenCheck() { // 阻止pro/lms雨课堂切屏检测 PRO-2684贡献
  111. const window = unsafeWindow;
  112. const blackList = new Set(["visibilitychange", "blur", "pagehide"]); // 限制调用事件名单:1.选项卡的内容变得可见或被隐藏时2.元素失去焦点3.页面隐藏事件
  113. const isDebug = false;
  114. const log = console.log.bind(console, "[阻止pro/lms切屏检测]");
  115. const debug = isDebug ? log : () => { };
  116. window._addEventListener = window.addEventListener;
  117. window.addEventListener = (...args) => { // args为剩余参数数组
  118. if (!blackList.has(args[0])) { // args[0]为想要定义的事件,如果不在限制名单,调用原生函数
  119. debug("allow window.addEventListener", ...args);
  120. return window._addEventListener(...args);
  121. } else { // 否则不执行,打印参数信息
  122. log("block window.addEventListener", ...args);
  123. return undefined;
  124. }
  125. };
  126. document._addEventListener = document.addEventListener;
  127. document.addEventListener = (...args) => {
  128. if (!blackList.has(args[0])) {
  129. debug("allow document.addEventListener", ...args);
  130. return window._addEventListener(...args);
  131. } else {
  132. log("block document.addEventListener", ...args);
  133. return undefined;
  134. }
  135. };
  136. log("addEventListener hooked!");
  137. if (isDebug) { // DEBUG ONLY: find out all timers
  138. window._setInterval = window.setInterval;
  139. window.setInterval = (...args) => {
  140. const id = window._setInterval(...args);
  141. debug("calling window.setInterval", id, ...args);
  142. return id;
  143. };
  144. debug("setInterval hooked!");
  145. window._setTimeout = window.setTimeout;
  146. window.setTimeout = (...args) => {
  147. const id = window._setTimeout(...args);
  148. debug("calling window.setTimeout", id, ...args);
  149. return id;
  150. };
  151. debug("setTimeout hooked!");
  152. }
  153. Object.defineProperties(document, {
  154. hidden: { // 表示页面是(true)否(false)隐藏。
  155. value: false
  156. },
  157. visibilityState: { // 当前可见元素的上下文环境。由此可以知道当前文档 (即为页面) 是在背后,或是不可见的隐藏的标签页
  158. value: "visible" // 此时页面内容至少是部分可见
  159. },
  160. hasFocus: { // 表明当前文档或者当前文档内的节点是否获得了焦点
  161. value: () => true
  162. },
  163. onvisibilitychange: { // 当其选项卡的内容变得可见或被隐藏时,会在 document 上触发 visibilitychange 事件 == visibilitychange
  164. get: () => undefined,
  165. set: () => { }
  166. },
  167. onblur: { // 当元素失去焦点的时候
  168. get: () => undefined,
  169. set: () => { }
  170. }
  171. });
  172. log("document properties set!");
  173. Object.defineProperties(window, {
  174. onblur: {
  175. get: () => undefined,
  176. set: () => { }
  177. },
  178. onpagehide: {
  179. get: () => undefined,
  180. set: () => { }
  181. },
  182. });
  183. log("window properties set!");
  184. }
  185. }
  186.  
  187. function addWindow() { // 1.添加交互窗口
  188. const css = `
  189. ul,
  190. li,
  191. p {
  192. margin: 0;
  193. padding: 0;
  194. }
  195. .mini-basic{
  196. position: fixed;
  197. top: 0;
  198. left: 0;
  199. background:#f5f5f5;
  200. border:1px solid #000;
  201. height:50px;
  202. width:50px;
  203. border-radius:6px;
  204. text-align:center;
  205. line-height:50px;
  206. }
  207. .miniwin{
  208. z-index:-9999;
  209. }
  210.  
  211. .n_panel {
  212. margin: 0;
  213. padding: 0;
  214. position: fixed;
  215. top: 0;
  216. left: 0;
  217. width: 500px;
  218. height: 250px;
  219. background-color: #fff;
  220. z-index: 99999;
  221. box-shadow: 6px 4px 17px 2px #000000;
  222. border-radius: 10px;
  223. border: 1px solid #a3a3a3;
  224. font-family: Avenir, Helvetica, Arial, sans-serif;
  225. color: #636363;
  226. }
  227. .hide{
  228. display:none;
  229. }
  230.  
  231. .n_header {
  232. text-align: center;
  233. height: 40px;
  234. background-color: #f7f7f7;
  235. color: #000;
  236. font-size: 18px;
  237. line-height: 40px;
  238. cursor: move;
  239. border-radius: 10px 10px 0 0;
  240. border-bottom: 2px solid #eee;
  241. }
  242.  
  243. .n_header .tools{
  244. position:absolute;
  245. right:0;
  246. top:0;
  247. }
  248.  
  249. .n_header .tools ul li{
  250. position:relative;
  251. display:inline-block;
  252. padding:0 5px;
  253. cursor:pointer;
  254. }
  255.  
  256. .n_header .minimality::after{
  257. content:'最小化';
  258. display:none;
  259. position:absolute;
  260. left:0;
  261. bottom:-30px;
  262. height:32px;
  263. width:50px;
  264. font-size:12px;
  265. background:#ffffe1;
  266. color:#000;
  267. border-radius:3px;
  268. }
  269.  
  270. .n_header .minimality:hover::after{
  271. display:block;
  272. }
  273. .n_header .question::after{
  274. content:'有问题';
  275. display:none;
  276. position:absolute;
  277. left:0;
  278. bottom:-30px;
  279. height:32px;
  280. width:50px;
  281. font-size:12px;
  282. background:#ffffe1;
  283. color:#000;
  284. border-radius:3px;
  285. }
  286.  
  287. .n_header .question:hover::after{
  288. display:block;
  289. }
  290.  
  291. .n_body {
  292. font-weight: bold;
  293. font-size: 13px;
  294. line-height: 26px;
  295. height: 183px;
  296. }
  297.  
  298. .n_body .n_infoAlert {
  299. overflow-y: scroll;
  300. height: 100%;
  301. }
  302.  
  303. /* 滚动条整体 */
  304. .n_body .n_infoAlert::-webkit-scrollbar {
  305. height: 20px;
  306. width: 7px;
  307. }
  308.  
  309. /* 滚动条轨道 */
  310. .n_body .n_infoAlert::-webkit-scrollbar-track {
  311. --webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
  312. border-radius: 10px;
  313. background: #ffffff;
  314. }
  315.  
  316. /* 滚动条滑块 */
  317. .n_body .n_infoAlert::-webkit-scrollbar-thumb {
  318. border-radius: 10px;
  319. --webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
  320. background: rgb(20, 19, 19, 0.6);
  321. }
  322.  
  323. .n_footer {
  324. position: absolute;
  325. bottom: 0;
  326. left: 0;
  327. text-align: right;
  328. height: 25px;
  329. width: 100%;
  330. background-color: #f7f7f7;
  331. color: #c5c5c5;
  332. font-size: 13px;
  333. line-height: 25px;
  334. border-radius: 0 0 10px 10px;
  335. border-bottom: 2px solid #eee;
  336. display: flex;
  337. justify-content: space-between;
  338. }
  339.  
  340. .n_footer #n_button {
  341. border-radius: 6px;
  342. border: 0;
  343. background-color: blue;
  344. color: #fff;
  345. cursor: pointer;
  346. }
  347.  
  348. .n_footer #n_button:hover {
  349. background-color: yellow;
  350. color: #000;
  351. }
  352.  
  353. .n_footer #n_clear{
  354. border-radius: 6px;
  355. border: 0;
  356. cursor: pointer;
  357. }
  358.  
  359. .n_footer #n_clear::after{
  360. content:'用于清除课程进度缓存';
  361. display:none;
  362. position:absolute;
  363. left:250px;
  364. bottom:-30px;
  365. height:32px;
  366. width:100px;
  367. font-size:12px;
  368. background:#ffffe1;
  369. color:#000;
  370. border-radius:3px;
  371. }
  372.  
  373. .n_footer #n_clear:hover::after{
  374. display:block;
  375. }
  376.  
  377. .n_footer #n_zanshang {
  378. cursor: pointer;
  379. position: relative;
  380. color: red;
  381. }
  382.  
  383. .n_footer #n_zanshang img {
  384. position: absolute;
  385. top: 30px;
  386. left: -130px;
  387. display: none;
  388. width: 300px;
  389. }
  390.  
  391. .n_footer #n_zanshang:hover img {
  392. display: block;
  393. }
  394. `;
  395. const html = `
  396. <div>
  397. <style>${css}</style>
  398. <div class="mini-basic miniwin">
  399. 放大
  400. </div>
  401. <div class="n_panel">
  402. <div class="n_header">
  403. 雨课堂刷课助手
  404. <div class='tools'>
  405. <ul>
  406. <li class='minimality'>_</li>
  407. <li class='question'>?</li>
  408. </ul>
  409. </div>
  410. </div>
  411. <div class="n_body">
  412. <ul class="n_infoAlert">
  413. <li>⭐ 脚本支持:雨课堂所有版本,支持多倍速,自动播放</li>
  414. <li>📢 使用方法:点击进入要刷的课程目录,点击开始刷课按钮即可自动运行</li>
  415. <li>⚠️ 运行后请不要随意点击刷课窗口,可新开窗口,可最小化浏览器</li>
  416. <li>💡 拖动上方标题栏可以进行拖拽哦!</li>
  417. <hr>
  418. </ul>
  419. </div>
  420. <div class="n_footer">
  421. <p>雨课堂助手 ${basicConf.version} </p>
  422. <button id="n_clear">清除进度缓存</button>
  423. <button id="n_button">开始刷课</button>
  424. </div>
  425. </div>
  426. </div>
  427. `;
  428. // 插入div隐藏dom元素
  429. const div = document.createElement('div');
  430. document.body.append(div);
  431. const shadowroot = div.attachShadow({ mode: 'closed' });
  432. shadowroot.innerHTML = html;
  433. console.log("已插入使用面板");
  434. $.panel = shadowroot.lastElementChild.lastElementChild; // 保存panel节点
  435. return $.panel; // 返回panel根容器
  436. }
  437.  
  438. function addUserOperate() { // 2.添加交互操作
  439. const panel = addWindow();
  440. const header = panel.querySelector(".n_header");
  441. const button = panel.querySelector("#n_button");
  442. const clear = panel.querySelector("#n_clear");
  443. const minimality = panel.querySelector(".minimality");
  444. const question = panel.querySelector(".question");
  445. const infoAlert = panel.querySelector(".n_infoAlert");
  446. const miniWindow = panel.previousElementSibling;
  447. let mouseMoveHander;
  448. const mouseDownHandler = function (e) { // 鼠标在header按下处理逻辑
  449. e.preventDefault();
  450. // console.log("鼠标按下/////header");
  451. let innerLeft = e.offsetX,
  452. innerTop = e.offsetY;
  453. mouseMoveHander = function (e) {
  454. // console.log("鼠标移动////body");
  455. let left = e.clientX - innerLeft,
  456. top = e.clientY - innerTop;
  457. //获取body的页面可视宽高
  458. var clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
  459. var clientWidth = document.documentElement.clientWidth || document.body.clientWidth;
  460. // 通过判断是否溢出屏幕
  461. if (left <= 0) {
  462. left = 0;
  463. } else if (left >= clientWidth - panel.offsetWidth) {
  464. left = clientWidth - panel.offsetWidth
  465. }
  466. if (top <= 0) {
  467. top = 0
  468. } else if (top >= clientHeight - panel.offsetHeight) {
  469. top = clientHeight - panel.offsetHeight
  470. }
  471. panel.setAttribute("style", `left:${left}px;top:${top}px`);
  472. }
  473. document.body.addEventListener("mousemove", mouseMoveHander);
  474. }
  475. header.addEventListener('mousedown', mouseDownHandler);
  476. header.addEventListener('mouseup', function () {
  477. // console.log("鼠标松起/////header");
  478. document.body.removeEventListener("mousemove", mouseMoveHander);
  479. })
  480. document.body.addEventListener("mouseleave", function () {
  481. // console.log("鼠标移出了body页面");
  482. document.body.removeEventListener("mousemove", mouseMoveHander);
  483. })
  484. // 刷课按钮
  485. button.onclick = function () {
  486. start();
  487. button.innerText = '刷课中~';
  488. }
  489. // 清除数据按钮
  490. clear.onclick = function () {
  491. $.userInfo.removeProgress(location.href);
  492. localStorage.removeItem('pro_lms_classCount');
  493. }
  494. // 最小化按钮
  495. function minimalityHander(e) {
  496. if (miniWindow.className.includes("miniwin")) {
  497. console.log("点击了缩小");
  498. let leftPx = e.clientX - e.offsetX + 'px', topPx = e.clientY - e.offsetY + 'px';
  499. panel.setAttribute("style", `z-index:-9999;`);
  500. miniWindow.setAttribute("style", `z-index:9999;top:${topPx};left:${leftPx}`);
  501. } else {
  502. let leftPx = e.clientX - 450 + 'px', topPx = e.clientY - e.offsetY + 'px';
  503. console.log("点击了放大");
  504. panel.setAttribute("style", `z-index:9999;top:${topPx};left:${leftPx}`);
  505. miniWindow.setAttribute("style", `z-index:-9999;`);
  506. }
  507. miniWindow.classList.toggle("miniwin");
  508. }
  509. minimality.addEventListener("click", minimalityHander);
  510. miniWindow.addEventListener("click", minimalityHander);
  511. // 有问题按钮
  512. question.onclick = function () {
  513. alert('作者网站:niuwh.cn' + ' ' + '作者博客:blog.niuwh.cn');
  514. };
  515. // 鼠标移入窗口,暂停自动滚动
  516. (function () {
  517. let scrollTimer;
  518. scrollTimer = setInterval(function () {
  519. infoAlert.lastElementChild.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
  520. }, 500)
  521. infoAlert.addEventListener('mouseenter', () => {
  522. clearInterval(scrollTimer);
  523. // console.log('鼠标进入了打印区');
  524. })
  525. infoAlert.addEventListener('mouseleave', () => {
  526. scrollTimer = setInterval(function () {
  527. infoAlert.lastElementChild.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
  528. }, 500)
  529. // console.log('鼠标离开了打印区');
  530. })
  531. })();
  532. }
  533.  
  534. function start() { // 脚本入口函数
  535. const url = location.host;
  536. const pathName = location.pathname.split('/');
  537. const matchURL = url + pathName[0] + '/' + pathName[1] + '/' + pathName[2];
  538. $.alertMessage(`正在为您匹配${matchURL}的处理逻辑...`);
  539. if (matchURL.includes('yuketang.cn/v2/web') || matchURL.includes('gdufemooc.cn/v2/web')) {
  540. yuketang_v2();
  541. } else if (matchURL.includes('yuketang.cn/pro/lms') || matchURL.includes('gdufemooc.cn/pro/lms')) {
  542. yuketang_pro_lms();
  543. } else {
  544. $.panel.querySelector("button").innerText = "开始刷课";
  545. $.alertMessage(`这不是刷课的页面哦,刷课页面的网址应该匹配 */v2/web/* 或 */pro/lms/*`)
  546. return false;
  547. }
  548. }
  549.  
  550. // yuketang.cn/v2/web页面的处理逻辑
  551. function yuketang_v2() {
  552. const baseUrl = location.href; // 用于判断不同的课程
  553. let count = $.userInfo.getProgress(baseUrl).outside; // 记录当前课程播放的外层集数
  554. let play = true; // 用于标记视频是否播放完毕
  555. $.alertMessage(`检测到已经播放到${count}集...`);
  556. $.alertMessage('已匹配到yuketang.cn/v2/web,正在处理...');
  557. // 主函数
  558. function main() {
  559. autoSlide(count).then(() => {
  560. let list = document.querySelector('.logs-list').childNodes; // 保存当前课程的所有外层集数
  561. const course = list[count]?.querySelector('.content-box')?.querySelector('section'); // 保存当前课程dom结构
  562. let classInfo = course.querySelector('.tag')?.querySelector('use')?.getAttribute('xlink:href') || 'piliang'; // 2023.11.23 雨课堂更新,去掉了批量字样,所有如果不存在就默认为批量课程
  563. $.alertMessage('刷课状态:第' + (count + 1) + '个/' + list.length + '个');
  564. if (count === list.length && play === true) { // 结束
  565. $.alertMessage('课程刷完了');
  566. $.panel.querySelector('#n_button').innerText = '刷完了~';
  567. $.userInfo.removeProgress(baseUrl);
  568. return;
  569. } else if (classInfo?.includes('shipin') && play === true) { // 视频处理
  570. play = false;
  571. course.click(); // 进入课程
  572. setTimeout(() => {
  573. let progress = document.querySelector('.progress-wrap').querySelector('.text'); // 课程进度
  574. let deadline = false; // 课程是否到了截止日期
  575. const title = document.querySelector(".title").innerText; // 课程标题
  576. $.alertMessage(`正在播放:${title}`);
  577. if (document.querySelector('.box').innerText.includes('已过考核截止时间')) {
  578. deadline = true;
  579. $.alertMessage(`${title}已经过了截至日期,进度不再增加,将跳过~`);
  580. }
  581. $.ykt_speed();
  582. $.claim();
  583. $.observePause();
  584. let timer1 = setInterval(() => {
  585. // console.log(progress);
  586. if (progress.innerHTML.includes('100%') || progress.innerHTML.includes('99%') || progress.innerHTML.includes('98%') || progress.innerHTML.includes('已完成') || deadline) {
  587. count++;
  588. $.userInfo.setProgress(baseUrl, count);
  589. play = true;
  590. if (!!$.observer) { // 防止oberver为undefined(网速卡导致视频没加载出来,observer为空)
  591. $.observer.disconnect(); // 视频播放完了,停止监听
  592. }
  593. history.back();
  594. main();
  595. clearInterval(timer1);
  596. }
  597. }, 10000);
  598. }, 3000)
  599. // 批量处理
  600. } else if (classInfo?.includes('piliang') && play === true) { // 批量处理
  601. let zhankai = course.querySelector('.sub-info').querySelector('.gray').querySelector('span');
  602. sync();
  603. async function sync() {
  604. await zhankai.click();
  605. setTimeout(() => {
  606. // 保存所有视频
  607. let a = list[count].querySelector('.leaf_list__wrap').querySelectorAll('.activity__wrap');
  608. let count1 = $.userInfo.allInfo[baseUrl].inside; // 保存内部集数
  609. $.alertMessage('第' + (count + 1) + '个:进入了批量区');
  610. bofang();
  611. function bofang() {
  612. let play = true;
  613. let classInfo1;
  614. let videotitle, audiotitle;
  615. if (count1 === a.length && play === true) {
  616. $.alertMessage('合集播放完毕');
  617. count++;
  618. $.userInfo.setProgress(baseUrl, count);
  619. main();
  620. }
  621. console.log(a[count1]?.querySelector('.tag').innerText);
  622. if (a[count1]?.querySelector('.tag').innerText === '音频') {
  623. classInfo1 = "音频";
  624. audiotitle = a[count1]?.querySelector("h2").innerText;
  625. } else { // 不是音频
  626. classInfo1 = a[count1]?.querySelector('.tag').querySelector('use').getAttribute('xlink:href');
  627. videotitle = a[count1].querySelector("h2").innerText;
  628. console.log(classInfo1);
  629. }
  630. if (classInfo1 == "音频" && play === true) {
  631. play = false;
  632. a[count1].click();
  633. $.alertMessage(`开始播放:${audiotitle}`);
  634. setTimeout(() => {
  635. $.audioDetail();
  636. }, 3000);
  637. let timer = setInterval(() => {
  638. let progress = document.querySelector('.progress-wrap').querySelector('.text');
  639. if (document.querySelector('audio').paused) {
  640. document.querySelector('audio').play();
  641. }
  642. if (progress.innerHTML.includes('100%') || progress.innerHTML.includes('99%') || progress.innerHTML.includes('98%') || progress.innerHTML.includes('已完成')) {
  643. count1++;
  644. $.userInfo.setProgress(baseUrl, count, count1);
  645. clearInterval(timer);
  646. $.alertMessage(`${audiotitle}播放完毕`);
  647. history.back();
  648. setTimeout(() => {
  649. bofang();
  650. }, 2000);
  651. }
  652. }, 3000)
  653. } else if (classInfo1?.includes('shipin') && play === true) {
  654. play = false;
  655. a[count1].click();
  656. $.alertMessage(`开始播放:${videotitle}`);
  657. // 延迟3秒后加速
  658. setTimeout(() => {
  659. $.ykt_speed();
  660. $.claim();
  661. $.observePause();
  662. }, 3000);
  663. let timer = setInterval(() => {
  664. let progress = document.querySelector('.progress-wrap').querySelector('.text');
  665. if (progress.innerHTML.includes('100%') || progress.innerHTML.includes('99%') || progress.innerHTML.includes('98%') || progress.innerHTML.includes('已完成')) {
  666. count1++;
  667. $.userInfo.setProgress(baseUrl, count, count1);
  668. clearInterval(timer);
  669. $.alertMessage(`${videotitle}播放完毕`);
  670. if (!!$.observer) { // 防止oberver为undefined.
  671. $.observer.disconnect(); // 视频播放完了,停止监听
  672. }
  673. history.back();
  674. setTimeout(() => {
  675. bofang();
  676. }, 2000);
  677. }
  678. }, 3000)
  679. } else if (classInfo1 && !classInfo1.includes('shipin') && play === true) {
  680. $.alertMessage('不是视频');
  681. count1++;
  682. $.userInfo.setProgress(baseUrl, count, count1);
  683. bofang();
  684. }
  685. }
  686. }, 2000)
  687. }
  688. } else if (classInfo?.includes('ketang') && play === true) { // 课堂处理
  689. $.alertMessage('第' + (count + 1) + '个:进入了课堂区');
  690. play = false;
  691. course.click();
  692. setTimeout(() => {
  693. let playBack = document.querySelector('.playback');
  694. if (playBack) { // 存在回放按钮时进入详情页
  695. playBack.click();
  696. setTimeout(() => {
  697. // 内容为视频的逻辑
  698. if (document.querySelector('video')) {
  699. $.videoDetail();
  700. function isComplate() {
  701. let videoTime = document.querySelector('.video__time').innerHTML.toString();
  702. let currentTime = videoTime.split('/')[0];
  703. let totalTime = videoTime.split('/')[1];
  704. if (currentTime == totalTime || currentTime == '00:00' || currentTime == '00:00:00') {
  705. count++;
  706. $.userInfo.setProgress(baseUrl, count);
  707. play = true;
  708. history.go(-2);
  709. main();
  710. clearInterval(timer);
  711. }
  712. }
  713. let timer = setInterval(() => {
  714. isComplate();
  715. }, 10000)
  716. }
  717. // 内容为音频的逻辑
  718. if (document.querySelector('audio')) {
  719. $.audioDetail();
  720. function isComplate() {
  721. let mainArea = document.querySelector('.mainArea');
  722. let currentTime = mainArea.querySelectorAll('span')[0].innerHTML.toString();
  723. let totalTime = mainArea.querySelectorAll('span')[1].innerHTML.toString();
  724. if (currentTime == totalTime || currentTime == '00:00' || currentTime == '00:00:00') {
  725. count++;
  726. $.userInfo.setProgress(baseUrl, count);
  727. play = true;
  728. history.go(-2);
  729. main();
  730. clearInterval(timer);
  731. }
  732. }
  733. let timer = setInterval(() => {
  734. isComplate();
  735. }, 10000)
  736. }
  737. }, 3000)
  738. } else { // 不存在回放按钮时退出
  739. count++;
  740. $.userInfo.setProgress(baseUrl, count);
  741. play = true;
  742. history.go(-1);
  743. main();
  744. }
  745. }, 3000)
  746. } else if (classInfo?.includes('kejian') && play === true) { // 课件处理
  747. const tableDate = course.parentNode.parentNode.parentNode.__vue__.tableData;
  748. console.log(tableDate.deadline, tableDate.end);
  749. if ((tableDate.deadline || tableDate.end) ? (tableDate.deadline < Date.now() || tableDate.end < Date.now()) : false) { // 没有该属性默认没有结课
  750. $.alertMessage('第' + (count + 1) + '个:' + course.childNodes[0].childNodes[2].childNodes[0].innerText + '课件结课了,已跳过');
  751. count++;
  752. $.userInfo.setProgress(baseUrl, count);
  753. main();
  754. } else {
  755. // $.alertMessage('根据ycj用户的反馈修改新增课件处理,且赞助支持,表示感谢') // 8.8元
  756. $.alertMessage('第' + (count + 1) + '个:进入了课件区');
  757. play = false;
  758. console.log();
  759. course.click();
  760. let classType;
  761. (async function () {
  762. await new Promise(function (resolve) {
  763. setTimeout(function () {
  764. classType = document.querySelector('.el-card__header').innerText;
  765. console.log(classType);
  766. document.querySelector('.check').click();
  767. resolve();
  768. }, 3000)
  769. }) // 3秒后执行点击事件
  770. let className = document.querySelector('.dialog-header').firstElementChild.innerText;
  771. console.log(className);
  772. if (classType == '课件PPT') { // 课件为ppt
  773. let allPPT = document.querySelector('.swiper-wrapper').children;
  774. let pptTime = basicConf.pptTime || 3000;
  775. $.alertMessage(`开始播放${className}`)
  776. for (let i = 0; i < allPPT.length; i++) {
  777. await new Promise(function (resolve) {
  778. setTimeout(function () {
  779. allPPT[i].click();
  780. $.alertMessage(`${className}:第${i + 1}个ppt已经播放`);
  781. resolve();
  782. }, pptTime)
  783. })
  784. }
  785. await new Promise(function (resolve) { // 稍微等待
  786. setTimeout(function () {
  787. resolve();
  788. }, pptTime) // 最后一张ppt等待时间
  789. })
  790. if (document.querySelector('.video-box')) { // 回头检测如果ppt里面有视频
  791. let pptVideo = document.querySelectorAll('.video-box');
  792. $.alertMessage('检测到ppt里面有视频,将继续播放视频');
  793. for (let i = 0; i < pptVideo.length; i++) {
  794. if (document.querySelectorAll('.video-box')[i].innerText != '已完成') { // 判断视频是否已播放
  795. pptVideo[i].click();
  796. $.alertMessage(`开始播放:${className}里面的第${i + 1}个视频`)
  797. await new Promise(function (resolve) {
  798. setTimeout(function () {
  799. $.ykt_speed(); // 加速
  800. document.querySelector('.xt_video_player_common_icon').click(); // 静音
  801. $.observePause(); // 防止切屏自动暂停
  802. resolve();
  803. }, 3000)
  804. })
  805. await new Promise(function (resolve) {
  806. let timer = setInterval(function () {
  807. let allTime = document.querySelector('.xt_video_player_current_time_display').innerText;
  808. nowTime = allTime.split(' / ')[0];
  809. totalTime = allTime.split(' / ')[1]
  810. console.log(nowTime + totalTime);
  811. if (nowTime == totalTime) {
  812. clearInterval(timer);
  813. if (!!$.observer) { // 防止新的视频已经播放完了,还未来得及赋值observer的问题
  814. $.observer.disconnect(); // 停止监听
  815. }
  816. resolve();
  817. }
  818. }, 200);
  819. }) // 等待视频结束
  820. } else { // 视频已完成
  821. $.alertMessage(`检测到${className}里面的第${i + 1}个视频已经播放完毕`);
  822. }
  823. }
  824. }
  825. $.alertMessage(`${className} 已经播放完毕`)
  826. } else { // 课件为视频
  827. document.querySelector('.video-box').click();
  828. $.alertMessage(`开始播放视频:${className}`);
  829. await new Promise(function (resolve) {
  830. setTimeout(function () {
  831. $.ykt_speed();
  832. document.querySelector('.xt_video_player_common_icon').click();
  833. resolve();
  834. }, 3000)
  835. }) // 3秒后加速,静音
  836. await new Promise(function (resolve) {
  837. let timer = setInterval(function () {
  838. let allTime = document.querySelector('.xt_video_player_current_time_display').innerText;
  839. let nowTime = allTime.split(' / ')[0];
  840. let totalTime = allTime.split(' / ')[1]
  841. console.log(nowTime + totalTime);
  842. if (nowTime == totalTime) {
  843. clearInterval(timer);
  844. resolve();
  845. }
  846. }, 200);
  847. }) // 等待视频结束
  848. $.alertMessage(`${className} 视频播放完毕`)
  849. }
  850. count++;
  851. $.userInfo.setProgress(baseUrl, count);
  852. play = true;
  853. history.back();
  854. main();
  855. })()
  856. }
  857. } else if (!(classInfo.includes('shipin') || classInfo.includes('piliang') || classInfo.includes('kejian')) && play === true) { // 视频,批量,课件都不是的时候跳过,此处可以优化
  858. $.alertMessage('第' + (count + 1) + '个:不是视频,已跳过');
  859. count++;
  860. $.userInfo.setProgress(baseUrl, count);
  861. main();
  862. }
  863. })
  864. }
  865. // 根据视频集数,自动下拉刷新集数
  866. async function autoSlide(count) {
  867. let frequency = parseInt((count + 1) / 20) + 1;
  868. for (let i = 0; i < frequency; i++) {
  869. await new Promise((resolve, reject) => {
  870. setTimeout(() => {
  871. document.querySelector('.viewContainer').scrollTop = document.querySelector('.el-tab-pane').scrollHeight;
  872. resolve();
  873. }, 1000)
  874. })
  875. }
  876. }
  877. main();
  878. }
  879.  
  880. // yuketang.cn/pro/lms旧页面的跳转逻辑
  881. function yuketang_pro_lms() {
  882. localStorage.setItem('n_type', true);
  883. $.alertMessage('正准备打开新标签页...');
  884. localStorage.getItem('pro_lms_classCount') ? null : localStorage.setItem('pro_lms_classCount', 1); // 初始化集数
  885. let classCount = localStorage.getItem('pro_lms_classCount') - 1;
  886. let leafDetail = document.querySelectorAll('.leaf-detail'); // 课程列表
  887. while (!leafDetail[classCount].firstChild.querySelector('i').className.includes('shipin')) {
  888. classCount++;
  889. localStorage.setItem('pro_lms_classCount', classCount);
  890. $.alertMessage('课程不属于视频,已跳过^_^');
  891. };
  892. document.querySelectorAll('.leaf-detail')[classCount].click(); // 进入第一个【视频】课程,启动脚本
  893. }
  894.  
  895. // yuketang.cn/pro/lms新页面的刷课逻辑
  896. function yuketang_pro_lms_new() {
  897. $.preventScreenCheck();
  898. function nextCount(classCount) {
  899. event1 = new Event('mousemove', { bubbles: true });
  900. event1.clientX = 9999;
  901. event1.clientY = 9999;
  902. if (document.querySelector('.btn-next')) {
  903. localStorage.setItem('pro_lms_classCount', classCount);
  904. document.querySelector('.btn-next').dispatchEvent(event1);
  905. document.querySelector('.btn-next').dispatchEvent(new Event('click'));
  906. localStorage.setItem('n_type', true);
  907. main();
  908. } else {
  909. localStorage.removeItem('pro_lms_classCount');
  910. $.alertMessage('课程播放完毕了');
  911. }
  912. }
  913. $.alertMessage('已就绪,开始刷课,请尽量保持页面不动。');
  914. let classCount = localStorage.getItem('pro_lms_classCount');
  915. async function main() {
  916. $.alertMessage(`准备播放第${classCount}集...`);
  917. await new Promise(function (resolve) {
  918. setTimeout(function () {
  919. let className = document.querySelector('.header-bar').firstElementChild.innerText;
  920. let classType = document.querySelector('.header-bar').firstElementChild.firstElementChild.getAttribute('class');
  921. let classStatus = document.querySelector('#app > div.app_index-wrapper > div.wrap > div.viewContainer.heightAbsolutely > div > div > div > div > section.title')?.lastElementChild?.innerText;
  922. if (classType.includes('tuwen') && classStatus != '已读') {
  923. $.alertMessage(`正在废寝忘食地看:${className}中...`);
  924. setTimeout(() => {
  925. resolve();
  926. }, 2000)
  927. } else if (classType.includes('taolun')) {
  928. $.alertMessage(`只是看看,目前没有自动发表讨论功能,欢迎反馈...`);
  929. setTimeout(() => {
  930. resolve();
  931. }, 2000)
  932. } else if (classType.includes('shipin') && !classStatus.includes('100%')) {
  933. $.alertMessage(`7s后开始播放:${className}`);
  934. setTimeout(() => {
  935. // 监测视频播放状态
  936. let timer = setInterval(() => {
  937. let classStatus = document.querySelector('#app > div.app_index-wrapper > div.wrap > div.viewContainer.heightAbsolutely > div > div > div > div > section.title')?.lastElementChild?.innerText;
  938. if (classStatus.includes('100%') || classStatus.includes('99%') || classStatus.includes('98%') || classStatus.includes('已完成')) {
  939. $.alertMessage(`${className}播放完毕...`);
  940. clearInterval(timer);
  941. if (!!$.observer) { // 防止新的视频已经播放完了,还未来得及赋值observer的问题
  942. $.observer.disconnect(); // 停止监听
  943. }
  944. resolve();
  945. }
  946. }, 200)
  947. // 根据video是否加载出来判断加速时机
  948. let nowTime = Date.now();
  949. let videoTimer = setInterval(() => {
  950. let video = document.querySelector('video');
  951. if (video) {
  952. setTimeout(() => { // 防止视频刚加载出来,就加速,出现无法获取到元素地bug
  953. $.ykt_speed();
  954. $.claim();
  955. $.observePause();
  956. clearInterval(videoTimer);
  957. }, 2000)
  958. } else if (!video && Date.now() - nowTime > 20000) { // 如果20s内仍未加载出video
  959. localStorage.setItem('n_type', true);
  960. location.reload();
  961. }
  962. }, 5000)
  963. }, 2000)
  964. } else if (classType.includes('zuoye')) {
  965. $.alertMessage(`进入:${className},目前没有自动作答功能,敬请期待...`);
  966. setTimeout(() => {
  967. resolve();
  968. }, 2000)
  969. } else if (classType.includes('kaoshi')) {
  970. $.alertMessage(`进入:${className},目前没有自动考试功能,敬请期待...`);
  971. setTimeout(() => {
  972. resolve();
  973. }, 2000)
  974. } else if (classType.includes('ketang')) {
  975. $.alertMessage(`进入:${className},目前没有课堂作答功能,敬请期待...`);
  976. setTimeout(() => {
  977. resolve();
  978. }, 2000)
  979. } else {
  980. $.alertMessage(`您已经看过${className}...`);
  981. setTimeout(() => {
  982. resolve();
  983. }, 2000)
  984. }
  985. }, 2000);
  986. })
  987. $.alertMessage(`第${classCount}集播放完了...`);
  988. classCount++;
  989. nextCount(classCount);
  990. }
  991. main();
  992. };
  993.  
  994. // 油猴执行文件
  995. (function () {
  996. 'use strict';
  997. // window.addEventListener('load', (event) => { // 用于检测页面是否已经完全正常加载出来
  998. // console.log(('页面成功加载出来了'));
  999. // })
  1000. const listenDom = setInterval(() => {
  1001. if (document.body) {
  1002. addUserOperate();
  1003. if (localStorage.getItem('n_type') === 'true') {
  1004. $.panel.querySelector('#n_button').innerText = '刷课中~';
  1005. localStorage.setItem('n_type', false);
  1006. yuketang_pro_lms_new();
  1007. }
  1008. clearInterval(listenDom);
  1009. }
  1010. }, 100)
  1011. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址