Drawaria Avatar Builder Loading Fix Suite

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

Ce script ne doit pas être installé directement. C'est une librairie destinée à être incluse dans d'autres scripts avec la méta-directive // @require https://update.greatest.deepsurf.us/scripts/569955/1787222/Drawaria%20Avatar%20Builder%20Loading%20Fix%20Suite.js

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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

})();