// ==UserScript==
// @name 自动点击菜单
// @namespace http://tampermonkey.net/
// @version 1.42
// @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; // 当前网址的根URL
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: 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;
}
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); // 保存数据
}
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); // 添加点击事件监听器
}
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) // 返回自动点击间隔时间
};
}
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 elements = [];
if (this.select.value === 'id') {
elements = Array.from(document.querySelectorAll(`#${this.input.value}`)); // 根据ID获取所有元素
} else if (this.select.value === 'class') {
elements = Array.from(document.getElementsByClassName(this.input.value)); // 根据类名获取所有元素
} else if (this.select.value === 'text') {
elements = this.findElementsByText(this.input.value); // 根据文本获取所有匹配的元素
}
elements.forEach(element => {
if (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实例
})();