AUTO CLICK FR

Dual left/right autoclicker with 0-100 CPS sliders, toggle/hold keys, and freeze key.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         AUTO CLICK FR
// @namespace    https://greasyfork.org/en/users/NotNightmare
// @version      1.0
// @description  Dual left/right autoclicker with 0-100 CPS sliders, toggle/hold keys, and freeze key.
// @author       You
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    /**********************
     * Utility & State
     **********************/

    const MouseButtonNames = {
        0: 'Mouse Left',
        1: 'Mouse Middle',
        2: 'Mouse Right',
        3: 'Mouse Back',
        4: 'Mouse Forward'
    };

    function keyEventToId(e) {
        if (e.type === 'mousedown' || e.type === 'mouseup') {
            return `MOUSE_${e.button}`;
        }
        // Keyboard
        let parts = [];
        if (e.ctrlKey) parts.push('Ctrl');
        if (e.altKey) parts.push('Alt');
        if (e.shiftKey) parts.push('Shift');
        if (e.metaKey) parts.push('Meta');
        parts.push(e.code || e.key);
        return parts.join('+');
    }

    function keyIdToLabel(id) {
        if (!id) return 'Unbound';
        if (id.startsWith('MOUSE_')) {
            const btn = parseInt(id.split('_')[1], 10);
            return MouseButtonNames[btn] || `Mouse ${btn}`;
        }
        return id;
    }

    function createEl(tag, props = {}, children = []) {
        const el = document.createElement(tag);
        Object.assign(el, props);
        for (const c of children) {
            if (typeof c === 'string') el.appendChild(document.createTextNode(c));
            else if (c) el.appendChild(c);
        }
        return el;
    }

    const state = {
        freezeKey: null,
        freezeActive: false,

        left: {
            cps: 10,
            toggleKey: null,
            holdKey: null,
            toggledOn: false,
            holdActive: false,
            intervalId: null
        },
        right: {
            cps: 10,
            toggleKey: null,
            holdKey: null,
            toggledOn: false,
            holdActive: false,
            intervalId: null
        },

        waitingForBind: null, // { target: 'leftToggle' | 'leftHold' | 'rightToggle' | 'rightHold' | 'freeze' }
        uiVisible: false
    };

    /**********************
     * Autoclick Logic
     **********************/

    function startClicker(side) {
        const cfg = state[side];
        stopClicker(side);

        if (state.freezeActive) return;
        if (!cfg.toggledOn && !cfg.holdActive) return;
        if (cfg.cps <= 0) return;

        const interval = 1000 / cfg.cps;
        cfg.intervalId = setInterval(() => {
            if (state.freezeActive) return;
            if (!cfg.toggledOn && !cfg.holdActive) return;

            const evt = new MouseEvent('click', {
                bubbles: true,
                cancelable: true,
                view: window,
                button: side === 'left' ? 0 : 2
            });
            document.dispatchEvent(evt);
        }, interval);
    }

    function stopClicker(side) {
        const cfg = state[side];
        if (cfg.intervalId) {
            clearInterval(cfg.intervalId);
            cfg.intervalId = null;
        }
    }

    function refreshClicker(side) {
        stopClicker(side);
        startClicker(side);
    }

    function setFreezeActive(active) {
        state.freezeActive = active;
        if (active) {
            stopClicker('left');
            stopClicker('right');
        } else {
            refreshClicker('left');
            refreshClicker('right');
        }
        updateUIStatus();
    }

    /**********************
     * UI
     **********************/

    let panel, statusLeft, statusRight, statusFreeze;
    let leftCpsSlider, rightCpsSlider, leftCpsLabel, rightCpsLabel;
    let leftToggleBtn, leftHoldBtn, rightToggleBtn, rightHoldBtn, freezeBtn;

    function buildUI() {
        if (panel) return;

        panel = createEl('div', {
            style: `
                position: fixed;
                top: 80px;
                right: 20px;
                z-index: 999999;
                background: rgba(15,15,20,0.95);
                color: #eee;
                font-family: system-ui, sans-serif;
                font-size: 12px;
                padding: 10px 12px;
                border-radius: 8px;
                box-shadow: 0 4px 16px rgba(0,0,0,0.4);
                width: 260px;
                display: none;
            `
        });

        const title = createEl('div', {
            style: 'font-weight:bold; margin-bottom:6px; font-size:13px;'
        }, ['Dual Autoclicker']);

        const hint = createEl('div', {
            style: 'font-size:11px; opacity:0.7; margin-bottom:8px;'
        }, ['Open/close: Right Shift + Period']);

        // Left section
        const leftHeader = createEl('div', {
            style: 'margin-top:4px; font-weight:bold;'
        }, ['Left Click']);

        leftCpsSlider = createEl('input', {
            type: 'range',
            min: '0',
            max: '100',
            value: String(state.left.cps),
            style: 'width:100%;'
        });
        leftCpsLabel = createEl('div', {
            style: 'font-size:11px; margin-bottom:4px;'
        }, [`CPS: ${state.left.cps}`]);

        leftToggleBtn = createEl('button', {
            type: 'button',
            style: buttonStyle()
        }, ['Toggle: ', keyIdToLabel(state.left.toggleKey)]);
        leftHoldBtn = createEl('button', {
            type: 'button',
            style: buttonStyle()
        }, ['Hold: ', keyIdToLabel(state.left.holdKey)]);

        statusLeft = createEl('div', {
            style: 'font-size:11px; margin-top:3px;'
        }, ['Status: OFF']);

        // Right section
        const rightHeader = createEl('div', {
            style: 'margin-top:8px; font-weight:bold;'
        }, ['Right Click']);

        rightCpsSlider = createEl('input', {
            type: 'range',
            min: '0',
            max: '100',
            value: String(state.right.cps),
            style: 'width:100%;'
        });
        rightCpsLabel = createEl('div', {
            style: 'font-size:11px; margin-bottom:4px;'
        }, [`CPS: ${state.right.cps}`]);

        rightToggleBtn = createEl('button', {
            type: 'button',
            style: buttonStyle()
        }, ['Toggle: ', keyIdToLabel(state.right.toggleKey)]);
        rightHoldBtn = createEl('button', {
            type: 'button',
            style: buttonStyle()
        }, ['Hold: ', keyIdToLabel(state.right.holdKey)]);

        statusRight = createEl('div', {
            style: 'font-size:11px; margin-top:3px;'
        }, ['Status: OFF']);

        // Freeze
        const freezeHeader = createEl('div', {
            style: 'margin-top:8px; font-weight:bold;'
        }, ['Freeze']);

        freezeBtn = createEl('button', {
            type: 'button',
            style: buttonStyle()
        }, ['Freeze key: ', keyIdToLabel(state.freezeKey)]);

        statusFreeze = createEl('div', {
            style: 'font-size:11px; margin-top:3px;'
        }, ['Freeze: OFF']);

        // Close hint
        const closeHint = createEl('div', {
            style: 'font-size:10px; opacity:0.6; margin-top:6px; text-align:right;'
        }, ['Click outside or press hotkey to hide']);

        panel.appendChild(title);
        panel.appendChild(hint);

        panel.appendChild(leftHeader);
        panel.appendChild(leftCpsSlider);
        panel.appendChild(leftCpsLabel);
        panel.appendChild(leftToggleBtn);
        panel.appendChild(leftHoldBtn);
        panel.appendChild(statusLeft);

        panel.appendChild(rightHeader);
        panel.appendChild(rightCpsSlider);
        panel.appendChild(rightCpsLabel);
        panel.appendChild(rightToggleBtn);
        panel.appendChild(rightHoldBtn);
        panel.appendChild(statusRight);

        panel.appendChild(freezeHeader);
        panel.appendChild(freezeBtn);
        panel.appendChild(statusFreeze);

        panel.appendChild(closeHint);

        document.body.appendChild(panel);

        wireUIEvents();
    }

    function buttonStyle() {
        return `
            margin: 2px 2px 0 0;
            padding: 2px 6px;
            font-size: 11px;
            border-radius: 4px;
            border: 1px solid #555;
            background: #222;
            color: #eee;
            cursor: pointer;
        `;
    }

    function wireUIEvents() {
        leftCpsSlider.addEventListener('input', () => {
            state.left.cps = parseInt(leftCpsSlider.value, 10);
            leftCpsLabel.textContent = `CPS: ${state.left.cps}`;
            refreshClicker('left');
        });

        rightCpsSlider.addEventListener('input', () => {
            state.right.cps = parseInt(rightCpsSlider.value, 10);
            rightCpsLabel.textContent = `CPS: ${state.right.cps}`;
            refreshClicker('right');
        });

        leftToggleBtn.addEventListener('click', () => {
            state.waitingForBind = 'leftToggle';
            leftToggleBtn.textContent = 'Toggle: [press key or mouse]';
        });

        leftHoldBtn.addEventListener('click', () => {
            state.waitingForBind = 'leftHold';
            leftHoldBtn.textContent = 'Hold: [press key or mouse]';
        });

        rightToggleBtn.addEventListener('click', () => {
            state.waitingForBind = 'rightToggle';
            rightToggleBtn.textContent = 'Toggle: [press key or mouse]';
        });

        rightHoldBtn.addEventListener('click', () => {
            state.waitingForBind = 'rightHold';
            rightHoldBtn.textContent = 'Hold: [press key or mouse]';
        });

        freezeBtn.addEventListener('click', () => {
            state.waitingForBind = 'freeze';
            freezeBtn.textContent = 'Freeze key: [press key or mouse]';
        });

        document.addEventListener('mousedown', (e) => {
            if (state.waitingForBind) {
                e.preventDefault();
                e.stopPropagation();
                handleBindFromEvent(e);
            }
        }, true);

        document.addEventListener('keydown', (e) => {
            if (state.waitingForBind) {
                e.preventDefault();
                e.stopPropagation();
                handleBindFromEvent(e);
            }
        }, true);

        document.addEventListener('click', (e) => {
            if (!panel || !state.uiVisible) return;
            if (!panel.contains(e.target)) {
                toggleUI(false);
            }
        });
    }

    function handleBindFromEvent(e) {
        const id = keyEventToId(e);

        switch (state.waitingForBind) {
            case 'leftToggle':
                state.left.toggleKey = id;
                leftToggleBtn.textContent = 'Toggle: ' + keyIdToLabel(id);
                break;
            case 'leftHold':
                state.left.holdKey = id;
                leftHoldBtn.textContent = 'Hold: ' + keyIdToLabel(id);
                break;
            case 'rightToggle':
                state.right.toggleKey = id;
                rightToggleBtn.textContent = 'Toggle: ' + keyIdToLabel(id);
                break;
            case 'rightHold':
                state.right.holdKey = id;
                rightHoldBtn.textContent = 'Hold: ' + keyIdToLabel(id);
                break;
            case 'freeze':
                state.freezeKey = id;
                freezeBtn.textContent = 'Freeze key: ' + keyIdToLabel(id);
                break;
        }

        state.waitingForBind = null;
        updateUIStatus();
    }

    function toggleUI(force) {
        buildUI();
        if (typeof force === 'boolean') {
            state.uiVisible = force;
        } else {
            state.uiVisible = !state.uiVisible;
        }
        panel.style.display = state.uiVisible ? 'block' : 'none';
    }

    function updateUIStatus() {
        if (!panel) return;

        const leftOn = (state.left.toggledOn || state.left.holdActive) && !state.freezeActive && state.left.cps > 0;
        const rightOn = (state.right.toggledOn || state.right.holdActive) && !state.freezeActive && state.right.cps > 0;

        statusLeft.textContent = `Status: ${leftOn ? 'ON' : 'OFF'}`;
        statusRight.textContent = `Status: ${rightOn ? 'ON' : 'OFF'}`;
        statusFreeze.textContent = `Freeze: ${state.freezeActive ? 'ON' : 'OFF'}`;
    }

    /**********************
     * Global Input Handling
     **********************/

    function handleKeyOrMouseDown(e) {
        const id = keyEventToId(e);

        // Freeze key (hold)
        if (state.freezeKey && id === state.freezeKey) {
            setFreezeActive(true);
            return;
        }

        // Left hold
        if (state.left.holdKey && id === state.left.holdKey) {
            state.left.holdActive = true;
            refreshClicker('left');
            updateUIStatus();
        }

        // Right hold
        if (state.right.holdKey && id === state.right.holdKey) {
            state.right.holdActive = true;
            refreshClicker('right');
            updateUIStatus();
        }

        // Left toggle
        if (state.left.toggleKey && id === state.left.toggleKey) {
            state.left.toggledOn = !state.left.toggledOn;
            refreshClicker('left');
            updateUIStatus();
        }

        // Right toggle
        if (state.right.toggleKey && id === state.right.toggleKey) {
            state.right.toggledOn = !state.right.toggledOn;
            refreshClicker('right');
            updateUIStatus();
        }
    }

    function handleKeyOrMouseUp(e) {
        const id = keyEventToId(e);

        // Freeze key release
        if (state.freezeKey && id === state.freezeKey) {
            setFreezeActive(false);
            return;
        }

        // Left hold release
        if (state.left.holdKey && id === state.left.holdKey) {
            state.left.holdActive = false;
            refreshClicker('left');
            updateUIStatus();
        }

        // Right hold release
        if (state.right.holdKey && id === state.right.holdKey) {
            state.right.holdActive = false;
            refreshClicker('right');
            updateUIStatus();
        }
    }

    // Menu hotkey: Right Shift + Period
    function isMenuHotkey(e) {
        return e.code === 'Period' && e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey && e.location === KeyboardEvent.DOM_KEY_LOCATION_RIGHT;
    }

    document.addEventListener('keydown', (e) => {
        if (isMenuHotkey(e)) {
            e.preventDefault();
            toggleUI();
            return;
        }
    }, true);

    document.addEventListener('keydown', (e) => {
        if (state.waitingForBind) return; // binding mode handled separately
        handleKeyOrMouseDown(e);
    }, true);

    document.addEventListener('keyup', (e) => {
        if (state.waitingForBind) return;
        handleKeyOrMouseUp(e);
    }, true);

    document.addEventListener('mousedown', (e) => {
        if (state.waitingForBind) return;
        handleKeyOrMouseDown(e);
    }, true);

    document.addEventListener('mouseup', (e) => {
        if (state.waitingForBind) return;
        handleKeyOrMouseUp(e);
    }, true);

})();