Created
November 25, 2025 16:16
-
-
Save mnixry/5cabcbf07d6cb05e9de8a43ab8a8c249 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Auto-Submitter (Multi-Page + 100 Batch Limit) | |
| // @namespace Violentmonkey Scripts | |
| // @match https://match.ichunqiu.com/index* | |
| // @version 1.3 | |
| // @author - | |
| // @description Fetches 5 pages, dedupes, and submits in batches of 100 flags per request | |
| // @grant GM_xmlhttpRequest | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| // ================= CONFIGURATION ================= | |
| const RACE_UUID = "51a20bcf-acad-4ad9-a090-17efd8f5c285"; | |
| // {page} will be replaced dynamically | |
| const API_TEMPLATE = `http://192.168.2.245:8080/api/Races/${RACE_UUID}/flags?page={page}&pageSize=50&status=&search=&exprunid=`; | |
| const MAX_PAGES_BACK = 5; // How many pages to scrape | |
| const BATCH_SIZE = 100; // Max flags per submission | |
| // Selectors | |
| const SEL_DROPDOWN_ITEMS = '.el-select-dropdown__item'; | |
| const SEL_INPUT = '[placeholder="请输入目标答案"]'; | |
| const SEL_SUBMIT_BTN = '.el-overlay button.btn'; | |
| // ================================================= | |
| // State Control | |
| let isRunning = false; | |
| let uiButton = null; | |
| const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); | |
| // --- Helper: Chunk Array --- | |
| function chunkArray(myArray, chunk_size) { | |
| const results = []; | |
| for (let i = 0; i < myArray.length; i += chunk_size) { | |
| results.push(myArray.slice(i, i + chunk_size)); | |
| } | |
| return results; | |
| } | |
| // --- Helper: Fetch --- | |
| function fetchFlags(pageNumber) { | |
| return new Promise((resolve, reject) => { | |
| const targetUrl = API_TEMPLATE.replace('{page}', pageNumber); | |
| GM_xmlhttpRequest({ | |
| method: "GET", | |
| url: targetUrl, | |
| onload: function(response) { | |
| if (response.status >= 200 && response.status < 300) { | |
| try { | |
| const json = JSON.parse(response.responseText); | |
| resolve(json); | |
| } catch (e) { | |
| reject("Failed to parse JSON: " + e.message); | |
| } | |
| } else { | |
| reject("API Error: " + response.statusText); | |
| } | |
| }, | |
| onerror: function(err) { | |
| reject("Network Error: " + err); | |
| } | |
| }); | |
| }); | |
| } | |
| // --- Helper: Set Input Value --- | |
| function setNativeValue(element, value) { | |
| element.value = value; | |
| element.dispatchEvent(new Event('input', { bubbles: true })); | |
| element.dispatchEvent(new Event('change', { bubbles: true })); | |
| } | |
| // --- Main Logic --- | |
| async function processBatch() { | |
| try { | |
| let allItems = []; | |
| // 1. Fetch Data (Pages 1 to MAX_PAGES_BACK) | |
| console.log(`Fetching last ${MAX_PAGES_BACK} pages...`); | |
| for (let p = 1; p <= MAX_PAGES_BACK; p++) { | |
| if (!isRunning) break; | |
| try { | |
| const data = await fetchFlags(p); | |
| if (data.items && Array.isArray(data.items) && data.items.length > 0) { | |
| allItems = allItems.concat(data.items); | |
| } else { | |
| break; // Stop if page is empty | |
| } | |
| await sleep(100); | |
| } catch (e) { | |
| console.error(`Error fetching page ${p}:`, e); | |
| } | |
| } | |
| if (allItems.length === 0) { | |
| console.warn("No flags found in API."); | |
| return; | |
| } | |
| // 2. Deduplicate | |
| const uniqueValues = [...new Set(allItems.map(item => item.value))]; | |
| // 3. Split into chunks of 100 | |
| const flagChunks = chunkArray(uniqueValues, BATCH_SIZE); | |
| console.log(`Loaded ${uniqueValues.length} unique flags. Split into ${flagChunks.length} chunk(s).`); | |
| // 4. Get Dropdown Items | |
| const items = document.querySelectorAll(SEL_DROPDOWN_ITEMS); | |
| if (items.length === 0) { | |
| console.warn("No dropdown items found. Please click the dropdown menu."); | |
| return; | |
| } | |
| // 5. Iterate Dropdown Items | |
| for (let i = 0; i < items.length; i++) { | |
| if (!isRunning) break; | |
| const item = items[i]; | |
| if (item.style.display === 'none') continue; | |
| // A. Click the Challenge/Dropdown Item | |
| console.log(`\n--- Selecting Item ${i + 1}/${items.length} ---`); | |
| item.click(); | |
| await sleep(600); | |
| // B. Iterate Chunks (Submit 100, wait, Submit next 100...) | |
| for (let c = 0; c < flagChunks.length; c++) { | |
| if (!isRunning) break; | |
| const currentChunkStr = flagChunks[c].join('\n'); | |
| console.log(` Submitting Chunk ${c + 1}/${flagChunks.length} (${flagChunks[c].length} flags)...`); | |
| // Find Input | |
| const inputEl = document.querySelector(SEL_INPUT); | |
| if (!inputEl) { | |
| console.error(" Input field not found!"); | |
| continue; | |
| } | |
| // Set Value | |
| setNativeValue(inputEl, currentChunkStr); | |
| await sleep(200); | |
| // Click Submit | |
| const btn = document.querySelector(SEL_SUBMIT_BTN); | |
| if (btn) { | |
| btn.click(); | |
| console.log(" Submit button clicked."); | |
| } else { | |
| console.error(" Submit button not found!"); | |
| } | |
| // Wait for server processing before next chunk or next item | |
| await sleep(10000); | |
| } | |
| } | |
| } catch (err) { | |
| console.error("Batch Error:", err); | |
| } | |
| } | |
| // --- Infinite Loop Manager --- | |
| async function runInfiniteLoop() { | |
| console.log(">>> Auto-Submitter STARTED <<<"); | |
| while (isRunning) { | |
| await processBatch(); | |
| if (isRunning) { | |
| console.log("Cycle finished. Sleeping 2s..."); | |
| await sleep(2000); | |
| } | |
| } | |
| console.log(">>> Auto-Submitter STOPPED <<<"); | |
| updateUIState(false); | |
| } | |
| // --- UI --- | |
| function updateUIState(running) { | |
| if (!uiButton) return; | |
| if (running) { | |
| uiButton.innerText = `Stop (Chunks of ${BATCH_SIZE})`; | |
| uiButton.style.backgroundColor = "#F56C6C"; | |
| } else { | |
| uiButton.innerText = `Start (Chunks of ${BATCH_SIZE})`; | |
| uiButton.style.backgroundColor = "#409EFF"; | |
| } | |
| uiButton.disabled = false; | |
| } | |
| function createUI() { | |
| uiButton = document.createElement('button'); | |
| updateUIState(false); | |
| uiButton.style.position = "fixed"; | |
| uiButton.style.top = "10px"; | |
| uiButton.style.right = "10px"; | |
| uiButton.style.zIndex = "999999"; | |
| uiButton.style.padding = "10px 20px"; | |
| uiButton.style.color = "white"; | |
| uiButton.style.border = "none"; | |
| uiButton.style.borderRadius = "4px"; | |
| uiButton.style.cursor = "pointer"; | |
| uiButton.style.boxShadow = "0 2px 12px 0 rgba(0,0,0,0.1)"; | |
| uiButton.style.fontWeight = "bold"; | |
| uiButton.onclick = function() { | |
| if (isRunning) { | |
| isRunning = false; | |
| uiButton.innerText = "Stopping..."; | |
| uiButton.style.backgroundColor = "#E6A23C"; | |
| uiButton.disabled = true; | |
| } else { | |
| isRunning = true; | |
| updateUIState(true); | |
| runInfiniteLoop(); | |
| } | |
| }; | |
| document.body.appendChild(uiButton); | |
| } | |
| window.addEventListener('load', createUI); | |
| if (document.readyState === 'complete') createUI(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment