Universal Macro Pro v0.1

Record/play macros with 1-5 keys. Fixed infinite loop, Save/Load, and Draggable UI.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         Universal Macro Pro v0.1
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Record/play macros with 1-5 keys. Fixed infinite loop, Save/Load, and Draggable UI.
// @author       @Denysmilano94®
// @license      MIT
// @match        *://*/*
// @grant        none
// ==/UserScript==
 
(function() {
    "use strict";
 
    let isRecording = false;
    let isPlaying = false;
    let macro = [];
    let startTime = 0;
    let pauseOffset = 0;
    let timeouts = [];
    let loopLimit = 1;
    let currentLoop = 0;
 
    // --- UI SETUP ---
 
    const toggleBtn = document.createElement("div");
    toggleBtn.id = "macro-toggle";
    toggleBtn.innerHTML = "⚙️";
    toggleBtn.style = "position: fixed; top: 20px; right: 20px; z-index: 10002; background: #58cc02; color: white; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border-radius: 50%; cursor: move; font-size: 20px; box-shadow: 0 4px 10px rgba(0,0,0,0.5); user-select: none;";
    document.body.appendChild(toggleBtn);
 
    const container = document.createElement("div");
    container.id = "macro-container";
    container.style = "position: fixed; top: 70px; right: 20px; z-index: 10001; background: #1a1a1a; color: white; padding: 15px; border-radius: 12px; font-family: 'Segoe UI', sans-serif; box-shadow: 0 10px 25px rgba(0,0,0,0.7); width: 250px; border: 1px solid #333; user-select: none; display: none;";
    
    container.innerHTML = `
        <div id="macro-header" style="margin-bottom: 12px; font-weight: bold; color: #58cc02; text-align: center; border-bottom: 1px solid #333; padding-bottom: 8px; cursor: move; font-size: 15px;">
            UNIVERSAL MACRO PRO v0.1
            <div style="margin-top: 12px; display: flex; justify-content: center; gap: 20px; align-items: center;">
                <a href="https://www.threads.net/@denys_894" target="_blank" title="Threads">
                    <svg width="24" height="24" viewBox="0 0 24 24" fill="#ffffff"><path d="M14.886 12.269c.004.577-.04 1.15-.13 1.714-.143.896-.466 1.701-.947 2.34-.482.639-1.125 1.054-1.886 1.217-.36.077-.733.115-1.107.114-.73-.002-1.446-.168-2.096-.486-.65-.318-1.206-.777-1.625-1.342-.42-.565-.71-1.22-.848-1.916-.139-.696-.16-1.411-.061-2.119.1-.708.31-1.393.619-2.03.31-.637.717-1.212 1.207-1.69.49-.478 1.06-.856 1.685-1.11.625-.254 1.295-.386 1.97-.389.873.004 1.72.247 2.449.704.73.457 1.326 1.101 1.722 1.861.16-.38.358-.742.593-1.082.49-.705 1.127-1.29 1.875-1.72.748-.43 1.584-.66 2.456-.677.371-.007.742.025 1.109.096 1.053.203 2.016.732 2.768 1.519.752.787 1.258 1.8 1.455 2.91.197 1.111.128 2.249-.197 3.32-.325 1.07-.903 2.053-1.685 2.863-.782.81-1.745 1.432-2.805 1.815-1.06.383-2.193.565-3.329.533-1.614-.045-3.18-.535-4.575-1.424-1.396-.89-2.545-2.147-3.355-3.669-.81-1.522-1.233-3.23-1.234-4.965.001-1.734.426-3.441 1.239-4.96.812-1.52 1.964-2.774 3.363-3.66.194-.124.4-.233.614-.326C10.74 2.27 12.35 1.98 13.974 1.986c1.55-.006 3.087.24 4.544.727 1.457.487 2.802 1.237 3.971 2.213l-1.452 1.565c-.968-.809-2.083-1.43-3.284-1.83-1.2-.4-2.464-.601-3.737-.594-1.332-.005-2.653.233-3.896.702-1.243.469-2.373 1.186-3.333 2.112-.96.926-1.737 2.036-2.293 3.275-.556 1.24-.838 2.578-.832 3.93.003 1.35.289 2.686.845 3.923.556 1.237 1.334 2.346 2.295 3.27.962.925 2.091 1.642 3.334 2.113 1.243.471 2.563.712 3.895.711.954.024 1.908-.124 2.806-.437.898-.313 1.722-.816 2.423-1.482.7-.665 1.252-1.478 1.623-2.392.37-.913.563-1.895.568-2.887 0-.74-.13-1.474-.384-2.164-.253-.69-.63-1.314-1.112-1.843-.482-.528-1.07-.95-1.728-1.239-.658-.289-1.374-.437-2.1-.435-.615.004-1.223.111-1.792.316-.57.204-1.084.516-1.516.918-.432.401-.78.887-1.025 1.432-.244.544-.38 1.13-.398 1.726l-.01.422-.003.425-.01.422z"/></svg>
                </a>
                <a href="https://paypal.me/DenysBixio" target="_blank" title="Donate via PayPal">
                    <svg width="24" height="24" viewBox="0 0 24 24" fill="#003087"><path d="M20.067 6.947c.49 2.94-.36 5.61-3.23 6.08h-2.14c-.67 0-1.22.48-1.34 1.14l-.84 5.34c-.05.34-.35.59-.69.59H8.647c-.44 0-.76-.41-.69-.84l2.12-13.43c.1-.66.67-1.14 1.34-1.14h3.9c2.85 0 4.26 1.31 4.75 4.26z"/><path d="M15.427 6.947c.49 2.94-.36 5.61-3.23 6.08h-2.14c-.67 0-1.22.48-1.34 1.14l-.84 5.34c-.05.34-.35.59-.69.59H4.007c-.44 0-.76-.41-.69-.84l2.12-13.43c.1-.66.67-1.14 1.34-1.14h3.9c2.85 0 4.26 1.31 4.75 4.26z" fill="#009cde" opacity="0.6"/></svg>
                </a>
            </div>
        </div>
        
        <button id="recBtn" style="background: #ff4444; color: white; border: 2px solid #fff; padding: 12px; cursor: pointer; border-radius: 8px; font-weight: 900; width: 100%; margin-bottom: 8px; font-size: 11px; letter-spacing: 0.5px; text-shadow: 1px 1px 2px rgba(0,0,0,0.5);">REC (1)</button>
 
        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px;">
            <button id="stopBtn" style="background: #ff9800; color: white; border: none; padding: 12px; cursor: pointer; border-radius: 8px; font-weight: bold; font-size: 11px;">STOP (2)</button>
            <button id="playBtn" style="background: #44bb44; color: white; border: none; padding: 12px; cursor: pointer; border-radius: 8px; font-weight: bold; font-size: 11px;">PLAY (3)</button>
        </div>
 
        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px;">
            <button id="saveBtn" style="background: #2196F3; color: white; border: none; padding: 10px; cursor: pointer; border-radius: 8px; font-weight: bold; font-size: 10px;">💾 SAVE (4)</button>
            <button id="loadBtn" style="background: #9C27B0; color: white; border: none; padding: 10px; cursor: pointer; border-radius: 8px; font-weight: bold; font-size: 10px;">📂 LOAD (5)</button>
        </div>
 
        <div style="background: #252525; padding: 10px; border-radius: 8px; margin-bottom: 8px; border: 1px solid #444; display: flex; justify-content: space-between; align-items: center;">
            <span style="font-size: 11px; font-weight: bold; color: #aaa;">LOOPS (0=∞):</span>
            <input type="number" id="loopInput" value="1" min="0" style="width: 55px; background: #000; color: #58cc02; border: 1px solid #555; border-radius: 4px; padding: 3px; font-size: 12px; text-align: center;">
        </div>
        
        <div id="status" style="margin-bottom: 10px; font-size: 13px; color: #58cc02; text-align: center; font-weight: bold; min-height: 16px; text-transform: uppercase; letter-spacing: 1px;">Ready</div>
 
        <details style="background: #252525; border-radius: 8px; border: 1px solid #444; cursor: pointer;">
            <summary style="padding: 8px; font-size: 11px; color: #aaa; text-align: center; list-style: none;">HOTKEYS GUIDE</summary>
            <div style="padding: 10px; border-top: 1px solid #333; font-size: 12px; line-height: 1.8;">
                1: Rec/Resume | 2: Stop/Pause | 3: Play | 4: Save | 5: Load | 6: Clear | ESC: Kill
            </div>
        </details>
    `;
    document.body.appendChild(container);
 
    const recBtn = document.getElementById("recBtn");
    const playBtn = document.getElementById("playBtn");
    const stopBtn = document.getElementById("stopBtn");
    const saveBtn = document.getElementById("saveBtn");
    const loadBtn = document.getElementById("loadBtn");
    const status = document.getElementById("status");
    const loopInput = document.getElementById("loopInput");
 
    // --- TOGGLE & DRAG LOGIC ---
 
    let moved = false;
    toggleBtn.onclick = () => { if(!moved) container.style.display = container.style.display === "none" ? "block" : "none"; };
 
    function makeDraggable(el, handle) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        handle.onmousedown = (e) => {
            moved = false;
            pos3 = e.clientX; pos4 = e.clientY;
            document.onmouseup = () => { document.onmouseup = null; document.onmousemove = null; };
            document.onmousemove = (e) => {
                moved = true;
                pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY;
                pos3 = e.clientX; pos4 = e.clientY;
                el.style.top = (el.offsetTop - pos2) + "px";
                el.style.left = (el.offsetLeft - pos1) + "px";
                el.style.right = "auto";
            };
        };
    }
 
    makeDraggable(container, document.getElementById("macro-header"));
    makeDraggable(toggleBtn, toggleBtn);
 
    // --- CORE MACRO LOGIC ---
 
    const saveMacro = () => {
        if (macro.length === 0) { status.innerText = "Empty!"; status.style.color = "#f44"; return; }
        localStorage.setItem('universal_macro_v2', JSON.stringify(macro));
        status.innerText = "Saved!"; status.style.color = "#2196F3";
    };
 
    const loadMacro = () => {
        const data = localStorage.getItem('universal_macro_v2');
        if (data) {
            macro = JSON.parse(data);
            status.innerText = "Loaded: " + macro.length; status.style.color = "#9C27B0";
        } else { status.innerText = "No data!"; status.style.color = "#f44"; }
    };
 
    const stopAll = () => {
        timeouts.forEach(clearTimeout); timeouts = [];
        isPlaying = false; currentLoop = 0;
        if (isRecording) {
            pauseOffset = Date.now() - startTime;
            isRecording = false;
            status.innerText = "Paused";
        } else { status.innerText = "Stopped"; }
        status.style.color = "#ff9800";
        recBtn.innerText = "RESUME (1)";
    };
 
    const toggleRecording = () => {
        if (isPlaying) return;
        isRecording = !isRecording;
        if (isRecording) {
            if (pauseOffset === 0) { macro = []; startTime = Date.now(); }
            else startTime = Date.now() - pauseOffset;
            recBtn.innerText = "STOP REC";
            status.innerText = "🔴 Rec..."; status.style.color = "#ff4444";
        } else {
            pauseOffset = 0; recBtn.innerText = "REC (1)";
            status.innerText = "Saved: " + macro.length; status.style.color = "#58cc02";
        }
    };
 
    const runPlaybackCycle = () => {
        if (loopLimit !== 0 && currentLoop >= loopLimit) { 
            isPlaying = false; 
            status.innerText = "Finished"; 
            return; 
        }
 
        status.innerText = `Loop ${currentLoop + 1} / ${loopLimit === 0 ? "∞" : loopLimit}`;
        
        macro.forEach((action, index) => {
            const t = setTimeout(() => {
                if (!isPlaying) return;
                const el = document.querySelector(action.selector);
                if (el) el.click();
                
                if (index === macro.length - 1) { 
                    currentLoop++; 
                    setTimeout(runPlaybackCycle, 500); 
                }
            }, action.time);
            timeouts.push(t);
        });
    };
 
    const startPlayback = () => {
        if (macro.length === 0 || isPlaying) return;
        loopLimit = parseInt(loopInput.value);
        if (isNaN(loopLimit)) loopLimit = 1;
        
        currentLoop = 0; 
        isPlaying = true; 
        isRecording = false;
        runPlaybackCycle();
    };
 
    const getPathTo = (element) => {
        if (element.id !== "") return "#" + CSS.escape(element.id);
        if (element === document.body) return element.tagName;
        let ix = 0, siblings = element.parentNode.childNodes;
        for (let i = 0; i < siblings.length; i++) {
            let sibling = siblings[i];
            if (sibling === element) return getPathTo(element.parentNode) + " > " + element.tagName + ":nth-of-type(" + (ix + 1) + ")";
            if (sibling.nodeType === 1 && sibling.tagName === element.tagName) ix++;
        }
    };
 
    window.addEventListener("click", (e) => {
        if (!isRecording || e.target.closest("#macro-container") || e.target.closest("#macro-toggle")) return;
        macro.push({ time: Date.now() - startTime, selector: getPathTo(e.target) });
        status.innerText = "Clicks: " + macro.length;
    }, true);
 
    window.addEventListener("keydown", (e) => {
        if (document.activeElement.tagName === "INPUT") return;
        switch(e.key) {
            case '1': e.preventDefault(); toggleRecording(); break;
            case '2': e.preventDefault(); stopAll(); break;
            case '3': e.preventDefault(); startPlayback(); break;
            case '4': e.preventDefault(); saveMacro(); break;
            case '5': e.preventDefault(); loadMacro(); break;
            case '6': e.preventDefault(); stopAll(); macro = []; pauseOffset = 0; status.innerText = "Cleared"; break;
            case 'Escape': stopAll(); break;
        }
    });
 
    saveBtn.onclick = saveMacro; loadBtn.onclick = loadMacro;
    recBtn.onclick = toggleRecording; playBtn.onclick = startPlayback; stopBtn.onclick = stopAll;
 
})();