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
🕵️ 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
// ==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);
}
})();
Add a comment