2025年9月22日星期一

雨课堂任务脚本

1.购买服务器阿里云:服务器购买地址https://t.aliyun.com/U/DT4XYh若失效,可用地址

1.购买服务器

阿里云:

服务器购买地址

https://t.aliyun.com/U/DT4XYh

若失效,可用地址

https://www.aliyun.com/activity/wuying/dj?source=5176.29345612&userCode=49hts92d

腾讯云:

https://curl.qcloud.com/wJpWmSfU

若失效,可用地址

https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=ad201ee2ef3b771157f72ee5464b1fea&from=console

华为云

https://activity.huaweicloud.com/cps.html?fromacct=64b5cf7cc11b4840bb4ed2ea0b2f4468&utm_source=V1g3MDY4NTY=&utm_medium=cps&utm_campaign=201905

2.部署教程

2024年最新青龙面板跑脚本教程(一)持续更新中

3.代码如下

let console_config={    maxLogs: 1000,  // 最大日志数量    autoScroll: true,   // 自动滚动到最新日志    showTimestamp: true,    // 显示时间戳    videoPlayRate: 2.0    // 视频播放速率}let my_console;
/** * 获取课程视频的观看信息 * @param targetUrl 发送请求的地址 */function hijackXMLHTTPRequest(targetUrl) {    const originalOpen = XMLHttpRequest.prototype.open;    const originalSend = XMLHttpRequest.prototype.send;    // 重写open方法,用于检测特定的请求URL    XMLHttpRequest.prototype.open = function (method, url, async, user, password) {        // 先调用原始方法        originalOpen.apply(this, arguments);        // 检查URL是否匹配要拦截的目标        if (url && url.includes(targetUrl)) {            // 监听readystatechange事件以获取响应            this.addEventListener('readystatechange', function () {                if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {                    try {                        // 服务器返回的数据在this.responseText中,通常是JSON                        const responseData = JSON.parse(this.responseText);                        //  将数据存储到全局变量供页面其他脚本使用                        window._interceptedVideoLogData = responseData;                    } catch (e) {                        console.error('解析响应数据时出错:', e);                    }                }            });        }    };
}hijackXMLHTTPRequest('/video-log/detail/');/** * 劫持雨课堂心跳发送api,利用Web Worker代替雨课堂原有js的定时器精准控制心跳发送,从而绕过浏览器针对后台页面的节流限制 */function hijackHeartbeat() {    // 劫持 setInterval    const _originalSetInterval = unsafeWindow.setInterval;    unsafeWindow.setInterval = function(callback, interval, ...args) {        //根据 interval参数来过滤心跳的定时器,心跳的定时器为5000ms        if (interval === 5000) {            console.log("[定时器监听] 发现 setInterval: ", interval, "ms""回调函数: ", callback);            unsafeWindow.heartBeat = callback;            unsafeWindow.heartContext=this;            unsafeWindow.heartBeatArgs=args;            return;        }        return _originalSetInterval.call(this, callback, interval, ...args);    };    //在主线程中创建Web Worker    const heartbeatBlob = new Blob([`     const interval = 5000// 10秒间隔     setInterval(function() {         postMessage({ type: 'tick'});     }, interval); `], { type: 'application/javascript' });    const heartbeatWorker = new Worker(URL.createObjectURL(heartbeatBlob));    heartbeatWorker.onmessage = function(e) {        if (e.data.type === 'tick') {            // 收到Worker的定时信号,触发心跳发送逻辑            if (unsafeWindow.heartBeat && unsafeWindow.heartContext&&my_console) {                unsafeWindow.heartBeat.apply(unsafeWindow.heartContext,unsafeWindow.heartBeatArgs);            }        }    };}//启动心跳劫持hijackHeartbeat();/** * css样式注入 */GM_addStyle(`  #console-container {    position: fixed;    top: 20px; right: 20px;    width: 450px;    background: rgba(0,0,0,0.85);    color: #fff;    border: 1px solid #444;    font-family: monospace;    z-index: 99999;    display: block  }  .console-header {    padding: 8px;    background: #333;    cursor: move;  }  .console-switch {    float: right;    background: transparent;    border: none;    color: white;    cursor: pointer;  }  .console-body {    padding: 8px;    height: 300px;    overflow: auto;  }  .log-entry {    margin-bottom: 4px;  };/** * 防止雨课堂切屏检测 */function preventScreenCheck() {    const blackList = new Set(["visibilitychange""blur""focus""pagehide""pageshow"]); // 需要拦截的事件类型
    // 保存原生方法    const originalAddEventListener = window.EventTarget.prototype.addEventListener;    const originalRemoveEventListener = window.EventTarget.prototype.removeEventListener;
    // 劫持 EventTarget.prototype.addEventListener    window.EventTarget.prototype.addEventListener = function(type, listener, options) {        if (blackList.has(type)) {            return undefined; // 直接返回undefined,阻止监听器被添加        }        return originalAddEventListener.call(this, type, listener, options);    };
    // 劫持 EventTarget.prototype.removeEventListener    window.EventTarget.prototype.removeEventListener = function(type, listener, options) {        if (blackList.has(type)) {            return undefined;        }        return originalRemoveEventListener.call(this, type, listener, options);    };
    // 伪造关键属性:让检测代码读取到"永远处于焦点和前台"的状态    Object.defineProperties(document, {        'hidden': {            get: () => false,            configurable: false,            enumerable: true        },        'visibilityState': {            get: () => 'visible',            configurable: false,            enumerable: true        },        'hasFocus': {            value: () => true,            configurable: false,            writable: false        },        // 拦截对onvisibilitychange等的赋值        'onvisibilitychange': {            get: () => undefined,            set: () => {},            configurable: false,            enumerable: true        },        'onblur': {            get: () => undefined,            set: () => {},            configurable: false,            enumerable: true        },        'onfocus': {            get: () => undefined,            set: () => {},            configurable: false,            enumerable: true        },        'onpagehide': {            get: () => undefined,            set: () => {},            configurable: false,            enumerable: true        },        'onpageshow': {            get: () => undefined,            set: () => {},            configurable: false,            enumerable: true        }    });
    // 伪造 window 对象的相关属性    Object.defineProperties(window, {        'onblur': {            get: () => undefined,            set: () => {},            configurable: false,            enumerable: true        },        'onfocus': {            get: () => undefined,            set: () => {},            configurable: false,            enumerable: true        },        'onpagehide': {            get: () => undefined,            set: () => {},            configurable: false,            enumerable: true        },        'onpageshow': {            get: () => undefined,            set: () => {},            configurable: false,            enumerable: true        }    });}/** * 鼠标滑动模拟器,模拟真人鼠标滑动 */class MouseSliderSimulator {    constructor(options = {}) {        // 合并配置参数        this.config = {            interval: options.interval || 3000,          // 滑动间隔(毫秒)            moveSteps: options.moveSteps || 8,          // 每次滑动的步数            maxOffset: options.maxOffset || 12,         // 每次滑动的最大偏移量(像素)            container: options.container || document, // 目标容器选择器            autoStart: options.autoStart || false        // 是否自动启动        };
        this.containerElement = null;        this.moveInterval = null;        this.lastX = 0;        this.lastY = 0;        // 用户活动检测        this.isUserActive = false;        // 用户活动检测定时器        this.userActivityTimer = null;        // 修复: 提前绑定方法,确保this指向实例        this._handleUserActivity = this._handleUserActivity.bind(this);
        // 初始化        this.init();
        // 如果配置为自动启动,则开始滑动        if (this.config.autoStart) {            this.start();        }    }
    // 初始化方法    init() {        this.containerElement = this.config.container;        if (!this.containerElement) {            console.error(`目标对象 "${this.config.container}" 为空!`);            return false;        }
        // 获取容器边界并设置初始位置        const containerRect = this.containerElement.getBoundingClientRect();        this.lastX = containerRect.left + containerRect.width / 2;        this.lastY = containerRect.top + containerRect.height / 2;
        // 立即移动鼠标到初始位置        this.triggerMouseMove(this.lastX, this.lastY);        this._setupUserActivityMonitoring();        return true;    }
    // 启动滑动模拟    start() {        if (!this.containerElement) {            if (!this.init()) {                return false;            }        }
        // 清除现有的定时器(防止重复启动)        this.stop();
        this.moveInterval = setInterval(() => {            this.slideMouse();        }, this.config.interval);
        return true;    }
    // 执行单次滑动    slideMouse() {        const containerRect = this.containerElement.getBoundingClientRect();
        // 生成带随机偏移的目标位置        let targetX = this.lastX + (Math.random() * 2 - 1) * this.config.maxOffset;        let targetY = this.lastY + (Math.random() * 2 - 1) * this.config.maxOffset;
        // 确保目标位置在容器边界内        targetX = Math.max(containerRect.left, Math.min(containerRect.left + containerRect.width, targetX));        targetY = Math.max(containerRect.top, Math.min(containerRect.top + containerRect.height, targetY));
        // 贝塞尔曲线控制点(增加随机轨迹)        const controlX = this.lastX + (Math.random() - 0.5) * this.config.maxOffset * 2;        const controlY = this.lastY + (Math.random() - 0.5) * this.config.maxOffset * 2;
        // 分步滑动(沿贝塞尔曲线路径)        for (let i = 0; i <= this.config.moveSteps; i++) {            setTimeout(() => {                const t = i / this.config.moveSteps;                // 二次贝塞尔曲线计算                const stepX = Math.round((1-t)**2 * this.lastX + 2*(1-t)*t*controlX + t**2*targetX);                const stepY = Math.round((1-t)**2 * this.lastY + 2*(1-t)*t*controlY + t**2*targetY);
                this.triggerMouseMove(stepX, stepY);
                // 更新最后位置                if (i === this.config.moveSteps) {                    this.lastX = stepX;                    this.lastY = stepY;                }            }, i * 50); // 步间延迟        }    }
    // 触发鼠标移动事件    triggerMouseMove(x, y) {        const event = new MouseEvent('mousemove', {            clientX: x,            clientY: y,            bubbles: true,        });        document.dispatchEvent(event);    }
    // 停止滑动    stop() {        if (this.moveInterval) {            clearInterval(this.moveInterval);            this.moveInterval = null;        }    }
    // 更新配置    updateConfig(newConfig) {        Object.assign(this.config, newConfig);
        // 如果容器选择器有变化,重新初始化        if (newConfig.container && newConfig.container !== this.config.container) {            this.init();        }
        return this;    }
    // 销毁实例,清理资源    destroy() {        this.stop();        // 移除事件监听器        document.removeEventListener('mousemove'this._handleUserActivity);        document.removeEventListener('keydown'this._handleUserActivity);        document.removeEventListener('click'this._handleUserActivity);        // 清除用户活动定时器        if (this.userActivityTimer) {            clearTimeout(this.userActivityTimer);        }        this.containerElement = null;    }

    // 监听真人操作事件    _setupUserActivityMonitoring() {        document.addEventListener('mousemove'this._handleUserActivity);        document.addEventListener('keydown'this._handleUserActivity);        document.addEventListener('click'this._handleUserActivity);        // 可根据需要添加其他事件监听    }
    // 事件处理函数    _handleUserActivity(event) {        // 检查事件是否由用户真实操作触发        if (event.isTrusted) {            // 立即停止鼠标模拟            this.stop();            // 标记用户为活跃状态            this.isUserActive = true;            // 清除现有的定时器(如果存在)            if (this.userActivityTimer) {                clearTimeout(this.userActivityTimer);            }
            // 设置一个新的定时器,3秒后认为用户不再活跃            this.userActivityTimer = setTimeout(() => {                this.isUserActive = false;                this.start();            }, 3000); // 3秒无操作后重置        }    }}/** * 控制台类的实现 */class Console {    constructor(options = {}) {        // 默认配置        this.config = {            maxLogs: 1000,// 最大日志数量            autoScroll: true,// 自动滚动到最新日志            showTimestamp: true,// 显示时间戳            videoPlayRate: 2.0,// 视频播放速率            ...options// 用户自定义配置覆盖默认配置        };
        // 创建控制台容器        this.container = document.createElement('div');        this.container.id = 'console-container';
        // 创建标题栏        this.header = document.createElement('div');        this.header.className = 'console-header';        this.header.innerHTML = `                    <span class="console-title">Console</span>                    <button class="console-switch">收起</button>                `;
        // 创建日志区域        this.logContainer = document.createElement('div');        this.logContainer.className = 'console-body';
        // 组装元素        this.container.appendChild(this.header);        this.container.appendChild(this.logContainer);        document.body.appendChild(this.container);
        //给头部的console-close按钮添加点击隐藏事件        this.header.querySelector('.console-switch').addEventListener('click', () => this.switch());        // 给控制台容器添加拖拽功能        this._setupDrag(this.header);
        // 日志存储        this.logs = [];    }
    // 核心日志方法    log(...args) {        this._addEntry('log''INFO''#d4d4d4', ...args);    }
    warn(...args) {        this._addEntry('warn''WARN''#d7ba7d', ...args);    }
    error(...args) {        this._addEntry('error''ERROR''#f44747', ...args);    }
    clear() {        this.logContainer.innerHTML = '';        this.logs = [];    }
    show() {        this.logContainer.style.height = '300px';        this.header.querySelector('.console-switch').innerText = "收起"    }
    hide() {        this.logContainer.style.height = '0px';        this.header.querySelector('.console-switch').innerText = "展开"    }
    switch() {        if (this.logContainer.style.height === "0px") {            this.show()        } else {            this.hide()        }    }
    // 内部实现    _addEntry(type, label, color, ...messages) {        // 创建日志条目        const logEntry = document.createElement('div');        logEntry.className = `log-entry log-${type}`;
        // 如果设置中显示时间戳,则添加时间戳元素        if (this.config.showTimestamp) {            const timeSpan = document.createElement('span');            timeSpan.className = 'log-time';            timeSpan.textContent = new Date().toLocaleTimeString();            logEntry.appendChild(timeSpan);        }
        // 添加日志标签        const labelSpan = document.createElement('span');        labelSpan.textContent = `[${label}] `;        labelSpan.style.color = color;        logEntry.appendChild(labelSpan);
        // 处理消息内容        messages.forEach(msg => {            const contentSpan = document.createElement('span');
            if (msg.nodeType===1){                contentSpan.appendChild(msg);            } else if(typeof msg === 'object'){                contentSpan.textContent = JSON.stringify(msg, null2);            } else {                contentSpan.textContent = msg;            }            contentSpan.style.color = color;            logEntry.appendChild(contentSpan);            logEntry.appendChild(document.createTextNode(' '));        });
        // 添加到容器        this.logContainer.appendChild(logEntry);        this.logs.push(logEntry);
        // 自动滚动        if (this.config.autoScroll) {            this.logContainer.scrollTop = this.logContainer.scrollHeight;        }
        // 日志数量限制        if (this.logs.length > this.config.maxLogs) {            this.logs.shift().remove();        }    }
    // 拖拽功能实现    _setupDrag(header) {        let isDragging = false;        let offsetX, offsetY;        // 鼠标按下事件,开始拖拽        header.addEventListener('mousedown', (e) => {            isDragging = true;            offsetX = e.clientX - this.container.getBoundingClientRect().left;            offsetY = e.clientY - this.container.getBoundingClientRect().top;            this.container.style.cursor = 'grabbing';        });
        document.addEventListener('mousemove', (e) => {            if (!isDragging) return;
            this.container.style.left = (e.clientX - offsetX) + 'px';            this.container.style.top = (e.clientY - offsetY) + 'px';            this.container.style.right = 'unset';            this.container.style.bottom = 'unset';        });
        document.addEventListener('mouseup', () => {            isDragging = false;            this.container.style.cursor = 'default';        });    }}/** * 模拟人类点击事件,欺骗某些对事件检测严格的元素 * @param element */const simulateHumanClick = (element) => {    const rect = element.getBoundingClientRect();    const events = [        {type: 'mousemove', x: rect.left-10, y: rect.top-10},        {type: 'mousemove', x: rect.left+5, y: rect.top+5},        {type: 'mousedown'},        {type: 'mouseup'},        {type: 'click'}    ];
    events.forEach(e => {        const event = new MouseEvent(e.type, {            bubbles: true,            clientX: e.x || rect.left + rect.width/2,            clientY: e.y || rect.top + rect.height/2        });        element.dispatchEvent(event);    });};/** * dom元素查找的可靠实现 * @param selector 选择器字符串 * @param targetContainer 可选,默认为document。指定在哪个容器内查找元素 * @param timeout 可选,默认为500ms。dom树变动的最长等待时间 * @param baseDelay 可选,默认为10ms。每次重试的延迟时间,指数增长 * @param maxRetries 可选,默认为5。最大重试次数 * @returns {Promise<unknown>} Promise对象,解析为找到的元素或错误信息 */function waitForElement(selector, targetContainer = document, timeout = 1000, baseDelay = 10, maxRetries = 10) {    return new Promise((resolve, reject) => {        let retryCount = 0;
        function attempt() {            // 1. 立即检查            const element = targetContainer.querySelector(selector);            if (element) {                return resolve(element);            }
            // 2. 设置单次观察的超时            let timeoutId = setTimeout(() => {                observer.disconnect();                onTimeout();            }, timeout);
            // 3. 启动Observer监听            const observer = new MutationObserver((mutations) => {            });
            observer.observe(targetContainer, { childList: true, subtree: true });
            // 4. 处理单次观察超时的函数            function onTimeout() {                retryCount++;                if (retryCount <= maxRetries) {                    // 等待指数退避时间后,进行下一次attempt                    setTimeout(attempt, baseDelay * Math.pow(2, retryCount - 1));                } else {                    reject(new Error(`查找元素${selector}失败,重试次数已达上限,请检查网络连接。`));                }            }        }
        // 开始第一次尝试        attempt();    });}/** * 页面为选择课程主页时执行的逻辑 */function selectLessonPageLogic(){    my_console.log("当前页面为课程选择页面,正在查找所有的课程...");    let app = document.getElementById("app");    waitForElement(".grid",app).then(element=>{        setTimeout(()=>{            let lessonsName=element.querySelectorAll("h1");            my_console.log("查找结果:");            let names=[];            lessonsName.forEach(node=>{                names.push(node.textContent.trim());            })            my_console.log(names);            my_console.warn("请选择对应的课程进行学习,脚本会自动开始刷课");        },1000);    }).catch(err=>{        my_console.error(err);    });}/** * 课程中选择学习内容才处理逻辑 */function selectLessonItemPageLogic(){    my_console.log("当前页面为课程页面,正在寻找第一个未完成的课程...");    let app = document.getElementById("app");    waitForElement(".main-box",app).then((element)=>{        setTimeout(()=>{            let states= element.querySelectorAll(".chapter-list>.content>.el-tooltip>.progress-time>.progress-wrap>.item");            let i;            for(i =0;i<states.length;i++){                if(states[i].textContent!=="已完成"){                    my_console.log("已找到,开始学习...");                    states[i].click();                    break;                }            }            if(i===states.length){                my_console.log("全部课程已经学完!");            }        },1000);    }).catch(err=>{        my_console.error(err);    });}/** * 自动播放视频 */function autoPlayVideo(){    GM_addStyle(        `.el-dialog__wrapper{        display:none !important;    }`    )    /**     * 对视频进行区间播放,跳过已经播放过的视频     */    function intervalPlay(){        let watchedInterval=window._interceptedVideoLogData.data.heartbeat.result;        my_console.log("已观看区间:"+JSON.stringify(watchedInterval));        let currentInterval=0;        let end=currentInterval<watchedInterval.length?watchedInterval[currentInterval]['s']:videoElement.duration;        videoElement.currentTime=0;        //在主线程中创建Web Worker        const videoFlushBlob = new Blob([`         const interval = 10000// 10秒间隔         setInterval(function() {            self.postMessage({ type: 'check'});         }, interval); `      ], { type: 'application/javascript' });        videoElement.play();        const videoFlushWorker = new Worker(URL.createObjectURL(videoFlushBlob));        videoFlushWorker.postMessage({type:'launch'});        videoFlushWorker.onmessage = function(e) {            if (e.data.type === 'check') {                my_console.log("当前视频时间:"+videoElement.currentTime);                // 收到Worker的定时信号                if(videoElement.currentTime<=videoElement.duration&&videoElement.currentTime>=end){                    my_console.log("跳过已经观看过的区间:"+watchedInterval[currentInterval]['s']+"-"+watchedInterval[currentInterval]['e']);                    videoElement.currentTime=watchedInterval[currentInterval]['e'];                    currentInterval++;                    end=currentInterval<watchedInterval.length?watchedInterval[currentInterval]['s']:videoElement.duration;                }            }        };    }    my_console.log("当前页面为视频播放页面,正在自动播放视频...");    let videoElement;    //获取雨课堂的最高层静态div    let app=document.getElementById('app');    let callback=()=>{        //如果该视频已经是完成状态了,则直接跳转下一个        waitForElement(".title-fr>.progress-wrap>.item>.text",app).then(finish=>{            if(finish.innerText==="完成度:100%"){                my_console.log("当前课程已经学完,即将跳转至下一个课程...");                let nextButton=document.querySelector('span.btn-next.ml20.pointer');                simulateHumanClick(nextButton);            }        }).catch(err=>{            location.reload();        });        //打印当前课程名称        waitForElement('.title-fl > span',app).then(title=>{            my_console.log("当前课程:"+title.innerText);        }).catch(err=>{            my_console.error("课程名出错:"+err);            location.reload();        });        //选择视频播放速率        let rateListPromise=waitForElement('.xt_video_player_common_list',app);        let rateButtonPromise=waitForElement('xt-speedbutton',app);        //两个dom元素必须都要获取到        Promise.all([rateListPromise,rateButtonPromise]).then(([rateList,rateButton])=>{            //如果用户设定的速率并不在原有速率列表中,将速率列表中的第一个速率改成对应速率            if (![0.51.01.251.52.0].includes(console_config.videoPlayRate)){                let newVideoPlayRate = rateList.childNodes[0];                newVideoPlayRate.setAttribute('data-speed', console_config.videoPlayRate);                newVideoPlayRate.setAttribute('keyt',console_config.videoPlayRate);                newVideoPlayRate.innerText=console_config.videoPlayRate.toFixed(2)+"x";            }            //鼠标移动到速率选择上            let rateButtonRect = rateButton.getBoundingClientRect();            const mouseMove = new MouseEvent('mousemove', {                bubbles: true,                cancelable: true,                clientX: (rateButtonRect.left+rateButtonRect.right)/2// 相对于视口的X坐标                clientY: (rateButtonRect.top+rateButtonRect.bottom)/2  // 相对于视口的Y坐标            });            rateButton.dispatchEvent(mouseMove);            //选择速率的第一个            rateList.children[0].click();            //静音播放视频            waitForElement('xt-controls > xt-inner > xt-volumebutton > xt-icon',app).then((mute=>{                //静音播放视频                mute.click();                intervalPlay();            })).catch(err=>{                my_console.error("静音:"+err);                location.reload();            })        }).catch(err=>{            my_console.error("速率调整:"+err);            location.reload();        })    }    //寻找视频元素,并对其进行操作    waitForElement("video.xt_video_player",app).then(element=>{        videoElement=element;        //触发获取视频详情api        waitForElement('.log-detail').then(element=>{            element.click();            waitForElement('.v-modal').then(element => {                element.remove();            })        })        //监听视频暂停事件,重新播放视频        videoElement.addEventListener("pause",()=>{            if (videoElement.currentTime <= videoElement.duration - 1){                videoElement.play();            }        });        //监听视频播放完毕事件,自动跳转至下一个视频        videoElement.addEventListener("ended",()=>{            let nextButton=document.querySelector('span.btn-next.ml20.pointer');            if(nextButton){                my_console.log("当前视频播放完毕,3s后播放下一个视频...");                const nextVideoTimer = new Blob([`                    const interval = 3000// 3秒间隔                    setTimeout(function() {                    self.postMessage({ type: 'next'});                    }, interval); `              ], { type: 'application/javascript' });                const nextVideoWorker = new Worker(URL.createObjectURL(nextVideoTimer));                nextVideoWorker.onmessage = function(e) {                    if (e.data.type === 'next') {                        simulateHumanClick(nextButton);                    }                };            }else{                my_console.log("最后一个视频播放完毕,本课程已经结束!");            }        });        let mouseSliderConfig={            container: app,            autoStart: true        }        window.mouseSliderSimulator = new MouseSliderSimulator(mouseSliderConfig);        const callbackTimer = new Blob([`                    const interval = 1000// 3秒间隔                    setTimeout(function() {                    self.postMessage({ type: 'callback'});                    }, interval); `              ], { type: 'application/javascript' });        const callbackWorker = new Worker(URL.createObjectURL(callbackTimer));        callbackWorker.onmessage = function(e) {            if (e.data.type === 'callback') {                callback();            }        };    }).catch(err=>{        my_console.error("寻找视频元素"+err);        location.reload();    });}//当路由变动时,所需要进行的操作function onRouteChange(path) {    my_console.clear();    if(window.mouseSliderSimulator){        window.mouseSliderSimulator.destroy();    }    start(path);}/** * 劫持跳转页面的行为,重新匹配目的地址的操作逻辑 */function hackHistoryApi(){    /**     * History API     *  包含 pushState()和 replaceState()方法,用于主动操作浏览器历史记录栈:     *  pushState():新增历史记录条目(URL 变化但页面不刷新)     *  replaceState():替换当前历史记录条目(常用于静默更新 URL)     *  本质是开发者主动控制路由的工具。     * popstate事件     * 属于被动监听机制,在用户触发浏览器行为(如点击前进/后退按钮)或调用 history.back()/forward()时自动触发。     * 本质是对用户导航行为的响应。     */        //劫持原有的History API    const _originalPushState = history.pushState;    const _originalReplaceState = history.replaceState;    //重写History API.增加路由改变时的行为逻辑    history.pushState = function (state, title, url) {        //执行原始的 pushState 方法        const result = _originalPushState.apply(this, arguments);        onRouteChange(url)        return result;    };    history.replaceState = function (state, title, url) {        // 执行原始的 replaceState 方法        const result = _originalReplaceState.apply(this, arguments);        onRouteChange(url);        return result;    };    //修复:以上的History Api劫持不能作用于浏览器的前进/后退,因此监听 popstate 事件(右键前进/后退触发)    window.addEventListener('popstate', () => {        onRouteChange(window.location.pathname);    });}//正则表达式匹配规则class Regex{    static videoPathRegex = /^\/pro\/[^\/]+(\/[^\/]+)*\/video\/[^\/]+$/;    static lessonPathRegex = /^\/pro(\/.*)?\/studycontent$/;    static host="/pro/courselist";}//网页不同路由的处理逻辑匹配function start(currentPath){    //页面匹配处理逻辑:查找表    const pathHandler=[        { condition: path => path === Regex.host, action: selectLessonPageLogic },        { condition: path => Regex.lessonPathRegex.test(path), action: selectLessonItemPageLogic },        { condition: path => Regex.videoPathRegex.test(path), action: autoPlayVideo },        { condition: path => true, action: ()=>{my_console.error("未知路径,暂未开发对应的功能,请进入学习空间")}}    ]    //页面匹配处理    my_console.log("当前路径:"+currentPath);    for(const{condition,action} of pathHandler){        if(condition(currentPath)){            action();            break;        }    }}
/** * 主程序 */(function(){    'use strict';    preventScreenCheck();    hackHistoryApi();    window.addEventListener('DOMContentLoaded', function() {        my_console=new Console(console_config);        my_console.log(welcome);        const currentPath = window.location.pathname;        start(currentPath);    });})()

解析

这是一个在雨课堂网页端自动化刷课脚本。它会自动识别你当前所处的课程页面,查找未完成的小节,自动进入视频页、调整播放速率、静音播放、监看播放进度并在结尾自动跳到下一节。脚本同时带一个页面内的浮动"控制台",用于显示运行日志、拖拽定位与一键收起。

主要方法

  • 页面路由匹配与分发(start() + 路由劫持)
    监听并拦截前端路由变更(包含前进/后退),根据 URL 判定当前是"课程列表""小节列表"还是"视频播放"页面,然后分发到对应逻辑函数。

  • 元素等待与稳健查询(waitForElement()
    对指定选择器做多次、指数退避的 DOM 查询;结合 MutationObserver 等待页面渲染完成,避免"元素未就绪"导致的操作失败。

  • 自动学习流程(

selectLessonPageLogic() / selectLessonItemPageLogic() / autoPlayVideo()

    • 在课程列表页罗列课程标题、提示用户进入;

    • 在小节列表页自动定位第一个未完成的小节并进入;

    • 在视频页自动设置播放速率与静音、监听播放结束并跳转下一节,且定期检查已看区间,避免重复观看。

  • 浮动日志面板(Console 类)
    页面右上角自带的"控制台",支持拖拽、自动滚动、时间戳、收起/展开等,统一输出脚本运行状态与提示信息。

  • "人类操作"模拟(

simulateHumanClick() & MouseSliderSimulator
通过合成鼠标事件与缓慢随机移动,尽量让点击/移动行为更像真人操作,降低被简单前端规则识别为脚本的概率(但不保证安全)。

  • 页面"前台"与心跳维系
    以前端层面的手段,尽量让页面维持"可见/有焦点"的状态,并以更稳定的方式定时触发心跳/进度上报,避免后台标签页被节流影响(不展开细节,以免造成不当用途)。

  • 请求拦截读取状态
    监听与课程进度相关的请求响应,从返回数据中拿到"已观看区间",据此跳过重复片段。


注意

本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。


历史脚本txt文件获取>>
服务器搭建,人工服务咨询>>

没有评论:

发表评论

【国内协会不好注册】?香港非盈利组织注册全指南——协会、社团、研究院一站式解读

在全球化的大背景下,香港作为国际金融中心,其优越的地理位置、开放的政策环境以及丰富的资源,吸引了众多企业和非盈 在全球化的大背景下,香港作为国际金融中心,其优越的地理位置、开放的政策环境以及丰富的资源,吸引了众多企业和非盈利组织在此设立机构。本文旨在为广大有意注册香港协会、社...