fixes
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Grepolis Remote Control
|
// @name Grepolis Remote Control
|
||||||
// @namespace http://tampermonkey.net/
|
// @namespace http://tampermonkey.net/
|
||||||
// @version 2.1
|
// @version 2.3
|
||||||
// @description Polls grepo.haunter-pets.top for remote commands and executes them in-game
|
// @description Polls grepo.haunter-pets.top for remote commands and executes them in-game
|
||||||
// @author Dimitrios
|
// @author Dimitrios
|
||||||
// @match https://*.grepolis.com/game/*
|
// @match https://*.grepolis.com/game/*
|
||||||
@@ -15,8 +15,22 @@
|
|||||||
|
|
||||||
const uw = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
|
const uw = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
|
||||||
const BASE_URL = 'https://grepo.haunter-pets.top';
|
const BASE_URL = 'https://grepo.haunter-pets.top';
|
||||||
const POLL_INTERVAL_MS = 5000; // poll for commands
|
|
||||||
const STATE_INTERVAL_MS = 30000; // push town state
|
// ---- Jitter helpers -----------------------------------------------
|
||||||
|
// Returns a random integer between min and max (inclusive)
|
||||||
|
function randInt(min, max) {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
// Schedules fn to run after a random ms delay, then reschedules itself
|
||||||
|
function jitterLoop(fn, minMs, maxMs) {
|
||||||
|
function schedule() {
|
||||||
|
setTimeout(async () => {
|
||||||
|
await fn();
|
||||||
|
schedule(); // reschedule with a NEW random delay every time
|
||||||
|
}, randInt(minMs, maxMs));
|
||||||
|
}
|
||||||
|
schedule();
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
// Toolbar indicator button
|
// Toolbar indicator button
|
||||||
@@ -303,7 +317,11 @@
|
|||||||
log(`Resource check skipped: ${e}`);
|
log(`Resource check skipped: ${e}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fire the build request
|
// Fire the build request — with a human-like reaction delay
|
||||||
|
const reactionMs = randInt(800, 2500);
|
||||||
|
log(`Waiting ${reactionMs}ms before firing buildUp (reaction time)...`);
|
||||||
|
await sleep(reactionMs);
|
||||||
|
|
||||||
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
||||||
model_url: 'BuildingOrder',
|
model_url: 'BuildingOrder',
|
||||||
action_name: 'buildUp',
|
action_name: 'buildUp',
|
||||||
@@ -334,6 +352,11 @@
|
|||||||
];
|
];
|
||||||
const endpoint = navalUnits.includes(unit_id) ? 'building_docks' : 'building_barracks';
|
const endpoint = navalUnits.includes(unit_id) ? 'building_docks' : 'building_barracks';
|
||||||
|
|
||||||
|
// Fire the recruit request — with a human-like reaction delay
|
||||||
|
const reactionMs = randInt(800, 2500);
|
||||||
|
log(`Waiting ${reactionMs}ms before firing recruit (reaction time)...`);
|
||||||
|
await sleep(reactionMs);
|
||||||
|
|
||||||
uw.gpAjax.ajaxPost(endpoint, 'build', {
|
uw.gpAjax.ajaxPost(endpoint, 'build', {
|
||||||
unit_id,
|
unit_id,
|
||||||
amount: parseInt(amount) || 1,
|
amount: parseInt(amount) || 1,
|
||||||
@@ -345,7 +368,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
// Poll for and execute a pending command
|
// Poll for and execute pending commands (build + recruit in parallel)
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
async function pollAndExecute() {
|
async function pollAndExecute() {
|
||||||
if (paused) return;
|
if (paused) return;
|
||||||
@@ -359,41 +382,86 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cmd = cmdData.command;
|
// Build queue and Recruit queue are now independent
|
||||||
if (!cmd) return; // nothing pending
|
const buildCmd = cmdData.build;
|
||||||
|
const recruitCmd = cmdData.recruit;
|
||||||
|
|
||||||
log(`Executing command #${cmd.id} — type:${cmd.type} town:${cmd.town_id}`);
|
const execute = async (cmd) => {
|
||||||
|
if (!cmd) return;
|
||||||
let result;
|
log(`Executing command #${cmd.id} — type:${cmd.type} town:${cmd.town_id}`);
|
||||||
try {
|
let result;
|
||||||
if (cmd.type === 'build') {
|
try {
|
||||||
result = await executeBuild(cmd);
|
if (cmd.type === 'build') result = await executeBuild(cmd);
|
||||||
} else if (cmd.type === 'recruit') {
|
else if (cmd.type === 'recruit') result = await executeRecruit(cmd);
|
||||||
result = await executeRecruit(cmd);
|
else result = { ok: false, msg: `Unknown type: ${cmd.type}` };
|
||||||
} else {
|
} catch (e) {
|
||||||
result = { ok: false, msg: `Unknown command type: ${cmd.type}` };
|
result = { ok: false, msg: `Exception: ${e.message}` };
|
||||||
}
|
}
|
||||||
} catch (e) {
|
const finalStatus = result.requeue ? 'pending' : (result.ok ? 'done' : 'failed');
|
||||||
result = { ok: false, msg: `Exception: ${e.message}` };
|
log(`Command #${cmd.id}: ${finalStatus === 'done' ? '✅' : finalStatus === 'pending' ? '⏳' : '❌'} ${result.msg}`);
|
||||||
}
|
reportResult(cmd.id, finalStatus, result.msg);
|
||||||
|
};
|
||||||
|
|
||||||
const finalStatus = result.requeue ? 'pending' : (result.ok ? 'done' : 'failed');
|
// Run both queues concurrently — they do NOT block each other
|
||||||
log(`Command #${cmd.id} result: ${finalStatus === 'done' ? '✅' : (finalStatus === 'pending' ? '⏳' : '❌')} ${result.msg}`);
|
await Promise.all([execute(buildCmd), execute(recruitCmd)]);
|
||||||
reportResult(cmd.id, finalStatus, result.msg);
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
// Observers — instant triggers on meaningful game events
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
function setupObservers() {
|
||||||
|
try {
|
||||||
|
// 1. Town switch — instant dashboard sync when you click a different town
|
||||||
|
uw.$.Observer(uw.GameEvents.town.town_switch).subscribe(() => {
|
||||||
|
log('Observer: town switched — pushing state');
|
||||||
|
pushState();
|
||||||
|
});
|
||||||
|
} catch(e) { log(`Observer town_switch failed: ${e}`); }
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 2. Building finished — push state AND immediately try the next build command
|
||||||
|
uw.$.Observer(uw.GameEvents.town.building.order_completed).subscribe(() => {
|
||||||
|
log('Observer: building completed — pushing state + polling');
|
||||||
|
pushState();
|
||||||
|
pollAndExecute();
|
||||||
|
});
|
||||||
|
} catch(e) { log(`Observer building.order_completed failed: ${e}`); }
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 3. Troops finished training — same logic for the recruit queue
|
||||||
|
uw.$.Observer(uw.GameEvents.town.unit.order_completed).subscribe(() => {
|
||||||
|
log('Observer: unit order completed — pushing state + polling');
|
||||||
|
pushState();
|
||||||
|
pollAndExecute();
|
||||||
|
});
|
||||||
|
} catch(e) { log(`Observer unit.order_completed failed: ${e}`); }
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 4. Points changed — fires when a building finishes (extra safety net)
|
||||||
|
uw.$.Observer(uw.GameEvents.player.points.changed).subscribe(() => {
|
||||||
|
log('Observer: player points changed — pushing state');
|
||||||
|
pushState();
|
||||||
|
});
|
||||||
|
} catch(e) { log(`Observer player.points.changed failed: ${e}`); }
|
||||||
|
|
||||||
|
log('Observers registered successfully');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
// Boot
|
// Boot
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
log('Grepolis Remote Control loaded');
|
log('Grepolis Remote Control v2.3 loaded');
|
||||||
|
|
||||||
// Push state immediately, then on interval
|
// Push state once after load, then every 45–90 seconds (randomized)
|
||||||
setTimeout(pushState, 5000);
|
setTimeout(pushState, 5000);
|
||||||
setInterval(pushState, STATE_INTERVAL_MS);
|
jitterLoop(pushState, 45000, 90000);
|
||||||
|
|
||||||
// Poll for commands
|
// Poll for commands every 8–18 seconds (randomized jitter)
|
||||||
setInterval(pollAndExecute, POLL_INTERVAL_MS);
|
jitterLoop(pollAndExecute, 8000, 18000);
|
||||||
|
|
||||||
|
// Wire up game event observers after a short delay to ensure game is ready
|
||||||
|
setTimeout(setupObservers, 6000);
|
||||||
});
|
});
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -62,38 +62,44 @@ def receive_state():
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# GET /api/commands/pending
|
# GET /api/commands/pending
|
||||||
# Tampermonkey polls this to get the next command to execute.
|
# Tampermonkey polls this to get the next command to execute.
|
||||||
# Returns ONE command at a time, marks it as 'executing'.
|
# Returns one 'build' AND one 'recruit' command independently,
|
||||||
|
# so both queues are served in parallel without blocking each other.
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@api.route('/api/commands/pending', methods=['GET'])
|
def _fetch_pending_of_type(c, cmd_type):
|
||||||
def get_pending_command():
|
|
||||||
conn = get_db()
|
|
||||||
c = conn.cursor()
|
|
||||||
row = c.execute('''
|
row = c.execute('''
|
||||||
SELECT * FROM commands
|
SELECT * FROM commands
|
||||||
WHERE status = 'pending'
|
WHERE status = 'pending' AND type = ?
|
||||||
ORDER BY id ASC
|
ORDER BY id ASC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
''').fetchone()
|
''', (cmd_type,)).fetchone()
|
||||||
|
|
||||||
if not row:
|
if not row:
|
||||||
conn.close()
|
return None
|
||||||
return jsonify({'command': None})
|
|
||||||
|
|
||||||
c.execute('''
|
c.execute('''
|
||||||
UPDATE commands
|
UPDATE commands
|
||||||
SET status = 'executing', updated_at = ?
|
SET status = 'executing', updated_at = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
''', (datetime.utcnow().isoformat(), row['id']))
|
''', (datetime.utcnow().isoformat(), row['id']))
|
||||||
|
return {
|
||||||
|
'id': row['id'],
|
||||||
|
'town_id': row['town_id'],
|
||||||
|
'type': row['type'],
|
||||||
|
'payload': json.loads(row['payload'])
|
||||||
|
}
|
||||||
|
|
||||||
|
@api.route('/api/commands/pending', methods=['GET'])
|
||||||
|
def get_pending_command():
|
||||||
|
conn = get_db()
|
||||||
|
c = conn.cursor()
|
||||||
|
|
||||||
|
build_cmd = _fetch_pending_of_type(c, 'build')
|
||||||
|
recruit_cmd = _fetch_pending_of_type(c, 'recruit')
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'command': {
|
'build': build_cmd,
|
||||||
'id': row['id'],
|
'recruit': recruit_cmd
|
||||||
'town_id': row['town_id'],
|
|
||||||
'type': row['type'],
|
|
||||||
'payload': json.loads(row['payload'])
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user