Drawaria Symbols Loader Menu

Load symbols from BarsikSymbols.json and allow inserting them into the chat with a click. Improved and draggable menu

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Drawaria Symbols Loader Menu
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Load symbols from BarsikSymbols.json and allow inserting them into the chat with a click. Improved and draggable menu
// @author       YouTubeDrawaria, Barsik Hacker
// @match        https://drawaria.online/*
// @grant        none
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// ==/UserScript==

(function() {
    'use strict';

    const SYM_URL = "https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaWordList/main/BarsikSymbols.json";
    const PAGE_SIZE = 30; // Número de símbolos por página

    let allSymbols = []; // Almacena todos los símbolos cargados
    let currentPage = 0;
    let symLoaderBox; // Referencia al cuadro principal de la UI

    // Variables para la funcionalidad de arrastre
    let isDragging = false;
    let offsetX, offsetY;

    /**
     * Crea y añade la interfaz de usuario al DOM.
     * Incluye estilos CSS y la estructura HTML básica.
     */
    function createUI() {
        // --- Estilos CSS ---
        const style = document.createElement('style');
        style.textContent = `
            #symLoaderBox {
                position: fixed;
                top: 60px;
                right: 20px;
                background: #2b2b2b; /* Fondo oscuro */
                color: #e0e0e0; /* Texto claro */
                z-index: 9999; /* Asegura que esté por encima de otros elementos */
                border-radius: 10px;
                padding: 15px;
                width: 280px; /* Ancho ligeramente mayor */
                font-size: 15px;
                box-shadow: 0 4px 18px rgba(0, 0, 0, 0.4); /* Sombra suave */
                font-family: 'Arial', sans-serif;
                border: 1px solid #444; /* Borde sutil */
                cursor: grab; /* Cursor para indicar que es arrastrable */
            }

            #symLoaderHeader {
                display: flex;
                align-items: center;
                justify-content: space-between;
                margin-bottom: 10px;
                padding-bottom: 5px;
                border-bottom: 1px solid #3a3a3a; /* Separador */
                cursor: grab; /* Cursor para indicar que el header es arrastrable */
            }
            #symLoaderHeader span {
                font-size: 18px;
                font-weight: bold;
                color: #fff;
            }

            #symLoaderBox button {
                margin: 0 2px;
                padding: 5px 9px;
                font-size: 17px;
                vertical-align: middle;
                border-radius: 5px;
                border: none;
                background: #444;
                color: #fff;
                cursor: pointer;
                transition: background 0.2s ease, transform 0.1s ease; /* Transiciones suaves */
            }
            #symLoaderBox button:hover {
                background: #666;
                transform: translateY(-1px); /* Efecto de "levantar" */
            }
            #symLoaderBox button:active {
                transform: translateY(0);
            }

            #symLoaderDL {
                background: #3a7bd5; /* Color distintivo para descargar */
            }
            #symLoaderDL:hover {
                background: #2a6bc5;
            }

            #symLoaderClose {
                background: #d32f2f; /* Rojo para cerrar */
            }
            #symLoaderClose:hover {
                background: #c31f1f;
            }

            #symLoaderSymbols {
                max-height: 280px; /* Altura máxima para el scroll */
                overflow-y: auto;
                word-break: break-all; /* Rompe palabras largas */
                white-space: normal;
                padding-right: 5px; /* Espacio para la barra de desplazamiento */
                margin-bottom: 10px;
            }

            /* Estilos de la barra de desplazamiento (para navegadores Webkit como Chrome, Safari) */
            #symLoaderSymbols::-webkit-scrollbar {
                width: 8px;
            }
            #symLoaderSymbols::-webkit-scrollbar-track {
                background: #333;
                border-radius: 10px;
            }
            #symLoaderSymbols::-webkit-scrollbar-thumb {
                background: #666;
                border-radius: 10px;
            }
            #symLoaderSymbols::-webkit-scrollbar-thumb:hover {
                background: #888;
            }

            .symBtn { /* Estilo para los botones de símbolos individuales */
                display: inline-block;
                background: #555;
                color: #fff;
                padding: 4px 8px;
                margin: 3px;
                border-radius: 4px;
                cursor: pointer;
                font-size: 16px;
                transition: background 0.15s ease, transform 0.05s ease;
                user-select: none; /* Previene la selección de texto */
            }
            .symBtn:hover {
                background: #777;
                transform: scale(1.02);
            }
            .symBtn:active {
                transform: scale(1.0);
            }

            #symLoaderPager {
                display: flex;
                justify-content: center;
                align-items: center;
                margin-top: 5px;
                padding-top: 5px;
                border-top: 1px solid #3a3a3a; /* Separador */
            }
            #symLoaderPager .pager-button { /* Clase para los botones de paginación */
                background: #007bff;
                color: #fff;
                padding: 3px 8px;
                margin: 0 5px;
                font-size: 15px;
                border-radius: 4px;
                transition: background 0.2s ease;
            }
            #symLoaderPager .pager-button:hover {
                background: #0056b3;
            }
            #symLoaderPager .pager-button:disabled {
                background: #555;
                cursor: not-allowed;
            }
            #symLoaderPager span {
                font-size: 15px;
                color: #bbb;
            }
        `;
        document.head.appendChild(style);

        // --- Estructura HTML ---
        symLoaderBox = document.createElement('div');
        symLoaderBox.id = 'symLoaderBox';
        symLoaderBox.innerHTML = `
            <div id="symLoaderHeader">
                <span>Drawaria Symbols</span>
                <div>
                    <button id="symLoaderDL">💾</button>
                    <button id="symLoaderClose">❌</button>
                </div>
            </div>
            <div id="symLoaderSymbols">
                Cargando símbolos...
            </div>
            <div id="symLoaderPager">
                <!-- Los botones de paginación se insertarán aquí -->
            </div>
        `;
        document.body.appendChild(symLoaderBox);

        // --- Event Listeners ---
        document.getElementById('symLoaderClose').onclick = () => symLoaderBox.remove();
        document.getElementById('symLoaderDL').onclick = downloadJSON;

        // Delegación de eventos para los botones de símbolos (eficiente para muchos botones)
        document.getElementById('symLoaderBox').addEventListener('click', e => {
            if (e.target.classList.contains('symBtn')) {
                insertToChat(e.target.dataset.symbol);
            }
        });

        // --- Draggable functionality ---
        const symLoaderHeader = document.getElementById('symLoaderHeader');

        symLoaderHeader.addEventListener('mousedown', (e) => {
            isDragging = true;
            // Calcular el desplazamiento del cursor dentro del elemento
            offsetX = e.clientX - symLoaderBox.getBoundingClientRect().left;
            offsetY = e.clientY - symLoaderBox.getBoundingClientRect().top;
            symLoaderBox.style.cursor = 'grabbing'; // Cambiar cursor al arrastrar
            // Prevenir la selección de texto durante el arrastre
            symLoaderBox.style.userSelect = 'none';
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;

            // Calcular la nueva posición del elemento
            let newLeft = e.clientX - offsetX;
            let newTop = e.clientY - offsetY;

            // Opcional: Limitar el arrastre dentro de la ventana
            const maxX = window.innerWidth - symLoaderBox.offsetWidth;
            const maxY = window.innerHeight - symLoaderBox.offsetHeight;

            newLeft = Math.max(0, Math.min(newLeft, maxX));
            newTop = Math.max(0, Math.min(newTop, maxY));

            symLoaderBox.style.left = `${newLeft}px`;
            symLoaderBox.style.top = `${newTop}px`;
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
            symLoaderBox.style.cursor = 'grab'; // Restaurar cursor
            symLoaderBox.style.userSelect = 'auto'; // Habilitar selección de texto
        });
    }

    /**
     * Rellena el contenedor de símbolos con los símbolos de la página actual.
     * Siempre usa `allSymbols` ya que no hay filtro.
     * @param {number} page - El número de página a mostrar (0-indexado).
     */
    function populateSymbols(page = 0) {
        if (!allSymbols || allSymbols.length === 0) {
            fillSymbols('No hay símbolos disponibles.');
            updatePager(0, 0, 0); // Actualiza la paginación a "sin páginas"
            return;
        }

        currentPage = page;
        const wrap = document.getElementById('symLoaderSymbols');
        wrap.innerHTML = ''; // Limpia los símbolos anteriores

        let start = page * PAGE_SIZE;
        let end = Math.min(start + PAGE_SIZE, allSymbols.length);

        const fragment = document.createDocumentFragment(); // Para mejor rendimiento al añadir muchos elementos
        for (let i = start; i < end; i++) {
            const symbol = allSymbols[i];
            const button = document.createElement('button');
            button.classList.add('symBtn');
            button.dataset.symbol = symbol; // Almacena el símbolo en un atributo de datos
            button.textContent = symbol;
            fragment.appendChild(button);
        }
        wrap.appendChild(fragment);

        updatePager(page, allSymbols.length, PAGE_SIZE);
    }

    /**
     * Actualiza los botones y la información de la paginación.
     * @param {number} currentPage - La página actual (0-indexada).
     * @param {number} totalSymbols - El número total de símbolos disponibles.
     * @param {number} pageSize - El número de símbolos por página.
     */
    function updatePager(currentPage, totalSymbols, pageSize) {
        const pager = document.getElementById('symLoaderPager');
        pager.innerHTML = ''; // Limpia la paginación existente

        if (totalSymbols <= pageSize) {
            return; // No se necesita paginación si hay una sola página o menos
        }

        const totalPages = Math.ceil(totalSymbols / pageSize);

        // Botón "Anterior"
        const prevButton = document.createElement('button');
        prevButton.classList.add('pager-button');
        prevButton.textContent = '<';
        prevButton.disabled = currentPage === 0; // Deshabilitar si es la primera página
        prevButton.onclick = () => populateSymbols(currentPage - 1);
        pager.appendChild(prevButton);

        // Información de la página (ej: "1/5")
        const pageInfo = document.createElement('span');
        pageInfo.textContent = `${currentPage + 1}/${totalPages}`;
        pager.appendChild(pageInfo);

        // Botón "Siguiente"
        const nextButton = document.createElement('button');
        nextButton.classList.add('pager-button');
        nextButton.textContent = '>';
        nextButton.disabled = currentPage >= totalPages - 1; // Deshabilitar si es la última página
        nextButton.onclick = () => populateSymbols(currentPage + 1);
        pager.appendChild(nextButton);
    }

    /**
     * Rellena el contenedor de símbolos con un mensaje de estado.
     * @param {string} msg - El mensaje a mostrar.
     */
    function fillSymbols(msg) {
        document.getElementById('symLoaderSymbols').textContent = msg;
    }

    /**
     * Descarga los símbolos cargados como un archivo JSON.
     */
    function downloadJSON() {
        const a = document.createElement('a');
        // Convierte el array a JSON con indentación para que sea legible
        a.href = 'data:application/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(allSymbols, null, 2));
        a.download = 'BarsikSymbols.json';
        a.click();
    }

    /**
     * Inserta el texto dado en el campo de entrada del chat.
     * Se actualizó el selector para el chatbox de Drawaria.
     * @param {string} txt - El texto a insertar.
     */
    function insertToChat(txt) {
        const chatInput = document.getElementById('chatbox_textinput'); // Selector actualizado
        if (!chatInput) {
            alert('No se ha encontrado el input de chat con ID "chatbox_textinput".');
            return;
        }
        chatInput.value = txt;
        // Dispara un evento 'input' para asegurar que frameworks como React/Vue detecten el cambio
        chatInput.dispatchEvent(new Event('input', { bubbles: true }));
        // Opcional: enviar el mensaje (descomenta la siguiente línea, ¡bajo tu responsabilidad!)
        // document.querySelector('.chat__message-form button')?.click();
    }

    /**
     * Carga los símbolos desde la URL especificada.
     */
    async function loadSymbols() {
        fillSymbols("Cargando símbolos..."); // Mostrar mensaje de carga
        try {
            const response = await fetch(SYM_URL);
            if (!response.ok) {
                throw new Error(`Error HTTP! estado: ${response.status}`);
            }
            const obj = await response.json();
            // Soporta si el JSON es un array directamente o un objeto con una propiedad 'symbols'
            allSymbols = Array.isArray(obj) ? obj : obj.symbols ?? [];

            if (allSymbols.length === 0) {
                fillSymbols("No se encontraron símbolos en el archivo JSON o el formato es incorrecto.");
            } else {
                populateSymbols(0); // Carga la página inicial de símbolos
            }
        } catch (e) {
            console.error("Error al cargar símbolos:", e);
            fillSymbols(`Error al cargar símbolos: ${e.message}. Por favor, intenta recargar la página.`);
        }
    }

    // Inicializa la interfaz de usuario y carga los símbolos al iniciar el script
    createUI();
    loadSymbols();
})();