Drawaria Avatar Builder Loading Fix Suite

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

이 스크립트는 직접 설치하는 용도가 아닙니다. 다른 스크립트에서 메타 지시문 // @require https://update.greatest.deepsurf.us/scripts/569955/1787222/Drawaria%20Avatar%20Builder%20Loading%20Fix%20Suite.js을(를) 사용하여 포함하는 라이브러리입니다.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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

})();