Greasy Fork

MouseHunt Custom Themes

Change your hunter's journal to use custom themes (only works locally)

// ==UserScript==
// @name         MouseHunt Custom Themes
// @author       LethalVision
// @version      1.2.3
// @description  Change your hunter's journal to use custom themes (only works locally)
// @include	http://mousehuntgame.com/*
// @include	https://mousehuntgame.com/*
// @include	http://www.mousehuntgame.com/*
// @include	https://www.mousehuntgame.com/*
// @grant        none
// @namespace    https://greasyfork.org/en/users/683695-lethalvision
// ==/UserScript==

const TAG = "CUST_THEME"
var topUrl = "";
var midUrl = "";
var botUrl = "";
// true = tile mid image, false = stretch
const tileMid = true;

const defaultThemes = {
    "None":{description:"None", links:[]},
    "VR":{description:"Valour Rift by wayew",
         links:["https://cdn.discordapp.com/attachments/487964004162207745/751107541764800672/non_UU_theme.png",
               "https://cdn.discordapp.com/attachments/487964004162207745/748804207171207238/middle_non_UU.png",
               "https://cdn.discordapp.com/attachments/487964004162207745/748804197306073149/bottom_non_UU.png"]},
    "VR_A":{description:"Valour Rift (animated) by wayew & Wellker",
         links:["https://cdn.discordapp.com/attachments/487964004162207745/748810922642112583/lightning_non_UU_gif_cut.gif",
               "https://cdn.discordapp.com/attachments/487964004162207745/748804207171207238/middle_non_UU.png",
               "https://cdn.discordapp.com/attachments/487964004162207745/748804197306073149/bottom_non_UU.png"]},
    "UU":{description:"Ultimate Umbra by wayew",
          links:["https://cdn.discordapp.com/attachments/487964004162207745/748528376750407750/top.png",
                 "https://cdn.discordapp.com/attachments/487964004162207745/756163238768410644/UU_theme_mid.png",
                 "https://cdn.discordapp.com/attachments/487964004162207745/748569283591798864/bottom.png"]},
    "UU_A":{description:"Ultimate Umbra (animated) by wayew & Wellker",
            links:["https://cdn.discordapp.com/attachments/487964004162207745/748572650237853736/UU_theme_top_gif.gif",
                   "https://cdn.discordapp.com/attachments/487964004162207745/756163238768410644/UU_theme_mid.png",
                   "https://cdn.discordapp.com/attachments/487964004162207745/748569283591798864/bottom.png"]},
    "CT_A":{description:"Crystal Theme (animated) by Wellker",
            links:["https://cdn.discordapp.com/attachments/487964004162207745/749338680295948428/Crystal_Theme_Top.gif",
                   "https://cdn.discordapp.com/attachments/487964004162207745/756164045291389029/Crystal_theme_middle.png",
                   "https://cdn.discordapp.com/attachments/487964004162207745/749332702188142693/Crystal_theme_bot.png"]},
    "SH":{description:"Shrine Theme by Wellker",
            links:["https://cdn.discordapp.com/attachments/487964004162207745/898641949676957706/Sacred_shrine_top.png",
                   "https://cdn.discordapp.com/attachments/487964004162207745/898642019956711434/Shrine_mid.png",
                   "https://cdn.discordapp.com/attachments/487964004162207745/898642043239272580/shrine_bottom.png"]},
    "SH_A":{description:"Shrine Theme (animated) by Wellker",
            links:["https://cdn.discordapp.com/attachments/487964004162207745/898641930269892638/Sacred_Shrine_top_Animated.gif",
                   "https://cdn.discordapp.com/attachments/487964004162207745/898642019956711434/Shrine_mid.png",
                   "https://cdn.discordapp.com/attachments/487964004162207745/898642043239272580/shrine_bottom.png"]},
    "Custom":{description:"Custom", links:[]}
};

const imgSizes = {
    top:{width:0, height:0, loaded:false},
    mid:{width:0, height:0, loaded:false},
    bot:{width:0, height:0, loaded:false}
};

var preferences = {
    selection: "None",
    topUrl: "",
    midUrl: "",
    botUrl: ""
};

function init() {
    var loadedPref = window.localStorage.getItem(TAG);
    if (loadedPref){
        try{
            loadedPref = JSON.parse(loadedPref);
            console.log('Loaded preference: ' + loadedPref.selection);
            preferences.selection = loadedPref.selection;
            preferences.topUrl = loadedPref.topUrl;
            preferences.midUrl = loadedPref.midUrl;
            preferences.botUrl = loadedPref.botUrl;
        } catch (err) {
            console.log('Preference parse error: ' + err);
        }
    }
    addListener();
    loadUrls(preferences.selection);
}

function loadUrls(selection) {
    if (selection == "Custom"){
        topUrl = preferences.topUrl;
        midUrl = preferences.midUrl;
        botUrl = preferences.botUrl;
        loadImageSize("top", topUrl);
        loadImageSize("mid", midUrl);
        loadImageSize("bot", botUrl);
    } else if (defaultThemes.hasOwnProperty(selection) && defaultThemes[selection].links.length == 3){
        topUrl = defaultThemes[selection].links[0];
        midUrl = defaultThemes[selection].links[1];
        botUrl = defaultThemes[selection].links[2];
        loadImageSize("top", topUrl);
        loadImageSize("mid", midUrl);
        loadImageSize("bot", botUrl);
    } else {
        topUrl = "";
        midUrl = "";
        botUrl = "";
        // no need to load theme
    }
}

function addListener() {
    const originalOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function() {
        this.addEventListener("load", function() {
            if ( this.responseURL === "https://www.mousehuntgame.com/managers/ajax/pages/page.php") {
                updateTheme();
                updateSelector();
            } else if (this.responseURL === "https://www.mousehuntgame.com/managers/ajax/users/journal_theme.php") {
                updateSelector();
                updateCheckboxes();
                // add interface on first load
                addInterface(true);
            }
        });
        originalOpen.apply(this, arguments);
    };
}

function loadImageSize(portion, url){
    if (url == null || url.length == 0){
        return;
    }
    var img = new Image();
    img.onload = function(){
        setSize(portion, this.width, this.height);
    };
    img.src = url;
}

function setSize(portion, width, height){
    console.log(`${portion}: ${width}, ${height}`);
    switch(portion){
        case "top":
            imgSizes.top.width = width;
            imgSizes.top.height = height;
            imgSizes.top.loaded = true;
            break;
        case "mid":
            imgSizes.mid.width = width;
            imgSizes.mid.height = height;
            imgSizes.mid.loaded = true;
            break;
        case "bot":
            imgSizes.bot.width = width;
            imgSizes.bot.height = height;
            imgSizes.bot.loaded = true;
            break;
        default:
            console.log(`setSize unrecognized portion: ${portion}`);
            return;
    }
    if (imgSizes.top.loaded && imgSizes.mid.loaded && imgSizes.bot.loaded) {
        // all images loaded successfully, apply to theme
        updateTheme();
    }
}

function updateSelector(){
    var updateLink = function(link){
        if (link.getAttribute('attached')){
            // check if the link is already tagged
            return;
        }
        link.addEventListener("click", function(){
            updateCheckboxes();
            addInterface(true);
        });
        // tag the link
        link.setAttribute('attached', true);
    };
     // append new event to all links that bring up the theme selector
    [].forEach.call(document.getElementsByClassName('campPage-tabs-journal-theme'), updateLink);
    [].forEach.call(document.getElementsByClassName('journalContainer-selectTheme'), updateLink);
}

function updateCheckboxes() {
    // get those pesky checkboxes
    // this reapplies the onclick modification on the checkboxes after a short delay as the entire theme selector seems to be recreated upon clicking the checkboxes
    var checkboxes = document.getElementsByClassName('journalThemeSelectorView-filter');
    for (var i = 0; i < checkboxes.length; i++) {
        checkboxes[i].getElementsByTagName("input")[0].onclick = function(){
            setTimeout(function(){
                updateCheckboxes();
                addInterface(false);
            }, 50);
        };
    }
}

function addInterface(forceTab){
    // make sure the theme selector exists
    var parent = document.getElementsByClassName('journalThemeSelectorView-tagContainer');
    if (parent.length == 0){
        return;
    } else {
        parent = parent[0];
    }

    // force tab to "General" if there is an active custom theme
    if (forceTab && preferences.selection != "None"){
        var tabs = document.getElementsByClassName('mousehuntTabHeader');
        for (var i = 0; i < tabs.length; i++) {
            // find and click the "General" tab
            if (tabs[i].getAttribute("data-tab") == "misc") {
                tabs[i].onclick();
            }
        }
    }

    if (document.getElementById('customThemeMain')){
        // if the mainDiv already exists, exit
        return;
    }

    const mainDiv = document.createElement('div');
    mainDiv.id = "customThemeMain";

    // setup title
    var titleDiv = document.createElement('div');
    titleDiv.innerHTML = "<b>Custom Themes</b>";
    titleDiv.style.height = "17px";

    // a horizontal line. Was this really necessary? Nope.
    // it's here anyway so y'all better appreciate it
    var lineDiv = document.createElement('div');
    lineDiv.style.height = "1px";
    lineDiv.style.backgroundColor = "black";

    var descDiv = document.createElement('div');
    descDiv.innerHTML = "Apply custom themes to your Hunter's Journal. Note that these changes are local - only you can see this!";
    descDiv.style.height = "34px";
    descDiv.style.marginTop = "3px";
    descDiv.style.marginRight = "3px";

    mainDiv.appendChild(titleDiv);
    mainDiv.appendChild(lineDiv);
    mainDiv.appendChild(descDiv);

    // setup selector
    var themeDiv = document.createElement('div');
    themeDiv.style.height = "24px";
    var themeSelect = document.createElement('select');
    themeSelect.id = "selectTheme";
    themeSelect.style.width = "75%"
    // iterate through defaultThemes to add options to select
    for (var key in defaultThemes) {
        if (defaultThemes.hasOwnProperty(key)) {
            themeSelect.options[themeSelect.options.length] = new Option(defaultThemes[key].description, key);
        }
    }
    // set current option to selected theme
    themeSelect.value = preferences.selection;
    themeSelect.onchange = function() {
        var customDiv = document.getElementById('customDiv')
        if (document.getElementById('selectTheme').value == "Custom") {
            customDiv.style.display = "block";
        } else {
            customDiv.style.display = "none";
        }
    }
    themeDiv.appendChild(themeSelect);
    mainDiv.appendChild(themeDiv);

    // setup input UI for Custom themes
    var customDiv = document.createElement('div');
    customDiv.id = "customDiv";
    customDiv.style.marginTop = "10px";
    customDiv.style.marginBottom = "10px";

    var detailDiv = document.createElement('div');
    detailDiv.style.height = "34px";
    detailDiv.innerHTML = "Set images for your custom theme here: images must be provided as URLs, <i>local images will not work.</i>";
    customDiv.appendChild(detailDiv);

    var warningDiv = document.createElement('div');
    warningDiv.style.height = "34px";
    warningDiv.innerHTML = "<b>Please check that the URLs link to images before loading them!</b>".fontcolor("red");
    customDiv.appendChild(warningDiv);

    var customTable = document.createElement('table');
    customTable.style.width = "100%";
    descDiv.style.marginRight = "3px";
    customDiv.appendChild(customTable);

    var topRow = customTable.insertRow();
    topRow.style.height = "24px";
    var topDesc = topRow.insertCell(0);
    topDesc.appendChild(document.createTextNode("Top"));
    // set width once and the rest should follow
    topDesc.style.width = "20%"
    var topUrlInput = document.createElement('input');
    topUrlInput.id = "topInput";
    topUrlInput.style.width = "95%";
    topUrlInput.value = preferences.topUrl;
    topRow.insertCell(1).appendChild(topUrlInput);

    var midRow = customTable.insertRow();
    midRow.style.height = "24px";
    midRow.insertCell(0).appendChild(document.createTextNode("Middle"));
    var midUrlInput = document.createElement('input');
    midUrlInput.id = "midInput";
    midUrlInput.style.width = "95%";
    midUrlInput.value = preferences.midUrl;
    midRow.insertCell(1).appendChild(midUrlInput);

    var botRow = customTable.insertRow();
    botRow.style.height = "24px";
    botRow.insertCell(0).appendChild(document.createTextNode("Bottom"));
    var botUrlInput = document.createElement('input');
    botUrlInput.id = "botInput";
    botUrlInput.style.width = "95%";
    botUrlInput.value = preferences.botUrl;
    botRow.insertCell(1).appendChild(botUrlInput);

    mainDiv.appendChild(customDiv);

    // setup buttons
    var buttonDiv = document.createElement("div");
    buttonDiv.style.marginTop = "5px";

    var loadButton = document.createElement("button");
    loadButton.textContent = "Load Theme";
    loadButton.onclick = savePreferences;
    buttonDiv.appendChild(loadButton);

    var clearButton = document.createElement("button");
    clearButton.textContent = "Clear Preferences";
    clearButton.style.marginLeft = "5px";
    clearButton.onclick = clearPreferences;
    buttonDiv.appendChild(clearButton);

    mainDiv.appendChild(buttonDiv);

    // add the whole UI to theme dialog
    parent.appendChild(mainDiv);

    // trigger onchange on themeSelect to set the display attribute correctly on first load
    themeSelect.onchange();
}

function savePreferences() {
    var selectorElem = document.getElementById('selectTheme');
    var selectValue = selectorElem.value;
    // store preference
    preferences.selection = selectValue;
    if (selectValue == "Custom") {
        preferences.topUrl = document.getElementById('topInput').value;
        preferences.midUrl = document.getElementById('midInput').value;
        preferences.botUrl = document.getElementById('botInput').value;
    }
    var jsonString = JSON.stringify(preferences);
    window.localStorage.setItem(TAG, jsonString);
    window.location.reload(false);
}

function clearPreferences() {
    localStorage.removeItem(TAG);
    window.location.reload(false);
}

function updateJournal(journalElem, url, imgSize) {
    // calculate proper height
    var elemWidth = journalElem.clientWidth;
    var elemHeight = journalElem.clientHeight;
    if (elemWidth == 0 || elemHeight == 0){
        return;
    }
    var newHeight = parseInt(imgSize.height * ((elemWidth*1.0)/ imgSize.width));
    //console.log(`newHeight ${journalElem.className}: ${newHeight}px`);

    journalElem.style.height = `${newHeight}px`;
    journalElem.style.backgroundImage = `url('${url}')`;
    // use automatic height
    journalElem.style.backgroundSize = "100% auto";
}

function updateTheme() {
    if (imgSizes.top.width == 0 || imgSizes.top.height == 0 ||
        imgSizes.mid.width == 0 || imgSizes.mid.height == 0 ||
        imgSizes.bot.width == 0 || imgSizes.bot.height == 0){
        // skip update if image load failed for any of the images
        return;
    }
    // "Set Journal Theme" container on camp page
    var campJournalLinks = document.getElementsByClassName("journal-detailLinkContainer");
    // "Set Journal Theme" container on own profile page (it looks broken as of MouseHunt v3.2-86955, will probably be fixed soon - and break this script again.)
    var profileJournalLinks = document.getElementsByClassName("campPage-tabs-journal-link-container");
    if (campJournalLinks.length == 0 && profileJournalLinks.length == 0 && !window.location.href.includes("journal.php")) {
        // proceed if "Set Journal Theme" is found or on "journal.php", skip update otherwise
        // i.e. don't update theme on other people's profiles
        return;
    }

    var themeTop = document.getElementById('journalContainer').children[0];
    var themeMid = document.getElementById('journalContainer').children[1];
    var themeBot = document.getElementById('journalContainer').children[2];

    updateJournal(themeTop, topUrl, imgSizes.top);
    updateJournal(themeBot, botUrl, imgSizes.bot);
    // manually update mid as it needs to be handled differently
    themeMid.style.backgroundColor = "#F2F2F2";
    themeMid.style.backgroundImage = `url('${midUrl}')`;
    if (tileMid){
        themeMid.style.backgroundSize = "100% auto"
    } else {
        themeMid.style.backgroundSize = "100% 100%";
    }
}

function addEvent(element, eventName, fn) {
    if (element.addEventListener){
        element.addEventListener(eventName, fn, false);
    }
    else if (element.attachEvent){
        element.attachEvent('on' + eventName, fn);
    }
}

// init script on load, use addEvent to preserve onLoad events
//addEvent(window, 'load', function(){ init() });
init();