// ==UserScript== // @name 自动点击菜单 // @namespace http://tampermonkey.net/ // @version 1.32 // @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; // 当前网址的根URL this.autoClickEnabled = GM_getValue(`${this.currentUrl}_autoClickEnabled`, false); // 获取自动点击功能是否启用的状态 this.speed = GM_getValue(`${this.currentUrl}_speed`, 1); // 获取保存的速度 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.addSpeedController(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: 10px; } .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; } `; 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; } addSpeedController(container) { const speedController = document.createElement('div'); speedController.className = 'yuohira-input-wrapper'; // 重置按钮 const resetButton = document.createElement('button'); resetButton.innerText = '重置为1'; resetButton.className = 'yuohira-button'; resetButton.onclick = () => { speedInput.value = 1; speedSlider.value = 1; this.speed = 1; GM_setValue(`${this.currentUrl}_speed`, this.speed); }; speedController.appendChild(resetButton); // 滑块 const speedSlider = document.createElement('input'); speedSlider.type = 'range'; speedSlider.min = '0'; speedSlider.max = '100'; speedSlider.value = this.speed; speedSlider.className = 'yuohira-input'; speedSlider.oninput = () => { speedInput.value = speedSlider.value; this.speed = speedSlider.value; GM_setValue(`${this.currentUrl}_speed`, this.speed); }; speedController.appendChild(speedSlider); // 手动输入框 const speedInput = document.createElement('input'); speedInput.type = 'number'; speedInput.min = '0'; speedInput.max = '100'; speedInput.value = this.speed; speedInput.className = 'yuohira-input'; speedInput.oninput = () => { speedSlider.value = speedInput.value; this.speed = speedInput.value; GM_setValue(`${this.currentUrl}_speed`, this.speed); }; speedController.appendChild(speedInput); container.appendChild(speedController); } loadSavedData() { const savedData = GM_getValue(this.currentUrl, []); // 获取保存的数据 savedData.forEach(item => { this.addInputField(item.type, item.value, item.enabled, item.interval); // 为每个保存的数据项添加输入字段 }); } saveData() { const data = this.menuItems.map(item => item.getData()); // 获取所有菜单项的数据 GM_setValue(this.currentUrl, data); // 保存数据 GM_setValue(`${this.currentUrl}_speed`, this.speed); // 保存速度 } addInputField(type = 'id', value = '', enabled = false, interval = 1000) { const menuItem = new MenuItem(type, value, enabled, interval, this); // 创建新的菜单项 this.menuItems.push(menuItem); // 将菜单项添加到菜单项数组中 this.inputContainer.appendChild(menuItem.createElement()); // 将菜单项的元素添加到输入容器中 } applyAutoClick() { const autoClick = () => { if (this.autoClickEnabled) { // 如果自动点击功能启用 const currentTime = Date.now(); // 获取当前时间 this.menuItems.forEach(item => item.autoClick(currentTime, this.lastUpdateTime)); // 为每个菜单项应用自动点击功能 } requestAnimationFrame(autoClick); // 请求下一帧执行autoClick函数 }; requestAnimationFrame(autoClick); // 请求第一帧执行autoClick函数 } } class MenuItem { constructor(type, value, enabled, interval, menu) { this.type = type; // 元素类型(id、class、text) this.value = value; // 元素值 this.enabled = enabled; // 是否启用自动点击 this.interval = interval; // 自动点击间隔时间 this.menu = menu; // 关联的菜单对象 } createElement() { 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 = '文本'; this.select.appendChild(optionId); this.select.appendChild(optionClass); this.select.appendChild(optionText); 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'; 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'; } else { this.selectButton.disabled = false; // 启用选取按钮 this.selectButton.style.backgroundColor = ''; this.selectButton.style.borderColor = ''; } }); 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); // 设置按钮的data-enabled属性 this.toggleInputClickButton.addEventListener('click', (e) => { e.stopPropagation(); const isEnabled = this.toggleInputClickButton.innerText === '开始'; this.toggleInputClickButton.innerText = isEnabled ? '暂停' : '开始'; // 切换按钮文本 this.toggleInputClickButton.setAttribute('data-enabled', isEnabled); // 切换data-enabled属性 }); 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 80px 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 = '30px'; this.intervalInput.style.width = '100%'; 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.progressBar = document.createElement('div'); this.progressBar.className = 'yuohira-progress-bar'; inputWrapper.appendChild(this.progressBar); 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(); 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}`; // 显示元素的ID } else if (this.select.value === 'class' && elements[0].className) { hoverBox.innerText = `Class: ${elements[0].className}`; // 显示元素的类名 } else { hoverBox.innerText = '无ID或类名'; // 显示无ID或类名 } }; const clickHandler = (e) => { e.stopPropagation(); e.preventDefault(); const selectedElement = e.target; if (this.select.value === 'id' && selectedElement.id) { this.input.value = selectedElement.id; // 设置输入框的值为选中元素的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); // 添加点击事件监听器 } findElementByText(text) { const elements = document.querySelectorAll('*'); // 获取所有元素 for (const element of elements) { if (element.textContent.trim() === text) { return element; // 返回匹配文本的元素 } } return null; // 未找到匹配的元素 } getData() { return { type: this.select.value, // 返回元素类型 value: this.input.value, // 返回元素值 enabled: this.toggleInputClickButton.getAttribute('data-enabled') === 'true', // 返回是否启用自动点击 interval: parseInt(this.intervalInput.value, 10) // 返回自动点击间隔时间 }; } autoClick(currentTime, lastUpdateTime) { if (this.toggleInputClickButton.getAttribute('data-enabled') === 'true') { // 如果启用自动点击 const lastTime = lastUpdateTime.get(this) || 0; // 获取上次更新时间 const elapsedTime = currentTime - lastTime; // 计算已过去的时间 if (elapsedTime >= this.interval) { // 如果已过去的时间大于等于间隔时间 let element; if (this.select.value === 'id') { element = document.getElementById(this.input.value); // 根据ID获取元素 } else if (this.select.value === 'class') { element = document.getElementsByClassName(this.input.value)[0]; // 根据类名获取元素 } else if (this.select.value === 'text') { element = this.findElementByText(this.input.value); // 根据文本获取元素 } if (element && typeof element.click === 'function' && !this.menu.menuContainer.contains(element)) { element.click(); // 如果元素存在且其click方法是一个函数,并且该元素不在菜单容器内,则执行点击操作 } this.progressBar.style.width = '100%'; // 将进度条的宽度设置为100% lastUpdateTime.set(this, currentTime); // 更新最后的点击时间为当前时间 } else { this.progressBar.style.width = `${(1 - elapsedTime / this.interval) * 100}%`; // 根据已过去的时间更新进度条的宽度 } } } } 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'; // 设置按钮的内边距为0 toggleButton.style.boxSizing = 'border-box'; // 设置按钮的盒模型为border-box toggleButton.style.display = 'flex'; // 设置按钮的显示方式为flex toggleButton.style.alignItems = 'center'; // 设置按钮的垂直对齐方式为居中 toggleButton.style.justifyContent = 'center'; // 设置按钮的水平对齐方式为居中 document.body.appendChild(toggleButton); // 将按钮添加到文档的body中 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; // 返回创建的按钮元素 } } class InputField { constructor(type, value) { this.type = type; // 保存输入字段的类型 this.value = value; // 保存输入字段的值 } createElement() { const inputWrapper = document.createElement('div'); // 创建一个div元素作为输入字段的容器 inputWrapper.className = 'yuohira-input-wrapper'; // 设置容器的类名 this.select = document.createElement('select'); // 创建一个select元素 const optionId = document.createElement('option'); // 创建一个option元素 optionId.value = 'id'; // 设置option的值为'id' optionId.innerText = 'ID'; // 设置option的文本为'ID' const optionClass = document.createElement('option'); // 创建另一个option元素 optionClass.value = 'class'; // 设置option的值为'class' optionClass.innerText = '类名'; // 设置option的文本为'类名' const optionText = document.createElement('option'); // 创建第三个option元素 optionText.value = 'text'; // 设置option的值为'text' optionText.innerText = '文本'; // 设置option的文本为'文本' this.select.appendChild(optionId); // 将第一个option添加到select中 this.select.appendChild(optionClass); // 将第二个option添加到select中 this.select.appendChild(optionText); // 将第三个option添加到select中 this.select.value = this.type; // 设置select的值为当前输入字段的类型 this.select.className = 'yuohira-input'; // 设置select的类名 inputWrapper.appendChild(this.select); // 将select添加到容器中 this.input = document.createElement('input'); // 创建一个input元素 this.input.type = 'text'; // 设置input的类型为'text' this.input.value = this.value; // 设置input的值为当前输入字段的值 this.input.className = 'yuohira-input'; // 设置input的类名 inputWrapper.appendChild(this.input); // 将input添加到容器中 return inputWrapper; // 返回创建的输入字段容器 } } (function () { 'use strict'; new AutoClickMenu(); // 创建并初始化AutoClickMenu实例 })();