[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 提交的版本,檢視 最新版本

// ==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()
});