// ==UserScript== // @name 全国生态环境执法培训云课堂-自动学习助手 // @namespace https://onlinelearning.21tb.com // @version 2.3.0 // @description 多开多账号自动学习:自动登录、自动关弹窗、自动播放视频、自动切章节、智能题库自学习自动答题(越用越准)。支持多人同时学习。 // @author 环保执法助手 // @match *://onlinelearning.21tb.com/* // @match *://*.21tb.com/rtr-frontend/* // @match *://*.21tb.com/courseSetting/* // @match *://*.21tb.com/apaas-design/* // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // @run-at document-idle // @license MIT // ==/UserScript== (function () { 'use strict'; // ============ 配置与题库存储 ============ const CFG_KEY = 'autoLearnConfig'; const QB_KEY = 'autoLearnQuestionBank'; const defaultConfig = { username: '', password: '', autoLogin: true, autoPlay: true, autoNextChapter: true, autoAnswerTest: true, autoClosePopup: true, muteVideo: true, speed: 1.0, }; function loadJSON(key, fallback) { try { const v = GM_getValue(key, ''); if (v) return JSON.parse(v); } catch (e) {} return fallback; } function saveJSON(key, val) { try { GM_setValue(key, JSON.stringify(val)); } catch (e) {} } let config = loadJSON(CFG_KEY, defaultConfig); let questionBank = loadJSON(QB_KEY, {}); // {题目哈希: {question, answer}} // ============ 工具 ============ function $(s, r) { return (r || document).querySelector(s); } function $$(s, r) { return Array.from((r || document).querySelectorAll(s)); } function hasText(el, kws) { const t = (el.innerText || el.textContent || ''); return kws.some(k => t.includes(k)); } function fmt(s) { if (!s || isNaN(s)) return '--:--'; return `${Math.floor(s/60)}:${Math.floor(s%60).toString().padStart(2,'0')}`; } function hashText(t) { let h = 0; const s = (t || '').replace(/\s+/g, '').substring(0, 200); for (let i = 0; i < s.length; i++) { h = ((h << 5) - h + s.charCodeAt(i)) | 0; } return 'q' + Math.abs(h).toString(36); } let panel = null; let isRunning = true; let audioCtx = null; let testAnswered = false; function log(msg) { console.log(`[自动学习] ${msg}`); const el = $('#al-log'); if (el) { const t = new Date().toLocaleTimeString().substring(0, 5); const d = document.createElement('div'); d.textContent = `[${t}] ${msg}`; el.appendChild(d); while (el.children.length > 80) el.removeChild(el.firstChild); el.scrollTop = el.scrollHeight; } } // ============ 后台保活 ============ function keepAlive() { try { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); const o = audioCtx.createOscillator(); const g = audioCtx.createGain(); g.gain.value = 0; o.connect(g).connect(audioCtx.destination); o.start(); } } catch (e) {} } // ============ 自动登录 ============ function tryAutoLogin() { if (!config.autoLogin || !config.username || !config.password) return; if (!location.href.includes('login')) return; log('🔐 检测到登录页,自动填充账号...'); setTimeout(() => { // 多种选择器匹配用户名输入框 const userInput = document.querySelector('#loginName') || document.querySelector('input[placeholder="用户名"]') || document.querySelector('input[placeholder*="用户"]') || document.querySelector('input[placeholder*="账号"]') || document.querySelector('input[placeholder*="手机"]') || document.querySelector('input[type="text"]'); // 多种选择器匹配密码输入框 const passInput = document.querySelector('#swInput') || document.querySelector('input[type="password"]') || document.querySelector('input[placeholder="密码"]') || document.querySelector('input[placeholder*="密码"]'); if (userInput && passInput) { const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set; setter.call(userInput, config.username); userInput.dispatchEvent(new Event('input', { bubbles: true })); userInput.dispatchEvent(new Event('change', { bubbles: true })); setter.call(passInput, config.password); passInput.dispatchEvent(new Event('input', { bubbles: true })); passInput.dispatchEvent(new Event('change', { bubbles: true })); log('✅ 账号密码已填充'); setTimeout(() => { // 按文字"登录"查找按钮 const buttons = document.querySelectorAll('button, .ant-btn, input[type="submit"], a.btn, .login-btn'); for (const btn of buttons) { const text = (btn.innerText || btn.value || '').trim(); if (text.includes('登录') && !btn.disabled) { btn.click(); log('✅ 已点击登录按钮'); return; } } const submitBtn = document.querySelector('button[type="submit"]'); if (submitBtn && !submitBtn.disabled) { submitBtn.click(); log('✅ 已点击登录按钮'); } else { log('⚠️ 未找到登录按钮'); } }, 800); } else { log('⚠️ 未找到登录输入框'); } }, 1500); } // ============ 控制面板 ============ function createPanel() { if (panel) return; panel = document.createElement('div'); panel.id = 'auto-learn-panel'; panel.style.cssText = ` position:fixed;top:60px;right:16px;z-index:2147483647; width:310px;background:rgba(25,25,35,0.97);color:#fff; border-radius:10px;padding:14px;font-size:13px; font-family:'Microsoft YaHei',sans-serif; box-shadow:0 4px 24px rgba(0,0,0,0.6); max-height:90vh;overflow-y:auto; `; const qbCount = Object.keys(questionBank).length; panel.innerHTML = `
▶ 自动学习助手 v2.3
账号配置(自动保存)
📚 题库已收录 ${qbCount}
启动中...
`; document.body.appendChild(panel); $('#al-save').onclick = () => { config.username = $('#al-username').value.trim(); config.password = $('#al-password').value.trim(); config.autoLogin = $('#al-autologin').checked; config.autoPlay = $('#al-autoplay').checked; config.autoNextChapter = $('#al-autonext').checked; config.autoAnswerTest = $('#al-autoanswer').checked; config.autoClosePopup = $('#al-autoclose').checked; config.muteVideo = $('#al-mute').checked; saveJSON(CFG_KEY, config); log('✅ 配置已保存'); }; $('#al-toggle').onclick = () => { isRunning = !isRunning; $('#al-toggle').textContent = isRunning ? '暂停' : '继续'; $('#al-toggle').style.background = isRunning ? '#4CAF50' : '#ff9800'; }; $('#al-qb').onclick = () => { const entries = Object.entries(questionBank); if (entries.length === 0) { log('题库为空,答题后自动收录'); return; } log(`📚 题库共 ${entries.length} 题:`); entries.slice(0, 10).forEach(([k, v]) => { log(` ${(v.question || k).substring(0, 40)} => ${(v.answer || '?').substring(0, 30)}`); }); if (entries.length > 10) log(` ...还有 ${entries.length - 10} 题`); }; $('#al-min').onclick = () => { panel.innerHTML = '
'; panel.style.cssText = 'position:fixed;top:60px;right:16px;z-index:2147483647;width:40px;height:40px;background:rgba(25,25,35,0.97);border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;'; panel.firstChild.onclick = () => location.reload(); }; } function setStatus(t) { const el = $('#al-status'); if (el) el.textContent = t; } function updateQbCount() { const el = $('#al-qbcount'); if (el) el.textContent = Object.keys(questionBank).length; } // ============ 查找video ============ function findVideo() { let v = $('video'); if (v) return v; for (const f of $$('iframe')) { try { v = f.contentDocument?.querySelector('video'); if (v) return v; } catch (e) {} } return null; } // ============ 自动关弹窗 ============ function closePopups() { if (!isRunning || !config.autoClosePopup) return; let closed = 0; // 自动点击"继续登录"(异地登录提示) for (const el of $$('button, .ant-btn')) { if (!el.offsetParent) continue; const text = (el.innerText || '').trim(); if (text.includes('继续登录') || text.includes('确认登录') || text.includes('知道了')) { el.click(); closed++; log('✅ 自动确认登录'); } } for (const sel of ['.ant-modal-close', '.ant-modal-close-x', '[class*="modal-close"]', '[class*="dialog-close"]', '.anticon-close', '.close-btn', '[class*="popup-close"]', '[aria-label="Close"]']) { for (const el of $$(sel)) { if (el.offsetParent) { el.click(); closed++; } } } for (const el of $$('div, span, button, i')) { if (!el.offsetParent) continue; const txt = (el.innerText || el.textContent || '').trim(); if ((txt === '×' || txt === '✕') && el.offsetWidth <= 60) { el.click(); closed++; } } for (const f of $$('iframe')) { try { for (const sel of ['.ant-modal-close', '.anticon-close']) { for (const el of f.contentDocument?.querySelectorAll(sel) || []) { if (el.offsetParent) { el.click(); closed++; } } } } catch (e) {} } for (const el of $$('.ant-modal-wrap, .ant-modal-mask')) el.style.display = 'none'; if (closed > 0) log(`关闭 ${closed} 个弹窗`); } // ============ 视频播放 ============ function ensurePlaying() { if (!isRunning || !config.autoPlay) return; const v = findVideo(); if (!v) return; if (config.muteVideo) v.muted = true; v.volume = 0; if (v.playbackRate !== config.speed) v.playbackRate = config.speed; if (v.paused || v.ended) { v.play().catch(() => { $('.prism-big-play-btn')?.click(); for (const f of $$('iframe')) { try { f.contentDocument?.querySelector('.prism-big-play-btn')?.click(); } catch(e){} } }); } if (v.duration && !v.ended) { setStatus(`播放 ${fmt(v.currentTime)}/${fmt(v.duration)} (${(v.currentTime/v.duration*100).toFixed(1)}%)`); } if (v.ended) { log('✅ 本节完成'); setTimeout(nextChapter, 2000); } } // ============ 下一节 ============ function nextChapter() { if (!config.autoNextChapter) return; const nt = $('.ant-tabs-tab-next:not(.ant-tabs-tab-btn-disabled)'); if (nt && nt.offsetParent) { log('→ 下一节'); nt.click(); return; } for (const f of $$('iframe')) { try { const b = f.contentDocument?.querySelector('.ant-tabs-tab-next:not(.ant-tabs-tab-btn-disabled)'); if (b && b.offsetParent) { log('→ iframe下一节'); b.click(); return; } } catch (e) {} } for (const el of $$('button, a, .ant-btn')) { if (el.disabled || !el.offsetParent) continue; if (hasText(el, ['下一节','下一章','下一步','继续学习'])) { el.click(); return; } } } // ============ 智能答题 + 题库自学习 ============ function getQuestions() { const questions = []; const blocks = $$('.question-item, .q-item, [class*="question-item"], .ant-list-item'); for (const block of blocks) { const qText = (block.querySelector('.question-title, .q-title, [class*="stem"]')?.innerText || block.innerText || '').trim(); if (!qText || qText.length < 5) continue; const options = []; for (const opt of block.querySelectorAll('.ant-radio-wrapper, .ant-checkbox-wrapper, .option-item, li')) { const t = (opt.innerText || '').trim(); if (t && t.length < 200) options.push({el: opt, text: t}); } if (options.length >= 2) questions.push({el: block, text: qText, options}); } return questions; } async function handleTest() { if (!config.autoAnswerTest || !isRunning) return; const body = document.body.innerText || ''; if (!(body.includes('课后测试') || body.includes('单选题') || body.includes('多选题'))) return; if (testAnswered) { await learnFromResult(); for (const b2 of $$('button, .ant-btn')) { if (b2.disabled || !b2.offsetParent) continue; if (hasText(b2, ['补考','重新考试','再试一次'])) { log('未通过,自动补考'); b2.click(); testAnswered = false; await new Promise(r => setTimeout(r, 3000)); handleTest(); return; } } return; } log('📝 开始智能答题...'); await new Promise(r => setTimeout(r, 2000)); const questions = getQuestions(); if (questions.length === 0) { log('未找到题目块,默认选第一项'); for (const g of $$('.ant-radio-group, .ant-checkbox-group')) { const opt = g.querySelector('.ant-radio-wrapper:not(.ant-radio-wrapper-disabled), .ant-checkbox-wrapper:not(.ant-checkbox-wrapper-disabled)'); if (opt) opt.click(); } } else { let matched = 0, guessed = 0; for (const q of questions) { const qHash = hashText(q.text); const saved = questionBank[qHash]; if (saved && saved.answer) { const opt = q.options.find(o => o.text.includes(saved.answer.substring(0, 15)) || saved.answer.includes(o.text.substring(0, 15))); if (opt) { opt.el.click(); matched++; continue; } } if (q.options[0]) { q.options[0].el.click(); guessed++; } if (!questionBank[qHash]) { questionBank[qHash] = { question: q.text.substring(0, 100), answer: null }; } } saveJSON(QB_KEY, questionBank); updateQbCount(); log(`题库命中 ${matched} 题,猜测 ${guessed} 题`); } await new Promise(r => setTimeout(r, 1000)); for (const btn of $$('button, .ant-btn')) { if (btn.disabled || !btn.offsetParent) continue; if (hasText(btn, ['提交答案','提交','交卷'])) { log('→ 提交试卷'); btn.click(); testAnswered = true; await new Promise(r => setTimeout(r, 3000)); break; } } } async function learnFromResult() { log('📚 分析答题结果,收录正确答案...'); await new Promise(r => setTimeout(r, 2000)); let learned = 0; const resultBlocks = $$('.question-item, .q-item, [class*="result-item"], [class*="analysis"]'); for (const block of resultBlocks) { const qText = (block.querySelector('.question-title, .q-title, [class*="stem"]')?.innerText || block.innerText || '').trim(); if (!qText || qText.length < 5) continue; const qHash = hashText(qText); const correctEl = block.querySelector('.right-answer, .correct, [class*="right"], [class*="correct-answer"]'); if (correctEl) { const ansText = (correctEl.innerText || '').trim(); if (ansText && ansText.length > 1) { questionBank[qHash] = { question: qText.substring(0, 100), answer: ansText.substring(0, 100) }; learned++; } } } if (learned > 0) { saveJSON(QB_KEY, questionBank); updateQbCount(); log(`✅ 题库新增 ${learned} 题,共 ${Object.keys(questionBank).length} 题`); } else { log('未检测到答案解析,下次继续学习'); } } // ============ 主循环 ============ function tick() { if (!isRunning) return; tryAutoLogin(); closePopups(); ensurePlaying(); handleTest(); } // ============ 启动 ============ function start() { createPanel(); keepAlive(); log(`自动学习助手 v2.3 已启动,题库 ${Object.keys(questionBank).length} 题`); log(`当前账号: ${config.username || '未设置'}`); if (location.href.includes('login') && config.username) tryAutoLogin(); tick(); setInterval(tick, 2000); let lastUrl = location.href; new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; testAnswered = false; setTimeout(tick, 3000); } }).observe(document.body, { childList: true, subtree: true }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', start); } else { start(); } })();