SCNU学生评价辅助工具

需要手动提交并切换至下一个老师。

// ==UserScript==
// @name         SCNU学生评价辅助工具
// @namespace    https://github.com/FaterYU/SCNU-Evaluation-Helper-Anti-Detection
// @version      1.0
// @description  需要手动提交并切换至下一个老师。
// @author       FaterYU (Enhanced by Gemini)
// @match        https://jwxt.scnu.edu.cn/xspjgl/xspj_cxXspjIndex.html*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=scnu.edu.cn
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const PROCESSED_MARKER = 'data-script-processed';

    // 生成一个在min和max之间的随机延迟时间(毫秒)
    function randomDelay(min = 500, max = 1500) {
        return Math.floor(Math.random() * (max - min + 1) + min);
    }

    /**
     * 模拟人类行为,逐个填充选项,然后提交
     * @param {HTMLElement} formElement - 评价表单的 <form> 元素
     */
    async function humanizedFillAndSubmit(formElement) {
        console.log("检测到评价表单,开始模拟人类行为填充...", formElement);

        const radioButtons = Array.from(formElement.querySelectorAll('input.radio-pjf'));
        if (radioButtons.length === 0) {
            console.error("未找到任何 .radio-pjf 按钮,操作中断。");
            return;
        }

        console.log(`找到 ${radioButtons.length} 个评分项,开始逐个选择...`);

        const questionNames = [...new Set(radioButtons.map(radio => radio.name))];

        // 模拟逐个问题进行回答
        for (const name of questionNames) {
            const firstOption = formElement.querySelector(`input.radio-pjf[name="${name}"]`);
            if (firstOption) {
                firstOption.click(); // 使用 .click() 来模拟点击,更能触发相关事件
            }
            // 在回答每个问题后加入一个短暂的随机停顿
            await new Promise(resolve => setTimeout(resolve, randomDelay(50, 150)));
        }

        // 找到提交按钮
        const submitButton = formElement.querySelector('#btn_xspj_tj');
        if (submitButton) {
            console.log("所有选项填充完毕,等待一个随机延迟后点击提交...");
            // 在点击提交前也加入一个明显的停顿
            await new Promise(resolve => setTimeout(resolve, randomDelay(800, 1800)));
            submitButton.click();
            console.log("已点击提交按钮。");
        } else {
            console.warn("未在表单内找到提交按钮 #btn_xspj_tj");
        }
    }

    /**
     * 查找并点击下一个未评价课程
     */
    function findAndClickNextCourse() {
        console.log("开始查找下一个未评价的课程...");
        const courseListContainer = document.getElementById('gbox_tempGrid');
        if (!courseListContainer) {
            console.log("未找到课程列表容器。");
            return false;
        }

        const courses = courseListContainer.querySelectorAll('tr.jqgrow');
        for (const course of courses) {
            const statusCell = course.querySelector('td[title="未评"]');
            if (statusCell) {
                console.log("找到未评价课程,延迟后点击:", course.innerText.trim().replace(/\s+/g, ' '));
                // 点击前增加延迟
                setTimeout(() => course.click(), randomDelay(1000, 2000));
                return true; // 表示已找到并安排点击
            }
        }

        console.log("所有课程均已评价完毕,脚本任务结束。");
        return false; // 表示没有未评课程了
    }

    /**
     * 主循环,持续监控页面状态
     */
    function mainLoop() {
        // --- 任务1: 处理新加载的“评价”表单 ---
        const panelContent = document.getElementById('panel_content');
        if (panelContent) {
            const evalForm = panelContent.querySelector(`form#ajaxForm1:not([${PROCESSED_MARKER}])`);
            if (evalForm && evalForm.querySelector('input.radio-pjf')) {
                console.log("检测到新的评价表单,准备进行人性化填充。");
                evalForm.setAttribute(PROCESSED_MARKER, 'true');
                humanizedFillAndSubmit(evalForm); // 调用增加了延迟的新函数
            }
        }

        // --- 任务2: 处理“提交成功”的弹窗 ---
        const successAlert = document.querySelector("#tip_alert");
        if (successAlert && successAlert.offsetParent !== null) {
            const okButton = successAlert.querySelector('#btn_ok');
            if (okButton) {
                console.log("检测到'提交成功'弹窗,延迟后点击确定。");
                 // 在自动关闭成功提示前也加上延迟
                setTimeout(() => {
                    okButton.click();
                    console.log("成功弹窗已关闭,准备评价下一个课程...");
                    findAndClickNextCourse();
                }, randomDelay());
                // 为防止重复处理,可以临时隐藏它
                successAlert.style.display = 'none';
            }
        }

        // --- 任务3: 处理“请勿使用脚本”的警告弹窗 ---
        const warningAlert = document.querySelector(".bootbox-alert");
        if (warningAlert && warningAlert.offsetParent !== null && warningAlert.innerText.includes('请勿使用类似脚本')) {
            const warningOkButton = warningAlert.querySelector('#btn_ok');
            if (warningOkButton) {
                console.warn("检测到'反脚本'警告!将自动关闭并减慢速度重试。");
                 // 立即关闭警告框
                warningOkButton.click();

                // 由于被检测到了,我们可以在这里采取一些策略,比如等待更长时间再继续
                // 这里我们简单地让主循环在下一次迭代时重新查找课程
                console.log("已关闭警告弹窗,脚本将在下次循环时继续。");
            }
        }
    }

    // --- 脚本启动入口 ---
    window.addEventListener('load', () => {
        console.log("SCNU评价辅助脚本已启动。");

        // 首次运行时,更长的延迟,更像人类
        setTimeout(findAndClickNextCourse, randomDelay(2000, 3000));

        // 启动核心的循环监视器,将检查间隔也设为一个随机值
        setInterval(mainLoop, randomDelay(1000, 1500));
    });

})();