Sore Foot Club - RR Mugger Detection

Detect whether or not your RR opponent is a known mugger.

// ==UserScript==
// @name         Sore Foot Club - RR Mugger Detection
// @namespace    dekleinekobini.rr-mugger-detect
// @version      1.0.4
// @author       DeKleineKobini [2114440]
// @description  Detect whether or not your RR opponent is a known mugger.
// @icon         https://i.ibb.co/6vFksXW/sfc-logo.jpg
// @match        https://www.torn.com/page.php?sid=russianRoulette*
// @connect      api.no1irishstig.co.uk
// @grant        GM_addStyle
// @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:12px;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} ");
 
(function () {
  'use strict';
 
  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 currentPlayerId;
  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_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___']";
  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);
    new MutationObserver(() => {
      const subpage = getSubpage();
      onSubpage(subpage);
    }).observe(rootElement, { childList: true });
    onSubpage(currentPage);
  }
  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"))
      });
    });
  }
 
})();