Greasy Fork 支持简体中文。

Answer2fill (Windows 98 Style with Shadow DOM)

auto-filling answers with Windows 98 style interface using Shadow DOM

// ==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();
  }
})();