From f26326700942c6ea762d8b310d67cec30832c393 Mon Sep 17 00:00:00 2001 From: haunter Date: Sun, 19 Apr 2026 19:50:15 +0000 Subject: [PATCH] Upload files to "/" --- GrepolisRemoteControl.user.js | 269 ++++++++++++++++++++++++++++++++++ app.py | 16 ++ db.py | 45 ++++++ grepo.db | Bin 0 -> 20480 bytes requirements.txt | 2 + 5 files changed, 332 insertions(+) create mode 100644 GrepolisRemoteControl.user.js create mode 100644 app.py create mode 100644 db.py create mode 100644 grepo.db create mode 100644 requirements.txt diff --git a/GrepolisRemoteControl.user.js b/GrepolisRemoteControl.user.js new file mode 100644 index 0000000..f60ec63 --- /dev/null +++ b/GrepolisRemoteControl.user.js @@ -0,0 +1,269 @@ +// ==UserScript== +// @name Grepolis Remote Control +// @namespace http://tampermonkey.net/ +// @version 1.0 +// @description Polls grepo.haunter-pets.top for remote commands and executes them in-game +// @author Dimitrios +// @match https://*.grepolis.com/game/* +// @grant unsafeWindow +// ==/UserScript== + +(function () { + 'use strict'; + + const uw = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; + const BASE_URL = 'https://grepo.haunter-pets.top'; + const POLL_INTERVAL_MS = 5000; // poll for commands + const STATE_INTERVAL_MS = 30000; // push town state + + // ---------------------------------------------------------------- + // Toolbar indicator button + // ---------------------------------------------------------------- + const btnHtml = ` +
+
+

Remote

+
`; + + let paused = false; + + function togglePause() { + paused = !paused; + const label = document.getElementById('grc_label'); + const btn = document.getElementById('grc_btn'); + if (paused) { + label.textContent = 'Paused'; + btn.style.filter = 'brightness(70%) sepia(100%) hue-rotate(-50deg) saturate(1000%) contrast(0.8)'; + } else { + label.textContent = 'Remote'; + btn.style.filter = 'brightness(294%) sepia(100%) hue-rotate(200deg) saturate(1000%) contrast(0.8)'; + } + log(`Remote is now ${paused ? 'PAUSED' : 'ACTIVE'}`); + } + + setTimeout(() => { + if (!document.getElementById('grc_btn')) { + uw.$('.tb_activities, .toolbar_activities').find('.middle').append(btnHtml); + } + }, 4000); + + uw.$(document).on('click', '#grc_btn', togglePause); + + // ---------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------- + function log(msg) { + console.log(`[GRC] ${msg}`); + } + + function sleep(ms) { + return new Promise(r => setTimeout(r, ms)); + } + + // ---------------------------------------------------------------- + // Push town state to relay + // ---------------------------------------------------------------- + function gatherState() { + const towns = uw.ITowns?.towns || {}; + const player = uw.Game?.player_name || ''; + const world = uw.Game?.world_id || ''; + + const townList = Object.values(towns).map(town => { + const res = town.resources(); + const buildings = town.buildings()?.attributes ?? {}; + + const unitsObj = {}; + try { + const units = town.units(); + if (units) { + Object.keys(units).forEach(k => { + unitsObj[k] = typeof units[k] === 'number' + ? units[k] + : (units[k]?.getAmount?.() ?? 0); + }); + } + } catch (e) {} + + let buildQueue = []; + try { + const bo = town.buildingOrders?.(); + if (bo?.models) buildQueue = bo.models.map(m => m.attributes); + } catch (e) {} + + return { + town_id: town.id, + town_name: town.name, + wood: res.wood, + stone: res.stone, + iron: res.iron, + population: res.population, + points: town.getPoints?.() ?? 0, + god: town.god?.() ?? null, + buildings, + units: unitsObj, + buildingOrder: buildQueue, + }; + }); + + return { player, world_id: world, towns: townList }; + } + + function pushState() { + if (paused) return; + try { + const payload = gatherState(); + fetch(`${BASE_URL}/api/state`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }) + .then(() => log(`State pushed — ${payload.towns.length} towns`)) + .catch(e => log(`State push failed: ${e}`)); + } catch (e) { + log(`gatherState error: ${e}`); + } + } + + // ---------------------------------------------------------------- + // Report command result back to relay + // ---------------------------------------------------------------- + function reportResult(cmdId, success, message) { + fetch(`${BASE_URL}/api/commands/${cmdId}/result`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ status: success ? 'done' : 'failed', message }) + }).catch(e => log(`reportResult failed: ${e}`)); + } + + // ---------------------------------------------------------------- + // Execute: Build + // ---------------------------------------------------------------- + async function executeBuild(cmd) { + const { town_id, payload } = cmd; + const { building_id } = payload; + + const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id]; + if (!town) { + return { ok: false, msg: `Town ${town_id} not found in ITowns` }; + } + + // Check build queue + const queueLen = town.buildingOrders?.()?.length ?? 0; + const hasCurator = uw.GameDataPremium?.isAdvisorActivated?.('curator'); + const maxQueue = hasCurator ? 7 : 2; + if (queueLen >= maxQueue) { + return { ok: false, msg: `Build queue full (${queueLen}/${maxQueue})` }; + } + + // Check resources + try { + const buildData = uw.MM.getModels?.()?.BuildingBuildData?.[town_id] + ?.attributes?.building_data?.[building_id]; + if (buildData) { + const res = town.resources(); + const { resources_for, population_for } = buildData; + if (town.getAvailablePopulation?.() < population_for) { + return { ok: false, msg: `Not enough population for ${building_id}` }; + } + if (res.wood < resources_for.wood || res.stone < resources_for.stone || res.iron < resources_for.iron) { + return { ok: false, msg: `Not enough resources for ${building_id}` }; + } + } + } catch (e) { + log(`Resource check skipped: ${e}`); + } + + // Fire the build request + uw.gpAjax.ajaxPost('frontend_bridge', 'execute', { + model_url: 'BuildingOrder', + action_name: 'buildUp', + arguments: { building_id }, + town_id + }); + + await sleep(500); + return { ok: true, msg: `buildUp ${building_id} queued` }; + } + + // ---------------------------------------------------------------- + // Execute: Recruit + // ---------------------------------------------------------------- + async function executeRecruit(cmd) { + const { town_id, payload } = cmd; + const { unit_id, amount } = payload; + + const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id]; + if (!town) { + return { ok: false, msg: `Town ${town_id} not found` }; + } + + // Determine endpoint based on unit type + const navalUnits = [ + 'big_transporter', 'small_transporter', 'bireme', + 'attack_ship', 'trireme', 'colonize_ship', 'sea_monster' + ]; + const endpoint = navalUnits.includes(unit_id) ? 'building_docks' : 'building_barracks'; + + uw.gpAjax.ajaxPost(endpoint, 'build', { + unit_id, + amount: parseInt(amount) || 1, + town_id + }); + + await sleep(500); + return { ok: true, msg: `Recruit ${amount}x ${unit_id} submitted` }; + } + + // ---------------------------------------------------------------- + // Poll for and execute a pending command + // ---------------------------------------------------------------- + async function pollAndExecute() { + if (paused) return; + + let cmdData; + try { + const res = await fetch(`${BASE_URL}/api/commands/pending`); + cmdData = await res.json(); + } catch (e) { + log(`Poll failed: ${e}`); + return; + } + + const cmd = cmdData.command; + if (!cmd) return; // nothing pending + + log(`Executing command #${cmd.id} — type:${cmd.type} town:${cmd.town_id}`); + + let result; + try { + if (cmd.type === 'build') { + result = await executeBuild(cmd); + } else if (cmd.type === 'recruit') { + result = await executeRecruit(cmd); + } else { + result = { ok: false, msg: `Unknown command type: ${cmd.type}` }; + } + } catch (e) { + result = { ok: false, msg: `Exception: ${e.message}` }; + } + + log(`Command #${cmd.id} result: ${result.ok ? '✅' : '❌'} ${result.msg}`); + reportResult(cmd.id, result.ok, result.msg); + } + + // ---------------------------------------------------------------- + // Boot + // ---------------------------------------------------------------- + window.addEventListener('load', () => { + log('Grepolis Remote Control loaded'); + + // Push state immediately, then on interval + setTimeout(pushState, 5000); + setInterval(pushState, STATE_INTERVAL_MS); + + // Poll for commands + setInterval(pollAndExecute, POLL_INTERVAL_MS); + }); + +})(); diff --git a/app.py b/app.py new file mode 100644 index 0000000..7046ea9 --- /dev/null +++ b/app.py @@ -0,0 +1,16 @@ +from flask import Flask +from flask_cors import CORS +from db import init_db +from routes.api import api +from routes.dashboard import dashboard + +app = Flask(__name__) +CORS(app) # Allow cross-origin requests from the Tampermonkey script + +app.register_blueprint(api) +app.register_blueprint(dashboard) + +if __name__ == '__main__': + init_db() + print("✅ Grepolis Remote — DB initialised") + app.run(host='0.0.0.0', port=5050, debug=True) diff --git a/db.py b/db.py new file mode 100644 index 0000000..b82f1c9 --- /dev/null +++ b/db.py @@ -0,0 +1,45 @@ +import sqlite3 +import os + +DB_PATH = os.path.join(os.path.dirname(__file__), 'grepo.db') + + +def get_db(): + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + return conn + + +def init_db(): + conn = get_db() + c = conn.cursor() + + # Commands queue — sent from dashboard, consumed by Tampermonkey + c.execute(''' + CREATE TABLE IF NOT EXISTS commands ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + town_id TEXT NOT NULL, + town_name TEXT, + type TEXT NOT NULL, -- 'build' | 'recruit' + payload TEXT NOT NULL, -- JSON string + status TEXT NOT NULL DEFAULT 'pending', -- pending | executing | done | failed + result_msg TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ) + ''') + + # Town state — pushed by Tampermonkey every poll cycle + c.execute(''' + CREATE TABLE IF NOT EXISTS town_state ( + town_id TEXT PRIMARY KEY, + town_name TEXT, + player TEXT, + world_id TEXT, + data TEXT NOT NULL, -- full JSON snapshot + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ) + ''') + + conn.commit() + conn.close() diff --git a/grepo.db b/grepo.db new file mode 100644 index 0000000000000000000000000000000000000000..38c9f308fb31dfa2c2ecd1ab0576643f70e932a3 GIT binary patch literal 20480 zcmeI&O>5gQ7zc1W&Dzmo=wXmkK{+|0>0tB=EZf|YEp@uof#ooa8Y`W_Z?$F8z)quw zu`jf5($6zVd9%~x(%r(}kXW*$$MNrZd=TX3YuD$DjN>$G^}7D(b9x&v9#0}S7;k!9QNIm8}xyDdV_ z&d4|q0`mE$XOk?VN%kY=rJt53A_VhXvB^<4F9sl=tc&)b>yU<=!~Kvo%qX6i&E_&h z^SruWyR7S)A9?H{yAQ;s$)!1uRo`9lx$jnOxp{gm(|29jubrOiLzRsA;*8zrEE=)3 z?p`S^Yl97G+Y&3Aj~Dtu?c_xN?W&lgI1FjzWs7sA6lHOBEw%HkN(s`j9qZES7gI~x z1E<%qMWidsUQ6Yss#vCDi6#T9nOjSOPZL=y>R43QGLA~Cd7JxzXOf@9OxY;SeQuWG zCv+Obv|OL%2(y~tsUO{yR?7Dw&oM6|s)V&XKW ze!#raJyMqC0e8dfZqo}fO4$o)viws0Z(SR*K9A)W4hTR10uX=z1Rwwb2tWV=5P$## zc2xlT|6R>2o(lpHfB*y_009U<00Izz00bbg62Sg{Wq}q1AOHafKmY;|fB*y_009U< mU{?hW<%8k>|GS!AJQoBY009U<00Izz00bZa0SG`q3H$>t>?SP$ literal 0 HcmV?d00001 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f0861ea --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +flask +flask-cors