Este script é uma ferramenta simples para monitorar e exportar dados do jogo Elvenar diretamente do navegador. Vamos analisar seus principais componentes. O código é uma IIFE (Immediately Invoked Function Expression) que encapsula toda a lógica. Intercepta requisições HTTP (fetch e XMLHttpRequest) para captura. Painel lateral fixo com várias abas. Exporta scripts e JSONs para o computador do usuário

Principais Funcionalidades:

🕵️ Monitora requisições HTTP (scripts e JSONs) 📊 Analisa estruturas de dados complexas do jogo 💾 Exporta dados coletados para seu computador 📈 Dashboard personalizável para métricas importantes ⚙️ Configurações de performance ajustáveis

Como usar?

  1. Instale o Tampermonkey no seu navegador
  2. Adicione este script
  3. Acesse pt1.elvenar.com
  4. Um painel lateral aparecerá com todas as ferramentas

Benefícios:

  • Entenda melhor a mecânica do jogo
  • Exporte dados para análise externa
  • Monitore valores importantes em tempo real
    Dica: Use o Scraper para encontrar dados específicos nos JSONs do jogo!
// ==UserScript==
// @name         Elvenar Ultimate Traffic Exporter Lite
// @namespace    http://tampermonkey.net/
// @version      6.0
// @description  Versão simplificada sem monitor de tráfego
// @author       Você
// @match        https://pt1.elvenar.com/*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        unsafeWindow
// @run-at       document-start
// @connect      pt1.elvenar.com
// @connect      *
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==

(function() {
    'use strict';

    // 1. Configurações de performance
    const PERFORMANCE_SETTINGS = {
        MAX_JSON_FILES: 50,
        MAX_SCRIPTS: 50,
        MAX_RENDER_ITEMS: 100,
        ANALYSIS_BATCH_SIZE: 3,
        ANALYSIS_DELAY: 100,
        RENDER_DELAY: 50
    };

    // 1. Estilos CSS corrigidos
    const css = `
       #elvenarExporterPanel {
            position: fixed;
            left: 0;
            top: 0;
            height: 100vh;
            width: 50vw;
            max-width: 1200px;
            min-width: 800px;
            background: #1e1e1e;
            border-right: 1px solid #444;
            z-index: 2147483647;
            display: none;
            flex-direction: column;
            transition: transform 0.3s ease;
            box-shadow: 2px 0 10px rgba(0,0,0,0.5);
            font-family: 'Segoe UI', Arial, sans-serif;
            color: #e0e0e0;
            will-change: transform;
            contain: strict;
        }
        #elvenarExporterPanel.initialized { display: flex; }
        #elvenarExporterPanel.collapsed { transform: translateX(calc(-100% + 30px)); }

        .panel-header {
            padding: 12px 15px;
            background: #252526;
            border-bottom: 1px solid #444;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .panel-tabs {
            display: flex;
            background: #252526;
            border-bottom: 1px solid #444;
            overflow-x: auto;
        }
        .panel-tab {
            padding: 10px 15px;
            cursor: pointer;
            font-size: 13px;
            border-right: 1px solid #444;
            white-space: nowrap;
        }
        .panel-tab.active {
            background: #1e1e1e;
            color: #4CAF50;
            border-bottom: 2px solid #4CAF50;
        }
        .panel-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
        .tab-content { display: none; flex: 1; overflow: hidden; flex-direction: column; }
        .tab-content.active { display: flex; }
        .btn {
            background: #333;
            color: #fff;
            border: none;
            padding: 8px 12px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 13px;
            transition: background 0.2s;
        }
        .btn:hover { background: #444; }
        .btn-primary { background: #4CAF50; }
        .btn-primary:hover { background: #45a049; }
        .btn-danger { background: #f44336; }
        .btn-danger:hover { background: #d32f2f; }
        .section {
            margin-bottom: 15px;
            background: #252526;
            border-radius: 4px;
            overflow: hidden;
        }
        .section-header {
            padding: 8px 12px;
            background: #333;
            font-weight: bold;
            font-size: 14px;
        }
        .section-content {
            padding: 10px;
            max-height: 200px;
            overflow-y: auto;
        }
        .data-item {
            padding: 6px 0;
            border-bottom: 1px solid #333;
            font-size: 13px;
        }
        .progress-container {
            width: 100%;
            background: #333;
            border-radius: 4px;
            margin: 10px 0;
            overflow: hidden;
        }
        .progress-bar {
            height: 20px;
            background: #4CAF50;
            text-align: center;
            line-height: 20px;
            color: white;
            font-size: 12px;
            width: 0%;
            transition: width 0.3s;
        }
        .scraper-container {
            display: flex;
            flex-direction: column;
            height: 100%;
            padding: 15px;
        }
        .scraper-controls { margin-bottom: 15px; }
        .scraper-results { flex: 1; overflow-y: auto; }
        .json-item {
            padding: 8px;
            border-bottom: 1px solid #333;
            display: flex;
            align-items: center;
        }
        .json-path {
            flex: 1;
            font-family: monospace;
            font-size: 12px;
            word-break: break-all;
        }
        .json-value {
            flex: 1;
            font-family: monospace;
            font-size: 12px;
            color: #4CAF50;
            margin-left: 10px;
            word-break: break-all;
        }
        .json-actions { margin-left: 10px; }
        .json-action-btn {
            width: 24px;
            height: 24px;
            border-radius: 50%;
            border: none;
            cursor: pointer;
            font-weight: bold;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .json-action-btn.add { background: #4CAF50; color: white; }
        .json-action-btn.remove { background: #f44336; color: white; }
        .dashboard-card {
            background: #252526;
            border-radius: 4px;
            padding: 12px;
            margin-bottom: 10px;
            position: relative;
        }
        .dashboard-card-header {
            display: flex;
            align-items: center;
            margin-bottom: 8px;
        }
        .dashboard-icon {
            width: 24px;
            height: 24px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            margin-right: 8px;
            font-size: 12px;
        }
        .icon-number { background: #2196F3; }
        .icon-string { background: #4CAF50; }
        .icon-boolean { background: #FF9800; }
        .icon-array { background: #9C27B0; }
        .icon-object { background: #607D8B; }
        .icon-default { background: #795548; }
        .dashboard-card-title {
            font-weight: bold;
            flex: 1;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .dashboard-card-value {
            font-size: 14px;
            margin-bottom: 8px;
            word-break: break-all;
        }
        .dashboard-card-details {
            display: flex;
            justify-content: space-between;
            font-size: 11px;
            color: #aaa;
        }
        .dashboard-card-source {
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            max-width: 60%;
        }
        .dashboard-card-actions {
            position: absolute;
            top: 8px;
            right: 8px;
        }
        .collapse-handle {
            position: absolute;
            right: -25px;
            top: 50%;
            transform: translateY(-50%);
            width: 25px;
            height: 60px;
            background: #1e1e1e;
            border: 1px solid #444;
            border-left: none;
            border-radius: 0 4px 4px 0;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            font-size: 16px;
        }
        .search-box {
            padding: 8px;
            border-radius: 4px;
            border: 1px solid #444;
            background: #252526;
            color: #e0e0e0;
            width: 100%;
        }
        .dashboard-empty {
            text-align: center;
            padding: 30px;
            color: #aaa;
        }
        .dashboard-empty-icon {
            font-size: 40px;
            margin-bottom: 15px;
        }
        .type-filters {
            margin: 10px 0;
            display: flex;
            flex-wrap: wrap;
            gap: 5px;
        }
        .filter-btn {
            font-size: 12px;
            padding: 5px 8px;
        }
    `;

    // 3. Variáveis globais com limites de performance
    let collectedData = {
        scripts: [],
        jsonFiles: [],
        jsonAnalysis: {},
        dashboardItems: GM_getValue('dashboardItems', []),
        settings: GM_getValue('exporterSettings', {
            autoAnalyze: true,
            maxDepth: 3,
            analyzeImportantOnly: true,
            batchSize: PERFORMANCE_SETTINGS.ANALYSIS_BATCH_SIZE,
            useWebWorker: true,
            workerTimeout: 3000,
            renderLimit: PERFORMANCE_SETTINGS.MAX_RENDER_ITEMS
        }),
        isAnalyzing: false
    };

    // 4. Função para injetar CSS otimizada
    function injectStyles() {
        if (document.getElementById('elvenarExporterStyles')) return;

        const style = document.createElement('style');
        style.id = 'elvenarExporterStyles';
        style.textContent = css;
        (document.head || document.documentElement).appendChild(style);
    }

    // 5. Monitoramento simplificado com debounce
    function setupMonitoring() {
        const originalFetch = window.fetch;
        const originalXHROpen = XMLHttpRequest.prototype.open;
        const originalXHRSend = XMLHttpRequest.prototype.send;

        // Debounce para evitar chamadas excessivas
        const debouncedProcess = debounce(function(url, contentType, text) {
            if (contentType.includes('application/json')) {
                try {
                    const jsonData = JSON.parse(text);
                    addJsonFile(url, jsonData);
                } catch (e) {
                    console.error('Error parsing JSON:', e);
                }
            }
            else if (url.endsWith('.js') || contentType.includes('application/javascript')) {
                addScriptFile(url, text);
            }
        }, 200);

        window.fetch = async function(input, init) {
            const url = typeof input === 'string' ? input : input.url;
            const response = await originalFetch.apply(this, arguments);

            try {
                const clonedResponse = response.clone();
                const contentType = clonedResponse.headers.get('content-type') || '';
                const text = await clonedResponse.text();
                debouncedProcess(url, contentType, text);
            } catch (e) {
                console.error('Error processing fetch response:', e);
            }

            return response;
        };

        XMLHttpRequest.prototype.open = function(method, url) {
            this._requestUrl = url;
            return originalXHROpen.apply(this, arguments);
        };

        XMLHttpRequest.prototype.send = function(body) {
            const xhr = this;
            const url = xhr._requestUrl;

            const originalOnload = xhr.onload;
            const originalOnerror = xhr.onerror;

            xhr.onload = function(e) {
                try {
                    const contentType = xhr.getResponseHeader('content-type') || '';
                    debouncedProcess(url, contentType, xhr.responseText);
                } catch (e) {
                    console.error('Error processing XHR response:', e);
                }
                if (originalOnload) originalOnload.apply(xhr, arguments);
            };

            xhr.onerror = function(e) {
                if (originalOnerror) originalOnerror.apply(xhr, arguments);
            };

            return originalXHRSend.apply(xhr, arguments);
        };
    }

    // 6. Funções para adicionar arquivos com limites
    function addJsonFile(url, data) {
        try {
            if (!url || !data) return;

            // Normalizar URL
            if (url.startsWith('//')) {
                url = window.location.protocol + url;
            }

            // Verificar se já existe
            const existingIndex = collectedData.jsonFiles.findIndex(f => f.url === url);
            const timestamp = new Date().toISOString();

            if (existingIndex >= 0) {
                collectedData.jsonFiles[existingIndex] = {
                    url,
                    data,
                    timestamp,
                    size: JSON.stringify(data).length
                };
            } else {
                // Limitar quantidade de arquivos armazenados
                if (collectedData.jsonFiles.length >= PERFORMANCE_SETTINGS.MAX_JSON_FILES) {
                    collectedData.jsonFiles.pop();
                }

                collectedData.jsonFiles.unshift({
                    url,
                    data,
                    timestamp,
                    size: JSON.stringify(data).length
                });
            }

            // Atualização otimizada
            requestIdleCallback(() => {
                updateUI();
                if (collectedData.settings.autoAnalyze &&
                    (!collectedData.settings.analyzeImportantOnly || url.includes('/game/json?'))) {
                    setTimeout(() => analyzeJson(url), 300);
                }
            });

            saveData();
        } catch (e) {
            console.error('Error adding JSON file:', e);
        }
    }

    function addScriptFile(url, content) {
        if (!url || !content) return;

        // Limitar quantidade de scripts armazenados
        if (collectedData.scripts.length >= PERFORMANCE_SETTINGS.MAX_SCRIPTS) {
            collectedData.scripts.pop();
        }

        const existingIndex = collectedData.scripts.findIndex(s => s.url === url);
        const timestamp = new Date().toISOString();

        if (existingIndex >= 0) {
            collectedData.scripts[existingIndex] = {
                url,
                content,
                loaded: document.querySelector(`script[src="${url}"]`) !== null,
                timestamp,
                size: content.length
            };
        } else {
            collectedData.scripts.unshift({
                url,
                content,
                loaded: document.querySelector(`script[src="${url}"]`) !== null,
                timestamp,
                size: content.length
            });
        }

        requestIdleCallback(() => {
            updateUI();
            saveData();
        });
    }

    // 7. Funções de análise otimizadas
    function analyzeJson(url) {
        const jsonFile = collectedData.jsonFiles.find(f => f.url === url);
        if (!jsonFile) return;

        if (!collectedData.jsonAnalysis[url]) {
            collectedData.jsonAnalysis[url] = {
                analyzed: new Date().toISOString(),
                items: []
            };
        }

        // Usar requestIdleCallback para análise não crítica
        requestIdleCallback(() => {
            const results = [];
            flattenJson(jsonFile.data, results, '', collectedData.settings.maxDepth);

            collectedData.jsonAnalysis[url].items = results.slice(0, PERFORMANCE_SETTINGS.MAX_RENDER_ITEMS);
            saveData();

            if ($('#devtoolsTab').hasClass('active')) {
                displayJsonAnalysis();
            }
        }, { timeout: 1000 });
    }

    function flattenJson(obj, results, path, maxDepth, depth = 0) {
        if (depth > maxDepth || results.length >= PERFORMANCE_SETTINGS.MAX_RENDER_ITEMS) return;

        if (obj !== null && typeof obj === 'object') {
            for (const key in obj) {
                if (obj.hasOwnProperty(key)) {
                    const newPath = path ? path + '.' + key : key;
                    flattenJson(obj[key], results, newPath, maxDepth, depth + 1);
                }
            }
        } else {
            results.push({
                path,
                value: obj,
                type: typeof obj,
                fullPath: path
            });
        }
    }

    // 7. Funções de UI simplificadas
    function updateUI() {
        updateScriptsList();
        updateJsonsList();
        updateExportStats();
    }

    function updateScriptsList() {
        $('#scriptCount').text(collectedData.scripts.length);
    }

    function updateJsonsList() {
        $('#jsonCount').text(collectedData.jsonFiles.length);
    }

    function updateExportStats() {
        $('#exportScriptCount').text(collectedData.scripts.length);
        $('#exportJsonCount').text(collectedData.jsonFiles.length);
    }

    // 8. Funções do painel simplificado
    function createPanel() {
        if (document.getElementById('elvenarExporterPanel')) return;

        const panelHTML = `
            <div class="panel-header">
                <div class="panel-title">Elvenar Ultimate Exporter Lite</div>
                <button class="btn" id="togglePanel">☰</button>
            </div>
            <div class="panel-tabs">
                <div class="panel-tab active" data-tab="resources">Recursos</div>
                <div class="panel-tab" data-tab="export">Exportar</div>
                <div class="panel-tab" data-tab="scraper">Scraper</div>
                <div class="panel-tab" data-tab="dashboard">Dashboard</div>
                <div class="panel-tab" data-tab="settings">Configurações</div>
            </div>
            <div class="panel-content">
                <!-- Tab Recursos simplificado -->
                <div id="resourcesTab" class="tab-content active">
                    <div style="padding: 15px;">
                        <div class="section">
                            <div class="section-header">Scripts Carregados (<span id="scriptCount">0</span>)</div>
                        </div>
                        <div class="section">
                            <div class="section-header">JSONs Detectados (<span id="jsonCount">0</span>)</div>
                        </div>
                    </div>
                </div>

                <!-- Tab Exportar -->
                <div id="exportTab" class="tab-content">
                    <div style="padding: 15px; height: 100%; overflow-y: auto;">
                        <div class="export-section">
                            <h3 style="margin-top: 0;">Exportar Dados</h3>
                            <div class="export-buttons" style="display: flex; gap: 10px; margin-bottom: 15px;">
                                <button class="btn btn-primary" id="exportJsBtn">Exportar Todos JS</button>
                                <button class="btn btn-primary" id="exportJsonBtn">Exportar Todos JSON</button>
                            </div>
                            <div class="progress-container">
                                <div class="progress-bar" id="progressBar">0%</div>
                            </div>
                        </div>
                        <div class="export-section">
                            <h3>Estatísticas</h3>
                            <div id="exportStats" style="font-size: 13px;">
                                Scripts: <span id="exportScriptCount">0</span><br>
                                JSONs: <span id="exportJsonCount">0</span>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Tab Scraper -->
                <div id="scraperTab" class="tab-content">
                    <div class="scraper-container">
                        <div class="scraper-controls">
                            <input type="text" class="search-box" id="jsonSearch" placeholder="Procurar em JSONs..." style="width: 100%; margin-bottom: 10px;">
                            <div class="type-filters">
                                <button class="btn filter-btn" data-type="all">Todos</button>
                                <button class="btn filter-btn" data-type="number">
                                    <span class="dashboard-icon icon-number" style="margin-right: 5px;"></span>Números
                                </button>
                                <button class="btn filter-btn" data-type="string">
                                    <span class="dashboard-icon icon-string" style="margin-right: 5px;"></span>Strings
                                </button>
                                <button class="btn filter-btn" data-type="boolean">
                                    <span class="dashboard-icon icon-boolean" style="margin-right: 5px;"></span>Booleanos
                                </button>
                                <button class="btn filter-btn" data-type="array">
                                    <span class="dashboard-icon icon-array" style="margin-right: 5px;"></span>Arrays
                                </button>
                                <button class="btn filter-btn" data-type="object">
                                    <span class="dashboard-icon icon-object" style="margin-right: 5px;"></span>Objetos
                                </button>
                            </div>
                            <div style="display: flex; gap: 10px; margin-bottom: 10px;">
                                <button class="btn btn-primary" id="analyzeJsonBtn">Analisar JSONs</button>
                                <button class="btn" id="clearSearchBtn">Limpar</button>
                            </div>
                            <div id="jsonAnalysisStatus" style="margin-top: 10px; font-size: 12px;"></div>
                        </div>
                        <div class="scraper-results" id="jsonAnalysisResults"></div>
                    </div>
                </div>

                <!-- Tab Dashboard -->
                <div id="dashboardTab" class="tab-content">
                    <div class="dashboard-header" style="padding: 15px; border-bottom: 1px solid #444; display: flex; justify-content: space-between; align-items: center;">
                        <div class="dashboard-title">Dashboard de Monitoramento</div>
                        <button class="refresh-btn btn" id="refreshDashboardBtn" title="Atualizar">↻</button>
                    </div>
                    <div class="dashboard-items" id="dashboardItemsContainer" style="padding: 15px; overflow-y: auto;"></div>
                </div>

                <!-- Tab Configurações -->
                <div id="settingsTab" class="tab-content">
                    <div style="padding: 15px;">
                        <h3 style="margin-top: 0;">Configurações do Exporter</h3>

                        <div class="section">
                            <div class="section-header">Análise Automática</div>
                            <div class="section-content">
                                <div class="auto-analysis-toggle">
                                    <label class="toggle-switch">
                                        <input type="checkbox" id="autoAnalyzeToggle" ${collectedData.settings.autoAnalyze ? 'checked' : ''}>
                                        <span class="toggle-slider"></span>
                                    </label>
                                    <label for="autoAnalyzeToggle">Ativar análise automática de JSONs</label>
                                </div>

                                <div class="auto-analysis-toggle" style="margin-top: 10px;">
                                    <label class="toggle-switch">
                                        <input type="checkbox" id="analyzeImportantOnlyToggle" ${collectedData.settings.analyzeImportantOnly ? 'checked' : ''}>
                                        <span class="toggle-slider"></span>
                                    </label>
                                    <label for="analyzeImportantOnlyToggle">Analisar apenas JSONs importantes (/game/json?)</label>
                                </div>

                                <div style="margin-top: 15px;">
                                    <label for="maxDepthInput">Profundidade máxima de análise:</label>
                                    <input type="number" id="maxDepthInput" min="1" max="10" value="${collectedData.settings.maxDepth}" style="width: 50px; margin-left: 10px;">
                                </div>

                                <div style="margin-top: 15px;">
                                    <label for="batchSizeInput">Tamanho do lote para análise:</label>
                                    <input type="number" id="batchSizeInput" min="1" max="20" value="${collectedData.settings.batchSize}" style="width: 50px; margin-left: 10px;">
                                </div>

                                <div class="auto-analysis-toggle" style="margin-top: 10px;">
                                    <label class="toggle-switch">
                                        <input type="checkbox" id="useWebWorkerToggle" ${collectedData.settings.useWebWorker ? 'checked' : ''}>
                                        <span class="toggle-slider"></span>
                                    </label>
                                    <label for="useWebWorkerToggle">Usar Web Workers (recomendado)</label>
                                </div>

                                <button class="btn btn-primary" id="saveSettingsBtn" style="margin-top: 15px;">Salvar Configurações</button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="collapse-handle" id="collapseHandle">❮</div>
        `;

        const panel = document.createElement('div');
        panel.id = 'elvenarExporterPanel';
        panel.innerHTML = panelHTML;
        document.body.appendChild(panel);

        setupPanelEvents();
        panel.classList.add('initialized');
        updateUI();
        updateDashboard();
    }

    function setupPanelEvents() {
        // Botão de recolher
        $(document).on('click', '#collapseHandle', function() {
            const panel = $('#elvenarExporterPanel');
            panel.toggleClass('collapsed');
            $(this).text(panel.hasClass('collapsed') ? '❯' : '❮');
        });

        // Alternar abas
        $(document).on('click', '.panel-tab', function() {
            $('.panel-tab').removeClass('active');
            $('.tab-content').removeClass('active');

            $(this).addClass('active');
            const tabId = $(this).data('tab') + 'Tab';
            $('#' + tabId).addClass('active');

            if ($(this).data('tab') === 'scraper') {
                if (Object.keys(collectedData.jsonAnalysis).length > 0) {
                    displayJsonAnalysis();
                } else {
                    analyzeJsonFiles(true);
                }
            } else if ($(this).data('tab') === 'dashboard') {
                updateDashboard();
            }
        });

        // Botões de ação
        $('#exportJsBtn').click(exportAllScripts);
        $('#exportJsonBtn').click(exportAllJson);
        $('#analyzeJsonBtn').click(function() { analyzeJsonFiles(false); });
        $('#clearSearchBtn').click(function() {
            $('#jsonSearch').val('');
            displayJsonAnalysis();
        });
        $('#refreshDashboardBtn').click(updateDashboard);
        $('#saveSettingsBtn').click(saveSettings);

        // Evento de pesquisa
        $('#jsonSearch').keyup(function(e) {
            if (e.key === 'Enter') {
                analyzeJsonFiles(false);
            }
        });

        // Filtros por tipo
        $('.filter-btn').click(function() {
            const type = $(this).data('type');
            $('.filter-btn').removeClass('btn-primary');

            if (type === 'all') {
                $(this).addClass('btn-primary');
                $('.json-item').show();
            } else {
                $(this).addClass('btn-primary');
                $('.json-item').each(function() {
                    const icon = $(this).find('.dashboard-icon')[0];
                    if (icon) {
                        const itemType = icon.className.includes(type) ? type :
                                        type === 'array' && icon.className.includes('icon-array') ? 'array' :
                                        type === 'object' && icon.className.includes('icon-object') ? 'object' : null;
                        $(this).toggle(itemType === type);
                    }
                });
            }
        });

        // Ativar filtro "Todos" por padrão
        $('.filter-btn[data-type="all"]').addClass('btn-primary').click();
    }

    function saveSettings() {
        collectedData.settings = {
            autoAnalyze: $('#autoAnalyzeToggle').is(':checked'),
            analyzeImportantOnly: $('#analyzeImportantOnlyToggle').is(':checked'),
            maxDepth: parseInt($('#maxDepthInput').val()) || 5,
            batchSize: parseInt($('#batchSizeInput').val()) || 3,
            useWebWorker: $('#useWebWorkerToggle').is(':checked')
        };

        GM_setValue('exporterSettings', collectedData.settings);
        alert('Configurações salvas com sucesso!');
    }

    // 9. Funções de exportação
    async function exportAllScripts() {
        const progressBar = $('#progressBar');
        progressBar.css('width', '0%').text('0%');

        const scriptsToExport = collectedData.scripts.filter(script => {
            try {
                new URL(script.url);
                return true;
            } catch {
                return false;
            }
        });

        const total = scriptsToExport.length;
        let completed = 0;

        if (total === 0) {
            progressBar.text('Nada para exportar');
            return;
        }

        for (const script of scriptsToExport) {
            try {
                const filename = script.url.split('/').pop() || 'script_' + Date.now() + '.js';
                const content = script.content || '';

                if (typeof GM_download !== 'undefined') {
                    await new Promise((resolve) => {
                        GM_download({
                            url: URL.createObjectURL(new Blob([content], {type: 'application/javascript'})),
                            name: filename,
                            saveAs: true,
                            onload: resolve,
                            onerror: function(err) {
                                console.error('Download failed:', err);
                                resolve();
                            }
                        });
                    });
                } else {
                    const a = document.createElement('a');
                    a.href = URL.createObjectURL(new Blob([content], {type: 'application/javascript'}));
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                    setTimeout(function() {
                        document.body.removeChild(a);
                        URL.revokeObjectURL(a.href);
                    }, 100);
                }
            } catch (e) {
                console.error('Falha ao exportar ' + script.url + ':', e);
            } finally {
                completed++;
                const progress = Math.round((completed / total) * 100);
                progressBar.css('width', progress + '%').text(progress + '%');
            }
        }
    }

    async function exportAllJson() {
        const progressBar = $('#progressBar');
        progressBar.css('width', '0%').text('0%');

        const total = collectedData.jsonFiles.length;
        let completed = 0;

        if (total === 0) {
            progressBar.text('Nada para exportar');
            return;
        }

        for (const jsonFile of collectedData.jsonFiles) {
            try {
                const filename = jsonFile.url.split('/').pop() || 'data_' + Date.now() + '.json';
                const content = JSON.stringify(jsonFile.data, null, 2);

                if (typeof GM_download !== 'undefined') {
                    await new Promise((resolve) => {
                        GM_download({
                            url: URL.createObjectURL(new Blob([content], {type: 'application/json'})),
                            name: filename,
                            saveAs: true,
                            onload: resolve,
                            onerror: function(err) {
                                console.error('Download failed:', err);
                                resolve();
                            }
                        });
                    });
                } else {
                    const a = document.createElement('a');
                    a.href = URL.createObjectURL(new Blob([content], {type: 'application/json'}));
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                    setTimeout(function() {
                        document.body.removeChild(a);
                        URL.revokeObjectURL(a.href);
                    }, 100);
                }
            } catch (e) {
                console.error('Falha ao exportar JSON:', jsonFile.url, e);
            } finally {
                completed++;
                const progress = Math.round((completed / total) * 100);
                progressBar.css('width', progress + '%').text(progress + '%');
            }
        }
    }

    // 10. Funções de análise otimizadas
    function analyzeJsonFiles(initialLoad) {
        if (collectedData.isAnalyzing) {
            console.log('Análise já em andamento...');
            return;
        }

        collectedData.isAnalyzing = true;
        const searchTerm = $('#jsonSearch').val().toLowerCase();
        const statusElement = $('#jsonAnalysisStatus');

        statusElement.text('Preparando análise...').css('color', '#4CAF50');
        $('#analyzeJsonBtn, #clearSearchBtn').prop('disabled', true);

        if (initialLoad) {
            $('#jsonAnalysisResults').html('<div class="data-item">Carregando análise inicial...</div>');
        } else {
            $('#jsonAnalysisResults').empty();
        }

        if (collectedData.settings.useWebWorker && window.Worker) {
            analyzeWithWebWorker(searchTerm, statusElement);
        } else {
            analyzeWithTimeout(searchTerm, statusElement, initialLoad);
        }
    }

    function analyzeWithWebWorker(searchTerm, statusElement) {
        statusElement.text('Iniciando análise em segundo plano...');

        const workerCode = `
            self.onmessage = function(e) {
                const { jsonData, searchTerm, maxDepth } = e.data;
                const results = [];

                function flatten(obj, path, depth) {
                    if (depth > maxDepth) return;

                    if (obj !== null && typeof obj === 'object') {
                        for (const key in obj) {
                            if (obj.hasOwnProperty(key)) {
                                const newPath = path ? path + '.' + key : key;
                                flatten(obj[key], newPath, depth + 1);
                            }
                        }
                    } else {
                        if (!searchTerm ||
                            path.toLowerCase().includes(searchTerm) ||
                            String(obj).toLowerCase().includes(searchTerm)) {
                            results.push({
                                path: path,
                                value: obj,
                                type: typeof obj,
                                fullPath: path
                            });
                        }
                    }
                }

                flatten(jsonData, '', 0);
                postMessage(results);
            };
        `;

        const workerBlob = new Blob([workerCode], { type: 'application/javascript' });
        const workerUrl = URL.createObjectURL(workerBlob);
        const worker = new Worker(workerUrl);

        let processedFiles = 0;
        const totalFiles = collectedData.jsonFiles.length;
        let allResults = {};

        worker.onmessage = function(e) {
            const jsonFile = collectedData.jsonFiles[processedFiles - 1];
            if (e.data.length > 0) {
                allResults[jsonFile.url] = {
                    analyzed: new Date().toISOString(),
                    items: e.data
                };
            }

            statusElement.text('Processando: ' + processedFiles + '/' + totalFiles + ' arquivos...');

            if (processedFiles < totalFiles) {
                processNextFile();
            } else {
                finalizeAnalysis();
            }
        };

        worker.onerror = function(e) {
            console.error('Worker error:', e);
            statusElement.text('Erro no worker!').css('color', '#f44336');
            collectedData.isAnalyzing = false;
            $('#analyzeJsonBtn, #clearSearchBtn').prop('disabled', false);
        };

        function processNextFile() {
            if (processedFiles < totalFiles) {
                const jsonFile = collectedData.jsonFiles[processedFiles++];
                worker.postMessage({
                    jsonData: jsonFile.data,
                    searchTerm: searchTerm,
                    maxDepth: collectedData.settings.maxDepth
                });
            }
        }

        function finalizeAnalysis() {
            worker.terminate();
            URL.revokeObjectURL(workerUrl);

            collectedData.jsonAnalysis = allResults;
            collectedData.isAnalyzing = false;

            statusElement.text('Análise completa! ' +
                Object.values(allResults).reduce((a, b) => a + b.items.length, 0) +
                ' itens encontrados.').css('color', '#4CAF50');

            $('#analyzeJsonBtn, #clearSearchBtn').prop('disabled', false);
            saveData();
            displayJsonAnalysis();
        }

        processNextFile();
    }

    function analyzeWithTimeout(searchTerm, statusElement, initialLoad) {
        statusElement.text('Usando método seguro (pode ser mais lento)...');

        let currentIndex = 0;
        const totalFiles = collectedData.jsonFiles.length;
        let allResults = {};

        function processBatch() {
            const startTime = performance.now();
            let processedInBatch = 0;

            while (currentIndex < totalFiles &&
                   processedInBatch < collectedData.settings.batchSize &&
                   performance.now() - startTime < 100) {

                const jsonFile = collectedData.jsonFiles[currentIndex++];
                processedInBatch++;

                const results = [];
                flattenJsonWithTimeout(jsonFile.data, results, '', collectedData.settings.maxDepth, searchTerm);

                if (results.length > 0) {
                    allResults[jsonFile.url] = {
                        analyzed: new Date().toISOString(),
                        items: results
                    };
                }
            }

            statusElement.text('Processando: ' + currentIndex + '/' + totalFiles + ' arquivos...');

            if (initialLoad || searchTerm) {
                displayPartialResults(allResults);
            }

            if (currentIndex < totalFiles) {
                setTimeout(processBatch, 50);
            } else {
                finalizeAnalysis();
            }
        }

        function flattenJsonWithTimeout(obj, results, path, maxDepth, searchTerm, depth) {
            depth = depth || 0;
            if (depth > maxDepth) return;

            if (obj !== null && typeof obj === 'object') {
                const keys = Object.keys(obj);
                for (let i = 0; i < keys.length; i++) {
                    const key = keys[i];
                    const newPath = path ? path + '.' + key : key;
                    flattenJsonWithTimeout(obj[key], results, newPath, maxDepth, searchTerm, depth + 1);
                }
            } else {
                if (!searchTerm ||
                    path.toLowerCase().includes(searchTerm) ||
                    String(obj).toLowerCase().includes(searchTerm)) {
                    results.push({
                        path: path,
                        value: obj,
                        type: typeof obj,
                        fullPath: path
                    });
                }
            }
        }

        function finalizeAnalysis() {
            collectedData.jsonAnalysis = allResults;
            collectedData.isAnalyzing = false;

            statusElement.text('Análise completa! ' +
                Object.values(allResults).reduce((a, b) => a + b.items.length, 0) +
                ' itens encontrados.').css('color', '#4CAF50');

            $('#analyzeJsonBtn, #clearSearchBtn').prop('disabled', false);
            saveData();
            displayJsonAnalysis();
        }

        processBatch();
    }

    function displayPartialResults(resultsData) {
        const container = $('#jsonAnalysisResults');

        if (Object.keys(resultsData).length === collectedData.settings.batchSize) {
            container.empty();
        }

        const lastUrl = Object.keys(resultsData).pop();
        const lastAnalysis = resultsData[lastUrl];

        if (lastAnalysis && lastAnalysis.items.length > 0) {
            const fileDiv = $('<div>').addClass('scraper-file');
            const fileName = lastUrl.split('/').pop() || 'dynamic_data';

            const fileHeader = $('<div>').addClass('section-header')
                .text('Arquivo: ' + fileName + ' (' + lastAnalysis.items.length + ' itens)')
                .attr('title', lastUrl);

            const contentDiv = $('<div>').addClass('section-content');

            lastAnalysis.items.slice(0, 50).forEach(function(item) {
                renderJsonItem(item, lastUrl, contentDiv);
            });

            if (lastAnalysis.items.length > 50) {
                contentDiv.append('<div class="data-item">...</div>');
            }

            fileDiv.append(fileHeader, contentDiv);
            container.append(fileDiv);
        }
    }

    function renderJsonItem(item, url, container) {
        try {
            const itemDiv = $('<div>').addClass('json-item');

            const typeIcon = $('<span>').addClass('dashboard-icon ' + getValueIcon(item.value))
                .css({ display: 'inline-block', marginRight: '5px', width: '12px', height: '12px' });

            const pathSpan = $('<div>').addClass('json-path')
                .text(item.path || 'N/A')
                .attr('title', item.path)
                .css('cursor', 'pointer')
                .click(function() {
                    const pathParts = item.path.split('.');
                    const newSearch = pathParts[pathParts.length - 1];
                    $('#jsonSearch').val(newSearch);
                    analyzeJsonFiles(false);
                });

            let valueText = 'N/A';
            if (item.value !== undefined && item.value !== null) {
                if (typeof item.value === 'object') {
                    valueText = JSON.stringify(item.value, null, 2);
                } else {
                    valueText = String(item.value);
                }
            }

            const isTruncated = valueText.length > 100;
            const displayValue = isTruncated ? valueText.substring(0, 97) + '...' : valueText;

            const valueDiv = $('<div>').addClass('json-value')
                .text(displayValue)
                .attr('title', valueText)
                .css('cursor', isTruncated ? 'pointer' : 'default')
                .click(function() {
                    if (isTruncated) {
                        if ($(this).data('expanded')) {
                            $(this).text(displayValue).removeData('expanded');
                        } else {
                            $(this).text(valueText).data('expanded', true);
                        }
                    }
                });

            const actionsDiv = $('<div>').addClass('json-actions');
            const isInDashboard = collectedData.dashboardItems.some(function(di) {
                return di.path === item.fullPath && di.sourceUrl === url;
            });

            const actionBtn = $('<button>')
                .addClass('json-action-btn ' + (isInDashboard ? 'remove' : 'add'))
                .text(isInDashboard ? '-' : '+')
                .attr('title', isInDashboard ? 'Remover do Dashboard' : 'Adicionar ao Dashboard')
                .click(function() {
                    if (isInDashboard) {
                        removeFromDashboard(item.fullPath, url);
                    } else {
                        addToDashboard(item.fullPath, url, item.value);
                    }
                });

            const copyBtn = $('<button>')
                .addClass('json-action-btn')
                .text('⎘')
                .attr('title', 'Copiar valor')
                .css('background', '#2196F3')
                .click(function() {
                    navigator.clipboard.writeText(valueText).then(function() {
                        const originalText = copyBtn.text();
                        copyBtn.text('✓');
                        setTimeout(function() { copyBtn.text(originalText); }, 1000);
                    });
                });

            actionsDiv.append(actionBtn, copyBtn);
            itemDiv.append(typeIcon, pathSpan, valueDiv, actionsDiv);
            container.append(itemDiv);
        } catch (e) {
            console.error('Erro ao renderizar item:', e);
        }
    }

    function displayJsonAnalysis() {
        const container = $('#jsonAnalysisResults');
        container.empty();

        if (collectedData.jsonFiles.length === 0) {
            container.html('<div class="data-item">Nenhum JSON coletado ainda.</div>');
            return;
        }

        try {
            if (!$('#jsonSearch').val()) {
                const allResults = {};
                Object.keys(collectedData.jsonAnalysis).forEach(function(url) {
                    if (collectedData.jsonAnalysis[url].items.length > 0) {
                        allResults[url] = collectedData.jsonAnalysis[url];
                    }
                });

                if (Object.keys(allResults).length === 0) {
                    container.html('<div class="data-item">Nenhum dado analisado ainda. Clique em "Analisar JSONs".</div>');
                    return;
                }

                displayFullResults(allResults);
            }
        } catch (e) {
            console.error('Erro ao exibir análise:', e);
            container.html('<div class="data-item">Ocorreu um erro ao exibir a análise.</div>');
        }
    }

    function displayFullResults(resultsData) {
        const container = $('#jsonAnalysisResults');

        const groupedResults = {};
        Object.keys(resultsData).forEach(function(url) {
            try {
                // Fix URLs missing protocol
                let fullUrl = url;
                if (url.startsWith('//')) {
                    fullUrl = window.location.protocol + url;
                } else if (!url.includes('://')) {
                    fullUrl = window.location.protocol + '//' + url;
                }

                const urlObj = new URL(fullUrl);
                const domain = urlObj.hostname;
                const path = urlObj.pathname.split('/').slice(0, -1).join('/');
                const groupKey = domain + path;

                if (!groupedResults[groupKey]) {
                    groupedResults[groupKey] = {
                        domain: domain,
                        path: path,
                        files: []
                    };
                }

                groupedResults[groupKey].files.push({
                    url: url,
                    items: resultsData[url].items
                });
            } catch (e) {
                console.error('Error processing URL:', url, e);
                // Fallback for malformed URLs
                const groupKey = 'other';
                if (!groupedResults[groupKey]) {
                    groupedResults[groupKey] = {
                        domain: 'Other',
                        path: '',
                        files: []
                    };
                }
                groupedResults[groupKey].files.push({
                    url: url,
                    items: resultsData[url].items
                });
            }
        });

        Object.keys(groupedResults)
            .sort(function(a, b) { return a.localeCompare(b); })
            .forEach(function(groupKey) {
                const group = groupedResults[groupKey];
                const groupDiv = $('<div>').addClass('scraper-group');

                const groupHeader = $('<div>').addClass('section-header')
                    .text(group.domain + group.path)
                    .click(function() {
                        $(this).next('.group-content').toggle();
                    });

                const groupContent = $('<div>').addClass('group-content');

                group.files.forEach(function(file) {
                    const fileDiv = $('<div>').addClass('scraper-file');
                    const fileName = file.url.split('/').pop() || 'dynamic_data';

                    const fileHeader = $('<div>').addClass('section-header')
                        .text('Arquivo: ' + fileName + ' (' + file.items.length + ' itens)')
                        .attr('title', file.url)
                        .click(function() {
                            $(this).next('.section-content').toggle();
                        });

                    const contentDiv = $('<div>').addClass('section-content');

                    file.items.forEach(function(item) {
                        renderJsonItem(item, file.url, contentDiv);
                    });

                    fileDiv.append(fileHeader, contentDiv);
                    groupContent.append(fileDiv);
                });

                groupDiv.append(groupHeader, groupContent);
                container.append(groupDiv);
            });
    }

    function addToDashboard(path, sourceUrl, initialValue) {
        if (!collectedData.dashboardItems.some(function(item) {
            return item.path === path && item.sourceUrl === sourceUrl;
        })) {
            collectedData.dashboardItems.push({
                path,
                sourceUrl,
                lastValue: initialValue,
                lastUpdated: new Date().toISOString(),
                valueType: typeof initialValue,
                displayName: getFriendlyName(path)
            });
            saveData();
            updateDashboard();
            displayJsonAnalysis();
        }
    }

    function removeFromDashboard(path, sourceUrl) {
        collectedData.dashboardItems = collectedData.dashboardItems.filter(function(item) {
            return !(item.path === path && item.sourceUrl === sourceUrl);
        });
        saveData();
        updateDashboard();
        displayJsonAnalysis();
    }

    function updateDashboard() {
        const container = $('#dashboardItemsContainer');

        if (!container.length) return;

        if (collectedData.dashboardItems.length === 0) {
            container.html(`
                <div class="dashboard-empty">
                    <div class="dashboard-empty-icon">📊</div>
                    <div>Nenhum item adicionado ao dashboard</div>
                    <div style="margin-top: 10px; font-size: 12px;">Use o Scraper para adicionar itens para monitorar</div>
                </div>
            `);
            return;
        }

        container.empty();

        collectedData.dashboardItems.forEach(function(item) {
            try {
                const jsonFile = collectedData.jsonFiles.find(function(f) { return f.url === item.sourceUrl; });
                if (jsonFile) {
                    const value = getValueByPath(jsonFile.data, item.path);
                    item.lastValue = value;
                    item.lastUpdated = new Date().toISOString();
                    item.valueType = typeof value;
                }
            } catch (e) {
                console.error('Erro ao atualizar item do dashboard ' + item.path + ':', e);
                item.lastValue = 'Erro: ' + e.message;
            }
        });

        saveData();

        collectedData.dashboardItems.forEach(function(item) {
            const iconType = getValueIcon(item.lastValue);
            const formattedValue = formatValue(item.lastValue);
            const sourceName = item.sourceUrl.split('/').pop().split('.')[0] || 'Fonte';
            const updatedTime = new Date(item.lastUpdated).toLocaleTimeString();

            const card = $(`
                <div class="dashboard-card">
                    <div class="dashboard-card-header">
                        <div class="dashboard-icon ${iconType}"></div>
                        <div class="dashboard-card-title" title="${item.path}">${item.displayName || getFriendlyName(item.path)}</div>
                    </div>
                    <div class="dashboard-card-value" title="${formattedValue}">${formattedValue}</div>
                    <div class="dashboard-card-details">
                        <div class="dashboard-card-source" title="${item.sourceUrl}">${sourceName}</div>
                        <div>${updatedTime}</div>
                    </div>
                    <div class="dashboard-card-actions">
                        <button class="json-action-btn remove" title="Remover do Dashboard">−</button>
                    </div>
                </div>
            `);

            card.find('.json-action-btn.remove').click(function() {
                removeFromDashboard(item.path, item.sourceUrl);
            });

            container.append(card);
        });
    }

    function getValueIcon(value) {
        if (typeof value === 'number') return 'icon-number';
        if (typeof value === 'string') return 'icon-string';
        if (typeof value === 'boolean') return 'icon-boolean';
        if (Array.isArray(value)) return 'icon-array';
        if (typeof value === 'object' && value !== null) return 'icon-object';
        return 'icon-default';
    }

    function formatValue(value) {
        if (value === null) return 'null';
        if (value === undefined) return 'undefined';
        if (typeof value === 'boolean') return value ? '✔ Verdadeiro' : '✖ Falso';
        if (typeof value === 'object') {
            const str = JSON.stringify(value);
            return str.length > 50 ? str.substring(0, 47) + '...' : str;
        }
        return value.toString();
    }

    function getFriendlyName(path) {
        const lastPart = path.split('.').pop() || path;
        return lastPart
            .replace(/([A-Z])/g, ' $1')
            .replace(/_/g, ' ')
            .replace(/^./, function(str) { return str.toUpperCase(); })
            .trim();
    }

    function getValueByPath(obj, path) {
        const parts = path.split('.');
        let current = obj;

        for (const part of parts) {
            if (current && current.hasOwnProperty(part)) {
                current = current[part];
            } else {
                throw new Error('Caminho não encontrado: ' + part + ' em ' + path);
            }
        }

        return current;
    }

    // 11. Funções de persistência
    function saveData() {
        collectedData.lastUpdate = new Date().toISOString();
        GM_setValue('elvenarExporterData', collectedData);
        GM_setValue('dashboardItems', collectedData.dashboardItems);
    }

    function loadData() {
        const saved = GM_getValue('elvenarExporterData');
        if (saved) {
            collectedData = {
                scripts: saved.scripts || [],
                jsonFiles: saved.jsonFiles || [],
                jsonAnalysis: saved.jsonAnalysis || {},
                dashboardItems: GM_getValue('dashboardItems', []),
                settings: GM_getValue('exporterSettings', {
                    autoAnalyze: true,
                    maxDepth: 5,
                    analyzeImportantOnly: true,
                    batchSize: 3,
                    useWebWorker: true
                })
            };
            updateUI();
        }
    }

    // 12. Inicialização
    function init() {
        // Verificar se jQuery está carregado
        if (typeof jQuery === 'undefined') {
            setTimeout(init, 100);
            return;
        }

        injectStyles();
        setupMonitoring();
        loadData();

        // Adiar a criação do painel para não bloquear o carregamento
        if (document.readyState === 'complete') {
            setTimeout(createPanel, 2000);
        } else {
            window.addEventListener('load', () => setTimeout(createPanel, 2000));
        }
    }

    // Helper functions
    function debounce(func, wait) {
        let timeout;
        return function() {
            const context = this, args = arguments;
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(context, args), wait);
        };
    }

    function requestIdleCallback(callback, options) {
        if ('requestIdleCallback' in window) {
            return window.requestIdleCallback(callback, options);
        } else {
            return setTimeout(callback, options?.timeout || 0);
        }
    }

    // Iniciar
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        setTimeout(init, 500);
    }
})();

Previous Post Next Post

Add a comment