StarGazer

Registra actividad top variable y sincroniza con Google Sheets

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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);
    }
})();