// ==UserScript==
// @name Answer2fill (Windows 98 Style with Shadow DOM)
// @namespace http://tampermonkey.net/
// @version 1.2.3
// @description auto-filling answers with Windows 98 style interface using Shadow DOM
// @match https://*/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// ==/UserScript==
(function () {
"use strict";
const GLOBAL = {
fillAnswerDelay: 300,
};
const DEFAULT_SELECTORS = {
"czvtc.cj-edu.com": {
subjectContainer: ".el-container .all_subject>.el-row"
},
"learning.mhtall.com": {
subjectContainer: "#div_item"
},
"168wangxiao.com": {
subjectContainer: ".Answer-area"
},
};
let questions = [];
const SELECTORS = JSON.parse(
GM_getValue("domainSelectors", JSON.stringify(DEFAULT_SELECTORS))
);
const currentDomain = window.location.hostname;
// Windows 98 style CSS
const styles = `
:host {
all: initial;
font-family: 'MS Sans Serif', Arial, sans-serif;
font-size: 12px;
line-height: 1.2;
}
#auto-fill-container {
position: fixed;
top: 10px;
right: 10px;
background-color: #c0c0c0;
border: 2px outset #ffffff;
box-shadow: 2px 2px 0 #000000;
color: #000000;
padding: 3px;
border-radius: 0;
z-index: 9999999;
width: 300px;
}
#bulk-input, #fill-button, .progress-bar, #subjectContainer, #save-selector {
width: calc(100% - 4px);
margin: 2px;
box-sizing: border-box;
outline: none;
}
#bulk-input {
height: 100px;
resize: none;
}
button {
font-family: 'MS Sans Serif', Arial, sans-serif;
background-color: #c0c0c0;
border: 2px outset #ffffff;
padding: 2px 8px;
color: #000000;
cursor: pointer;
}
button:active {
border-style: inset;
}
input[type="text"], textarea {
border: 2px inset #ffffff;
background-color: #ffffff;
padding: 2px;
width: 100%;
}
.win98-tab {
display: inline-block;
padding: 3px 8px;
background-color: #c0c0c0;
border: 2px outset #ffffff;
border-bottom: none;
margin-right: 2px;
cursor: pointer;
}
.win98-tab.active {
background-color: #dfdfdf;
border-style: inset;
border-bottom: none;
}
#tab-content {
border: 2px inset #ffffff;
padding: 10px;
background-color: #dfdfdf;
}
#title-bar {
background: linear-gradient(to right, #000080, #1084d0);
padding: 2px 4px;
margin-bottom: 4px;
color: #ffffff;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
cursor: move;
}
.title-bar-controls {
display: flex;
}
.title-bar-controls button {
width: 16px;
height: 14px;
border: 1px outset #ffffff;
background-color: #c0c0c0;
margin-left: 2px;
font-size: 9px;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
color: #000000;
font-weight: bold;
padding: 0;
}
#status-bar {
border-top: 2px groove #ffffff;
padding: 2px 4px;
font-size: 11px;
margin-top: 4px;
}
.progress-bar {
background-color: #ffffff;
border: 1px inset #808080;
height: 15px;
margin-top: 5px;
}
.progress-bar-fill {
width: 0%;
height: 100%;
background-color: #000080;
transition: width 0.3s ease-in-out;
}
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: #ffffe1;
color: #000;
text-align: center;
border: 1px solid #000;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -60px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
#help-dialog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #c0c0c0;
border: 2px outset #ffffff;
box-shadow: 2px 2px 0 #000000;
padding: 2px;
z-index: 10000;
display: none;
width: 300px;
}
#help-dialog-title {
background: linear-gradient(to right, #000080, #1084d0);
color: #ffffff;
padding: 2px 4px;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
}
#help-content {
padding: 10px;
max-height: 400px;
overflow-y: auto;
}
#help-content h3, #help-content p, #help-content ul, #help-content li {
margin: revert;
padding: revert;
}
#subjectContainer {
width: calc(100% - 4px) !important;
}
#fill-button, #save-selector {
width: calc(100% - 4px);
margin: 2px;
}
`;
function getSelectorsForCurrentDomain() {
return SELECTORS[currentDomain] || null;
}
function createMainInterface() {
const container = document.createElement("div");
container.id = "auto-fill-wrapper";
container.style.position = "fixed";
container.style.top = "10px";
container.style.right = "10px";
container.style.zIndex = "9999999";
document.body.appendChild(container);
const shadow = container.attachShadow({ mode: 'open' });
const styleElement = document.createElement('style');
styleElement.textContent = styles;
shadow.appendChild(styleElement);
const mainContainer = document.createElement('div');
mainContainer.id = 'auto-fill-container';
mainContainer.style.display = GM_getValue("PanelVisible", true) ? "block" : "none";
mainContainer.innerHTML = `
<div id="title-bar">
<span>Auto-Fill Panel</span>
<div class="title-bar-controls">
<button id="help-button" class="tooltip">?<span class="tooltiptext">Help</span></button>
<button id="minimize-button" class="tooltip">_<span class="tooltiptext">Minimize</span></button>
<button id="close-button" class="tooltip">X<span class="tooltiptext">Close</span></button>
</div>
</div>
<div id="panel-content">
<p id="question-count">Detecting questions...</p>
<div id="tab-buttons">
<span id="fill-tab-button" class="win98-tab active tooltip">Fill Answers<span class="tooltiptext">Enter and fill answers</span></span>
<span id="config-tab-button" class="win98-tab tooltip">Configure<span class="tooltiptext">Set up selectors</span></span>
</div>
<div id="tab-content">
<div id="fill-tab">
<textarea id="bulk-input" placeholder="Enter answers (e.g., A,B,C)"></textarea>
<button id="fill-button" class="tooltip">Fill Answers<span class="tooltiptext">Start filling answers</span></button>
<div class="progress-bar">
<div class="progress-bar-fill"></div>
</div>
</div>
<div id="config-tab" style="display:none;">
<div id="selector-inputs"></div>
<button id="save-selector" class="tooltip">Save Configuration<span class="tooltiptext">Save current selectors</span></button>
</div>
</div>
<div id="status-bar">Ready</div>
</div>
`;
shadow.appendChild(mainContainer);
// Help dialog
const helpDialog = document.createElement("div");
helpDialog.id = "help-dialog";
helpDialog.innerHTML = `
<div id="help-dialog-title">
<span>Help</span>
<button id="close-help">X</button>
</div>
<div id="help-content">
<h3>Auto-Fill Panel Help</h3>
<p><strong>Fill Answers Tab:</strong></p>
<ul>
<li>Enter answers in the text area (e.g., A,B,C,D)</li>
<li>Click "Fill Answers" or press Enter to start filling</li>
</ul>
<p><strong>Configure Tab:</strong></p>
<ul>
<li>Enter the selector for question containers</li>
<li>Click "Save Configuration" to apply changes</li>
</ul>
<p><strong>Other Features:</strong></p>
<ul>
<li>Minimize: Collapse the panel to title bar only</li>
<li>Close: Hide the panel (reopen from browser menu)</li>
</ul>
</div>
`;
shadow.appendChild(helpDialog);
addEventListeners(shadow);
updateConfigTab(shadow);
return shadow;
}
function addEventListeners(shadow) {
const mainContainer = shadow.getElementById('auto-fill-container');
mainContainer.addEventListener("click", handleContainerClick);
mainContainer.addEventListener("mousedown", handleContainerMouseDown);
const bulkInput = shadow.getElementById("bulk-input");
bulkInput.addEventListener("keydown", handleBulkInputKeydown);
bulkInput.addEventListener("input", handleBulkInputChange);
shadow.getElementById("close-button").addEventListener("click", togglePanelVisibility);
shadow.getElementById("minimize-button").addEventListener("click", minimizePanel);
shadow.getElementById("help-button").addEventListener("click", toggleHelpDialog);
shadow.getElementById("close-help").addEventListener("click", toggleHelpDialog);
}
const handleContainerClick = (e) => {
const target = e.target;
if (target.id === "fill-tab-button" || target.id === "config-tab-button") {
switchTab(target.id.replace("-tab-button", ""));
} else if (target.id === "save-selector") {
saveSelectors();
} else if (target.id === "fill-button") {
fillAnswers();
}
};
const handleContainerMouseDown = (e) => {
if (e.target.id === "title-bar") {
let offsetX = e.clientX - e.target.getBoundingClientRect().left;
let offsetY = e.clientY - e.target.getBoundingClientRect().top;
function mouseMoveHandler(e) {
const shadow = document.getElementById("auto-fill-wrapper").shadowRoot;
const container = shadow.getElementById("auto-fill-container");
container.style.right = "auto";
container.style.left = `${e.clientX - offsetX}px`;
container.style.top = `${e.clientY - offsetY}px`;
}
function mouseUpHandler() {
document.removeEventListener("mousemove", mouseMoveHandler);
document.removeEventListener("mouseup", mouseUpHandler);
}
document.addEventListener("mousemove", mouseMoveHandler);
document.addEventListener("mouseup", mouseUpHandler);
}
};
const handleBulkInputKeydown = (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
qucikAnswers();
} else if (e.key === "Backspace" || e.key === "Delete") {
e.stopPropagation();
}
};
const handleBulkInputChange = (e) => {
const input = e.target;
input.value = input.value.replace(/[^A-Za-z,\s]/g, '');
};
function switchTab(tabName) {
const shadow = document.getElementById("auto-fill-wrapper").shadowRoot;
shadow.getElementById("fill-tab").style.display = tabName === "fill" ? "block" : "none";
shadow.getElementById("config-tab").style.display = tabName === "config" ? "block" : "none";
shadow.getElementById("fill-tab-button").classList.toggle("active", tabName === "fill");
shadow.getElementById("config-tab-button").classList.toggle("active", tabName === "config");
}
function togglePanelVisibility() {
const container = document.getElementById("auto-fill-wrapper").shadowRoot.getElementById("auto-fill-container");
const newVisibility = container.style.display === "none";
container.style.display = newVisibility ? "block" : "none";
GM_setValue("PanelVisible", newVisibility);
}
function minimizePanel() {
const shadow = document.getElementById("auto-fill-wrapper").shadowRoot;
const panelContent = shadow.getElementById("panel-content");
if (panelContent.style.display === "none") {
panelContent.style.display = "block";
} else {
panelContent.style.display = "none";
}
}
function toggleHelpDialog() {
const shadow = document.getElementById("auto-fill-wrapper").shadowRoot;
const helpDialog = shadow.getElementById("help-dialog");
helpDialog.style.display = helpDialog.style.display === "none" || helpDialog.style.display === "" ? "block" : "none";
}
function updateConfigTab(shadow) {
const currentSelectors = SELECTORS[currentDomain] || DEFAULT_SELECTORS[currentDomain] || {};
shadow.getElementById("selector-inputs").innerHTML = `
<div class="tooltip" style="width:100%;">
<input id="subjectContainer" type="text" value="${currentSelectors.subjectContainer || ""}" placeholder="subjectContainer">
<span class="tooltiptext">Enter the CSS selector for question containers</span>
</div>
`;
}
function saveSelectors() {
const shadow = document.getElementById("auto-fill-wrapper").shadowRoot;
SELECTORS[currentDomain] = {
subjectContainer: shadow.getElementById("subjectContainer").value,
};
GM_setValue("domainSelectors", JSON.stringify(SELECTORS));
detectQuestions();
switchTab("fill");
updateStatusBar("Configuration saved");
}
function updateQuestionCount() {
const shadow = document.getElementById("auto-fill-wrapper").shadowRoot;
const countElement = shadow.getElementById("question-count");
if (countElement) {
countElement.textContent = `Detected questions: ${questions.length}`;
}
}
function detectQuestions() {
const currentSelectors = getSelectorsForCurrentDomain();
if (currentSelectors && currentSelectors.subjectContainer) {
questions = Array.from(
document.querySelectorAll(currentSelectors.subjectContainer)
).filter(
(item) => item.querySelectorAll('input[type="radio"],input[type="checkbox"]').length > 1
);
}
if (questions.length === 0) {
const optionsGroup = new Map();
document.querySelectorAll('input[type="radio"],input[type="checkbox"]').forEach(opt => {
const name = opt.getAttribute("name");
if (name) {
if (!optionsGroup.has(name)) {
optionsGroup.set(name, []);
}
optionsGroup.get(name).push(opt);
}
});
questions = Array.from(optionsGroup.values());
}
updateQuestionCount();
if (questions.length === 0) switchTab("config");
}
function parseAnswers(input) {
return input.replace(/\s/g, "").toUpperCase().split(",");
}
const qucikAnswers = async () => {
const shadow = document.getElementById("auto-fill-wrapper").shadowRoot;
const currentSelectors = getSelectorsForCurrentDomain();
const answers = parseAnswers(shadow.getElementById("bulk-input").value);
const fillPromises = [];
for (let i = 0; i < Math.min(questions.length, answers.length); i++) {
const subject = currentSelectors && currentSelectors.subjectContainer ?
Array.from(document.querySelectorAll(currentSelectors.subjectContainer))
.filter(item => item.querySelectorAll('input[type="radio"],input[type="checkbox"]').length > 0)[i] :
null;
const options = subject ?
subject.querySelectorAll('input[type="radio"],input[type="checkbox"]') :
questions[i];
const answer = answers[i];
fillPromises.push(fillAnswer(options, answer));
}
await Promise.all(fillPromises);
};
const fillAnswers = async () => {
const shadow = document.getElementById("auto-fill-wrapper").shadowRoot;
const currentSelectors = getSelectorsForCurrentDomain();
const answers = parseAnswers(shadow.getElementById("bulk-input").value);
const totalQuestions = Math.min(questions.length, answers.length);
let completedQuestions = 0;
updateStatusBar("Filling answers...");
for (let i = 0; i < totalQuestions; i++) {
const subject = currentSelectors && currentSelectors.subjectContainer ?
Array.from(document.querySelectorAll(currentSelectors.subjectContainer))
.filter(item => item.querySelectorAll('input[type="radio"],input[type="checkbox"]').length > 0)[i] :
null;
const options = subject ?
subject.querySelectorAll('input[type="radio"],input[type="checkbox"]') :
questions[i];
const answer = answers[i];
await fillAnswer(options, answer);
completedQuestions++;
updateProgressBar(completedQuestions / totalQuestions);
}
updateStatusBar("Answers filled successfully");
};
const fillAnswer = async (options, answer) => {
for (const char of answer) {
const optionIndex = char.charCodeAt(0) - 65;
if (optionIndex >= 0 && optionIndex < options.length) {
const option = options[optionIndex];
if (!option.checked) {
const clickTarget = option.style.display === "none" ? option.parentElement : option;
clickTarget.click();
await new Promise(resolve => setTimeout(resolve, GLOBAL.fillAnswerDelay));
}
}
}
};
function updateProgressBar(progress) {
const shadow = document.getElementById("auto-fill-wrapper").shadowRoot;
const progressBarFill = shadow.querySelector('.progress-bar-fill');
progressBarFill.style.width = `${progress * 100}%`;
}
function updateStatusBar(message) {
const shadow = document.getElementById("auto-fill-wrapper").shadowRoot;
const statusBar = shadow.getElementById('status-bar');
statusBar.textContent = message;
}
function init() {
const shadow = createMainInterface();
setTimeout(() => detectQuestions(), 2000);
GM_registerMenuCommand("Auto-Fill Panel", () => {
togglePanelVisibility();
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();