Drawaria Avatar Builder Loading Fix Suite

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

Este script no debería instalarse directamente. Es una biblioteca que utilizan otros scripts mediante la meta-directiva de inclusión // @require https://update.greatest.deepsurf.us/scripts/569955/1787222/Drawaria%20Avatar%20Builder%20Loading%20Fix%20Suite.js

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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);
    };

})();