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