// ==UserScript==
// @name 全国生态环境执法培训云课堂-自动学习助手
// @namespace https://onlinelearning.21tb.com
// @version 2.4.2
// @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
// @tag 自动学习
// @tag 在线教育
// @tag 培训
// ==/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;
let errorCount = 0;
let lastErrorTime = 0;
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.4.2
账号配置(自动保存)
📚 题库已收录 ${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"]',
'[class*="translation"] [class*="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++; }
}
// iframe内弹窗
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); }
// 检查是否还有下一节,没有的话切换到下一门课
if (v.ended) {
setTimeout(() => {
const hasNext = $('.ant-tabs-tab-next:not(.ant-tabs-tab-btn-disabled)');
if (!hasNext) {
log('→ 本课程已全部学完');
setTimeout(nextCourse, 3000);
}
}, 3000);
}
}
// ============ 下一节 ============
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 nextCourse() {
// 检查是否显示"课程不存在"
if (document.body.innerText.includes('课程不存在')) {
log('⚠️ 课程不存在,返回首页重新选择');
location.href = 'https://onlinelearning.21tb.com/rtr-frontend/student/index';
return;
}
log('→ 返回课程中心,准备学习下一门课');
setTimeout(() => {
// 回到首页
location.href = 'https://onlinelearning.21tb.com/rtr-frontend/student/index';
setTimeout(() => {
// 点击"我的课程"菜单
const menuItems = $$('.ant-menu-item, .ant-menu-submenu-title, .menu-item');
for (const item of menuItems) {
if ((item.innerText || '').includes('我的课程')) {
item.click();
log('→ 进入我的课程');
break;
}
}
setTimeout(() => {
// 找第一个未完成的课程卡片
const cards = $$('.div-container.mixins-action, .course-card, .card-item');
let found = false;
for (const card of cards) {
const text = card.innerText || '';
// 找进度不是100%的课程
if (!text.includes('100%') && !text.includes('已完成') && card.offsetParent) {
log('→ 找到下一门课程,点击进入');
// 点击卡片内的"继续学习"或"开始学习"按钮
const btn = card.querySelector('button, a, .ant-btn');
if (btn) {
btn.click();
} else {
card.click();
}
found = true;
return;
}
}
if (!found) {
log('✅ 所有课程都已学完!');
}
}, 5000);
}, 3000);
}, 2000);
}
// ============ 智能答题 + 题库自学习 ============
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;
// 错误次数太多,暂停1分钟再试
if (errorCount >= 5) {
const now = Date.now();
if (now - lastErrorTime < 60000) {
setStatus(`⚠️ 错误过多,暂停中... (${Math.ceil((60000 - (now - lastErrorTime)) / 1000)}s)`);
return;
} else {
errorCount = 0;
log('🔄 错误计数已重置,继续学习');
}
}
try {
// 检查是否显示"课程不存在",自动返回
if (document.body.innerText.includes('课程不存在')) {
log('⚠️ 检测到课程不存在,返回首页');
location.href = 'https://onlinelearning.21tb.com/rtr-frontend/student/index';
return;
}
tryAutoLogin();
closePopups();
ensurePlaying();
handleTest();
errorCount = Math.max(0, errorCount - 1); // 成功一次减少错误计数
} catch (e) {
errorCount++;
lastErrorTime = Date.now();
log(`⚠️ 出错: ${e.message} (错误次数: ${errorCount})`);
}
}
// ============ 启动 ============
function start() {
createPanel();
keepAlive();
log(`自动学习助手 v2.4.2 已启动,题库 ${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();
}
})();