您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Invite players and send SB+ directly from the map interface
当前为
- // ==UserScript==
- // @name MouseHunt - Mapping Helper
- // @author Tran Situ (tsitu)
- // @namespace https://greasyfork.org/en/users/232363-tsitu
- // @version 1.0
- // @description Invite players and send SB+ directly from the map interface
- // @match http://www.mousehuntgame.com/*
- // @match https://www.mousehuntgame.com/*
- // ==/UserScript==
- (function() {
- // RH endpoint listener - caches maps (which 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"
- ) {
- 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 for individual mice/items in the future
- 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);
- }
- }
- });
- 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 <a> because favorite button <div> 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", "number");
- inputField.setAttribute("class", "tsitu-mapping");
- inputField.setAttribute("name", "tsitu-mapping-id-input");
- inputField.setAttribute("data-lpignore", "true"); // Get rid of LastPass Autofill
- inputField.setAttribute("min", 1);
- inputField.setAttribute("max", 9999999);
- inputField.setAttribute("placeholder", "e.g. 1234567");
- 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) {
- 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();
- } else {
- alert(
- "Map invite unsuccessful - may be because map is full"
- );
- }
- }
- } catch (error2) {
- alert("Error while inviting hunter to map");
- console.error(error2.stack);
- }
- });
- }
- } else {
- if (data.name) {
- alert(
- `${
- data.name
- } cannot to be invited to a map at this time`
- );
- } else {
- alert("Invalid hunter information");
- }
- }
- }
- } catch (error) {
- alert("Error while requesting 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";
- invitedSpan.appendChild(document.createTextNode("\u00A0"));
- invitedSpan.appendChild(link);
- // Prevent text from running past width of dialog (fails when >999)
- if (count < 100) {
- if (count === 20) {
- invitedSpan.appendChild(document.createElement("br"));
- } else if ((count - 20) % 30 === 0) {
- invitedSpan.appendChild(document.createElement("br"));
- }
- } else {
- if (count === 108) {
- invitedSpan.appendChild(document.createElement("br"));
- } else if ((count - 108) % 24 === 0) {
- invitedSpan.appendChild(document.createElement("br"));
- }
- }
- count += 1;
- });
- // -- Text debugging --
- // for (let i = 0; i < 1337; i++) {
- // const link = document.createElement("a");
- // link.innerText = `[${i + 1}]`;
- // link.href = `#`;
- // invitedSpan.appendChild(document.createTextNode("\u00A0"));
- // invitedSpan.appendChild(link);
- // // Prevent text from running past width of dialog (formatting breaks past 1k but meh)
- // if (i + 1 < 100) {
- // if (i + 1 === 20) {
- // invitedSpan.appendChild(document.createElement("br"));
- // } else if ((i + 1 - 20) % 30 === 0) {
- // invitedSpan.appendChild(document.createElement("br"));
- // }
- // } else {
- // if (i + 1 === 108) {
- // invitedSpan.appendChild(document.createElement("br"));
- // } else if ((i + 1 - 108) % 24 === 0) {
- // invitedSpan.appendChild(document.createElement("br"));
- // }
- // }
- // }
- }
- 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
- });
- }
- })();