YAD Multi Auto Clicker

Automates clicks at various locations on the screen with customizable intervals

// ==UserScript==
// @name         YAD Multi Auto Clicker
// @namespace    https://greasyfork.org/en/users/781396
// @version      2.9
// @description  Automates clicks at various locations on the screen with customizable intervals
// @author       YAD
// @license      MIT
// @icon         https://i.ibb.co/z7JVjSp/image.png
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    let buttons = [];
    let buttonId = 1;
    let isRunning = false;
    let clickIntervals = [];

    // Load button positions and settings from localStorage
    const loadButtonData = () => JSON.parse(localStorage.getItem('buttonData') || '[]');

    // Save button positions and settings to localStorage
    const saveButtonData = () => {
        const buttonData = buttons.map(button => ({
            id: button.id,
            top: button.style.top,
            left: button.style.left,
            interval: button.dataset.interval,
            clicks: button.dataset.clicks
        }));
        localStorage.setItem('buttonData', JSON.stringify(buttonData));
    };

    // Save the current running state in localStorage
    const saveRunningState = () => {
        localStorage.setItem('isRunning', JSON.stringify(isRunning));
    };

    // Load the running state from localStorage on page reload
    const loadRunningState = () => {
        return JSON.parse(localStorage.getItem('isRunning') || 'false');
    };

    // Create the main interface
    const mainInterface = document.createElement('div');
    mainInterface.style.position = 'fixed';
    mainInterface.style.right = '10px';
    mainInterface.style.top = '50%';
    mainInterface.style.transform = 'translateY(-50%)';
    mainInterface.style.zIndex = '9999';

    // Plus button to add new auto-click buttons
    const plusButton = document.createElement('button');
    plusButton.textContent = '➕';
    plusButton.style.display = 'block';
    plusButton.style.padding = '10px';
    plusButton.style.marginBottom = '5px';
    plusButton.style.fontSize = '16px';
    plusButton.style.cursor = 'pointer';
    plusButton.style.backgroundColor = '#28a745'; // Green background
    plusButton.style.border = 'none';
    plusButton.style.color = 'transparent';
    plusButton.style.textShadow = '0 0 0 white';
    plusButton.style.borderRadius = '5px';
    mainInterface.appendChild(plusButton);

    // Start/Stop button
    const startStopButton = document.createElement('button');
    startStopButton.textContent = '👆';
    startStopButton.style.display = 'block';
    startStopButton.style.padding = '10px';
    startStopButton.style.marginBottom = '5px';
    startStopButton.style.fontSize = '16px';
    startStopButton.style.cursor = 'pointer';
    startStopButton.style.backgroundColor = '#007bff'; // Blue background
    startStopButton.style.border = 'none';
    startStopButton.style.color = 'white';
    startStopButton.style.borderRadius = '5px';
    mainInterface.appendChild(startStopButton);

    // Reset button
    const resetButton = document.createElement('button');
    resetButton.textContent = '♻️';
    resetButton.style.display = 'block';
    resetButton.style.padding = '10px';
    resetButton.style.fontSize = '16px';
    resetButton.style.cursor = 'pointer';
    resetButton.style.backgroundColor = '#dc3545'; // Red background
    resetButton.style.border = 'none';
    resetButton.style.color = 'transparent';
    resetButton.style.textShadow = '0 0 0 white';
    resetButton.style.borderRadius = '5px';
    mainInterface.appendChild(resetButton);

    document.body.appendChild(mainInterface);

    // Plus button functionality
    plusButton.addEventListener('click', () => {
        createAutoClickButton();
    });

    // Start/Stop button functionality
    startStopButton.addEventListener('click', () => {
        isRunning = !isRunning;
        saveRunningState();
        if (isRunning) {
            startStopButton.textContent = '🛑';
            startAutoClick();
        } else {
            startStopButton.textContent = '👆';
            stopAutoClick();
        }
    });

    // Reset button functionality
    resetButton.addEventListener('click', () => {
        stopAutoClick(); // Ensure all running intervals are stopped
        buttons.forEach(button => button.remove());
        buttons = [];
        localStorage.removeItem('buttonData');
        buttonId = 1;
        startStopButton.textContent = '👆';
        saveRunningState();
    });

    // Function to create a new auto-click button
    function createAutoClickButton(buttonData = null) {
        const autoButton = document.createElement('div');
        autoButton.id = buttonId++;
        autoButton.textContent = autoButton.id;
        autoButton.style.width = '50px';
        autoButton.style.height = '50px';
        autoButton.style.borderRadius = '50%';
        autoButton.style.backgroundColor = '#0061ffcc';
        autoButton.style.position = 'absolute';
        autoButton.style.top = buttonData?.top || '50%';
        autoButton.style.left = buttonData?.left || '50%';
        autoButton.style.display = 'flex';
        autoButton.style.alignItems = 'center';
        autoButton.style.justifyContent = 'center';
        autoButton.style.cursor = 'pointer';
        autoButton.style.zIndex = '9999';
        autoButton.style.color = 'white'; // Ensure text is visible
        autoButton.dataset.interval = buttonData?.interval || 1000; // Default interval
        autoButton.dataset.clicks = buttonData?.clicks || 1; // Default number of clicks
        document.body.appendChild(autoButton);
        makeDraggable(autoButton);

        buttons.push(autoButton);
        saveButtonData();

        // Open settings modal on right-click
        autoButton.addEventListener('contextmenu', (e) => {
            e.preventDefault();
            openSettingsModal(autoButton);
        });
    }

    // Function to make the buttons draggable
    function makeDraggable(element) {
        let posX = 0, posY = 0, mouseX = 0, mouseY = 0;
        element.onmousedown = function (e) {
            e.preventDefault();
            mouseX = e.clientX;
            mouseY = e.clientY;
            document.onmousemove = moveElement;
            document.onmouseup = stopMovingElement;
        };

        function moveElement(e) {
            posX = mouseX - e.clientX;
            posY = mouseY - e.clientY;
            mouseX = e.clientX;
            mouseY = e.clientY;
            element.style.top = (element.offsetTop - posY) + 'px';
            element.style.left = (element.offsetLeft - posX) + 'px';
        }

        function stopMovingElement() {
            document.onmouseup = null;
            document.onmousemove = null;
            saveButtonData();
        }
    }

    // Function to open settings modal
    function openSettingsModal(button) {
        const modal = document.createElement('div');
        modal.style.position = 'fixed';
        modal.style.top = '50%';
        modal.style.left = '50%';
        modal.style.transform = 'translate(-50%, -50%)';
        modal.style.zIndex = '10000';
        modal.style.padding = '15px';
        modal.style.backgroundColor = '#6f42c1';
        modal.style.borderRadius = '8px';
        modal.style.boxShadow = '0px 4px 12px rgba(0, 0, 0, 0.1)';
        modal.style.width = '300px'; // Make it more compact
        modal.style.fontFamily = 'Arial, sans-serif';

        const title = document.createElement('h3');
        title.textContent = 'Settings';
        title.style.marginTop = '0';
        title.style.marginBottom = '15px';
        title.style.color = '#fff';
        title.style.textAlign = 'center';
        title.style.fontSize = '18px';
        modal.appendChild(title);

        const form = document.createElement('form');
        form.style.display = 'flex';
        form.style.flexDirection = 'column';
        form.style.gap = '10px';
        modal.appendChild(form);

        const createInputField = (labelText, inputType, inputValue) => {
            const fieldWrapper = document.createElement('div');
            fieldWrapper.style.display = 'flex';
            fieldWrapper.style.flexDirection = 'column';

            const label = document.createElement('label');
            label.textContent = labelText;
            label.style.fontSize = '14px';
            label.style.color = '#fff';
            label.style.marginBottom = '5px';
            fieldWrapper.appendChild(label);

            const input = document.createElement('input');
            input.type = inputType;
            input.value = inputValue;
            input.style.padding = '5px';
            input.style.borderRadius = '4px';
            input.style.border = '1px solid #ccc';
            input.style.fontSize = '14px';
            fieldWrapper.appendChild(input);

            form.appendChild(fieldWrapper);

            return input;
        };

        const intervalInput = createInputField('Interval (ms):', 'number', button.dataset.interval);
        const clicksInput = createInputField('Clicks per Interval:', 'number', button.dataset.clicks);

        const saveButton = document.createElement('button');
        saveButton.textContent = 'Save';
        saveButton.style.padding = '10px';
        saveButton.style.borderRadius = '5px';
        saveButton.style.border = 'none';
        saveButton.style.backgroundColor = '#28a745';
        saveButton.style.color = 'white';
        saveButton.style.cursor = 'pointer';
        form.appendChild(saveButton);

        saveButton.addEventListener('click', (e) => {
            e.preventDefault();
            button.dataset.interval = intervalInput.value;
            button.dataset.clicks = clicksInput.value;
            saveButtonData();
            document.body.removeChild(modal);
        });

        const cancelButton = document.createElement('button');
        cancelButton.textContent = 'Cancel';
        cancelButton.style.padding = '10px';
        cancelButton.style.borderRadius = '5px';
        cancelButton.style.border = 'none';
        cancelButton.style.backgroundColor = '#dc3545';
        cancelButton.style.color = 'white';
        cancelButton.style.cursor = 'pointer';
        form.appendChild(cancelButton);

        cancelButton.addEventListener('click', (e) => {
            e.preventDefault();
            document.body.removeChild(modal);
        });

        document.body.appendChild(modal);
    }

    // Function to simulate auto-clicking on buttons with a specified interval and number of clicks
    async function autoClick(button) {
        const rect = button.getBoundingClientRect();
        const interval = parseInt(button.dataset.interval) || 1000;
        const clicks = parseInt(button.dataset.clicks) || 1;
        button.style.visibility = 'hidden';

        const elemUnderButton = document.elementFromPoint(
            rect.left + rect.width / 2,
            rect.top + rect.height / 2
        );

        button.style.visibility = 'visible';

        if (elemUnderButton && isRunning) {
            button.style.opacity = '0.6';

            // Perform clicks at the specified interval
            for (let i = 0; i < clicks; i++) {
                if (!isRunning) break; // Stop the clicks if not running anymore
                simulateClick(elemUnderButton);
                await new Promise(resolve => setTimeout(resolve, interval));  // Wait for the interval between clicks
            }

            button.style.opacity = '1';
        }
    }

    // Function to simulate clicks anywhere, including on HTML5 game canvas elements
    function simulateClick(target, x = null, y = null) {
        const eventNames = ['mousedown', 'mouseup', 'click'];

        // If specific coordinates are provided, simulate the click there
        if (x !== null && y !== null) {
            const elementAtPoint = document.elementFromPoint(x, y);

            // If the element at the coordinates is a canvas, simulate a click on it
            if (elementAtPoint.tagName === 'CANVAS') {
                eventNames.forEach(eventName => {
                    const event = new MouseEvent(eventName, {
                        bubbles: true,
                        cancelable: true,
                        view: window,
                        clientX: x,
                        clientY: y
                    });
                    elementAtPoint.dispatchEvent(event);
                });
            } else {
                // Simulate click at the provided coordinates for non-canvas elements
                eventNames.forEach(eventName => {
                    const event = new MouseEvent(eventName, {
                        bubbles: true,
                        cancelable: true,
                        view: window,
                        clientX: x,
                        clientY: y
                    });
                    elementAtPoint?.dispatchEvent(event);
                });
            }
            return;
        }

        // If no coordinates, click on the target element
        eventNames.forEach(eventName => {
            const event = new MouseEvent(eventName, {
                bubbles: true,
                cancelable: true,
                view: window
            });
            target.dispatchEvent(event);
        });

        // Handle specific cases like canvas, iframes, and shadow DOM

        // HTML5 Canvas
        if (target.tagName === 'CANVAS') {
            const rect = target.getBoundingClientRect();
            const canvasClickEvent = new MouseEvent('click', {
                bubbles: true,
                cancelable: true,
                view: window,
                clientX: rect.left + rect.width / 2,  // Click at center
                clientY: rect.top + rect.height / 2
            });
            target.dispatchEvent(canvasClickEvent);
        }

        // Handle IFRAMEs
        if (target.tagName === 'IFRAME') {
            const iframeDoc = target.contentDocument || target.contentWindow.document;
            const clickableElement = iframeDoc?.body || iframeDoc?.querySelector('canvas');
            clickableElement?.dispatchEvent(new MouseEvent('click', {
                bubbles: true,
                cancelable: true,
                view: window
            }));
        }

        // Handle Shadow DOM
        if (target.shadowRoot) {
            const clickableElement = target.shadowRoot.querySelector('canvas, [clickable], button, a');
            clickableElement?.dispatchEvent(new MouseEvent('click', {
                bubbles: true,
                cancelable: true,
                view: window
            }));
        }

        // Handle Flash or Java embedded objects (legacy games)
        if (target.tagName === 'OBJECT' || target.tagName === 'EMBED') {
            try {
                target.focus();
                const objectClickEvent = new MouseEvent('click', {
                    bubbles: true,
                    cancelable: true,
                    view: window
                });
                target.dispatchEvent(objectClickEvent);
            } catch (e) {
                console.warn("Flash or Java interaction failed:", e);
            }
        }
    }

    // Start auto-clicking in sequence
    async function startAutoClick() {
        for (const button of buttons) {
            const interval = parseInt(button.dataset.interval);
            await autoClick(button);  // Wait for the current button to finish before starting the next
        }
        if (isRunning) {
            setTimeout(startAutoClick, 0);  // Immediately restart the clicking sequence if still running
        }
    }

    // Stop auto-clicking
    function stopAutoClick() {
        isRunning = false;
    }

    // Load buttons and state on page load
    window.addEventListener('load', () => {
        loadButtonData().forEach(buttonData => createAutoClickButton(buttonData));
        if (loadRunningState()) {
            isRunning = true;
            startStopButton.textContent = '🛑';
            startAutoClick();
        }
    });
})();