// ==UserScript== // @name 自动点击菜单 // @namespace http://tampermonkey.net/ // @version 1.47.1 // @description 自动点击菜单,支持按ID、类名、文本、位置自动点击,可设定执行次数 // @author YuoHira // @license MIT // @match *://*/* // @icon https://www.google.com/s2/favicons?sz=64&domain=github.io // @grant GM_setValue // @grant GM_getValue // @downloadURL none // ==/UserScript== class AutoClickMenu { constructor() { this.currentUrl = window.location.origin; this.autoClickEnabled = GM_getValue(`${this.currentUrl}_autoClickEnabled`, false); this.lastUpdateTime = new Map(); this.menuItems = []; this.init(); } init() { window.onload = () => { this.createStyles(); this.toggleButton = new ToggleButton(this).createElement(); this.menuContainer = this.createMenuContainer(); this.addMenuTitle(this.menuContainer); this.saveButton = this.addButton(this.menuContainer, '保存', 'yuohira-button', (e) => { e.stopPropagation(); this.saveData(); }); this.addButtonElement = this.addButton(this.menuContainer, '+', 'yuohira-button', (e) => { e.stopPropagation(); this.addInputField(); }); this.toggleAutoClickButton = this.addButton(this.menuContainer, this.autoClickEnabled ? '暂停' : '开始', 'yuohira-button', (e) => { e.stopPropagation(); this.autoClickEnabled = !this.autoClickEnabled; this.toggleAutoClickButton.innerText = this.autoClickEnabled ? '暂停' : '开始'; GM_setValue(`${this.currentUrl}_autoClickEnabled`, this.autoClickEnabled); }); this.inputContainer = document.createElement('div'); this.menuContainer.appendChild(this.inputContainer); this.loadSavedData(); this.applyAutoClick(); }; } createStyles() { const style = document.createElement('style'); style.innerHTML = ` .yuohira-button { background-color: #6cb2e8; border: 1px solid #0099cc; color: #fff; border-radius: 5px; padding: 5px 10px; cursor: pointer; font-size: 14px; margin: 5px; box-shadow: 0 0 10px #6cb2e8; } .yuohira-button:hover { background-color: #0099cc; } .yuohira-container { background-color: #b2ebf2; border: 1px solid #0099cc; border-radius: 10px; padding: 10px; box-shadow: 0 0 20px #6cb2e8; display: flex; flex-direction: column; align-items: center; } .yuohira-title { color: #0099cc; font-family: 'Courier New', Courier, monospace; margin-bottom: 10px; } .yuohira-input { border: 1px solid #0099cc; border-radius: 5px; padding: 5px; margin: 5px; background-color: #a0d3e0; color: #0099cc; } .yuohira-toggle-button { background-color: #6cb2e8; border: 1px solid #0099cc; color: #fff; border-radius: 50%; padding: 5px; cursor: pointer; font-size: 14px; width: 30px; height: 30px; position: fixed; top: 10px; right: 10px; z-index: 10001; opacity: 0.5; transition: opacity 0.3s; box-shadow: 0 0 10px #6cb2e8; } .yuohira-toggle-button:hover { opacity: 1; } .yuohira-input-wrapper { display: flex; align-items: center; margin-bottom: 5px; position: relative; padding-bottom: 18px; } .yuohira-progress-bar { height: 5px; position: absolute; bottom: 0; left: 0; background-color: #6cb2e8; clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%); border-radius: 2.5px; } .yuohira-warning { color: #e74c3c; font-size: 12px; position: absolute; left: 0; bottom: -13px; width: 100%; text-align: left; z-index: 2; pointer-events: none; } .yuohira-crosshair-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 99999; pointer-events: auto; background: rgba(0,0,0,0.05); } .yuohira-crosshair-line { position: absolute; background: #e74c3c; z-index: 999999; } .yuohira-crosshair-label { position: absolute; background: #222; color: #fff; font-size: 12px; padding: 2px 6px; border-radius: 3px; z-index: 999999; pointer-events: none; transform: translateY(-150%); } `; document.head.appendChild(style); } createMenuContainer() { const menuContainer = document.createElement('div'); menuContainer.className = 'yuohira-container'; menuContainer.style.position = 'fixed'; menuContainer.style.top = '10px'; menuContainer.style.right = '10px'; menuContainer.style.zIndex = '10000'; menuContainer.style.display = 'none'; document.body.appendChild(menuContainer); menuContainer.addEventListener('click', (e) => { e.stopPropagation(); }); return menuContainer; } addMenuTitle(container) { const menuTitle = document.createElement('h3'); menuTitle.innerText = '自动点击菜单'; menuTitle.className = 'yuohira-title'; container.appendChild(menuTitle); } addButton(container, text, className, onClick) { const button = document.createElement('button'); button.innerText = text; button.className = className; button.addEventListener('click', onClick); container.appendChild(button); return button; } loadSavedData() { const savedData = GM_getValue(this.currentUrl, []); savedData.forEach(item => { this.addInputField(item.type, item.value, item.enabled, item.interval, item.count); }); } saveData() { const data = this.menuItems.map(item => item.getData()); GM_setValue(this.currentUrl, data); } addInputField(type = 'id', value = '', enabled = false, interval = 1000, count = -1) { const menuItem = new MenuItem(type, value, enabled, interval, this, count); this.menuItems.push(menuItem); this.inputContainer.appendChild(menuItem.createElement()); } applyAutoClick() { const autoClick = () => { if (this.autoClickEnabled && this.menuItems.some(item => item.isEnabled())) { const currentTime = Date.now(); this.menuItems.forEach(item => item.autoClick(currentTime, this.lastUpdateTime)); } requestAnimationFrame(autoClick); }; requestAnimationFrame(autoClick); } } class MenuItem { constructor(type, value, enabled, interval, menu, count = -1) { this.type = type; this.value = value; this.enabled = enabled; this.interval = interval; this.menu = menu; this.count = (typeof count === "number" ? count : -1); } createElement() { const MIN_INTERVAL = 1; const inputWrapper = document.createElement('div'); inputWrapper.className = 'yuohira-input-wrapper'; this.select = document.createElement('select'); const optionId = document.createElement('option'); optionId.value = 'id'; optionId.innerText = 'ID'; const optionClass = document.createElement('option'); optionClass.value = 'class'; optionClass.innerText = '类名'; const optionText = document.createElement('option'); optionText.value = 'text'; optionText.innerText = '文本'; const optionPosition = document.createElement('option'); optionPosition.value = 'position'; optionPosition.innerText = '位置'; this.select.appendChild(optionId); this.select.appendChild(optionClass); this.select.appendChild(optionText); this.select.appendChild(optionPosition); this.select.value = this.type; this.select.className = 'yuohira-input'; inputWrapper.appendChild(this.select); this.input = document.createElement('input'); this.input.type = 'text'; this.input.value = this.value; this.input.className = 'yuohira-input'; this.input.placeholder = 'ID/类名/文本/坐标'; inputWrapper.appendChild(this.input); this.selectButton = document.createElement('button'); this.selectButton.innerText = '选取'; this.selectButton.className = 'yuohira-button'; this.selectButton.addEventListener('click', (e) => this.selectElement(e)); inputWrapper.appendChild(this.selectButton); this.select.addEventListener('change', () => { if (this.select.value === 'text') { this.selectButton.disabled = true; this.selectButton.style.backgroundColor = '#d3d3d3'; this.selectButton.style.borderColor = '#a9a9a9'; this.input.placeholder = '请输入文本'; } else if (this.select.value === 'position') { this.selectButton.disabled = false; this.selectButton.style.backgroundColor = ''; this.selectButton.style.borderColor = ''; this.input.placeholder = '点击“选取”后屏幕定位'; } else { this.selectButton.disabled = false; this.selectButton.style.backgroundColor = ''; this.selectButton.style.borderColor = ''; this.input.placeholder = '请输入ID/类名'; } }); if (this.type === 'text') { this.selectButton.disabled = true; this.selectButton.style.backgroundColor = '#d3d3d3'; this.selectButton.style.borderColor = '#a9a9a9'; } this.toggleInputClickButton = document.createElement('button'); this.toggleInputClickButton.className = 'yuohira-button toggle-input-click-button'; this.toggleInputClickButton.innerText = this.enabled ? '暂停' : '开始'; this.toggleInputClickButton.setAttribute('data-enabled', this.enabled); this.toggleInputClickButton.addEventListener('click', (e) => { e.stopPropagation(); const isEnabled = this.toggleInputClickButton.innerText === '开始'; this.toggleInputClickButton.innerText = isEnabled ? '暂停' : '开始'; this.toggleInputClickButton.setAttribute('data-enabled', isEnabled); this.warningMsg && (this.warningMsg.style.display = 'none'); }); inputWrapper.appendChild(this.toggleInputClickButton); const intervalWrapper = document.createElement('div'); intervalWrapper.style.position = 'relative'; intervalWrapper.style.display = 'inline-block'; intervalWrapper.style.width = '100px'; intervalWrapper.style.padding = '0px 140px 0px 0px'; this.intervalInput = document.createElement('input'); this.intervalInput.type = 'number'; this.intervalInput.value = this.interval; this.intervalInput.className = 'yuohira-input'; this.intervalInput.placeholder = '间隔'; this.intervalInput.style.paddingRight = '10px'; this.intervalInput.style.width = '100px'; this.intervalInput.min = MIN_INTERVAL; this.intervalInput.addEventListener('input', () => { let val = parseInt(this.intervalInput.value, 10) || 0; if (val < MIN_INTERVAL) { val = MIN_INTERVAL; this.intervalInput.value = MIN_INTERVAL; } this.interval = val; }); intervalWrapper.appendChild(this.intervalInput); const intervalSuffix = document.createElement('span'); intervalSuffix.innerText = 'ms'; intervalSuffix.style.color = '#0099cc'; intervalSuffix.style.position = 'absolute'; intervalSuffix.style.right = '10px'; intervalSuffix.style.top = '50%'; intervalSuffix.style.transform = 'translateY(-50%)'; intervalSuffix.style.pointerEvents = 'none'; intervalSuffix.style.zIndex = '1'; intervalWrapper.appendChild(intervalSuffix); inputWrapper.appendChild(intervalWrapper); // ==== 新增:执行次数输入框 ==== this.countInput = document.createElement('input'); this.countInput.type = 'number'; this.countInput.value = this.count; this.countInput.className = 'yuohira-input'; this.countInput.style.width = '60px'; this.countInput.style.marginLeft = '8px'; this.countInput.placeholder = '-1为无限'; this.countInput.title = '执行次数,-1为无限'; this.countInput.addEventListener('input', () => { let val = parseInt(this.countInput.value, 10); if (isNaN(val)) val = -1; this.count = val; }); inputWrapper.appendChild(this.countInput); const countLabel = document.createElement('span'); countLabel.innerText = '次'; countLabel.style.color = '#0099cc'; countLabel.style.marginLeft = '2px'; inputWrapper.appendChild(countLabel); // ==== 新增结束 ==== this.progressBar = document.createElement('div'); this.progressBar.className = 'yuohira-progress-bar'; inputWrapper.appendChild(this.progressBar); // 警告信息 this.warningMsg = document.createElement('div'); this.warningMsg.className = 'yuohira-warning'; this.warningMsg.style.display = 'none'; inputWrapper.appendChild(this.warningMsg); const removeButton = document.createElement('button'); removeButton.innerText = '-'; removeButton.className = 'yuohira-button'; removeButton.addEventListener('click', () => { inputWrapper.remove(); this.menu.menuItems = this.menu.menuItems.filter(item => item !== this); }); inputWrapper.appendChild(removeButton); return inputWrapper; } selectElement(event) { event.stopPropagation(); if (this.select.value === 'position') { // 显示全屏十字准星 this.showCrosshairSelector(); return; } document.body.style.cursor = 'crosshair'; this.selectButton.disabled = true; const hoverBox = document.createElement('div'); hoverBox.style.position = 'fixed'; hoverBox.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; hoverBox.style.color = 'white'; hoverBox.style.padding = '5px'; hoverBox.style.borderRadius = '5px'; hoverBox.style.pointerEvents = 'none'; hoverBox.style.zIndex = '10002'; document.body.appendChild(hoverBox); const mouseMoveHandler = (e) => { const elements = document.elementsFromPoint(e.clientX, e.clientY); elements.forEach((el) => { el.style.outline = '2px solid red'; }); document.addEventListener('mouseout', () => { elements.forEach((el) => { el.style.outline = ''; }); }); hoverBox.style.left = `${e.clientX + 10}px`; hoverBox.style.top = `${e.clientY + 10}px`; if (this.select.value === 'id' && elements[0].id) { hoverBox.innerText = `ID: ${elements[0].id}`; } else if (this.select.value === 'class' && elements[0].className) { hoverBox.innerText = `Class: ${elements[0].className}`; } else { hoverBox.innerText = '无ID或类名'; } }; const clickHandler = (e) => { e.stopPropagation(); e.preventDefault(); const selectedElement = e.target; if (this.select.value === 'id' && selectedElement.id) { this.input.value = selectedElement.id; } else if (this.select.value === 'class' && selectedElement.className) { this.input.value = selectedElement.className; } document.body.style.cursor = 'default'; document.removeEventListener('mousemove', mouseMoveHandler); document.removeEventListener('click', clickHandler, true); this.selectButton.disabled = false; document.body.removeChild(hoverBox); }; document.addEventListener('mousemove', mouseMoveHandler); document.addEventListener('click', clickHandler, true); } showCrosshairSelector() { // 创建全屏遮罩和十字准星 const overlay = document.createElement('div'); overlay.className = 'yuohira-crosshair-overlay'; // 横线 const hLine = document.createElement('div'); hLine.className = 'yuohira-crosshair-line'; hLine.style.height = '1px'; hLine.style.width = '100vw'; hLine.style.top = '50%'; hLine.style.left = '0'; hLine.style.background = '#e74c3c'; // 竖线 const vLine = document.createElement('div'); vLine.className = 'yuohira-crosshair-line'; vLine.style.width = '1px'; vLine.style.height = '100vh'; vLine.style.left = '50%'; vLine.style.top = '0'; vLine.style.background = '#e74c3c'; // 坐标显示 const label = document.createElement('div'); label.className = 'yuohira-crosshair-label'; label.innerText = '点击以选取位置'; label.style.left = '50%'; label.style.top = '50%'; overlay.appendChild(hLine); overlay.appendChild(vLine); overlay.appendChild(label); document.body.appendChild(overlay); // 鼠标移动时更新准星位置和坐标 const moveHandler = (e) => { hLine.style.top = `${e.clientY}px`; vLine.style.left = `${e.clientX}px`; label.style.left = `${e.clientX + 10}px`; label.style.top = `${e.clientY + 10}px`; label.innerText = `X: ${e.clientX}, Y: ${e.clientY}`; }; overlay.addEventListener('mousemove', moveHandler); const clickHandler = (e) => { e.stopPropagation(); e.preventDefault(); this.input.value = `${e.clientX},${e.clientY}`; document.body.removeChild(overlay); overlay.removeEventListener('mousemove', moveHandler); overlay.removeEventListener('click', clickHandler); }; overlay.addEventListener('click', clickHandler); } findElementsByText(text) { const elements = document.querySelectorAll('*'); const matchingElements = []; elements.forEach(element => { if (element.textContent.trim() === text) { matchingElements.push(element); } }); return matchingElements; } getData() { return { type: this.select.value, value: this.input.value, enabled: this.toggleInputClickButton.getAttribute('data-enabled') === 'true', interval: parseInt(this.intervalInput.value, 10), count: parseInt(this.countInput.value, 10) }; } isEnabled() { return this.toggleInputClickButton.getAttribute('data-enabled') === 'true'; } simulateMouseClick(element) { const rect = element.getBoundingClientRect(); const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; const opts = { bubbles: true, cancelable: true, clientX: x, clientY: y }; element.dispatchEvent(new PointerEvent('pointerdown', opts)); element.dispatchEvent(new MouseEvent('mousedown', opts)); element.dispatchEvent(new PointerEvent('pointerup', opts)); element.dispatchEvent(new MouseEvent('mouseup', opts)); element.dispatchEvent(new MouseEvent('click', opts)); } autoClick(currentTime, lastUpdateTime) { if (typeof this.count !== 'number') this.count = -1; if (this.count === 0) return; if (!this.isEnabled()) return; const lastTime = lastUpdateTime.get(this) || 0; const elapsedTime = currentTime - lastTime; if (elapsedTime >= this.interval) { let elements = []; let inputVal = (this.input.value || '').trim(); let clicked = false; if (this.select.value === 'id') { if (inputVal) { elements = Array.from(document.querySelectorAll(`#${CSS.escape(inputVal)}`)); } } else if (this.select.value === 'class') { if (inputVal) { elements = Array.from(document.getElementsByClassName(inputVal)); } } else if (this.select.value === 'text') { if (inputVal) { elements = this.findElementsByText(inputVal); } } else if (this.select.value === 'position') { const pos = inputVal.split(','); if (pos.length === 2) { const x = parseInt(pos[0].trim(), 10); const y = parseInt(pos[1].trim(), 10); if (!isNaN(x) && !isNaN(y)) { const el = document.elementFromPoint(x, y); if (el && !this.menu.menuContainer.contains(el)) { elements = [el]; } } } } if (this.select.value !== 'position') { elements.forEach(element => { if (!this.menu.menuContainer.contains(element)) { this.simulateMouseClick(element); clicked = true; } }); } else if (elements.length > 0) { this.simulateMouseClick(elements[0]); clicked = true; } // 点击成功后减少次数 if (clicked && this.count > 0) { this.count--; this.countInput.value = this.count; } // 异常提示处理 if (this.select.value !== 'position') { if (inputVal && elements.length === 0) { this.warningMsg.innerText = '未找到目标元素'; this.warningMsg.style.display = 'block'; } else { this.warningMsg.style.display = 'none'; } } else { this.warningMsg.style.display = 'none'; } this.progressBar.style.width = '100%'; lastUpdateTime.set(this, currentTime); } else { let percent = (1 - elapsedTime / this.interval) * 100; if (percent < 0) percent = 0; if (percent > 100) percent = 100; this.progressBar.style.width = `${percent}%`; } } } class ToggleButton { constructor(menu) { this.menu = menu; } createElement() { const toggleButton = document.createElement('button'); toggleButton.innerText = '>'; toggleButton.className = 'yuohira-toggle-button'; toggleButton.style.width = '15px'; toggleButton.style.height = '15px'; toggleButton.style.fontSize = '10px'; toggleButton.style.textAlign = 'center'; toggleButton.style.lineHeight = '15px'; toggleButton.style.padding = '0'; toggleButton.style.boxSizing = 'border-box'; toggleButton.style.display = 'flex'; toggleButton.style.alignItems = 'center'; toggleButton.style.justifyContent = 'center'; document.body.appendChild(toggleButton); toggleButton.addEventListener('click', (e) => { e.stopPropagation(); if (this.menu.menuContainer.style.display === 'none') { this.menu.menuContainer.style.display = 'block'; toggleButton.innerText = '<'; } else { this.menu.menuContainer.style.display = 'none'; toggleButton.innerText = '>'; } }); return toggleButton; } } (function () { 'use strict'; new AutoClickMenu(); })();