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

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         网页基于xpath|文本的自动点击(优化版)
// @namespace    https://game.lpengine.cn/*
// @version      1.0.2
// @description  根据输入的XPath定位元素进行循环点击及按指定文本查找点击元素,优化了遍历框架查找元素、点击停止功能、执行顺序及延时机制,设置成分区形式,美化按钮样式,并添加每次点击时间显示功能,同时添加脚本保活机制。
// @author       toyourtomorrow
// @match        https://fz.lpengine.cn/*
// @match        https://game.lpengine.cn/*
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM.getValue
// @grant        GM_addStyle
// ==/UserScript==

(function () {
    'use strict';

    // 创建用于输入XPath、延时、指定文本以及显示状态的HTML元素,并设置样式使其在最上方显示,同时设置初始为可拖动状态,设置分区形式
    const inputDiv = document.createElement('div');
    inputDiv.style.position = 'fixed';
    inputDiv.style.top = '10px';
    inputDiv.style.left = '10px';
    inputDiv.style.zIndex = '9999';
    inputDiv.style.backgroundColor = '#f8f9fa';
    inputDiv.style.padding = '10px';
    inputDiv.style.borderRadius = '5px';
    inputDiv.style.boxShadow = '0 0 5px rgba(0, 0, 0, 0.2)';
    inputDiv.style.userSelect = 'none';  // 防止文本被误选
    inputDiv.draggable = true;  // 设置可拖动

    inputDiv.innerHTML = `
        <div style="display: flex; flex-direction: column;">
            <!-- XPath 分区 -->
            <div style="background-color: #e9ecef; padding: 10px; border-radius: 5px; margin-bottom: 10px;">
                <div style="display: flex; align-items: center; margin-bottom: 10px;">
                    <label for="xpathInput" style="margin-right: 10px;">XPath表达式:</label>
                    <input type="text" id="xpathInput" style="padding: 5px; border: 1px solid #ced4da; border-radius: 3px; width: 300px;" />
                </div>
                <div style="display: flex; align-items: center; margin-bottom: 10px;">
                    <label for="xpathDelayInput" style="margin-right: 10px;">XPath延时(毫秒):</label>
                    <input type="number" id="xpathDelayInput" style="padding: 5px; border: 1px solid #ced4da; border-radius: 3px; width: 100px;" value="300" />
                </div>
                <div style="display: flex; align-items: center;">
                    <button id="startXPathButton" style="padding: 5px 10px; border: none; border-radius: 3px; background-color: #007bff; color: white; cursor: pointer;">开始点击(XPath)</button>
                    <button id="stopXPathButton" style="padding: 5px 10px; border: none; border-radius: 3px; background-color: #dc3545; color: white; cursor: pointer;">停止点击(XPath)</button>
                </div>
                <span id="xpathStatus" style="margin-left: 10px; color: #6c757d; margin-top: 10px;">状态: 未开始</span>
                <span id="xpathLastClickTime" style="margin-left: 10px; color: #6c757d; margin-top: 5px;">上次点击时间(XPath): -</span>
                <span id="xpathNextClickTime" style="margin-left: 10px; color: 6c757d; margin-top: 5px;">下次点击时间(XPath): -</span>
            </div>
            <!-- 文本分区 -->
            <div style="background-color: #e9ecef; padding: 10px; border-radius: 5px;">
                <div style="display: flex; align-items: center; margin-bottom: 10px;">
                    <label for="textInput" style="margin-right: 10px;">指定文本:</label>
                    <input type="text" id="textInput" style="padding: 5px; border: 1px solid #ced4da; border-radius: 3px; width: 200px;" />
                </div>
                <div style="display: flex; align-items: center; margin-bottom: 10px;">
                    <label for="textDelayInput" style="margin-right: 10px;">文本延时(毫秒):</label>
                    <input type="number" id="textDelayInput" style="padding: 5px; border: 1px solid #ced4da; border-radius: 3px; width: 100px;" value="300" />
                </div>
                <div style="display: flex; align-items: center;">
                    <button id="startTextButton" style="padding: 5px 10px; border: none; border-radius: 3px; background-color: #28a745; color: white; cursor: pointer;">开始点击(文本)</button>
                    <button id="stopTextButton" style="padding: 5px 10px; border: none; border-radius: 3px; background-color: #6c757d; color: white; cursor: pointer;">停止点击(文本)</button>
                </div>
                <span id="textStatus" style="margin-left: 10px; color: #6c757d; margin-top: 10px;">状态: 未开始</span>
                <span id="textLastClickTime" style="margin-left: 10px; color: #6c757d; margin-top: 5px;">上次点击时间(文本): -</span>
                <span id="textNextClickTime" style="margin-left: 10px; color: 6c757d; margin-top: 5px;">下次点击时间(文本): -</span>
            </div>
            <button id="closeButton" style="padding: 5px 10px; border: none; border-radius: 3px; background-color: #6c757d; color: white; cursor: pointer; margin-top: 15px;">关闭</button>
            <button id="minimizeButton" style="padding: 5px 10px; border: none; border-radius: 3px; background-color: #6c757d; color: white; cursor: pointer; margin-top: 5px;">缩小</button>
        </div>
    `;
    document.body.appendChild(inputDiv);

    // 用于记录鼠标按下时的坐标,实现拖动功能
    let startX, startY;

    // 为可拖动元素添加鼠标按下、移动和抬起事件监听器,正确实现拖动功能
    inputDiv.addEventListener('mousedown', function (e) {
        startX = e.clientX;
        startY = e.clientY;
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', stopDrag);
    });

    function drag(e) {
        const dx = e.clientX - startX;
        const dy = e.clientY - startY;
        inputDiv.style.left = (parseInt(inputDiv.style.left) || 0) + dx + 'px';
        inputDiv.style.top = (parseInt(inputDiv.style.top) || 0) + dy + 'px';
        startX = e.clientX;
        startY = e.clientY;
    }

    function stopDrag() {
        document.removeEventListener('mousemove', drag);
        document.removeEventListener('mouseup', stopDrag);
    }

    // 记录界面是否最小化
    let isMinimized = false;

    // 缩小按钮点击事件处理函数
    function minimize() {
        if (isMinimized) {
            // 如果已最小化,恢复原始大小,并重新显示文字相关元素
            inputDiv.style.width = 'auto';
            inputDiv.style.height = 'auto';
            inputDiv.querySelectorAll('span').forEach(span => span.style.display = '');
            isMinimized = false;
        } else {
            // 如果未最小化,缩小界面并隐藏文字相关元素
            inputDiv.style.width = '30px';
            inputDiv.style.height = '30px';
            inputDiv.querySelectorAll('span').forEach(span => span.style.display = 'none');
            isMinimized = true;
        }
    }

    // 获取相关DOM元素
    const xpathInput = document.getElementById('xpathInput');
    const xpathDelayInput = document.getElementById('xpathDelayInput');
    const startXPathButton = document.getElementById('startXPathButton');
    const stopXPathButton = document.getElementById('stopXPathButton');
    const textInput = document.getElementById('textInput');
    const textDelayInput = document.getElementById('textDelayInput');
    const startTextButton = document.getElementById('startTextButton');
    const stopTextButton = document.getElementById('stopTextButton');
    const closeButton = document.getElementById('closeButton');
    const minimizeButton = document.getElementById('minimizeButton');
    const xpathStatusSpan = document.getElementById('xpathStatus');
    const xpathLastClickTimeSpan = document.getElementById('xpathLastClickTime');
    const xpathNextClickTimeSpan = document.getElementById('xpathNextClickTime');
    const textStatusSpan = document.getElementById('textStatus');
    const textLastClickTimeSpan = document.getElementById('textLastClickTime');
    const textNextClickTimeSpan = document.getElementById('textNextClickTime');

    // 初始化保存的XPath值、延时值和指定文本值(如果存在)
    (async () => {
        const savedXPath = await GM.getValue('savedXPath', '');
        const savedXPathDelay = await GM.getValue('savedXPathDelay', 300);
        const savedText = await GM.getValue('savedText', '');
        const savedTextDelay = await GM.getValue('savedTextDelay', 300);
        xpathInput.value = savedXPath;
        xpathDelayInput.value = savedXPathDelay;
        textInput.value = savedText;
        textDelayInput.value = savedTextDelay;
    })();

    // 用于存储基于XPath点击的定时器标识和基于文本点击的定时器标识
    let xPathIntervalId = null;
    let textIntervalId = null;

    // 递归遍历所有节点(包括跨iframe、处理Shadow DOM等)查找元素的函数(XPath方式),优化错误处理
    function findElementsByXPathInAllContexts(xpath, context = document) {
        const results = [];
        const iterate = (node) => {
            try {
                const elements = document.evaluate(xpath, node, null, XPathResult.ANY_TYPE, null).iterateNext();
                while (elements) {
                    results.push(elements);
                    elements = document.evaluate(xpath, node, null, XPathResult.ANY_TYPE, null).iterateNext();
                }
            } catch (error) {
                console.error('XPath查找元素出现错误:', error);
            }
            const shadowRoots = node.querySelectorAll('*');
            shadowRoots.forEach((element) => {
                if (element.shadowRoot) {
                    iterate(element.shadowRoot);
                }
            });
            const iframes = node.querySelectorAll('iframe');
            for (const iframe of iframes) {
                try {
                    const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
                    iterate(iframeDoc);
                } catch (error) {
                    console.error('处理iframe时出现错误:', error);
                    continue;
                }
            }
        };
        iterate(context);
        return results;
    }

    // 递归遍历所有节点(包括跨iframe、处理Shadow DOM等)查找包含指定文本元素的函数,优化错误处理
    function findElementsByTextInAllContexts(text, context = document) {
        const results = [];
        const iterate = (node) => {
            const elements = node.querySelectorAll('*');
            elements.forEach((element) => {
                try {
                    if (element.textContent.includes(text)) {
                        results.push(element);
                    }
                } catch (error) {
                    console.error('文本查找元素出现错误:', error);
                }
            });
            const shadowRoots = node.querySelectorAll('*');
            shadowRoots.forEach((element) => {
                if (element.shadowRoot) {
                    iterate(element.shadowRoot);
                }
            });
            const iframes = node.querySelectorAll('iframe');
            for (const iframe of iframes) {
                try {
                    const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
                    iterate(iframeDoc);
                } catch (error) {
                    console.error('处理iframe时出现错误:', error);
                    continue;
                }
            }
        };
        iterate(context);
        return results;
    }

    // 等待页面加载完成(包括所有资源、iframe等加载完毕)后再执行查找等操作的函数
    function waitForPageLoad(callback) {
        if (document.readyState === 'complete') {
            callback();
        } else {
            window.addEventListener('load', callback);
        }
    }

    // 开始点击的函数(基于XPath),优化执行顺序和时间显示
    async function startClickingByXPath() {
        const xpath = xpathInput.value;
        const delay = parseInt(xpathDelayInput.value, 10);
        GM_setValue('savedXPath', xpath);
        GM_setValue('savedXPathDelay', delay);

        try {
            waitForPageLoad(() => {
                const startTime = Date.now();
                const doClick = async () => {
                    const elements = findElementsByXPathInAllContexts(xpath);
                    if (elements.length > 0) {
                        for (const element of elements) {
                            element.click();
                        }
                        const currentTime = Date.now();
                        xpathLastClickTimeSpan.textContent = `上次点击时间(XPath): ${new Date(currentTime).toLocaleString()}`;
                        xpathNextClickTimeSpan.textContent = `下次点击时间(XPath): ${new Date(currentTime + delay).toLocaleString()}`;
                    } else {
                        clearInterval(xPathIntervalId);
                        alert('未找到匹配XPath的元素,已停止点击。');
                        xpathStatusSpan.textContent = '状态: 已停止(未找到元素)';
                    }
                };
                doClick();
                xPathIntervalId = setInterval(doClick, delay);
                xpathStatusSpan.textContent = '状态: 正在点击(XPath)';
            });
        } catch (error) {
            clearInterval(xPathIntervalId);
            alert('执行过程出现错误,请检查XPath表达式是否正确。');
            xpathStatusSpan.textContent = '状态: 已停止(出错)';
        }
    }

    // 开始点击的函数(基于指定文本),优化执行顺序和时间显示
    async function startClickingByText() {
        const text = textInput.value;
        const delay = parseInt(textDelayInput.value, 10);
        GM_setValue('savedText', text);
        GM_setValue('savedTextDelay', delay);

        try {
            waitForPageLoad(() => {
                const startTime = Date.now();
                const doClick = async () => {
                    const elements = findElementsByTextInAllContexts(text);
                    if (elements.length > 0) {
                        for (const element of elements) {
                            element.click();
                        }
                        const currentTime = Date.now();
                        textLastClickTimeSpan.textContent = `上次点击时间(文本): ${new Date(currentTime).toLocaleString()}`;
                        textNextClickTimeSpan.textContent = `下次点击时间(文本): ${new Date(currentTime + delay).toLocaleString()}`;
                    } else {
                        clearInterval(textIntervalId);
                        alert('未找到包含指定文本的元素,已停止点击。');
                        textStatusSpan.textContent = '状态: 已停止(未找到元素)';
                    }
                };
                doClick();
                textIntervalId = setInterval(doClick, delay);
                textStatusSpan.textContent = '状态: 正在点击(文本)';
            });
        } catch (error) {
            clearInterval(textIntervalId);
            alert('执行过程出现错误,请检查输入的文本是否正确。');
            textStatusSpan.textContent = '状态: 已停止(出错)';
        }
    }

    // 为开始按钮(XPath方式)添加点击事件监听器
    startXPathButton.addEventListener('click', startClickingByXPath);

    // 为停止按钮(XPath方式)添加点击事件监听器
    stopXPathButton.addEventListener('click', () => {
        if (xPathIntervalId) {
            clearInterval(xPathIntervalId);
            xPathIntervalId = null;
            xpathStatusSpan.textContent = '状态: 已停止';
        }
    });

    // 为开始按钮(文本方式)添加点击事件监听器
    startTextButton.addEventListener('click', startClickingByText);

    // 为停止按钮(文本方式)添加点击事件监听器
    stopTextButton.addEventListener('click', () => {
        if (textIntervalId) {
            clearInterval(textIntervalId);
            textIntervalId = null;
            textStatusSpan.textContent = '状态: 已停止';
        }
    });

    // 为关闭按钮添加点击事件监听器,点击后移除整个输入框区域
    closeButton.addEventListener('click', () => {
        inputDiv.remove();
    });

    // 为缩小按钮添加点击事件监听器
    minimizeButton.addEventListener('click', minimize);

    // 脚本保活机制,定期执行一个简单的函数来保持脚本活跃,这里简单打印个信息示例,可按需调整具体逻辑
    setInterval(() => {
        console.log('脚本保持活跃');
    }, 60000);  // 每60秒执行一次,可根据实际情况调整时间间隔

})();