Roblox AutoFill Stable Typing v2

Simule la vraie frappe, vérifie que les champs sont pris en compte, retry si besoin. Ne soumet pas, ne contourne pas le captcha.

// ==UserScript==
// @name         Roblox AutoFill Stable Typing v2
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Simule la vraie frappe, vérifie que les champs sont pris en compte, retry si besoin. Ne soumet pas, ne contourne pas le captcha.
// @match        https://www.roblox.com/fr/*
// @grant        none
// ==/UserScript==

(function(){
    'use strict';

    const PSEUDOS = [
        "05nvf","05o7i","05onh","05p6j","05qme","05ry4","05t2o","05tfl","05tn7",
        "05uzw","05wkm","05wl1","05xqj","05yqq","05z6d","05ztt","060af","060ku",
        "066ac","068e0","068mw","068sq","0693f","069a7","06b1v","06bui","06cof",
        "06cuv","06fm5","06gmq","06hb6","06hri","06i3k","06i9j","06ic7","06ifa",
        "06il1","06j9w"
    ];
    const PASSWORD = "GDCRQDGY"; // ton password (8 chars)
    const DOB = { day: '01', month: 'Jan', year: '2000' };
    const GENDER = 'male';

    const log = (...a) => console.log('[RBX-AutoFill-v2]', ...a);

    function waitFor(selector, timeout = 20000) {
        return new Promise((resolve, reject) => {
            const el = document.querySelector(selector);
            if (el) return resolve(el);
            const mo = new MutationObserver(() => {
                const e = document.querySelector(selector);
                if (e) {
                    mo.disconnect();
                    resolve(e);
                }
            });
            mo.observe(document.body, { childList: true, subtree: true });
            if (timeout > 0) setTimeout(() => { mo.disconnect(); reject(new Error('timeout ' + selector)); }, timeout);
        });
    }

    // setter "robuste" - set native value (helps React)
    function setNativeValue(el, value) {
        try {
            const proto = Object.getPrototypeOf(el);
            const desc = Object.getOwnPropertyDescriptor(proto, 'value') || Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value');
            if (desc && desc.set) {
                desc.set.call(el, value);
            } else {
                el.value = value;
            }
            el.dispatchEvent(new Event('input', { bubbles: true }));
            el.dispatchEvent(new Event('change', { bubbles: true }));
            return true;
        } catch (e) {
            try { el.value = value; el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true })); return true; }
            catch (e2) { return false; }
        }
    }

    // simule une vraie frappe, caractère par caractère, renvoie une Promise qui résout quand fini
    function typeLikeHuman(el, text, charDelay = 80) {
        return new Promise((resolve) => {
            if (!el) return resolve(false);
            el.focus();
            // vider d'abord (mais parfois safer de ne pas tout vider si page attend)
            try { setNativeValue(el, ''); } catch(e){ el.value = ''; }

            let i = 0;
            const step = () => {
                if (i >= text.length) {
                    // events finaux
                    el.dispatchEvent(new Event('input', { bubbles: true }));
                    el.dispatchEvent(new Event('change', { bubbles: true }));
                    // blur après court délai
                    setTimeout(()=>{ try{ el.blur(); el.dispatchEvent(new Event('blur', { bubbles: true })); }catch(e){} }, 40);
                    return resolve(true);
                }
                const ch = text[i];
                // Key events (keydown, keypress, input, keyup)
                try {
                    el.dispatchEvent(new KeyboardEvent('keydown', { key: ch, bubbles: true }));
                } catch(e) {}
                // append char via native setter to be safe for React
                setNativeValue(el, el.value + ch);
                try {
                    el.dispatchEvent(new KeyboardEvent('keypress', { key: ch, bubbles: true }));
                } catch(e) {}
                el.dispatchEvent(new Event('input', { bubbles: true }));
                try {
                    el.dispatchEvent(new KeyboardEvent('keyup', { key: ch, bubbles: true }));
                } catch(e) {}
                i++;
                setTimeout(step, charDelay + Math.round(Math.random()*20)); // small jitter
            };
            step();
        });
    }

    // vérifie que l'élément contient exactement la valeur souhaitée
    function verifyValue(el, expected) {
        try {
            if (!el) return false;
            const current = (el.value || '').toString();
            return current === expected;
        } catch(e) { return false; }
    }

    // remplit pseudo/pwd avec retries et vérifications
    async function fillCredentialsWithVerification(usernameEl, passwordEl, username, password) {
        const MAX_ATTEMPTS = 4;
        for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
            log(`Typing username attempt ${attempt}...`);
            await typeLikeHuman(usernameEl, username, 70);
            // petite pause pour que la page réagisse
            await new Promise(r => setTimeout(r, 250));
            if (verifyValue(usernameEl, username)) {
                log('Username verified OK');
                break;
            } else {
                log('Username mismatch after typing, retrying (setNative fallback)');
                setNativeValue(usernameEl, username);
                await new Promise(r => setTimeout(r, 200));
                if (verifyValue(usernameEl, username)) { log('Username OK after fallback'); break; }
            }
            if (attempt === MAX_ATTEMPTS) log('Unable to reliably set username (giving up after retries)');
        }

        // now password
        for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
            log(`Typing password attempt ${attempt}...`);
            await typeLikeHuman(passwordEl, password, 70);
            await new Promise(r => setTimeout(r, 200));
            if (verifyValue(passwordEl, password)) {
                log('Password verified OK');
                break;
            } else {
                log('Password mismatch after typing, retrying (setNative fallback)');
                setNativeValue(passwordEl, password);
                await new Promise(r => setTimeout(r, 120));
                if (verifyValue(passwordEl, password)) { log('Password OK after fallback'); break; }
            }
            if (attempt === MAX_ATTEMPTS) log('Unable to reliably set password (giving up after retries)');
        }

        return {
            userOk: verifyValue(usernameEl, username),
            passOk: verifyValue(passwordEl, password)
        };
    }

    // remplit selects DOB de façon robuste (choisit option existante par value ou text)
    function setSelectByValueOrText(selectEl, want) {
        if (!selectEl || !want) return false;
        // essayer value d'abord
        try {
            const optByValue = Array.from(selectEl.options).find(o => o.value === want);
            if (optByValue) { selectEl.value = optByValue.value; selectEl.dispatchEvent(new Event('change', { bubbles: true })); return true; }
            // sinon comparer le texte (abrégé ou complet)
            const optByText = Array.from(selectEl.options).find(o => (o.text||'').toLowerCase().includes(want.toLowerCase()));
            if (optByText) { selectEl.value = optByText.value; selectEl.dispatchEvent(new Event('change', { bubbles: true })); return true; }
            // fallback : set first non disabled
            const fallback = Array.from(selectEl.options).find(o => !o.disabled && o.value);
            if (fallback) { selectEl.value = fallback.value; selectEl.dispatchEvent(new Event('change', { bubbles: true })); return true; }
        } catch(e) { return false; }
        return false;
    }

    async function performFullFill() {
        try {
            // attendre que les éléments existent
            const usernameEl = await waitFor('#signup-username').catch(()=>document.getElementById('signup-username'));
            const passwordEl = await waitFor('#signup-password').catch(()=>document.querySelector('input[type="password"]'));
            const dayEl = document.querySelector('#DayDropdown') || document.querySelector('select[name="birthdayDay"]');
            const monthEl = document.querySelector('#MonthDropdown') || document.querySelector('select[name="birthdayMonth"]');
            const yearEl = document.querySelector('#YearDropdown') || document.querySelector('select[name="birthdayYear"]');
            const maleBtn = document.getElementById('MaleButton');
            const femaleBtn = document.getElementById('FemaleButton');
            const checkbox = document.getElementById('signup-checkbox');
            const signupBtn = document.getElementById('signup-button') || document.querySelector('button[name="signupSubmit"]');

            const chosenPseudo = PSEUDOS[Math.floor(Math.random() * PSEUDOS.length)];
            log('Chosen pseudo:', chosenPseudo);

            if (!usernameEl || !passwordEl) {
                log('Elements username/password non trouvés, abort.');
                showToast('Champs username/password non trouvés — attends le chargement et réessaie.');
                return;
            }

            // set DOB early to avoid race conditions (some pages revalidate after DOB change)
            if (dayEl) setSelectByValueOrText(dayEl, DOB.day);
            if (monthEl) setSelectByValueOrText(monthEl, DOB.month);
            if (yearEl) setSelectByValueOrText(yearEl, DOB.year);

            // short pause
            await new Promise(r=>setTimeout(r, 220));

            // fill credentials with verification
            const res = await fillCredentialsWithVerification(usernameEl, passwordEl, chosenPseudo, PASSWORD);

            // Re-set DOB and gender again after credentials to avoid page clearing them
            if (dayEl) setSelectByValueOrText(dayEl, DOB.day);
            if (monthEl) setSelectByValueOrText(monthEl, DOB.month);
            if (yearEl) setSelectByValueOrText(yearEl, DOB.year);

            if (GENDER === 'male' && maleBtn) try{ maleBtn.click(); } catch(e){}
            if (GENDER === 'female' && femaleBtn) try{ femaleBtn.click(); } catch(e){}

            if (checkbox && !checkbox.checked) try{ checkbox.click(); } catch(e){ try{ checkbox.checked = true; checkbox.dispatchEvent(new Event('change', { bubbles: true })); } catch(e){} }

            // enable signup button (best effort)
            if (signupBtn) { try { signupBtn.removeAttribute('disabled'); signupBtn.disabled = false; } catch(e){} }

            // final check
            const finalUserOk = verifyValue(usernameEl, chosenPseudo);
            const finalPassOk = verifyValue(passwordEl, PASSWORD);

            showToast(`Remplissage fini — user:${finalUserOk? 'OK':'FAIL'} pass:${finalPassOk? 'OK':'FAIL'}. Résous le captcha puis clique sur S'inscrire.`);
            log('Final status', {finalUserOk, finalPassOk});
        } catch (e) {
            console.error('[RBX-AutoFill-v2] erreur', e);
            showToast('Erreur interne — regarde la console (F12).');
        }
    }

    // UI: gros bouton
    function createButton() {
        if (document.getElementById('rbx-auto-btn-v2')) return;
        const btn = document.createElement('button');
        btn.id = 'rbx-auto-btn-v2';
        btn.innerText = 'Remplir (stable)';
        Object.assign(btn.style, {
            position: 'fixed', right: '16px', bottom: '16px', zIndex: 2147483647,
            padding: '14px 18px', background: '#ff5a5f', color: '#fff', border: 'none',
            fontSize: '15px', fontWeight: '700', borderRadius: '10px', cursor: 'pointer',
            boxShadow: '0 6px 20px rgba(0,0,0,0.3)', fontFamily: 'Segoe UI, Roboto, Arial'
        });
        btn.addEventListener('click', performFullFill);
        document.body.appendChild(btn);
    }

    // toast
    function showToast(msg, timeout = 4500) {
        const id = 'rbx-autofill-toast-v2';
        let el = document.getElementById(id);
        if (!el) {
            el = document.createElement('div');
            el.id = id;
            Object.assign(el.style, {
                position: 'fixed', right: '16px', bottom: '86px', zIndex: 2147483647,
                background: 'rgba(0,0,0,0.78)', color: '#fff', padding: '10px 14px', borderRadius: '10px',
                fontFamily: 'Segoe UI, Roboto, Arial', fontSize: '13px', boxShadow: '0 6px 20px rgba(0,0,0,0.35)'
            });
            document.body.appendChild(el);
        }
        el.textContent = msg;
        el.style.opacity = '1';
        clearTimeout(el._t);
        el._t = setTimeout(()=>{ el.style.transition = 'opacity 400ms'; el.style.opacity = '0'; }, timeout);
    }

    // Wait for page and add button
    (async function bootstrap() {
        try {
            await waitFor('#signup-username').catch(()=>null);
        } catch(e){}
        createButton();
        log('Autofill stable ready.');
    })();

    // expose for console
    window.RBXAutoFillStable = { run: performFullFill };
})();