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