// ==UserScript== // @name 鲸鱼自动评教 // @namespace https://scriptcat.org/zh-CN/script-show-page/6663 // @version 9.3.1 // @description 教学质量管理平台自动化评教,半自动/全自动,自动识别弹窗按钮 // @author nullnow // @match https://jxzlgl.hbpa.edu.cn/index.html* // @grant GM_addStyle // @license MIT // ==/UserScript== (function() { 'use strict'; const C = { comment: "好", modalTimeout: 15000, loadDelay: 3000 }; let semiRunning = false, autoRunning = false; let semiBtn, autoBtn; window.alert = (msg) => (console.log("alert:", msg), true); window.confirm = (msg) => (console.log("confirm:", msg), true); function setTextarea(el, text) { if (!el) return; el.focus(); el.value = ''; el.dispatchEvent(new Event('focus', { bubbles: true })); if (document.execCommand) { el.select(); document.execCommand('insertText', false, text); el.dispatchEvent(new Event('change', { bubbles: true })); el.dispatchEvent(new Event('blur', { bubbles: true })); } else { el.value = text; ['input', 'change', 'blur'].forEach(e => el.dispatchEvent(new Event(e, { bubbles: true }))); } } function fillTextareas() { let count = 0; document.querySelectorAll('textarea.ant-input').forEach(ta => { if (ta.value !== C.comment) { setTextarea(ta, C.comment); count++; } }); if (count) console.log(`filled ${count} textarea(s)`); } function selectRadios() { let radios = document.querySelectorAll('input[type="radio"]'); radios.forEach(r => { if (!r.checked && !r.disabled) r.click(); }); console.log(`selected ${radios.length} radio(s)`); } function waitConfirm() { return new Promise((resolve) => { const start = Date.now(); const obs = new MutationObserver(() => { const btn = document.querySelector('.ant-modal-body .ant-btn-primary'); if (btn && btn.offsetParent !== null && !btn.disabled) { obs.disconnect(); btn.click(); resolve(true); } else if (btn && btn.disabled) { console.log("confirm button disabled, waiting..."); } if (Date.now() - start > C.modalTimeout) { obs.disconnect(); resolve(false); } }); obs.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['disabled', 'class'] }); setTimeout(() => { obs.disconnect(); resolve(false); }, C.modalTimeout); }); } function waitSuccessButton() { return new Promise((resolve) => { const start = Date.now(); const obs = new MutationObserver(() => { const btns = Array.from(document.querySelectorAll('.ant-modal-body button, .ant-modal button')); const actionBtns = btns.filter(btn => !btn.querySelector('.anticon-close') && btn.innerText !== '取消'); let target = actionBtns.find(btn => btn.innerText !== '确定' && btn.innerText !== '确 定'); if (!target && actionBtns.length === 2) { target = actionBtns.find(btn => btn.innerText.includes('确定')); } if (target && target.offsetParent !== null && !target.disabled) { obs.disconnect(); resolve(target); return; } if (Date.now() - start > C.modalTimeout) { obs.disconnect(); resolve(null); } }); obs.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { obs.disconnect(); resolve(null); }, C.modalTimeout); }); } async function evaluateOnce() { selectRadios(); await new Promise(r => setTimeout(r, 1000)); fillTextareas(); await new Promise(r => setTimeout(r, 500)); fillTextareas(); let submit = document.querySelector('.ant-btn-primary, .index__submit--jiKIA'); if (!submit) { console.error("submit button not found"); return null; } fillTextareas(); await new Promise(r => setTimeout(r, 200)); submit.click(); if (!(await waitConfirm())) return null; return await waitSuccessButton(); } async function waitForQuestions(timeout = 15000) { const start = Date.now(); while (Date.now() - start < timeout) { if (document.querySelector('textarea.ant-input, input[type="radio"]')) { await new Promise(r => setTimeout(r, 500)); return true; } await new Promise(r => setTimeout(r, 300)); } return false; } async function semiAuto() { if (semiRunning) return; semiRunning = true; if (semiBtn) { semiBtn.classList.add('disabled'); semiBtn.textContent = '⏳ 运行中...'; } try { const next = await evaluateOnce(); if (next) console.log(`next button found: ${next.innerText}`); else console.log("evaluation finished"); } catch(e) { console.error(e); } finally { if (semiBtn) { semiBtn.classList.remove('disabled'); semiBtn.textContent = '📋 一键评教'; } semiRunning = false; } } async function fullAuto() { if (autoRunning) return; autoRunning = true; if (autoBtn) { autoBtn.classList.add('disabled'); autoBtn.textContent = '⏳ 全自动中...'; } if (semiBtn) semiBtn.disabled = true; let count = 0; try { while (true) { count++; const next = await evaluateOnce(); if (!next) break; next.click(); await new Promise(r => setTimeout(r, C.loadDelay)); if (!(await waitForQuestions())) break; } } catch(e) { console.error(e); } finally { autoRunning = false; if (autoBtn) { autoBtn.classList.remove('disabled'); autoBtn.textContent = '🔄 全自动评教'; } if (semiBtn) semiBtn.disabled = false; } } function createUI() { const container = document.createElement('div'); Object.assign(container.style, { position: 'fixed', bottom: '30px', right: '30px', zIndex: '9999', display: 'flex', flexDirection: 'column', gap: '10px', alignItems: 'flex-end' }); semiBtn = document.createElement('button'); semiBtn.textContent = '📋 一键评教'; Object.assign(semiBtn.style, { background: '#4CAF50', color: 'white', border: 'none', borderRadius: '40px', padding: '10px 20px', fontSize: '14px', fontWeight: 'bold', cursor: 'pointer', width: '140px', boxShadow: '0 2px 8px rgba(0,0,0,0.2)' }); semiBtn.onclick = semiAuto; autoBtn = document.createElement('button'); autoBtn.textContent = '🔄 全自动评教'; Object.assign(autoBtn.style, { background: '#2196F3', color: 'white', border: 'none', borderRadius: '40px', padding: '10px 20px', fontSize: '14px', fontWeight: 'bold', cursor: 'pointer', width: '140px', boxShadow: '0 2px 8px rgba(0,0,0,0.2)' }); autoBtn.onclick = fullAuto; container.appendChild(semiBtn); container.appendChild(autoBtn); document.body.appendChild(container); } GM_addStyle(`.disabled { opacity: 0.6; cursor: not-allowed !important; }`); window.addEventListener('load', createUI); })();