Greasy Fork

Greasy Fork is available in English.

DMS雨课堂刷课助手

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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('作者邮箱:[email protected]');
    };
    // 鼠标移入窗口,暂停自动滚动
    (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)
})();