网页基于xpath|文本的自动点击(优化版)

根据输入的XPath定位元素进行循环点击及按指定文本查找点击元素,优化了遍历框架查找元素、点击停止功能、执行顺序及延时机制,设置成分区形式,美化按钮样式,并添加每次点击时间显示功能,同时添加脚本保活机制。

  1. // ==UserScript==
  2. // @name 网页基于xpath|文本的自动点击(优化版)
  3. // @namespace https://game.lpengine.cn/*
  4. // @version 1.0.2
  5. // @description 根据输入的XPath定位元素进行循环点击及按指定文本查找点击元素,优化了遍历框架查找元素、点击停止功能、执行顺序及延时机制,设置成分区形式,美化按钮样式,并添加每次点击时间显示功能,同时添加脚本保活机制。
  6. // @author toyourtomorrow
  7. // @match https://fz.lpengine.cn/*
  8. // @match https://game.lpengine.cn/*
  9. // @match *://*/*
  10. // @grant GM_setValue
  11. // @grant GM.getValue
  12. // @grant GM_addStyle
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17.  
  18. // 创建用于输入XPath、延时、指定文本以及显示状态的HTML元素,并设置样式使其在最上方显示,同时设置初始为可拖动状态,设置分区形式
  19. const inputDiv = document.createElement('div');
  20. inputDiv.style.position = 'fixed';
  21. inputDiv.style.top = '10px';
  22. inputDiv.style.left = '10px';
  23. inputDiv.style.zIndex = '9999';
  24. inputDiv.style.backgroundColor = '#f8f9fa';
  25. inputDiv.style.padding = '10px';
  26. inputDiv.style.borderRadius = '5px';
  27. inputDiv.style.boxShadow = '0 0 5px rgba(0, 0, 0, 0.2)';
  28. inputDiv.style.userSelect = 'none'; // 防止文本被误选
  29. inputDiv.draggable = true; // 设置可拖动
  30.  
  31. inputDiv.innerHTML = `
  32. <div style="display: flex; flex-direction: column;">
  33. <!-- XPath 分区 -->
  34. <div style="background-color: #e9ecef; padding: 10px; border-radius: 5px; margin-bottom: 10px;">
  35. <div style="display: flex; align-items: center; margin-bottom: 10px;">
  36. <label for="xpathInput" style="margin-right: 10px;">XPath表达式:</label>
  37. <input type="text" id="xpathInput" style="padding: 5px; border: 1px solid #ced4da; border-radius: 3px; width: 300px;" />
  38. </div>
  39. <div style="display: flex; align-items: center; margin-bottom: 10px;">
  40. <label for="xpathDelayInput" style="margin-right: 10px;">XPath延时(毫秒):</label>
  41. <input type="number" id="xpathDelayInput" style="padding: 5px; border: 1px solid #ced4da; border-radius: 3px; width: 100px;" value="300" />
  42. </div>
  43. <div style="display: flex; align-items: center;">
  44. <button id="startXPathButton" style="padding: 5px 10px; border: none; border-radius: 3px; background-color: #007bff; color: white; cursor: pointer;">开始点击(XPath)</button>
  45. <button id="stopXPathButton" style="padding: 5px 10px; border: none; border-radius: 3px; background-color: #dc3545; color: white; cursor: pointer;">停止点击(XPath)</button>
  46. </div>
  47. <span id="xpathStatus" style="margin-left: 10px; color: #6c757d; margin-top: 10px;">状态: 未开始</span>
  48. <span id="xpathLastClickTime" style="margin-left: 10px; color: #6c757d; margin-top: 5px;">上次点击时间(XPath): -</span>
  49. <span id="xpathNextClickTime" style="margin-left: 10px; color: 6c757d; margin-top: 5px;">下次点击时间(XPath): -</span>
  50. </div>
  51. <!-- 文本分区 -->
  52. <div style="background-color: #e9ecef; padding: 10px; border-radius: 5px;">
  53. <div style="display: flex; align-items: center; margin-bottom: 10px;">
  54. <label for="textInput" style="margin-right: 10px;">指定文本:</label>
  55. <input type="text" id="textInput" style="padding: 5px; border: 1px solid #ced4da; border-radius: 3px; width: 200px;" />
  56. </div>
  57. <div style="display: flex; align-items: center; margin-bottom: 10px;">
  58. <label for="textDelayInput" style="margin-right: 10px;">文本延时(毫秒):</label>
  59. <input type="number" id="textDelayInput" style="padding: 5px; border: 1px solid #ced4da; border-radius: 3px; width: 100px;" value="300" />
  60. </div>
  61. <div style="display: flex; align-items: center;">
  62. <button id="startTextButton" style="padding: 5px 10px; border: none; border-radius: 3px; background-color: #28a745; color: white; cursor: pointer;">开始点击(文本)</button>
  63. <button id="stopTextButton" style="padding: 5px 10px; border: none; border-radius: 3px; background-color: #6c757d; color: white; cursor: pointer;">停止点击(文本)</button>
  64. </div>
  65. <span id="textStatus" style="margin-left: 10px; color: #6c757d; margin-top: 10px;">状态: 未开始</span>
  66. <span id="textLastClickTime" style="margin-left: 10px; color: #6c757d; margin-top: 5px;">上次点击时间(文本): -</span>
  67. <span id="textNextClickTime" style="margin-left: 10px; color: 6c757d; margin-top: 5px;">下次点击时间(文本): -</span>
  68. </div>
  69. <button id="closeButton" style="padding: 5px 10px; border: none; border-radius: 3px; background-color: #6c757d; color: white; cursor: pointer; margin-top: 15px;">关闭</button>
  70. <button id="minimizeButton" style="padding: 5px 10px; border: none; border-radius: 3px; background-color: #6c757d; color: white; cursor: pointer; margin-top: 5px;">缩小</button>
  71. </div>
  72. `;
  73. document.body.appendChild(inputDiv);
  74.  
  75. // 用于记录鼠标按下时的坐标,实现拖动功能
  76. let startX, startY;
  77.  
  78. // 为可拖动元素添加鼠标按下、移动和抬起事件监听器,正确实现拖动功能
  79. inputDiv.addEventListener('mousedown', function (e) {
  80. startX = e.clientX;
  81. startY = e.clientY;
  82. document.addEventListener('mousemove', drag);
  83. document.addEventListener('mouseup', stopDrag);
  84. });
  85.  
  86. function drag(e) {
  87. const dx = e.clientX - startX;
  88. const dy = e.clientY - startY;
  89. inputDiv.style.left = (parseInt(inputDiv.style.left) || 0) + dx + 'px';
  90. inputDiv.style.top = (parseInt(inputDiv.style.top) || 0) + dy + 'px';
  91. startX = e.clientX;
  92. startY = e.clientY;
  93. }
  94.  
  95. function stopDrag() {
  96. document.removeEventListener('mousemove', drag);
  97. document.removeEventListener('mouseup', stopDrag);
  98. }
  99.  
  100. // 记录界面是否最小化
  101. let isMinimized = false;
  102.  
  103. // 缩小按钮点击事件处理函数
  104. function minimize() {
  105. if (isMinimized) {
  106. // 如果已最小化,恢复原始大小,并重新显示文字相关元素
  107. inputDiv.style.width = 'auto';
  108. inputDiv.style.height = 'auto';
  109. inputDiv.querySelectorAll('span').forEach(span => span.style.display = '');
  110. isMinimized = false;
  111. } else {
  112. // 如果未最小化,缩小界面并隐藏文字相关元素
  113. inputDiv.style.width = '30px';
  114. inputDiv.style.height = '30px';
  115. inputDiv.querySelectorAll('span').forEach(span => span.style.display = 'none');
  116. isMinimized = true;
  117. }
  118. }
  119.  
  120. // 获取相关DOM元素
  121. const xpathInput = document.getElementById('xpathInput');
  122. const xpathDelayInput = document.getElementById('xpathDelayInput');
  123. const startXPathButton = document.getElementById('startXPathButton');
  124. const stopXPathButton = document.getElementById('stopXPathButton');
  125. const textInput = document.getElementById('textInput');
  126. const textDelayInput = document.getElementById('textDelayInput');
  127. const startTextButton = document.getElementById('startTextButton');
  128. const stopTextButton = document.getElementById('stopTextButton');
  129. const closeButton = document.getElementById('closeButton');
  130. const minimizeButton = document.getElementById('minimizeButton');
  131. const xpathStatusSpan = document.getElementById('xpathStatus');
  132. const xpathLastClickTimeSpan = document.getElementById('xpathLastClickTime');
  133. const xpathNextClickTimeSpan = document.getElementById('xpathNextClickTime');
  134. const textStatusSpan = document.getElementById('textStatus');
  135. const textLastClickTimeSpan = document.getElementById('textLastClickTime');
  136. const textNextClickTimeSpan = document.getElementById('textNextClickTime');
  137.  
  138. // 初始化保存的XPath值、延时值和指定文本值(如果存在)
  139. (async () => {
  140. const savedXPath = await GM.getValue('savedXPath', '');
  141. const savedXPathDelay = await GM.getValue('savedXPathDelay', 300);
  142. const savedText = await GM.getValue('savedText', '');
  143. const savedTextDelay = await GM.getValue('savedTextDelay', 300);
  144. xpathInput.value = savedXPath;
  145. xpathDelayInput.value = savedXPathDelay;
  146. textInput.value = savedText;
  147. textDelayInput.value = savedTextDelay;
  148. })();
  149.  
  150. // 用于存储基于XPath点击的定时器标识和基于文本点击的定时器标识
  151. let xPathIntervalId = null;
  152. let textIntervalId = null;
  153.  
  154. // 递归遍历所有节点(包括跨iframe、处理Shadow DOM等)查找元素的函数(XPath方式),优化错误处理
  155. function findElementsByXPathInAllContexts(xpath, context = document) {
  156. const results = [];
  157. const iterate = (node) => {
  158. try {
  159. const elements = document.evaluate(xpath, node, null, XPathResult.ANY_TYPE, null).iterateNext();
  160. while (elements) {
  161. results.push(elements);
  162. elements = document.evaluate(xpath, node, null, XPathResult.ANY_TYPE, null).iterateNext();
  163. }
  164. } catch (error) {
  165. console.error('XPath查找元素出现错误:', error);
  166. }
  167. const shadowRoots = node.querySelectorAll('*');
  168. shadowRoots.forEach((element) => {
  169. if (element.shadowRoot) {
  170. iterate(element.shadowRoot);
  171. }
  172. });
  173. const iframes = node.querySelectorAll('iframe');
  174. for (const iframe of iframes) {
  175. try {
  176. const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
  177. iterate(iframeDoc);
  178. } catch (error) {
  179. console.error('处理iframe时出现错误:', error);
  180. continue;
  181. }
  182. }
  183. };
  184. iterate(context);
  185. return results;
  186. }
  187.  
  188. // 递归遍历所有节点(包括跨iframe、处理Shadow DOM等)查找包含指定文本元素的函数,优化错误处理
  189. function findElementsByTextInAllContexts(text, context = document) {
  190. const results = [];
  191. const iterate = (node) => {
  192. const elements = node.querySelectorAll('*');
  193. elements.forEach((element) => {
  194. try {
  195. if (element.textContent.includes(text)) {
  196. results.push(element);
  197. }
  198. } catch (error) {
  199. console.error('文本查找元素出现错误:', error);
  200. }
  201. });
  202. const shadowRoots = node.querySelectorAll('*');
  203. shadowRoots.forEach((element) => {
  204. if (element.shadowRoot) {
  205. iterate(element.shadowRoot);
  206. }
  207. });
  208. const iframes = node.querySelectorAll('iframe');
  209. for (const iframe of iframes) {
  210. try {
  211. const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
  212. iterate(iframeDoc);
  213. } catch (error) {
  214. console.error('处理iframe时出现错误:', error);
  215. continue;
  216. }
  217. }
  218. };
  219. iterate(context);
  220. return results;
  221. }
  222.  
  223. // 等待页面加载完成(包括所有资源、iframe等加载完毕)后再执行查找等操作的函数
  224. function waitForPageLoad(callback) {
  225. if (document.readyState === 'complete') {
  226. callback();
  227. } else {
  228. window.addEventListener('load', callback);
  229. }
  230. }
  231.  
  232. // 开始点击的函数(基于XPath),优化执行顺序和时间显示
  233. async function startClickingByXPath() {
  234. const xpath = xpathInput.value;
  235. const delay = parseInt(xpathDelayInput.value, 10);
  236. GM_setValue('savedXPath', xpath);
  237. GM_setValue('savedXPathDelay', delay);
  238.  
  239. try {
  240. waitForPageLoad(() => {
  241. const startTime = Date.now();
  242. const doClick = async () => {
  243. const elements = findElementsByXPathInAllContexts(xpath);
  244. if (elements.length > 0) {
  245. for (const element of elements) {
  246. element.click();
  247. }
  248. const currentTime = Date.now();
  249. xpathLastClickTimeSpan.textContent = `上次点击时间(XPath): ${new Date(currentTime).toLocaleString()}`;
  250. xpathNextClickTimeSpan.textContent = `下次点击时间(XPath): ${new Date(currentTime + delay).toLocaleString()}`;
  251. } else {
  252. clearInterval(xPathIntervalId);
  253. alert('未找到匹配XPath的元素,已停止点击。');
  254. xpathStatusSpan.textContent = '状态: 已停止(未找到元素)';
  255. }
  256. };
  257. doClick();
  258. xPathIntervalId = setInterval(doClick, delay);
  259. xpathStatusSpan.textContent = '状态: 正在点击(XPath)';
  260. });
  261. } catch (error) {
  262. clearInterval(xPathIntervalId);
  263. alert('执行过程出现错误,请检查XPath表达式是否正确。');
  264. xpathStatusSpan.textContent = '状态: 已停止(出错)';
  265. }
  266. }
  267.  
  268. // 开始点击的函数(基于指定文本),优化执行顺序和时间显示
  269. async function startClickingByText() {
  270. const text = textInput.value;
  271. const delay = parseInt(textDelayInput.value, 10);
  272. GM_setValue('savedText', text);
  273. GM_setValue('savedTextDelay', delay);
  274.  
  275. try {
  276. waitForPageLoad(() => {
  277. const startTime = Date.now();
  278. const doClick = async () => {
  279. const elements = findElementsByTextInAllContexts(text);
  280. if (elements.length > 0) {
  281. for (const element of elements) {
  282. element.click();
  283. }
  284. const currentTime = Date.now();
  285. textLastClickTimeSpan.textContent = `上次点击时间(文本): ${new Date(currentTime).toLocaleString()}`;
  286. textNextClickTimeSpan.textContent = `下次点击时间(文本): ${new Date(currentTime + delay).toLocaleString()}`;
  287. } else {
  288. clearInterval(textIntervalId);
  289. alert('未找到包含指定文本的元素,已停止点击。');
  290. textStatusSpan.textContent = '状态: 已停止(未找到元素)';
  291. }
  292. };
  293. doClick();
  294. textIntervalId = setInterval(doClick, delay);
  295. textStatusSpan.textContent = '状态: 正在点击(文本)';
  296. });
  297. } catch (error) {
  298. clearInterval(textIntervalId);
  299. alert('执行过程出现错误,请检查输入的文本是否正确。');
  300. textStatusSpan.textContent = '状态: 已停止(出错)';
  301. }
  302. }
  303.  
  304. // 为开始按钮(XPath方式)添加点击事件监听器
  305. startXPathButton.addEventListener('click', startClickingByXPath);
  306.  
  307. // 为停止按钮(XPath方式)添加点击事件监听器
  308. stopXPathButton.addEventListener('click', () => {
  309. if (xPathIntervalId) {
  310. clearInterval(xPathIntervalId);
  311. xPathIntervalId = null;
  312. xpathStatusSpan.textContent = '状态: 已停止';
  313. }
  314. });
  315.  
  316. // 为开始按钮(文本方式)添加点击事件监听器
  317. startTextButton.addEventListener('click', startClickingByText);
  318.  
  319. // 为停止按钮(文本方式)添加点击事件监听器
  320. stopTextButton.addEventListener('click', () => {
  321. if (textIntervalId) {
  322. clearInterval(textIntervalId);
  323. textIntervalId = null;
  324. textStatusSpan.textContent = '状态: 已停止';
  325. }
  326. });
  327.  
  328. // 为关闭按钮添加点击事件监听器,点击后移除整个输入框区域
  329. closeButton.addEventListener('click', () => {
  330. inputDiv.remove();
  331. });
  332.  
  333. // 为缩小按钮添加点击事件监听器
  334. minimizeButton.addEventListener('click', minimize);
  335.  
  336. // 脚本保活机制,定期执行一个简单的函数来保持脚本活跃,这里简单打印个信息示例,可按需调整具体逻辑
  337. setInterval(() => {
  338. console.log('脚本保持活跃');
  339. }, 60000); // 每60秒执行一次,可根据实际情况调整时间间隔
  340.  
  341. })();