MJ: claude fix

This commit is contained in:
2026-04-26 12:53:14 +03:00
parent 7beece5aaa
commit 929af21d08
6 changed files with 757 additions and 862 deletions

View File

@@ -1,21 +1,31 @@
// ================================================================
// 00_config.js — Shared constants and utility helpers
// Runs first; everything here is available to all other modules.
// ================================================================
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';
// ---- Jitter helpers -----------------------------------------------
// Returns a random integer between min and max (inclusive) // Returns a random integer between min and max (inclusive)
function randInt(min, max) { function randInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min; return Math.floor(Math.random() * (max - min + 1)) + min;
} }
// Schedules fn to run after a random ms delay, then reschedules itself // Schedules fn to run after a random ms delay, then reschedules itself
function jitterLoop(fn, minMs, maxMs) { function jitterLoop(fn, minMs, maxMs) {
function schedule() { function schedule() {
setTimeout(async () => { setTimeout(async () => {
await fn(); await fn();
schedule(); // reschedule with a NEW random delay every time schedule();
}, randInt(minMs, maxMs)); }, randInt(minMs, maxMs));
} }
schedule(); schedule();
} }
// ---------------------------------------------------------------- function log(msg) {
console.log(`[GRC] ${msg}`);
}
function sleep(ms) {
return new Promise(r => setTimeout(r, ms));
}

View File

@@ -1,5 +1,8 @@
// Toolbar indicator button // ================================================================
// ---------------------------------------------------------------- // 01_ui.js — Toolbar button + pause toggle
// Depends on: uw, log (00_config.js)
// ================================================================
const btnHtml = ` const btnHtml = `
<div class="divider"></div> <div class="divider"></div>
<div class="activity" id="grc_btn" <div class="activity" id="grc_btn"
@@ -30,16 +33,3 @@
}, 4000); }, 4000);
uw.$(document).on('click', '#grc_btn', togglePause); 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));
}
// ----------------------------------------------------------------

View File

@@ -1,5 +1,8 @@
// Push town state to relay // ================================================================
// ---------------------------------------------------------------- // 02_state.js — Gather & push town state to the relay server
// Depends on: uw, BASE_URL, log, paused (00_config.js / 01_ui.js)
// ================================================================
function gatherState() { function gatherState() {
const towns = uw.ITowns?.towns || {}; const towns = uw.ITowns?.towns || {};
const player = uw.Game?.player_name || ''; const player = uw.Game?.player_name || '';
@@ -9,10 +12,7 @@
try { try {
const pm = uw.MM.getModels().Player[player_id]; const pm = uw.MM.getModels().Player[player_id];
if (pm && pm.attributes) alliance_name = pm.attributes.alliance_name; if (pm && pm.attributes) alliance_name = pm.attributes.alliance_name;
console.log("GrepoRemote: Extracted alliance_name =", alliance_name); } catch (e) {}
} catch (e) {
console.log("GrepoRemote: Failed to extract alliance_name", e);
}
const total_points = uw.Game?.player_points ?? 0; const total_points = uw.Game?.player_points ?? 0;
const world = uw.Game?.world_id || ''; const world = uw.Game?.world_id || '';
@@ -57,34 +57,21 @@
has_max_level: !!buildDataRaw[k].has_max_level has_max_level: !!buildDataRaw[k].has_max_level
}; };
} }
} catch (e) { } catch (e) { log(`Failed to gather build data: ${e}`); }
log(`Failed to gather build data: ${e}`);
}
// ---- Storage capacity ----------------------------------------------- // ---- Storage capacity -----------------------------------------------
// res.storage / res.storage_capacity are always 0 in Grepolis.
// Real capacity lives in GameData keyed by the storage building level.
let storageCapacity = 0; let storageCapacity = 0;
try { try {
// Strategy 1: dedicated getter (exists on some server versions)
storageCapacity = town.getStorageCapacity?.() || 0; storageCapacity = town.getStorageCapacity?.() || 0;
// Strategy 2: GameData.buildingData.storage.max_storage[level]
if (!storageCapacity) { if (!storageCapacity) {
const storageLevel = buildings.storage ?? 0; const storageLevel = buildings.storage ?? 0;
const gd = uw.GameData?.buildingData?.storage; const gd = uw.GameData?.buildingData?.storage;
storageCapacity = gd?.max_storage?.[storageLevel] storageCapacity = gd?.max_storage?.[storageLevel] || gd?.storage?.[storageLevel] || 0;
|| gd?.storage?.[storageLevel]
|| 0;
} }
// Strategy 3: resource object fallback keys
if (!storageCapacity) { if (!storageCapacity) {
storageCapacity = res.capacity || res.storage_capacity || res.storage || 0; storageCapacity = res.capacity || res.storage_capacity || res.storage || 0;
} }
} catch (e) { } catch (e) { log(`storage capacity lookup failed: ${e}`); }
log(`storage capacity lookup failed: ${e}`);
}
// ---- Market / Trade capacity ----------------------------------------- // ---- Market / Trade capacity -----------------------------------------
let marketCapacity = 0; let marketCapacity = 0;
@@ -92,14 +79,10 @@
const marketLevel = buildings.market ?? 0; const marketLevel = buildings.market ?? 0;
const gd = uw.GameData?.buildingData?.market; const gd = uw.GameData?.buildingData?.market;
marketCapacity = gd?.capacity_per_level?.[marketLevel] || 0; marketCapacity = gd?.capacity_per_level?.[marketLevel] || 0;
// Add Trade Office bonus if present
if (buildings.trade_office && buildings.trade_office > 0) { if (buildings.trade_office && buildings.trade_office > 0) {
marketCapacity += (uw.GameData?.buildingData?.trade_office?.capacity_extra_per_level || 500) * marketLevel; marketCapacity += (uw.GameData?.buildingData?.trade_office?.capacity_extra_per_level || 500) * marketLevel;
} }
} catch (e) { } catch (e) { log(`market capacity lookup failed: ${e}`); }
log(`market capacity lookup failed: ${e}`);
}
// ---- Coordinates & sea zone ----------------------------------------- // ---- Coordinates & sea zone -----------------------------------------
let x = null, y = null, sea = null; let x = null, y = null, sea = null;
@@ -122,13 +105,10 @@
let unitDataMap = {}; let unitDataMap = {};
try { try {
const gdUnits = uw.GameData?.units || {}; const gdUnits = uw.GameData?.units || {};
for (const u in gdUnits) { for (const u in gdUnits) {
if (u === 'militia') continue; if (u === 'militia') continue;
const reqBuildings = gdUnits[u].building_dependencies || {}; const reqBuildings = gdUnits[u].building_dependencies || {};
const reqResearch = gdUnits[u].research_dependencies || []; const reqResearch = gdUnits[u].research_dependencies || [];
let missing_deps = {}; let missing_deps = {};
for (const reqB in reqBuildings) { for (const reqB in reqBuildings) {
if ((buildings[reqB] || 0) < reqBuildings[reqB]) { if ((buildings[reqB] || 0) < reqBuildings[reqB]) {
@@ -140,19 +120,11 @@
missing_deps[reqR] = { name: reqR, needed_level: 'Έρευνα' }; missing_deps[reqR] = { name: reqR, needed_level: 'Έρευνα' };
} }
} }
const cost = gdUnits[u].resources || {}; const cost = gdUnits[u].resources || {};
const w = cost.wood || 0; const w = cost.wood || 0, s = cost.stone || 0, i = cost.iron || 0;
const s = cost.stone || 0; let enough = !(res.wood < w || res.stone < s || res.iron < i);
const i = cost.iron || 0;
let enough = true;
if (res.wood < w || res.stone < s || res.iron < i) enough = false;
unitDataMap[u] = { unitDataMap[u] = {
wood: w, wood: w, stone: s, iron: i,
stone: s,
iron: i,
pop: gdUnits[u].population || 0, pop: gdUnits[u].population || 0,
build_time: gdUnits[u].build_time || 0, build_time: gdUnits[u].build_time || 0,
enough_resources: enough, enough_resources: enough,
@@ -191,8 +163,6 @@
// ---- Extra town flags ----------------------------------------------- // ---- Extra town flags -----------------------------------------------
let has_premium = false; let has_premium = false;
let bonuses = {};
let wonder_points = 0;
try { try {
has_premium = uw.GameDataPremium?.isAdvisorActivated?.('curator') || false; has_premium = uw.GameDataPremium?.isAdvisorActivated?.('curator') || false;
} catch (e) {} } catch (e) {}
@@ -216,8 +186,8 @@
unitData: unitDataMap, unitData: unitDataMap,
researches, researches,
has_premium, has_premium,
bonuses, bonuses: {},
wonder_points, wonder_points: 0,
alliance_name, alliance_name,
farms, farms,
}; };
@@ -242,9 +212,7 @@
} }
} }
// ---------------------------------------------------------------- // ---- AJAX Interceptor for Zero-Delay Push ----------------------------
// AJAX Interceptor for Zero-Delay Push
// ----------------------------------------------------------------
let pushTimeout = null; let pushTimeout = null;
function debouncedPushState() { function debouncedPushState() {
if (paused) return; if (paused) return;
@@ -252,28 +220,21 @@
pushTimeout = setTimeout(() => { pushTimeout = setTimeout(() => {
log('⚡ State change detected (AJAX). Syncing to Remote...'); log('⚡ State change detected (AJAX). Syncing to Remote...');
pushState(); pushState();
}, 1200); // Wait 1.2s for memory models to update after AJAX finishes }, 1200);
} }
if (uw.$) { if (uw.$) {
uw.$(document).ajaxComplete(function (e, xhr, opt) { uw.$(document).ajaxComplete(function (e, xhr, opt) {
if (!opt || !opt.url) return; if (!opt || !opt.url) return;
// Ignore requests to our own bot server or map data
if (opt.url.includes(BASE_URL)) return; if (opt.url.includes(BASE_URL)) return;
if (opt.url.includes('map_tiles')) return; if (opt.url.includes('map_tiles')) return;
// Most game actions use "action=" parameter
// Switching towns uses "switch_town"
if (opt.url.includes('action=') || opt.url.includes('switch_town')) { if (opt.url.includes('action=') || opt.url.includes('switch_town')) {
debouncedPushState(); debouncedPushState();
} }
}); });
} }
// ---------------------------------------------------------------- // ---- Report command result back to relay -----------------------------
// Report command result back to relay
// ----------------------------------------------------------------
function reportResult(cmdId, status, message) { function reportResult(cmdId, status, message) {
fetch(`${BASE_URL}/api/commands/${cmdId}/result`, { fetch(`${BASE_URL}/api/commands/${cmdId}/result`, {
method: 'POST', method: 'POST',
@@ -281,5 +242,3 @@
body: JSON.stringify({ status, message }) body: JSON.stringify({ status, message })
}).catch(e => log(`reportResult failed: ${e}`)); }).catch(e => log(`reportResult failed: ${e}`));
} }
// ----------------------------------------------------------------

View File

@@ -1,7 +1,8 @@
// Captcha detection via MutationObserver // ================================================================
// Watches document.body for #hcaptcha_window being added/removed. // 03_captcha.js — hCaptcha detection & server alert
// Confirmed selector from live DOM: DIV#hcaptcha_window > DIV.h-captcha // Depends on: uw, BASE_URL, log, paused (00_config.js / 01_ui.js)
// ---------------------------------------------------------------- // ================================================================
let captchaActive = false; let captchaActive = false;
function reportCaptcha(detected) { function reportCaptcha(detected) {
@@ -20,7 +21,6 @@
let isVisible = false; let isVisible = false;
if (win) { if (win) {
// Check if it's actually visible on screen (not display: none by the game)
const style = window.getComputedStyle(win); const style = window.getComputedStyle(win);
if (style.display !== 'none' && style.visibility !== 'hidden') { if (style.display !== 'none' && style.visibility !== 'hidden') {
isVisible = true; isVisible = true;
@@ -38,11 +38,8 @@
reportCaptcha(true); reportCaptcha(true);
} else if (!isVisible && captchaActive) { } else if (!isVisible && captchaActive) {
captchaActive = false; captchaActive = false;
// Don't auto-resume — let the user click the button manually
log('✅ Captcha resolved — alert cleared (bot remains paused)'); log('✅ Captcha resolved — alert cleared (bot remains paused)');
reportCaptcha(false); reportCaptcha(false);
} }
}, 1000); }, 1000);
} }
// ----------------------------------------------------------------

View File

@@ -1,11 +1,12 @@
// ================================================================
// 04_execute.js — All command executors
// Depends on: uw, BASE_URL, log, sleep, randInt, paused, pushState
// ================================================================
// ----------------------------------------------------------------
// Execute: Farm Upgrade / Unlock // Execute: Farm Upgrade / Unlock
// Iterates all farm relations. Locked villages (status 0) get
// unlocked; unlocked villages below max level get upgraded.
// Uses random 800ms2000ms delay between each action.
// payload.threshold = minimum kill points to keep (default 0)
// ---------------------------------------------------------------- // ----------------------------------------------------------------
async function executeFarmUpgrade(cmd) { async function executeFarmUpgrade(cmd) {
const threshold = parseInt(cmd.payload?.threshold ?? 0);
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
let farmModels, relModels; let farmModels, relModels;
@@ -35,10 +36,7 @@
return { ok: false, msg: `Cannot build town list: ${e.message}` }; return { ok: false, msg: `Cannot build town list: ${e.message}` };
} }
let upgraded = 0; let upgraded = 0, unlocked = 0, skipped = 0, errors = 0;
let unlocked = 0;
let skipped = 0;
let errors = 0;
for (const town_id of polisList) { for (const town_id of polisList) {
const town = uw.ITowns?.towns?.[town_id]; const town = uw.ITowns?.towns?.[town_id];
@@ -57,24 +55,17 @@
const level = rel.attributes.expansion_stage || 0; const level = rel.attributes.expansion_stage || 0;
const expAt = rel.attributes.expansion_at || 0; const expAt = rel.attributes.expansion_at || 0;
// Skip if upgrade already in progress
if (expAt > now) { skipped++; continue; } if (expAt > now) { skipped++; continue; }
// Skip if already max level
if (status === 1 && level >= 5) { skipped++; continue; } if (status === 1 && level >= 5) { skipped++; continue; }
// Skip if locked and we can't unlock (status -1 means enemy)
if (status < 0) { skipped++; continue; } if (status < 0) { skipped++; continue; }
const isLocked = status === 0; const isLocked = status === 0;
const action = isLocked ? 'unlock' : 'upgrade'; const action = isLocked ? 'unlock' : 'upgrade';
const requestedAction = cmd.payload?.action_type; const requestedAction = cmd.payload?.action_type;
if (requestedAction && requestedAction !== action) { if (requestedAction && requestedAction !== action) { skipped++; continue; }
skipped++; continue;
}
if (paused) { if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
return { ok: false, msg: 'Aborted due to pause/captcha' };
}
log(`Farm ${action}: farm_id=${farm.attributes.id} level=${level} town=${town_id}`); log(`Farm ${action}: farm_id=${farm.attributes.id} level=${level} town=${town_id}`);
try { try {
@@ -87,22 +78,17 @@
isLocked ? unlocked++ : upgraded++; isLocked ? unlocked++ : upgraded++;
} catch (e) { errors++; } } catch (e) { errors++; }
// Random delay between actions: 1200ms 2500ms
await sleep(randInt(1200, 2500)); await sleep(randInt(1200, 2500));
} }
} }
} }
pushState(); // refresh farm data after upgrades pushState();
return { ok: true, msg: `Farm upgrade done: ${unlocked} unlocked, ${upgraded} upgraded, ${skipped} skipped, ${errors} errors` }; return { ok: true, msg: `Farm upgrade done: ${unlocked} unlocked, ${upgraded} upgraded, ${skipped} skipped, ${errors} errors` };
} }
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// Execute: Farm Loot // 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 500ms1500ms (never below 500ms)
// Between-town-group delay: random 30s90s
// ---------------------------------------------------------------- // ----------------------------------------------------------------
async function executeFarmLoot(cmd) { async function executeFarmLoot(cmd) {
const { loot_option } = cmd.payload || {}; const { loot_option } = cmd.payload || {};
@@ -121,7 +107,7 @@
} }
// Build island groups using MM (all towns, not just visible) // Build island groups using MM (all towns, not just visible)
const islandTownsMap = {}; // island_id -> [town_id1, town_id2] const islandTownsMap = {};
try { try {
const allTowns = uw.MM.getCollections().Town[0].models; const allTowns = uw.MM.getCollections().Town[0].models;
for (const town of allTowns) { for (const town of allTowns) {
@@ -133,13 +119,11 @@
} catch (e) { } catch (e) {
return { ok: false, msg: `Cannot build town list: ${e.message}` }; return { ok: false, msg: `Cannot build town list: ${e.message}` };
} }
const islandList = Object.keys(islandTownsMap);
const islandList = Object.keys(islandTownsMap);
log(`Farm: processing ${islandList.length} islands with option=${option}`); log(`Farm: processing ${islandList.length} islands with option=${option}`);
let claimed = 0; let claimed = 0, skipped = 0, errors = 0;
let skipped = 0;
let errors = 0;
for (let i = 0; i < islandList.length; i++) { for (let i = 0; i < islandList.length; i++) {
const island_id = islandList[i]; const island_id = islandList[i];
@@ -161,16 +145,11 @@
} }
const res = t.resources?.() || {}; const res = t.resources?.() || {};
const w = res.wood || 0; const w = res.wood || 0, s = res.stone || 0, ir = res.iron || 0;
const s = res.stone || 0;
const ir = res.iron || 0;
// If completely full (all 3 resources >= max storage), skip this town // Skip completely full towns
if (storageCapacity > 0 && w >= storageCapacity && s >= storageCapacity && ir >= storageCapacity) { if (storageCapacity > 0 && w >= storageCapacity && s >= storageCapacity && ir >= storageCapacity) continue;
continue;
}
// Pick town with most space (lowest total resources)
const total_res = w + s + ir; const total_res = w + s + ir;
if (total_res < lowest_total_res) { if (total_res < lowest_total_res) {
lowest_total_res = total_res; lowest_total_res = total_res;
@@ -188,13 +167,10 @@
const town = uw.ITowns?.towns?.[town_id]; const town = uw.ITowns?.towns?.[town_id];
if (!town) { skipped++; continue; } if (!town) { skipped++; continue; }
// Use the same method as the original script
const ix = town.getIslandCoordinateX(); const ix = town.getIslandCoordinateX();
const iy = town.getIslandCoordinateY(); const iy = town.getIslandCoordinateY();
if (ix == null || iy == null) { skipped++; continue; } if (ix == null || iy == null) { skipped++; continue; }
// Find ready farms on this island (mirrors original getLootableFarms exactly)
const readyFarms = []; const readyFarms = [];
for (const farm of farmModels) { for (const farm of farmModels) {
if (farm.attributes.island_x !== ix || farm.attributes.island_y !== iy) continue; if (farm.attributes.island_x !== ix || farm.attributes.island_y !== iy) continue;
@@ -217,10 +193,7 @@
log(`Farm: ${readyFarms.length} ready on island of town ${town_id}`); log(`Farm: ${readyFarms.length} ready on island of town ${town_id}`);
for (const farm of readyFarms) { for (const farm of readyFarms) {
if (paused) { if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
return { ok: false, msg: 'Aborted due to pause/captcha' };
}
try { try {
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', { uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
model_url: `FarmTownPlayerRelation/${farm.relation_id}`, model_url: `FarmTownPlayerRelation/${farm.relation_id}`,
@@ -231,14 +204,11 @@
claimed++; claimed++;
} catch (e) { errors++; } } catch (e) { errors++; }
// Random per-claim delay: 1000ms 2200ms
await sleep(randInt(1000, 2200)); await sleep(randInt(1000, 2200));
} }
// Refresh map icons after claiming (same as original)
try { uw.WMap.removeFarmTownLootCooldownIconAndRefreshLootTimers(); } catch (e) {} try { uw.WMap.removeFarmTownLootCooldownIconAndRefreshLootTimers(); } catch (e) {}
// Random between-island delay: 30s 90s (only if more islands remain)
if (i < islandList.length - 1) { if (i < islandList.length - 1) {
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' }; if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
const gap = randInt(30000, 90000); const gap = randInt(30000, 90000);
@@ -250,7 +220,6 @@
return { ok: true, msg: `Farm done: ${claimed} claimed, ${skipped} islands skipped, ${errors} errors` }; return { ok: true, msg: `Farm done: ${claimed} claimed, ${skipped} islands skipped, ${errors} errors` };
} }
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// Execute: Build // Execute: Build
// ---------------------------------------------------------------- // ----------------------------------------------------------------
@@ -259,11 +228,8 @@
const { building_id } = payload; const { building_id } = payload;
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id]; const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
if (!town) { if (!town) return { ok: false, msg: `Town ${town_id} not found in ITowns` };
return { ok: false, msg: `Town ${town_id} not found in ITowns` };
}
// Check build queue
const queueLen = town.buildingOrders?.()?.length ?? 0; const queueLen = town.buildingOrders?.()?.length ?? 0;
const hasCurator = uw.GameDataPremium?.isAdvisorActivated?.('curator'); const hasCurator = uw.GameDataPremium?.isAdvisorActivated?.('curator');
const maxQueue = hasCurator ? 7 : 2; const maxQueue = hasCurator ? 7 : 2;
@@ -271,7 +237,6 @@
return { ok: false, requeue: true, msg: `Build queue full (${queueLen}/${maxQueue})` }; return { ok: false, requeue: true, msg: `Build queue full (${queueLen}/${maxQueue})` };
} }
// Check resources
try { try {
const buildData = uw.MM.getModels?.()?.BuildingBuildData?.[town_id] const buildData = uw.MM.getModels?.()?.BuildingBuildData?.[town_id]
?.attributes?.building_data?.[building_id]; ?.attributes?.building_data?.[building_id];
@@ -285,11 +250,8 @@
return { ok: false, requeue: true, msg: `Not enough resources for ${building_id}` }; return { ok: false, requeue: true, msg: `Not enough resources for ${building_id}` };
} }
} }
} catch (e) { } catch (e) { log(`Resource check skipped: ${e}`); }
log(`Resource check skipped: ${e}`);
}
// Fire the build request — with a human-like reaction delay
const reactionMs = randInt(800, 2500); const reactionMs = randInt(800, 2500);
log(`Waiting ${reactionMs}ms before firing buildUp (reaction time)...`); log(`Waiting ${reactionMs}ms before firing buildUp (reaction time)...`);
await sleep(reactionMs); await sleep(reactionMs);
@@ -315,18 +277,14 @@
const { unit_id, amount } = payload; const { unit_id, amount } = payload;
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id]; const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
if (!town) { if (!town) return { ok: false, msg: `Town ${town_id} not found` };
return { ok: false, msg: `Town ${town_id} not found` };
}
// Determine endpoint based on unit type
const navalUnits = [ const navalUnits = [
'big_transporter', 'small_transporter', 'bireme', 'big_transporter', 'small_transporter', 'bireme',
'attack_ship', 'trireme', 'colonize_ship', 'sea_monster' 'attack_ship', 'trireme', 'colonize_ship', 'sea_monster'
]; ];
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); const reactionMs = randInt(800, 2500);
log(`Waiting ${reactionMs}ms before firing recruit (reaction time)...`); log(`Waiting ${reactionMs}ms before firing recruit (reaction time)...`);
await sleep(reactionMs); await sleep(reactionMs);
@@ -344,16 +302,14 @@
} }
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// Execute: Market // Execute: Market Offer
// ---------------------------------------------------------------- // ----------------------------------------------------------------
async function executeMarketOffer(cmd) { async function executeMarketOffer(cmd) {
const { town_id, payload } = cmd; const { town_id, payload } = cmd;
const { offer, offer_type, demand, demand_type, max_delivery_time, visibility } = payload; const { offer, offer_type, demand, demand_type, max_delivery_time, visibility } = payload;
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id]; const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
if (!town) { if (!town) return { ok: false, msg: `Town ${town_id} not found` };
return { ok: false, msg: `Town ${town_id} not found` };
}
const reactionMs = randInt(800, 2500); const reactionMs = randInt(800, 2500);
log(`Waiting ${reactionMs}ms before firing market offer (reaction time)...`); log(`Waiting ${reactionMs}ms before firing market offer (reaction time)...`);
@@ -365,9 +321,7 @@
model_url: 'CreateOffers/' + town_id, model_url: 'CreateOffers/' + town_id,
action_name: 'createOffer', action_name: 'createOffer',
captcha: null, captcha: null,
arguments: { arguments: { offer, offer_type, demand, demand_type, max_delivery_time, visibility }
offer, offer_type, demand, demand_type, max_delivery_time, visibility
}
}); });
await sleep(500); await sleep(500);
@@ -382,9 +336,7 @@
const { research_id } = payload; const { research_id } = payload;
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id]; const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
if (!town) { if (!town) return { ok: false, msg: `Town ${town_id} not found` };
return { ok: false, msg: `Town ${town_id} not found` };
}
const reactionMs = randInt(800, 2500); const reactionMs = randInt(800, 2500);
log(`Waiting ${reactionMs}ms before firing research (reaction time)...`); log(`Waiting ${reactionMs}ms before firing research (reaction time)...`);
@@ -396,11 +348,9 @@
model_url: 'ResearchOrder', model_url: 'ResearchOrder',
action_name: 'research', action_name: 'research',
arguments: { id: research_id }, arguments: { id: research_id },
town_id: town_id town_id
}); });
await sleep(500); await sleep(500);
return { ok: true, msg: `Research ${research_id} queued` }; return { ok: true, msg: `Research ${research_id} queued` };
} }
// ----------------------------------------------------------------

View File

@@ -1,5 +1,8 @@
// Poll for and execute pending commands (build + recruit + market) // ================================================================
// ---------------------------------------------------------------- // 05_main.js — Poll loop, command dispatch, boot
// Depends on: everything above
// ================================================================
async function pollAndExecute() { async function pollAndExecute() {
if (paused) return; if (paused) return;
const player_id = uw.Game?.player_id; const player_id = uw.Game?.player_id;
@@ -14,7 +17,6 @@
return; return;
} }
// Build queue, Recruit queue and Market queue are independent
const buildCmd = cmdData.build; const buildCmd = cmdData.build;
const recruitCmd = cmdData.recruit; const recruitCmd = cmdData.recruit;
const marketCmd = cmdData.market; const marketCmd = cmdData.market;
@@ -22,8 +24,6 @@
const farmCmd = cmdData.farm; const farmCmd = cmdData.farm;
const farmUpgradeCmd = cmdData.farm_upgrade; const farmUpgradeCmd = cmdData.farm_upgrade;
if (cmdData.sync_requested) { if (cmdData.sync_requested) {
log('Sync requested by server — pushing state immediately'); log('Sync requested by server — pushing state immediately');
pushState(); pushState();
@@ -49,6 +49,7 @@
} catch (e) { } catch (e) {
result = { ok: false, msg: `Exception: ${e}` }; result = { ok: false, msg: `Exception: ${e}` };
} }
const finalStatus = result.requeue ? 'pending' : (result.ok ? 'done' : 'failed'); const finalStatus = result.requeue ? 'pending' : (result.ok ? 'done' : 'failed');
log(`Command #${cmd.id}: ${finalStatus === 'done' ? '✅' : finalStatus === 'pending' ? '⏳' : '❌'} ${result.msg}`); log(`Command #${cmd.id}: ${finalStatus === 'done' ? '✅' : finalStatus === 'pending' ? '⏳' : '❌'} ${result.msg}`);
reportResult(cmd.id, finalStatus, result.msg); reportResult(cmd.id, finalStatus, result.msg);
@@ -66,7 +67,6 @@
const farmSettings = cmdData.farm_settings || {}; const farmSettings = cmdData.farm_settings || {};
if (farmSettings.enabled && !farmCmd) { if (farmSettings.enabled && !farmCmd) {
const nowTs = Math.floor(Date.now() / 1000); const nowTs = Math.floor(Date.now() / 1000);
// Check if ANY farm relation is ready
let readyFarms = []; let readyFarms = [];
try { try {
const coll = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation'); const coll = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation');
@@ -77,11 +77,9 @@
} catch (e) { /* silent */ } } catch (e) { /* silent */ }
if (readyFarms.length > 0) { if (readyFarms.length > 0) {
// Check if the CURRENT town's warehouse is full (>95%)
let allFull = true; let allFull = true;
let claimedAny = false; let claimedAny = false;
// Iterate over all towns that have ready farms
const towns = Object.values(uw.ITowns?.towns || {}); const towns = Object.values(uw.ITowns?.towns || {});
for (const town of towns) { for (const town of towns) {
const storage = town.resources?.()?.storage || town.get?.('storage') || 0; const storage = town.resources?.()?.storage || town.get?.('storage') || 0;
@@ -94,19 +92,17 @@
const pct = maxRes / storage; const pct = maxRes / storage;
if (pct < 0.95) { if (pct < 0.95) {
// This town has room — loot using its town_id context
allFull = false; allFull = false;
log(`⚡ Auto-farm: looting into town ${town.get?.('name')} (${Math.round(pct * 100)}% full)`); log(`⚡ Auto-farm: looting into town ${town.get?.('name')} (${Math.round(pct * 100)}% full)`);
await executeFarmLoot({ payload: { loot_option: farmSettings.loot_option } }); await executeFarmLoot({ payload: { loot_option: farmSettings.loot_option } });
claimedAny = true; claimedAny = true;
pushState(); pushState();
break; // one loot pass is enough per poll cycle break;
} }
} }
if (allFull) { if (allFull) {
log('⚠️ Auto-farm: ALL warehouses are full (>95%) — skipping loot this cycle'); log('⚠️ Auto-farm: ALL warehouses are full (>95%) — skipping loot this cycle');
// Report full status to backend so farm.html can show notice
try { try {
await fetch(`${BASE_URL}/api/farm_status?player_id=${uw.Game.player_id}`, { await fetch(`${BASE_URL}/api/farm_status?player_id=${uw.Game.player_id}`, {
method: 'POST', method: 'POST',
@@ -115,7 +111,6 @@
}); });
} catch (e) {} } catch (e) {}
} else if (claimedAny) { } else if (claimedAny) {
// Clear the full flag
try { try {
await fetch(`${BASE_URL}/api/farm_status?player_id=${uw.Game.player_id}`, { await fetch(`${BASE_URL}/api/farm_status?player_id=${uw.Game.player_id}`, {
method: 'POST', method: 'POST',
@@ -132,17 +127,11 @@
// Boot // Boot
// ---------------------------------------------------------------- // ----------------------------------------------------------------
window.addEventListener('load', () => { window.addEventListener('load', () => {
log('Grepolis Remote Control v3.5.9 loaded'); log('Grepolis Remote Control v4.0.0 (remote) loaded');
// Start captcha watcher immediately
detectCaptcha(); detectCaptcha();
// Push state once after load, then heartbeat every 1-2 minutes
// The AJAX interceptor handles the real-time syncing!
setTimeout(pushState, 5000); setTimeout(pushState, 5000);
jitterLoop(pushState, 60000, 120000); jitterLoop(pushState, 60000, 120000);
// Poll for commands every 818 seconds (randomized jitter)
jitterLoop(pollAndExecute, 8000, 18000); jitterLoop(pollAndExecute, 8000, 18000);
}); });