[TORN] OC 2.0 Helper

Adds a list of members available for OC2.0, and adds a notifier to the sidebar if you are not in an OC.

当前为 2025-01-10 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        [TORN] OC 2.0 Helper
// @namespace   Violentmonkey Scripts
// @match       https://www.torn.com/*
// @version     3.2.3
// @author      callmericky [3299880] / whatdoesthespacebardo
// @description Adds a list of members available for OC2.0, and adds a notifier to the sidebar if you are not in an OC.
// @grant       GM_registerMenuCommand
// @grant       GM.setValue
// @grant       GM.getValue
// @license     GNU GPLv3
// ==/UserScript==

/*
 * All edits can be done from the dropdown menu from your userscript extension
 */

//IF DROPDOWN MENU DOESN'T WORK, MANUALLY ADD YOUR API KEY HERE
var APIKey = "";
const PDA_APIKey = "###PDA-APIKEY###"

//STOP CHANGING THINGS FROM HERE
let memberInfo = {};
let pageURL = $(location).attr("href");
let totalMembers = 0;
let availableMembers = 0;
let activeMembers = 0;
let userInfo = {};
let membersLoaded = false;
let crimeListUninitiated = []
let crimeListRecruiting = []
let crimeListPlanning = []
let crimeList = []
let myFactionInfo = null
let itemIDObj = {}

var OC2_timerID = null

var membersButtonShowText = (`⏵`) //⏵ ⏵
var membersButtonHideText = (`⏷`) //⏷ ⏷
if (isPDA()) {
  membersButtonShowText = (`▶`) //▶ ▶
  membersButtonHideText = (`▼`) //▼ ▼
}
const crimeButtonShowText = (`Show Crimes`)
const crimeButtonHideText = (`Hide Crimes`)

let _isWindowSmall = window.matchMedia("(max-width: 784px)")
let _isWindowTiny = window.matchMedia("(max-width: 386px)")

function getUserID() {
  let _profileLink = $(".settings-menu > .link > a")[0]
  let _matchregex = /profiles\.php.+XID=(\d+)/i
  let _userID = _matchregex.exec(_profileLink)
  userInfo.id = _userID[1]
  return _userID[1]
}

//GM_registerMenuCommand stuff
if (!isPDA()) {
  const menu_command_1 = GM_registerMenuCommand("Set API Key (Limited + faction access)", menuCommand)
}
async function getAPIKey() {
  if (isPDA()) {
    APIKey = PDA_APIKey
    return PDA_APIKey
  } else {
    return await GM.getValue("CMR_OC2_APIKey", null)
      .then(function(data) {
        APIKey = data
        return data
      })
  }
}

function menuCommand(event) {
  let _userKey = window.prompt("OC 2.0 participation and notifier\n- Requires a minimal API key with faction access\n\nSet API Key:", APIKey)
  if ((_userKey == null || _userKey == "")) {
    window.alert("OC 2.0 participation and notifier\nYou did not set an API Key - this script will not run")
  } else {
    GM.setValue("CMR_OC2_APIKey", _userKey)
  }
}

//boolean logic functions
function isPDA() {
  const PDATestRegex = !/^(###).+(###)$/.test(PDA_APIKey);
  return PDATestRegex;
}

function checkCrimesPage() {
  let pageURL = $(location).attr("href")
  return ((pageURL.search("step=your") >= 0) && (pageURL.search("tab=crimes") >= 0))
}

async function checkTravelFactionPage() {
  let pageURL = $(location).attr("href")
  if ( ($(body).attr("data-traveling") == "true") || ($(body).attr("data-traveling") == true) ) {
    console.log("checkTravelFactionPage: YOU ARE TRAVELING")
    if (!myFactionInfo) {
        await getAndAnalyzeAPIData()
    }
    if (pageURL.search("ID="+myFactionInfo.id) >= 0) {
      return true;
    }
  }
  return false;
}

//API call functions
async function getAndAnalyzeAPIData() {
  await $.getJSON(`https://api.torn.com/v2/faction/basic,crimes,members?key=${APIKey}&cat=available&offset=0&striptags=true&comment=OC2-helper`)
    .then(data => {
      checkMembersInCrimes(data)
      myFactionInfo = data.basic
    })

}

async function getItemNamesFromID(_arrayOfIds) {
  return await $.getJSON(`https://api.torn.com/torn/${_arrayOfIds.toString()}?selections=items&key=${APIKey}&comment=OC2-helper`)
}

//calculations and conversions
async function convertItemIDArrayToItems() {
  let _arrayOfIDs = []
  let _matchregex = /\<\#(\d+)\>/i
  for (var _key of Object.keys(itemIDObj)) {
    _arrayOfIDs.push(_key)
  }
  await getItemNamesFromID(_arrayOfIDs)
    .then( (_data) => {
     for (var _itemID of Object.keys(_data.items)) {
       itemIDObj[_itemID].name = _data.items[_itemID].name
     }
  })
  for (i = 0; i < $(".OC2-tableCrimeMemberItem:has(*)").length; i++) {
    let _oldTitle = $(".OC2-tableCrimeMemberItem:has(*)").eq(i).attr("title")
    let _regexResult = _matchregex.exec($(".OC2-tableCrimeMemberItem:has(*)").eq(i).attr("title"))
    let _newTitle = _oldTitle.replace(_matchregex, `${itemIDObj[_regexResult[1]].name} <$1>`)
    $(".OC2-tableCrimeMemberItem:has(*)").eq(i).attr("title", _newTitle)
  }
}

function timestampDiff(laterTimestamp) {
  var currentTimestamp = Math.floor(Date.now()/1000)
  var _returnString = ""
  var timeDiff = 0
  var _highlightClass = ""

  if (laterTimestamp > currentTimestamp) {
    timeDiff = laterTimestamp - currentTimestamp
  }

  if (timeDiff < 43200) { //12 hours = 12 * 60 * 60 = 43200
    _highlightClass = "OC2-highlightText"
  }

  let _d = timeDiff < 86400 ? 0 : Math.floor(timeDiff/86400)
  let _h = timeDiff < 3600 ? 0 : Math.floor(timeDiff/3600) - _d*24 //24h in 1d
  let _m = timeDiff < 60 ? 0 : Math.floor(timeDiff/60) - _h*60 - _d*1440 //60m in 1h, 1440m in 1d
  let _s = timeDiff - _m*60 - _h*3600 - _d*86400 //60s in 1m, 3600s in 1h, 86400s in 1d

  _returnString += `<span class="${_highlightClass}">${_d.toString().padStart(2,'0')}:${_h.toString().padStart(2,'0')}:${_m.toString().padStart(2,'0')}:${_s.toString().padStart(2,'0')}</span>`

  return _returnString
}

const timerTick = () => {
  let _timeList = $("span.OC2-countdown")
  for (i = 0; i < _timeList.length; i++) {
    $(_timeList[i]).html(timestampDiff(parseInt($(_timeList[i]).attr("data-countdown"))))
  }
  OC2_timerID = setTimeout(timerTick, 1000)
  $(".OC2-highlightText").css({
    "color": "rgb(252, 196, 25)",
    "font-weight": "bold"
  })
  if ($("body").css("background-color") == "rgb(204, 204, 204)") {
    $(".OC2-highlightText").css({
      "color": "rgb(230, 119, 0)",
      "font-weight": "bold"
    })
  }
}

//the nitty gritty functions
function checkMembersInCrimes(_data) {
  //put all member ids into a list
  for (i = 0; i < (_data.members).length; i++) {
    if (_data.members[i].status.state == "Fallen") {
      //skip fallen members
    } else if (_data.members[i].position == "Recruit") {
      //skip recruits since they can't join OC
    } else {
      memberInfo[_data.members[i].id] = {
        "name": _data.members[i].name,
        "last_action": _data.members[i].last_action.relative,
        "statusDesc": _data.members[i].status.description,
        "status": _data.members[i].status.state
      }
    }
  }
  totalMembers = (_data.members).length;
  activeMembers = 0 //if this doesn't reset, hashchange will cause the following part to re-fire and get activemembers count wrong.

  //go through crime list
  for (i = 0; i < (_data.crimes).length; i++) {
    //sort members into objects
    //if crime is not initiated, it will be null for planning_at. No point looking for members because there won't be any.
    if (_data.crimes[i].planning_at) {
      //console.log(`checkMembersInCrimes(${_members}, ${_crimes})`, `_crimes.crimes[${i}] is initiated at ${_crimes.crimes[i].planning_at}`)
      for (j=0; j<(_data.crimes[i].slots).length; j++) {
        if (_data.crimes[i].slots[j].user_id) {
          //console.log(`_crimes.crimes[${i}].slots[${j}].user_id`, _crimes.crimes[i].slots[j].user_id)
          memberInfo[_data.crimes[i].slots[j].user_id].crimeInfo = {
            "crimeName": _data.crimes[i].name,
            "crimeDifficulty": _data.crimes[i].difficulty,
            "crimeId": _data.crimes[i].id,
            "crimePosition": _data.crimes[i].slots[j].position,
            "crimeSuccess": _data.crimes[i].slots[j].success_chance
          }
          activeMembers = activeMembers + 1;
          if ((_data.crimes[i].slots[j].user_id) == getUserID()) {
            userInfo = memberInfo[_data.crimes[i].slots[j].user_id]
            //console.log("userInfo", userInfo)
          }
        }
      }
    }
    //sort crimes into arrays
    //crimes in recruiting include both crimes with members (has planning_at and with no members (don't have planning_at)
    if (_data.crimes[i].status == "Recruiting") {
      //get crimes with no members
      if (_data.crimes[i].planning_at == null) {
        crimeListUninitiated.push(_data.crimes[i])
      } else {
        crimeListRecruiting.push(_data.crimes[i])
      }
    }
    //crimes filled with members move to status = planning
    if (_data.crimes[i].status == "Planning") {
      crimeListPlanning.push(_data.crimes[i])
    }
  }

  availableMembers = totalMembers - activeMembers;
}

function putMemberInfoIntoTable() {
  //console.log("putMemberInfoIntoTable() fired")
  //fix for tornPDA, idk why but checking the li.tablecell works but checking the ul.table doesn't
  if ($(".OC2-memberTable .OC2-tableCell")[0]) {
    //console.log("exit putMemberInfoIntoTable() early due to OC2-tableCell existing")
    return
  }
  for (var _key of Object.keys(memberInfo)) {
    let _outputHTML = ""
    let _memberHighlight = "OC2-memberAvailable"
    if (_key == userInfo.id) {
      _memberHighlight += " OC2-userIndicator"
    }
    _outputHTML = (`<li class="table-cell ${_memberHighlight}">
        <div class="OC2-tableCell OC2-tableMember">${memberInfo[_key].name} <a href="https://www.torn.com/profiles.php?XID=${_key}">[${_key}]</a></div>
        <div class="OC2-tableCell OC2-tableStatus">${styleMemberStatus(memberInfo[_key].status,memberInfo[_key].statusDesc)}</div>
      </li>`)
    //put all the members currently in crimes below
    if (!(memberInfo[_key].crimeInfo)) {
      $(".OC2-memberTable li.OC2-titleLiAvailableMembers").after(_outputHTML)
    }
  }
  $(".OC2-memberTableFooter").text(`${availableMembers} / ${totalMembers} members available (${activeMembers} in an OC)`)

  putCrimeInfoIntoTable(crimeListUninitiated, $(".OC2-memberTable li.OC2-titleLiUninitiated").eq(0))
  putCrimeInfoIntoTable(crimeListRecruiting, $(".OC2-memberTable li.OC2-titleLiRecruiting").eq(0))
  putCrimeInfoIntoTable(crimeListPlanning, $(".OC2-memberTable li.OC2-titleLiPlanning").eq(0))

  convertItemIDArrayToItems()
  styleTable()
  timerTick()
}

function putCrimeInfoIntoTable(_crimeArray, _afterElm) {
  //console.log(`putCrimeInfoIntoTable with:`, _crimeArray)
  let _countdownText = ""
  let _countdownToTimestamp = ""
  let _countdownMouseover = ""
  let _memberOutputHTML = ""
  let _userIndicatorClass = ""
  let _userIndicatorCrimeClass = ""
  if (_crimeArray.length > 0) {
    for (i = 0; i < _crimeArray.length; i++) {
      //crimes in recruiting include both crimes with members (has planning_at) and with no members (don't have planning_at)
      if (_crimeArray[i].status == "Recruiting") {
        //get crimes with no members
        if (_crimeArray[i].planning_at == null) {
          _countdownText = "Expires: "
          _countdownToTimestamp = _crimeArray[i].expired_at
          _countdownMouseover = "Time until this crime is no longer be available"
        } else {
          _countdownText = "Join in:"
          _countdownToTimestamp = _crimeArray[i].ready_at
          _countdownMouseover = "Time until this crime needs a new member to join to continue planning"
        }
      }
      //crimes filled with members move to status = planning
      if (_crimeArray[i].status == "Planning") {
        _countdownText = "Ready in:"
        _countdownToTimestamp = _crimeArray[i].ready_at
        _countdownMouseover = "Time until this crime is ready to start"
      }

      _memberOutputHTML = ""
      _userIndicatorCrimeClass = ""
      let _memberCount = 0
      //count number of slots filled
      for (j = 0; j < (_crimeArray[i].slots).length; j++) {
        let _crimeSlotMemberName = `<span class="OC2-textGray">&nbsp;&nbsp;&nbsp;&nbsp;N/A</span>`
        let _crimeSlotMemberID = ""
        let _crimeSlotMemberStatus = ""
        let _crimeSlotPosition = ""
        let _crimeItem = ""
        let _crimeItemMouseover = ""
        let _crimeItemIcon = (`&#128736;`) //🛠 &#128736;
        if (isPDA()) {
          _crimeItemIcon = (`&#9874;`) // ⚒ $#9874;
        }
        let _crimeSuccess = ""
        let _crimeSuccessWrapper = ""
        _userIndicatorClass = ""
        if (_crimeArray[i].slots[j].item_requirement) {
          if(!(_crimeArray[i].slots[j].item_requirement.id in itemIDObj)) {
              itemIDObj[_crimeArray[i].slots[j].item_requirement.id] = {
                "name": ""
              };
              //console.log(`added ${_crimeArray[i].slots[j].item_requirement.id} to itemIDObj`, itemIDObj);
          }
          _crimeItemMouseover = (`Required item: <#${_crimeArray[i].slots[j].item_requirement.id}>`)
          if (_crimeArray[i].slots[j].item_requirement.is_reusable) {
            _crimeItemMouseover += (` (reusable)`)
            _crimeItemIcon += (`<span style="vertical-align:top">∞</span>`)
          }
          if (_crimeArray[i].slots[j].item_requirement.is_available) {
             _crimeItemMouseover += (`; Item is owned by member`)
          }
          //_crimeItem = (`<a href="https://www.torn.com/page.php?sid=ItemMarket#/market/view=search&itemID=${_crimeArray[i].slots[j].item_requirement.id}" target="_new"><span title="${_crimeItemMouseover}" class="OC2-itemHave${_crimeArray[i].slots[j].item_requirement.is_available}">${_crimeItemIcon}</span></a>`)
          _crimeItem = (`<span class="OC2-itemHave${_crimeArray[i].slots[j].item_requirement.is_available}">${_crimeItemIcon}</span>`)
        }
        if (_crimeArray[i].slots[j].user_id) {
          //console.log(memberInfo[_crimeArray[i].slots[j].user_id])
          if (_crimeArray[i].slots[j].user_id == userInfo.id) {
            _userIndicatorClass = "OC2-userIndicator"
            _userIndicatorCrimeClass = "OC2-userIndicator"
          }
          _memberCount = _memberCount + 1
          _crimeSlotMemberID = ` <a href="https://www.torn.com/profiles.php?XID=${_crimeArray[i].slots[j].user_id}">[${_crimeArray[i].slots[j].user_id}]</a>`
          _crimeSlotMemberName = memberInfo[_crimeArray[i].slots[j].user_id].name
          _crimeSlotMemberStatus = styleMemberStatus(memberInfo[_crimeArray[i].slots[j].user_id].status, memberInfo[_crimeArray[i].slots[j].user_id].statusDesc)
          if (_crimeArray[i].slots[j].success_chance > 90) {
            _crimeSuccessWrapper = "OC2-highSuccess"
          } else if (_crimeArray[i].slots[j].success_chance > 50) {
            _crimeSuccessWrapper = "OC2-midSuccess"
          } else {
            _crimeSuccessWrapper = "OC2-lowSuccess"
          }
          _crimeSuccess = (`<span class="${_crimeSuccessWrapper}">${_crimeArray[i].slots[j].success_chance}</span>`)
        } else {
          _crimeSuccess = (`<span class="OC2-textGray">-</span>`)
        }
        _memberOutputHTML += (`<li class="table-cell OC2-crimeMemberLi OC2-crimeID_${_crimeArray[i].id} ${_userIndicatorClass}">
          <div class="OC2-tableCell OC2-tableCrimeMemberSuccess">${_crimeSuccess}</div>
          <div class="OC2-tableCell OC2-tableCrimeMemberItem" title="${_crimeItemMouseover}" >${_crimeItem}</div>
          <div class="OC2-tableCell OC2-hideSmall OC2-tableCrimePosition">${_crimeArray[i].slots[j].position}</div>
          <div class="OC2-tableCell OC2-tableCrimeMemberName">${_crimeSlotMemberName}${_crimeSlotMemberID}</div>
          <div class="OC2-tableCell OC2-tableCrimeMemberStatus">${_crimeSlotMemberStatus}</div>
        </li>`)
      }
      let _outputHTML = (`<li class="table-cell OC2-crimeLi OC2-crimeID_${_crimeArray[i].id} ${_userIndicatorCrimeClass}">
        <div class="OC2-tableCell OC2-tableCrimeMemberCount OC2-crimeID_${_crimeArray[i].id}">${_memberCount} / ${(_crimeArray[i].slots).length} <span class="hideMembersButton">${membersButtonShowText}</span></div>
        <div class="OC2-tableCell OC2-tableCrime"><a href="https://www.torn.com/factions.php?step=your&type=12#/tab=crimes&crimeId=${_crimeArray[i].id}">Lv${_crimeArray[i].difficulty} ${_crimeArray[i].name}</a></div>
        <div class="OC2-tableCell OC2-tableCountdown OC2-crimeID_${_crimeArray[i].id}" title="${_countdownMouseover}"><span class="OC2-countdownText OC2-hideSmall">${_countdownText}</span> <span class="OC2-countdown" data-countdown="${_countdownToTimestamp}">${_countdownToTimestamp}</span></div>
      </li>`)
      _afterElm.after(_outputHTML)
      $("li.OC2-crimeID_"+_crimeArray[i].id).after(_memberOutputHTML)
      $("div.OC2-crimeID_"+_crimeArray[i].id).off().on("click", (event) => {
        toggleMemberView(((event.currentTarget.attributes.class.value).split("OC2-crimeID_"))[1])
        if ($(event.currentTarget).parent(".OC2-crimeLi").hasClass("OC2-crimeLiActive")) {
          $(event.currentTarget).parent(".OC2-crimeLi.OC2-crimeLiActive").removeClass("OC2-crimeLiActive")
        } else {
          $(event.currentTarget).parent(".OC2-crimeLi").not(".OC2-crimeLiActive").addClass("OC2-crimeLiActive")
        }
        styleCrimeLiActive()
      }).css({"cursor": "pointer"})
    }
  } else {
    _afterElm.after(`<li class="table-cell OC2-crimeLi"><div class="OC2-tableCell OC2-tableCrime">None</div></li>`)
  }
}

//templating functions
async function generateInsertHTML() {
  //console.log("generateInsertHTML fired")
  let _insertHTML = (`
  <div class="category-wrap OC2-memberViewer m-top10">
    <div class="title-black top-round t-overflow">OC 2.0 Overview <span class="hideCrimesButton">${crimeButtonShowText}</span></div>
    <div class="cont-gray OC2-memberTable"><ul class="table-body">
      <li class="table-cell OC2-availableMembers OC2-titleLiAvailableMembers"><div class="OC2-titleCell">Members not in an OC</div></li>
      <li class="table-cell OC2-crimeLi OC2-titleLiUninitiated"><div class="OC2-titleCell">Uninitiated Crimes</div></li>
      <li class="table-cell OC2-crimeLi OC2-titleLiRecruiting"><div class="OC2-titleCell">Recruiting Crimes</div></li>
      <li class="table-cell OC2-crimeLi OC2-titleLiPlanning"><div class="OC2-titleCell">Full Crimes</div></li>
    </ul></div>
    <div class="OC2-memberTableFooter"></div>
  </div>`)
  //what to do in normal crimes2.0 page
  if (checkCrimesPage()) {
    $("div#faction-crimes").before(_insertHTML)
    putMemberInfoIntoTable()
    styleTable()
    $("span.hideCrimesButton").off().on("click", event => {
      toggleCrimeView()
    })
    $(".OC2-crimeMemberLi").hide();
    $(".OC2-crimeLi").hide();
  } else //this "else" is important to like the two if statements. Without it, it won't load on the faction -> crimes OC page because the second if statement takes precidence due to await
  //what to do if traveling AND on faction page
  if (await checkTravelFactionPage()) {
    waitForElm('div#react-root').then((elm) => {
      $(elm).before(_insertHTML)
      putMemberInfoIntoTable()
      styleTable()
      $("span.hideCrimesButton").off().on("click", event => {
        toggleCrimeView()
      })
      $(".OC2-crimeMemberLi").hide();
      $(".OC2-crimeLi").hide();
    })
  }
}

function insertOCNotifier() {
  let _userNotice = (`<a href="https://www.torn.com/factions.php?step=your#/tab=crimes"><span class="OC2-redtext">No active OC.</span></a>`)
  if (userInfo.crimeInfo) {
    _userNotice = (`<span class="OC2-normaltext"><a href="https://www.torn.com/factions.php?step=your&type=5#/tab=crimes&crimeId=${userInfo.crimeInfo.crimeId}">${userInfo.crimeInfo.crimePosition} of ${userInfo.crimeInfo.crimeName} (Lv ${userInfo.crimeInfo.crimeDifficulty})</a></span>`)
  }
  let _insertHTML = (`<div class="OC2-sidebarNotice"><a href="https://www.torn.com/factions.php?step=your#/tab=crimes"><span style="font-weight: bold">OC 2.0:</span></a>
    ${_userNotice}
  </div>`)
  $('div[class^="sidebar_"] div[class^="user-information_"] div[class^="toggle-block_"] div[class^="toggle-content_"] div[class^="content_"]').append(_insertHTML)
  styleOCNotifier()
}

//on click functions
function toggleCrimeView() {
  if ($(".hideCrimesButton").hasClass("text-hide")) {
    $(".hideCrimesButton").html(crimeButtonShowText)
    $(".hideCrimesButton").removeClass("text-hide")
    $(".OC2-crimeLi").slideUp()
  } else {
    $(".hideCrimesButton").html(crimeButtonHideText)
    $(".hideCrimesButton").addClass("text-hide")
    $(".OC2-crimeLi").slideDown()
  }

  $(".hideMembersButton").html(membersButtonShowText)
  $(".hideMembersButton").removeClass("text-hide")
  $(".OC2-crimeMemberLi").hide()
  $(".OC2-crimeLi.OC2-crimeLiActive").removeClass("OC2-crimeLiActive")
  styleCrimeLiActive()
}

function toggleMemberView(_crimeID) {
  if ($(".OC2-crimeID_"+_crimeID+" .hideMembersButton").hasClass("text-hide")) {
    $(".OC2-crimeID_"+_crimeID+" .hideMembersButton").html(membersButtonShowText)
    $(".OC2-crimeID_"+_crimeID+" .hideMembersButton").removeClass("text-hide")
    $(".OC2-crimeMemberLi.OC2-crimeID_"+_crimeID).slideUp()
  } else {
    $(".OC2-crimeID_"+_crimeID+" .hideMembersButton").html(membersButtonHideText)
    $(".OC2-crimeID_"+_crimeID+" .hideMembersButton").addClass("text-hide")
    $(".OC2-crimeMemberLi.OC2-crimeID_"+_crimeID).slideDown()
  }
}

//prettifying functions
function styleCrimeLiActive() {
  $(".OC2-crimeLi.OC2-crimeLiActive").not(".OC2-userIndicator").css({
    "background-color": "rgba(0,0,0,0.3)"
  })
  $(".OC2-crimeLi").not(".OC2-crimeLiActive").not(".OC2-userIndicator").css({
    "background-color": "transparent"
  })
  if ($("body").css("background-color") == "rgb(204, 204, 204)") {
    $(".OC2-crimeLi.OC2-crimeLiActive").not(".OC2-userIndicator").css({
      "background-color": "rgba(150,150,150,0.2)"
    })
  }
}

function styleMemberStatus(_statusState, _statusDesc) {
  let _lowerCaseStatusText = _statusState.toLowerCase()
  let _output = (``)
  if (_lowerCaseStatusText == "okay") {
    _output = (`<span title="${_statusDesc}" class="OC2-statusText okay">${_statusState}</span><span title="${_statusDesc}" class="OC2-statusText OC2-hideSmall okay">${_statusDesc}</span>`)
  }
  if (_lowerCaseStatusText.search("traveling") >= 0) {
    _output = (`<span title="${_statusDesc}" class="OC2-statusText travel">${_statusState}</span><span title="${_statusDesc}" class="OC2-statusText OC2-hideSmall travel">${_statusDesc}</span>`)
  }
  if (_lowerCaseStatusText.search("abroad") >= 0) {
    _output = (`<span title="${_statusDesc}" class="OC2-statusText travel">${_statusState}</span><span title="${_statusDesc}" class="OC2-statusText OC2-hideSmall travel">${_statusDesc}</span>`)
  }
  if (_lowerCaseStatusText.search("hospital") >= 0) {
    _output = (`<span title="${_statusDesc}" class="OC2-statusText hospital">${_statusState}</span><span title="${_statusDesc}" class="OC2-statusText OC2-hideSmall hospital">${_statusDesc}</span>`)
  }
  if (_lowerCaseStatusText.search("jail") >= 0) {
    _output = (`<span title="${_statusDesc}" class="OC2-statusText jail">${_statusState}</span><span title="${_statusDesc}" class="OC2-statusText OC2-hideSmall jail">${_statusDesc}</span>`)
  }
  return _output
}

function styleTable() {
  $(".OC2-memberTable ul.table-body").css({
    "display": "flex",
    "flex-direction": "column"
  })
  $(".OC2-memberTable a").css({
    "color": "rgb(116, 192, 252)",
    "text-decoration": "none"
  })
  $(".OC2-memberTable a").hover(
    function() {
      $(this).css({
         "text-decoration": "underline"
      })
    },
    function() {
      $(this).css({
        "text-decoration": "none"
      })
    })
  $(".OC2-memberTable div.OC2-tableCrimeMemberName a").css({
    "color": "inherit",
  })
  $(".OC2-memberTable div.OC2-tableMember a").css({
    "color": "inherit",
  })
  $(".OC2-memberTable li.table-cell").css({
    "display": "flex",
    "flex-direction": "row"
  })
  $(".OC2-memberTable li.OC2-memberAvailable").css({
    "font-weight": "normal",
  })
  $(".OC2-memberTable .OC2-textGray").css({
    "color": "rgb(153, 153, 153)",
  })
  $(".OC2-memberTable div").css({
    "font-size": "11px",
    "line-height": "12px",
    "padding": "5px 0"
  })
  $(".OC2-memberTable div.OC2-tableCell").css({
    "display": "inline"
  })
  $(".OC2-memberTable div.OC2-titleCell").css({
    "display": "inline",
    "width": "100%",
    "font-family": "Fjalla One",
    "padding-left": "10px",
    "border-top": "1px solid rgb(34, 34, 34)",
  })
  $(".OC2-memberTable div.OC2-tableMember").css({
    "width": "320px",
  })
  $(".OC2-memberTable div.OC2-tableStatus").css({
    "width": "auto",
    "margin-right": "10px",
  })
  $(".OC2-statusText.okay").css({
    "color": "rgb(130, 201, 30)"
  })
  $(".OC2-statusText.travel").css({
    "color": "rgb(59, 201, 219)"
  })
  $(".OC2-statusText.hospital").css({
    "color": "rgb(255, 135, 135)"
  })
  $(".OC2-statusText.jail").css({
    "color": "rgb(255, 135, 135)"
  })
  $(".OC2-memberTable div.OC2-tableStatus img").css({
    "height": "11px"
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberCount").css({
    "width": "50px",
  })
  $(".OC2-memberTable div.OC2-tableCrimePosition").css({
    "width": "70px",
    "font-size": "10px"
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberName").css({
    "width": "210px",
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberSuccess").css({
    "width": "18px",
    "text-align": "center"
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberItem").css({
    "width": "30px",
    "text-align": "center"
  })
  $(".OC2-memberTable .OC2-tableCrimeMemberSuccess .OC2-highSuccess").css({
    "color": "rgb(148, 216, 45)"
  })
  $(".OC2-memberTable .OC2-tableCrimeMemberSuccess .OC2-midSuccess").css({
    "color": "rgb(252, 196, 25)"
  })
  $(".OC2-memberTable .OC2-tableCrimeMemberSuccess .OC2-lowSuccess").css({
    "color": "rgb(255, 121, 76)"
  })
  $(".OC2-memberTable .OC2-itemHavetrue").css({
    "color": "rgb(148, 216, 45)"
  })
  $(".OC2-memberTable .OC2-itemHavefalse").css({
    "color": "rgb(255, 121, 76)"
  })
  $(".OC2-memberTable .OC2-itemReusable").css({
    "background-color": "#0000FF"
  })
  $(".OC2-memberTable div.OC2-tableCrime").css({
    "width": "270px",
  })
  $(".OC2-memberTable div.OC2-tableCountdown").css({
    "width": "auto"
  })
  $(".OC2-memberTableFooter").css({
    "border-radius": "0 0 5px 5px",
    "background-color": "rgb(51, 51, 51)",
    "padding": "5px 5px 5px 10px",
    "text-align": "center"
  })
  $(".hideCrimesButton").css({
    "position": "absolute",
    "right": "10px",
    "cursor": "pointer"
  })
  $(".OC2-memberTable div.OC2-titleCell").not(".OC2-titleLiAvailableMembers .OC2-titleCell").css({
    "margin-top": "5px"
  })
  $(".OC2-memberTable li.OC2-memberAvailable").css({
    "padding-left": "20px"
  })
  $(".OC2-memberTable li.OC2-crimeMemberLi").css({
    "padding-left": "25px",
    "background-color": "rgba(0,0,0,0.2)"
  })
  $(".OC2-memberTable li.OC2-crimeLi").not(".OC2-memberTable li[class*='OC2-titleLi']").css({
    "padding-left": "20px"
  })
  $(".OC2-crimeLi").prev(".OC2-crimeMemberLi").css({
    "border-radius": "0 0 15px 15px"
  })
  if (($(".OC2-crimeMemberLi").last().next()).length < 1) {
    $(".OC2-crimeMemberLi").last().css({
      "border-radius": "0 0 15px 15px"
    })
  }
  $(".OC2-userIndicator").css({
    "background-color": "rgb(63, 68, 45)"
  })
  //light mode styling
  if ($("body").css("background-color") == "rgb(204, 204, 204)") {
    $(".OC2-memberTable a").css({
      "color": "#006699",
    })
    $(".OC2-memberTable div.OC2-tableCrimeMemberName a").css({
      "color": "inherit",
    })
    $(".OC2-memberTable div.OC2-tableMember a").css({
      "color": "inherit",
    })
    $(".OC2-memberTableFooter").css({
      "background-color": "rgb(242, 242, 242)"
    })
    $(".OC2-memberTable .OC2-tableCrimeMemberSuccess .OC2-highSuccess").css({
      "color": "rgb(92, 148, 13)"
    })
    $(".OC2-memberTable .OC2-tableCrimeMemberSuccess .OC2-midSuccess").css({
      "color": "rgb(230, 119, 0)"
    })
    $(".OC2-memberTable .OC2-tableCrimeMemberSuccess .OC2-lowSuccess").css({
      "color": "rgb(255, 121, 76)"
    })
    $(".OC2-memberTable .OC2-itemHavetrue").css({
      "color": "rgb(92, 148, 13)"
    })
    $(".OC2-memberTable .OC2-itemHavefalse").css({
      "color": "rgb(255, 121, 76)"
    })
    $(".OC2-statusText.okay").css({
    "color": "rgb(103, 140, 0)"
    })
    $(".OC2-statusText.travel").css({
      "color": "rgb(12, 133, 153)"
    })
    $(".OC2-statusText.hospital").css({
      "color": "rgb(224, 49, 49)"
    })
    $(".OC2-statusText.jail").css({
      "color": "rgb(255, 121, 76)"
    })
    $(".OC2-memberTable li.OC2-crimeMemberLi").css({
      "background-color": "rgba(150,150,150,0.1)"
    })
    $(".OC2-userIndicator").css({
      "background-color": "rgb(238, 241, 228)"
    })
  }
  if (_isWindowTiny.matches) {
    styleTableTinyScreen()
  } else if (_isWindowSmall.matches) {
    styleTableSmallScreen()
  } else {
    styleTableBigScreen()
  }
}

function styleTableSmallScreen() {
  //console.log("Smallscreen!")
  //total row length: 386
  //margins: 20px <item> 10px
  //OC2-tableMember 260px
  $(".OC2-hideSmall").css({
    "display": "none"
  })
  $(".OC2-memberTable div.OC2-tableStatus").css({
    "width": "96px",
    "text-align": "left"
  })
  $(".OC2-memberTable div.OC2-tableCrime").css({
    "width": "210px",
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberName").css({
    "width": "210px",
  })
  $(".OC2-statusText").not(".OC2-hideSmall").css({
    "display": ""
  })
}

function styleTableTinyScreen() {
  //console.log("Tinyscreen!")
  $(".OC2-hideSmall").css({
    "display": "none"
  })
  $(".OC2-tableCrimeMemberCount").css({
    "width": "40px"
  })
  $(".OC2-memberTable div.OC2-tableStatus").css({
    "width": "90px",
    "text-align": "left"
  })
  $(".OC2-memberTable div.OC2-tableCrime").css({
    "width": "190px",
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberName").css({
    "width": "190px",
  })
  $(".OC2-statusText").not(".OC2-hideSmall").css({
    "display": ""
  })
  $(".OC2-memberTable li.OC2-crimeMemberLi").css({
    "padding-left": "18px",
  })
    $(".OC2-memberTable li.OC2-memberAvailable").css({
    "padding-left": "12px"
  })
  $(".OC2-memberTable li.OC2-crimeLi").not(".OC2-memberTable li[class*='OC2-titleLi']").css({
    "padding-left": "12px"
  })
}

function styleTableBigScreen() {
  $(".OC2-hideSmall").css({
    "display": ""
  })
  $(".OC2-statusText").not(".OC2-hideSmall").css({
    "display": "none"
  })
}

function styleOCNotifier() {
  $(".OC2-sidebarNotice a").css({
    "text-decoration": "none",
    "color": "inherit"
  })
  $(".OC2-sidebarNotice .OC2-redtext").css({
    "text-decoration": "none",
    "color": "rgb(255, 121, 76)"
  })
}


//main functions
/* if page is the crimes 2.0 page
 *  -> if memberViewer table does NOT exist, get data and fill table
 *  -> otherwise, show the table
 * -> otherwise, hide the table
 */
async function hashChangeFunction() {
  if (checkCrimesPage()) {
    if (!$(".OC2-memberViewer")[0]) {
      if (!myFactionInfo) {
        await getAndAnalyzeAPIData()
      }
      generateInsertHTML();
    } else {
      $(".OC2-memberViewer").show()
    }
  } else {
    if ($(".OC2-memberViewer")[0]) {
      $(".OC2-memberViewer").hide();
    }
  }
}

async function runOnceFunction() {
  //exit if API key doesn't exist
  await getAPIKey()
    .then(function(data) {
    //console.log("APIKey runOnceFunction", APIKey)
    if (APIKey == null || APIKey == "") {
      return;
    }
  })
  //sidebar notifier, but not if the sidebar doesn't exist
  if (($("div[class*='sidebar_'][class*='desktop_']")[0]) && (!$isPDA())) {
    await getAndAnalyzeAPIData()
    insertOCNotifier()
  }
  //insert member overview
  if (checkCrimesPage() || await checkTravelFactionPage()) {
    if (!$(".OC2-memberViewer .OC2-tableCell")[0]) {
      if (!myFactionInfo) {
        await getAndAnalyzeAPIData()
      }
      generateInsertHTML()
    } else {
      $(".OC2-memberViewer").show()
    }
  }
}

//taken from stackoverflow https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists because mutation observer confuses me
//needed because the faction page info only loads after the document is ready
function waitForElm(selector) {
    return new Promise(resolve => {
        if (document.querySelector(selector)) {
            return resolve(document.querySelector(selector));
        }
        const observer = new MutationObserver(mutations => {
            if (document.querySelector(selector)) {
                observer.disconnect();
                resolve(document.querySelector(selector));
            }
        });
        // If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    });
}

runOnceFunction()
$(window).on('hashchange', hashChangeFunction);

_isWindowSmall.addEventListener("change", function() {
  styleTable()
});
_isWindowTiny.addEventListener("change", function() {
  styleTable()
});