Easily invite many friends to your maps
// ==UserScript==
// @name MouseHunt - Bulk Map Invites
// @author Tran Situ (tsitu)
// @namespace https://gf.qytechs.cn/en/users/232363-tsitu
// @version 1.1
// @description Easily invite many friends to your maps
// @match http://www.mousehuntgame.com/*
// @match https://www.mousehuntgame.com/*
// ==/UserScript==
(function() {
const observerTarget = document.getElementById("overlayPopup");
if (observerTarget) {
MutationObserver =
window.MutationObserver ||
window.WebKitMutationObserver ||
window.MozMutationObserver;
const observer = new MutationObserver(function() {
// Callback
const inviteHeader = document.querySelector(
".treasureMapPopup-inviteFriend-header"
);
// Render if friend invite header is in DOM
if (inviteHeader) {
// Disconnect and reconnect later to prevent mutation loop
observer.disconnect();
render(observer);
observer.observe(observerTarget, {
childList: true,
subtree: true
});
}
});
observer.observe(observerTarget, {
childList: true,
subtree: true
});
}
function render(observer) {
const obj = {}; // key = location, value = <a> array
const friendVal = localStorage.getItem("tsitu-invite-friends") || "false";
document
.querySelectorAll(
".userSelectorView-userList-group.default a.treasureMapPopup-inviteFriend-row"
)
.forEach(el => {
if (el.children.length === 7) insert(el);
});
if (friendVal == "true") {
document
.querySelectorAll(
".userSelectorView-userList-group.busy a.treasureMapPopup-inviteFriend-row"
)
.forEach(el => {
if (el.children.length === 7) insert(el);
});
}
// Insert location and <a> into obj
function insert(el) {
const location = el.children[1].textContent;
if (obj[location] === undefined) {
obj[location] = [el];
} else {
obj[location].push(el);
}
}
const target = document.querySelector(
".treasureMapPopup-map-state.inviteFriends .treasureMapPopup-rightBlock"
);
if (target) {
// Remove master div if it exists
const existing = document.querySelector(".tsitu-map-invites-div");
if (existing) existing.remove();
// Initialize master div + styling
const div = document.createElement("div");
div.className = "tsitu-map-invites-div";
div.style.margin = "0 10px 0 10px";
div.style.textAlign = "center";
function clickListener() {
// Updates localStorage and re-renders
observer.disconnect();
localStorage.setItem(
"tsitu-invite-friends",
document.querySelector(".tsitu-map-friends-box").checked
);
localStorage.setItem(
"tsitu-invite-sort",
document.querySelector(".tsitu-loc-sort-box").checked
);
localStorage.setItem(
"tsitu-invite-select",
document.querySelector(".tsitu-friend-select-box").checked
);
render(observer);
observer.observe(observerTarget, {
childList: true,
subtree: true
});
}
// All friends or only those not currently on a map
const friendType = document.createElement("input");
friendType.type = "checkbox";
friendType.className = "tsitu-map-friends-box";
friendType.name = "tsitu-map-friends";
friendType.addEventListener("click", clickListener);
const friendTypeLabel = document.createElement("label");
friendTypeLabel.className = "tsitu-map-friends-label";
friendTypeLabel.htmlFor = "tsitu-map-friends";
friendTypeLabel.innerHTML = "Friends: <b>Not On Map</b> / All";
// friendType checkmark
const ftChecked = localStorage.getItem("tsitu-invite-friends") || "false";
friendType.checked = ftChecked === "true";
if (friendType.checked) {
friendTypeLabel.innerHTML = "Friends: Not On Map / <b>All</b>";
}
// Sort locations alphabetically or by most hunters
const locationSort = document.createElement("input");
locationSort.type = "checkbox";
locationSort.className = "tsitu-loc-sort-box";
locationSort.name = "tsitu-loc-sort";
locationSort.addEventListener("click", clickListener);
const locationSortLabel = document.createElement("label");
locationSortLabel.className = "tsitu-loc-sort-label";
locationSortLabel.htmlFor = "tsitu-loc-sort";
locationSortLabel.innerHTML = "Sort: <b>Alpha</b> / # Hunters";
// locationSort checkmark
const lsChecked = localStorage.getItem("tsitu-invite-sort") || "false";
locationSort.checked = lsChecked === "true";
if (locationSort.checked) {
locationSortLabel.innerHTML = "Sort: Alpha / <b># Hunters</b>";
}
// Select friends randomly or by most clues found
const friendSelect = document.createElement("input");
friendSelect.type = "checkbox";
friendSelect.className = "tsitu-friend-select-box";
friendSelect.name = "tsitu-friend-select";
friendSelect.addEventListener("click", clickListener);
const friendSelectLabel = document.createElement("label");
friendSelectLabel.className = "tsitu-friend-select-label";
friendSelectLabel.htmlFor = "tsitu-friend-select";
friendSelectLabel.innerHTML = "Select: <b>Random</b> / # Clues";
// friendSelect checkmark
const fsChecked = localStorage.getItem("tsitu-invite-select") || "false";
friendSelect.checked = fsChecked === "true";
if (friendSelect.checked) {
friendSelectLabel.innerHTML = "Select: Random / <b># Clues</b>";
}
// Button to click <a>'s
const goButton = document.createElement("button");
goButton.className = "button";
goButton.style.fontSize = "1.7em";
goButton.style.marginBottom = "5px";
goButton.innerText = "Go";
goButton.addEventListener("click", function() {
observer.disconnect();
unclickRows();
// Routine to click up to 8 friends
const location = document.querySelector(".tsitu-map-loc-dropdown")
.value;
if (location) {
// Cache location name
localStorage.setItem("tsitu-invite-location", location);
// Get friend select preference
const selectVal =
localStorage.getItem("tsitu-invite-select") == "true";
if (location === "All") {
const rawArr = [];
for (let el of Object.keys(obj)) {
for (let a of obj[el]) {
rawArr.push(a);
}
}
let sortArr = [];
if (selectVal) {
sortArr = arrayClues(rawArr);
} else {
sortArr = arrayShuffle(rawArr);
}
const maxIter = sortArr.length > 8 ? 8 : sortArr.length;
for (let i = 0; i < maxIter; i++) {
sortArr[i].click();
}
} else {
let sortArr = [];
if (selectVal) {
sortArr = arrayClues(obj[location]);
} else {
sortArr = arrayShuffle(obj[location]);
}
const maxIter = sortArr.length > 8 ? 8 : sortArr.length;
for (let i = 0; i < maxIter; i++) {
sortArr[i].click();
}
}
}
observer.observe(observerTarget, {
childList: true,
subtree: true
});
});
// Button to unclick <a>'s
const undoButton = document.createElement("button");
undoButton.className = "button";
undoButton.style.fontSize = "1.3em";
undoButton.innerText = "↩️";
undoButton.addEventListener("click", function() {
observer.disconnect();
unclickRows();
observer.observe(observerTarget, {
childList: true,
subtree: true
});
});
// Final element manipulation
div.appendChild(goButton);
div.appendChild(undoButton);
div.appendChild(document.createElement("br"));
div.appendChild(populateDropdown(obj));
div.appendChild(document.createElement("br"));
div.appendChild(friendType);
div.appendChild(friendTypeLabel);
div.appendChild(document.createElement("br"));
div.appendChild(locationSort);
div.appendChild(locationSortLabel);
div.appendChild(document.createElement("br"));
div.appendChild(friendSelect);
div.appendChild(friendSelectLabel);
target.appendChild(div);
}
}
/**
* Return <select> dropdown of sorted locations
* Includes # of hunters per location in parentheses
* Implicitly handles empty obj
* @param {object} obj Object with key = location, value = array of <a>
* @return {<select>} <select> with desired friend inclusion & location sort
*/
function populateDropdown(obj) {
// Remove dropdown if it exists
const existing = document.querySelector(".tsitu-map-loc-dropdown");
if (existing) existing.remove();
// Create new dropdown and style it
const dropdown = document.createElement("select");
dropdown.className = "tsitu-map-loc-dropdown";
dropdown.style.width = "100%";
dropdown.style.marginBottom = "2px";
// Add initial 'All' location option
let counter = 0;
const unsortedKeys = Object.keys(obj);
unsortedKeys.forEach(el => {
counter += obj[el].length;
});
const allOption = document.createElement("option");
allOption.textContent = `All (${counter})`;
allOption.value = "All";
dropdown.appendChild(allOption);
// Apply desired sort to location keys
const sortVal = localStorage.getItem("tsitu-invite-sort") || "false";
let sortedKeys = unsortedKeys;
if (sortVal == "false") {
sortedKeys = unsortedKeys.sort();
} else if (sortVal == "true") {
sortedKeys = unsortedKeys.sort(function(a, b) {
return obj[b].length - obj[a].length;
});
}
// Append <option>'s to the <select>
for (let loc of sortedKeys) {
const option = document.createElement("option");
option.textContent = `${loc} (${obj[loc].length})`;
option.value = loc;
dropdown.appendChild(option);
}
// Select a dropdown value if available from cache
const cachedLoc = localStorage.getItem("tsitu-invite-location");
for (let el of dropdown.options) {
const loc = el.textContent.split(" (")[0];
if (loc === cachedLoc) {
dropdown.value = cachedLoc;
}
}
return dropdown;
}
// Routine to unclick invited friend rows
function unclickRows() {
// First pass: Try highlighted rows
const highlighted = document.querySelectorAll(
".treasureMapPopup-inviteFriend-row.selected"
);
highlighted.forEach(el => el.click());
// Second pass: Try to match icon images and names
const selected = document.querySelectorAll(
".treasureMapPopup-inviteAction-friendSlot:not(.empty)"
);
if (selected.length > 0) {
// Memoize obj with key = graph icon ID, value = player name
const memo = {};
selected.forEach(el => {
const id = el.style.backgroundImage
.split(".com/")[1]
.split("/picture")[0];
const name = el.title;
memo[id] = name;
});
const memoKeys = Object.keys(memo);
const rows = document.querySelectorAll(
".treasureMapPopup-inviteFriend-row.userSelectorView-user"
);
rows.forEach(el => {
const id = el
.querySelector(".treasureMapPopup-inviteFriend-profilePic")
.style.backgroundImage.split(".com/")[1]
.split("/picture")[0];
if (memoKeys.indexOf(id) >= 0) {
const name = el.querySelector(
".treasureMapPopup-inviteFriend-friendName"
).textContent;
if (memo[id] === name) {
el.click();
}
}
});
}
}
/**
* @param {<a>[]} arr Input <a> array
* @return {<a>[]} Randomly shuffled <a> array
*/
function arrayShuffle(arr) {
// Durstenfeld Shuffle
let shuffledArr = arr.slice(0);
for (let i = shuffledArr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = shuffledArr[i];
shuffledArr[i] = shuffledArr[j];
shuffledArr[j] = temp;
}
return shuffledArr;
}
/**
* Returns an <a> array sorted by most clues found
* @param {<a>[]} arr Input <a> array
* @return {<a>[]} Sorted <a> array
*/
function arrayClues(arr) {
return arr.sort(function(a, b) {
return (
parseInt(b.children[2].textContent) -
parseInt(a.children[2].textContent)
);
});
}
})();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址