// ==UserScript==
// @name 淘宝自动点击脚本
// @namespace http://tampermonkey.net/
// @version 0.6
// @description 在指定时间自动点击淘宝网站上的指定元素,可控制开始和停止
// @author Vincent Ko (https://vincentko.top | https://github.com/forrany)
// @match *://*.taobao.com/*
// @match *://*.tmall.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 配置常量
const CONFIG = {
MAX_CLICKS: 20,
MAX_FIND_ATTEMPTS: 100,
FIND_INTERVAL: 100,
STORAGE_KEY: 'autoClickState',
CONTROL_PANEL_ID: 'autoClickControl'
};
// 核心状态管理
const State = {
executeTime: GM_getValue('executeTime', ''),
targetSelector: GM_getValue('targetSelector', ''),
isRunning: false,
timer: null,
clickCount: 0,
init() {
const storedState = localStorage.getItem(CONFIG.STORAGE_KEY);
if (storedState) {
const state = JSON.parse(storedState);
this.isRunning = state.isRunning;
this.clickCount = state.clickCount;
}
},
save() {
localStorage.setItem(CONFIG.STORAGE_KEY, JSON.stringify({
isRunning: this.isRunning,
clickCount: this.clickCount
}));
}
};
// UI 控制器
const UI = {
init() {
this.injectStyles();
this.createControlPanel();
this.bindEvents();
this.initializeElementPicker();
this.updateStatus();
},
injectStyles() {
GM_addStyle(`
#autoClickControl {
position: fixed;
top: 10px;
right: 10px;
z-index: 9999;
background: #fff;
border: 1px solid #ccc;
padding: 15px;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
min-width: 300px;
font-family: Arial, sans-serif;
}
#autoClickControl .control-header {
font-weight: bold;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #eee;
}
#autoClickControl .control-item {
margin-bottom: 10px;
}
#autoClickControl .control-item label {
display: block;
margin-bottom: 5px;
color: #666;
font-size: 12px;
}
#autoClickControl input {
width: 100%;
padding: 5px;
border: 1px solid #ddd;
border-radius: 3px;
margin-bottom: 5px;
}
#autoClickControl .button-group {
display: flex;
justify-content: space-between;
margin-top: 10px;
}
#autoClickControl button {
padding: 5px 15px;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
transition: all 0.3s;
}
#startAutoClick {
background: #1890ff;
color: white;
}
#stopAutoClick {
background: #ff4d4f;
color: white;
}
#startAutoClick:hover {
background: #40a9ff;
}
#stopAutoClick:hover {
background: #ff7875;
}
.status-text {
font-size: 12px;
color: #666;
margin-top: 5px;
text-align: center;
}
#autoClickControl .minimize-btn {
position: absolute;
right: 5px;
top: 5px;
cursor: pointer;
padding: 2px 6px;
background: #f0f0f0;
border-radius: 3px;
font-size: 12px;
}
#autoClickControl.minimized {
min-width: auto;
padding: 5px;
}
#autoClickControl.minimized .control-content {
display: none;
}
`);
},
createControlPanel() {
const controlDiv = document.createElement('div');
controlDiv.id = CONFIG.CONTROL_PANEL_ID;
controlDiv.innerHTML = `
<div class="minimize-btn">_</div>
<div class="control-content">
<div class="control-header">自动点击控制面板</div>
<div class="control-item">
<label>执行时间:</label>
<input type="datetime-local" id="executeTimeInput" step="1">
</div>
<div class="control-item">
<label>目标元素 (CSS选择器):</label>
<input type="text" id="targetSelectorInput" placeholder="请输入CSS选择器">
<button id="pickElement" style="width: auto; margin-top: 5px;">选择元素</button>
</div>
<div class="button-group">
<button id="startAutoClick">开始</button>
<button id="stopAutoClick">停止</button>
</div>
<div class="status-text" id="statusText"></div>
</div>
`;
document.body.appendChild(controlDiv);
// 初始化输入框值
if (State.executeTime) {
document.getElementById('executeTimeInput').value = State.executeTime.replace(' ', 'T');
}
if (State.targetSelector) {
document.getElementById('targetSelectorInput').value = State.targetSelector;
}
},
bindEvents() {
// 最小化功能
const minimizeBtn = document.querySelector('.minimize-btn');
minimizeBtn.addEventListener('click', () => {
const controlDiv = document.getElementById(CONFIG.CONTROL_PANEL_ID);
controlDiv.classList.toggle('minimized');
minimizeBtn.textContent = controlDiv.classList.contains('minimized') ? '+' : '_';
});
// 输入框事件
document.getElementById('executeTimeInput').addEventListener('change', function() {
State.executeTime = this.value.replace('T', ' ');
GM_setValue('executeTime', State.executeTime);
UI.updateStatus();
});
document.getElementById('targetSelectorInput').addEventListener('change', function() {
State.targetSelector = this.value;
GM_setValue('targetSelector', State.targetSelector);
UI.updateStatus();
});
// 控按钮事件
document.getElementById('startAutoClick').addEventListener('click', () => Controller.start());
document.getElementById('stopAutoClick').addEventListener('click', () => Controller.stop());
},
updateStatus() {
const statusText = document.getElementById('statusText');
const timeStr = State.executeTime ? new Date(State.executeTime).toLocaleString() : '未设置';
const selectorStr = State.targetSelector || '未设置';
const elements = document.querySelectorAll(State.targetSelector);
const totalElements = elements.length;
statusText.innerHTML = `
当前状态: ${State.isRunning ? '运行中' : '已停止'}<br>
执行时间: ${timeStr}<br>
目标元素: ${selectorStr} (匹配到 ${totalElements} 个元素)<br>
点击次数: ${State.clickCount}/${CONFIG.MAX_CLICKS * Math.max(1, totalElements)}
`;
},
updateButtonState() {
document.getElementById('startAutoClick').disabled = State.isRunning;
document.getElementById('stopAutoClick').disabled = !State.isRunning;
},
initializeElementPicker() {
const pickButton = document.getElementById('pickElement');
pickButton.addEventListener('click', () => ElementPicker.enable());
}
};
// 点击控制器
const ClickController = {
findAttempts: 0,
async performClicks() {
this.findAttempts = 0;
for (let i = 0; i < CONFIG.MAX_CLICKS; i++) {
if (!State.isRunning) return false;
const elements = await this.findElements();
if (!elements) {
Controller.stop();
alert(`未找到目标元素,已尝试 ${CONFIG.MAX_FIND_ATTEMPTS} 次,脚本已停止`);
return false;
}
let allElementsProcessed = true;
for (const element of elements) {
if (!State.isRunning) return false;
if (this.checkElementInnerHTML(element)) {
console.log('元素内容为"去使用",跳过点击');
continue;
}
element.click();
State.clickCount++;
console.log(`点击元素 (${State.clickCount}/${CONFIG.MAX_CLICKS * elements.length})`);
await new Promise(resolve => setTimeout(resolve, 10));
if (this.checkElementInnerHTML(element)) {
allElementsProcessed = false;
break;
}
}
UI.updateStatus();
if (allElementsProcessed) {
console.log('所有元素处理完成,停止脚本');
Controller.stop();
alert('所有元素处理完成,脚本已停止');
return false;
}
}
// 达到最大点击次数,停止脚本
if (State.isRunning) {
Controller.stop();
alert('已达到最大点击次数,脚本已停止');
}
return false;
},
async findElements() {
while (this.findAttempts < CONFIG.MAX_FIND_ATTEMPTS) {
const elements = document.querySelectorAll(State.targetSelector);
if (elements.length > 0) {
console.log(`找到 ${elements.length} 个目标元素`);
return elements;
}
this.findAttempts++;
console.log(`未找到目标元素,第 ${this.findAttempts} 次尝试`);
document.getElementById('statusText').innerHTML = `
当前状态: 查找目标元素中 (${this.findAttempts}/${CONFIG.MAX_FIND_ATTEMPTS})<br>
目标选择器: ${State.targetSelector}
`;
await new Promise(resolve => setTimeout(resolve, CONFIG.FIND_INTERVAL));
}
return null;
},
checkElementInnerHTML(element) {
return element && element.innerHTML.includes("去使用");
}
};
// 元素选择器工具
const ElementPicker = {
isActive: false,
overlay: null,
highlightElement: null,
currentPanel: null,
enable() {
if (this.isActive) return;
this.isActive = true;
const controlPanel = document.getElementById(CONFIG.CONTROL_PANEL_ID);
controlPanel.style.pointerEvents = 'none';
this.overlay = this.createOverlay();
this.highlightElement = this.createHighlightElement();
document.body.appendChild(this.overlay);
document.body.appendChild(this.highlightElement);
this.setupOverlayEvents(controlPanel);
},
cleanup() {
if (this.overlay && this.overlay.parentNode) {
this.overlay.parentNode.removeChild(this.overlay);
}
if (this.highlightElement && this.highlightElement.parentNode) {
this.highlightElement.parentNode.removeChild(this.highlightElement);
}
const panels = document.querySelectorAll('div[style*="z-index: 10002"]');
panels.forEach(panel => {
if (panel.parentNode) {
panel.parentNode.removeChild(panel);
}
});
const controlPanel = document.getElementById(CONFIG.CONTROL_PANEL_ID);
if (controlPanel) {
controlPanel.style.pointerEvents = 'auto';
}
this.isActive = false;
this.overlay = null;
this.highlightElement = null;
this.currentPanel = null;
document.removeEventListener('keydown', this.handleKeyPress);
},
createOverlay() {
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
cursor: crosshair;
z-index: 10000;
`;
return overlay;
},
createHighlightElement() {
const highlightElement = document.createElement('div');
highlightElement.style.cssText = `
position: fixed;
border: 2px solid #ff0000;
background: rgba(255, 0, 0, 0.2);
pointer-events: none;
z-index: 10001;
display: none;
`;
return highlightElement;
},
setupOverlayEvents(controlPanel) {
let hoveredElement = null;
this.overlay.addEventListener('mousemove', (e) => {
e.stopPropagation();
this.overlay.style.pointerEvents = 'none';
hoveredElement = document.elementFromPoint(e.clientX, e.clientY);
this.overlay.style.pointerEvents = 'auto';
this.updateHighlight(hoveredElement, controlPanel);
});
this.overlay.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (hoveredElement && hoveredElement !== this.overlay) {
if (this.currentPanel && this.currentPanel.parentNode) {
this.currentPanel.parentNode.removeChild(this.currentPanel);
}
const selectors = this.generateSelectors(hoveredElement);
const panel = this.createSelectorPanel(selectors, hoveredElement, e.clientX, e.clientY);
document.body.appendChild(panel);
this.currentPanel = panel;
}
});
document.addEventListener('keydown', this.handleKeyPress);
},
updateHighlight(element, controlPanel) {
if (!element || element === this.overlay || element === controlPanel) {
this.highlightElement.style.display = 'none';
return;
}
const rect = element.getBoundingClientRect();
this.highlightElement.style.display = 'block';
this.highlightElement.style.top = rect.top + 'px';
this.highlightElement.style.left = rect.left + 'px';
this.highlightElement.style.width = rect.width + 'px';
this.highlightElement.style.height = rect.height + 'px';
},
generateSelectors(element) {
const selectors = [];
// 基础选择器
if (element.className) {
const classes = element.className.trim().split(/\s+/);
if (classes[0]) {
selectors.push(`${element.tagName.toLowerCase()}.${classes[0]}`);
}
}
// 带父元素的选择器
let parent = element.parentElement;
if (parent && parent.className) {
const parentClass = parent.className.trim().split(/\s+/)[0];
selectors.push(`.${parentClass} ${selectors[0]}`);
}
// 带couponOuterWrapper的选择器
let wrapper = element.closest('.couponOuterWrapper_88763c3f');
if (wrapper) {
const allWrappers = Array.from(document.querySelectorAll('.couponOuterWrapper_88763c3f'));
const wrapperIndex = allWrappers.indexOf(wrapper);
if (wrapperIndex !== -1) {
selectors.push(`.couponOuterWrapper_88763c3f:nth-of-type(${wrapperIndex + 1}) ${selectors[0]}`);
}
}
return selectors;
},
createSelectorPanel(selectors, element, x, y) {
const panel = document.createElement('div');
panel.style.cssText = `
position: fixed;
left: ${x}px;
top: ${y}px;
background: white;
border: 1px solid #ccc;
border-radius: 4px;
padding: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
z-index: 10002;
max-width: 300px;
`;
panel.innerHTML = `
<div style="margin-bottom: 8px; font-weight: bold;">请选择合适的选择器:</div>
<div class="selector-list" style="max-height: 200px; overflow-y: auto;"></div>
`;
const selectorList = panel.querySelector('.selector-list');
selectors.forEach(selector => {
const count = document.querySelectorAll(selector).length;
const item = document.createElement('div');
item.style.cssText = `
padding: 5px;
margin: 2px 0;
cursor: pointer;
border-radius: 3px;
background: #f5f5f5;
`;
item.innerHTML = `
<div style="font-size: 12px; color: #666;">匹配到 ${count} 个元素</div>
<div style="word-break: break-all;">${selector}</div>
`;
item.addEventListener('mouseover', () => {
item.style.background = '#e6f7ff';
});
item.addEventListener('mouseout', () => {
item.style.background = '#f5f5f5';
});
item.addEventListener('click', (e) => {
e.stopPropagation();
const targetSelectorInput = document.getElementById('targetSelectorInput');
targetSelectorInput.value = selector;
State.targetSelector = selector;
GM_setValue('targetSelector', selector);
UI.updateStatus();
this.cleanup();
});
selectorList.appendChild(item);
});
return panel;
},
handleKeyPress(e) {
if (e.key === 'Escape') {
ElementPicker.cleanup();
}
}
};
// 主控制器
const Controller = {
async start() {
if (!State.executeTime || !State.targetSelector) {
alert('请先设置执行时间和目标元素');
return;
}
State.isRunning = true;
State.clickCount = 0;
this.main();
UI.updateStatus();
UI.updateButtonState();
State.save();
},
stop() {
State.isRunning = false;
if (State.timer) {
clearTimeout(State.timer);
State.timer = null;
}
UI.updateStatus();
UI.updateButtonState();
State.save();
},
async main() {
if (!State.executeTime || !State.targetSelector) {
document.getElementById('statusText').textContent = '请先设置执行时间和目标元素';
return;
}
const targetTime = new Date(State.executeTime).getTime();
const now = new Date().getTime();
if (now < targetTime) {
const delay = targetTime - now;
document.getElementById('statusText').textContent =
`将在 ${Math.floor(delay/1000)} 秒后执行点击操作`;
State.timer = setTimeout(() => this.executeClickSequence(), delay);
} else {
document.getElementById('statusText').textContent = '指定时间已过,立即执行点击操作';
this.executeClickSequence();
}
},
async executeClickSequence() {
if (State.isRunning) {
await ClickController.performClicks();
}
}
};
// 初始化
function initialize() {
State.init();
UI.init();
if (State.isRunning) {
Controller.main();
}
}
initialize();
})();