Greasy Fork

Greasy Fork is available in English.

MouseHunt - Mapping Helper

Invite players and send SB+ directly from the map interface!

当前为 2019-06-03 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         MouseHunt - Mapping Helper
// @author       Tran Situ (tsitu)
// @namespace    http://greasyfork.icu/en/users/232363-tsitu
// @version      0.2 (beta)
// @description  Invite players and send SB+ directly from the map interface!
// @match        http://www.mousehuntgame.com/*
// @match        https://www.mousehuntgame.com/*
// ==/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) {
        const mapIdEl = document.querySelector("[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;
              let invitedSpan = -1;
              if (invitedArr.length > 0) {
                invitedSpan = document.createElement("span");
                invitedSpan.className = "tsitu-mapping";
                invitedSpan.style.marginLeft = "5px";
                invitedSpan.innerText = "Pending Invites:";
                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);
              if (invitedSpan !== -1) 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
    });
  }
})();