Ikariam Auto Builder script

Ikariam Auto Builder HACK lets you add your building to an upgrading queue (YES! NOW YOU CAN DO IT FOR FREE!).

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(У мене вже є менеджер скриптів, дайте мені встановити його!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name       Ikariam Auto Builder script
// @namespace  http://google.com/
// @version    3.3.2
// @description  Ikariam Auto Builder HACK lets you add your building to an upgrading queue (YES! NOW YOU CAN DO IT FOR FREE!).
// @unwrap
// @include      *://s*.ikariam.gameforge.com/*
// @match	*://s*.ikariam.gameforge.com/*
// @copyright  2017-2026, Daniels Šatcs, [email protected]
// @grant unsafeWindow
// @run-at document-idle
// ==/UserScript==

this.$ = this.jQuery = jQuery.noConflict(true);

iframe = document.createElement('iframe');
document.body.appendChild(iframe);
console = { error: iframe.contentWindow.console.log, log: function() {}};


var key_currentUpdateInterval = "current_update_interval";
let key_state = "script_state";

var updateInterval = getValue(key_currentUpdateInterval, 600000);
var timerUpdateInterval = 1000; //In ms (1000 ms = 1s). This indicates how often will be the timer refreshed. RECOMMENDED: 1000

//TRANSLATIONS//
var addToQueueText = "Add to queue"; //This appears on the button
var queueEmptyText = "The building queue is empty in the selected city"; //This appears when the queue is empty

var VERBOSE = true; //Set to true to print (to console) information about what is going on.
var queuePrefix = "IAcity_"; //do NOT change this!
var separator = ";"; ////do NOT change this!


class ScriptState {
    constructor(data = {}) {
        this.cityIds = data.cityIds ?? [];
        this.cityCount = this.cityIds.length;
        // This ensures lastProcessed is ALWAYS at least an empty object
        this.lastProcessed = data.lastProcessed ?? {};
    }

    save() {
        // alert("Saving " + JSON.stringify(this));
        setValue(key_state, JSON.stringify(this));
    }
}

function setProcessed(state, cityId) {
    if (state.lastProcessed === undefined) {
        state.lastProcessed = {};
    }

    state.lastProcessed[cityId] = Date.now();
    state.save();
}

function getLastProcessed(state, cityId) {
    return state.lastProcessed[cityId] ?? 0;
}

function getState() {
    let value = getValue(key_state);
    if (value === undefined) {
        return undefined;
    } else {
        const parsedData = JSON.parse(value);
        return new ScriptState(parsedData);
    }
}

function initUI() {
    // Append queue table immediately when the specific container is found
    waitForElement("div.city_water_bottom").then(() => {
        if ($("#IA_queue").length === 0) {
            $("div.city_water_bottom").append('<div id="IA_queue" style="background-color: #DDD; width: 300px; position: absolute; left: 810px; border-radius: 4px; border: 1px solid #555"></div>');
            setTimeout(updateQueueView, 200);
        }
    });
}

//Script entry point:
initUI();
setTimeout(start, 1000);


function changeUpdateInterval(){
    updateInterval = Math.floor((Math.random() * (900000-600000+1)) + 600000); //Math.round((Math.random() * (Max-Min)) + Min);
    setValue(key_currentUpdateInterval, updateInterval);
    return updateInterval;
}

function callFunc(f,params){
    f.apply(this, params);
}


window.doAfterLoading = doAfterLoading;

function doAfterLoading(f){
    if(!isLoading()){
        var params = Array.prototype.slice.call(arguments);
        params.shift();
        f.apply(this,params);
    }
    else setTimeout(callFunc, 100, doAfterLoading, arguments);
}


function goToCity(id) {
    const url = new URL(window.location.href);
    const params = new URLSearchParams(url.search);
    params.set("cityId", id);
    params.set("currentCityId", id);

    url.search = params.toString();
    window.location.href = url.toString();

    // $("div#js_citySelectContainer span").click();
    // setTimeout(clickCityInDropDown, 100, id, callback); //Must wait here a little, bacause city drop down doesn't open immediately
}

function getCityName(){
    return $("div#js_citySelectContainer span a").text();
}

function getCityId() {
    const params = new URLSearchParams($("#js_CityPosition0Link").attr("href"));
    // const url = new URL(window.location.href);
    // const params = new URLSearchParams(url.search);

    return params.get('cityId');
}

function getQueue(){
    var raw = getValue(queuePrefix + getCityId());
    if(raw === undefined) return [];
    return raw.split(separator);
}

function canUpgrade() {
    return enoughResources()
    && !isUpgrading()
}

function inBuildingView() {
    return $("#buildingUpgrade").length > 0; // The building upgrade window is open
}

function enoughResources() {
    return inBuildingView() && $("div#buildingUpgrade ul.resources li.red").length === 0; // No resource is displayed as red (i.e. enough resources)
}


function addToQueue(id) {
    if (canUpgrade())
    {
        $("ul.actions li.upgrade a").click();
    }
    else {
        var before = getValue(queuePrefix + getCityId());
        if(before === undefined){
            setValue(queuePrefix + getCityId(), id);
        }
        else setValue(queuePrefix + getCityId(), before + separator + id);
        updateQueueView();
    }
}

function removeFromQueue(number){
    console.error("Removing building " + number + " from update queue of city " + getCityName());
    var before = getValue(queuePrefix + getCityId());
    if(before !== undefined){
        var after = "";
        var items = before.split(separator);
        for(var i = 0; i < items.length; i++){
            if(i != number) after = after + items[i] + separator;
        }

        after = after.substring(0,after.length-1);

        if(after === ""){
            deleteValue(queuePrefix + getCityId());
        }
        else {
            setValue(queuePrefix + getCityId(), after);
        }
    }
    updateQueueView();
}

function upgradeBuilding(){
    if($("div#buildingUpgrade ul.resources li.red").length === 0){ //If the city has enough resources for an upgrade
        $("ul.actions li.upgrade a").click(); //Clicks upgrade button
        doAfterLoading(closeWindow);
        removeFromQueue(0);
        if (VERBOSE) console.error("Upgrading and removing from queue!")
    }
    else {
        if (VERBOSE) console.error("Not enough resources, skipping");
        setTimeout(closeWindow, 100);
    }
}

function closeWindow(){
    $("div.close").click();
}

function getCitySkipDelay(){
    return Math.floor(Math.random() * (1000 - 500 + 1)) + 500; //Math.floor(Math.random() * (max - min + 1)) + min; Time to pause before going to the next city. Just for aesthetic purposes.
}

function isLoading(){
    return $("div#loadingPreview").css("display") != "none";
}
function isUpgrading(){
    return $("div.buildingUpgradeIcon:visible").length > 0;
}

function countdownPopupClose() {
    var el = $("#closePopupButton");
    timeLeft = el.text().substring(6);
    timeLeft -= 1;
    el.text("Close " + timeLeft);
    if (timeLeft <= 0) {
        hideUpgradePopup();
    }
    else setTimeout(countdownPopupClose, 1000);

}

function doWorkInCity(onDone) {
    let cityId = getCityId();
    var info = "<li style='list-style: unset;'>" + getCityName() + ": ";

    if (VERBOSE) console.error("   ==========   CITY = " + (cityId + "   ==========   "));
    var q = getQueue();
    var upgrading = isUpgrading();
    if(q.length === 0 || upgrading) { //Can't upgrade anything in this city. Go to the next city in that case.
        if(VERBOSE) {
            causes = []; if (q.length === 0) causes.push("queue is empty"); if (upgrading) causes.push("something is upgrading");
            info += "Nothing to do because " + causes.join(", ") + "</li>";
            console.error("Skipping city " + cityId + ": " + causes.join(", "));
        }
        onDone()
    }
    else {
        if (VERBOSE) console.error("Trying to upgrade... (since queue not empty and nothing upgrading a.t.m.");
        var enoughResources = $("#js_CityPosition" + q[0] + "ScrollName").css("color") != "rgb(170, 3, 3)"
        info += "Next in queue: " + getBuildingTitle(q[0]) + (enoughResources ? " <span style='color: green;'>Upgrading!</span>" : " <span style='color: red;'>Not enough resources, not removing from queue</span>");
        openBuilding(q[0]);
        doAfterLoading(upgradeBuilding);
    }
    $("#upgradeProgressList").append(info);
}

function anyWindowOpen(){
    return $("div.accordionContent").length !== 0;
}

function processCity(onDone){
    if(anyWindowOpen()){
        closeWindow();
        setTimeout(processCity, 100, onDone);
        return;
    }

    setTimeout(doWorkInCity, getCitySkipDelay(), onDone)
}

function getBuildingTitle(id){
    return $("a#js_CityPosition" + id + "Link").attr('title');
}

function updateQueueView() {
    $("div#IA_queue").text("");
    var queue = getQueue();
    if(queue.length !== 0){
        for(var i = 0; i < queue.length; i++){
            $("div#IA_queue").append('<p align="center" style="cursor:default; padding: 8px;">' + getBuildingTitle(queue[i]) + '<a onclick="removeFromQueue('+i+')" style="cursor:pointer;">&#10006;</a></p>');
        }
    }
    else $("div#IA_queue").append('<p align="center">' + queueEmptyText + '</p>');
}

function update(){
    var dialogCss = `position: fixed;
    width: 500px;
    height: 200px;
    top: 50%;
    left: 50%;
    margin-top: -100px; /* Negative half of height. */
    margin-left: -250px; /* Negative half of width. */
    background-color: rgb(250, 243, 215);
    z-index: 100;
    padding: 20px;
    overflow: scroll;`;

    //     $("body").append(`
    // <div id="IAB-upgrade-popup" style="${dialogCss}">
    //  <h2 style="font-size: 120%; font-weight: 700;"> Trying to upgrade buildings from the queue ... </h2>
    //  <ol id="upgradeProgressList">
    //
    //  </ol>
    // </div>
    // `);

    let state = getState();
    let currentCity = getCityId();
    for (let cityId of state.cityIds) {
        if (getLastProcessed(state, cityId) < parseInt(Date.now()) - parseInt(updateInterval)) {
            if (cityId != currentCity) {
                goToCity(cityId);
                return;
            } else {
                break;
            }
        }
    }
    if (getLastProcessed(state, currentCity) < parseInt(Date.now()) - parseInt(updateInterval)) {

        if($("div.close").length > 0) $("div.close").click();
        setValue("IA_lastUpdate", Date.now());
        changeUpdateInterval();

        processCity(function() {
            setProcessed(state, currentCity);
            state.save();
            setTimeout(update, getCitySkipDelay());
        });
        return;
    } else {
        // let idx = state.cityIds.indexOf(currentCity);
        // let nextIdx = idx + 1
        // if (nextIdx < state.cityCount) {
        //     goToCity(state.cityIds[nextIdx]);
        // } else {
        //     console.log("Done, nextIdx = " + nextIds);
        // }
    }
}

function hideUpgradePopup() {
    $("#IAB-upgrade-popup").remove();
}



function openBuilding(id){
    $("a#js_CityPosition" + id + "Link").click();
}

function getCityCount(){
    return $("#dropDown_js_citySelectContainer li.ownCity").length;
}

function getCityIds() {
    return $("#dropDown_js_citySelectContainer li.ownCity").map(function() {
        return $(this).attr("selectvalue");
    }).get();
}

function setValue(key,value) {
    localStorage[key]=value;
}

function getValue(key,def) {
    return localStorage[key] || def;
}

function deleteValue(key) {
    return delete localStorage[key];
}

function timerUpdateFunc() {
    var time = parseInt(getValue("IA_lastUpdate")) + parseInt(updateInterval) - parseInt(Date.now());
    time = Math.floor(time / 1000); //seconds
    let secondsLeft = time;
    if (secondsLeft % 5 == 0) {
        setTimeout(update, 200);
    }
    var minutes = Math.floor(time / 60);
    time -= minutes * 60;
    if (time < 10) time = "0" + time; //replace 2:9 with 2:09
    $("li#IA_timer").text(minutes + ":" + time);
    if(minutes < 0){
        update();
    }
}

function addButton(buildingId) {
    var canUpgradeImmediately = canUpgrade();
    console.error("Can upgrade " + canUpgradeImmediately);
    var buttonText = addToQueueText;
    if(anyWindowOpen() && inBuildingView() && $("div#IAButtonContainer").length === 0) {
        if($("ul.actions li.upgrade a").length > 0) {
            if (!canUpgradeImmediately) $("div#buildingUpgrade").append('<div style="margin: 20px;" id="IAButtonContainer"><a class="button" id="IAButton" onclick="addToQueue('+ buildingId + ');">' + buttonText + '</a></div>');
            if (!canUpgradeImmediately && !enoughResources()) $("div#buildingUpgrade").append('<div style="margin: 20px;"><i>Upgrade will stay in the queue until you collect necessary resources or you remove it from the queue.</i></div>');
        }
    }
    else setTimeout(addButton, 100, buildingId);
}

function addAdvisor() {
    $("div#advisors ul").prepend('<li class="advisor_bubble">' +
    '<a id="js_GlobalMenu_builder" href="javascript:advisorWindow()" title="Overview of towns and finances" class="normalactive">' +
    '<span class="smallFont">Auto builder</span>' +
    '</a>' +
    '<a id="js_GlobalMenu_builderPremium" class="plus_button plusteaser" title="View building queues"></a>' +
    '</li>');

    var container = $("div#advisors");
    container.width(container.width() / 4 * 5);
}

function advisorWindow() {
    var html = '<div id="architect_c" class="templateView focusable focus" style="left: 725px; top: -56px; right: auto; z-index: 98;">' +
    '<div id="architect" class="mainContentBox contentBox01h toggleMenu"><div class="hd header mainHeader draggable"><h3 id="js_mainBoxHeaderTitle">Auto Builder`s Office</h3><div id="js_backlinkButton" class="back" title="Hideout"></div><div class="close"></div></div><div class="bd mainContentScroll" style="height: 350px;"><div class="scroll_area scroll_disabled"><div class="scroll_arrow_top"></div><div class="scroller" style="width: 5px; top: 0px; left: 0px;"></div><div class="scroll_arrow_bottom"></div></div><div class="mainContent minimizableContent" style="top: 0px;"><div class="center" style="height: 60px; padding-bottom: 8px;"><a onclick="ajaxHandlerCall(\'?view=premium\');return false;"><img class="clickable" src="//gf1.geo.gfsrv.net/cdn0f/d3040171fefe8f8a0bbc75f6c8b344.jpg"></a></div>' +
    '        <div id="reductionBuilding">' +
    '            <div class="buildingDescription">' +
    '                <h1>Auto Builder\`s Office</h1>' +
    '                                    <p></p>' +
    '                            </div>' +
    '                            <div class="contentBox01h">' +
    '                <div class="buildingPictureImg"><img src="//gf1.geo.gfsrv.net/cdn6b/898c4f2181909413623c079be63094.png"></div>' +
    '                <h3 class="header">Building queues in your cities</h3>' +
    '                <div class="content">' +
    '                </div>' +
    '                <div class="footer"></div>' +
    '                </div>        </div>' +
    '    </div></div><div class="ft footer"></div></div></div>';
    $("div#container").append(html);
}

function waitForElement(selector) {
    return new Promise(resolve => {
        if (document.querySelector(selector)) {
            return resolve(document.querySelector(selector));
        }

        const observer = new MutationObserver(mutations => {
            if (document.querySelector(selector)) {
                observer.disconnect();
                resolve(document.querySelector(selector));
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    });
}


function start(){
    if($("#btn-login").length > 0) return;
    //addAdvisor();

    try {
        getCityCount();
    }
    catch(e){
        alert("Sorry, Ikariam Auto Builder Hack won't work in this browser. Please use Chrome or Opera!");
    }

    waitForElement("#dropDown_js_citySelectContainer li.ownCity").then((el) => {
        let state = getState();
        if (state === undefined) {
            new ScriptState(getCityIds()).save();
        } else {
            // Not the first run. Just check if any new cities were added.
            state.cityIds = getCityIds();
            state.cityCount = state.cityIds.length;
            state.save();
        }
    });
    // alert("Updating...")
    update();

    $("a[id^='js_CityPosition']").click(function(){
        var htmlID = $(this).attr('id');
        var id = htmlID.split("Link")[0].substring(15,htmlID.length + 1 - 4);
        doAfterLoading(addButton, id);
    });

    //Add CSS styling for add to queue button
    $("head").append("<style>div.addToQueueCityButton {z-index: 9000; cursor: pointer; position: absolute;right: 0px;bottom: 0px;border-top-left-radius: 3px;border-top-right-radius: 3px;border-bottom-left-radius: 3px;border-bottom-right-radius: 3px; background-color: rgba(200,200,200, 1); -webkit-transition: background-color 300ms linear;-moz-transition: background-color 300ms linear;-o-transition: background-color 300ms linear;-ms-transition: background-color 300ms linear;transition: background-color 300ms linear;} div.addToQueueCityButton:hover { background-color: rgba(200,100,100,1);}</style>");

    //Append queue table
    $("div.city_water_bottom").append('<div id="IA_queue" style="background-color: #DDD; width: 300px; position: absolute; left: 810px; border-radius: 4px; border: 1px solid #555"></div>');

    //Append timer
    $("DIV#GF_toolbar ul").append('<li id="IA_timer"></li> <img id="doCheckButton" alt="Check upgrades now" src="https://res.cloudinary.com/dshatz/image/upload/v1505664588/refresh_etrdy3.png" style="height: 1em; cursor:pointer;">');
    $("DIV#GF_toolbar ul").append('<li id="IA_script_feedback"><a target="_blank" href="https://greasyfork.org/en/scripts/8254-ikariam-auto-builder-hack">Auto-Builder</a></li>');

    $("#doCheckButton").click(function() {
        let state = getState();
        state.lastProcessed = {};
        state.save();
        update();
    });

    timerUpdateFunc();

    setInterval(timerUpdateFunc, timerUpdateInterval);
}