Greasy Fork

Greasy Fork is available in English.

东航易学助手

检测超时弹窗,稳定多开加速,并处理所有弹出按钮

// ==UserScript==
// @name         东航易学助手
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  检测超时弹窗,稳定多开加速,并处理所有弹出按钮
// @author       买不起泡面的Hanley
// @match        *://dhyx.ceair.com/*
// @require      http://code.jquery.com/jquery-1.9.1.min.js
// @grant        unsafeWindow
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @license MIT
// ==/UserScript==

var Status = 0; // 初始化状态标识
var lastClickTime = Date.now(); // 最后一次点击的时间
var refreshInterval; // 用于存储定时器ID
var isDragging = false; // 用于标识是否正在拖动
var dragStartX, dragStartY; // 记录拖动开始时的鼠标位置
var detectStringObserver = null; // 新增变量
var lastPageDetected = false; // 新增变量

var autoNavigateTimer = null;

function startAutoNavigateTimer() {
    if (autoNavigateTimer) clearInterval(autoNavigateTimer);
    autoNavigateTimer = setInterval(function() {
        if (getAutoNavigateEnabled()) {
            autoNavigateNextChapter();
        }
    }, 300000); // 每5分钟检查一次
}

function safeQuerySelector(selector, root = document) {
    try {
        return root.querySelector(selector);
    } catch (e) {
        return null;
    }
}

function safeQuerySelectorAll(selector, root = document) {
    try {
        return Array.from(root.querySelectorAll(selector));
    } catch (e) {
        return [];
    }
}

function ReSetTimer() {
    //重置并启动计时器
    document.querySelectorAll('iframe').forEach(function(iframe) {
        try {
            // 在每个 iframe 的 window 上下文执行
            iframe.contentWindow.startCount = 0;
            if (typeof iframe.contentWindow.startCountTool === 'function') {
                iframe.contentWindow.startCountTool();
            }
            iframe.contentWindow.startCount = 0;
        } catch (e) {
            // 跨域等异常忽略
            console.warn('无法访问iframe:', e);
        }
    });
}

function getAutoNavigateEnabled() {
    return localStorage.getItem('autoNavigateEnabled') === 'true';
}

function setAutoNavigateEnabled(value) {
    localStorage.setItem('autoNavigateEnabled', value);
    manageAutoRefresh();
}

function manageAutoRefresh() {
    clearInterval(refreshInterval);
    clearInterval(autoNavigateTimer);
    refreshInterval = null;
    autoNavigateTimer = null;

    if (Status == 0 && !getAutoNavigateEnabled()) {
        refreshInterval = setInterval(function() {
            clickCurrentFocusSection();
        }, 360000);
    }
    if (Status == 0 && getAutoNavigateEnabled()) {
        autoNavigateNextChapter(); // 立即执行一次
        autoNavigateTimer = setInterval(function() {
            autoNavigateNextChapter();
        }, 300000);
    }
}

function updateToggleButton(toggleButton) {
    const autoNavigateEnabled = getAutoNavigateEnabled();
    toggleButton.innerText = autoNavigateEnabled ? "自动导航:开" : "自动导航:关";
    toggleButton.style.background = autoNavigateEnabled ? 'rgba(255, 255, 0, 0.5)' : 'rgba(128, 128, 128, 0.5)';
}

function detectAndClickAlertButton() {
    // 查找弹窗的wrapper
    const alertWrapper = document.querySelector('.alert-shadow.new-alert-shadow');
    if (!alertWrapper) {
        return;
    }

    // 查找确定按钮
    const alertButton = document.getElementById('D253btn-ok');
    if (alertButton) {
        alertButton.click(); // 自动点击确定按钮
    }
}

function detectString() {
    if (detectStringObserver) return; // 已注册则不再注册

    function checkString(node) {
        if (node.nodeType === Node.TEXT_NODE) {
            if (node.nodeValue.includes('可继续学习')) {
                var confirmBtns3 = document.getElementsByClassName("btn");
                if (confirmBtns3 != null && confirmBtns3.length > 0) {
                    confirmBtns3[0].click();
                    lastClickTime = Date.now();
                }
            } else if (node.nodeValue.includes('恭喜')) {
                var cancelBtns = document.querySelectorAll('button[data-bb-handler="cancel"]');
                if (cancelBtns.length > 0) {
                    cancelBtns[0].click();
                }
                if (getAutoNavigateEnabled()) {
                    setTimeout(function() {
                        window.top.location.reload(true);
                    }, 5000);
                }
            } else if (node.nodeValue.includes('进度已保存')) {
                if (getAutoNavigateEnabled()) {
                    // 5秒后刷新页面
                    setTimeout(function() {
                        window.top.location.reload(true);
                    }, 5000);
                }
            } else if (node.nodeValue.includes('小测试')) {
                // 执行一次遍历
            }
        } else if (node.nodeType === Node.ELEMENT_NODE) {
            // 检查元素节点中的文本内容
            if (node.textContent.includes('进度已保存')) {
                if (getAutoNavigateEnabled()) {
                    // 5秒后刷新页面
                    setTimeout(function() {
                        window.top.location.reload(true);
                    }, 5000);
                }
            }
            // 遍历子节点
            for (let child of node.childNodes) {
                checkString(child);
            }
        }
    }

    detectStringObserver = new MutationObserver(function(mutations) {
        for (let mutation of mutations) {
            for (let addedNode of mutation.addedNodes) {
                checkString(addedNode);
            }
        }
    });

    detectStringObserver.observe(document.body, {
        childList: true,
        subtree: true
    });
}

function updateFlagStatus() {
    const flag1 = document.getElementById('flag');
    if (Status == 1) {
        flag1.innerText = "检测到考试!代码停止运行!\nby:买不起泡面的Hanley";
    } else if (Status == 2) {
        flag1.innerText = "运行阅卷脚本\nby:买不起泡面的Hanley";
    } else if (Status == -1) {
        flag1.innerText = "脚本不在首页运行\nby:买不起泡面的Hanley";
    } else {
        flag1.innerText = "脚本正在运行\nby:买不起泡面的Hanley";
    }
}

function makeElementDraggable(element) {
    let offsetX, offsetY;

    element.addEventListener('mousedown', function(event) {
        isDragging = false;
        dragStartX = event.clientX;
        dragStartY = event.clientY;
        offsetX = event.clientX - element.getBoundingClientRect().left;
        offsetY = event.clientY - element.getBoundingClientRect().top;
        element.style.cursor = 'move';

        function onMouseMove(event) {
            const deltaX = event.clientX - dragStartX;
            const deltaY = event.clientY - dragStartY;
            if (!isDragging && (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5)) {
                isDragging = true;
            }
            if (isDragging) {
                element.style.left = event.clientX - offsetX + 'px';
                element.style.top = event.clientY - offsetY + 'px';
                event.preventDefault(); // 防止选择文本
            }
        }

        function onMouseUp() {
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
            element.style.cursor = 'pointer';
        }

        document.addEventListener('mousemove', onMouseMove);
        document.addEventListener('mouseup', onMouseUp);
    });
}

if (window.self === window.top) { // 仅在顶层窗口显示悬浮窗
    setTimeout(function() {
        const flag = document.createElement("div");
        flag.id = 'flag';
        flag.style.cssText = 'left: 10px;bottom: 10px;background: rgba(26, 89, 183, 0.5);color:#ffffff;overflow: hidden;z-index: 9999;position: fixed;padding:3px;text-align:center;width: 175px;height: 45px;line-height: 20px;border-radius: 4px;cursor: pointer;';
        document.getElementById("content").appendChild(flag);

        updateFlagStatus();

        makeElementDraggable(flag);

        flag.addEventListener('click', function() {
            if (!isDragging && flag.innerText.includes("脚本正在运行")) {
                flag.style.background = 'rgba(255, 0, 0, 0.5)';
                flag.innerText = "正在尝试爆破\nby:买不起泡面的Hanley";
                markVideoAsCompleted();
                setTimeout(function() {
                    flag.style.background = 'rgba(26, 89, 183, 0.5)';
                    updateFlagStatus();
                }, 2000);
                loadKnockoutJs(proceedWithModifications);
            }
        });

        const toggleButton = document.createElement("div");
        toggleButton.id = 'toggleButton';
        toggleButton.style.cssText = 'right: 10px;bottom: 10px;color:#000000;overflow: hidden;z-index: 9999;position: fixed;padding:3px;text-align:center;width: 100px;height: 30px;line-height: 25px;border-radius: 4px;cursor: pointer;';
        document.body.appendChild(toggleButton);

        updateToggleButton(toggleButton); // 确保页面加载时立即更新按钮背景颜色

        makeElementDraggable(toggleButton);

        toggleButton.addEventListener('click', function() {
            if (!isDragging) {
                const newState = !getAutoNavigateEnabled();
                setAutoNavigateEnabled(newState);
                updateToggleButton(toggleButton);
                setTimeout(function() {
                    window.top.location.reload(true);
                }, 1000);//一秒刷新
            }
        });

        // 初始化时检查并设置自动刷新
        manageAutoRefresh();
        ReSetTimer();
        detectString(); // 只在初始化时调用一次
        startAutoNavigateTimer(); // 启动定时器
    }, 4000); // 4秒后显示状态牌
}

var AutoClick = setInterval(function() { Clicker() }, 1000); // 每秒运行一次

function Clicker() {
    console.error = function() {};
    if (location.href.includes('/#/home')) {
        clearInterval(AutoClick);
        clearInterval(refreshInterval);
        Status = -1; // 首页不搞
        updateFlagStatus();
        return;
    }
    if (window.location.href.indexOf("exam") > -1) {
        if (window.location.href.indexOf("mark-paper") > -1) { // 检测到是在阅卷,停止运行但不跳弹窗
            clearInterval(AutoClick); // 阅卷了就不乱点了
            clearInterval(refreshInterval);
            Status = 2; // 阅卷了阅卷了
            updateFlagStatus();
            return;
        } else {
            clearInterval(AutoClick); // 别是在考试,考试就直接摆烂
            clearInterval(refreshInterval);
            Status = 1; // 考试了还玩球,状态标异常
            alert('检测到考试!代码停止运行!');
            updateFlagStatus();
            return;
        }
    }
    checkLoginStatus();
    handleClicks();
    detectAndClickAlertButton();
    clickNextButtonInIframe();

    if (getAutoNavigateEnabled()) {
        const nextPage = document.querySelector(".navBtn.glyphicon.glyphicon-chevron-right");
        if (nextPage && !nextPage.disabled) {
            nextPage.click();
            lastClickTime = Date.now();
        }
    }
}

function handleClicks() {

    const confirmBtns = document.querySelectorAll(".bootbox-close-button.close");
    if (confirmBtns.length > 0) {
        confirmBtns[0].click(); // 自动点击
        lastClickTime = Date.now();
    }

    const confirmBtns2 = document.querySelectorAll(".alertify-button.alertify-button-ok");
    if (confirmBtns2.length > 0) {
        confirmBtns2[0].click();
        lastClickTime = Date.now();
    }
}

let currentSectionId = null;

function autoNavigateNextChapter() {
    // 1. 收集所有章节标题
    const allTitles = Array.from(document.querySelectorAll('.common-title.text-overflow[data-path]'));
    const uuidTitleMap = {};
    allTitles.forEach(title => {
        const dataPathArr = title.getAttribute('data-path').split(',').filter(Boolean);
        if (dataPathArr.length > 0) {
            const uuid = dataPathArr[dataPathArr.length - 1];
            uuidTitleMap[uuid] = title;
        }
    });

    // 2. 按直接父级分组所有小节
    const allSections = Array.from(document.querySelectorAll('dl.chapter-list-box'));
    const parentMap = new Map();

    for (const section of allSections) {
        let parentDiv = section.parentElement;
        while (parentDiv && !parentDiv.className.match(/chapter-/)) {
            parentDiv = parentDiv.parentElement;
        }
        let upUuids = [];
        if (parentDiv) {
            upUuids = Array.from(parentDiv.classList)
                .filter(cls => cls.startsWith('chapter-'))
                .map(cls => cls.replace('chapter-', ''));
        }
        const parentUuid = upUuids.length > 0 ? upUuids[upUuids.length - 1] : null;
        if (!parentUuid) continue;
        if (!parentMap.has(parentUuid)) parentMap.set(parentUuid, []);
        parentMap.get(parentUuid).push(section);
    }

    // 获取下拉菜单选择的起始序号
    const startIndex = parseInt(localStorage.getItem('autoNavigateStartIndex') || "1", 10);

    // 3. 处理每个父级
    for (const [parentUuid, sections] of parentMap.entries()) {
        const parentTitle = uuidTitleMap[parentUuid];
        if (!parentTitle) continue;
        const isParentFinished = parentTitle.innerText.includes('【已完成】');
        // 3.1 优先点击未完成/无状态小节(分流:跳过前N-1个)
        let pendingCount = 0;
        for (const section of sections) {
            const statusSpan = section.querySelector('.pull-right');
            const status = statusSpan ? statusSpan.innerText.trim() : '';
            if (!status || !status.includes('已完成')) {
                pendingCount++;
                if (pendingCount < startIndex) continue; // 跳过前N-1个
                const clickable = section.querySelector('.chapter-item');
                const sectionId = section.id || section.getAttribute('data-sectiontype') || section;
                if (currentSectionId !== sectionId && clickable) {
                    simulateClick(clickable, window);
                    lastPageDetected = false;
                    currentSectionId = sectionId;
                }
                // 停留在未完成/无状态小节
                return;
            }
        }
        // 3.2 如果所有小节都已完成但父级未完成,停留在最后一个小节,并刷新
        if (!isParentFinished && sections.length > 0) {
            const lastSection = sections[sections.length - 1];
            const clickable = lastSection.querySelector('.chapter-item');
            const sectionId = lastSection.id || lastSection.getAttribute('data-sectiontype') || lastSection;
            if (currentSectionId !== sectionId && clickable) {
                simulateClick(clickable, window);
                lastPageDetected = false;
                currentSectionId = sectionId;
            }
            clickCurrentFocusSection();
            return;
        }
    }
    // 4. 所有父级都已完成,重置currentSectionId,并刷新
    currentSectionId = null;
    clickCurrentFocusSection();
}



function clickNextButtonInIframe() {

    // 获取页面上的所有 iframe 元素
    const iframes = document.querySelectorAll('iframe');
    if (iframes.length === 0) {
        return; // 如果没有 iframe,退出函数
    }

    // 遍历每个 iframe,执行相关操作
    iframes.forEach(function(iframe) {
        const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;


        if (getAutoNavigateEnabled()) {
            // 查找并点击下一个完成的按钮
            const nextPageButton = iframeDocument.querySelector('.navBtn.glyphicon.glyphicon-chevron-right.nextCompleted');
            if (nextPageButton) {
                nextPageButton.click();
                lastClickTime = Date.now();
            }

            // 查找并模拟点击弹窗按钮
            const popupBtns = iframeDocument.querySelectorAll('.btn[data-bb-handler="ok"], .btn[data-bb-handler="next"], .btn[data-bb-handler="confirm"]');
            popupBtns.forEach(function(btn) {
                simulateClick(btn, iframe.contentWindow);
                btn.click();
                lastClickTime = Date.now();
            });

            // 检查当前页面是否是最后一页,如果是则点击当前章节刷新学习状态(只执行一次)
            const navigationLabel = iframeDocument.querySelector('.navigation-controls__label');
            if (navigationLabel){
                const pageNumbers = navigationLabel.innerText.split('/');
                if (
                    pageNumbers.length === 2 &&
                    pageNumbers[0].trim() === pageNumbers[1].trim() &&
                    !lastPageDetected
                ) {
                    lastPageDetected = true;
                    // 点击当前章节刷新学习状态
                    clickCurrentFocusSection();
                }
            }
            // 查找并点击暂停按钮,跳过视频
            const buttons = iframeDocument.querySelectorAll('.uikit-primary-button.uikit-primary-button_size_medium.play-controls-container__play-pause-button');
            buttons.forEach(function(button) {
                const svg = button.querySelector('svg');
                if (svg) {
                    const path = svg.querySelector('path');
                    if (path && getAutoNavigateEnabled() && path.getAttribute('d') === 'M5 16.3087V3.54659L5 3.54659L16.8484 8.87836C17.2245 9.04759 17.2455 9.57369 16.8842 9.77243L5 16.3087Z') {
                        simulateClick(button, iframe.contentWindow);
                    }
                }
            });
            //下一页
            const buttonsNext = iframeDocument.querySelectorAll('.uikit-primary-button.uikit-primary-button_size_medium.navigation-controls__button.uikit-primary-button_next.navigation-controls__button_next');
            buttonsNext.forEach(function(button) {
                const svg = button.querySelector('svg');
                if (svg) {
                    const path = svg.querySelector('path');
                    if (path && getAutoNavigateEnabled() && path.getAttribute('d') === 'M8 4L14 10L8 16') {
                        simulateClick(button, iframe.contentWindow);
                    }
                }
            });
        }

        // 处理弹窗按钮
        const popupBtns2 = iframeDocument.querySelectorAll('.message-box-buttons__window-button');
        popupBtns2.forEach(function(btn) {
            simulateClick(btn, iframe.contentWindow);
        });
    });
}

function simulateClick(element, win) {
    let mouseDownEvent, mouseUpEvent, clickEvent;
    try {
        // 优先用传入的 win,如果失败自动降级
        mouseDownEvent = new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: win || window });
        mouseUpEvent = new MouseEvent('mouseup', { bubbles: true, cancelable: true, view: win || window });
        clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true, view: win || window });
    } catch (e) {
        // 如果view参数报错则降级处理
        mouseDownEvent = new MouseEvent('mousedown', { bubbles: true, cancelable: true });
        mouseUpEvent = new MouseEvent('mouseup', { bubbles: true, cancelable: true });
        clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
    }
    element.dispatchEvent(mouseDownEvent);
    element.dispatchEvent(mouseUpEvent);
    element.dispatchEvent(clickEvent);
}

function loadKnockoutJs(callback) {
    var resources = performance.getEntriesByType('resource');
    var regex = /file-cloud\/01\/[0-9A-Za-z]+\/[0-9A-ZaZ]+\/[0-9A-ZaZ]+/;
    var knockoutJsUrl = '';

    for (var i = 0; i < resources.length; i++) {
        var resource = resources[i];
        if (resource.name.includes('scorm_api.jsp?')) {
            var match = resource.name.match(regex);
            if (match) {
                var dynamicPath = match[0];
                if (dynamicPath.includes('/player')) {
                    dynamicPath = dynamicPath.replace('/player', '');
                }
                knockoutJsUrl = 'https://dhyx.ceair.com/' + dynamicPath + '/lib/knockout/knockout.min.js';
                break;
            }
        }
    }

    if (knockoutJsUrl) {
        var script = document.createElement('script');
        script.src = knockoutJsUrl;
        document.head.appendChild(script);

        script.onload = function() {
            callback();
        };
    } else {
        callback();
    }
}

function proceedWithModifications() {
    function findViewModel(root) {
        var queue = [{ obj: root, path: '' }];
        var visited = new Set();

        while (queue.length > 0) {
            var current = queue.shift();
            var obj = current.obj;
            var path = current.path;

            if (visited.has(obj)) {
                continue;
            }
            visited.add(obj);

            for (var prop in obj) {
                if (obj.hasOwnProperty(prop) && typeof obj[prop] === 'object' && obj[prop] !== null) {
                    try {
                        if (obj[prop].constructor && obj[prop].constructor.name === 'CBTPlayer') {
                            return obj[prop];
                        }
                    } catch (e) {
                        continue;
                    }
                    queue.push({ obj: obj[prop], path: path + prop + '.' });
                }
            }
        }
        return null;
    }

    var cbtPlayer = findViewModel(window);

    if (cbtPlayer) {
        var currentOrg = cbtPlayer.currentOrg();
        if (currentOrg && currentOrg.items) {
            currentOrg.items().forEach(function(item) {
                item.completed(true);
                item.visited(true);
                item.playbackFinished(true);
                if (item.type === 'topic' || item.type === 'mergedpages') {
                    item.items().forEach(function(subItem) {
                        subItem.completed(true);
                        subItem.visited(true);
                        subItem.playbackFinished(true);
                    });
                }
            });

            currentOrg.items.valueHasMutated();

            if (typeof cbtPlayer.SavePlayer === 'function') {
                cbtPlayer.SavePlayer();
            }
        }
    }
}

//调用统一认证登录
function checkLoginStatus() {
    if (window.location.href.indexOf("oauth/#login") > -1) {
        setTimeout(function() {
            document.querySelector(".step-item.pointer:nth-child(3)").click();
        }, 5000);
        lastClickTime = Date.now();
    }
}
function clickCurrentFocusSection() {
    try {
        const focused = document.querySelector('dl.chapter-list-box.focus');
        if (focused) {
            let clickable = focused.querySelector('.chapter-item');
            if (!clickable) {
                clickable = focused.querySelector('.text-overflow');
            }
            if (clickable) {
                // 优先用 simulateClick,若无效可直接用 click()
                simulateClick(clickable, window);
                // clickable.click();
            }
        }
    } catch (e) {
        console.error('clickCurrentFocusSection error:', e);
    }
}


// 添加视频爆破功能
function markVideoAsCompleted() {
    var videoElements = document.querySelectorAll('video[id$="player_html5_api"]');
    if (videoElements.length > 0) {
        var videoElement = videoElements[0];
        videojs(videoElement).ready(function() {
            var player = this;
            player.currentTime(player.duration());
            player.trigger('ended');
        });
    }
}

// ==窗口编号选择悬浮窗补丁==
// 仅在 Status 不为 1、2、-1 时显示
(function() {
    if (window.self !== window.top) return;
    setTimeout(function() {
        if (typeof Status !== "undefined" && (Status === 1 || Status === 2 || Status === -1)) return;
        if (document.getElementById("windowSelectDiv")) return;

        const windowSelectDiv = document.createElement("div");
        windowSelectDiv.id = "windowSelectDiv";
        windowSelectDiv.style.cssText = `
            left: 30px;
            top: 80px;
            z-index: 9999;
            position: fixed;
            background: rgba(150, 89, 183, 0.5);
            color: #fff;
            border-radius: 6px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.10);
            padding: 0 18px;
            height: 40px;
            display: flex;
            align-items: center;
            font-size: 16px;
            cursor: move;
            user-select: none;
        `;

        const windowLabel = document.createElement("span");
        windowLabel.innerText = "窗口编号";
        windowLabel.style.cssText = "font-size:15px;margin-right:4px;letter-spacing:0px;";

        const windowSelect = document.createElement("select");
        windowSelect.id = "startIndexSelect";
        windowSelect.style.cssText = `
            width: 40px;
            height: 32px;
            font-size: 15px;
            border-radius: 6px;
            border: 1px solid #e0e0e0;
            outline: none;
            background: rgba(255,255,255,1);
            color: #333;
            cursor: pointer;
            box-shadow: none;
            padding-left: 8px;
        `;
        for (let i = 1; i <= 6; i++) {
            const option = document.createElement("option");
            option.value = i;
            option.text = i;
            windowSelect.appendChild(option);
        }
        windowSelect.value = localStorage.getItem('autoNavigateStartIndex') || "1";
        windowSelect.onchange = function() {
            localStorage.setItem('autoNavigateStartIndex', windowSelect.value);
            window.location.reload();
        };

        windowSelectDiv.appendChild(windowLabel);
        windowSelectDiv.appendChild(windowSelect);
        document.body.appendChild(windowSelectDiv);

        // 调用原有可拖动函数,防止变形
        if (typeof makeElementDraggable === "function") {
            makeElementDraggable(windowSelectDiv);
        }
    }, 4000);
})();