Greasy Fork

MouseHunt HUD Backgroundifier

Change the background of (almost) everything on mousehuntgame.com (But why? What possessed you to seek this out?)

// ==UserScript==
// @name         MouseHunt HUD Backgroundifier
// @author       LethalVision
// @version      0.4.1
// @description  Change the background of (almost) everything on mousehuntgame.com (But why? What possessed you to seek this out?)
// @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==

// localStorage tag - changing this will break saved preferences!
const TAG = "HUD_BCKGRNDFR";

// Dark mode palette
// TODO: make these customizable?
const DARK_BG_COLOR = "#333333";
const DARK_HL_COLOR = "#494949";
const DARK_TEXT_COLOR = "#EEEEEE";
const DARK_LINK_COLOR = "#99B9FF";

var preferences = {
    hudUrl: "",
    backUrl: "",
    leftUrl: "",
    rightUrl: "",
    statOpacity: 1,
    hudOpacity: 1,
    statShadow: false,
    darkMode: false
};

function init() {
    var loadedPref = window.localStorage.getItem(TAG);
    if (loadedPref){
        try{
            loadedPref = JSON.parse(loadedPref);
            preferences.hudUrl = loadedPref.hudUrl;
            preferences.backUrl = loadedPref.backUrl;
            preferences.leftUrl = loadedPref.leftUrl;
            preferences.rightUrl = loadedPref.rightUrl;
            preferences.statOpacity = loadedPref.statOpacity;
            preferences.hudOpacity = loadedPref.hudOpacity;
            preferences.statShadow = loadedPref.statShadow;
            preferences.darkMode = loadedPref.darkMode;
            console.log("Backgroundifier preferences loaded");
        } catch (err) {
            console.log('Preference parse error: ' + err);
        }
    }
    addListener();
    addButton();
    updateAll();
    setTimeout(function(){
        fixFbBanner();
    }, 600);
}

// stuff preferences into the localStorage
function savePreferences() {
    var jsonString = JSON.stringify(preferences);
    window.localStorage.setItem(TAG, jsonString);
}

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" ||
               this.responseURL === "https://www.mousehuntgame.com/managers/ajax/pages/preferences.php") {
                addButton();
                updateAll();
            }
        });
        originalOpen.apply(this, arguments);
    };
}

// === SMOKE AND MIRRORS HERE ===
// add button to bring up the settings
function addButton() {
    if (document.getElementById("hudBtn")) {
        // if button already exists
        return;
    }
    const div = document.createElement("div");
    div.className = "hudBtn-container";
    div.style.position = "absolute";
    div.style.bottom = "5px";
    div.style.right = "7px"

    const hudBtn = document.createElement("button");
    hudBtn.id = "hudBtn";
    hudBtn.textContent = "!";
    hudBtn.style.width = "20px"
    hudBtn.style.height = "20px"
    hudBtn.style.opacity = "0.5"
    hudBtn.onclick = function(){renderBox();}

    div.appendChild(hudBtn);
    const hudContainer = document.getElementsByClassName("headsUpDisplayView");
    if (!hudContainer || hudContainer.length != 1) {
        // hudContainer can't be found or the length is unexpected
        return;
    }
    hudContainer[0].insertAdjacentElement("afterend",div);
}

// render the almighty Tsitu's Floating Box
function renderBox(){return new Promise((resolve, reject) => {
    // clear all open boxes
    document
     .querySelectorAll("#hud-options")
     .forEach(el=> el.remove())

    const div = document.createElement("div");
    div.id = "hud-options";
    div.style.backgroundColor = "#F5F5F5";
    div.style.position = "fixed";
    div.style.zIndex = "9999";

    div.style.left = "35vw";
    div.style.top = "55vh";

    div.style.border = "solid 3px #696969";
    div.style.borderRadius = "20px";
    div.style.padding = "10px";
    div.style.textAlign = "left";
    div.style.minWidth = "207px";

    const buttonDiv = document.createElement("div");
    buttonDiv.style.float= "right";
    buttonDiv.style.textAlign = "right";
    const closeButton = document.createElement("button", {
        id: "close-button"
    });
    closeButton.textContent = "x";
    closeButton.onclick = function () {
    document.body.removeChild(div);
    };
    closeButton.style.marginRight = "5px"

    const titleDiv = document.createElement("div")
    titleDiv.id = "hud-optionsheader";
    titleDiv.textContent = "HUD Backgroundifier";
    titleDiv.style.width = "75%";
    titleDiv.style.float= "left";
    titleDiv.style.textAlign = "left";
    titleDiv.style.fontSize = "12px"
    titleDiv.style.fontWeight = "bold";
    titleDiv.style.marginLeft = "5px";

    const urlDiv = document.createElement("div")
    urlDiv.className = "clear"
    urlDiv.textContent = "Image URLs";
    urlDiv.style.fontWeight = "bold";
    urlDiv.style.textAlign = "left";
    urlDiv.style.marginTop = "5px";
    urlDiv.style.marginLeft = "5px";

    var urlTable = document.createElement('table');
    urlTable.style.textAlign = "left";
    urlTable.style.borderSpacing = "1em 0";
    urlTable.style.paddingTop = "5px"

    var topRow = urlTable.insertRow();
    topRow.style.height = "24px";
    var topDesc = topRow.insertCell(0);
    topDesc.appendChild(document.createTextNode("HUD background:"));
    // set width once and the rest should follow
    topDesc.style.width = "35%"
    var urlInput = document.createElement('input');
    urlInput.id = "urlInput";
    urlInput.style.width = "95%";
    urlInput.value = preferences.hudUrl;
    urlInput.onchange = function(){
        checkAndLoadImage("hud", urlInput.value);
    }
    topRow.insertCell(1).appendChild(urlInput);

    var backRow = urlTable.insertRow();
    backRow.style.height = "24px";
    var backDesc = backRow.insertCell(0);
    backDesc.appendChild(document.createTextNode("Camp background:"));
    var backInput = document.createElement('input');
    backInput.id = "backInput";
    backInput.style.width = "95%";
    backInput.value = preferences.backUrl;
    backInput.onchange = function(){
        checkAndLoadImage("back", backInput.value);
    }
    backRow.insertCell(1).appendChild(backInput);

    var leftRow = urlTable.insertRow();
    leftRow.style.height = "24px";
    var leftDesc = leftRow.insertCell(0);
    leftDesc.appendChild(document.createTextNode("Left screen background:"));
    var leftInput = document.createElement('input');
    leftInput.id = "leftInput";
    leftInput.style.width = "95%";
    leftInput.value = preferences.leftUrl;
    leftInput.onchange = function(){
        checkAndLoadImage("left", leftInput.value);
    }
    leftRow.insertCell(1).appendChild(leftInput);

    var rightRow = urlTable.insertRow();
    rightRow.style.height = "24px";
    var rightDesc = rightRow.insertCell(0);
    rightDesc.appendChild(document.createTextNode("Right screen background:"));
    var rightInput = document.createElement('input');
    rightInput.id = "rightInput";
    rightInput.style.width = "95%";
    rightInput.value = preferences.rightUrl;
    rightInput.onchange = function(){
        checkAndLoadImage("right", rightInput.value);
    }
    rightRow.insertCell(1).appendChild(rightInput);

    const opDiv = document.createElement("div")
    opDiv.textContent = "Transparency Settings";
    opDiv.style.fontWeight = "bold";
    opDiv.style.textAlign = "left";
    opDiv.style.marginTop = "5px";
    opDiv.style.marginLeft = "5px";

    var opTable = document.createElement('table');
    opTable.style.textAlign = "left";
    opTable.style.borderSpacing = "1em 0";
    opTable.style.paddingTop = "5px"

    var statOpRow = opTable.insertRow();
    statOpRow.style.height = "24px";
    var statDesc = statOpRow.insertCell(0);
    statDesc.appendChild(document.createTextNode("Hunter Stats:"));
    // set width once and the rest should follow
    statDesc.style.width = "20%"
    var statSlider = document.createElement('input');
    statSlider.id = "statSlider";
    statSlider.type = "range"
    statSlider.style.width = "95%";
    statSlider.style.verticalAlign = "middle";
    // this is where I should use some fancy function to handle the conversion for each slider instead of just copy/pasting it twice
    // but it's 3AM and I'm in physical pain
    statSlider.value = preferences.statOpacity*100;
    statSlider.onchange = function(){
        preferences.statOpacity = statSlider.value/100.0;
        updateOpacity()
    }
    statOpRow.insertCell(1).appendChild(statSlider);

    var hudOpRow = opTable.insertRow();
    hudOpRow.style.height = "24px";
    var hudDesc = hudOpRow.insertCell(0);
    hudDesc.appendChild(document.createTextNode("Location HUD:"));
    // set width once and the rest should follow
    hudDesc.style.width = "20%"
    var hudSlider = document.createElement('input');
    hudSlider.id = "hudSlider";
    hudSlider.type = "range"
    hudSlider.style.width = "95%";
    hudSlider.style.verticalAlign = "middle";
    hudSlider.value = preferences.hudOpacity*100;
    hudSlider.onchange = function(){
        preferences.hudOpacity = hudSlider.value/100.0;
        updateOpacity()
    }
    hudOpRow.insertCell(1).appendChild(hudSlider);

    const miscDiv = document.createElement("div")
    miscDiv.textContent = "Misc Settings";
    miscDiv.style.fontWeight = "bold";
    miscDiv.style.textAlign = "Left";
    miscDiv.style.marginTop = "5px";
    miscDiv.style.marginLeft = "5px";

    var shadowLabel = document.createElement("label");
    shadowLabel.textContent = "Darken hunter stat background";
    shadowLabel.style.textAlign = "left";
    shadowLabel.style.marginLeft = "10px";
    var shadowCheckbox = document.createElement('input');
    shadowCheckbox.id = "shadowCheckbox";
    shadowCheckbox.style.height = "24px";
    shadowCheckbox.style.verticalAlign = "middle";
    shadowCheckbox.type = "checkbox"
    shadowCheckbox.checked = preferences.statShadow;
    shadowCheckbox.onchange = function(){
        preferences.statShadow = shadowCheckbox.checked;
        updateShadow();
    }
    shadowLabel.appendChild(shadowCheckbox);

    var darkLabel = document.createElement("label");
    darkLabel.textContent = "Dark Mode (experimental)";
    darkLabel.style.textAlign = "left";
    darkLabel.style.marginLeft = "10px";
    var darkCheckbox = document.createElement('input');
    darkCheckbox.id = "darkCheckbox";
    darkCheckbox.style.height = "24px";
    darkCheckbox.style.verticalAlign = "middle";
    darkCheckbox.type = "checkbox"
    darkCheckbox.checked = preferences.darkMode;
    darkCheckbox.onchange = function(){
        preferences.darkMode = darkCheckbox.checked;
        updateDarkMode();
    }
    darkLabel.appendChild(darkCheckbox);

    buttonDiv.appendChild(closeButton);
    div.appendChild(titleDiv);
    div.appendChild(buttonDiv)
    div.appendChild(urlDiv);
    div.appendChild(urlTable);
    div.appendChild(opDiv);
    div.appendChild(opTable);
    div.appendChild(miscDiv);
    div.appendChild(shadowLabel);
    div.appendChild(darkLabel);
    document.body.appendChild(div);
    // why does this exist
    dragElement(div);
    resolve();
})}

// === Tsitu's drag magic functions ===
// no seriously this should be a library or something
function dragElement(elmnt) {
  var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  if (document.getElementById(elmnt.id + "header")) {
    // if present, the header is where you move the DIV from:
    document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
  } else {
    // otherwise, move the DIV from anywhere inside the DIV:
    elmnt.onmousedown = dragMouseDown;
  }

  function dragMouseDown(e) {
    e = e || window.event;
    e.preventDefault();
    // get the mouse cursor position at startup:
    pos3 = e.clientX;
    pos4 = e.clientY;
    document.onmouseup = closeDragElement;
    // call a function whenever the cursor moves:
    document.onmousemove = elementDrag;
  }

  function elementDrag(e) {
    e = e || window.event;
    e.preventDefault();
    // calculate the new cursor position:
    pos1 = pos3 - e.clientX;
    pos2 = pos4 - e.clientY;
    pos3 = e.clientX;
    pos4 = e.clientY;
    // set the element's new position:
    elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
    elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
  }

  function closeDragElement() {
    // stop moving when mouse button is released:
    document.onmouseup = null;
    document.onmousemove = null;
  }
}

// === Main logic ===

// the whole script boils down to calling this function a million times
// so don't touch this
function modifyElement(element, portion, value){
    var replaceElem;
    // check if it's a HTMLCollection/NodeList/Array/whatever
    if (element.length) {
        if (element.length == 0) {
            // silently skip empty containers
            return;
        }
        // unpack with RECURSION
        for (var i=0; i<element.length; i++) modifyElement(element[i], portion, value);
        return;
    }
    // single element
    replaceElem = element;
    try {
        replaceElem.style[portion] = value;
    } catch (err) {
        // very spammy so for now this will just silently consume errors
        // TODO: debug mode logging?
        //console.log('modifyElement error: ' + err);
    }
}

async function checkAndLoadImage(portion, url) {
    var imgUrl;
    if (url){
        // check if url is a valid image
        let imgPromise = new Promise(function(resolve) {
            try{
                var img = new Image();
                img.onload = function(){
                    resolve(this.width != 0 && this.height != 0)
                };
                img.src = url;
            } catch (error) {
                console.error(error.stack);
                resolve(false);
            }
        });
        var validImage = await imgPromise;
        imgUrl = validImage ? url : "";
    } else {
        imgUrl = "";
    }
    switch(portion) {
        case "hud":
            preferences.hudUrl = imgUrl;
            break;
        case "back":
            preferences.backUrl = imgUrl;
            break;
        case "left":
            preferences.leftUrl = imgUrl;
            break;
        case "right":
            preferences.rightUrl = imgUrl;
            break;
        default:
            console.log("checkAndLoadImage(): invalid portion");
    }
    savePreferences();
    updateImg(portion);
}

// Update HUD background
function updateImg(portion) {
    var target;
    var bgString;
    var url;
    // set the variables
    // YEAH HARDCODING
    switch(portion) {
        case "hud":
            target = document.getElementsByClassName("mousehuntHud-marbleDrawer");
            bgString = `url(https://www.mousehuntgame.com/images/ui/hud/mousehuntHudPedestal.gif?asset_cache_version=2) -37px 0 no-repeat,
        url(https://www.mousehuntgame.com/images/ui/hud/mousehuntHudPedestal.gif?asset_cache_version=2) 723px 0 no-repeat,
        url(${preferences.hudUrl}) bottom center / 100%`;
            url = preferences.hudUrl;
            break;
        case "back":
            target = document.getElementById("mousehuntContainer");
            bgString = `url(${preferences.backUrl}) repeat-y center / 100%`;
            // special handling for journal back - it should only be applied on the camp page.
            if (window.location.href.includes("camp.php")) {
                url = preferences.backUrl;
            } else {
                url = "";
            }
            break;
        case "left":
            target = document.getElementsByClassName("pageFrameView-column left");
            bgString = `url(${preferences.leftUrl})`;
            url = preferences.leftUrl;
            break;
        case "right":
            target = document.getElementsByClassName("pageFrameView-column right");
            bgString = `url(${preferences.rightUrl})`;
            url = preferences.rightUrl;
            break;
        default:
            updateImg("hud");
            updateImg("back");
            updateImg("left");
            updateImg("right");
            return;
    }
    // actually replace stuff
    if (url) {
        modifyElement(target, "background", bgString);
    } else {
        modifyElement(target, "background", "");
    }
}

// update transparency
function updateOpacity() {
    savePreferences();
    const statsElem = document.getElementsByClassName("headsUpDisplayView-stats");
    const hudElem = document.getElementsByClassName("hudLocationContent");

    modifyElement(statsElem, "opacity", preferences.statOpacity);
    modifyElement(hudElem, "opacity", preferences.hudOpacity);
}

// update stat shading
function updateShadow() {
    savePreferences();
    const statsElem = document.getElementsByClassName("headsUpDisplayView-stats");
    if (preferences.statShadow) {
        // set background to black with 0.3 opacity
        modifyElement(statsElem, "backgroundColor", "rgba(0, 0, 0, 0.30)");
    } else {
        modifyElement(statsElem, "backgroundColor", "");
    }
}

// experimental dark mode
function updateDarkMode() {
    savePreferences();
    const darkBgArray = [document.getElementById("overlayContainer"), document.getElementsByClassName("pageFrameView-contentContainer"),
                         document.getElementsByClassName("mousehuntHeaderView"),document.getElementsByClassName("pageSidebarView")];
    const whiteBgArray = [document.getElementById("mousehuntContainer"),
                          document.getElementsByClassName("mousehuntHeaderView-newsTicker"), document.getElementsByClassName("ticker")];
    const textArray = [document.getElementsByClassName("pageSidebarView-user"), document.getElementsByClassName("pageSidebarView-block-description"),
                       document.getElementsByClassName("communityGroupView-ad-groupDescription"), document.getElementsByClassName("pageSidebarView-title"),
                       document.querySelectorAll(".scoreboardRelativeRankingTableView-table th"),
                       document.querySelectorAll(".scoreboardRelativeRankingTableView-table td:first-child"),
                       document.querySelectorAll(".scoreboardRelativeRankingTableView-table td:last-child")];
    const linkArray = [document.querySelectorAll(".pageFrameView-footer-links a"), document.querySelectorAll(".pageSidebarView-user a"),
                       document.querySelectorAll(".communityGroupView-ad a"), document.querySelectorAll(".scoreboardRelativeRankingTableView-table a")];
    const hlArray = document.querySelectorAll(".scoreboardRelativeRankingTableView-table .highlight");
    const tables = document.getElementsByClassName("scoreboardRelativeRankingTableView-table");

    if (preferences.darkMode) {
        // recolor backgrounds
        darkBgArray.forEach(element => modifyElement(element, "backgroundColor", DARK_BG_COLOR));
        whiteBgArray.forEach(element => modifyElement(element, "backgroundColor", "white"));
        textArray.forEach(element => modifyElement(element, "color", DARK_TEXT_COLOR));
        linkArray.forEach(element => modifyElement(element, "color", DARK_LINK_COLOR));
        modifyElement(document.getElementsByClassName("pageFrameView-column left"), "borderRightColor", DARK_BG_COLOR);
        modifyElement(document.getElementsByClassName("pageFrameView-column right"), "borderLeftColor", DARK_BG_COLOR);
        modifyElement(document.getElementsByClassName("pageSidebarView-user"), "borderBottom", `5px solid ${DARK_BG_COLOR}`);
        modifyElement(hlArray, "backgroundColor", DARK_HL_COLOR);
        modifyElement(tables, "borderBottom", "2px solid #ddedff");
    } else {
        // clear all
        darkBgArray.forEach(element => modifyElement(element, "backgroundColor", ""));
        whiteBgArray.forEach(element => modifyElement(element, "backgroundColor", ""));
        textArray.forEach(element => modifyElement(element, "color", ""));
        linkArray.forEach(element => modifyElement(element, "color", ""));
        modifyElement(document.getElementsByClassName("pageFrameView-column left"), "borderRightColor", "");
        modifyElement(document.getElementsByClassName("pageFrameView-column right"), "borderLeftColor", "");
        modifyElement(document.getElementsByClassName("pageSidebarView-user"), "borderBottom", "");
        modifyElement(hlArray, "backgroundColor", "");
        modifyElement(tables, "borderBottom", "");
    }
}

function fixFbBanner(){
    // FINALLY, A WORTHY OPPONENT
    // OUR BATTLE WILL BE LEGENDARY!
    modifyElement(document.getElementsByClassName("fb-page fb_iframe_widget"), "borderBottom", "1px solid #ebedf0");
    modifyElement(document.getElementsByClassName("fb-page fb_iframe_widget"), "height", "100%");
}

function updateAll() {
    updateImg();
    updateOpacity();
    updateShadow();
    updateDarkMode();
    fixFbBanner()
}
// start the script
init();