Greasy Fork镜像 支持简体中文。

淘宝自动点击脚本

在指定时间自动点击淘宝网站上的指定元素,可控制开始和停止

  1. // ==UserScript==
  2. // @name 淘宝自动点击脚本
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.6
  5. // @description 在指定时间自动点击淘宝网站上的指定元素,可控制开始和停止
  6. // @author Vincent Ko (https://vincentko.top | https://github.com/forrany)
  7. // @match *://*.taobao.com/*
  8. // @match *://*.tmall.com/*
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_addStyle
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17. // 配置常量
  18. const CONFIG = {
  19. MAX_CLICKS: 20,
  20. MAX_FIND_ATTEMPTS: 100,
  21. FIND_INTERVAL: 100,
  22. STORAGE_KEY: 'autoClickState',
  23. CONTROL_PANEL_ID: 'autoClickControl'
  24. };
  25.  
  26. // 核心状态管理
  27. const State = {
  28. executeTime: GM_getValue('executeTime', ''),
  29. targetSelector: GM_getValue('targetSelector', ''),
  30. isRunning: false,
  31. timer: null,
  32. clickCount: 0,
  33. init() {
  34. const storedState = localStorage.getItem(CONFIG.STORAGE_KEY);
  35. if (storedState) {
  36. const state = JSON.parse(storedState);
  37. this.isRunning = state.isRunning;
  38. this.clickCount = state.clickCount;
  39. }
  40. },
  41. save() {
  42. localStorage.setItem(CONFIG.STORAGE_KEY, JSON.stringify({
  43. isRunning: this.isRunning,
  44. clickCount: this.clickCount
  45. }));
  46. }
  47. };
  48.  
  49. // UI 控制器
  50. const UI = {
  51. init() {
  52. this.injectStyles();
  53. this.createControlPanel();
  54. this.bindEvents();
  55. this.initializeElementPicker();
  56. this.updateStatus();
  57. },
  58.  
  59. injectStyles() {
  60. GM_addStyle(`
  61. #autoClickControl {
  62. position: fixed;
  63. top: 10px;
  64. right: 10px;
  65. z-index: 9999;
  66. background: #fff;
  67. border: 1px solid #ccc;
  68. padding: 15px;
  69. border-radius: 5px;
  70. box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  71. min-width: 300px;
  72. font-family: Arial, sans-serif;
  73. }
  74. #autoClickControl .control-header {
  75. font-weight: bold;
  76. margin-bottom: 10px;
  77. padding-bottom: 5px;
  78. border-bottom: 1px solid #eee;
  79. }
  80. #autoClickControl .control-item {
  81. margin-bottom: 10px;
  82. }
  83. #autoClickControl .control-item label {
  84. display: block;
  85. margin-bottom: 5px;
  86. color: #666;
  87. font-size: 12px;
  88. }
  89. #autoClickControl input {
  90. width: 100%;
  91. padding: 5px;
  92. border: 1px solid #ddd;
  93. border-radius: 3px;
  94. margin-bottom: 5px;
  95. }
  96. #autoClickControl .button-group {
  97. display: flex;
  98. justify-content: space-between;
  99. margin-top: 10px;
  100. }
  101. #autoClickControl button {
  102. padding: 5px 15px;
  103. border: none;
  104. border-radius: 3px;
  105. cursor: pointer;
  106. font-size: 12px;
  107. transition: all 0.3s;
  108. }
  109. #startAutoClick {
  110. background: #1890ff;
  111. color: white;
  112. }
  113. #stopAutoClick {
  114. background: #ff4d4f;
  115. color: white;
  116. }
  117. #startAutoClick:hover {
  118. background: #40a9ff;
  119. }
  120. #stopAutoClick:hover {
  121. background: #ff7875;
  122. }
  123. .status-text {
  124. font-size: 12px;
  125. color: #666;
  126. margin-top: 5px;
  127. text-align: center;
  128. }
  129. #autoClickControl .minimize-btn {
  130. position: absolute;
  131. right: 5px;
  132. top: 5px;
  133. cursor: pointer;
  134. padding: 2px 6px;
  135. background: #f0f0f0;
  136. border-radius: 3px;
  137. font-size: 12px;
  138. }
  139. #autoClickControl.minimized {
  140. min-width: auto;
  141. padding: 5px;
  142. }
  143. #autoClickControl.minimized .control-content {
  144. display: none;
  145. }
  146. `);
  147. },
  148.  
  149. createControlPanel() {
  150. const controlDiv = document.createElement('div');
  151. controlDiv.id = CONFIG.CONTROL_PANEL_ID;
  152. controlDiv.innerHTML = `
  153. <div class="minimize-btn">_</div>
  154. <div class="control-content">
  155. <div class="control-header">自动点击控制面板</div>
  156. <div class="control-item">
  157. <label>执行时间:</label>
  158. <input type="datetime-local" id="executeTimeInput" step="1">
  159. </div>
  160. <div class="control-item">
  161. <label>目标元素 (CSS选择器):</label>
  162. <input type="text" id="targetSelectorInput" placeholder="请输入CSS选择器">
  163. <button id="pickElement" style="width: auto; margin-top: 5px;">选择元素</button>
  164. </div>
  165. <div class="button-group">
  166. <button id="startAutoClick">开始</button>
  167. <button id="stopAutoClick">停止</button>
  168. </div>
  169. <div class="status-text" id="statusText"></div>
  170. </div>
  171. `;
  172. document.body.appendChild(controlDiv);
  173.  
  174. // 初始化输入框值
  175. if (State.executeTime) {
  176. document.getElementById('executeTimeInput').value = State.executeTime.replace(' ', 'T');
  177. }
  178. if (State.targetSelector) {
  179. document.getElementById('targetSelectorInput').value = State.targetSelector;
  180. }
  181. },
  182.  
  183. bindEvents() {
  184. // 最小化功能
  185. const minimizeBtn = document.querySelector('.minimize-btn');
  186. minimizeBtn.addEventListener('click', () => {
  187. const controlDiv = document.getElementById(CONFIG.CONTROL_PANEL_ID);
  188. controlDiv.classList.toggle('minimized');
  189. minimizeBtn.textContent = controlDiv.classList.contains('minimized') ? '+' : '_';
  190. });
  191.  
  192. // 输入框事件
  193. document.getElementById('executeTimeInput').addEventListener('change', function() {
  194. State.executeTime = this.value.replace('T', ' ');
  195. GM_setValue('executeTime', State.executeTime);
  196. UI.updateStatus();
  197. });
  198.  
  199. document.getElementById('targetSelectorInput').addEventListener('change', function() {
  200. State.targetSelector = this.value;
  201. GM_setValue('targetSelector', State.targetSelector);
  202. UI.updateStatus();
  203. });
  204.  
  205. // 控按钮事件
  206. document.getElementById('startAutoClick').addEventListener('click', () => Controller.start());
  207. document.getElementById('stopAutoClick').addEventListener('click', () => Controller.stop());
  208. },
  209.  
  210. updateStatus() {
  211. const statusText = document.getElementById('statusText');
  212. const timeStr = State.executeTime ? new Date(State.executeTime).toLocaleString() : '未设置';
  213. const selectorStr = State.targetSelector || '未设置';
  214. const elements = document.querySelectorAll(State.targetSelector);
  215. const totalElements = elements.length;
  216. statusText.innerHTML = `
  217. 当前状态: ${State.isRunning ? '运行中' : '已停止'}<br>
  218. 执行时间: ${timeStr}<br>
  219. 目标元素: ${selectorStr} (匹配到 ${totalElements} 个元素)<br>
  220. 点击次数: ${State.clickCount}/${CONFIG.MAX_CLICKS * Math.max(1, totalElements)}
  221. `;
  222. },
  223.  
  224. updateButtonState() {
  225. document.getElementById('startAutoClick').disabled = State.isRunning;
  226. document.getElementById('stopAutoClick').disabled = !State.isRunning;
  227. },
  228.  
  229. initializeElementPicker() {
  230. const pickButton = document.getElementById('pickElement');
  231. pickButton.addEventListener('click', () => ElementPicker.enable());
  232. }
  233. };
  234.  
  235. // 点击控制器
  236. const ClickController = {
  237. findAttempts: 0,
  238.  
  239. async performClicks() {
  240. this.findAttempts = 0;
  241. for (let i = 0; i < CONFIG.MAX_CLICKS; i++) {
  242. if (!State.isRunning) return false;
  243. const elements = await this.findElements();
  244. if (!elements) {
  245. Controller.stop();
  246. alert(`未找到目标元素,已尝试 ${CONFIG.MAX_FIND_ATTEMPTS} 次,脚本已停止`);
  247. return false;
  248. }
  249.  
  250. let allElementsProcessed = true;
  251. for (const element of elements) {
  252. if (!State.isRunning) return false;
  253. if (this.checkElementInnerHTML(element)) {
  254. console.log('元素内容为"去使用",跳过点击');
  255. continue;
  256. }
  257. element.click();
  258. State.clickCount++;
  259. console.log(`点击元素 (${State.clickCount}/${CONFIG.MAX_CLICKS * elements.length})`);
  260. await new Promise(resolve => setTimeout(resolve, 10));
  261. if (this.checkElementInnerHTML(element)) {
  262. allElementsProcessed = false;
  263. break;
  264. }
  265. }
  266. UI.updateStatus();
  267. if (allElementsProcessed) {
  268. console.log('所有元素处理完成,停止脚本');
  269. Controller.stop();
  270. alert('所有元素处理完成,脚本已停止');
  271. return false;
  272. }
  273. }
  274. // 达到最大点击次数,停止脚本
  275. if (State.isRunning) {
  276. Controller.stop();
  277. alert('已达到最大点击次数,脚本已停止');
  278. }
  279. return false;
  280. },
  281. async findElements() {
  282. while (this.findAttempts < CONFIG.MAX_FIND_ATTEMPTS) {
  283. const elements = document.querySelectorAll(State.targetSelector);
  284. if (elements.length > 0) {
  285. console.log(`找到 ${elements.length} 个目标元素`);
  286. return elements;
  287. }
  288. this.findAttempts++;
  289. console.log(`未找到目标元素,第 ${this.findAttempts} 次尝试`);
  290. document.getElementById('statusText').innerHTML = `
  291. 当前状态: 查找目标元素中 (${this.findAttempts}/${CONFIG.MAX_FIND_ATTEMPTS})<br>
  292. 目标选择器: ${State.targetSelector}
  293. `;
  294. await new Promise(resolve => setTimeout(resolve, CONFIG.FIND_INTERVAL));
  295. }
  296. return null;
  297. },
  298. checkElementInnerHTML(element) {
  299. return element && element.innerHTML.includes("去使用");
  300. }
  301. };
  302.  
  303. // 元素选择器工具
  304. const ElementPicker = {
  305. isActive: false,
  306. overlay: null,
  307. highlightElement: null,
  308. currentPanel: null,
  309. enable() {
  310. if (this.isActive) return;
  311. this.isActive = true;
  312. const controlPanel = document.getElementById(CONFIG.CONTROL_PANEL_ID);
  313. controlPanel.style.pointerEvents = 'none';
  314.  
  315. this.overlay = this.createOverlay();
  316. this.highlightElement = this.createHighlightElement();
  317.  
  318. document.body.appendChild(this.overlay);
  319. document.body.appendChild(this.highlightElement);
  320.  
  321. this.setupOverlayEvents(controlPanel);
  322. },
  323.  
  324. cleanup() {
  325. if (this.overlay && this.overlay.parentNode) {
  326. this.overlay.parentNode.removeChild(this.overlay);
  327. }
  328. if (this.highlightElement && this.highlightElement.parentNode) {
  329. this.highlightElement.parentNode.removeChild(this.highlightElement);
  330. }
  331. const panels = document.querySelectorAll('div[style*="z-index: 10002"]');
  332. panels.forEach(panel => {
  333. if (panel.parentNode) {
  334. panel.parentNode.removeChild(panel);
  335. }
  336. });
  337. const controlPanel = document.getElementById(CONFIG.CONTROL_PANEL_ID);
  338. if (controlPanel) {
  339. controlPanel.style.pointerEvents = 'auto';
  340. }
  341. this.isActive = false;
  342. this.overlay = null;
  343. this.highlightElement = null;
  344. this.currentPanel = null;
  345.  
  346. document.removeEventListener('keydown', this.handleKeyPress);
  347. },
  348.  
  349. createOverlay() {
  350. const overlay = document.createElement('div');
  351. overlay.style.cssText = `
  352. position: fixed;
  353. top: 0;
  354. left: 0;
  355. width: 100%;
  356. height: 100%;
  357. background: rgba(0, 0, 0, 0.3);
  358. cursor: crosshair;
  359. z-index: 10000;
  360. `;
  361. return overlay;
  362. },
  363.  
  364. createHighlightElement() {
  365. const highlightElement = document.createElement('div');
  366. highlightElement.style.cssText = `
  367. position: fixed;
  368. border: 2px solid #ff0000;
  369. background: rgba(255, 0, 0, 0.2);
  370. pointer-events: none;
  371. z-index: 10001;
  372. display: none;
  373. `;
  374. return highlightElement;
  375. },
  376.  
  377. setupOverlayEvents(controlPanel) {
  378. let hoveredElement = null;
  379.  
  380. this.overlay.addEventListener('mousemove', (e) => {
  381. e.stopPropagation();
  382. this.overlay.style.pointerEvents = 'none';
  383. hoveredElement = document.elementFromPoint(e.clientX, e.clientY);
  384. this.overlay.style.pointerEvents = 'auto';
  385. this.updateHighlight(hoveredElement, controlPanel);
  386. });
  387.  
  388. this.overlay.addEventListener('click', (e) => {
  389. e.preventDefault();
  390. e.stopPropagation();
  391.  
  392. if (hoveredElement && hoveredElement !== this.overlay) {
  393. if (this.currentPanel && this.currentPanel.parentNode) {
  394. this.currentPanel.parentNode.removeChild(this.currentPanel);
  395. }
  396.  
  397. const selectors = this.generateSelectors(hoveredElement);
  398. const panel = this.createSelectorPanel(selectors, hoveredElement, e.clientX, e.clientY);
  399. document.body.appendChild(panel);
  400. this.currentPanel = panel;
  401. }
  402. });
  403.  
  404. document.addEventListener('keydown', this.handleKeyPress);
  405. },
  406.  
  407. updateHighlight(element, controlPanel) {
  408. if (!element || element === this.overlay || element === controlPanel) {
  409. this.highlightElement.style.display = 'none';
  410. return;
  411. }
  412.  
  413. const rect = element.getBoundingClientRect();
  414. this.highlightElement.style.display = 'block';
  415. this.highlightElement.style.top = rect.top + 'px';
  416. this.highlightElement.style.left = rect.left + 'px';
  417. this.highlightElement.style.width = rect.width + 'px';
  418. this.highlightElement.style.height = rect.height + 'px';
  419. },
  420.  
  421. generateSelectors(element) {
  422. const selectors = [];
  423. // 基础选择器
  424. if (element.className) {
  425. const classes = element.className.trim().split(/\s+/);
  426. if (classes[0]) {
  427. selectors.push(`${element.tagName.toLowerCase()}.${classes[0]}`);
  428. }
  429. }
  430. // 带父元素的选择器
  431. let parent = element.parentElement;
  432. if (parent && parent.className) {
  433. const parentClass = parent.className.trim().split(/\s+/)[0];
  434. selectors.push(`.${parentClass} ${selectors[0]}`);
  435. }
  436. // 带couponOuterWrapper的选择器
  437. let wrapper = element.closest('.couponOuterWrapper_88763c3f');
  438. if (wrapper) {
  439. const allWrappers = Array.from(document.querySelectorAll('.couponOuterWrapper_88763c3f'));
  440. const wrapperIndex = allWrappers.indexOf(wrapper);
  441. if (wrapperIndex !== -1) {
  442. selectors.push(`.couponOuterWrapper_88763c3f:nth-of-type(${wrapperIndex + 1}) ${selectors[0]}`);
  443. }
  444. }
  445. return selectors;
  446. },
  447.  
  448. createSelectorPanel(selectors, element, x, y) {
  449. const panel = document.createElement('div');
  450. panel.style.cssText = `
  451. position: fixed;
  452. left: ${x}px;
  453. top: ${y}px;
  454. background: white;
  455. border: 1px solid #ccc;
  456. border-radius: 4px;
  457. padding: 10px;
  458. box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  459. z-index: 10002;
  460. max-width: 300px;
  461. `;
  462. panel.innerHTML = `
  463. <div style="margin-bottom: 8px; font-weight: bold;">请选择合适的选择器:</div>
  464. <div class="selector-list" style="max-height: 200px; overflow-y: auto;"></div>
  465. `;
  466. const selectorList = panel.querySelector('.selector-list');
  467. selectors.forEach(selector => {
  468. const count = document.querySelectorAll(selector).length;
  469. const item = document.createElement('div');
  470. item.style.cssText = `
  471. padding: 5px;
  472. margin: 2px 0;
  473. cursor: pointer;
  474. border-radius: 3px;
  475. background: #f5f5f5;
  476. `;
  477. item.innerHTML = `
  478. <div style="font-size: 12px; color: #666;">匹配到 ${count} 个元素</div>
  479. <div style="word-break: break-all;">${selector}</div>
  480. `;
  481. item.addEventListener('mouseover', () => {
  482. item.style.background = '#e6f7ff';
  483. });
  484. item.addEventListener('mouseout', () => {
  485. item.style.background = '#f5f5f5';
  486. });
  487. item.addEventListener('click', (e) => {
  488. e.stopPropagation();
  489. const targetSelectorInput = document.getElementById('targetSelectorInput');
  490. targetSelectorInput.value = selector;
  491. State.targetSelector = selector;
  492. GM_setValue('targetSelector', selector);
  493. UI.updateStatus();
  494. this.cleanup();
  495. });
  496. selectorList.appendChild(item);
  497. });
  498. return panel;
  499. },
  500.  
  501. handleKeyPress(e) {
  502. if (e.key === 'Escape') {
  503. ElementPicker.cleanup();
  504. }
  505. }
  506. };
  507.  
  508. // 主控制器
  509. const Controller = {
  510. async start() {
  511. if (!State.executeTime || !State.targetSelector) {
  512. alert('请先设置执行时间和目标元素');
  513. return;
  514. }
  515. State.isRunning = true;
  516. State.clickCount = 0;
  517. this.main();
  518. UI.updateStatus();
  519. UI.updateButtonState();
  520. State.save();
  521. },
  522. stop() {
  523. State.isRunning = false;
  524. if (State.timer) {
  525. clearTimeout(State.timer);
  526. State.timer = null;
  527. }
  528. UI.updateStatus();
  529. UI.updateButtonState();
  530. State.save();
  531. },
  532. async main() {
  533. if (!State.executeTime || !State.targetSelector) {
  534. document.getElementById('statusText').textContent = '请先设置执行时间和目标元素';
  535. return;
  536. }
  537.  
  538. const targetTime = new Date(State.executeTime).getTime();
  539. const now = new Date().getTime();
  540.  
  541. if (now < targetTime) {
  542. const delay = targetTime - now;
  543. document.getElementById('statusText').textContent =
  544. `将在 ${Math.floor(delay/1000)} 秒后执行点击操作`;
  545. State.timer = setTimeout(() => this.executeClickSequence(), delay);
  546. } else {
  547. document.getElementById('statusText').textContent = '指定时间已过,立即执行点击操作';
  548. this.executeClickSequence();
  549. }
  550. },
  551.  
  552. async executeClickSequence() {
  553. if (State.isRunning) {
  554. await ClickController.performClicks();
  555. }
  556. }
  557. };
  558.  
  559. // 初始化
  560. function initialize() {
  561. State.init();
  562. UI.init();
  563. if (State.isRunning) {
  564. Controller.main();
  565. }
  566. }
  567.  
  568. initialize();
  569. })();
  570.  

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址