Drawaria Avatar Builder Loading Fix Suite

Bypass Loading, Exportación Secuencial en Vivo y Sobrescritura de Librería JSON

Questo script non dovrebbe essere installato direttamente. È una libreria per altri script da includere con la chiave // @require https://update.greatest.deepsurf.us/scripts/569955/1787222/Drawaria%20Avatar%20Builder%20Loading%20Fix%20Suite.js

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

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

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         Drawaria Avatar Builder Loading Fix Suite
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  Bypass Loading, Exportación Secuencial en Vivo y Sobrescritura de Librería JSON
// @author       youtubedrawaria
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// @match        https://drawaria.online/avatar/builder/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- ESTILOS DEL MENÚ ---
    const style = document.createElement('style');
    style.innerHTML = `
        #drawaria-suite {
            position: fixed; top: 20px; right: 20px; width: 280px;
            background: #1a1a1a; color: white; border: 2px solid #007cff;
            border-radius: 8px; z-index: 99999; font-family: sans-serif;
            box-shadow: 0 4px 15px rgba(0,0,0,0.5); overflow: hidden;
        }
        #suite-header {
            background: #007cff; padding: 10px; cursor: move;
            font-weight: bold; text-align: center; font-size: 14px;
        }
        .suite-content { padding: 15px; display: flex; flex-direction: column; gap: 10px; }
        .suite-btn {
            background: #333; color: white; border: 1px solid #444;
            padding: 8px; cursor: pointer; border-radius: 4px; font-size: 12px;
            transition: 0.2s;
        }
        .suite-btn:hover { background: #444; border-color: #007cff; }
        .suite-btn.primary { background: #007cff; border: none; font-weight: bold; }
        .suite-btn.primary:hover { background: #0056b3; }
        .status-msg { font-size: 11px; color: #00ff00; text-align: center; margin-top: 5px; font-weight: bold; }
    `;
    document.head.appendChild(style);

    // --- ESTRUCTURA DEL MENÚ ---
    const menu = document.createElement('div');
    menu.id = 'drawaria-suite';
    menu.innerHTML = `
        <div id="suite-header">Drawaria Avatar Builder Suite Pro Max</div>
        <div class="suite-content">
            <button id="btn-bypass" class="suite-btn primary">🚀 FORZAR BYPASS (Entrar)</button>
            <hr style="border: 0; border-top: 1px solid #444; width: 100%;">
            <button id="btn-export" class="suite-btn">📤 Exportar Librería Lenta (JSON)</button>
            <button id="btn-import" class="suite-btn">📥 Sobrescribir Imágenes (JSON)</button>
            <input type="file" id="file-input" style="display:none" accept=".json">
            <div id="suite-status" class="status-msg" style="color:#aaa;">Esperando acción...</div>
            <div id="suite-progress" style="width: 100%; background: #333; height: 5px; border-radius: 3px; display: none;">
                <div id="progress-bar" style="width: 0%; background: #007cff; height: 100%; transition: width 0.1s;"></div>
            </div>
        </div>
    `;
    document.body.appendChild(menu);

    // --- LÓGICA DRAGGABLE ---
    function makeDraggable(el) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        const header = document.getElementById('suite-header');
        header.onmousedown = (e) => {
            e.preventDefault();
            pos3 = e.clientX; pos4 = e.clientY;
            document.onmouseup = () => { document.onmouseup = null; document.onmousemove = null; };
            document.onmousemove = (e) => {
                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";
            };
        };
    }
    makeDraggable(menu);

    // --- UTILIDADES ---

   // --- UTILIDADES CON ESCUDO PROTECTOR PARA CUSTOM EMOJIS ---
    const getReactApp = () => {
        const root = document.querySelector('.App') || document.querySelector('#root');
        if (!root) return null;
        const key = Object.keys(root).find(k => k.startsWith('__react'));
        let fiber = root[key];
        let app = null;
        while (fiber) {
            if (fiber.stateNode && typeof fiber.stateNode.setState === 'function' && fiber.stateNode.randomize) {
                app = fiber.stateNode;
                break;
            }
            fiber = fiber.return;
        }

        // EL ESCUDO: Si encontramos la App y no la hemos protegido aún
        if (app && !app._isShielded) {
            const originalSetState = app.setState.bind(app);

            // Interceptamos cualquier intento de actualizar la pantalla
            app.setState = function(newState, callback) {

                // 1. Proteger los avatares personalizados de tu script (evita la amnesia)
                if (newState && newState.assets && this.state && this.state.assets) {
                    const customAssets = this.state.assets.filter(a => a.category === 'custom_hack' || a.category === 'animations');
                    if (customAssets.length > 0) {
                        // Si la nueva actualización no tiene tus custom, se los volvemos a inyectar a la fuerza
                        const hasCustom = newState.assets.some(a => a.category === 'custom_hack');
                        if (!hasCustom) {
                            newState.assets = [...customAssets, ...newState.assets];
                        }
                    }
                }

                // 2. Proteger la interfaz HTML (Buscador y botón de añadir)
                const suiteControls = document.getElementById('suite-controls');

                // Llamamos a la actualización real de React
                originalSetState(newState, () => {
                    // 3. Restaurar la UI inmediatamente después de que React la borre
                    if (suiteControls && !document.getElementById('suite-controls')) {
                        const library = document.querySelector('.Library');
                        if (library) library.prepend(suiteControls);
                    }
                    if (callback) callback();
                });
            };
            app._isShielded = true; // Marcamos como protegida
        }

        return app;
    };

    const setStatus = (msg, color = "#aaa") => {
        const statusEl = document.getElementById('suite-status');
        statusEl.innerText = msg;
        statusEl.style.color = color;
    };
    const updateProgress = (percent) => {
        const bar = document.getElementById('progress-bar');
        const container = document.getElementById('suite-progress');
        container.style.display = 'block';
        bar.style.width = `${percent}%`;
        if (percent >= 100) setTimeout(() => container.style.display = 'none', 1000);
    };

    // --- ACCIÓN 1: BYPASS ---
    document.getElementById('btn-bypass').onclick = () => {
        const app = getReactApp();
        if (app) {
            app.setState({
                    allAssetsLoaded: true,
                    assetsLoaded: app.state.assets.length,
                    assets: [...app.state.assets] // <-- Importante: Clonamos la memoria
                });
                setStatus(`✅ ${inyectados} imágenes fijadas en React`, "#00ff00");

            } else {
            setStatus("❌ React no encontrado", "#ff4444");
        }
    };

    // --- ACCIÓN 2: EXPORTAR (Proceso Secuencial con DOM Update) ---
    document.getElementById('btn-export').onclick = async () => {
        const container = document.querySelector('.List.Library');
        if (!container) return setStatus("❌ Abre la librería primero", "#ff4444");

        const imagenes = container.querySelectorAll('li:not(.category) img');
        if (imagenes.length === 0) return setStatus("❌ No hay imágenes para exportar", "#ff4444");

        console.log("🚀 Iniciando conversión secuencial de la librería...");
        let convertidas = 0;
        let omitidas = 0;
        const libraryData = [];

        const getBase64 = async (blobUrl) => {
            const res = await fetch(blobUrl);
            const blob = await res.blob();
            return new Promise((resolve) => {
                const reader = new FileReader();
                reader.onloadend = () => resolve(reader.result);
                reader.readAsDataURL(blob);
            });
        };

        for (let i = 0; i < imagenes.length; i++) {
            const img = imagenes[i];
            const currentSrc = img.src;
            const itemName = img.alt || img.parentElement.title;
            let finalData = currentSrc;

            if (currentSrc.startsWith('data:image')) {
                omitidas++;
            } else if (currentSrc.startsWith('blob:')) {
                try {
                    finalData = await getBase64(currentSrc);
                    img.setAttribute('src', finalData); // Sobrescribe en el DOM visualmente
                    convertidas++;
                    await new Promise(r => setTimeout(r, 50)); // Pausa para no bloquear navegador
                } catch (e) {
                    console.error(`Error en imagen ${itemName}:`, e);
                }
            }

            // Guardamos en el Array que luego será nuestro JSON
            libraryData.push({
                name: itemName,
                data: finalData
            });

            // Actualizar Interfaz
            const percent = Math.round(((i + 1) / imagenes.length) * 100);
            setStatus(`⏳ Procesando ${i + 1}/${imagenes.length}...`, "#007cff");
            updateProgress(percent);
            console.log(`Procesando ${i + 1}/${imagenes.length} (${itemName})... OK`);
        }

        setStatus(`✅ Fin. Convertidas: ${convertidas}, Omitidas: ${omitidas}`, "#00ff00");
        console.log(`✅ Proceso terminado. Convertidas: ${convertidas}, Ya existentes: ${omitidas}.`);

        // Generar y descargar JSON
        const dataStr = JSON.stringify(libraryData);
        const blobFinal = new Blob([dataStr], { type: "application/json" });
        const urlDescarga = URL.createObjectURL(blobFinal);
        const link = document.createElement('a');
        link.href = urlDescarga;
        link.download = `Drawaria_Library_Exportada.json`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    };

   // --- ACCIÓN 3: IMPORTAR (Secuencial con Sobrescritura total: Library + Layers) ---
    document.getElementById('btn-import').onclick = () => document.getElementById('file-input').click();

    document.getElementById('file-input').onchange = (e) => {
        const file = e.target.files[0];
        if (!file) return;

        const reader = new FileReader();
        reader.onload = async (event) => {
            try {
                const data = JSON.parse(event.target.result);
                const app = getReactApp();
                if (!app) throw new Error("React no detectado");

                // Buscamos las imágenes tanto en la Librería como en las Capas activas
                const imagenesDOM = document.querySelectorAll('.List.Library img, .List.Layers img');
                if (imagenesDOM.length === 0) return setStatus("❌ No hay imágenes para reemplazar", "#ff4444");

                setStatus(`⌛ Inyectando ${data.length} imágenes...`, "#007cff");
                let inyectados = 0;

                for (let i = 0; i < data.length; i++) {
                    const item = data[i];

                    // 1. Encontrar el asset en la memoria de la aplicación
                    const targetAsset = app.state.assets.find(a => a.prettyName === item.name || a.name === item.name);

                    if (targetAsset) {
                        // 2. Actualizar las fuentes base
                        targetAsset.src = item.data;
                        if (targetAsset.img) targetAsset.img.src = item.data;

                        // 3. Hack del Virtual DOM para evitar que React lo borre
                        if (targetAsset.imgEl && targetAsset.imgEl.props) {
                            targetAsset.imgEl = Object.assign({}, targetAsset.imgEl, {
                                props: Object.assign({}, targetAsset.imgEl.props, {
                                    src: item.data
                                })
                            });
                        }
                    }

                    // 4. Forzar visualización en el HTML (Library Y Layers al mismo tiempo)
                    // Usamos filter en lugar de find para atrapar TODAS las coincidencias (ej. si hay dos ojos iguales en Layers)
                    const elementosAActualizar = Array.from(imagenesDOM).filter(img =>
                        img.alt === item.name ||
                        (img.parentElement && img.parentElement.title === item.name)
                    );

                    if (elementosAActualizar.length > 0) {
                        elementosAActualizar.forEach(imgElement => {
                            imgElement.setAttribute('src', item.data);
                            inyectados++;
                        });

                        // Progreso visual
                        const percent = Math.round(((i + 1) / data.length) * 100);
                        setStatus(`⏳ Restaurando ${i + 1}/${data.length}...`, "#007cff");
                        updateProgress(percent);
                        await new Promise(r => setTimeout(r, 5));
                    }
                }

                // 5. Al actualizar React, clonamos la memoria para proteger tus Custom Emojis
                app.setState({
                    allAssetsLoaded: true,
                    assetsLoaded: app.state.assets.length,
                    assets: [...app.state.assets]
                });
                setStatus(`✅ ${inyectados} iconos restaurados (Library y Layers)`, "#00ff00");

            } catch (err) {
                setStatus("❌ Error en JSON o React", "#ff4444");
                console.error(err);
            }
        };
        reader.readAsText(file);
    };

})();