# OGame Galaxy Viewer — Documentazione tecnica Visualizzatore di galassie per **OGame (s170-ar, universo "Rosalind")** con archivio personale dei rapporti di spionaggio e classifiche filtrate. Questa documentazione descrive l'architettura, i sorgenti e, per ciascuno, funzioni e metodi. --- ## 1. Panoramica Il progetto è una **web app personale** composta da: | Componente | Tecnologia | Ruolo | |---|---|---| | Backend | Python 3 + Flask | API REST, aggiornamento dati, archivio rapporti | | Frontend | HTML/CSS/JS vanilla | Galaxy view, dettagli rapporti, classifiche | | Dati | File JSON su disco | Nessun database: snapshot + rapporti come file | | Servizi | systemd (utente) | App sempre attiva + aggiornamenti schedulati | **Flusso dati (dati universo)**: ``` Gameforge API pubblica (XML + ?toJson=1) │ ogni giorno (03:30) ▼ update_data.py ──► data/latest/*.json ──► world.json (indici) │ ▲ └── highscore ── ogni ora (:17) ─────┘ │ app.py (Flask :8899) ──► browser (galassia/classifiche) ``` **Flusso dati (rapporti di spionaggio)**: ``` In gioco (pulsante "API" del rapporto) ──► API string "sr-ar-170-<40 hex>" │ incollata nella pagina ▼ POST /api/reports ──► report_store.py ──► proxy community (ogapi.faw-kes.de) ▼ data/reports/.json (+ index.json) ▼ clic sul pianeta in galassia ──► dettagli archiviati ``` --- ## 2. Struttura delle directory ``` /opt/ogame_db/ ├── app.py # Backend Flask: pagine + API + aggiornamento all'avvio ├── update_data.py # Download snapshot pubblici + classifiche + world.json ├── report_store.py # Import/archivio/consultazione rapporti di spionaggio ├── technames.py # Nomi tecnologici (edifici/ricerche/navi/difese) lato server ├── config.json # Configurazione (universo, proxy, account personale) ├── requirements.txt # Dipendenze Python ├── README.md # Guida rapida d'uso ├── DOCUMENTAZIONE.md # (questo file) ├── data/ │ ├── latest/ # Snapshot correnti + world.json │ ├── snapshots/ # Storico giornaliero (ritenzione: keep_snapshots) │ └── reports/ # Rapporti importati (1 file = 1 rapporto) + index.json └── static/ ├── index.html # Pagina galassia ├── rankings.html # Pagina classifiche ├── style.css # Stili (tema spaziale, responsive) ├── common.js # Utility condivise (fetch, avatar SVG, icone, stelle) ├── app.js # Logica della galassia e dei rapporti ├── rankings.js # Logica della pagina classifiche ├── icons.js # Icone SVG incorporate (game-icons, CC BY 3.0) └── icons/ # (riservata a eventuali svg esterni; non usata a runtime) ``` --- ## 3. `config.json` — configurazione ```json { "server_number": 170, // numero universo "community": "ar", // community (lingua/regione) "domain": "s170-ar.ogame.gameforge.com", "my_player_id": 100634, // tuo account: pianeti evidenziati in verde "my_player_name": "MaNaki", "proxy_base": "https://ogapi.faw-kes.de", // proxy ufficiale per i rapporti "refresh_hours": 24, // dati "stale" oltre N ore → update all'avvio "keep_snapshots": 30 // giorni di storico conservati } ``` --- ## 4. Backend — moduli Python ### 4.1 `app.py` — server Flask (porta 8899) Avviato da systemd come `ogame-galaxy.service`. All'avvio verifica l'età dei dati (`_ensure_fresh_data`) e, se più vecchi di `refresh_hours`, lancia `update_data.main()`. **Middleware** | Funzione | Descrizione | |---|---| | `_no_cache(resp)` | `after_request`: imposta `Cache-Control: no-store` su pagine e file statici (niente cache nel browser) | **Helper** | Funzione | Descrizione | |---|---| | `load_config()` | Legge `config.json` | | `load_world()` | Legge `data/latest/world.json` e vi fonde i **pianeti noti solo dai rapporti** (player recenti assenti da `universe.xml`), marcandoli `report_only` | | `fmt_ts(epoch)` | Converte epoch in stringa `YYYY-MM-DD HH:MM UTC` | | `_report_view(rec, full)` | Trasforma un record d'archivio + payload in una **vista sintetica** per la UI (date, attività, risorse, conteggi per categoria, flag `failed_*`) | | `_ensure_fresh_data()` | Aggiornamento automatico all'avvio se i dati sono datati | **Endpoint HTTP** | Rotte | Descrizione | |---|---| | `GET /` | Pagina galassia (`static/index.html`) | | `GET /rankings` | Pagina classifiche (`static/rankings.html`) | | `GET /api/bootstrap` | Stato: `server` (nome, galassie, sistemi, velocità…), `myPlayer`, `fetched` (date ultimo aggiornamento per fonte), `reportStats` (n. rapporti/pianeti) | | `GET /api/galaxy?g=&s=` | Celle del sistema (15 posizioni): per ogni cella pianeta, luna, giocatore (+stato, alleanza, **rank totale/armamenti**), rapporti disponibili | | `GET /api/planet?g=&s=&p=` | Dettaglio pianeta: info, giocatore con rank, altri pianeti dello stesso giocatore, rapporti del pianeta e della luna | | `GET /api/player?id=` | Dettaglio giocatore: tutti i suoi pianeti e **tutti** i rapporti (su qualunque pianeta o luna), ordinati per data | | `POST /api/reports` | Importa un rapporto: body `{"token": "sr-ar-170-"}` (o solo l'id a 40 hex). Idempotente: se già presente ritorna `created:false` | | `GET /api/reports/` | JSON completo di un rapporto archiviato (`{meta, report}`) | | `DELETE /api/reports/` | Elimina un rapporto archiviato (file su disco + voce in `index.json`) | | `GET /api/rankings?type=&status=&start=&end=&q=` | Classifiche filtrate (vedi 4.4) | | `POST /api/refresh` | Forza `update_data.main()` in background | ### 4.2 `update_data.py` — aggiornamento dati Scarica l'API pubblica ufficiale di Gameforge (endpoint XML con `?toJson=1`). **Costanti** | Costante | Valore | |---|---| | `ENDPOINTS` | `serverData`, `universe`, `players`, `alliances`, `localization` | | `HIGHSCORES` | `highscore_total` (cat.1, type 0), `highscore_military` (cat.1, type 3) | | `UA` | User-Agent dichiarato nelle richieste | **Funzioni** | Funzione | Descrizione | |---|---| | `load_config()` | Legge `config.json` | | `http_get(url, timeout)` | GET con User-Agent | | `fetch_xml_json(cfg, name, query)` | Scarica `https://s-.ogame.gameforge.com/api/.xml?toJson=1` e aggiunge `_fetched_at` | | `fetch_endpoint(cfg, name)` | Wrapper per gli endpoint semplici | | `fetch_highscore(...)` | *(helper storico)* scarica un highscore | | `save_latest(name, data)` | Salva in `data/latest/.json` | | `save_snapshot(name, data)` | Copia giornaliera in `data/snapshots/_.json` (una per giorno; prune oltre `keep_snapshots`) | | `_as_dict(payload, key, attr_key)` | Normalizza `{"player":[{"@attributes":{...}}]}` → `{id: {...}}` | | `build_world(cfg)` | Costruisce `world.json` (vedi §6) | | `_file_time(name)` | Epoch dell'ultimo fetch di un file `latest` | | `update_highscores(cfg, with_snapshot)` | Scarica i 2 highscore | | `main()` | Esegue l'aggiornamento; con argomento `--highscore-only` aggiorna **solo** le classifiche (usato dal timer orario) | ### 4.3 `report_store.py` — archivio rapporti di spionaggio Gestisce il ciclo di vita dei rapporti: parsing API string → recupero dal proxy → salvataggio JSON → indicizzazione per coordinate. **Funzioni** | Funzione | Descrizione | |---|---| | `load_config()` | Legge `config.json` | | `_load_index()` / `_save_index(idx)` | Legge/scrive `data/reports/index.json` | | `parse_token(token, cfg)` | Valida l'API string `(cr\|sr\|rr\|mr)---<40hex>`; verifica che community/server combacino con la config; accetta anche il solo id esadecimale (assunto `sr`) | | `fetch_from_proxy(api_string, cfg)` | `GET {proxy}/v1/report/{api_string}/1`; gestisce 429 (limite proxy ~10 req/min) e `RESULT_CODE != 1000` | | `_defender_info(report)` | Estrae dal payload `generic` i dati del bersaglio (coordinate, tipo pianeta/luna, nomi, data evento, attività, % bottino) | | `import_report(api_string, cfg)` | Parsa → scarica → salva `data/reports/.json` → aggiorna `index.json`. Ritorna `(record, creato?)`. Idempotente | | `get_report(rid)` | Legge il file completo `{meta, report}` | | `delete_report(rid)` | Elimina un rapporto (file + indice, ripulisce `by_coords`). Ritorna `True` se rimosso | | `list_by_coords(coords, scope)` | Elenca i rapporti di un pianeta. `scope`: `planet` \| `moon` \| `all`, ordinati per `event_timestamp` desc | | `summary(rec)` | *(utilità)* alias del record sintetico | | `stats(cfg)` | Conta rapporti totali e pianeti coperti | ### 4.4 Filtri `/api/rankings` Parametri: `type` (`total`|`military`|`fleet`), `status`, `start`/`end` (range posizione), `q` (testo su nome giocatore, tag o nome alleanza). Con `type=fleet` (**Top Flotte**) la classifica non usa gli highscore: elenca solo i giocatori che hanno almeno un rapporto importato, ordinati per **numero di navi**. Le navi sono sommate prendendo, per ogni pianeta/luna spiato, il **rapporto più recente** (campo `total_ship_count`, memorizzato nell'indice all'import); la posizione `rank` è quella della classifica flotte e il valore è in `score`. Valori di `status` e relativa condizione sullo stato API: | status | stato API ammesso | |---|---| | `all` | qualsiasi | | `active` | *(vuoto)* | | `inactive` | `i`, `I` | | `vacation` | `v` | | `vacation_inactive` | `vi`, `vI` | | `admin` | `a` | > **Nota UI:** la pagina `/rankings` espone solo le voci **Tutti** (`all`), > **Attivi** (`active`) e **Inattivi** (`inactive`). Gli altri valori restano > accettati dall'API per compatibilità, ma non sono più selezionabili > dall'interfaccia. Le righe restituite includono: id, nome, stato, alleanza (tag+nome), posizione, punteggio, coordinate del primo pianeta (per l'avatar), numero pianeti, flag `mine`, numero di rapporti di spionaggio sul giocatore (`reports`) e coordinate del rapporto più recente (`report_coords`, usata per il salto dal badge "Spie"). ### 4.5 `technames.py` — dizionario nomi tech (server) Mappe `id → nome` per la numerazione **client attuale** (diversa da quella storica): edifici 1–44, ricerca 106–199, navi 202–219, difese 401–503, edifici lifeform 11101+. Nota: il frontend ha una copia aggiornata nei propri sorgenti (vedi §5.4, `NAMES`); `technames.py` resta come riferimento/utilità lato server. | Funzione | Descrizione | |---|---| | `_localized_names()` | Carica i nomi localizzati da `data/latest/localization.json` (fallback) | | `item_name(category, tid)` | Nome di un elemento (IT → localizzato → `"Categoria #id"`) | | `category_name(cat)` | Etichetta della categoria | --- ## 5. Frontend — `static/` ### 5.1 `common.js` — utility condivise | Funzione | Descrizione | |---|---| | `esc(s)` | Escape HTML | | `num(v)` | Formatta numeri in stile italiano (`1.234.567`), `—` se nullo | | `hashStr(s)` | Hash deterministico (per i colori degli avatar) | | `api(path, opts)` | `fetch` JSON con gestione errori (`{ok:false, error}`) | | `icon(name, size)` | Ritorna l'SVG inline dell'icona da `window.ICONS` (icons.js) con `currentColor` | | `artSVG(seedKey, size, moon)` | Avatar pianeta/luna in SVG: colore scelto da palette in base all'hash, con ombra e (per la luna) crateri | | `initStars()` | Sfondo stellare su canvas (twinkle); **non bloccante** se canvas non disponibile | ### 5.2 `app.js` — galassia e rapporti | Funzione | Descrizione | |---|---| | `loadBootstrap()` | Popola header: nome universo, range galassie/sistemi, date dati, conteggio spie | | `loadGalaxy()` | `GET /api/galaxy` e render della tabella | | `renderGalaxy(data)` | Costruisce le 15 righe: posizione, pianeta (avatar SVG + nome), luna, giocatore (colore per stato + 🏆/⚔ rank), alleanza, badge "spie" | | `openPlanet(pos, focusMoon)` | Apre il popup del pianeta (`GET /api/planet`) | | `openPlanetAt(coords, focusMoon)` | Apre il popup di un pianeta date le coordinate `g:s:p`, anche di un altro sistema | | `openPlayer(pid)` / `renderPlayerPopup(d)` | Apre la finestra **giocatore** (`GET /api/player`): elenco di tutti i pianeti + tutti i rapporti di spionaggio sul giocatore, in un'unica vista | | `renderPlanetPopup(data, focusMoon)` | Header con avatar; chip giocatore/stato/classifica/luna; altri pianeti del giocatore (cliccabili); **griglia di card** per gli spionaggi del pianeta e della luna | | `rptCard(rec)` | Card riepilogo di un rapporto (data, attività colorata, bersaglio con **coordinate** `[g:s:p]`, attaccante, conteggi con icone categoria) | | `openReport(srId)` / `renderReportDetail(meta, rep)` | Vista dettaglio: risorse in tile colorate, meta (difensore/attaccante/attività/bottino/detriti), avvisi `failed_*`, sezioni in **griglia di card** con icona per ogni edificio/ricerca/nave/difesa | | `techSection(...)` | Griglia card per categoria | | `itemName(cat, id)` / `itemIconName(cat, id)` | Nome e chiave-icona di un elemento (fallback sulla categoria) | | `actClass/actLabel(activity)` | Colore/etichetta dell'attività rilevata (0/15/30/60 min) | | `fmtEv(evt)` | Data evento in formato locale italiano | | `setSystem/setGalaxy/closePopup/goToCoords` | Navigazione (con clamp sui limiti universo) | | `setMsg(kind, text)` | Messaggi di stato nella barra di import | | `importReport()` | Legge l'API string, `POST /api/reports`, aggiorna la vista | Stato globale: `state = {galaxy, system, maxGalaxy, maxSystem, boot}`. All'avvio legge `?g=` e `?s=` dalla URL (usato dai click dalle classifiche). **Colori stato giocatore** (come in galaxy OGame): attivo = giallo · inattivo (`i/I`) = grigio · vacanza+inattivo = grigio · in vacanza = celeste · admin = rosso. ### 5.3 `rankings.js` — classifiche | Funzione | Descrizione | |---|---| | `initHead()` | Header con nome universo e data aggiornamento classifiche | | `load()` | `GET /api/rankings` con i filtri correnti | | `render(d)` | Tabella: posizione (top3 colorati oro/argento/bronzo), avatar pianeta, giocatore (colore stato, ● = tu), alleanza, stato, pianeti, **Spie** (badge col numero di rapporti sul giocatore, cliccabile verso il pianeta del rapporto più recente), punti | | `goGalaxy(coords)` | Salta alla galassia `/?g=..&s=..` al pianeta del giocatore | | `readFilters()` | Legge i controlli (stato, range, testo) | Tabs: **Generale** (total), **Armamenti** (military) e **Top Flotte** (fleet — solo giocatori con rapporti, ordinati per navi), con icone SVG. ### 5.4 Mappe dati nel frontend (`NAMES`, `ITEM_ICON`) `NAMES[buildings|research|ships|defense|lfbuildings]` → `{id: nome}` e `ITEM_ICON[...]` → `{id: chiave icona}`. Nomi allineati al client attuale (es. `213 → "Corazzata"`, `215 → "Incrociatore da battaglia"`, `210 → "Sonda di spionaggio"`). ### 5.5 `icons.js` / icone `window.ICONS = { chiave: {vb, body} }` con **87 icone**: 79 da game-icons.net (CC BY 3.0, attribuzione nei footer) + icone UI custom (refresh, chevron, chiusura, logo, avviso). Tutte usano `fill="currentColor"`: il colore segue il CSS di categoria (`--tc`), quindi **nessuna dipendenza dai font emoji del sistema**. --- ## 6. Dati su disco — `data/` ### 6.1 `data/latest/*.json` Snapshot grezzi dell'API (`serverData`, `universe`, `players`, `alliances`, `localization`, `highscore_total`, `highscore_military`), ciascuno con `_fetched_at`. ### 6.2 `data/latest/world.json` (indici precompilati) | Chiave | Contenuto | |---|---| | `server` | numero, community, dominio, nome universo, galassie, sistemi, velocità, versione, lingua, timezone, ACS | | `fetched` | epoch dell'ultimo fetch per ogni fonte | | `players` | `{id: {name, status, alliance, alliance_tag, alliance_name}}` | | `alliances` | `{id: {name, tag, founder}}` | | `planets` | `{"g:s:p": {id, player, name, coords, moon:{...}}}`. Le voci con `report_only: true` non vengono dall'API pubblica ma da un rapporto di spionaggio (pianeti di player recenti assenti da `universe.xml`) | | `avatar` | `{playerId: prime-coordinate}` (per gli avatar in classifica) | | `ranks` | `{playerId: {total, military, total_score, military_score}}` | ### 6.3 `data/snapshots/` Copie giornaliere `*_AAAA-MM-GG.json` per fonte (ritenzione configurabile). ### 6.4 `data/reports/` - `index.json`: `{by_coords: {"g:s:p": [sr_id,…], "g:s:p#moon": [… ]}, reports: {sr_id: {meta…}}}` → ricerca immediata per pianeta/luna. - `.json`: `{"meta": {…}, "report": {generic, details}}` (payload completo del proxy). --- ## 7. Servizi systemd (utente) | Unità | Quando | Cosa fa | |---|---|---| | `ogame-galaxy.service` | sempre attivo | web app su `0.0.0.0:8899` (restart on failure) | | `ogame-data-update.timer` | giornaliero 03:30 | `update_data.py` (pianeti/giocatori/alleanze + highscore) | | `ogame-highscore.timer` | ogni ora (:17) | `update_data.py --highscore-only` (classifiche) | Comandi utili: `systemctl --user status/restart `, `journalctl --user -u ogame-galaxy.service -f`, `loginctl show-user principale | grep Linger` (linger=yes ⇒ i servizi partono al boot senza login). --- ## 8. API esterne utilizzate | Sorgente | Uso | Note | |---|---|---| | `https://s170-ar.ogame.gameforge.com/api/*.xml?toJson=1` | API **pubblica** ufficiale: universe, players, alliances, serverData, localization, highscore | `highscore.xml?category=1&type=0\|3`; cadenze server: universe ~settimanale, players ~giornaliero, highscore ~orario | | `https://ogapi.faw-kes.de/v1/report/{api_string}/1` | **Proxy community ufficiale** per il recupero dei rapporti (CR/SR/RR/MR) | Nessuna API key necessaria; limite ~10 richieste/min | | `https://forum.origin.ogame.gameforge.com` | Documentazione ufficiale (thread "OGame API", procedure accesso) | Riferimento del protocollo | --- ## 9. Note operative e di sicurezza 1. **Accesso**: app su `0.0.0.0:8899` **senza autenticazione**: se la rete non è privata, mettere un reverse proxy con login. 2. **Backup**: copiare `data/` (report inclusi) e `config.json`. 3. **Cache**: i file statici sono serviti con `no-store`: un refresh basta dopo ogni modifica (nessun `Ctrl+F5` necessario, ma non guasta). 4. **Privacy**: i rapporti restano su questo server; il proxy viene contattato solo al momento dell'import di un nuovo token (che il giocatore controlla e condivide volontariamente). 5. **Licenza icone**: game-icons.net, CC BY 3.0 (attribuzione nei footer di pagina).