// ==UserScript==
// @name 无用之脚本,没有任何作用
// @version 3.4
// @description 自动填入答案、自动提交、自动翻页,全流程自动化(带悬浮面板+倒计时+日志)
// @author Claude Code With DeepSeek-v4
// @match https://welearn.sflep.com/student/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// ============================================================
// 默认配置
// ============================================================
const DEFAULTS = {
pageDelay: 3,
submitDelay: 1.5,
noAnswerDelay: 1.5,
betweenCycleDelay: 2,
};
function loadConfig() {
try {
var saved = localStorage.getItem('autofill_config');
if (saved) return JSON.parse(saved);
} catch (e) {}
return Object.assign({}, DEFAULTS);
}
function saveConfig(cfg) {
try {
localStorage.setItem('autofill_config', JSON.stringify(cfg));
} catch (e) {}
}
var CONFIG = loadConfig();
// ============================================================
// 日志系统
// ============================================================
var logs = [];
var MAX_LOGS = 80;
function addLog(msg, type) {
type = type || 'info';
var now = new Date();
var time = now.getHours().toString().padStart(2, '0') + ':' +
now.getMinutes().toString().padStart(2, '0') + ':' +
now.getSeconds().toString().padStart(2, '0');
logs.push({ time: time, msg: msg, type: type });
if (logs.length > MAX_LOGS) logs.shift();
renderLogs();
// 同时输出到控制台
var prefix = '[AutoFill]';
if (type === 'ok') console.log(prefix, '✅', msg);
else if (type === 'err') console.error(prefix, '❌', msg);
else if (type === 'warn') console.warn(prefix, '⚠️', msg);
else console.log(prefix, msg);
}
function renderLogs() {
if (!ui.logContainer) return;
var icons = { ok: '✅', err: '❌', warn: '⚠️', info: 'ℹ️', fill: '✍️', nav: '➡️', sub: '📤' };
var html = '';
// 从最新到最旧显示
for (var i = logs.length - 1; i >= 0; i--) {
var l = logs[i];
var icon = icons[l.type] || 'ℹ️';
html += '
' + l.time + ' ' + icon + ' ' + escHtml(l.msg) + '
';
}
ui.logContainer.innerHTML = html || '暂无日志
';
// 自动滚动到顶部(最新)
ui.logContainer.scrollTop = 0;
}
function escHtml(s) {
return s.replace(/&/g, '&').replace(//g, '>');
}
function clearLogs() {
logs = [];
renderLogs();
}
// ============================================================
// 运行时状态
// ============================================================
var STATE = 'idle';
var currentCycle = 0;
var stopRequested = false;
// ============================================================
// 工具函数
// ============================================================
function sleep(ms) {
return new Promise(function(resolve) { setTimeout(resolve, ms); });
}
function cleanAnswer(raw) {
if (!raw) return '';
return raw.replace(/\s+/g, ' ').trim();
}
function getIframeDoc(iframe) {
try {
return iframe.contentDocument || iframe.contentWindow.document;
} catch (e) {
return null;
}
}
function waitForIframeLoad(iframe) {
return new Promise(function(resolve) {
var doc = getIframeDoc(iframe);
if (doc && doc.readyState === 'complete') {
var body = doc.querySelector('body');
if (body && body.innerHTML.trim().length > 0) {
resolve();
return;
}
}
iframe.addEventListener('load', function handler() {
iframe.removeEventListener('load', handler);
resolve();
});
});
}
/**
* 判断页面是否有可操作题目(填空/选择/自评)
*/
function hasAnswers(doc) {
if (!doc) return false;
return doc.querySelectorAll(
'input[data-solution], textarea[data-solution], [data-controltype="choice"] ul[data-itemtype="options"]'
).length > 0;
}
function setupAutoConfirm(iframe) {
try {
var win = iframe.contentWindow;
if (win) {
win['CONFIRM_AUTO_CALL'] = [
{ title: '您确定现在提交吗?', action: true },
{ title: '习题答案只能提交一次', action: true },
{ title: '确认继续提交?', action: true },
];
}
} catch (e) {}
}
async function waitWithCountdown(seconds, icon, prefix, badge) {
var step = (seconds % 1 === 0) ? 1 : 0.5;
for (var remaining = seconds; remaining > 0; remaining -= step) {
if (remaining < step) remaining = step;
updateUI(icon, prefix + remaining.toFixed(1).replace(/\.0$/, '') + 's', badge);
await sleep(step * 1000);
if (stopRequested) break;
}
}
// ============================================================
// 填答案函数
// ============================================================
function clickOption(li) {
var span = li.querySelector('span');
if (span) { span.click(); } else { li.click(); }
li.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
li.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
}
function fillInputs(doc) {
var inputs = doc.querySelectorAll('input[data-solution]');
var count = 0;
inputs.forEach(function(inp) {
var cleaned = cleanAnswer(inp.getAttribute('data-solution'));
if (cleaned) {
inp.value = cleaned;
inp.dispatchEvent(new Event('input', { bubbles: true }));
inp.dispatchEvent(new Event('change', { bubbles: true }));
count++;
}
});
if (count > 0) addLog('填入 ' + count + ' 个 input', 'fill');
return count;
}
function fillTextareas(doc) {
var textareas = doc.querySelectorAll('textarea[data-solution]');
var count = 0;
textareas.forEach(function(ta) {
var cleaned = cleanAnswer(ta.getAttribute('data-solution'));
if (cleaned) {
ta.value = cleaned;
ta.dispatchEvent(new Event('input', { bubbles: true }));
ta.dispatchEvent(new Event('change', { bubbles: true }));
count++;
}
});
if (count > 0) addLog('填入 ' + count + ' 个 textarea', 'fill');
return count;
}
function fillChoices(doc) {
var choices = doc.querySelectorAll('[data-controltype="choice"]');
var correctCount = 0;
var selfAssessCount = 0;
choices.forEach(function(choice) {
var targetLi = choice.querySelector('li[data-solution]');
if (targetLi) {
clickOption(targetLi);
correctCount++;
} else {
var firstLi = choice.querySelector('ul[data-itemtype="options"] li:first-child');
if (firstLi) {
clickOption(firstLi);
selfAssessCount++;
}
}
});
if (correctCount > 0) addLog('选择 ' + correctCount + ' 个正确答案', 'fill');
if (selfAssessCount > 0) addLog('自评选择 ' + selfAssessCount + ' 项(默认第1项)', 'fill');
return correctCount + selfAssessCount;
}
function fillAllAnswers(doc) {
return fillInputs(doc) + fillTextareas(doc) + fillChoices(doc);
}
/**
* 点击 Submit 按钮,并绕过确认弹窗。
* 方法1:设置 data-noconfirm 属性(框架会跳过 confirmEx)
* 方法2:如果弹窗仍然出现,自动点击"是"按钮
* 方法3:CONFIRM_AUTO_CALL 全局数组兜底
*/
function clickSubmit(doc, iframeWin) {
var submitBtn = doc.querySelector('[data-controltype="submit"]');
if (!submitBtn) {
addLog('未找到 Submit 按钮', 'warn');
return false;
}
// 方法1:data-noconfirm 绕过弹窗(adlcontrols_app.js:3783 检查此属性)
submitBtn.setAttribute('data-noconfirm', '');
// 方法3:CONFIRM_AUTO_CALL 全局数组兜底
if (iframeWin) {
try {
iframeWin['CONFIRM_AUTO_CALL'] = [
{ title: '您确定现在提交吗?', action: true },
{ title: '习题答案只能提交一次', action: true },
{ title: '确认继续提交?', action: true },
];
} catch (e) {}
}
submitBtn.click();
addLog('点击 Submit 按钮', 'sub');
// 方法2:延时检测弹窗,自动点击确认(兜底)
setTimeout(function() {
// 检查 iframe 内的弹窗
var btn = doc.querySelector('.layui-layer-btn0');
if (btn) { btn.click(); addLog('自动确认弹窗 (iframe)', 'sub'); return; }
// 检查外层页面的弹窗
btn = document.querySelector('.layui-layer-btn0');
if (btn) { btn.click(); addLog('自动确认弹窗 (outer)', 'sub'); }
}, 400);
return true;
}
// ============================================================
// 悬浮面板 UI
// ============================================================
var ui = {};
function injectUI() {
var style = document.createElement('style');
style.textContent = [
'#autofill-float {',
' position: fixed; bottom: 16px; right: 16px; z-index: 2147483647;',
' font-family: "Microsoft YaHei", "PingFang SC", sans-serif; font-size: 13px;',
' background: rgba(30,30,30,0.92); color: #e0e0e0;',
' border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.45);',
' width: 260px; max-height: 520px; display: flex; flex-direction: column;',
' user-select: none; transition: background 0.3s;',
'}',
'#autofill-bar {',
' display: flex; align-items: center; justify-content: space-between;',
' padding: 8px 12px; cursor: pointer; border-radius: 10px; flex-shrink: 0;',
'}',
'#autofill-bar:hover { background: rgba(255,255,255,0.06); }',
'#autofill-icon { font-size: 16px; margin-right: 6px; }',
'#autofill-msg { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }',
'#autofill-badge {',
' background: #4caf50; color: #fff; font-size: 11px;',
' padding: 1px 7px; border-radius: 10px; margin-left: 8px;',
' display: none;',
'}',
'#autofill-panel {',
' display: none; padding: 4px 12px 12px 12px; border-top: 1px solid rgba(255,255,255,0.1);',
' overflow-y: auto; flex: 1;',
'}',
'#autofill-panel hr { border: none; border-top: 1px solid rgba(255,255,255,0.08); margin: 6px 0; }',
'#autofill-panel .row {',
' display: flex; justify-content: space-between; align-items: center; padding: 5px 0;',
'}',
'#autofill-panel .row span.label { flex: 1; }',
'#autofill-panel .row input[type=number] {',
' width: 52px; padding: 2px 4px; text-align: center;',
' border: 1px solid rgba(255,255,255,0.2); border-radius: 4px;',
' background: rgba(255,255,255,0.08); color: #81c784; font-size: 13px;',
' margin: 0 4px;',
'}',
'#autofill-panel .row input[type=number]:focus {',
' outline: none; border-color: #4caf50;',
'}',
'#autofill-panel .row span.unit { color: #999; font-size: 12px; }',
'#autofill-panel button {',
' width: 100%; padding: 7px 0; margin-top: 4px; border: none; border-radius: 6px;',
' font-size: 13px; cursor: pointer; font-weight: bold;',
'}',
'#autofill-btn-start { background: #4caf50; color: #fff; }',
'#autofill-btn-start:hover { background: #43a047; }',
'#autofill-btn-stop { background: #f44336; color: #fff; }',
'#autofill-btn-stop:hover { background: #e53935; }',
'#autofill-btn-clear { background: rgba(255,255,255,0.1); color: #999; margin-top: 2px; font-weight: normal; }',
'#autofill-btn-clear:hover { background: rgba(255,255,255,0.2); color: #fff; }',
'#autofill-log {',
' max-height: 160px; overflow-y: auto; margin-top: 6px;',
' background: rgba(0,0,0,0.3); border-radius: 6px; padding: 4px 6px;',
' font-size: 11px; line-height: 1.6;',
'}',
'#autofill-log .logline {',
' padding: 2px 0; border-bottom: 1px solid rgba(255,255,255,0.03); white-space: nowrap;',
'}',
'#autofill-log .logtime { color: #666; margin-right: 4px; }',
'#autofill-log::-webkit-scrollbar { width: 4px; }',
'#autofill-log::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 2px; }',
].join('\n');
document.head.appendChild(style);
var keys = ['pageDelay', 'submitDelay', 'noAnswerDelay', 'betweenCycleDelay'];
var labels = ['填答案前等待', '提交后等待', '无答案页等待', '翻页后等待'];
var rows = '';
for (var i = 0; i < keys.length; i++) {
rows += [
'',
' ' + labels[i] + '',
' ',
' 秒',
'
',
].join('\n');
}
var html = [
'',
'
',
' 💤',
' 就绪',
' ',
'
',
'
',
rows,
'
',
'
',
'
',
'
',
'
',
' 📋 日志',
' ',
'
',
'
',
'
',
'
',
].join('\n');
var container = document.createElement('div');
container.innerHTML = html;
document.body.appendChild(container.firstElementChild);
// 缓存元素
ui.float = document.getElementById('autofill-float');
ui.bar = document.getElementById('autofill-bar');
ui.icon = document.getElementById('autofill-icon');
ui.msg = document.getElementById('autofill-msg');
ui.badge = document.getElementById('autofill-badge');
ui.panel = document.getElementById('autofill-panel');
ui.btnStart = document.getElementById('autofill-btn-start');
ui.btnStop = document.getElementById('autofill-btn-stop');
ui.btnClear = document.getElementById('autofill-btn-clear');
ui.logContainer = document.getElementById('autofill-log');
ui.inputs = {};
keys.forEach(function(k) { ui.inputs[k] = document.getElementById('cfg-' + k); });
// 折叠/展开
ui.bar.addEventListener('click', function() {
ui.panel.style.display = ui.panel.style.display === 'none' ? 'block' : 'none';
});
// 输入变更 → 保存
Object.keys(ui.inputs).forEach(function(k) {
ui.inputs[k].addEventListener('input', function() {
var v = parseFloat(this.value);
if (!isNaN(v) && v >= 0) {
CONFIG[k] = v;
saveConfig(CONFIG);
}
});
});
ui.btnStart.addEventListener('click', function(e) { e.stopPropagation(); startAutomation(); });
ui.btnStop.addEventListener('click', function(e) { e.stopPropagation(); stopAutomation(); });
ui.btnClear.addEventListener('click', function(e) { e.stopPropagation(); clearLogs(); });
addLog('面板初始化完成', 'ok');
}
function updateUI(icon, msg, badge) {
if (!ui.icon) return;
ui.icon.textContent = icon || '💤';
ui.msg.textContent = msg || '就绪';
if (badge !== undefined && badge !== null && badge !== '') {
ui.badge.style.display = 'inline-block';
ui.badge.textContent = badge;
} else {
ui.badge.style.display = 'none';
}
}
function setRunningUI(running) {
if (!ui.btnStart) return;
if (running) {
ui.btnStart.style.display = 'none';
ui.btnStop.style.display = 'block';
ui.float.style.background = 'rgba(20,20,20,0.94)';
Object.keys(ui.inputs).forEach(function(k) { ui.inputs[k].disabled = true; });
} else {
ui.btnStart.style.display = 'block';
ui.btnStop.style.display = 'none';
ui.float.style.background = 'rgba(30,30,30,0.92)';
Object.keys(ui.inputs).forEach(function(k) { ui.inputs[k].disabled = false; });
}
}
// ============================================================
// 核心自动循环
// ============================================================
function stopAutomation() {
if (STATE === 'idle') return;
stopRequested = true;
STATE = 'stopping';
updateUI('🛑', '正在停止...');
addLog('用户请求停止', 'warn');
}
async function startAutomation() {
if (STATE === 'running') return;
// 从输入框同步最新配置
Object.keys(ui.inputs).forEach(function(k) {
var v = parseFloat(ui.inputs[k].value);
if (!isNaN(v) && v >= 0) CONFIG[k] = v;
});
saveConfig(CONFIG);
STATE = 'running';
stopRequested = false;
setRunningUI(true);
addLog('===== 自动循环开始 =====', 'ok');
var iframe = document.getElementById('contentFrame');
if (!iframe) {
updateUI('❌', '未找到 contentFrame');
addLog('未找到 contentFrame 元素', 'err');
setRunningUI(false);
STATE = 'idle';
return;
}
var reachedEnd = false;
var origAlert = window.alert;
window.alert = function(msg) {
if (typeof msg === 'string' && msg.indexOf('已到达结尾') !== -1) reachedEnd = true;
origAlert.apply(window, arguments);
};
updateUI('⏳', '等待页面加载...');
addLog('等待初始页面加载', 'info');
await waitForIframeLoad(iframe);
if (stopRequested) { cleanup(origAlert); return; }
currentCycle = 0;
while (!reachedEnd && !stopRequested) {
currentCycle++;
addLog('--- 第 ' + currentCycle + ' 页 ---', 'nav');
await waitWithCountdown(CONFIG.betweenCycleDelay, '⏳', '翻页后等待: ', '#' + currentCycle);
if (stopRequested) break;
var iframeDoc = getIframeDoc(iframe);
if (!iframeDoc) {
updateUI('⚠️', '无法访问 iframe', '#' + currentCycle);
addLog('无法访问 iframe document', 'err');
await sleep(2000);
continue;
}
setupAutoConfirm(iframe);
if (hasAnswers(iframeDoc)) {
addLog('检测到题目,等待 ' + CONFIG.pageDelay + 's', 'info');
await waitWithCountdown(CONFIG.pageDelay, '⏱️', '倒计时: ', '#' + currentCycle);
if (stopRequested) break;
updateUI('✍️', '正在填入答案...', '#' + currentCycle);
var count = fillAllAnswers(iframeDoc);
addLog('共填入/选择 ' + count + ' 项', 'ok');
if (count > 0) {
updateUI('📤', '正在提交...', '#' + currentCycle);
clickSubmit(iframeDoc, iframe.contentWindow);
await waitWithCountdown(CONFIG.submitDelay, '⏱️', '提交后等待: ', '#' + currentCycle);
if (stopRequested) break;
}
} else {
addLog('此页面无题目', 'info');
await waitWithCountdown(CONFIG.noAnswerDelay, '⏭️', '无答案页等待: ', '#' + currentCycle);
if (stopRequested) break;
}
if (reachedEnd) break;
updateUI('➡️', '前往下一页...', '#' + currentCycle);
if (typeof NextSCO === 'function') {
NextSCO();
addLog('调用 NextSCO() 翻页', 'nav');
} else {
updateUI('❌', 'NextSCO 不存在');
addLog('NextSCO 函数不存在,停止', 'err');
break;
}
updateUI('⏳', '等待新页面加载...', '#' + currentCycle);
await waitForIframeLoad(iframe);
}
cleanup(origAlert);
}
function cleanup(origAlert) {
window.alert = origAlert;
STATE = 'idle';
setRunningUI(false);
if (stopRequested) {
updateUI('💤', '已停止', '共' + currentCycle + '页');
addLog('已停止,共处理 ' + currentCycle + ' 页', 'warn');
} else {
updateUI('🏁', '课程结尾', '共' + currentCycle + '页');
addLog('到达课程结尾,共处理 ' + currentCycle + ' 页', 'ok');
}
stopRequested = false;
currentCycle = 0;
}
// ============================================================
// 启动
// ============================================================
if (document.readyState === 'complete') {
injectUI();
} else {
window.addEventListener('load', injectUI);
}
})();