StarGazer

Registra actividad top variable y sincroniza con Google Sheets

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

Advertisement:

// ==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">&#9733; 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);
    }
})();