Drawaria Avatar Mii Builder

Transforms the Drawaria Avatar Builder UI into a Wii Channel Mii Editor.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Drawaria Avatar Mii Builder
// @namespace    YoutubeDrawariaAvatarMiiBuilder
// @version      4.4
// @description  Transforms the Drawaria Avatar Builder UI into a Wii Channel Mii Editor.
// @author       YouTubeDrawaria
// @match        https://*.drawaria.online/avatar/builder/
// @match        https://drawaria.online/
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @icon         https://cdn.miiwiki.org/8/85/Default_Male_Mii.png
// @grant        none
// @license      MIT
// ==/UserScript==

(($, undefined) => {
    // --- VARIABLES Y FUNCIONES CORS (EXTRAÍDAS DE TU CÓDIGO) ---
    const corsDomains = {
      image: [
        'imgur.com', 'i.imgur.com', 'ibb.co', 'i.ibb.co',
        'githubusercontent.com', 'unsplash.com', 'pexels.com',
        'pixabay.com', 'cdn.dribbble.com', 'images.unsplash.com',
        'media.giphy.com', 'giphy.com', 'tenor.com', 'c.tenor.com',
        'cdn.miiwiki.org', 'pbs.twimg.com', 'khrome.wordpress.com', 'www.miicharacters.com', 'i.pinimg.com', 'images.nintendolife.com', 'encrypted-tbn0.gstatic.com' // Dominios Mii añadidos
      ],
    };

    const corsProxies = [
      'https://corsproxy.io/?',
      'https://api.allorigins.win/raw?url=',
      'https://thingproxy.freeboard.io/fetch/',
      'https://api.codetabs.com/v1/proxy?quest='
    ];

    function getCorsEnabledUrl(originalUrl, type) {
      const domains = corsDomains[type] || [];
      return domains.some(domain => originalUrl.includes(domain)) ||
             originalUrl.startsWith('data:') ||
             originalUrl.startsWith('blob:');
    }

    // **NUEVA FUNCIÓN CLAVE:** Carga la imagen a través de proxy y devuelve una Blob URL.
    async function getCorsBypassImageUrl(originalUrl) {
        if (originalUrl.startsWith('data:') || originalUrl.startsWith('blob:')) {
            return originalUrl; // Ya es seguro
        }

        for (const proxy of corsProxies) {
            try {
                const proxyUrl = proxy + encodeURIComponent(originalUrl);
                const response = await fetch(proxyUrl);

                if (!response.ok) continue;

                const blob = await response.blob();
                if (blob.size === 0) continue;

                return URL.createObjectURL(blob); // Retorna la URL local (blob:...)

            } catch (error) {
                console.warn(`❌ Proxy ${proxy} falló para imagen: ${error}`);
                continue;
            }
        }

        console.error(`❌ Todos los proxies fallaron para cargar: ${originalUrl}. Usando URL directa.`);
        return originalUrl; // Falla segura (puede causar CORS error)
    }

    // --- URLs de Imágenes Mii Proporcionadas ---
    const MII_URLS = {
        BASE_MALE: "https://cdn.miiwiki.org/8/85/Default_Male_Mii.png",
        BASE_FEMALE: "https://cdn.miiwiki.org/2/2b/Default_Female_Mii.png",
        TWITTER_MII: "https://pbs.twimg.com/profile_images/1940066429899886592/MdFIytQ8_400x400.jpg",
        KHROME_MII: "https://khrome.wordpress.com/wp-content/uploads/2012/10/23e90-hni_0013.jpg",
        MARIO_MII: "https://www.miicharacters.com/miis/large/22223_mario.jpg",
        PINTEREST_MII: "https://i.pinimg.com/474x/a0/b5/26/a0b526999ce2b35596a7635863de0894.jpg",
        NINTENDO_LIFE_MII: "https://images.nintendolife.com/762252b2a9312/one-of-many.300x.jpg",
        ENCRYPTED_MII: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR_96fzxWKWhEqd46jC-ZvOsZpVJsP3HNokhg&s",
    };

    // --- ESTRUCTURA DE DATOS MII ---
    const MII_COMPONENT_DATA = {
        'face': [
            { name: 'Mii Base Hombre', drawariaId: 'MII_BASE_M', src: MII_URLS.BASE_MALE, layer: 'base' },
            { name: 'Mii Base Mujer', drawariaId: 'MII_BASE_F', src: MII_URLS.BASE_FEMALE, layer: 'base' },
            { name: 'Mii de Twitter', drawariaId: 'TWITTER_STYLE', src: MII_URLS.TWITTER_MII, layer: 'base' },
            { name: 'Mii de Khrome', drawariaId: 'KHROME_STYLE', src: MII_URLS.KHROME_MII, layer: 'base' },
        ],
        'eyes': [
            { name: 'Mii Mario', drawariaId: 'MII_MARIO', src: MII_URLS.MARIO_MII, layer: 'base' },
            { name: 'Mii Pinterest', drawariaId: 'MII_PIN_STYLE', src: MII_URLS.PINTEREST_MII, layer: 'base' },
        ],
        'mouth': [
            { name: 'Mii NintendoLife', drawariaId: 'NINTENDOLIFE', src: MII_URLS.NINTENDO_LIFE_MII, layer: 'base' },
            { name: 'Mii Encrypted', drawariaId: 'ENCRYPTED_STYLE', src: MII_URLS.ENCRYPTED_MII, layer: 'base' },
        ],
        'accessories': [
            { name: 'Mii Vacio', drawariaId: 'EMPTY_SLOT', src: MII_URLS.BASE_MALE, layer: 'base' },
            { name: 'Mii Base Mujer', drawariaId: 'MII_BASE_F', src: MII_URLS.BASE_FEMALE, layer: 'base' },
        ]
    };

    const MII_PAGES = [
        { name: 'Cara y Pelo', partKey: 'face', icon: '👤' },
        { name: 'Ojos y Cejas', partKey: 'eyes', icon: '👁️' },
        { name: 'Nariz y Boca', partKey: 'mouth', icon: '👃' },
        { name: 'Extras y Color', partKey: 'accessories', icon: '🎨' }
    ];
    let currentPage = 0;
    const AVATAR_SAVE_KEY = 'avatarsave_builder';

    // **ESTADO INICIAL DEL AVATAR MII**
    let currentMiiState = {
        'base': { drawariaId: MII_COMPONENT_DATA.face[0].drawariaId, src: MII_COMPONENT_DATA.face[0].src, blobUrl: null },
        'hair': { drawariaId: null, src: null, blobUrl: null },
        'brows': { drawariaId: null, src: null, blobUrl: null },
        'eyes': { drawariaId: null, src: null, blobUrl: null },
        'nose': { drawariaId: null, src: null, blobUrl: null },
        'mouth': { drawariaId: null, src: null, blobUrl: null },
        'accessories': { drawariaId: null, src: null, blobUrl: null },
        'shirt': { drawariaId: null, src: null, blobUrl: null },
    };

    const DRAW_ORDER_KEYS = ['base', 'shirt', 'hair', 'brows', 'eyes', 'nose', 'mouth', 'accessories'];

    // --- FUNCIÓN DE RENDERIZADO EN EL NUEVO CANVAS (MODIFICADA) ---
    const MiiCanvasRenderer = async () => {
        const canvas = document.getElementById('avatar-canvas');
        if (!canvas) return;
        const ctx = canvas.getContext('2d');
        const W = canvas.width;
        const H = canvas.height;
        ctx.clearRect(0, 0, W, H);

        // Cargar y dibujar de forma secuencial y asíncrona
        const loadAndDraw = async (index) => {
            if (index >= DRAW_ORDER_KEYS.length) return;

            const layerKey = DRAW_ORDER_KEYS[index];
            const partToDraw = currentMiiState[layerKey];

            if (!partToDraw || !partToDraw.src) {
                 await loadAndDraw(index + 1);
                 return;
            }

            // **PASO CLAVE:** Cargar la imagen usando el proxy para obtener una blobUrl
            if (!partToDraw.blobUrl) {
                partToDraw.blobUrl = await getCorsBypassImageUrl(partToDraw.src);
            }

            const img = new Image();
            img.crossOrigin = 'anonymous';
            img.onload = () => {
                ctx.drawImage(img, 0, 0, W, H);
                loadAndDraw(index + 1);
            };
            img.onerror = () => {
                console.error(`❌ Error al dibujar imagen (Blob/CORS) para capa: ${layerKey}`);
                loadAndDraw(index + 1);
            };
            // Usamos la blobUrl segura (o la URL directa si falla el proxy)
            img.src = partToDraw.blobUrl || partToDraw.src;
        };

        await loadAndDraw(0);
    };

    // --- FUNCIÓN DE ACTUALIZACIÓN DEL COMPONENTE (MODIFICADA) ---
    const updateAvatarComponent = (partLayer, componentName, drawariaId, src) => {
        // 1. SOBREESCRIBIR LA BASE y marcar para recarga CORS.
        currentMiiState['base'] = { drawariaId: drawariaId, src: src, blobUrl: null }; // blobUrl = null fuerza la recarga CORS

        // Borrar las capas superiores para evitar la superposición de Mii completos
        DRAW_ORDER_KEYS.forEach(key => {
            if (key !== 'base') {
                currentMiiState[key] = { drawariaId: null, src: null, blobUrl: null };
            }
        });

        // 2. Manipular el objeto de guardado de Drawaria (HIPOTÉTICO)
        if (window.ACCOUNT_AVATARSAVE) {
             window.ACCOUNT_AVATARSAVE['selectedMiiStyle_id'] = drawariaId;
        }

        // 3. Renderizar el nuevo estado en el canvas Mii
        MiiCanvasRenderer();
    };

    // --- LÓGICA DE NAVEGACIÓN Y RENDERIZADO DE PÁGINA ---
    const displayMiiPage = (index) => {
        currentPage = index;
        const page = MII_PAGES[index];
        const $selector = $('#miiComponentSelector');

        $('#miiPageIndicator').text(`${index + 1}/${MII_PAGES.length}`);
        $selector.empty();

        let html = `<div class="MiiGridHeader">${page.icon} Componentes de ${page.name}</div>`;
        html += '<div class="MiiGrid">';

        const allComponents = MII_COMPONENT_DATA[page.partKey] || [];
        allComponents.forEach(component => {
            const isSelected = currentMiiState.base.drawariaId === component.drawariaId ? 'selected' : '';

            html += `
                <div
                    class="MiiChannelSlot MiiButton ${isSelected}"
                    data-layer="${component.layer}"
                    data-name="${component.name}"
                    data-drawariaid="${component.drawariaId}"
                    data-src="${component.src}"
                    title="${component.name}"
                >
                    <img src="${component.src}" alt="${component.name}" class="MiiComponentImage" onerror="this.src='https://via.placeholder.com/70x70.png?text=ERROR'">
                </div>
            `;
        });

        html += '</div>';
        $selector.html(html);

        // Agregar listeners a los nuevos slots
        $('.MiiChannelSlot').on('click', function() {
            const layer = $(this).data('layer');
            const name = $(this).data('name');
            const drawariaId = $(this).data('drawariaid');
            const src = $(this).data('src');

            $('.MiiChannelSlot').removeClass('selected');
            $(this).addClass('selected');

            updateAvatarComponent(layer, name, drawariaId, src);
        });
    };

    // --- LÓGICA DE GUARDADO ---
    const saveAvatarChanges = () => {
        const $saveButton = $('#miiSaveButton');
        $saveButton.text('Guardando...');

        const currentAvatarSave = window.ACCOUNT_AVATARSAVE;

        if (!currentAvatarSave) {
            alert('Error: No se pudo obtener el estado del avatar.');
            $saveButton.text('Guardar');
            return;
        }

        $.ajax({
            url: window.LOGGEDIN ? '/saveavatar' : '/uploadavatarimage',
            type: 'POST',
            data: {
                [AVATAR_SAVE_KEY]: JSON.stringify(currentAvatarSave),
                'fromeditor': true,
            },
        })
        .done((data) => {
             $saveButton.text('¡Guardado OK!');
             setTimeout(() => {
                 // **LIMPIEZA:** Liberar las blob URLs creadas para evitar fugas de memoria.
                 DRAW_ORDER_KEYS.forEach(key => {
                     if (currentMiiState[key] && currentMiiState[key].blobUrl && currentMiiState[key].blobUrl.startsWith('blob:')) {
                         URL.revokeObjectURL(currentMiiState[key].blobUrl);
                     }
                 });
                 window.location.href = new URL(window.location.href).origin;
             }, 1000);
        })
        .fail((_jqXHR, _textStatus, errorThrown) => {
            alert(`Error al guardar: ${errorThrown}.`);
            $saveButton.text('Guardar');
        });
    };

    // --- FUNCIÓN DE INICIALIZACIÓN ---
    const MiiBuilderInit = () => {
        // Ocultar elementos de Drawaria
        $('header').hide();
        $('.List, .Panel:not(.preview)').hide();
        $('canvas.main').hide();

        const $previewPanel = $('.Panel.preview');
        $previewPanel.wrap('<div id="originalDrawariaPreviewWrapper" style="display:none;"></div>');

        // Inyectar el nuevo HTML y el NUEVO CANVAS
        const $miiUI = $(`
            <div id="miiContainer">
                <div id="miiContent">
                    <div id="miiAvatarArea">
                        <div id="miiAvatarWrapper">
                            <canvas id="avatar-canvas" width="400" height="400"></canvas>
                        </div>
                    </div>
                    <div id="miiEditorArea" class="MiiPanel">
                        <!-- Indicador de página -->
                        <div id="miiPageHeader">
                            <button id="miiPrevPage" class="MiiNavButton MiiButton">◀</button>
                            <span id="miiPageIndicator">1/${MII_PAGES.length}</span>
                            <button id="miiNextPage" class="MiiNavButton MiiButton">▶</button>
                        </div>

                        <!-- Cuadrícula de componentes -->
                        <div id="miiComponentSelector"></div>

                        <!-- Controles de Movimiento/Escala/Color (PLACEHOLDERS) -->
                        <div id="miiControlsPanel">
                            <div class="MiiControlGroup">
                                <span class="MiiControlLabel">Posición/Escala</span>
                                <div class="MiiControlGrid">
                                    <button class="MiiSmallButton MiiButton">↑</button>
                                    <button class="MiiSmallButton MiiButton">↔</button>
                                    <button class="MiiSmallButton MiiButton">↗</button>
                                    <button class="MiiSmallButton MiiButton">↓</button>
                                </div>
                            </div>
                            <div class="MiiControlGroup">
                                <span class="MiiControlLabel">Color</span>
                                <div class="MiiColorPalette">
                                    <div class="MiiColorSlot" style="background-color: #333;"></div>
                                    <div class="MiiColorSlot" style="background-color: #f7a83d;"></div>
                                    <div class="MiiColorSlot" style="background-color: #d10000;"></div>
                                    <div class="MiiColorSlot" style="background-color: #007cff;"></div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <div id="miiFooterBar">
                    <button id="miiQuitButton" class="MiiQuitButton MiiButton" onclick="window.location.href = '/';">Quit</button>
                    <div id="miiDate">Mar. 08-05</div>
                    <button id="miiSaveButton" class="MiiSaveButton MiiButton">Guardar</button>
                </div>
            </div>
        `);

        $('main').css({
            'display': 'flex', 'flex-direction': 'column', 'align-items': 'center', 'justify-content': 'center',
            'padding': '0', 'margin': '0', 'min-height': '100vh'
        }).append($miiUI);

        // 4. Agregar listeners para navegación y guardado.
        $('#miiPrevPage').on('click', () => {
            if (currentPage > 0) displayMiiPage(currentPage - 1);
        });
        $('#miiNextPage').on('click', () => {
            if (currentPage < MII_PAGES.length - 1) displayMiiPage(currentPage + 1);
        });
        $('#miiSaveButton').on('click', saveAvatarChanges);

        // 6. Cargar la primera página y dibujar el estado inicial.
        displayMiiPage(0);
        MiiCanvasRenderer();
    };

    // --- PUNTO DE ENTRADA ---
    $(() => {
        applyMiiVibeStyles();

        const mainObserver = new MutationObserver(() => {
            if ($('main').length && $('.Panel.preview').length) {
                MiiBuilderInit();
                mainObserver.disconnect();
            }
        });

        mainObserver.observe(document, { childList: true, subtree: true });
    });

    // Resto de estilos CSS (sin cambios)
    const applyMiiVibeStyles = () => {
        const style = `
            /* GENERAL MII VIBE */
            body, html {
                font-family: 'Arial', sans-serif;
                margin: 0;
                padding: 0;
                background: linear-gradient(180deg, #cce8ff, #e3f7ff);
                color: #333;
                overflow: hidden;
            }
            .App > header, .App > main > div:not(#miiContainer) {
                display: none !important;
            }
            .Panel {
                box-shadow: none !important;
            }

            /* CONTENEDOR PRINCIPAL MII */
            #miiContainer {
                width: 1000px;
                height: 700px;
                max-width: 95vw;
                max-height: 95vh;
                margin: auto;
                display: flex;
                flex-direction: column;
                border-radius: 10px;
                overflow: hidden;
                box-shadow: 0 0 30px rgba(0, 0, 0, 0.2);
            }

            /* BARRA SUPERIOR E INFERIOR (EL BLANCO REDONDEADO DE WII) */
            .MiiPanel {
                background: rgba(255, 255, 255, 0.8);
                backdrop-filter: blur(5px);
                border-radius: 10px;
                box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
                padding: 15px;
                margin: 10px;
            }
            #miiFooterBar {
                background: white;
                height: 80px;
                border-top: 2px solid #00b8ff;
                box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
                padding: 0 30px;
                display: flex;
                align-items: center;
                justify-content: space-between;
                font-size: 24px;
                font-weight: bold;
                color: #007cff;
            }

            /* ÁREA DE CONTENIDO */
            #miiContent {
                flex-grow: 1;
                display: flex;
                padding: 20px;
                background: linear-gradient(135deg, #e0f8ff, #cbf0ff);
            }
            #miiAvatarArea {
                flex: 1;
                display: flex;
                align-items: center;
                justify-content: center;
            }
            #miiAvatarWrapper {
                width: 400px;
                height: 400px;
                border-radius: 10px;
                overflow: hidden;
                box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
                background: linear-gradient(135deg, #4edaff, #007cff);
            }

            /* ÁREA DEL EDITOR DE COMPONENTES */
            #miiEditorArea {
                width: 450px;
                height: 100%;
                display: flex;
                flex-direction: column;
                margin-left: 20px;
                padding: 10px;
            }
            #miiPageHeader {
                display: flex;
                justify-content: center;
                align-items: center;
                font-size: 20px;
                font-weight: bold;
                margin-bottom: 10px;
                padding: 10px;
                border-bottom: 1px solid #ddd;
            }

            /* BOTONES GENERALES (MII BUTTONS) - Estilo en relieve */
            .MiiButton {
                padding: 10px 15px;
                font-size: 16px;
                font-weight: bold;
                color: #333;
                background: linear-gradient(180deg, #f0f0f0, #e8e8e8);
                border: 2px solid #ccc;
                border-radius: 8px;
                cursor: pointer;
                transition: all 0.1s;
                text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
                box-shadow: 0 3px 0 #b3b3b3;
            }
            .MiiButton:hover {
                background: linear-gradient(180deg, #fff, #f0f0f0);
                box-shadow: 0 3px 0 #888;
                transform: translateY(-1px);
            }
            .MiiButton:active {
                box-shadow: 0 0 0 #b3b3b3;
                transform: translateY(3px);
            }

            /* BOTONES DE GUARDAR/SALIR */
            .MiiQuitButton {
                background: linear-gradient(180deg, #f77, #d00);
                color: white;
                border-color: #a00;
                box-shadow: 0 3px 0 #700;
            }
            .MiiSaveButton {
                background: linear-gradient(180deg, #9f9, #080);
                color: white;
                border-color: #050;
                box-shadow: 0 3px 0 #030;
            }

            /* CUADRÍCULA DE COMPONENTES MII CON IMÁGENES */
            .MiiGridHeader {
                font-size: 14px;
                font-weight: bold;
                color: #007cff;
                margin-bottom: 10px;
            }
            .MiiGrid {
                display: grid;
                grid-template-columns: repeat(4, 1fr);
                gap: 10px;
                flex-grow: 1;
                overflow-y: auto;
                padding-right: 5px;
            }
            .MiiChannelSlot {
                height: 70px;
                display: flex;
                align-items: center;
                justify-content: center;
                padding: 5px;
                border-color: #aaa;
                box-shadow: 0 3px 0 #999;
            }
            .MiiComponentImage {
                max-width: 100%;
                max-height: 100%;
                object-fit: contain;
                pointer-events: none;
            }
            .MiiChannelSlot.selected {
                border: 3px solid #00f2ff;
                box-shadow: 0 0 15px rgba(0, 200, 255, 0.8), 0 3px 0 #999;
                transform: translateY(-1px);
            }

            /* PANELES DE CONTROL ADICIONALES */
            #miiControlsPanel {
                display: flex;
                justify-content: space-around;
                align-items: flex-start;
                padding-top: 15px;
                border-top: 1px solid #ddd;
                margin-top: 15px;
            }
            .MiiControlGroup { text-align: center; }
            .MiiControlLabel {
                display: block;
                font-size: 12px;
                color: #555;
                margin-bottom: 5px;
            }
            .MiiControlGrid {
                display: grid;
                grid-template-columns: 1fr 1fr;
                gap: 5px;
            }
            .MiiSmallButton {
                padding: 5px 10px;
                font-size: 14px;
            }
            .MiiColorPalette { display: flex; gap: 5px; }
            .MiiColorSlot {
                width: 25px;
                height: 25px;
                border-radius: 50%;
                border: 2px solid #fff;
                box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
                cursor: pointer;
            }
        `;

        const styleSheet = document.createElement('style');
        styleSheet.type = 'text/css';
        styleSheet.innerText = style;
        document.head.appendChild(styleSheet);
    };
})(window.jQuery.noConflict(true));