Registra actividad top variable y sincroniza con Google Sheets
// ==UserScript==
// @name StarGazer
// @namespace https://ogame.gameforge.com
// @version 1.02
// @description Registra actividad top variable y sincroniza con Google Sheets
// @author UlquiorraFH_
// @match https://*.ogame.gameforge.com/game/index.php*
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant unsafeWindow
// @connect script.google.com
// @connect script.googleusercontent.com
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// ─── CONFIG ───────────────────────────────────────────────────
const SERVER = window.location.hostname;
const STORAGE_KEY = 'ogat_' + SERVER.replace(/\./g, '_');
const DEDUP_MS = 15 * 60 * 1000;
const AUTO_EXPORT_MIN = 10;
let rankLimit = parseInt(GM_getValue('ogat_rank_' + SERVER, 500));
let SHEETS_URL = GM_getValue('ogat_sheets_url_' + SERVER, '');
// Se restaura unsafeWindow para evitar bloqueos de sandbox en el DOM de OGame
const store = unsafeWindow.localStorage;
// ─── STORAGE ──────────────────────────────────────────────────
function loadRecords() {
try { return JSON.parse(store.getItem(STORAGE_KEY) || '[]'); }
catch { return []; }
}
function saveRecords(r) { store.setItem(STORAGE_KEY, JSON.stringify(r)); }
function isDuplicate(records, key) {
const cutoff = Date.now() - DEDUP_MS;
return records.some(r => r.key === key && r.timestamp >= cutoff);
}
// ─── HELPERS ──────────────────────────────────────────────────
function getServerTime() {
const el = document.querySelector('#OGameClock, .OGameClock, #time, .servertime, #servertime');
if (el) {
const match = el.textContent.match(/(\d{2}:\d{2}:\d{2})/);
if (match) return match[1];
}
return new Date().toLocaleTimeString('de-DE', { hour12: false });
}
function getCoords() {
const g = document.querySelector('#galaxy_input');
const s = document.querySelector('#system_input');
if (g && s) {
const galaxy = parseInt(g.value);
const system = parseInt(s.value);
if (!isNaN(galaxy) && !isNaN(system)) return { galaxy, system };
}
return null;
}
function hasRedActivity(cell) {
if (!cell) return false;
const el = cell.querySelector('.activity');
if (!el) return false;
return el.textContent.trim() === '';
}
function getPlayerInfo(row) {
const cell = row.querySelector('.cellPlayerName');
if (!cell) return null;
const nameEl = cell.querySelector('[class*="playerName"]');
if (!nameEl) return null;
const name = nameEl.textContent.split('\n').map(s => s.trim()).find(s => s.length > 0);
const rankMatch = cell.textContent.match(/(?:Clasificaci[oó]n|Rang|Rank|Puntaje)[:\s]+(\d+)/i);
const rank = rankMatch ? parseInt(rankMatch[1]) : null;
return { name: name || '', rank };
}
function getPlanetName(row) {
const cell = row.querySelector('.cellPlanetName');
return cell ? cell.textContent.trim() : '';
}
// ─── ESCANEO ──────────────────────────────────────────────────
function scanGalaxy() {
const coords = getCoords();
if (!coords) return;
const { galaxy, system } = coords;
const rows = document.querySelectorAll('div[id^="galaxyRow"]');
if (!rows.length) return;
const records = loadRecords();
const hora = getServerTime();
const fecha = new Date().toLocaleDateString();
let added = 0;
rows.forEach(row => {
const posCell = row.querySelector('.cellPosition');
const position = posCell ? parseInt(posCell.textContent.trim()) : NaN;
if (isNaN(position) || position < 1 || position > 15) return;
const info = getPlayerInfo(row);
if (!info || !info.name) return;
const { name: jugador, rank: rango } = info;
if (!rango || rango === 0 || rango > rankLimit) return;
const planeta = getPlanetName(row);
const timestamp = Date.now();
const keyP = `${galaxy}-${system}-${position}-P`;
if (hasRedActivity(row.querySelector('.cellPlanet')) && !isDuplicate(records, keyP)) {
records.push({ key: keyP, timestamp, fecha, hora, galaxy, system, posicion: position, tipo: 'Planeta', planeta, jugador, rango });
added++;
}
const keyL = `${galaxy}-${system}-${position}-L`;
if (hasRedActivity(row.querySelector('.cellMoon')) && !isDuplicate(records, keyL)) {
records.push({ key: keyL, timestamp, fecha, hora, galaxy, system, posicion: position, tipo: 'Luna', planeta, jugador, rango });
added++;
}
});
saveRecords(records);
updatePanel(added, records.length, `G${galaxy}:SS${system}`, added > 0 ? hora : null);
}
// ─── EXPORT ───────────────────────────────────────────────────
function exportToSheets(silent = false) {
if (!SHEETS_URL) {
if (!silent) alert('Configurá primero la URL de Google Sheets en los ajustes (⚙).');
return;
}
const records = loadRecords();
if (!records.length) { if (!silent) alert('No hay registros.'); return; }
const sorted = [...records]
.map(r => ({ ...r, hora: (r.hora.match(/(\d{2}:\d{2}:\d{2})/) || [, r.hora])[1] }))
.sort((a, b) => {
if (a.rango !== b.rango) return a.rango - b.rango;
if (a.jugador !== b.jugador) return a.jugador.localeCompare(b.jugador);
if (a.fecha !== b.fecha) return a.fecha.localeCompare(b.fecha);
return a.hora.localeCompare(b.hora);
});
const btn = document.getElementById('ogat-export');
if (btn) btn.textContent = '...';
GM_xmlhttpRequest({
method: 'POST',
url: SHEETS_URL,
data: JSON.stringify({ records: sorted }),
headers: { 'Content-Type': 'application/json' },
onload: () => {
if (btn) btn.textContent = 'Sheets';
const s = document.getElementById('ogat-status');
if (s) s.textContent = `✓ ${sorted.length}`;
countdown = AUTO_EXPORT_MIN * 60;
},
onerror: () => {
if (btn) btn.textContent = 'Sheets';
if (!silent) alert('Error de conexión con Sheets.');
}
});
}
// ─── AUTO-EXPORT ──────────────────────────────────────────────
let autoExportStarted = false;
let countdown = AUTO_EXPORT_MIN * 60;
function startAutoExport() {
if (autoExportStarted) return;
autoExportStarted = true;
setInterval(() => {
countdown--;
if (countdown <= 0) {
countdown = AUTO_EXPORT_MIN * 60;
exportToSheets(true);
}
}, 1000);
}
// ─── SETTINGS ─────────────────────────────────────────────────
function openSettings() {
const overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.7);z-index:999999;display:flex;align-items:center;justify-content:center';
overlay.innerHTML = `
<div style="background:#0d1b2a;border:1px solid #c0392b;padding:20px;border-radius:8px;min-width:380px;font:13px monospace;color:#ecf0f1">
<div style="color:#e74c3c;font-weight:bold;font-size:15px;margin-bottom:4px">⚙ Ajustes — StarGazer v1.02</div>
<div style="color:#7f8c8d;font-size:10px;margin-bottom:14px">by UlquiorraFH_</div>
<div style="margin-bottom:8px;color:#7f8c8d;font-size:11px">Servidor: <b style="color:#ecf0f1">${SERVER}</b></div>
<label style="display:block;margin-bottom:4px">URL Google Apps Script:</label>
<input id="ogat-url-input" value="${SHEETS_URL}"
placeholder="https://script.google.com/macros/s/..."
style="width:100%;background:#1a2a3a;border:1px solid #2c3e50;color:#ecf0f1;
padding:5px 8px;border-radius:4px;font-size:12px;box-sizing:border-box;margin-bottom:12px">
<label style="display:block;margin-bottom:4px">Monitorear top <b id="ogat-rank-display">${rankLimit}</b> jugadores:</label>
<input id="ogat-rank-input" type="range" min="100" max="1000" step="50"
value="${rankLimit}"
style="width:100%;margin-bottom:4px;accent-color:#e74c3c">
<div style="display:flex;justify-content:space-between;color:#7f8c8d;font-size:10px;margin-bottom:14px">
<span>Top 100</span><span>Top 500</span><span>Top 1000</span>
</div>
<div style="text-align:right">
<button id="ogat-settings-cancel"
style="background:#2c3e50;color:#bbb;border:none;padding:5px 14px;cursor:pointer;border-radius:4px;margin-right:8px">
Cancelar
</button>
<button id="ogat-settings-save"
style="background:#27ae60;color:#fff;border:none;padding:5px 14px;cursor:pointer;border-radius:4px">
Guardar
</button>
</div>
</div>
`;
document.body.appendChild(overlay);
document.getElementById('ogat-settings-cancel').onclick = () => overlay.remove();
document.getElementById('ogat-rank-input').oninput = (e) => {
const display = document.getElementById('ogat-rank-display');
if (display) display.textContent = e.target.value;
};
document.getElementById('ogat-settings-save').onclick = () => {
const url = document.getElementById('ogat-url-input').value.trim();
const rank = parseInt(document.getElementById('ogat-rank-input').value);
SHEETS_URL = url;
rankLimit = rank;
GM_setValue('ogat_sheets_url_' + SERVER, url);
GM_setValue('ogat_rank_' + SERVER, rank);
overlay.remove();
const s = document.getElementById('ogat-status');
if (s) s.textContent = url ? `✓ Top ${rank}` : 'Sin URL configurada';
// Forzar reescaneo inmediato al guardar cambios en la vista activa
scanGalaxy();
};
}
// ─── PANEL ────────────────────────────────────────────────────
function createPanel() {
if (document.getElementById('ogat-panel')) return;
const panel = document.createElement('div');
panel.id = 'ogat-panel';
panel.style.cssText = 'position:fixed;top:165px;left:0;background:#0d1b2a;border:1px solid #c0392b;color:#ecf0f1;padding:8px 13px;border-radius:0 6px 6px 0;z-index:99999;font:13px/1.8 monospace;box-shadow:2px 2px 8px rgba(0,0,0,.7)';
panel.innerHTML = `
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:2px">
<span style="color:#e74c3c;font-weight:bold">★ StarGazer</span>
<button id="ogat-settings-btn"
style="background:none;border:none;color:#7f8c8d;cursor:pointer;font-size:13px;padding:0 0 0 8px"
title="Ajustes">⚙</button>
</div>
<div id="ogat-status" style="color:#bdc3c7">Iniciando...</div>
<div style="display:flex;gap:8px;margin:2px 0">
<span style="color:#3498db">P:<b id="ogat-p">0</b></span>
<span style="color:#f39c12">L:<b id="ogat-l">0</b></span>
<span style="color:#7f8c8d">T:<b id="ogat-total">0</b></span>
</div>
<div id="ogat-coord" style="color:#7f8c8d;margin-bottom:5px">—</div>
<button id="ogat-export" style="background:#27ae60;color:#fff;border:none;padding:3px 10px;cursor:pointer;border-radius:3px;font-size:12px;margin-right:3px">Sheets</button>
<button id="ogat-clear" style="background:#2c3e50;color:#bbb;border:none;padding:3px 10px;cursor:pointer;border-radius:3px;font-size:12px">Limpiar</button>
`;
document.body.appendChild(panel);
document.getElementById('ogat-export').onclick = () => exportToSheets(false);
document.getElementById('ogat-settings-btn').onclick = openSettings;
document.getElementById('ogat-clear').onclick = () => {
if (confirm('¿Eliminar registros locales?\n\nAsegurate de haber sincronizado con Sheets primero.')) {
store.removeItem(STORAGE_KEY);
updatePanel(0, 0, null, null);
}
};
}
function updatePanel(newCount, total, coordStr, lastTime) {
const s = document.getElementById('ogat-status');
const t = document.getElementById('ogat-total');
const coord = document.getElementById('ogat-coord');
if (s) s.textContent = newCount > 0 ? `+${newCount}` : 'Sin actividad';
if (t) t.textContent = total;
if (coord && coordStr) coord.textContent = coordStr;
const recs = loadRecords();
const p = document.getElementById('ogat-p');
const l = document.getElementById('ogat-l');
if (p) p.textContent = recs.filter(r => r.tipo === 'Planeta').length;
if (l) l.textContent = recs.filter(r => r.tipo === 'Luna').length;
}
// ─── OBSERVER ─────────────────────────────────────────────────
let scanTimer = null;
function debouncedScan() {
clearTimeout(scanTimer);
scanTimer = setTimeout(scanGalaxy, 800);
}
function attachObserver() {
const target = document.querySelector('#galaxyContent');
if (!target) { setTimeout(attachObserver, 1500); return; }
new MutationObserver(debouncedScan).observe(target, { childList: true, subtree: true });
}
// ─── INIT ─────────────────────────────────────────────────────
function init() {
// Aseguramos la creación del panel visual sin importar restricciones estrictas del DOM inicial
createPanel();
const recs = loadRecords();
const p = document.getElementById('ogat-p');
const l = document.getElementById('ogat-l');
const t = document.getElementById('ogat-total');
if (p) p.textContent = recs.filter(r => r.tipo === 'Planeta').length;
if (l) l.textContent = recs.filter(r => r.tipo === 'Luna').length;
if (t) t.textContent = recs.length;
if (!SHEETS_URL) {
const s = document.getElementById('ogat-status');
if (s) s.textContent = 'Configurá ⚙ primero';
} else {
const s = document.getElementById('ogat-status');
if (s) s.textContent = `✓ Top ${rankLimit}`;
}
if (!window.location.href.includes('component=galaxy') && !document.querySelector('#galaxyContent')) return;
attachObserver();
startAutoExport();
setTimeout(scanGalaxy, 900);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
setTimeout(init, 900);
}
})();