您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Extract text on maxroll.gg with adjustable hierarchy, selector locking, persistent settings, and customizable Ctrl+Letter shortcut.
// ==UserScript== // @name Maxroll Text Extractor with Selector Lock & Shortcut Config // @namespace http://tampermonkey.net/ // @version 2025.07.15.3 // @description Extract text on maxroll.gg with adjustable hierarchy, selector locking, persistent settings, and customizable Ctrl+Letter shortcut. // @author You // @match *://maxroll.gg/* // @grant none // ==/UserScript== (function() { 'use strict'; const wrapperId = "__zhizi_overlay__"; const timerKey = "__zhizi_html_preview_timer__"; const dataSetKey = "__zhizi_found_texts__"; const targetElementKey = "__zhizi_target_element__"; const lastElementKey = "__zhizi_last_extracted__"; const storeKeyLevel = "__zhizi_level_store__"; const storeKeyShortcut = "__zhizi_shortcut_store__"; const storeKeyPosition = "__zhizi_position_store__"; // -- 新增存储位置 const intervalMs = 500; // Utility: Generate a full CSS selector path to uniquely select an element // Source inspired by https://stackoverflow.com/questions/3620116/how-to-get-the-css-path-of-an-element function getCssPath(el) { if (!(el instanceof Element)) return ''; let path = []; while (el && el.nodeType === Node.ELEMENT_NODE) { let selector = el.nodeName.toLowerCase(); if (el.id) { selector += '#' + CSS.escape(el.id); path.unshift(selector); break; // id is unique in page } else { // count siblings with same tag let sibling = el; let nth = 1; while ((sibling = sibling.previousElementSibling) != null) { if (sibling.nodeName.toLowerCase() === selector) nth++; } if (nth > 1) selector += `:nth-of-type(${nth})`; } path.unshift(selector); el = el.parentElement; } return path.join(" > "); } // Utility: Get short selector: tag + (#id or .classes) function getShortSelector(el) { if (!(el instanceof Element)) return ''; const tag = el.tagName.toLowerCase(); if (el.id) return tag + '#' + CSS.escape(el.id); let classNames = Array.from(el.classList) .filter(c => c.trim().length > 0) .map(c => '.' + CSS.escape(c)) .join(''); return tag + classNames; } // Create main UI overlay if not exists let wrapper = document.getElementById(wrapperId); if (!wrapper) { wrapper = document.createElement("div"); wrapper.id = wrapperId; wrapper.style.cssText = ` position: fixed; top: 10px; left: 10px; z-index: 2147483647; background: #1e1e1e; color: #ddd; border: 1px solid #555; box-shadow: 0 0 10px rgba(0,0,0,0.7); padding: 12px; max-width: 440px; width: 420px; max-height: 90vh; overflow: hidden; font-family: monospace; font-size: 14px; border-radius: 8px; user-select: none; `; // Level input box const levelInput = document.createElement("input"); levelInput.type = "number"; levelInput.min = "1"; levelInput.max = "10"; levelInput.placeholder = "Hierarchy Level"; levelInput.style.cssText = ` width: 100%; padding: 6px 8px; margin-bottom: 8px; font-size: 14px; background: #2c2c2c; color: #fff; border: 1px solid #444; border-radius: 4px; `; levelInput.id = "__zhizi_level__"; // Shortcut config container const shortcutContainer = document.createElement("div"); shortcutContainer.style.cssText = ` margin-bottom: 8px; display: flex; align-items: center; gap: 8px; font-size: 14px; color: #ddd; user-select: none; `; // Ctrl checkbox const ctrlCheckbox = document.createElement("input"); ctrlCheckbox.type = "checkbox"; ctrlCheckbox.id = "__zhizi_ctrl_check__"; const ctrlLabel = document.createElement("label"); ctrlLabel.htmlFor = "__zhizi_ctrl_check__"; ctrlLabel.textContent = "Ctrl"; // Letter dropdown (A-Z) const letterSelect = document.createElement("select"); letterSelect.id = "__zhizi_letter_select__"; letterSelect.style.cssText = ` background: #2c2c2c; color: #fff; border: 1px solid #444; border-radius: 4px; padding: 2px 6px; font-size: 14px; user-select: none; `; for(let i=65; i<=90; i++) { const opt = document.createElement("option"); opt.value = String.fromCharCode(i).toLowerCase(); opt.textContent = String.fromCharCode(i); letterSelect.appendChild(opt); } shortcutContainer.appendChild(ctrlCheckbox); shortcutContainer.appendChild(ctrlLabel); shortcutContainer.appendChild(letterSelect); // Selector lock checkbox const lockContainer = document.createElement("div"); lockContainer.style.cssText = "margin-bottom:8px; font-size:14px; user-select:none; color:#ddd;"; const lockCheckbox = document.createElement("input"); lockCheckbox.type = "checkbox"; lockCheckbox.id = "__zhizi_lock_selector__"; const lockLabel = document.createElement("label"); lockLabel.htmlFor = "__zhizi_lock_selector__"; lockLabel.textContent = "Lock selector (stop following mouse)"; lockLabel.style.marginLeft = "6px"; lockContainer.appendChild(lockCheckbox); lockContainer.appendChild(lockLabel); // -- 新增位置选择相关开始 -- // Position select container const positionContainer = document.createElement("div"); positionContainer.style.cssText = ` margin-bottom: 8px; font-size: 14px; color: #ddd; user-select: none; display: flex; align-items: center; gap: 6px; `; const positionLabel = document.createElement("label"); positionLabel.textContent = "Overlay position:"; const positionSelect = document.createElement("select"); positionSelect.id = "__zhizi_position_select__"; positionSelect.style.cssText = ` background: #2c2c2c; color: #fff; border: 1px solid #444; border-radius: 4px; padding: 2px 6px; font-size: 14px; user-select: none; `; const positions = [ {value: "top-left", text: "左上"}, {value: "top-right", text: "右上"}, {value: "bottom-left", text: "左下"}, {value: "bottom-right", text: "右下"}, ]; positions.forEach(pos => { const opt = document.createElement("option"); opt.value = pos.value; opt.textContent = pos.text; positionSelect.appendChild(opt); }); positionContainer.appendChild(positionLabel); positionContainer.appendChild(positionSelect); // -- 新增位置选择相关结束 -- // Selector inputs container const selectorInputs = document.createElement("div"); selectorInputs.style.cssText = ` margin-bottom: 8px; display: flex; flex-direction: column; gap: 4px; `; // Full path selector input (readonly) const fullPathInput = document.createElement("input"); fullPathInput.type = "text"; fullPathInput.readOnly = true; fullPathInput.placeholder = "Full CSS Path Selector (copyable)"; fullPathInput.style.cssText = ` width: 100%; padding: 6px 8px; font-size: 13px; background: #2c2c2c; color: #eee; border: 1px solid #444; border-radius: 4px; user-select: text; `; // Short selector input (readonly) const shortSelectorInput = document.createElement("input"); shortSelectorInput.type = "text"; shortSelectorInput.readOnly = true; shortSelectorInput.placeholder = "Short Selector (tag#id or tag.class)"; shortSelectorInput.style.cssText = ` width: 100%; padding: 6px 8px; font-size: 13px; background: #2c2c2c; color: #eee; border: 1px solid #444; border-radius: 4px; user-select: text; `; selectorInputs.appendChild(fullPathInput); selectorInputs.appendChild(shortSelectorInput); // Output textarea const output = document.createElement("textarea"); output.id = "__zhizi_output__"; output.readOnly = true; output.style.cssText = ` width: 100%; height: 280px; padding: 10px; background: #2a2a2a; color: #f0f0f0; border: 1px solid #444; border-radius: 4px; resize: vertical; white-space: pre-wrap; word-break: break-word; user-select: text; `; wrapper.appendChild(levelInput); wrapper.appendChild(shortcutContainer); wrapper.appendChild(lockContainer); wrapper.appendChild(positionContainer); // -- 新增位置选择容器插入 -- wrapper.appendChild(selectorInputs); wrapper.appendChild(output); document.body.appendChild(wrapper); } // Elements refs const levelInputEl = document.getElementById("__zhizi_level__"); const outputEl = document.getElementById("__zhizi_output__"); const ctrlCheckboxEl = document.getElementById("__zhizi_ctrl_check__"); const letterSelectEl = document.getElementById("__zhizi_letter_select__"); const lockCheckboxEl = document.getElementById("__zhizi_lock_selector__"); const fullPathInputEl = wrapper.querySelector("input[placeholder^='Full CSS']"); const shortSelectorInputEl = wrapper.querySelector("input[placeholder^='Short Selector']"); // 新增位置选择元素引用 const positionSelectEl = document.getElementById("__zhizi_position_select__"); // Load stored values or defaults const storedLevel = parseInt(localStorage.getItem(storeKeyLevel)); if (storedLevel && storedLevel >= 1 && storedLevel <= 10) { levelInputEl.value = storedLevel; } else { levelInputEl.value = "3"; } // Shortcut defaults and load let shortcut = { ctrl: true, letter: "q" }; try { const storedShortcut = JSON.parse(localStorage.getItem(storeKeyShortcut)); if (storedShortcut && typeof storedShortcut.ctrl === "boolean" && /^[a-z]$/.test(storedShortcut.letter)) { shortcut = storedShortcut; } } catch(e) {} ctrlCheckboxEl.checked = shortcut.ctrl; letterSelectEl.value = shortcut.letter; // -- 新增加载位置设置 -- let position = localStorage.getItem(storeKeyPosition); if (!position || !["top-left", "top-right", "bottom-left", "bottom-right"].includes(position)) { position = "top-left"; // 默认左上 } positionSelectEl.value = position; // 根据position调整wrapper样式 function updateWrapperPosition(pos) { wrapper.style.top = ""; wrapper.style.left = ""; wrapper.style.right = ""; wrapper.style.bottom = ""; switch(pos) { case "top-left": wrapper.style.top = "10px"; wrapper.style.left = "10px"; break; case "top-right": wrapper.style.top = "10px"; wrapper.style.right = "10px"; break; case "bottom-left": wrapper.style.bottom = "10px"; wrapper.style.left = "10px"; break; case "bottom-right": wrapper.style.bottom = "10px"; wrapper.style.right = "10px"; break; } } updateWrapperPosition(position); // 监听位置选择变化,保存并更新位置 positionSelectEl.addEventListener("change", () => { const val = positionSelectEl.value; if (["top-left", "top-right", "bottom-left", "bottom-right"].includes(val)) { localStorage.setItem(storeKeyPosition, val); updateWrapperPosition(val); console.log(`[Position] Updated overlay position to ${val}`); } }); // Save and refresh on level change levelInputEl.addEventListener("change", () => { let val = parseInt(levelInputEl.value); if (isNaN(val) || val < 1) val = 1; if (val > 10) val = 10; levelInputEl.value = val; localStorage.setItem(storeKeyLevel, val); location.reload(); }); // Save shortcut on change and update runtime variable function saveShortcut() { shortcut.ctrl = ctrlCheckboxEl.checked; shortcut.letter = letterSelectEl.value; localStorage.setItem(storeKeyShortcut, JSON.stringify(shortcut)); console.log(`[Shortcut] Updated to Ctrl:${shortcut.ctrl} + ${shortcut.letter.toUpperCase()}`); } ctrlCheckboxEl.addEventListener("change", saveShortcut); letterSelectEl.addEventListener("change", saveShortcut); // Initialize globals window[dataSetKey] = window[dataSetKey] || []; window[targetElementKey] = null; window[lastElementKey] = null; // Clear previous timer if any if (window[timerKey]) { clearInterval(window[timerKey]); console.log("[Restarted] Previous timer cleared."); } // Recursive text extraction from element function extractTextRecursively(el, outputSet) { if (!el || !el.childNodes) return; for (const node of el.childNodes) { if (node.nodeType === Node.TEXT_NODE) { const text = node.nodeValue.trim().replace(/\n+/g, '\n'); if (text && !outputSet.includes(text)) { outputSet.push(text); outputEl.value += (outputEl.value ? "\n" : "") + text; console.log(`[Text] ${text}`); } } else if (node.nodeType === Node.ELEMENT_NODE) { extractTextRecursively(node, outputSet); } } } // Track hovered element let hoveredElement = null; document.addEventListener("mousemove", (e) => { if (!lockCheckboxEl.checked) hoveredElement = e.target; }); // Current locked selector path (string) or null let lockedSelectorPath = null; // On lock checkbox manual uncheck -> clear locked selector & resume hover tracking lockCheckboxEl.addEventListener("change", () => { if (!lockCheckboxEl.checked) { lockedSelectorPath = null; // Reset highlight to hovered element if (window[targetElementKey]) { window[targetElementKey].style.outline = ""; window[targetElementKey] = null; } } }); // Highlight element helper function highlightElement(el) { if (!el) return; if (window[targetElementKey] && window[targetElementKey] !== el) { window[targetElementKey].style.outline = ""; } window[targetElementKey] = el; el.style.outline = "2px solid red"; console.log(`[Hover Target] ${el.tagName}`); } // Every 500ms, find element to highlight according to locked selector or hovered + level window[timerKey] = setInterval(() => { let el = null; if (lockCheckboxEl.checked && lockedSelectorPath) { try { el = document.querySelector(lockedSelectorPath); } catch { el = null; } } else { if (!hoveredElement) return; let level = parseInt(levelInputEl.value) || 1; el = hoveredElement; for (let i = 0; i < level && el?.parentElement; i++) { el = el.parentElement; } } if (el && el !== window[targetElementKey]) { highlightElement(el); // Update selector input fields fullPathInputEl.value = getCssPath(el); shortSelectorInputEl.value = getShortSelector(el); } else if (el) { // If element same as current, update selectors anyway (in case changed) fullPathInputEl.value = getCssPath(el); shortSelectorInputEl.value = getShortSelector(el); } }, intervalMs); // Listen for shortcut to extract text from target element, and auto-lock selector if needed document.addEventListener("keydown", (e) => { if ( e.key.toLowerCase() === shortcut.letter && (!shortcut.ctrl || (shortcut.ctrl && e.ctrlKey)) ) { e.preventDefault(); const el = window[targetElementKey]; if (!el) return; // If lock checkbox not checked, auto check and lock selector on current element if (!lockCheckboxEl.checked) { lockCheckboxEl.checked = true; lockedSelectorPath = fullPathInputEl.value || getCssPath(el); localStorage.setItem(storeKeyLevel, levelInputEl.value); // save level for good measure console.log("[Lock] Auto-locked selector on shortcut press"); } if (el !== window[lastElementKey]) { window[dataSetKey] = []; outputEl.value = ""; window[lastElementKey] = el; console.log("[New Element] Cleared output and tracking"); } extractTextRecursively(el, window[dataSetKey]); } }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址