// ==UserScript== // @name MouseHunt - Mapping Helper // @author Tran Situ (tsitu) // @namespace https://greasyfork.org/en/users/232363-tsitu // @version 0.4 (beta) // @description Invite players and send SB+ directly from the map interface! // @match http://www.mousehuntgame.com/* // @match https://www.mousehuntgame.com/* // @downloadURL none // ==/UserScript== (function() { // Map endpoint listener - caches encountered data (maps come in one at a time) const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function() { this.addEventListener("load", function() { if ( this.responseURL === "https://www.mousehuntgame.com/managers/ajax/users/relichunter.php" ) { // console.group("Mapping Helper"); try { const map = JSON.parse(this.responseText).treasure_map; if (map) { const obj = {}; const condensed = {}; const condensedGroups = []; map.groups.forEach(el => { // TODO: Compress goals array if needed for individual mouse/item stuff const innerObj = {}; innerObj.name = el.name; innerObj.profile_pic = el.profile_pic; innerObj.snuid = el.snuid; condensedGroups.push(innerObj); }); condensed.groups = condensedGroups; condensed.hunters = map.hunters; condensed.invited_hunters = map.invited_hunters; condensed.is_complete = map.is_complete; condensed.is_owner = map.is_owner; condensed.is_scavenger_hunt = map.is_scavenger_hunt; condensed.is_wanted_poster = map.is_wanted_poster; condensed.map_class = map.map_class; condensed.map_id = map.map_id; condensed.timestamp = Date.now(); obj[map.name] = condensed; // console.log(obj); const mapCacheRaw = localStorage.getItem("tsitu-mapping-cache"); if (mapCacheRaw) { const mapCache = JSON.parse(mapCacheRaw); mapCache[map.name] = condensed; localStorage.setItem( "tsitu-mapping-cache", JSON.stringify(mapCache) ); } else { localStorage.setItem("tsitu-mapping-cache", JSON.stringify(obj)); } render(); } } catch (error) { console.log("Server response doesn't contain a valid treasure map"); console.error(error.stack); } // console.groupEnd("Mapping Helper"); } }); originalOpen.apply(this, arguments); }; // Renders custom UI elements onto the DOM function render() { // Clear out existing custom elements // Uses static collection instead of live one from getElementsByClassName document.querySelectorAll(".tsitu-mapping").forEach(el => el.remove()); /** * Refresh button * Iterate thru QRH.maps array for element matching current map and set its hash to empty string * This forces a hard refresh via hasCachedMap, which is called in show/showMap */ const refreshSpan = document.createElement("span"); refreshSpan.className = "tsitu-mapping tsitu-refresh-span"; const refreshButton = document.createElement("button"); refreshButton.innerText = "Refresh"; refreshButton.className = "treasureMapPopup-action-button tsitu-mapping"; refreshButton.style.cursor = "pointer"; refreshButton.style.fontSize = "9px"; refreshButton.style.padding = "2px"; refreshButton.style.margin = "3px 5px 0px 0px"; refreshButton.style.textShadow = "none"; refreshButton.style.display = "inline-block"; refreshButton.addEventListener("click", function() { const mapName = document.querySelector( ".treasureMapPopup-header-title.mapName" ).textContent; user.quests.QuestRelicHunter.maps.forEach(el => { if (el.name === mapName) { // Reset hash to bust cache el.hash = ""; } }); // Close map dialog and re-open either with current map, default, or overview const mapIdEl = document.querySelector("[data-map-id].active"); const mapId = mapIdEl ? mapIdEl.getAttribute("data-map-id") : -1; document.getElementById("jsDialogClose").click(); mapId === -1 ? hg.views.TreasureMapView.show() : hg.views.TreasureMapView.show(mapId); }); refreshSpan.appendChild(refreshButton); document .querySelector( ".treasureMapPopup-state.viewMap .treasureMapPopup-header-subtitle" ) .insertAdjacentElement("afterend", refreshSpan); // Utility handler that opens supply transfer page and selects SB+ function transferSB(snuid) { const newWindow = window.open( `https://www.mousehuntgame.com/supplytransfer.php?fid=${snuid}` ); newWindow.addEventListener("load", function() { if (newWindow.supplyTransfer1) { newWindow.supplyTransfer1.setSelectedItemType("super_brie_cheese"); newWindow.supplyTransfer1.renderTabMenu(); newWindow.supplyTransfer1.render(); } }); return false; } // Corkboard image click handling document.querySelectorAll("[data-message-id]").forEach(msg => { const snuid = msg .querySelector(".messageBoardView-message-name") .href.split("snuid=")[1]; const img = msg.querySelector(".messageBoardView-message-image"); img.href = "#"; img.onclick = function() { transferSB(snuid); }; }); // Hunter container image click handling document .querySelectorAll(".treasureMapPopup-hunter:not(.empty)") .forEach(el => { const img = el.querySelector(".treasureMapPopup-hunter-image"); const snuid = el.getAttribute("data-snuid"); img.style.cursor = "pointer"; img.onclick = function() { transferSB(snuid); }; }); // Features that require cache checking const cacheRaw = localStorage.getItem("tsitu-mapping-cache"); if (cacheRaw) { const cache = JSON.parse(cacheRaw); const mapName = document.querySelector( ".treasureMapPopup-header-title.mapName" ).textContent; if (cache[mapName] !== undefined) { // Must specify because favorite button
also matches the selector const mapIdEl = document.querySelector("a[data-map-id].active"); if (mapIdEl) { // Abstract equality comparison because map ID can be number or string const mapId = mapIdEl.getAttribute("data-map-id"); if (mapId == cache[mapName].map_id) { // "Last refreshed" timestamp const refreshSpan = document.querySelector(".tsitu-refresh-span"); if (refreshSpan && cache[mapName].timestamp) { const timeSpan = document.createElement("span"); timeSpan.innerText = `(This map was last refreshed on: ${new Date( parseInt(cache[mapName].timestamp) ).toLocaleString()})`; refreshSpan.appendChild(timeSpan); } // Invite via Hunter ID (only for map captains) if (cache[mapName].is_owner) { const inputLabel = document.createElement("label"); inputLabel.innerText = "Hunter ID: "; inputLabel.htmlFor = "tsitu-mapping-id-input"; inputLabel.setAttribute("class", "tsitu-mapping"); const inputField = document.createElement("input"); inputField.setAttribute("type", "text"); inputField.setAttribute("class", "tsitu-mapping"); inputField.setAttribute("name", "tsitu-mapping-id-input"); inputField.setAttribute("data-lpignore", "true"); // Get rid of LastPass Autofill inputField.setAttribute("size", 10); inputField.setAttribute("required", true); inputField.addEventListener("keyup", function(e) { if (e.keyCode === 13) { inviteButton.click(); // 'Enter' pressed } }); const inviteButton = document.createElement("button"); inviteButton.innerText = "Invite"; inviteButton.setAttribute("class", "tsitu-mapping"); inviteButton.addEventListener("click", function() { const rawText = inputField.value; if (rawText.length > 0) { const hunterId = parseInt(rawText); if (typeof hunterId === "number" && !isNaN(hunterId)) { if (hunterId > 0 && hunterId < 9999999) { // console.log(hunterId); postReq( "https://www.mousehuntgame.com/managers/ajax/pages/friends.php", `sn=Hitgrab&hg_is_ajax=1&action=community_search_by_id&user_id=${hunterId}&uh=${ user.unique_hash }` ).then(res => { let response = null; try { if (res) { response = JSON.parse(res.responseText); // console.log(response); const data = response.friend; if (data.has_invitable_map) { if ( confirm( `Are you sure you'd like to invite this hunter?\n\nName: ${ data.name }\nTitle: ${data.title_name} (${ data.title_percent }%)\nLocation: ${ data.environment_name }\nLast Active: ${ data.last_active_formatted } ago` ) ) { postReq( "https://www.mousehuntgame.com/managers/ajax/users/relichunter.php", `sn=Hitgrab&hg_is_ajax=1&action=send_invites&map_id=${mapId}&snuids%5B%5D=${ data.snuid }&uh=${user.unique_hash}` ).then(res2 => { let inviteRes = null; try { if (res2) { inviteRes = JSON.parse(res2.responseText); if (inviteRes.success === 1) { refreshButton.click(); } } } catch (error2) { alert("Error while inviting hunter to map"); console.error(error2.stack); } }); } } else { alert( `${ data.name } cannot to be invited to a map at this time` ); } } } catch (error) { alert("Error while retrieving hunter information"); console.error(error.stack); } }); } } } }); // Invited hunters aka pending invites const invitedArr = cache[mapName].invited_hunters; const invitedSpan = document.createElement("span"); invitedSpan.className = "tsitu-mapping"; invitedSpan.style.marginLeft = "5px"; invitedSpan.innerText = invitedArr.length > 0 ? "Pending Invites:" : "Pending Invites: None"; if (invitedArr.length > 0) { let count = 1; invitedArr.forEach(snuid => { const link = document.createElement("a"); link.innerText = `[${count}]`; link.href = `https://www.mousehuntgame.com/profile.php?snuid=${snuid}`; link.target = "_blank"; count += 1; invitedSpan.appendChild(document.createTextNode("\u00A0")); invitedSpan.appendChild(link); }); } const span = document.createElement("span"); span.style.display = "inline-block"; span.style.marginBottom = "10px"; span.appendChild(inputLabel); span.appendChild(inputField); span.appendChild(inviteButton); span.appendChild(invitedSpan); document .querySelector(".treasureMapPopup-hunterContainer") .insertAdjacentElement("afterend", span); } } } // "x caught these mice" image click handling const groups = cache[mapName].groups; const format = {}; groups.forEach(el => { if (el.profile_pic !== null) { format[el.profile_pic] = [el.name, el.snuid]; } }); document .querySelectorAll(".treasureMapPopup-goals-group-header") .forEach(group => { const text = group.textContent.split(":(")[0] + ":"; if (text !== "Uncaught mice in other locations:") { const img = group.querySelector( ".treasureMapPopup-goals-group-header-image" ); if (img) { const pic = img.style.backgroundImage .split('url("')[1] .split('")')[0]; if (format[pic] !== undefined) { if (format[pic][0] === text) { img.style.cursor = "pointer"; img.onclick = function() { const snuid = format[pic][1]; transferSB(snuid); }; } } } } }); } } } // POST to specified endpoint URL with desired form data function postReq(url, form) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open("POST", url, true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.onreadystatechange = function() { if (this.readyState === XMLHttpRequest.DONE && this.status === 200) { resolve(this); } }; xhr.onerror = function() { reject(this); }; xhr.send(form); }); } // MutationObserver logic for map UI // Observers are attached to a *specific* element (will DC if removed from DOM) const observerTarget = document.getElementById("overlayPopup"); if (observerTarget) { MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; const observer = new MutationObserver(function() { // Callback // Render if treasure map popup is available const mapTab = observerTarget.querySelector("[data-tab=map_mice]"); const groupLen = document.querySelectorAll( ".treasureMapPopup-goals-groups" ).length; // Prevent conflict with 'Bulk Map Invites' const inviteHeader = document.querySelector( ".treasureMapPopup-inviteFriend-header" ); if ( mapTab && mapTab.className.indexOf("active") >= 0 && groupLen > 0 && !inviteHeader ) { // Disconnect and reconnect later to prevent infinite mutation loop observer.disconnect(); render(); observer.observe(observerTarget, { childList: true, subtree: true }); } }); observer.observe(observerTarget, { childList: true, subtree: true }); } })();