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!).

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==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);
}