您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Sore Foot Club Scripts - RR Mugger Detection & Watcher Requests
// ==UserScript== // @name Sore Foot Club - RR Mugger Detection // @namespace dekleinekobini.rr-mugger-detect // @version 1.1.6 // @author DeKleineKobini [2114440] // @description Sore Foot Club Scripts - RR Mugger Detection & Watcher Requests // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @match https://www.torn.com/page.php?sid=russianRoulette* // @connect api.no1irishstig.co.uk // @grant GM_addStyle // @grant GM_info // @grant GM_xmlhttpRequest // @run-at document-end // ==/UserScript== (e=>{if(typeof GM_addStyle=="function"){GM_addStyle(e);return}const t=document.createElement("style");t.textContent=e,document.head.append(t)})(" .mugger-detect-message {color:var(--detect-color);background-color:var(--detect-background-color);text-align:center;padding:2px;font-size:16px;line-height:20px}@media only screen and (min-width: 768px) {.mugger-detect-message {color:var(--detect-color);background-color:var(--detect-background-color);text-align:center;padding:5px;font-size:18px;line-height:24px}.mugger-detect-message .title{line-height:30px}}.mugger-detect-message .title{line-height:25px}.mugger-detect-message .message{line-height:20px}.mugger-detect-message a{color:var(--detect-link-color, #fff);text-decoration:none}[class*=headerWrap___]{height:unset!important}.mugger-detect-watcher{display:flex;align-items:center;justify-content:space-between;color:var(--appheader-links-color, #777);cursor:pointer;text-shadow:var(--appheader-links-text-shadow-color, 0 1px 0 hsla(0, 0%, 100%, .651));height:100%;font:inherit}.mugger-detect-watcher svg{display:flex;align-items:center;justify-content:center;box-sizing:border-box;pointer-events:none;margin-right:7px;fill:var(--icon-color, var(--appheader-links-fill, url(#app-header-gradient)));margin-bottom:3px}@media screen and (max-width: 784px){.mugger-detect-watcher svg{margin-right:0;margin-bottom:0}}.mugger-detect-watcher span{font-weight:700}@media screen and (max-width: 784px){.mugger-detect-watcher span{display:none}}.mugger-detect-watcher--success{--icon-color:#54b358;color:#54b358}.mugger-detect-watcher--failure{--icon-color:#f77a71;color:#f77a71} "); (function () { 'use strict'; const SCRIPT_VERSION = "1.1.6"; const CHAT_SELECTORS = Object.freeze({ /* * Global */ CHAT_ROOT_ID: "chatRoot", AVATAR_WRAPPER_CLASS: "avatar__avatar-status-wrapper___", /* * Chat Boxes */ GROUP_MENU_DESKTOP_CLASS: "minimized-menus__desktop___", GROUP_MENU_MOBILE_CLASS: "minimized-menus__mobile___", GROUP_MENU_MOBILE_BUTTON_CLASS: "minimized-menus__mobile-button___", CHAT_LIST_CLASS: "chat-app__chat-list-chat-box-wrapper___", CHAT_LIST_GROUP_CHAT_CLASS: "minimized-menu-item___", CHAT_LIST_NOTEPAD_CLASS: "chat-note-button___", CHAT_LIST_SETTINGS_CLASS: "chat-setting-button___", CHAT_LIST_PEOPLE_CLASS: "chat-list-button___", CHAT_MESSAGE_COUNT_CLASS: "message-count___", CHAT_WRAPPER_WRAPPER_CLASS: "group-chat-box___", CHAT_WRAPPER_CLASS: "group-chat-box__chat-box-wrapper___", CHAT_HEADER_CLASS: "chat-box-header___", CHAT_HEADER_INFO_BUTTON_CLASS: "chat-box-header__info-btn___", CHAT_HEADER_INFO_CLASS: "chat-box-header__info___", CHAT_HEADER_AVATAR_CLASS: "chat-box-header__avatar___", CHAT_HEADER_MINIMIZE_ICON_CLASS: "minimize-icon", CHAT_HEADER_CLOSE_ICON_CLASS: "close-icon", CHAT_BODY_CLASS: "chat-box-body___", /* * Messages */ COLOR_ERROR_CLASS: "color-chatError", MESSAGE_BODY_WRAPPER_CLASS: "chat-box-message___", MESSAGE_BODY_WRAPPER_SELF_CLASS: "chat-box-message--self___", MESSAGE_BODY_CLASS: "chat-box-message__box___", MESSAGE_BODY_SELF_CLASS: "chat-box-message__box--self___", MESSAGE_SENDER_CLASS: "chat-box-message__sender___", MESSAGE_MINIMIZED_BOX_AVATAR_CLASS: "minimized-chat-box__avatar___", MESSAGE_AVATAR_CLASS: "chat-box-message__avatar___", MESSAGE_CONTENT_WRAPPER_CLASS: "chat-box-message__message___", MESSAGE_SEND_BUTTON_CLASS: "chat-box-footer__send-icon-wrapper___", LAST_MESSAGE_TIMESTAMP_CLASS: "chat-box-body__lastmessage-timestamp___", /* * People Panel */ PANEL_WRAPPER_CLASS: "chat-app__panel___", /* * People Panel */ PEOPLE_PANEL_LOADING: "chat-tab__loader___", PEOPLE_PANEL_CLASS: "chat-tab___", PEOPLE_PANEL_CLOSE_BUTTON_ID: "_close_default_dark", PEOPLE_PANEL_SETTINGS_BUTTON_ID: "setting_default", PEOPLE_PANEL_HEADER_BUTTON: "chat-list-header__button___", PEOPLE_PANEL_TABS_WRAPPER_CLASS: "chat-list-header__tabs___", PEOPLE_PANEL_TAB_ACTIVE_CLASS: "chat-list-header__tab--active___", PEOPLE_PANEL_TAB_CONTENT_CLASS: "chat-tab-content___", PEOPLE_PANEL_MEMBER_CARD_CLASS: "member-card___", PEOPLE_PANEL_STATUS_ONLINE_CLASS: "online-status--online___", PEOPLE_PANEL_STATUS_IDLE_CLASS: "online-status--idle___", PEOPLE_PANEL_STATUS_OFFLINE_CLASS: "online-status--offline___", /* * Settings Panel */ SETTINGS_PANEL_CLASS: "settings-panel___", SETTINGS_PANEL_HEADER_CLASS: "settings-header___", SETTINGS_PANEL_HEADER_TITLE_CLASS: "settings-header__text-container___", SETTINGS_PANEL_CLOSE_BUTTON_CLASS: "settings-header__close-button___" }); function findByPartialClass(node, className, subSelector = "") { return node.querySelector(`[class*='${className}'] ${subSelector}`.trim()); } async function findDelayed(node, findElement, timeout) { return new Promise((resolve, reject) => { const initialElement = findElement(); if (initialElement) { resolve(initialElement); return; } const observer = new MutationObserver(() => { const element = findElement(); element && (clearTimeout(timeoutId), observer.disconnect(), resolve(element)); }), timeoutId = setTimeout(() => { observer.disconnect(), reject("Failed to find the element within the acceptable timeout."); }, timeout); observer.observe(node, { childList: true, subtree: true }); }); } async function findBySelectorDelayed(node, selector, timeout = 5e3) { return findDelayed(node, () => node.querySelector(selector), timeout); } let currentPlayerName, currentPlayerId; function getCurrentPlayerName() { if (currentPlayerName) return currentPlayerName; const websocketElement = document.getElementById("websocketConnectionData"); if (websocketElement) { const data = JSON.parse(websocketElement.textContent); return currentPlayerName = data.playername, data.playername; } const sidebarElement = findByPartialClass(document, "menu-value___"); if (sidebarElement) return currentPlayerName = sidebarElement.textContent, sidebarElement.textContent; const attackerElement = document.querySelector(".user-name.left"); if (attackerElement) return currentPlayerName = attackerElement.textContent, attackerElement.textContent; const chatSenderElement = document.querySelector( `[class*='${CHAT_SELECTORS.MESSAGE_BODY_WRAPPER_SELF_CLASS}'] [class*='${CHAT_SELECTORS.MESSAGE_SENDER_CLASS}']` ); if (chatSenderElement) { let name = chatSenderElement.textContent; return name = name.substring(0, name.length - 1), currentPlayerName = name, name; } return console.warn("[Playground] Failed to get the current player's name."), "unknown current player"; } function getCurrentPlayerId() { if (currentPlayerId) return currentPlayerId; const websocketElement = document.getElementById("websocketConnectionData"); if (websocketElement) { const data = JSON.parse(websocketElement.textContent); return currentPlayerId = parseInt(data.userID, 10), parseInt(data.userID, 10); } const userInputElement = document.getElementById("torn-user"); if (userInputElement) { const data = JSON.parse(userInputElement.getAttribute("value")); return currentPlayerId = data.id, data.id; } return console.warn("[Playground] Failed to get the current player's id."), null; } var MessageType = /* @__PURE__ */ ((MessageType2) => { MessageType2["WARNING"] = "WARNING"; MessageType2["SAFE"] = "SAFE"; MessageType2["DISABLED"] = "DISABLED"; MessageType2["ERROR"] = "ERROR"; return MessageType2; })(MessageType || {}); const SETTINGS = { [ "WARNING" /* WARNING */ ]: { title: "- WARNING -", message: (_, opponent) => `<a href="https://tcy.sh/p/${opponent.id}" target="_blank">${opponent.name}</a> is a known mugger.`, color: "#520a04", backgroundColor: "#f77a71", linkColor: "#fff" }, [ "SAFE" /* SAFE */ ]: { title: "- Probably Safe -", message: (_, opponent) => `<a href="https://tcy.sh/p/${opponent.id}" target="_blank">${opponent.name}</a> is not a known mugger.`, color: "#132c15", backgroundColor: "#54b358", linkColor: "#fff" }, [ "DISABLED" /* DISABLED */ ]: { title: "- Script Disabled -", message: `You are not a <a href="https://discord.gg/58HgDHbxhd" target="_blank">Sore Foot Club</a> member.`, color: "#3a006d", backgroundColor: "#c582ff", linkColor: "#fff" }, [ "ERROR" /* ERROR */ ]: { title: "- Error -", message: "Something went wrong. Unable to fetch the mugger status.", color: "#322500", backgroundColor: "#cb9800" } }; var RussianRouletteSubpage = /* @__PURE__ */ ((RussianRouletteSubpage2) => { RussianRouletteSubpage2["GAME"] = "GAME"; RussianRouletteSubpage2["OVERVIEW"] = "OVERVIEW"; RussianRouletteSubpage2["UNKNOWN"] = "UNKNOWN"; return RussianRouletteSubpage2; })(RussianRouletteSubpage || {}); var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)(); var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)(); let currentPage = RussianRouletteSubpage.UNKNOWN; const SELECTOR_APP_CONTAINER = "[class*='appContainer___']"; const SELECTOR_PLAYER_NAME = "[class*='titleWrap___'] [class*='title___']"; const SELECTOR_LINKS_CONTAINER = "[class*='linksContainer___']"; void onPageLoad(); function getSubpage() { const { hash } = window.location; switch (hash) { case "#/game": return RussianRouletteSubpage.GAME; case "#/": return RussianRouletteSubpage.OVERVIEW; default: return RussianRouletteSubpage.UNKNOWN; } } async function onPageLoad() { const rootElement = await findBySelectorDelayed(document, SELECTOR_APP_CONTAINER); if (!rootElement) { console.error("[RR Mugger Detect] Couldn't load due to the game container not being found."); return; } new MutationObserver(() => { const subpage = getSubpage(); onSubpage(subpage); }).observe(rootElement, { childList: true }); onSubpage(currentPage); addRequestIcon(); } function onSubpage(subpage) { if (currentPage === subpage) return; currentPage = subpage; onSubpageChange(subpage); } function onSubpageChange(subpage) { if (subpage === RussianRouletteSubpage.GAME) { findBySelectorDelayed(document, SELECTOR_PLAYER_NAME).then(handleGameLoading).catch((reason) => { console.error("[RR Mugger Detect] Couldn't properly load the script.", reason); }); } } function handleGameLoading() { const waitingTitleElement = document.querySelector(`${SELECTOR_PLAYER_NAME}[href='/#']`); if (!waitingTitleElement) { handleGameLoaded(); return; } new MutationObserver((_, observer) => { observer.disconnect(); handleGameLoaded(); }).observe(waitingTitleElement, { attributes: true, attributeFilter: ["href"] }); } function handleGameLoaded() { if (document.querySelector(".mugger-detect-message")) { return; } const playerElements = Array.from(document.querySelectorAll(SELECTOR_PLAYER_NAME)); const playerIds = playerElements.map((element) => { const idMatch = element.href.match(/XID=(\d+)/); if (!idMatch) return null; return { id: parseInt(idMatch[1], 10), name: element.textContent.trim() }; }).filter((result) => !!result); if (playerIds.length !== 2) return; const userId = getCurrentPlayerId(); const user = playerIds.find(({ id }) => id === userId); const opponent = playerIds.find(({ id }) => id !== userId); if (!user || !opponent) return; fetchMuggerDetection(user.id, opponent.id).then((detection) => showDetection(user, opponent, detection)).catch((reason) => { showDetection(user, opponent, null); console.error("[RR Mugger Detect] Failed to fetch the mugger status.", reason); }); } function removeDetectionMessages() { Array.from(document.querySelectorAll(".mugger-detect-message")).forEach((message) => message.remove()); } function showDetection(user, opponent, detection) { removeDetectionMessages(); const wrapperElement = document.createElement("div"); wrapperElement.classList.add("mugger-detect-message"); let type; if (!detection) type = MessageType.ERROR; else if (!detection.is_member) type = MessageType.DISABLED; else if (!detection.is_mugger) type = MessageType.SAFE; else type = MessageType.WARNING; const settings = SETTINGS[type]; wrapperElement.style.setProperty("--detect-color", settings.color); wrapperElement.style.setProperty("--detect-background-color", settings.backgroundColor); if (settings.linkColor) { wrapperElement.style.setProperty("--detect-link-color", settings.linkColor); } const titleElement = document.createElement("span"); titleElement.classList.add("title"); titleElement.textContent = settings.title; wrapperElement.appendChild(titleElement); wrapperElement.appendChild(document.createElement("br")); let message; if (typeof settings.message === "function") { message = settings.message(user, opponent); } else { message = settings.message; } const messageElement = document.createElement("span"); messageElement.classList.add("message"); messageElement.innerHTML = message; wrapperElement.appendChild(messageElement); const bodyElement = document.querySelector("[class*='headerWrap___'] [class*='bottomSection___']"); bodyElement.insertAdjacentElement("beforebegin", wrapperElement); } async function fetchMuggerDetection(userId, opponentId) { return new Promise((resolve, reject) => { _GM_xmlhttpRequest({ url: `https://api.no1irishstig.co.uk/sfc?player_id=${userId}&opponent_id=${opponentId}`, onload: (response) => { if (response.status === 200) { resolve(JSON.parse(response.responseText)); } else { reject(new Error(`Request failed with status: ${response.status} - ${response.statusText}`)); } }, onerror: (response) => reject(new Error(`Request failed with status: ${response.status} - ${response.statusText} or error: ${response.error}`)), ontimeout: () => reject(new Error("Request timed out")), onabort: () => reject(new Error("Request aborted")) }); }); } function addRequestIcon() { const linksContainer = document.querySelector(SELECTOR_LINKS_CONTAINER); if (!linksContainer) return; if (linksContainer.getElementsByClassName("mugger-detect-watcher").length > 0) return; const button = document.createElement("button"); button.classList.add("mugger-detect-watcher"); button.addEventListener("click", () => { requestWatcher().then(() => { button.classList.add("mugger-detect-watcher--success"); alert(`Your watch request has been sent to the Sore Foot Club, it will expire in 30 minutes!`); }).catch((reason) => { button.classList.add("mugger-detect-watcher--failure"); if ("rr" in reason) { alert(`Error Code: ${reason.rr.code} Message: ${reason.rr.message}`); } else { console.error("[RR Mugger Detect] Failed to request watcher.", reason); alert(`An unknown error has occurred - Please report this to SFC Staff`); } }).finally(() => { setTimeout(() => { button.classList.remove("mugger-detect-watcher--success", "mugger-detect-watcher--failure"); }, 1e4); }); }); const iconElement = document.createElement("svg"); button.appendChild(iconElement); iconElement.outerHTML = ` <!-- Source: https://www.svgrepo.com/svg/115756/siren --> <svg height="16px" width="16px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 255.5 255.5"> <g> <path d="m200.583,222.5h-6.333v-99.66c0-36.362-29.145-65.34-65.507-65.34h-0.32c-36.362,0-66.173,28.978-66.173,65.34v99.66h-6.667c-5.522,0-10.333,3.977-10.333,9.5v13c0,5.523 4.811,10.5 10.333,10.5h145c5.523,0 9.667-4.977 9.667-10.5v-13c0-5.523-4.145-9.5-9.667-9.5zm-72.16-141h-0.173v16h0.173c-14.248,0-25.84,12-25.84,26h-16c0-23 18.769-42 41.84-42z"/> <path d="m128.25,33c4.418,0 8-3.582 8-8v-17c0-4.418-3.582-8-8-8s-8,3.582-8,8v17c0,4.418 3.582,8 8,8z"/> <path d="m93.935,42.519c1.563,1.562 3.609,2.343 5.657,2.343 2.048,0 4.095-0.781 5.657-2.343 3.124-3.125 3.124-8.189 0-11.315l-12.02-12.021c-3.125-3.123-8.189-3.123-11.314,0-3.124,3.125-3.124,8.19 0,11.315l12.02,12.021z"/> <path d="m157.575,44.861c2.048,0 4.096-0.781 5.657-2.344l12.02-12.022c3.124-3.124 3.124-8.189-0.001-11.313-3.125-3.125-8.191-3.124-11.314,0.001l-12.02,12.021c-3.124,3.124-3.124,8.189 0.001,11.314 1.563,1.563 3.609,2.343 5.657,2.343z"/> </g> </svg> `; const titleElement = document.createElement("span"); titleElement.textContent = "Request Watcher"; button.appendChild(titleElement); linksContainer.insertAdjacentElement("afterbegin", button); } async function requestWatcher() { return new Promise((resolve, reject) => { const userId = getCurrentPlayerId(); const userName = getCurrentPlayerName(); _GM_xmlhttpRequest({ method: "POST", url: "https://api.no1irishstig.co.uk/request", data: JSON.stringify({ tornid: userId, username: userName, vendor: "Sore Foot Club", source: `SFC Script ${SCRIPT_VERSION}`, type: "watch" }), onload: (response) => { if (response.status === 200) { resolve(JSON.parse(response.responseText)); } else { const responseText = response.responseText.replace(/^"|"$/g, ""); reject({ rr: { code: response.status, message: responseText } }); } }, onerror: (response) => reject(new Error(`Request failed with status: ${response.status} - ${response.statusText} or error: ${response.error}`)), ontimeout: () => reject(new Error("Request timed out")), onabort: () => reject(new Error("Request aborted")) }); }); } })();