Sore Foot Club - RR Mugger Detection

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"))
      });
    });
  }

})();