From a153b397d392bd4b5db075cf6f26eaff3202f7e3 Mon Sep 17 00:00:00 2001 From: haunter Date: Sun, 3 May 2026 22:25:30 +0300 Subject: [PATCH] live scan fix --- bot_modules/06_tracker.js | 19 +++++-- future_ideas.md | 111 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 future_ideas.md diff --git a/bot_modules/06_tracker.js b/bot_modules/06_tracker.js index 161283b..7868042 100644 --- a/bot_modules/06_tracker.js +++ b/bot_modules/06_tracker.js @@ -126,8 +126,9 @@ // --- Bind to GameEvents (passive, zero server cost) --- try { - // New incoming attack detected - uw.$.Observer(uw.GameEvents.attack.incoming).subscribe( + // New incoming attack detected (This event works according to logs) + const attackEvent = uw.GameEvents?.attack?.incoming || 'attack:incoming'; + uw.$.Observer(attackEvent).subscribe( 'GRC_TRACKER_ATTACK', function(e, data) { // Small delay so game model updates before we read it @@ -141,17 +142,25 @@ try { // Any command state changed (sent, landed, recalled, etc.) - uw.$.Observer(uw.GameEvents.command.change).subscribe( + // Fallback to string if the constant doesn't exist in this version + const cmdEvent = (uw.GameEvents?.command && uw.GameEvents.command.change) + ? uw.GameEvents.command.change + : 'CommandsMenuBubble:change'; + + uw.$.Observer(cmdEvent).subscribe( 'GRC_TRACKER_CMD', function(e, data) { setTimeout(_pushMovements, 500); } ); - log('[tracker] ✅ Subscribed to command.change event'); + log('[tracker] ✅ Subscribed to command changes'); } catch (e) { - log(`[tracker] Could not subscribe to command.change: ${e}`); + log(`[tracker] Could not subscribe to command changes: ${e}`); } + // --- Failsafe: push every 15 seconds regardless of events --- + setInterval(_pushMovements, 15000); + }, 6000); // 6s after boot — ensures CommandsMenuBubble model is loaded } diff --git a/future_ideas.md b/future_ideas.md new file mode 100644 index 0000000..b9122a9 --- /dev/null +++ b/future_ideas.md @@ -0,0 +1,111 @@ +# Future Ideas & Optimizations + +This document tracks potential architectural improvements and features inspired by other Grepolis alliance coordination scripts (like GrepoData and Noct). + +## 1. Timestamp-Based Polling Optimization (`since` parameter) +**Inspired by:** `noct-api.grepo-soft.workers.dev` + +**Current State:** +The Tampermonkey client polls the server every 8-18 seconds and receives the full state/command payload every time, even if nothing has changed. + +**Proposed Implementation:** +- Add a `since` timestamp parameter to the client's poll requests. +- The server checks if any commands or state updates have occurred *after* the `since` timestamp. +- **If no new data:** The server returns an empty `HTTP 204 No Content` response. +- **If new data:** The server returns `HTTP 200 OK` with only the data that changed. + +**Benefits:** +- Drastically reduces server bandwidth and CPU load. +- Minimizes the size of network requests on the client side, making the script stealthier and less resource-intensive in the browser. + +**Concrete Code Example (How Noct does it):** +```javascript +// 1. Client-side polling logic +let lastFetchTime = Date.now(); + +async function pollCommands() { + const url = `https://noct-api.grepo-soft.workers.dev/api/alliance/commands` + + `?alliance=p0PmzsZMo4xZ2o29uvqggy5d` + + `&world=gr118` + + `&clientId=848938473` + + `&since=${lastFetchTime}`; // Ask only for things after this timestamp + + const response = await fetch(url); + + // 2. Server returns HTTP 204 (No Content) if nothing new happened + if (response.status === 204) { + return; // Empty payload, exit early + } + + // 3. Server returns HTTP 200 (OK) only if there are NEW commands + if (response.status === 200) { + const data = await response.json(); + + // Process new commands... + executeCommands(data.commands); + + // Update the timestamp so the next poll only asks for things after this moment + lastFetchTime = Date.now(); + } +} +``` + +```python +# Backend (Python/Flask Equivalent) +@app.route('/api/alliance/commands') +def get_commands(): + # Get the timestamp from the URL query + since_ts = int(request.args.get('since', 0)) + + # Query DB for commands created AFTER the 'since' timestamp + new_commands = db.execute( + "SELECT * FROM commands WHERE created_at_ts > ?", (since_ts,) + ).fetchall() + + if not new_commands: + # Return empty body with 204 No Content + return '', 204 + + return jsonify({"commands": new_commands}), 200 +``` + +## 2. WebSocket Architecture for Real-Time Synchronization +**Inspired by:** `grepodata.com` (ReactPHP WebSocket server) + +**Current State:** +Command delivery relies on HTTP polling. If an attack plan requires a launch in 30 seconds, but the client is on an 18-second polling interval, there is a high risk of missing the execution window or executing late. + +**Proposed Implementation:** +- Integrate `Flask-SocketIO` (or a standalone async WebSocket server) into the backend. +- The client script establishes a persistent `wss://` connection upon loading the game. +- The client authenticates using its `clan_key` and subscribes to its alliance "topic/room". +- When an admin arms an attack plan or a player updates their town state, the server instantly *pushes* the payload to all connected alliance members. + +**Benefits:** +- **Zero Polling Latency:** Commands arrive in ~100ms instead of 8-18 seconds. +- **Perfect Attack Timing:** Ensures clients receive armed plans immediately, maximizing the margin for precise execution. +- **Instant UI Updates:** The dashboard and attack planner can update in real-time as members come online or troop counts change. + +## 3. Server-Sent Events (SSE) Lag/Refresh Bug Fix +**Issue:** +When refreshing or hitting the "back" button on the Live Tracker (`tracker.html`), the page occasionally hangs, lags heavily, or completely fails to load. + +**Root Cause:** +The Live Tracker uses SSE (`EventSource`) to receive real-time movement updates. Modern browsers strictly limit simultaneous HTTP/1.1 connections to the same server (usually 6 maximum). When the user navigates away or refreshes, the browser drops the frontend page, but the Python/Flask backend (`tracker.py`) does not immediately detect the broken pipe and keeps the socket open, waiting to send data. +If the user hits refresh multiple times, these "ghost" connections stack up. Upon reaching 6 ghost connections, the browser refuses to load any further requests until the old connections naturally time out (which can take 30+ seconds). + +**Proposed Fix:** +1. **Client-side (`tracker.html`)**: Ensure the browser explicitly tells the server the connection is closing exactly as the page unloads. +```javascript +window.addEventListener('beforeunload', () => { + if (typeof es !== 'undefined' && es !== null) { + es.close(); + } +}); +``` + +2. **Server-side (`tracker.py`)**: Ensure the generator handles client disconnects gracefully and immediately cleans up the subscriber queue without waiting for a timeout. +```python +# Make sure the generator yields spaces/heartbeats actively so the OS +# throws an IOError/GeneratorExit the moment the client drops. +```