Upload files to "/"
This commit is contained in:
269
GrepolisRemoteControl.user.js
Normal file
269
GrepolisRemoteControl.user.js
Normal file
@@ -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 = `
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="activity" id="grc_btn"
|
||||||
|
style="filter: brightness(70%) sepia(100%) hue-rotate(200deg) saturate(1000%) contrast(0.8);">
|
||||||
|
<p id="grc_label" style="position:relative;top:-8px;font-weight:bold;z-index:6;">Remote</p>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
16
app.py
Normal file
16
app.py
Normal file
@@ -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)
|
||||||
45
db.py
Normal file
45
db.py
Normal file
@@ -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()
|
||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
flask
|
||||||
|
flask-cors
|
||||||
Reference in New Issue
Block a user