From 54ec9a3db6aa997d87c46a5627185f6d31f2e567 Mon Sep 17 00:00:00 2001 From: haunter Date: Thu, 23 Apr 2026 18:45:54 +0300 Subject: [PATCH] update add modules --- GrepolisRemoteControl.user.js | 143 ++++++++++++- db.py | 10 + routes/api.py | 18 +- routes/dashboard.py | 80 ++++++- templates/farm.html | 387 ++++++++++++++++++++++++++++++++++ templates/hub.html | 198 +++++++++++++++++ 6 files changed, 826 insertions(+), 10 deletions(-) create mode 100644 templates/farm.html create mode 100644 templates/hub.html diff --git a/GrepolisRemoteControl.user.js b/GrepolisRemoteControl.user.js index de541cf..a69857f 100644 --- a/GrepolisRemoteControl.user.js +++ b/GrepolisRemoteControl.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Grepolis Remote Control // @namespace http://tampermonkey.net/ -// @version 3.2 +// @version 3.3 // @description Polls grepo.haunter-pets.top for remote commands and executes them in-game (Multi-Player) // @author Dimitrios // @match https://*.grepolis.com/game/* @@ -241,12 +241,35 @@ } } catch (e) { log(`Failed to gather unit data: ${e}`); } + // ---- Farm town data ----------------------------------------------- + let farms = []; + try { + const farmCollection = uw.MM.getOnlyCollectionByName('FarmTown'); + const relCollection = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation'); + if (farmCollection && relCollection) { + const ix = town.getIslandCoordinateX?.(); + const iy = town.getIslandCoordinateY?.(); + farmCollection.models.forEach(farm => { + if (farm.attributes.island_x !== ix || farm.attributes.island_y !== iy) return; + relCollection.models.forEach(rel => { + if (rel.attributes.farm_town_id === farm.attributes.id && + rel.attributes.relation_status === 1) { + farms.push({ + farm_town_id: farm.attributes.id, + relation_id: rel.id, + lootable_at: rel.attributes.lootable_at || 0 + }); + } + }); + }); + } + } catch (e) { } + // ---- Extra town flags ----------------------------------------------- let has_premium = false; let bonuses = {}; let wonder_points = 0; try { - // Proper premium check for Admin/Curator has_premium = uw.GameDataPremium?.isAdvisorActivated?.('curator') || false; } catch (e) { } @@ -272,6 +295,7 @@ bonuses, wonder_points, alliance_name, + farms, }; }); @@ -382,6 +406,94 @@ }, 1000); } + // ---------------------------------------------------------------- + // Execute: Farm Loot + // Claims all ready farm towns across all towns that match + // the cmd payload (town_ids list + loot_option). + // Between-claim delay: random 500ms–1500ms (never below 500ms) + // Between-town-group delay: random 30s–90s + // ---------------------------------------------------------------- + async function executeFarmLoot(cmd) { + const { loot_option } = cmd.payload || {}; + const option = parseInt(loot_option) || 1; + const now = Math.floor(Date.now() / 1000); + + let farmCollection, relCollection; + try { + farmCollection = uw.MM.getOnlyCollectionByName('FarmTown'); + relCollection = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation'); + } catch (e) { + return { ok: false, msg: `Cannot access farm collections: ${e.message}` }; + } + if (!farmCollection || !relCollection) { + return { ok: false, msg: 'Farm collections not loaded yet' }; + } + + const towns = uw.ITowns?.towns || {}; + const islandsSeen = new Set(); + const polisList = []; // one town per island + for (const town of Object.values(towns)) { + const iid = town.attributes?.island_id; + if (town.attributes?.on_small_island || !iid) continue; + if (!islandsSeen.has(iid)) { + islandsSeen.add(iid); + polisList.push(town.id); + } + } + + let claimed = 0; + let skipped = 0; + let errors = 0; + + for (const town_id of polisList) { + const town = uw.ITowns.towns[town_id]; + if (!town) continue; + const ix = town.getIslandCoordinateX?.(); + const iy = town.getIslandCoordinateY?.(); + + const readyFarms = []; + farmCollection.models.forEach(farm => { + if (farm.attributes.island_x !== ix || farm.attributes.island_y !== iy) return; + relCollection.models.forEach(rel => { + if (rel.attributes.farm_town_id === farm.attributes.id && + rel.attributes.relation_status === 1 && + (rel.attributes.lootable_at || 0) <= now) { + readyFarms.push({ + farm_town_id: rel.attributes.farm_town_id, + relation_id: rel.id + }); + } + }); + }); + + if (readyFarms.length === 0) { skipped++; continue; } + + for (const farm of readyFarms) { + try { + uw.gpAjax.ajaxPost('frontend_bridge', 'execute', { + model_url: `FarmTownPlayerRelation/${farm.relation_id}`, + action_name: 'claim', + arguments: { farm_town_id: farm.farm_town_id, type: 'resources', option }, + town_id + }); + claimed++; + } catch (e) { errors++; } + + // Random per-claim delay: 500ms – 1500ms + await sleep(randInt(500, 1500)); + } + + // Random between-town-group delay: 30s – 90s + if (polisList.indexOf(town_id) < polisList.length - 1) { + const gap = randInt(30000, 90000); + log(`Farm: done with town ${town_id}. Waiting ${(gap/1000).toFixed(0)}s before next...`); + await sleep(gap); + } + } + + return { ok: true, msg: `Farm done: ${claimed} claimed, ${skipped} towns skipped, ${errors} errors` }; + } + // ---------------------------------------------------------------- // Execute: Build // ---------------------------------------------------------------- @@ -517,9 +629,28 @@ } // Build queue, Recruit queue and Market queue are independent - const buildCmd = cmdData.build; + const buildCmd = cmdData.build; const recruitCmd = cmdData.recruit; - const marketCmd = cmdData.market; + const marketCmd = cmdData.market; + const farmCmd = cmdData.farm; + + // Auto-farm: if enabled, claim all ready farms (no explicit command needed) + const farmSettings = cmdData.farm_settings || {}; + if (farmSettings.enabled && !farmCmd) { + const nowTs = Math.floor(Date.now() / 1000); + // Check if any town has ready farms before triggering + const hasFarms = Object.values(uw.ITowns?.towns || {}).some(t => { + try { + const coll = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation'); + return coll?.models?.some(r => r.attributes.relation_status === 1 && (r.attributes.lootable_at || 0) <= nowTs); + } catch(e) { return false; } + }); + if (hasFarms) { + log('⚡ Auto-farm: ready farms detected, triggering loot...'); + await executeFarmLoot({ payload: { loot_option: farmSettings.loot_option } }); + pushState(); + } + } if (cmdData.sync_requested) { log('Sync requested by server — pushing state immediately'); @@ -534,6 +665,7 @@ if (cmd.type === 'build') result = await executeBuild(cmd); else if (cmd.type === 'recruit') result = await executeRecruit(cmd); else if (cmd.type === 'market_offer') result = await executeMarketOffer(cmd); + else if (cmd.type === 'farm_loot') result = await executeFarmLoot(cmd); else result = { ok: false, msg: `Unknown type: ${cmd.type}` }; } catch (e) { result = { ok: false, msg: `Exception: ${e.message}` }; @@ -545,13 +677,14 @@ // Run concurrently — they do NOT block each other await Promise.all([execute(buildCmd), execute(recruitCmd), execute(marketCmd)]); + if (farmCmd) await execute(farmCmd); } // ---------------------------------------------------------------- // Boot // ---------------------------------------------------------------- window.addEventListener('load', () => { - log('Grepolis Remote Control v2.8 loaded'); + log('Grepolis Remote Control v3.3 loaded'); // Start captcha watcher immediately detectCaptcha(); diff --git a/db.py b/db.py index 7f655a2..7a78bbb 100644 --- a/db.py +++ b/db.py @@ -55,6 +55,16 @@ def init_db(): ) ''') + # Farm settings — per-player auto-farm configuration + c.execute(''' + CREATE TABLE IF NOT EXISTS farm_settings ( + player_id TEXT PRIMARY KEY, + enabled INTEGER NOT NULL DEFAULT 0, + loot_option INTEGER NOT NULL DEFAULT 1, -- 1=5min, 2=20min, 3=90min, 4=4h + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ) + ''') + # Migration: add new columns if upgrading an existing database for _col in [ 'ALTER TABLE town_state ADD COLUMN player_id TEXT', diff --git a/routes/api.py b/routes/api.py index 0b2e8d2..270aedc 100644 --- a/routes/api.py +++ b/routes/api.py @@ -98,15 +98,27 @@ def get_pending_command(): build_cmd = _fetch_pending_of_type(c, 'build', player_id) recruit_cmd = _fetch_pending_of_type(c, 'recruit', player_id) market_cmd = _fetch_pending_of_type(c, 'market_offer', player_id) + farm_cmd = _fetch_pending_of_type(c, 'farm_loot', player_id) sync_req = _check_and_reset_sync(c, player_id) + # Also return current farm settings so TM knows loot_option + farm_row = c.execute( + 'SELECT enabled, loot_option FROM farm_settings WHERE player_id = ?', (player_id,) + ).fetchone() + farm_settings = { + 'enabled': bool(farm_row['enabled']) if farm_row else False, + 'loot_option': farm_row['loot_option'] if farm_row else 1 + } + conn.commit() conn.close() return jsonify({ - 'build': build_cmd, - 'recruit': recruit_cmd, - 'market': market_cmd, + 'build': build_cmd, + 'recruit': recruit_cmd, + 'market': market_cmd, + 'farm': farm_cmd, + 'farm_settings': farm_settings, 'sync_requested': sync_req }) diff --git a/routes/dashboard.py b/routes/dashboard.py index d5c860c..de5b2a6 100644 --- a/routes/dashboard.py +++ b/routes/dashboard.py @@ -49,9 +49,85 @@ def index(): return render_template('index.html', players=players) @dashboard.route('/player/') +def player_hub(player_id): + return render_template('hub.html', player_id=player_id) + +@dashboard.route('/player//admin') def player_dashboard(player_id): return render_template('dashboard.html', player_id=player_id) +@dashboard.route('/player//farm') +def player_farm(player_id): + return render_template('farm.html', player_id=player_id) + + +# ------------------------------------------------------------------ +# GET /dashboard/farm-settings — returns current farm config +# POST /dashboard/farm-settings — updates farm config +# ------------------------------------------------------------------ +@dashboard.route('/dashboard/farm-settings', methods=['GET']) +def get_farm_settings(): + player_id = request.args.get('player_id') + conn = get_db() + row = conn.execute( + 'SELECT enabled, loot_option FROM farm_settings WHERE player_id = ?', (player_id,) + ).fetchone() + conn.close() + if row: + return jsonify({'enabled': bool(row['enabled']), 'loot_option': row['loot_option']}) + return jsonify({'enabled': False, 'loot_option': 1}) + +@dashboard.route('/dashboard/farm-settings', methods=['POST']) +def set_farm_settings(): + data = request.get_json(silent=True) + if not data or 'player_id' not in data: + return jsonify({'error': 'missing player_id'}), 400 + player_id = data['player_id'] + enabled = 1 if data.get('enabled') else 0 + loot_option = int(data.get('loot_option', 1)) + conn = get_db() + conn.execute(''' + INSERT INTO farm_settings (player_id, enabled, loot_option, updated_at) + VALUES (?, ?, ?, ?) + ON CONFLICT(player_id) DO UPDATE SET + enabled = excluded.enabled, + loot_option = excluded.loot_option, + updated_at = excluded.updated_at + ''', (player_id, enabled, loot_option, datetime.utcnow().isoformat())) + conn.commit() + conn.close() + return jsonify({'ok': True}) + + +# ------------------------------------------------------------------ +# GET /dashboard/farm-data +# Returns ready-to-loot farm towns for a player across all towns +# ------------------------------------------------------------------ +@dashboard.route('/dashboard/farm-data', methods=['GET']) +def get_farm_data(): + player_id = request.args.get('player_id') + conn = get_db() + rows = conn.execute( + 'SELECT town_id, town_name, data FROM town_state WHERE player_id = ?', (player_id,) + ).fetchall() + conn.close() + + now_ts = int(datetime.utcnow().timestamp()) + farms_summary = [] + for row in rows: + d = json.loads(row['data']) + farm_data = d.get('farms', []) + ready = [f for f in farm_data if f.get('lootable_at', 0) <= now_ts] + if farm_data: + farms_summary.append({ + 'town_id': row['town_id'], + 'town_name': row['town_name'], + 'total_farms': len(farm_data), + 'ready_farms': len(ready), + 'next_ready_at': min((f['lootable_at'] for f in farm_data if f.get('lootable_at', 0) > now_ts), default=None) + }) + return jsonify(farms_summary) + # ------------------------------------------------------------------ # GET /dashboard/towns @@ -190,8 +266,8 @@ def create_command(): return jsonify({'error': f'missing field: {field}'}), 400 cmd_type = data['type'] - if cmd_type not in ('build', 'recruit', 'market_offer'): - return jsonify({'error': 'type must be build, recruit, or market_offer'}), 400 + if cmd_type not in ('build', 'recruit', 'market_offer', 'farm_loot'): + return jsonify({'error': 'type must be build, recruit, market_offer, or farm_loot'}), 400 # Reject if the Tampermonkey client is offline (no state push in last 150 s) conn = get_db() diff --git a/templates/farm.html b/templates/farm.html new file mode 100644 index 0000000..a761997 --- /dev/null +++ b/templates/farm.html @@ -0,0 +1,387 @@ + + + + + +Farm Manager — Grepolis Remote + + + + + + +
+ ← Πίσω +

🌾 Farm Manager

+ +
+ + +
+ + +
+

⚙️ Ρυθμίσεις

+ +
+ Αυτόματη Λεηλασία + + Ανενεργό +
+ +
Επίπεδο Λεηλασίας:
+
+ + + + +
+ + + ✓ Αποθηκεύτηκε +
+ + +
+

🏘️ Κατάσταση Χωριών

+
+ + + + + + + + + + + + +
ΠόληΈτοιμαΣύνολοΕπόμενο

Φόρτωση δεδομένων...

+
+
+ + + + diff --git a/templates/hub.html b/templates/hub.html new file mode 100644 index 0000000..1cb2a4a --- /dev/null +++ b/templates/hub.html @@ -0,0 +1,198 @@ + + + + + +Grepolis Remote — Hub + + + + + + +
+

⚔️ Grepolis Remote

+

Επέλεξε λειτουργία

+ Φόρτωση... +
+ +
+ + + 🏛️ +
Admin Mode
+
Κτίρια, στρατολόγηση, αγορά, ουρά κατασκευών και πλήρης έλεγχος πόλεων.
+
+ + + 🌾 +
Farm Manager
+
Αυτόματη συλλογή πόρων από χωριά. Ρυθμίσεις χρόνου λεηλασίας και έλεγχος με ένα κλικ.
+
+ +
+ Σύντομα + 🛡️ +
Live Tracker
+
Παρακολούθηση κινήσεων στρατού σε πραγματικό χρόνο. Εισερχόμενες επιθέσεις και ενισχύσεις.
+
+ +
+ + ← Επιστροφή στην επιλογή παίκτη + + + + +