// ==UserScript==
// @name 【自制】问卷星输入答案自动填写
// @namespace http://tampermonkey.net/
// @version 1.0.2
// @description 使用可配置的选择器来适配不同网站,支持复杂的输入格式。
// @match https://lms.ouchn.cn/exam/*
// @match https://ks.wjx.top/vm/mBcE5Ax.aspx
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
// 全局变量和常量
let questions = [];
let isQuestionDetected = false;
const GLOBAL = {
fillAnswerDelay: 300,
debounceDelay: 300,
};
const DEFAULT_SELECTORS = {
subjectContainer: '.exam-subjects > ol > li.subject',
questionText: '.subject-description',
options: '.subject-options input[type="radio"], .subject-options input[type="checkbox"]',
answerElement: '.answer-options',
};
let SELECTORS = {...DEFAULT_SELECTORS};
// 工具函数
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// UI 相关函数
function createMainInterface() {
const container = document.createElement('div');
container.id = 'auto-fill-container';
container.className = 'fixed top-5 right-5 bg-white p-6 rounded-lg shadow-xl w-96 max-w-[90%] transition-all duration-300 ease-in-out';
container.innerHTML = `
`;
document.body.appendChild(container);
// 添加事件监听器
document.getElementById('fillButton').addEventListener('click', fillAnswers);
document.getElementById('clearButton').addEventListener('click', clearInputs);
document.getElementById('pasteButton').addEventListener('click', pasteAndRecognize);
document.getElementById('configButton').addEventListener('click', showSelectorWizard);
document.getElementById('detectButton').addEventListener('click', smartDetectAnswers);
document.getElementById('bulk-input').addEventListener('input', debounce(updateQuestionsPreview, GLOBAL.debounceDelay));
}
function updateQuestionsPreview() {
const bulkInput = document.getElementById('bulk-input');
const questionsPreview = document.getElementById('questions-preview');
const answers = parseAnswers(bulkInput.value);
// 添加自定义样式
if (!document.getElementById('custom-question-preview-style')) {
const style = document.createElement('style');
style.id = 'custom-question-preview-style';
style.textContent = `
.question-row {
transition: all 0.3s ease;
}
.question-row:hover {
transform: translateY(-2px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
`;
document.head.appendChild(style);
}
questionsPreview.innerHTML = questions
.filter(q => q.type !== '未知类型')
.map((q, i) => {
const answer = answers[i] || '-';
const isValid = validateAnswer(answer, q);
const isFilled = answer !== '-';
const backgroundClass = isFilled ? (isValid ? 'bg-green-100' : 'bg-red-100') : 'bg-gray-100';
const answerColorClass = isFilled ? (isValid ? 'text-green-600' : 'text-red-600') : 'text-gray-600';
return `
${createSelectorInput('subjectContainer', '题目容器选择器')}
${createSelectorInput('questionText', '问题文本选择器')}
${createSelectorInput('options', '选项选择器')}
${createSelectorInput('answerElement', '答案元素选择器')}
`;
document.body.appendChild(wizard);
let highlightedElements = [];
let pickerActive = false;
let currentSelectorType = '';
function highlightElements(elements) {
highlightedElements.forEach(el => el.style.outline = '');
highlightedElements = Array.from(elements);
highlightedElements.forEach(el => {
el.style.outline = '2px solid red';
});
}
function clearHighlights() {
highlightedElements.forEach(el => el.style.outline = '');
highlightedElements = [];
}
function testSelector() {
const selector = document.getElementById('selector-input').value;
const elements = document.querySelectorAll(selector);
const results = document.getElementById('selector-results');
results.textContent = `找到 ${elements.length} 个元素`;
highlightElements(elements);
}
function pickElement(e) {
if (!pickerActive) return;
e.preventDefault();
e.stopPropagation();
const path = e.composedPath();
const selector = uniqueSelector(path[0]);
document.getElementById('selector-input').value = selector;
if (currentSelectorType) {
document.getElementById(currentSelectorType).value = selector;
}
testSelector();
pickerActive = false;
}
function uniqueSelector(el) {
if (el.id) return '#' + el.id;
if (el.className) return '.' + el.className.split(' ').join('.');
let selector = el.tagName.toLowerCase();
let siblings = Array.from(el.parentNode.children);
if (siblings.length > 1) {
let index = siblings.indexOf(el) + 1;
selector += `:nth-child(${index})`;
}
return el.parentNode ? uniqueSelector(el.parentNode) + ' > ' + selector : selector;
}
document.getElementById('test-selector').addEventListener('click', testSelector);
document.getElementById('pick-element').addEventListener('click', () => {
pickerActive = true;
showMessage('请点击页面元素以选择', 'info');
});
document.addEventListener('click', pickElement, true);
document.getElementById('save-selector').addEventListener('click', () => {
SELECTORS = {
subjectContainer: document.getElementById('subjectContainer').value,
questionText: document.getElementById('questionText').value,
options: document.getElementById('options').value,
answerElement: document.getElementById('answerElement').value
};
GM_setValue('customSelectors', JSON.stringify(SELECTORS));
clearHighlights();
wizard.remove();
showMessage('选择器配置已保存,正在重新检测题目', 'success');
detectQuestions();
});
document.getElementById('close-wizard').addEventListener('click', () => {
clearHighlights();
wizard.remove();
});
document.getElementById('reset-selectors').addEventListener('click', () => {
SELECTORS = {...DEFAULT_SELECTORS};
['subjectContainer', 'questionText', 'options', 'answerElement'].forEach(id => {
document.getElementById(id).value = DEFAULT_SELECTORS[id];
});
document.getElementById('selector-input').value = '';
clearHighlights();
showMessage('选择器已重置为默认值', 'info');
});
// 为每个选择器输入框添加事件监听器
['subjectContainer', 'questionText', 'options', 'answerElement'].forEach(id => {
document.getElementById(id).addEventListener('focus', () => {
currentSelectorType = id;
document.getElementById('selector-input').value = document.getElementById(id).value;
});
});
}
function createSelectorInput(id, label) {
return `