Untitled diff
715 lines
"use strict";
"use strict";
var settings = new Store("settings", {
var settings = new Store("settings", {
    "preferFriendSummons": true,
    "preferFriendSummons": true,
    "preferNonFriendSummonsInFavorites": true,
    "preferNonFriendSummonsInFavorites": true,
    "preferLimitBrokenSummons": true,
    "preferLimitBrokenSummons": true,
    "preferHighLevelSummons": true,
    "preferHighLevelSummons": true,
    "notifyOnFullAP": false,
    "notifyOnFullAP": false,
    "notifyOnFullBP": false,
    "notifyOnFullBP": false,
    "showSkillCooldowns": true,
    "showSkillCooldowns": true,
    "showDebuffTimers": true,
    "showDebuffTimers": true,
    "showBuffTimers": true,
    "showBuffTimers": true,
    "monitorRaidDebuffs": true,
    "monitorRaidDebuffs": true,
    "keyboardShortcuts": true,
    "keyboardShortcuts": true,
    "showBookmarks": true,
    "showBookmarks": true,
    "preferredSummonElement": "",
    "preferredSummonElement": "",
    "submenuSize": 1.0,
    "submenuSize": 1.0,
    "bookmarksSize": 1.0,
    "bookmarksSize": 1.0,
    "bookmarksMenuSize": 1.0,
    "bookmarksMenuSize": 1.0,
    "recentQuest": null,
    "recentQuest": null,
    "showQuickPanels": true,
    "showQuickPanels": true,
    "showGaugeOverlays": true,
    "showGaugeOverlays": true,
    "openBookmarksOnClick": false,
    "openBookmarksOnClick": false,
    "fixJPFontRendering": true,
    "fixJPFontRendering": true,
    "enableCoOpEnhancements": true,
    "enableCoOpEnhancements": true,
    "dropdownFix": true,
    "dropdownFix": true,
    "disableMiddleRightClick": true,
    "disableMiddleRightClick": true,
    "statusPanel": true,
    "statusPanel": true,
    "itemsPanel": true,
    "itemsPanel": true,
    "raidsPanel": false,
    "raidsPanel": false,
    "clockBrightness": 0.65,
    "clockBrightness": 0.65,
    "oneClickQuickSummons": true,
    "oneClickQuickSummons": true,
    "bookmarksInactiveIcon": null,
    "bookmarksInactiveIcon": null,
    "bookmarksActiveIcon": null,
    "bookmarksActiveIcon": null,
    "bookmarksIconPadding": 100,
    "bookmarksIconPadding": 100,
    "horizontalBookmarks": false,
    "horizontalBookmarks": false,
    "statusPanelBuffs": false,
    "statusPanelBuffs": false,
    "statusPanelExpiringBuffs": true,
    "statusPanelExpiringBuffs": true,
    "betterEnglishFont": false,
    "betterEnglishFont": false,
    "showItemWatchButtons": true,
    "showItemWatchButtons": true,
    "showPartyNames": true,
    "showPartyNames": true,
    "filterEnemyTimers": true,
    "filterEnemyTimers": true,
    "showPerformanceHud": false,
    "showPerformanceHud": false,
    "showNetworkHud": false,
    "showNetworkHud": false,
    "showWeaponAttack": true,
    "showWeaponAttack": true,
    "showSkillActivationIndicator": true,
    "showSkillActivationIndicator": true,
    "autofillBackupTweets": true,
    "autofillBackupTweets": true,
    "moveCoOpFooter": true,
    "moveCoOpFooter": true,
    "largeQuickPanels": false,
    "largeQuickPanels": false,
    "showPartyHelp": false,
    "showPartyHelp": false,
    "keepSoundOnBlur": true,
    "keepSoundOnBlur": true,
    "stuckButtonWorkaround2": true,
    "stuckButtonWorkaround2": true,
    "showLastActionTimer": true,
    "showLastActionTimer": true,
    "smartSupports": true,
    "smartSupports": true,
    "defaultToSmartSupports": false,
    "defaultToSmartSupports": false,
    "disablePhalanxSticker": true,
    "disablePhalanxSticker": true,
    "summonOrder": "{}",
    "summonOrder": "{}",
    "password": "",
    "password": "",
    "minimumPopupWait": 350,
    "minimumPopupWait": 350,
    "maximumPopupWait": 1750,
    "maximumPopupWait": 1750,
    "focusQuickPanels": true,
    "focusQuickPanels": true,
    "newSkillSystem": true
    "newSkillSystem": true
});
});
var failedUpdateMinimumDelay = 10 * 1000;
var failedUpdateMinimumDelay = 10 * 1000;
var minimumUpdateDelay = 60 * 4 * 1000;
var minimumUpdateDelay = 60 * 4 * 1000;
var minimumRaidUpdateDelay = 30 * 1000;
var minimumRaidUpdateDelay = 30 * 1000;
var minimumItemUpdateDelay = 60 * 30 * 1000;
var minimumItemUpdateDelay = 60 * 30 * 1000;
var minimumHaloUpdateDelay = 60 * 30 * 1000;
var minimumHaloUpdateDelay = 60 * 30 * 1000;
var secretKeysByTabId = {};
var secretKeysByTabId = {};
var lastFailure = -1;
var lastFailure = -1;
var users = {};
var users = {};
var lastRaidCodes = {};
var lastRaidCodes = {};
var isDead = true;
var isDead = true;
var isShutdown = false;
var isShutdown = false;
var lastLocation = null;
var lastLocation = null;
var idleRedirectPending = false;
var idleRedirectPending = false;
var lastRedirectTarget = null;
var lastRedirectTarget = null;
chrome.runtime.onMessage.addListener(onRuntimeMessage);
chrome.runtime.onMessage.addListener(onRuntimeMessage);
chrome.alarms.onAlarm.addListener(onAlarm);
chrome.alarms.onAlarm.addListener(onAlarm);
chrome.runtime.onInstalled.addListener(function () {
chrome.runtime.onInstalled.addListener(function () {
    var dc = chrome.declarativeContent;
    var dc = chrome.declarativeContent;
    dc.onPageChanged.removeRules(undefined, function () {
    dc.onPageChanged.removeRules(undefined, function () {
        dc.onPageChanged.addRules([
        dc.onPageChanged.addRules([
            {
            {
                conditions: [
                conditions: [
                    new dc.PageStateMatcher({
                    new dc.PageStateMatcher({
                        pageUrl: { hostEquals: 'game.granbluefantasy.jp', schemes: ["https", "http"] },
                        pageUrl: { hostEquals: 'game.granbluefantasy.jp', schemes: ["https", "http"] },
                    }),
                    }),
                    new dc.PageStateMatcher({
                    new dc.PageStateMatcher({
                        pageUrl: { hostEquals: 'gbf.game.mbga.jp', schemes: ["https", "http"] },
                        pageUrl: { hostEquals: 'gbf.game.mbga.jp', schemes: ["https", "http"] },
                    })
                    })
                ],
                ],
                actions: [new dc.ShowPageAction()]
                actions: [new dc.ShowPageAction()]
            }
            }
        ]);
        ]);
    });
    });
});
});
log("Started");
log("Started");
function getAdjustedSettings() {
function getAdjustedSettings() {
    var result = settings.toObject();
    var result = settings.toObject();
    result.allowDragSelect = false;
    result.allowDragSelect = false;
    // result.autoSkipToQuestResults = false;
    // result.autoSkipToQuestResults = false;
    // result.oneClickQuickSummons = false;
    // result.oneClickQuickSummons = false;
    result.realtimeRaidList = false;
    result.realtimeRaidList = false;
    // result.raidsPanel = false;
    // result.raidsPanel = false;
    // result.showQuickPanels = false;
    // result.showQuickPanels = false;
    result.touchInputSupport = false;
    result.touchInputSupport = false;
    // Cygamesssssssssssssss
    // Cygamesssssssssssssss
    result.autofillBackupTweets = false;
    result.autofillBackupTweets = false;
    // API changes broke this
    // API changes broke this
    result.detailedUpgradePage = false;
    result.detailedUpgradePage = false;
    return result;
    return result;
}
}
;
;
function log(...args) {
function log(...args) {
    args.unshift((new Date()).toLocaleString() + " |");
    args.unshift((new Date()).toLocaleString() + " |");
    console.log.apply(console, args);
    console.log.apply(console, args);
}
}
;
;
function getWatchedItems() {
function getWatchedItems() {
    var result = JSON.parse(settings.get("watchedItems") || "[]");
    var result = JSON.parse(settings.get("watchedItems") || "[]");
    if ((result.length === 1) && (result[0] === null))
    if ((result.length === 1) && (result[0] === null))
        result = [];
        result = [];
    var targetCounts = JSON.parse(settings.get("targetItemCounts") || "{}");
    var targetCounts = JSON.parse(settings.get("targetItemCounts") || "{}");
    var itemGroups = JSON.parse(settings.get("itemGroups") || "{}"); 
    return {
    return {
        items: result,
        items: result,
        counts: targetCounts
        counts: targetCounts, 
        groups: itemGroups
    };
    };
}
}
;
;
function setItemWatchTarget(id, count) {
function setItemWatchTarget(id, count) {
    var targetCounts = JSON.parse(settings.get("targetItemCounts") || "{}");
    var targetCounts = JSON.parse(settings.get("targetItemCounts") || "{}");
    targetCounts[id] = count;
    targetCounts[id] = count;
    settings.set("targetItemCounts", JSON.stringify(targetCounts));
    settings.set("targetItemCounts", JSON.stringify(targetCounts));
}
}
;
;
function setItemGroup(id, group) {
    var itemGroups = JSON.parse(settings.get("itemGroups") || "{}");
    if (group!="undefined"){itemGroups[id] = group;}
    else{
        //hack because it's not an array so can't just be pushed to
        var max = JSON.parse(settings.get("itemGroupsNumber") || "0");
        var newmax = "" + (parseInt(max)+1);
        settings.set("itemGroupsNumber", JSON.stringify(newmax));
        itemGroups[id] = newmax;
    }
    settings.set("itemGroups", JSON.stringify(itemGroups));
}
;
function addWatchedItem(id) {
function addWatchedItem(id) {
    var items = getWatchedItems().items;
    var items = getWatchedItems().items;
    if (items.indexOf(id) >= 0)
    if (items.indexOf(id) >= 0)
        return items;
        return items;
    if (!id)
    if (!id)
        return items;
        return items;
    items.push(id);
    items.push(id);
    settings.set("watchedItems", JSON.stringify(items));
    settings.set("watchedItems", JSON.stringify(items));
    return items;
    return items;
}
}
;
;
function removeWatchedItem(id) {
function removeWatchedItem(id) {
    var items = getWatchedItems().items;
    var items = getWatchedItems().items;
    var index = items.indexOf(id);
    var index = items.indexOf(id);
    if (index < 0)
    if (index < 0)
        return items;
        return items;
    items.splice(index, 1);
    items.splice(index, 1);
    settings.set("watchedItems", JSON.stringify(items));
    settings.set("watchedItems", JSON.stringify(items));
    return items;
    return items;
}
}
;
;
function getFavedSummons() {
function getFavedSummons() {
    var result = JSON.parse(settings.get("favedSummons") || "[]");
    var result = JSON.parse(settings.get("favedSummons") || "[]");
    if ((result.length === 1) && (result[0] === null))
    if ((result.length === 1) && (result[0] === null))
        result = [];
        result = [];
    return result;
    return result;
}
}
;
;
function addFavedSummon(id) {
function addFavedSummon(id) {
    var items = getFavedSummons();
    var items = getFavedSummons();
    if (items.indexOf(id) >= 0)
    if (items.indexOf(id) >= 0)
        return items;
        return items;
    if (!id)
    if (!id)
        return items;
        return items;
    items.push(id);
    items.push(id);
    settings.set("favedSummons", JSON.stringify(items));
    settings.set("favedSummons", JSON.stringify(items));
    return items;
    return items;
}
}
;
;
function removeFavedSummon(id) {
function removeFavedSummon(id) {
    var items = getFavedSummons();
    var items = getFavedSummons();
    var index = items.indexOf(id);
    var index = items.indexOf(id);
    if (index < 0)
    if (index < 0)
        return items;
        return items;
    items.splice(index, 1);
    items.splice(index, 1);
    settings.set("favedSummons", JSON.stringify(items));
    settings.set("favedSummons", JSON.stringify(items));
    return items;
    return items;
}
}
;
;
function getUserDict(uid) {
function getUserDict(uid) {
    var dict = users[uid];
    var dict = users[uid];
    if (!dict)
    if (!dict)
        dict = users[uid] = { lastUpdate: [] };
        dict = users[uid] = { lastUpdate: [] };
    return dict;
    return dict;
}
}
;
;
function setUserData(uid, key, data) {
function setUserData(uid, key, data) {
    var dict = getUserDict(uid);
    var dict = getUserDict(uid);
    if (data) {
    if (data) {
        dict.lastUpdate[key] = Date.now();
        dict.lastUpdate[key] = Date.now();
        dict[key] = data;
        dict[key] = data;
    }
    }
    else {
    else {
        dict.lastUpdate[key] = 0;
        dict.lastUpdate[key] = 0;
        dict[key] = null;
        dict[key] = null;
    }
    }
}
}
;
;
function getUserData(uid, key) {
function getUserData(uid, key) {
    var dict = getUserDict(uid);
    var dict = getUserDict(uid);
    return dict[key];
    return dict[key];
}
}
;
;
function getLastDataUpdate(uid, key) {
function getLastDataUpdate(uid, key) {
    var dict = getUserDict(uid);
    var dict = getUserDict(uid);
    return dict.lastUpdate[key];
    return dict.lastUpdate[key];
}
}
;
;
function formatItemCounters(uid) {
function formatItemCounters(uid) {
    var itemCounters = getUserDict(uid).itemCounters;
    var itemCounters = getUserDict(uid).itemCounters;
    if (!itemCounters)
    if (!itemCounters)
        return null;
        return null;
    var counterDict = {};
    var counterDict = {};
    for (var i = 0, l = itemCounters.length; i < l; i++) {
    for (var i = 0, l = itemCounters.length; i < l; i++) {
        var item = itemCounters[i];
        var item = itemCounters[i];
        counterDict[item.item_id] = item;
        counterDict[item.item_id] = item;
    }
    }
    return counterDict;
    return counterDict;
}
}
;
;
function onRuntimeMessage(msg, sender, sendResponse) {
function onRuntimeMessage(msg, sender, sendResponse) {
    if (chrome.runtime.lastError)
    if (chrome.runtime.lastError)
        log(chrome.runtime.lastError);
        log(chrome.runtime.lastError);
    var key = msg.type;
    var key = msg.type;
    var userDict;
    var userDict;
    if (msg.uid)
    if (msg.uid)
        userDict = getUserDict(msg.uid);
        userDict = getUserDict(msg.uid);
    var tabId;
    var tabId;
    if (sender.tab && sender.tab.id)
    if (sender.tab && sender.tab.id)
        tabId = sender.tab.id;
        tabId = sender.tab.id;
    else if (msg.tabId)
    else if (msg.tabId)
        tabId = msg.tabId;
        tabId = msg.tabId;
    if (tabId <= 0) {
    if (tabId <= 0) {
        log("Message has no tab id", key);
        log("Message has no tab id", key);
        return;
        return;
    }
    }
    var senderUrl = sender.url || "";
    var senderUrl = sender.url || "";
    if ((senderUrl.indexOf("granbluefantasy.jp") >= 0) ||
    if ((senderUrl.indexOf("granbluefantasy.jp") >= 0) ||
        (senderUrl.indexOf("mbga.jp") >= 0)) {
        (senderUrl.indexOf("mbga.jp") >= 0)) {
    }
    }
    else if ((senderUrl.indexOf("chrome-extension://") >= 0) &&
    else if ((senderUrl.indexOf("chrome-extension://") >= 0) &&
        (senderUrl.indexOf("api.html") >= 0)) {
        (senderUrl.indexOf("api.html") >= 0)) {
        switch (key) {
        switch (key) {
            case "apiRequest":
            case "apiRequest":
            case "getSettings":
            case "getSettings":
                break;
                break;
            default:
            default:
                log("Rejected disallowed API request", msg);
                log("Rejected disallowed API request", msg);
                return;
                return;
        }
        }
    }
    }
    switch (key) {
    switch (key) {
        case "apiRequest":
        case "apiRequest":
            if (!settings.get("webAPI")) {
            if (!settings.get("webAPI")) {
                log("Rejected API request because API is disabled", msg);
                log("Rejected API request because API is disabled", msg);
                sendResponse(null);
                sendResponse(null);
                return;
                return;
            }
            }
            log("Processing API request", msg.request.type);
            log("Processing API request", msg.request.type);
            switch (msg.request.type) {
            switch (msg.request.type) {
                case "getUserIds":
                case "getUserIds":
                    sendResponse(JSON.stringify(Object.keys(users)));
                    sendResponse(JSON.stringify(Object.keys(users)));
                    break;
                    break;
                case "getVersion":
                case "getVersion":
                    sendResponse(chrome.app.getDetails().version);
                    sendResponse(chrome.app.getDetails().version);
                    break;
                    break;
                case "tryJoinRaid":
                case "tryJoinRaid":
                case "getCombatState":
                case "getCombatState":
                    log("Preparing to send api request", msg);
                    log("Preparing to send api request", msg);
                    chrome.tabs.query({}, function (tabs) {
                    chrome.tabs.query({}, function (tabs) {
                        if (tabs.length === 0) {
                        if (tabs.length === 0) {
                            log("Found no granblue tab");
                            log("Found no granblue tab");
                            sendResponse({ type: "result", error: "No granblue tab found" });
                            sendResponse({ type: "result", error: "No granblue tab found" });
                            return;
                            return;
                        }
                        }
                        for (var i = 0, l = tabs.length; i < l; i++) {
                        for (var i = 0, l = tabs.length; i < l; i++) {
                            var tab = tabs[i];
                            var tab = tabs[i];
                            var tabUrl = tab.url;
                            var tabUrl = tab.url;
                            if (!tabUrl ||
                            if (!tabUrl ||
                                ((tabUrl.indexOf("game.granbluefantasy.jp") < 0) &&
                                ((tabUrl.indexOf("game.granbluefantasy.jp") < 0) &&
                                    (tabUrl.indexOf("gbf.game.mbga.jp") < 0))) {
                                    (tabUrl.indexOf("gbf.game.mbga.jp") < 0))) {
                                continue;
                                continue;
                            }
                            }
                            log("Sending api request", msg);
                            log("Sending api request", msg);
                            actuallySendMessage(msg, tab.id, function (result) {
                            actuallySendMessage(msg, tab.id, function (result) {
                                log("Got result for api request");
                                log("Got result for api request");
                                sendResponse(result);
                                sendResponse(result);
                            });
                            });
                            return;
                            return;
                        }
                        }
                        log("Found no granblue tab");
                        log("Found no granblue tab");
                        sendResponse({ type: "result", error: "No granblue tab found" });
                        sendResponse({ type: "result", error: "No granblue tab found" });
                    });
                    });
                    return true;
                    return true;
                default:
                default:
                    sendResponse({ type: "result", error: "Unknown message" });
                    sendResponse({ type: "result", error: "Unknown message" });
                    break;
                    break;
            }
            }
            break;
            break;
        case "getUserIds":
        case "getUserIds":
            sendResponse(JSON.stringify(Object.keys(users)));
            sendResponse(JSON.stringify(Object.keys(users)));
            break;
            break;
        case "setPassword":
        case "setPassword":
            settings.set("password", msg.password);
            settings.set("password", msg.password);
            break;
            break;
        case "pleaseInjectStylesheets":
        case "pleaseInjectStylesheets":
            injectStylesheetsIntoTab(sender);
            injectStylesheetsIntoTab(sender);
            break;
            break;
        case "heartbeat":
        case "heartbeat":
            isDead = false;
            isDead = false;
            break;
            break;
        case "cancelSuspend":
        case "cancelSuspend":
            if (userDict.isSuspended)
            if (userDict.isSuspended)
                log("Canceling idle/maintenance suspend for " + msg.uid);
                log("Canceling idle/maintenance suspend for " + msg.uid);
            userDict.isSuspended = false;
            userDict.isSuspended = false;
            lastFailure = -1;
            lastFailure = -1;
            break;
            break;
        case "isShutdown":
        case "isShutdown":
            sendResponse(isShutdown);
            sendResponse(isShutdown);
            break;
            break;
        case "setCompatibility":
        case "setCompatibility":
            var newState = (msg.state === false);
            var newState = (msg.state === false);
            if (newState !== isShutdown) {
            if (newState !== isShutdown) {
                log("Compatibility shutdown state set to", newState);
                log("Compatibility shutdown state set to", newState);
                isShutdown = newState;
                isShutdown = newState;
            }
            }
            break;
            break;
        case "openNewTab":
        case "openNewTab":
            chrome.tabs.create({
            chrome.tabs.create({
                url: msg.url
                url: msg.url
            });
            });
            break;
            break;
        case "getVersion":
        case "getVersion":
            sendResponse(chrome.app.getDetails().version);
            sendResponse(chrome.app.getDetails().version);
            break;
            break;
        case "setRecentCoOpHost":
        case "setRecentCoOpHost":
            settings.set("recentCoOpHost", msg.data);
            settings.set("recentCoOpHost", msg.data);
            break;
            break;
        case "getRecentCoOpHost":
        case "getRecentCoOpHost":
            sendResponse(settings.get("recentCoOpHost") || null);
            sendResponse(settings.get("recentCoOpHost") || null);
            break;
            break;
        case "setCurrentEvent":
        case "setCurrentEvent":
            if (msg.href !== settings.get("currentEvent"))
            if (msg.href !== settings.get("currentEvent"))
                log("Event changed to '" + msg.href + "'");
                log("Event changed to '" + msg.href + "'");
            settings.set("currentEvent", msg.href);
            settings.set("currentEvent", msg.href);
            break;
            break;
        case "setCurrentGuildWar":
        case "setCurrentGuildWar":
            if (msg.href !== settings.get("currentGuildWar"))
            if (msg.href !== settings.get("currentGuildWar"))
                log("Guild war changed to '" + msg.href + "'");
                log("Guild war changed to '" + msg.href + "'");
            settings.set("currentGuildWar", msg.href);
            settings.set("currentGuildWar", msg.href);
            break;
            break;
        case "getCurrentEvent":
        case "getCurrentEvent":
            sendResponse(settings.get("currentEvent") || null);
            sendResponse(settings.get("currentEvent") || null);
            break;
            break;
        case "getCurrentGuildWar":
        case "getCurrentGuildWar":
            sendResponse(settings.get("currentGuildWar") || null);
            sendResponse(settings.get("currentGuildWar") || null);
            break;
            break;
        case "setRecentQuest":
        case "setRecentQuest":
            settings.set("recentQuest", msg.url);
            settings.set("recentQuest", msg.url);
            break;
            break;
        case "getRecentQuest":
        case "getRecentQuest":
            sendResponse(settings.get("recentQuest") || null);
            sendResponse(settings.get("recentQuest") || null);
            break;
            break;
        case "setLastLocation":
        case "setLastLocation":
            // FIXME: Track per-tab
            // FIXME: Track per-tab
            lastLocation = msg.url;
            lastLocation = msg.url;
            break;
            break;
        case "setIdleRedirectPending":
        case "setIdleRedirectPending":
            // FIXME: Track per-tab
            // FIXME: Track per-tab
            idleRedirectPending = msg.state;
            idleRedirectPending = msg.state;
            if (msg.url)
            if (msg.url)
                lastRedirectTarget = msg.url;
                lastRedirectTarget = msg.url;
            break;
            break;
        case "getLastLocation":
        case "getLastLocation":
            sendResponse(lastLocation || null);
            sendResponse(lastLocation || null);
            break;
            break;
        case "getIdleRedirectInfo":
        case "getIdleRedirectInfo":
            if (idleRedirectPending) {
            if (idleRedirectPending) {
                sendResponse({ pending: true, location: lastLocation, lastRedirectTarget: lastRedirectTarget });
                sendResponse({ pending: true, location: lastLocation, lastRedirectTarget: lastRedirectTarget });
            }
            }
            else {
            else {
                sendResponse({ pending: false });
                sendResponse({ pending: false });
            }
            }
            break;
            break;
        case "getSettings":
        case "getSettings":
            sendResponse(getAdjustedSettings());
            sendResponse(getAdjustedSettings());
            break;
            break;
        case "getRaidCode":
        case "getRaidCode":
            sendResponse(lastRaidCodes[tabId]);
            sendResponse(lastRaidCodes[tabId]);
            break;
            break;
        case "updateRaidCode":
        case "updateRaidCode":
            lastRaidCodes[tabId] = msg.raidCode;
            lastRaidCodes[tabId] = msg.raidCode;
            break;
            break;
        case "getItemCounters":
        case "getItemCounters":
            return maybeDoUpdate(userDict.nextCounterUpdate, minimumUpdateDelay, formatItemCounters, updateItemCounters, sendResponse, msg.force, tabId, msg.uid);
            return maybeDoUpdate(userDict.nextCounterUpdate, minimumUpdateDelay, formatItemCounters, updateItemCounters, sendResponse, msg.force, tabId, msg.uid);
        case "updateItemCounters":
        case "updateItemCounters":
            userDict.nextCounterUpdate = Date.now() + minimumUpdateDelay;
            userDict.nextCounterUpdate = Date.now() + minimumUpdateDelay;
            userDict.itemCounters = msg.counters;
            userDict.itemCounters = msg.counters;
            break;
            break;
        case "invalidateStatus":
        case "invalidateStatus":
            if (userDict.lastStatus)
            if (userDict.lastStatus)
                log("Status invalidated");
                log("Status invalidated");
            userDict.nextStatusUpdate = 0;
            userDict.nextStatusUpdate = 0;
            userDict.lastStatus = null;
            userDict.lastStatus = null;
            break;
            break;
        case "getStatus":
        case "getStatus":
            var getLastStatus = function (uid) {
            var getLastStatus = function (uid) {
                var s = getUserDict(uid).lastStatus;
                var s = getUserDict(uid).lastStatus;
                if (s)
                if (s)
                    fixupStatus(s, uid);
                    fixupStatus(s, uid);
                return s;
                return s;
            };
            };
            if (msg.lazy) {
            if (msg.lazy) {
                var lastStatus = getLastStatus(msg.uid);
                var lastStatus = getLastStatus(msg.uid);
                if (lastStatus) {
                if (lastStatus) {
                    sendResponse(lastStatus);
                    sendResponse(lastStatus);
                    return;
                    return;
                }
                }
                else {
                else {
                    // log("Lazy status update failed");
                    // log("Lazy status update failed");
                }
                }
            }
            }
            return maybeDoUpdate(userDict.nextStatusUpdate, minimumUpdateDelay, getLastStatus, updateStatus, sendResponse, msg.force, tabId, msg.uid);
            return maybeDoUpdate(userDict.nextStatusUpdate, minimumUpdateDelay, getLastStatus, updateStatus, sendResponse, msg.force, tabId, msg.uid);
        case "updateStatus":
        case "updateStatus":
            handleNewStatus(msg.status, msg.uid);
            handleNewStatus(msg.status, msg.uid);
            break;
            break;
        case "invalidateBuffs":
        case "invalidateBuffs":
            userDict.nextGuildBuffUpdate = 0;
            userDict.nextGuildBuffUpdate = 0;
            userDict.nextPersonalBuffUpdate = 0;
            userDict.nextPersonalBuffUpdate = 0;
            userDict.guildBuffs = null;
            userDict.guildBuffs = null;
            userDict.personalBuffs = null;
            userDict.personalBuffs = null;
            log("Buffs invalidated");
            log("Buffs invalidated");
            break;
            break;
        case "updateGuildBuffs":
        case "updateGuildBuffs":
            userDict.lastGuildBuffUpdate = Date.now();
            userDict.lastGuildBuffUpdate = Date.now();
            userDict.nextGuildBuffUpdate = Date.now() + minimumUpdateDelay;
            userDict.nextGuildBuffUpdate = Date.now() + minimumUpdateDelay;
            userDict.guildBuffs = msg.buffs;
            userDict.guildBuffs = msg.buffs;
            break;
            break;
        case "updatePersonalBuffs":
        case "updatePersonalBuffs":
            userDict.lastPersonalBuffUpdate = Date.now();
            userDict.lastPersonalBuffUpdate = Date.now();
            userDict.nextPersonalBuffUpdate = Date.now() + minimumUpdateDelay;
            userDict.nextPersonalBuffUpdate = Date.now() + minimumUpdateDelay;
            userDict.personalBuffs = msg.buffs;
            userDict.personalBuffs = msg.buffs;
            break;
            break;
        case "getNextRankRp":
        case "getNextRankRp":
            // if not force, don't actually update since this is a heavy call
            // if not force, don't actually update since this is a heavy call
            if (!msg.force) {
            if (!msg.force) {
                if (userDict.nextNextRankRpUpdate) {
                if (userDict.nextNextRankRpUpdate) {
                    sendResponse(userDict.nextRankRp);
                    sendResponse(userDict.nextRankRp);
                    break;
                    break;
                }
                }
                sendResponse(null);
                sendResponse(null);
                break;
                break;
            }
            }
            return maybeDoUpdate(userDict.nextNextRankRpUpdate, minimumUpdateDelay, function (uid) { return getUserDict(uid).nextRankRp; }, updateNextRankRp, sendResponse, msg.force, tabId, msg.uid);
            return maybeDoUpdate(userDict.nextNextRankRpUpdate, minimumUpdateDelay, function (uid) { return getUserDict(uid).nextRankRp; }, updateNextRankRp, sendResponse, msg.force, tabId, msg.uid);
        case "updateNextRankRp":
        case "updateNextRankRp":
            userDict.nextNextRankRpUpdate = Date.now() + minimumUpdateDelay;
            userDict.nextNextRankRpUpdate = Date.now() + minimumUpdateDelay;
            userDict.nextRankRp = getRpToNextRank(msg.data);
            userDict.nextRankRp = getRpToNextRank(msg.data);
            break;
            break;
        case "getRaids":
        case "getRaids":
            return maybeDoUpdate(userDict.nextRaidUpdate, minimumRaidUpdateDelay, function (uid) { return getUserDict(uid).lastRaids; }, updateRaids, sendResponse, msg.force, tabId, msg.uid);
            return maybeDoUpdate(userDict.nextRaidUpdate, minimumRaidUpdateDelay, function (uid) { return getUserDict(uid).lastRaids; }, updateRaids, sendResponse, msg.force, tabId, msg.uid);
        case "invalidateRaids":
        case "invalidateRaids":
            if (msg.raids) {
            if (msg.raids) {
                handleNewRaids(msg.raids, msg.uid);
                handleNewRaids(msg.raids, msg.uid);
            }
            }
            else {
            else {
                if (userDict.lastRaids)
                if (userDict.lastRaids)
                    log("Raids invalidated");
                    log("Raids invalidated");
                userDict.nextRaidUpdate = 0;
                userDict.nextRaidUpdate = 0;
                userDict.lastRaids = null;
                userDict.lastRaids = null;
            }
            }
            break;
            break;
        case "getItems":
        case "getItems":
            return maybeDoUpdate(userDict.nextItemUpdate, minimumItemUpdateDelay, function (uid) { return getUserDict(uid).lastItems; }, updateItems, sendResponse, msg.force, tabId, msg.uid);
            return maybeDoUpdate(userDict.nextItemUpdate, minimumItemUpdateDelay, function (uid) { return getUserDict(uid).lastItems; }, updateItems, sendResponse, msg.force, tabId, msg.uid);
        case "invalidateItems":
        case "invalidateItems":
            if (msg.items) {
            if (msg.items) {
                handleNewItems(msg.items, msg.uid);
                handleNewItems(msg.items, msg.uid);
            }
            }
            else {
            else {
                if (userDict.lastItems)
                if (userDict.lastItems)
                    log("Items invalidated");
                    log("Items invalidated");
                userDict.nextItemUpdate = 0;
                userDict.nextItemUpdate = 0;
                userDict.lastItems = null;
                userDict.lastItems = null;
            }
            }
            break;
            break;
        case "getWatchedItems":
        case "getWatchedItems":
            sendResponse(getWatchedItems());
            sendResponse(getWatchedItems());
            break;
            break;
        case "setItemWatchState":
        case "setItemWatchState":
            if (msg.state)
            if (msg.state)
                sendResponse(addWatchedItem(msg.id));
                sendResponse(addWatchedItem(msg.id));
            else
            else
                sendResponse(removeWatchedItem(msg.id));
                sendResponse(removeWatchedItem(msg.id));
            break;
            break;
        case "setItemWatchTarget":
        case "setItemWatchTarget":
            setItemWatchTarget(msg.id, msg.count);
            setItemWatchTarget(msg.id, msg.count);
            break;
            break;
        case "setItemGroup":
            setItemGroup(msg.id, msg.group);
            break;
        case "getFavedSummons":
        case "getFavedSummons":
            sendResponse(getFavedSummons());
            sendResponse(getFavedSummons());
            break;
            break;
        case "setSummonFaveState":
        case "setSummonFaveState":
            if (msg.state)
            if (msg.state)
                sendResponse(addFavedSummon(msg.id));
                sendResponse(addFavedSummon(msg.id));
            else
            else
                sendResponse(removeFavedSummon(msg.id));
                sendResponse(removeFavedSummon(msg.id));
            break;
            break;
        case "setSummonOrder":
        case "setSummonOrder":
            settings.set("summonOrder", msg.data);
            settings.set("summonOrder", msg.data);
            break;
            break;
        case "getIsDead":
        case "getIsDead":
            sendResponse(isDead);
            sendResponse(isDead);
            break;
            break;
        case "doGameAjax":
        case "doGameAjax":
            doGameAjax(msg, tabId, msg.uid, sendResponse);
            doGameAjax(msg, tabId, msg.uid, sendResponse);
            // retain sendResponse
            // retain sendResponse
            return true;
            return true;
        case "doGamePopup":
        case "doGamePopup":
        case "doGameRedirect":
        case "doGameRedirect":
            actuallySendMessage(msg, tabId);
            actuallySendMessage(msg, tabId);
            break;
            break;
        case "getTabId":
        case "getTabId":
            sendResponse(tabId);
            sendResponse(tabId);
            break;
            break;
        case "getUserIdAndTabId":
        case "getUserIdAndTabId":
            msg.tabId = tabId;
            msg.tabId = tabId;
            actuallySendMessage(msg, tabId, sendResponse);
            actuallySendMessage(msg, tabId, sendResponse);
            return true;
            return true;
        case "getIsSuspended":
        case "getIsSuspended":
            sendResponse(!!userDict.isSuspended);
            sendResponse(!!userDict.isSuspended);
            return true;
            return true;
        case "recordRewards":
        case "recordRewards":
            handleQuestRewards(msg);
            handleQuestRewards(msg);
            break;
            break;
        case "recordRaidInfo":
        case "recordRaidInfo":
            handleRaidInfo(msg);
            handleRaidInfo(msg);
            break;
            break;
        case "registerSecretKey":
        case "registerSecretKey":
            secretKeysByTabId[tabId] = msg.key;
            secretKeysByTabId[tabId] = msg.key;
            break;
            break;
        case "actionStarted":
        case "actionStarted":
            userDict.lastActionStartedWhen = msg.when;
            userDict.lastActionStartedWhen = msg.when;
            userDict.lastActionId = msg.actionId;
            userDict.lastActionId = msg.actionId;
            broadcastActionTimestamps(msg.uid, userDict);
            broadcastActionTimestamps(msg.uid, userDict);
            break;
            break;
        case "actionEnded":
        case "actionEnded":
            if (msg.succeeded) {
            if (msg.succeeded) {
                userDict.lastSuccessfulActionStartedWhen =
                userDict.lastSuccessfulActionStartedWhen =
                    userDict.lastActionStartedWhen;
                    userDict.lastActionStartedWhen;
            }
            }
            if (userDict.lastActionId === msg.actionId) {
            if (userDict.lastActionId === msg.actionId) {
                if (msg.succeeded) {
                if (msg.succeeded) {
                    userDict.lastSuccessfulActionId =
                    userDict.lastSuccessfulActionId =
                        userDict.lastActionId;
                        userDict.lastActionId;
                }
                }
                else {
                else {
                    userDict.lastActionId = null;
                    userDict.lastActionId = null;
                }
                }
            }
            }
            userDict.lastActionEndedWhen = msg.when;
            userDict.lastActionEndedWhen = msg.when;
            broadcastActionTimestamps(msg.uid, userDict);
            broadcastActionTimestamps(msg.uid, userDict);
            break;
            break;
        case "actionCompletedAnimation":
        case "actionCompletedAnimation":
            // When an action's animation is complete, we disable
            // When an action's animation is complete, we disable
            //  the timer since the lockout is definitely over
            //  the timer since the lockout is definitely over
            if (msg.actionId === userDict.lastActionId) {
            if (msg.actionId === userDict.lastActionId) {
                userDict.lastActionStartedWhen =
                userDict.lastActionStartedWhen =
                    userDict.lastActionId = null;
                    userDict.lastActionId = null;
                broadcastActionTimestamps(msg.uid, userDict);
                broadcastActionTimestamps(msg.uid, userDict);
            }
            }
            break;
            break;
        case "getLastActionTimestamps":
        case "getLastActionTimestamps":
            sendResponse(makeActionTimestamps(userDict));
            sendResponse(makeActionTimestamps(userDict));
            break;
            break;
        default:
        default:
            log("Unknown message " + key);
            log("Unknown message " + key);
            sendResponse({ error: true });
            sendResponse({ error: true });
            break;
            break;
    }
    }
}
}
;
;
function broadcastActionTimestamps(uid, userDict) {
function broadcastActionTimestamps(uid, userDict) {
    var obj = makeActionTimestamps(userDict);
    var obj = makeActionTimestamps(userDict);
    var msg = {
    var msg = {
        type: "actionTimestampsChanged",
        type: "actionTimestampsChanged",
        data: obj,
        data: obj,
        uid: uid
        uid: uid
    };
    };
    chrome.tabs.query(
    chrome.tabs.query(
    // FIXME: Can we narrow this to granblue tabs without the 'tabs' permission?
    // FIXME: Can we narrow this to granblue tabs without the 'tabs' permission?
    {}, function (tabs) {
    {}, function (tabs) {
        if (!tabs)
        if (!tabs)
            return;
            return;
        for (var i = 0; i < tabs.length; i++) {
        for (var i = 0; i < tabs.length; i++) {
            var tab = tabs[i];
            var tab = tabs[i];
            chrome.tabs.sendMessage(tab.id, msg);
            chrome.tabs.sendMessage(tab.id, msg);
            if (chrome.runtime.lastError)
            if (chrome.runtime.lastError)
                log(chrome.runtime.lastError);
                log(chrome.runtime.lastError);
        }
        }
    });
    });
}
}
;
;
function makeActionTimestamps(userDict) {
function makeActionTimestamps(userDict) {
    return {
    return {
        actionId: userDict.lastActionId,
        actionId: userDict.lastActionId,
        successfulActionId: userDict.lastSuccessfulActionId,
        successfulActionId: userDict.lastSuccessfulActionId,
        started: userDict.lastActionStartedWhen,
        started: userDict.lastActionStartedWhen,
        successfulStarted: userDict.lastSuccessfulActionStartedWhen,
        successfulStarted: userDict.lastSuccessfulActionStartedWhen,
        ended: userDict.lastActionEndedWhen
        ended: userDict.lastActionEndedWhen
    };
    };
}
}
;
;
function handleQuestRewards(msg) {
function handleQuestRewards(msg) {
    var userDict = getUserDict(msg.uid);
    var userDict = getUserDict(msg.uid);
    if (!userDict.raids)
    if (!userDict.raids)
        return;
        return;
    var urlFragment = msg.url.substr(msg.url.lastIndexOf("/") + 1);
    var urlFragment = msg.url.substr(msg.url.lastIndexOf("/") + 1);
    urlFragment = urlFragment.substr(0, urlFragment.indexOf("?"));
    urlFragment = urlFragment.substr(0, urlFragment.indexOf("?"));
    var raidId = parseInt(urlFragment);
    var raidId = parseInt(urlFragment);
    var questId = userDict.raids[raidId];
    var questId = userDict.raids[raidId];
}
}
;
;
function handleRaidInfo(msg) {
function handleRaidInfo(msg) {
    var userDict = getUserDict(msg.uid);
    var userDict = getUserDict(msg.uid);
    if (!userDict.raids)
    if (!userDict.raids)
        userDict.raids = {};
        userDict.raids = {};
    userDict.raids[msg.raidId] = msg.questId;
    userDict.raids[msg.raidId] = msg.questId;
}
}
;
;
function parseTimeInMinutes(text) {
function parseTimeInMinutes(text) {
    var parts = text.split(/[\D]+/);
    var parts = text.split(/[\D]+/);
    var result = 0;
    var result = 0;
    for (var i = 0, len = Math.min(parts.length, 2); i < len; i++) {
    for (var i = 0, len = Math.min(parts.length, 2); i < len; i++) {
        if (parts[i].length === 0) {
        if (parts[i].length === 0) {
            break;
            break;
        }
        }
        result = result * 60 + parseInt(parts[i]);
        result = result * 60 + parseInt(parts[i]);
    }
    }
    return result;
    return result;
}
}
;
;
function estimateValue(truncated, maximum, timeRemaining, elapsedTimeMs, minutesPerUnit) {
function estimateValue(truncated, maximum, timeRemaining, elapsedTimeMs, minutesPerUnit) {
    if (truncated >= maximum)
    if (truncated >= maximum)
        return truncated;
        return truncated;
    // HACK: Add 59 seconds to the remaining time since they round down the number of minutes
    // HACK: Add 59 seconds to the remaining time since they round down the number of minutes
    timeRemaining += 59 / 60;
    timeRemaining += 59 / 60;
    var durationFromFull = maximum * minutesPerUnit;
    var durationFromFull = maximum * minutesPerUnit;
    timeRemaining = Math.max(0, timeRemaining - (elapsedTimeMs / 60000));
    timeRemaining = Math.max(0, timeRemaining - (elapsedTimeMs / 60000));
    var fract = (timeRemaining / durationFromFull);
    var fract = (timeRemaining / durationFromFull);
    fract = Math.max(0.0, Math.min(1.0, fract));
    fract = Math.max(0.0, Math.min(1.0, fract));
    var estimatedValue = maximum * (1.0 - fract);
    var estimatedValue = maximum * (1.0 - fract);
    return estimatedValue;
    return estimatedValue;
}
}
;
;
function fixupStatus(status, uid) {
function fixupStatus(status, uid) {
    if (!status)
    if (!status)
        return status;
        return status;
    var userDict = getUserDict(uid);
    var userDict = getUserDict(uid);
    // FIXME
    // FIXME
    var lastUpdate = userDict.lastStatusUpdate;
    var lastUpdate = userDict.lastStatusUpdate;
    status._lastUpdate = lastUpdate;
    status._lastUpdate = lastUpdate;
    status._now = Date.now();
    status._now = Date.now();
    var age = (status._now - status._lastUpdate);
    var age = (status._now - status._lastUpdate);
    status._precise_ap = estimateValue(status.ap, parseInt(status.max_action_point), parseTimeInMinutes(status.action_point_remain), age, 5);
    status._precise_ap = estimateValue(status.ap, parseInt(status.max_action_point), parseTimeInMinutes(status.action_point_remain), age, 5);
    status._precise_bp = estimateValue(status.bp, parseInt(status.max_battle_point), parseTimeInMinutes(status.battle_point_remain), age, 10);
    status._precise_bp = estimateValue(status.bp, parseInt(status.max_battle_point), parseTimeInMinutes(status.battle_point_remain), age, 10);
    status.buffs = [];
    status.buffs = [];
    if (userDict.guildBuffs) {
    if (userDict.guildBuffs) {
        // FIXME
        // FIXME
        age = (status._now - userDict.lastGuildBuffUpdate);
        age = (status._now - userDict.lastGuildBuffUpdate);
        for (var i = 0, l = userDict.guildBuffs.length; i < l; i++) {
        for (var i = 0, l = userDict.guildBuffs.length; i < l; i++) {
            var gb = userDict.guildBuffs[i];
            var gb = userDict.guildBuffs[i];
            var timeRemaining = (parseTimeInMinutes(gb.time) * 60 * 1000) - age;
            var timeRemaining = (parseTimeInMinutes(gb.time) * 60 * 1000) - age;
            if (timeRemaining <= 0)
            if (timeRemaining <= 0)
                continue;
                continue;
            status.buffs.push({
            status.buffs.push({
                comment: gb.comment,
                comment: gb.comment,
                timeRemaining: timeRemaining,
                timeRemaining: timeRemaining,
                imageUrl: "http://game-a.granbluefantasy.jp/assets_en/img/sp/assets/item/support/support_" +
                imageUrl: "http://game-a.granbluefantasy.jp/assets_en/img/sp/assets/item/support/support_" +
                    gb.image + "_" + gb.level + ".png"
                    gb.image + "_" + gb.level + ".png"
            });
            });
        }
        }
    }
    }
    if (userDict.personalBuffs) {
    if (userDict.personalBuffs) {
        // FIXME
        // FIXME
        age = (status._now - userDict.lastPersonalBuffUpdate);
        age = (status._now - userDict.lastPersonalBuffUpdate);
        for (var k in userDict.personalBuffs) {
        for (var k in userDict.personalBuffs) {
            if (!userDict.personalBuffs.hasOwnProperty(k))
            if (!userDict.personalBuffs.hasOwnProperty(k))
                continue;
                continue;
            var pb = userDict.personalBuffs[k];
            var pb = userDict.personalBuffs[k];
            var timeRemaining = (parseTimeInMinutes(pb.remain_time) * 60 * 1000) - age;
            var timeRemaining = (parseTimeInMinutes(pb.remain_time) * 60 * 1000) - age;
            if (timeRemaining <= 0)
            if (timeRemaining <
                continue;
            status.buffs.push({
                comment: pb.name,
                timeRemaining: timeRemaining,
                imageUrl: "http://game-a.granbluefantasy.jp/assets_en/img/sp/assets/item/support/" +
                    pb.image_path + "_" + pb.level + ".png"
            });
        }
    }
    return status;
}
;
function handleNewStatus(status, uid) {
    if (!status) {
        log("Failed status update");
        return;
    }
    getUserDict(uid).lastStatus = status;
    getUserDict(uid).lastStatusUpdate = Date.now();
    getUserDict(uid).nextStatusUpdate = Date.now() + minimumUpdateDelay;
    var minutesUntilApRefill = parseTimeInMinutes(status.action_poin