Greasy Fork is available in English.

DMS雨课堂刷课助手

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

// ==UserScript==
// @name         DMS雨课堂刷课助手
// @namespace    http://tampermonkey.net/ //是Tampermonkey脚本头部的元数据之一,用于为脚本定义一个独特的命名空间,以帮助管理和隔离用户脚本,防止冲突。
// @version      3.1.1
// @description  针对雨课堂视频进行自动播放
// @author       Kevin
// @license      GPL3
// @match        *://*.yuketang.cn/*
// @run-at       document-start
// @icon         http://yuketang.cn/favicon.ico
// @grant        unsafeWindow
// ==/UserScript==
// 雨课堂刷课脚本

const basicConf = {
    version: '3.0',
    rate: 2, //用户可改 视频播放速率,可选值[1,1.25,1.5,2,3,16],默认为2倍速,实测4倍速往上有可能出现 bug,3倍速暂时未出现bug,推荐二倍/一倍。
    // pptTime: 3000, // 用户可改 ppt播放时间,单位毫秒
}

const $ = { // 开发脚本的工具对象
    panel: "",      // panel节点,后期赋值
    observer: "",   // 保存observer观察对象
    userInfo: {     // 实时同步刷课记录,避免每次都从头开始检测
        allInfo: {},              // 刷课记录,运行时赋值
        getProgress(classUrl) {   // 参数:classUrl:课程地址
            if (!localStorage.getItem("[雨课堂脚本]刷课进度信息"))   // 第一次初始化这个localStorage
                this.setProgress(classUrl, 0, 0);
            this.allInfo = JSON.parse(localStorage.getItem("[雨课堂脚本]刷课进度信息"));  // 将信息保存到本地
            if (!this.allInfo[classUrl])         // 第一次初始化这个课程
                this.setProgress(classUrl, 0, 0);
            console.log(this.allInfo);
            return this.allInfo[classUrl];   // 返回课程记录对象{outside:外边第几集,inside:里面第几集}
        },
        setProgress(classUrl, outside, inside = 0) {   // 参数:classUrl:课程地址,outside为最外层集数,inside为最内层集数
            this.allInfo[classUrl] = {
                outside,
                inside
            }
            localStorage.setItem("[雨课堂脚本]刷课进度信息", JSON.stringify(this.allInfo));   // localstorage只能保存字符串,需要先格式化为字符串
        },
        removeProgress(classUrl) {   // 移除课程刷课信息,用在课程刷完的情况
            delete this.allInfo[classUrl];
            localStorage.setItem("[雨课堂脚本]刷课进度信息", JSON.stringify(this.allInfo));
        }
    },
    alertMessage(message) { // 向页面中添加信息
        const li = document.createElement("li");
        li.innerText = message;
        $.panel.querySelector('.n_infoAlert').appendChild(li);
    },
    ykt_speed() {    // 视频加速
        const rate = basicConf && basicConf.rate ? basicConf.rate : 2; // 使用逻辑运算符确保rate有备选值
        $.alertMessage('已开启' + rate + '倍速');

        // 使用querySelector替代getElementsByTagName来提升选择器的准确性
        let speedwrap = document.querySelector("xt-speedbutton");
        let speedlist = document.querySelector("xt-speedlist");

        // 检查speedlist是否存在并且有一个firstElementChild存在
        if (speedlist && speedlist.firstElementChild) {
            let speedlistBtn = speedlist.firstElementChild.firstElementChild; // 获取按钮

            // 检查speedlistBtn是否存在
            if (speedlistBtn) {
                // 如果存在,则设置相应的属性和文本
                speedlistBtn.setAttribute('data-speed', rate);
                speedlistBtn.setAttribute('keyt', rate + '.00');
                speedlistBtn.innerText = rate + '.00X';

                // 模拟点击
                let mousemove = new MouseEvent("mousemove", {
                    bubbles: true,
                    cancelable: true,
                    clientX: 10,
                    clientY: 10
                });

                if (speedwrap) {
                    speedwrap.dispatchEvent(mousemove); // 触发mousemove事件
                }

                speedlistBtn.click(); // 触发click事件
            } else {
                console.error('speedlistBtn元素未找到'); // 如果找不到按钮,输出错误消息
            }
        } else {
            // 如果找不到speedlist或firstElementChild不存在,输出错误消息
            console.error('speedlist元素未找到或第一个子元素不存在');
        }
    },
    claim() {
        const volumeIcon = document.querySelector(".xt_video_player_volume .xt_video_player_common_icon");
        if (volumeIcon) {
            volumeIcon.click();
            console.log('已尝试开启静音');
        } else {
            console.error('未找到静音按钮');
        }
    },
    observePause() { // 视频意外暂停,自动播放
        const targetClass = '.xt_video_player_big_play_layer.pause_show';
        const targetElements = document.querySelector(targetClass);
        const muteElement = document.querySelector('.xt_video_player_common_icon.xt_video_player_common_icon_muted');
        // 未静音
        if (!muteElement) {
            // setTimeout(observePause, 100);
            $.claim();
        }
        // 已播放
        if (!targetElements) {
            //   setTimeout(observePause, 100);
            document.querySelector("video").play();
            return;
        }
        document.querySelector("video").play();
    },

    preventScreenCheck() {  // 阻止pro/lms雨课堂切屏检测
        const window = unsafeWindow;
        const blackList = new Set(["visibilitychange", "blur", "pagehide"]); // 限制调用事件名单:1.选项卡的内容变得可见或被隐藏时2.元素失去焦点3.页面隐藏事件
        const isDebug = false;
        const log = console.log.bind(console, "[阻止pro/lms切屏检测]");
        const debug = isDebug ? log : () => { };
        window._addEventListener = window.addEventListener;
        window.addEventListener = (...args) => {                  // args为剩余参数数组
            if (!blackList.has(args[0])) {                          // args[0]为想要定义的事件,如果不在限制名单,调用原生函数
                debug("allow window.addEventListener", ...args);
                return window._addEventListener(...args);
            } else {                                                // 否则不执行,打印参数信息
                log("block window.addEventListener", ...args);
                return undefined;
            }
        };
        document._addEventListener = document.addEventListener;
        document.addEventListener = (...args) => {
            if (!blackList.has(args[0])) {
                debug("allow document.addEventListener", ...args);
                return window._addEventListener(...args);
            } else {
                log("block document.addEventListener", ...args);
                return undefined;
            }
        };
        log("addEventListener hooked!");
        if (isDebug) { // DEBUG ONLY: find out all timers
            window._setInterval = window.setInterval;
            window.setInterval = (...args) => {
                const id = window._setInterval(...args);
                debug("calling window.setInterval", id, ...args);
                return id;
            };
            debug("setInterval hooked!");
            window._setTimeout = window.setTimeout;
            window.setTimeout = (...args) => {
                const id = window._setTimeout(...args);
                debug("calling window.setTimeout", id, ...args);
                return id;
            };
            debug("setTimeout hooked!");
        }
        Object.defineProperties(document, {
            hidden: {                 // 表示页面是(true)否(false)隐藏。
                value: false
            },
            visibilityState: {        // 当前可见元素的上下文环境。由此可以知道当前文档 (即为页面) 是在背后,或是不可见的隐藏的标签页
                value: "visible"        // 此时页面内容至少是部分可见
            },
            hasFocus: {               // 表明当前文档或者当前文档内的节点是否获得了焦点
                value: () => true
            },
            onvisibilitychange: {     // 当其选项卡的内容变得可见或被隐藏时,会在 document 上触发 visibilitychange 事件  ==  visibilitychange
                get: () => undefined,
                set: () => { }
            },
            onblur: {                 // 当元素失去焦点的时候
                get: () => undefined,
                set: () => { }
            }
        });
        log("document properties set!");
        Object.defineProperties(window, {
            onblur: {
                get: () => undefined,
                set: () => { }
            },
            onpagehide: {
                get: () => undefined,
                set: () => { }
            },
        });
        log("window properties set!");
    }
}

function addWindow() {  // 1.添加交互窗口
    const css = `
    ul,
li,
p {
  margin: 0;
  padding: 0;
}

.mini-basic {
  position: fixed;
  top: 10px;
  left: 10px;
  background: #f5f5f5;
  border: 1px solid #000;
  border-radius: 0; /* 默认为方形 */
  transition: border-radius 0.3s ease-in-out;
  height: 50px;
  width: 50px;
  text-align: center;
  line-height: 50px;
  cursor: pointer;
  box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.miniwin {
  display: none; /* 或你需要的显示方式 */
  width: 40px; /* 圆形的宽度 */
  height: 40px; /* 圆形的高度 */
  border-radius: 50%; /* 圆形边框 */
  background-color: #ADD8E6; /* 浅蓝色背景 */
  color: #000000; /* 深黑色文本 */
  transition: width 0.3s, height 0.3s, border-radius 0.3s, transform 0.3s; /* 添加过渡动画 */
}
.n_panel {
  margin: 0;
  padding: 0;
  position: fixed;
  top: 80px;
  left: 20%;
  width: 300px;
  height: 200px;
  background-color: #fff;
  z-index: 99999;
  box-shadow: 6px 4px 17px 2px #000000;
  border-radius: 10px;
  border: 1px solid #a3a3a3;
  font-family: Avenir, Helvetica, Arial, sans-serif;
  color: #636363;
}
.hide {
  display: none;
}
.n_header {
  text-align: center;
  height: 40px;
  background-color: #f7f7f7;
  color: #000;
  font-size: 18px;
  line-height: 40px;
  cursor: move;
  border-radius: 10px 10px 0 0;
  border-bottom: 2px solid #eee;
}
.n_header .tools {
  position: absolute;
  right: 10px;
  top: 5px;
}
.n_header .tools ul li {
  position: relative;
  display: inline-block;
  padding: 2px 6px;
  cursor: pointer;
  font-size: 14px;
  user-select: none;
}

.n_body {
  font-weight: bold;
  font-size: 13px;
  line-height: 26px;
  height: 123px;
  padding: 5px;
  overflow-y: scroll;
  overflow-x: hidden;
}

/* 在这里添加两条重要的CSS规则 */
.n_body::-webkit-scrollbar {
  width: 4px;
}
.n_body::-webkit-scrollbar-thumb {
  background: #ccc;
  border-radius: 2px;
}

.n_footer {
  position: absolute;
  bottom: 0;
  left: 0;
  text-align: right;
  height: 25px;
  width: 100%;
  background-color: #f7f7f7;
  color: #c5c5c5;
  font-size: 13px;
  line-height: 25px;
  border-radius: 0 0 10px 10px;
  border-bottom: 2px solid #eee;
  display: flex;  /* 采用flex布局 */
  justify-content: space-between; /* 元素间距均匀分布 */
  align-items: center; /* 垂直居中对齐 */
  padding: 0 10px; /* 调整内边距 */
  box-sizing: border-box; /* 边框和内填充计入宽度 */
}
.n_footer p {
    margin: 0; /* 移除p标签默认外边距 */
    flex: 1; /* 允许p元素占据多余的空间,从而保证按钮在最右侧 */
    text-align: left; /* 文本左对齐 */
    color: #636363; /* 调整文字颜色以增加可读性 */
    font-size: 13px; /* 保持与.n_body中的文字尺寸一致 */
    flex-grow: 1; /* p标签占据剩余空间 */
}

.n_footer .author {
  color: #636363;
  font-size: 14px;
  font-weight: normal;
}

.n_footer #n_button {
  margin: 0; /* 移除button默认外边距 */
  font-size: 13px; /* 按钮字体尺寸与其他文本保持一致 */
  padding: 6px 12px; /* 调整按钮内边距为适当大小 */
  border-radius: 6px;
  border: 0;
  background-color: #007bff;
  color: #fff;
  cursor: pointer;
}

.n_footer #n_button:hover {
  background-color: #0056b3;
}

/* 这段是新增加的,用于显示作者信息 */
.author-info {
  font-size: 12px;
  color: #555;
  margin-top: 5px;
}
    `;
    const html = `
    <div>
    <style>${css}</style>
    <div class="mini-basic miniwin">
        点击放大
    </div>
    <div class="n_panel">
    <div class="n_header">
      DMS刷课助手
      <div class='tools'>
        <ul>
          <li class='minimality'>➖</li>
          <li class='question'>❓</li>
        </ul>
      </div>
    </div>
    <div class="n_body">
      <ul class="n_infoAlert">
        <li>⭐特性:适用于所有雨课堂版本,支持倍速播放,自动播放功能</li>
        <li>📢使用:在课程目录页点击“开始刷课”即开始自动播放</li>
        <li>⚠️注意:启动脚本后请勿操作刷课窗口,可新开窗口或最小化浏览器</li>
        <li>💡提示:拖动标题栏可移动窗口</li>
        <hr>
      </ul>
    </div>
    <div class="n_footer">
      <p>Kevin</p>
      <button id="n_button">开始刷课</button>
    </div>
    </div>
    </div>
    `;
    // 插入div隐藏dom元素
    const div = document.createElement('div');
    document.body.append(div);
    const shadowroot = div.attachShadow({ mode: 'closed' });
    shadowroot.innerHTML = html;
    $.panel = shadowroot.lastElementChild.lastElementChild; // 保存panel节点
    return $.panel;  // 返回panel根容器
}

function addUserOperate() { // 2.添加交互操作
    const panel = addWindow();
    const header = panel.querySelector(".n_header");
    const button = panel.querySelector("#n_button");
    const clear = panel.querySelector("#n_clear");
    const minimality = panel.querySelector(".minimality");
    const question = panel.querySelector(".question");
    const infoAlert = panel.querySelector(".n_infoAlert");
    const miniWindow = panel.previousElementSibling;
    let mouseMoveHander;
    const mouseDownHandler = function (e) {   // 鼠标在header按下处理逻辑
        e.preventDefault();
        // console.log("鼠标按下/////header");
        let innerLeft = e.offsetX,
            innerTop = e.offsetY;
        mouseMoveHander = function (e) {
            // console.log("鼠标移动////body");
            let left = e.clientX - innerLeft,
                top = e.clientY - innerTop;
            //获取body的页面可视宽高
            var clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
            var clientWidth = document.documentElement.clientWidth || document.body.clientWidth;
            // 通过判断是否溢出屏幕
            if (left <= 0) {
                left = 0;
            } else if (left >= clientWidth - panel.offsetWidth) {
                left = clientWidth - panel.offsetWidth
            }
            if (top <= 0) {
                top = 0
            } else if (top >= clientHeight - panel.offsetHeight) {
                top = clientHeight - panel.offsetHeight
            }
            panel.setAttribute("style", `left:${left}px;top:${top}px`);
        }
        document.body.addEventListener("mousemove", mouseMoveHander);
    }
    header.addEventListener('mousedown', mouseDownHandler);
    header.addEventListener('mouseup', function () {
        // console.log("鼠标松起/////header");
        document.body.removeEventListener("mousemove", mouseMoveHander);
    })
    document.body.addEventListener("mouseleave", function () {
        // console.log("鼠标移出了body页面");
        document.body.removeEventListener("mousemove", mouseMoveHander);
    })
    // 刷课按钮
    button.onclick = function () {
        start();
        button.innerText = '刷课中~';
    }
    // 初始化隐藏最小化按钮
    miniWindow.classList.add("hidden");
    // 最小化按钮
    function minimalityHander(e) {
        if (miniWindow.style.display === 'none') {
            // Show mini window
            miniWindow.style.display = 'inline-block';
            panel.style.opacity = '0';
            panel.style.transform = 'scale(0.5)';
        } else {
            // Hide mini window
            miniWindow.style.display = 'none';
            panel.style.opacity = '1';
            panel.style.transform = 'scale(1)';
        }
    }

    minimality.addEventListener("click", minimalityHander);
    miniWindow.addEventListener("click", minimalityHander);
    // 有问题按钮
    question.onclick = function () {
        alert('作者邮箱:2389765824@qq.com');
    };
    // 鼠标移入窗口,暂停自动滚动
    (function () {
        let scrollTimer;
        scrollTimer = setInterval(function () {
            infoAlert.lastElementChild.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
        }, 500)
        infoAlert.addEventListener('mouseenter', () => {
            clearInterval(scrollTimer);
            // console.log('鼠标进入了打印区');
        })
        infoAlert.addEventListener('mouseleave', () => {
            scrollTimer = setInterval(function () {
                infoAlert.lastElementChild.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
            }, 500)
            // console.log('鼠标离开了打印区');
        })
    })();
}

function start() {  // 脚本入口函数
    const url = location.host;
    const pathName = location.pathname.split('/');
    const matchURL = url + pathName[0] + '/' + pathName[1] + '/' + pathName[2];
    $.alertMessage(`正在为您匹配${matchURL}的处理逻辑...`);
    if (matchURL.includes('yuketang.cn/v2/web')) {
        yuketang_v2();
    } else if (matchURL.includes('yuketang.cn/pro/lms')) {
        yukerang_pro_lms();
    } else {
        $.panel.querySelector("button").innerText = "开始刷课";
        $.alertMessage(`这不是刷课的页面哦,刷课页面的网址应该匹配 */v2/web/* 或 */pro/lms/*`)
        return false;
    }
}

function scrollToBottomIfSectionNotHidden() { // 刷新检查
    // 选择<section>元素
    const section = document.querySelector('section[data-v-721ac683][data-v-3ad17776]');

    // 检查section元素的display属性
    if (section && window.getComputedStyle(section).display !== 'none') {
        // 如果section不是隐藏的(不为'none'),退出函数
        $.alertMessage('刷新完毕!!!')
        return;
    }

    // 如果为'none',执行滚动操作
    const viewContainer = document.querySelector('.viewContainer');
    const elTabPane = document.querySelector('.el-tab-pane');
    if (viewContainer && elTabPane) {
        viewContainer.scrollTop = elTabPane.scrollHeight;
    }
}

function getAnswerArrayFromTitleString(titleString, answersDictionary) {
    // 提取标题中的数字
    const titleMatch = titleString.match(/第(.*?)讲/);
    if (titleMatch && titleMatch[1]) {
        const chineseNumber = titleMatch[1]; // 获取中文数字,例如 "六"

        // 根据中文数字获取答案数组
        const answerArray = answersDictionary[chineseNumber];
        return answerArray;
    } else {
        console.log('未能提取到标题字符串中的数字。');
        return null;
    }
}

function selectAnswerAndSubmit(answersDictionary) {
    const title = document.querySelector('.title').innerText;
    const answerArray = getAnswerArrayFromTitleString(title, answersDictionary);
    console.log(answerArray);
    // 获取.item-type元素中的数字,用来确定问题的序号
    const itemTypeElement = document.querySelector('.item-type');
    if (!itemTypeElement) {
        console.error('未找到.item-type元素。');
        return;
    }

    // 从.item-type元素的文本中提取数字
    const match = itemTypeElement.textContent.match(/\d+/);
    if (!match) {
        console.error('.item-type元素中未包含数字。');
        return;
    }
    const questionNumber = parseInt(match[0], 10) - 1; // 将序号转换为数组索引

    // 根据问题序号选择答案
    const selectedAnswer = answerArray[questionNumber];
    if (!selectedAnswer) {
        console.error('答案数组中不存在对应序号的答案。');
        return;
    }

    // 从页面中查找所有选项的span元素
    const options = document.querySelectorAll('.el-radio__input');
    let optionFound = false;

    // 在所有选项中查找与selectedAnswer匹配的那个,并触发点击事件
    options.forEach(option => {
        const inputElement = option.querySelector('input.el-radio__original');
        if (inputElement && inputElement.value === selectedAnswer && selectedAnswer) {
            optionFound = true;
            option.click(); // 触发点击事件以选中答案
        }
    });

    if (!optionFound) {
        console.error('在页面选项中未找到与选定答案匹配的选项。');
    }
    // 查找并点击提交按钮
    const submitButton = document.querySelector('button.el-button--primary');
    if (submitButton) {
        submitButton.click();
    } else {
        console.error('未找到提交按钮。');
    }
}


function homework(mainCallback) {
    const answersDictionary = {
        '一': ['A', 'A', 'C'],
        '二': ['C', 'C', 'C'],
        '三': ['A', 'A', 'C'],
        '四': ['B', 'A', 'C'],
        '五': ['A', 'B', 'B'],
        '六': ['B', 'A', 'C'],
        '七': ['B', 'C', 'C'],
        '八': ['A', 'A', 'A'],
        '九': ['A', 'C', 'B'],
        '十': ['A', 'B', 'C'],
        '十一': ['C', 'B', 'C'],
        '十二': ['C', 'C', 'C'],
        '十三': ['C', 'A', 'B'],
        '十四': ['A', 'A', 'B'],
        '十五': ['B', 'A', 'C']
    };
    $.alertMessage(`处理作业部分!!!`);
    let count_work = 0;

    // 一定要注意计时器的清楚他们是异步执行的
    let checkInterval; // 保存检查页面元素存在的定时器引用
    let interval;

    function work_main() {
        let filteredSpans = [];
        const searchInterval = setInterval(() => {
            let allSpans = document.querySelectorAll('span[data-v-1c75131d]');
            console.log('Checking spans:', allSpans.length);
            if (allSpans.length > 0) {
                clearInterval(searchInterval); // 清除定时器,退出循环
                allSpans.forEach(span => {
                    const textContent = span.textContent.trim();
                    if (textContent === '未开始' || textContent === '进行中') {
                        const section = span.closest('section');
                        if (section) { // 确保section存在
                            const use = section.querySelector('use'); // 在section中查找use
                            if (use) { // 确保use存在
                                const href = use.getAttribute('xlink:href'); // 获取xlink:href属性值
                                if (href && href.includes('zuoye')) {
                                    filteredSpans.push(section);
                                }
                            }
                        }
                    }
                });
                console.error('Filtered spans length:', filteredSpans.length);
                console.log('Count work:', count_work);
                if (count_work === filteredSpans.length) { // 结束
                    $.alertMessage('作业刷完了');
                    clearInterval(interval);
                    clearInterval(checkInterval);
                    if (mainCallback && typeof mainCallback === 'function') {
                        mainCallback(); // 调用 main 函数作为回调,确保在作业完全完成后执行
                    }
                    return;
                }
                console.log('Count work:', count_work);
                if (filteredSpans[count_work]) {
                    console.log('Clicking filtered span');
                    filteredSpans[count_work].click(); // 进入课程
                }
            }

        }, 100); // 每100毫秒执行一次

        setTimeout(() => {
            console.log('In setTimeout callback');

            let checkCount = 0; // 初始化计数器

            function checkExistence() {
                const titleElement = document.querySelector('.item-type');
                if (titleElement) {
                    clearInterval(checkInterval); // 如果元素存在,清除定时器
                    title = titleElement.innerText;  // 标题文本
                    console.log('Title:', title);

                    interval = setInterval(() => {
                        const submittedButton = document.querySelector('button.is-disabled span');
                        if (submittedButton && submittedButton.responseContent.trim() === '已提交') {
                            console.log('已经提交,停止执行。');
                            count_work++;
                            clearInterval(interval); // 清除定时器
                            history.back();
                            work_main();
                            clearInterval(interval);
                        } else {
                            selectAnswerAndSubmit(answersDictionary);
                        }
                    }, 3000); // 每隔3000毫秒执行一次
                } else {
                    checkCount++; // 计数器递增
                    console.error('No--------.item-type');
                    if (checkCount >= 50) { // 检查次数是否达到50次
                        clearInterval(checkInterval); // 清除定时器
                        console.error('Reached maximum number of checks. Stopping.');
                    }
                }
            }

            checkInterval = setInterval(checkExistence, 100);
        }, 3000);

    }

    work_main();

}

// yuketang.cn/v2/web页面的处理逻辑
function yuketang_v2() {
    let count = 0;
    // const baseUrl = location.href;    // 用于判断不同的课程
    // let count = $.userInfo.getProgress(baseUrl).outside;  // 记录当前课程播放的外层集数
    $.alertMessage(`检测到已经播放到${count}集...`);
    // $.alertMessage('已匹配到yuketang.cn/v2/web,正在处理...');
    // 展开
    let intervalId = setInterval(() => {
        let open = document.querySelector('.content-box')?.querySelector('.sub-info')?.querySelector('.gray')?.querySelector('span');
        if (open) {
            open.click();
            clearInterval(intervalId);
        }
    }, 100); // 每隔1000毫秒检查一次

    // 调用 homework 函数,并将 main 函数作为回调传入
    homework(main);

    // 主函数
    function main() {
        let play = true;        // 用于标记视频是否播放完毕
        let allSpans;
        let filteredSpans = [];
        const searchInterval = setInterval(() => {
            let allSpans = document.querySelectorAll('span[data-v-1c75131d]');
            // console.log(allSpans);
            if (allSpans.length > 0) {
                clearInterval(searchInterval); // 清除定时器,退出循环
                allSpans.forEach(span => {
                    const textContent = span.textContent.trim();
                    if (textContent === '未开始' || textContent === '进行中') {
                        const section = span.closest('section');
                        if (section) { // 确保section存在
                            const use = section.querySelector('use'); // 在section中查找use
                            if (use) { // 确保use存在
                                const href = use.getAttribute('xlink:href'); // 获取xlink:href属性值
                                if (href && href.includes('shipin')) {
                                    filteredSpans.push(section);
                                }
                            }
                        }
                    }
                });
                console.log(filteredSpans);
                if (count === filteredSpans.length && play === true) {            // 结束
                    $.alertMessage('视频刷完了');
                    $.panel.querySelector('#n_button').innerText = '刷完了~';
                    // $.userInfo.removeProgress(baseUrl);
                    return;
                }
                // 遍历这些<span>元素
                // console.log('开始');
                // 寻找每个<span>的最近父级<section>元素
                // const section = span.closest('section');
                play = false;
                console.log(count);
                // 若存在<section>且它包含可以触发点击的方法,则进行点击操作
                if (filteredSpans[count]) {
                    filteredSpans[count].click();// 进入课程
                }
            }
        }, 100); // 每100毫秒执行一次

        setTimeout(() => {
            // var progress;  // 全局变量声明
            let title;
            const checkExistence = () => {
                // const progressElement = document.querySelector('.el-tooltip.item');
                const titleElement = document.querySelector('.title');

                if (titleElement) {
                    clearInterval(checkInterval); // 如果两个元素都存在,清除定时器
                    title = titleElement.innerText;  // 标题文本
                }
                // 否则,循环将继续进行直到找到这些元素
            };

            const checkInterval = setInterval(checkExistence, 100);

            function waitForElementToDisplay(selector, time) {
                if (document.querySelector(selector) != null) {
                    // document.querySelector("video").play();
                    // 执行后续操作
                    // console.log("5555555555555555");
                    $.alertMessage(`正在播放:${title}`);
                    $.ykt_speed();
                    // $.claim(); // 静音
                    setTimeout(() => {
                        $.observePause(); // 观察暂停
                    }, 3000); // 延迟3秒
                } else {
                    setTimeout(function () {
                        waitForElementToDisplay(selector, time);
                    }, time);
                }
            }
            waitForElementToDisplay('.xt_video_player_big_play_layer.pause_show', 100); // 每隔500毫秒检查一次

            let timer1 = setInterval(() => {
                let progress = document.querySelector('.el-tooltip.item');
                $.observePause(); //观察暂停
                if (progress) {
                    $.alertMessage(progress.textContent.trim());
                    if (progress.textContent.trim().includes('100%') || progress.textContent.trim().includes('99%') || progress.textContent.trim().includes('98%') || progress.textContent.trim().includes('已完成')) {
                        play = true; // 确保play变量在适当的作用域内
                        if ($.observer) { // 如果observer存在,则断开连接
                            $.observer.disconnect();
                        }
                        count++;
                        history.back(); // 导航回上一个页面
                        console.error('back');
                        main();
                        clearInterval(timer1);
                    }
                } else {
                    // 如果没有找到进度元素,有可能是页面还没完全加载,可以考虑记录日志或重试逻辑
                    console.error('Progress element not found');
                }
            }, 2000);
        }, 3000);
    }
    // main();
}

// yuketang.cn/pro/lms旧页面的跳转逻辑
function yukerang_pro_lms() {
    localStorage.setItem('n_type', true);
    $.alertMessage('正准备打开新标签页...');
    localStorage.getItem('pro_lms_classCount') ? null : localStorage.setItem('pro_lms_classCount', 1);  // 初始化集数
    let classCount = localStorage.getItem('pro_lms_classCount') - 1;
    document.querySelectorAll('.leaf-detail')[classCount].click();  // 进入第一个课程,启动脚本
}

// yuketang.cn/pro/lms新页面的刷课逻辑
function yukerang_pro_lms_new() {
    $.preventScreenCheck();
    function nextCount(classCount) {
        event1 = new Event('mousemove', { bubbles: true });
        event1.clientX = 9999;
        event1.clientY = 9999;
        if (document.querySelector('.btn-next')) {
            localStorage.setItem('pro_lms_classCount', classCount);
            document.querySelector('.btn-next').dispatchEvent(event1);
            document.querySelector('.btn-next').dispatchEvent(new Event('click'));
            localStorage.setItem('n_type', true);
            main();
        } else {
            localStorage.removeItem('pro_lms_classCount');
            $.alertMessage('课程播放完毕了');
        }
    }
    $.alertMessage('已就绪,开始刷课,请尽量保持页面不动。');
    let classCount = localStorage.getItem('pro_lms_classCount');
    async function main() {
        $.alertMessage(`准备播放第${classCount}集...`);
        await new Promise(function (resolve) {
            setTimeout(function () {
                let className = document.querySelector('.header-bar').firstElementChild.innerText;
                let classType = document.querySelector('.header-bar').firstElementChild.firstElementChild.getAttribute('class');
                let classStatus = document.querySelector('#app > div.app_index-wrapper > div.wrap > div.viewContainer.heightAbsolutely > div > div > div > div > section.title')?.lastElementChild?.innerText;
                if (classType.includes('tuwen') && classStatus != '已读') {
                    $.alertMessage(`正在废寝忘食地看:${className}中...`);
                    setTimeout(() => {
                        resolve();
                    }, 2000)
                } else if (classType.includes('taolun')) {
                    $.alertMessage(`只是看看,目前没有自动发表讨论功能,欢迎反馈...`);
                    setTimeout(() => {
                        resolve();
                    }, 2000)
                } else if (classType.includes('shipin') && !classStatus.includes('100%')) {
                    $.alertMessage(`正在播放:${className}`);
                    setTimeout(() => {
                        // 监测视频播放状态
                        let timer = setInterval(() => {
                            let classStatus = document.querySelector('#app > div.app_index-wrapper > div.wrap > div.viewContainer.heightAbsolutely > div > div > div > div > section.title')?.lastElementChild?.innerText;
                            if (classStatus.includes('100%') || classStatus.includes('99%') || classStatus.includes('98%') || classStatus.includes('已完成')) {
                                $.alertMessage(`${className}播放完毕...`);
                                clearInterval(timer);
                                if (!!$.observer) {  // 防止新的视频已经播放完了,还未来得及赋值observer的问题
                                    $.observer.disconnect();  // 停止监听
                                }
                                resolve();
                            }
                        }, 200)
                        // 根据video是否加载出来判断加速时机
                        let nowTime = Date.now();
                        let videoTimer = setInterval(() => {
                            let video = document.querySelector('video');
                            if (video) {
                                setTimeout(() => {  // 防止视频刚加载出来,就加速,出现无法获取到元素地bug
                                    $.ykt_speed();
                                    $.claim();
                                    $.observePause();
                                    clearInterval(videoTimer);
                                }, 2000)
                            } else if (!video && Date.now() - nowTime > 20000) {  // 如果20s内仍未加载出video
                                localStorage.setItem('n_type', true);
                                location.reload();
                            }
                        }, 5000)
                    }, 2000)
                } else if (classType.includes('zuoye')) {
                    $.alertMessage(`进入:${className},目前没有自动作答功能,敬请期待...`);
                    setTimeout(() => {
                        resolve();
                    }, 2000)
                } else {
                    $.alertMessage(`您已经看过${className}...`);
                    setTimeout(() => {
                        resolve();
                    }, 2000)
                }
            }, 2000);
        })
        $.alertMessage(`第${classCount}集播放完了...`);
        classCount++;
        nextCount(classCount);
    }
    main();
};

// 油猴执行文件
// 自执行的匿名函数(也被称为立即执行函数表达式
(function () {
    // **'use strict';**:这行代码启用了严格模式。在严格模式下,
    // JavaScript 会对一些不安全的行为抛出错误,比如给未声明的变量赋值等。
    'use strict';
    // setInterval: 这是一个定时器函数,每隔100毫秒(0.1秒)执行一次里面的箭头函数。
    // 这个箭头函数的目的是检查document.body是否存在,如果存在,说明DOM已经加载完成,可以对DOM进行操作了。
    const listenDom = setInterval(() => {
        if (document.body) {
            addUserOperate();
            /*
                localStorage获取和设置:
                localStorage.getItem('n_type')用来获取名为'n_type'的本地存储项的值。
                如果其值为'true',将执行进一步的操作。
            */
            if (localStorage.getItem('n_type') === 'true') {
                //修改了id为'n_button'的DOM元素的文本内容为'刷课中~'
                $.panel.querySelector('#n_button').innerText = '刷课中~';
                // 将'n_type'的值设置为'false',可能是为了标记某个动作已经开始或结束。
                localStorage.setItem('n_type', false);
                yukerang_pro_lms_new();
            }
            // 页面都加载出来了就不连续监听了
            clearInterval(listenDom);
        }
    }, 100)
})();