[TORN] OC 2.0 Helper

Gives an overview of OC 2.0, showing members not in crimes, members in each crime, and if there are issues with any crimes. Visible when flying.

Fra 05.03.2025. Se den seneste versjonen.

// ==UserScript==
// @name        [TORN] OC 2.0 Helper
// @namespace   Violentmonkey Scripts
// @match       https://www.torn.com/*
// @version     5.2.0
// @author      callmericky [3299880] / whatdoesthespacebardo
// @description Gives an overview of OC 2.0, showing members not in crimes, members in each crime, and if there are issues with any crimes. Visible when flying.
// @require     http://code.jquery.com/jquery-3.6.0.min.js
// @grant       GM_registerMenuCommand
// @grant       GM.setValue
// @grant       GM.getValue
// @license     GNU GPLv3
// ==/UserScript==

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

/*
 * STOP CHANGING THINGS FROM HERE
 */

//fix for tampermonkey
var $ = window.jQuery;

let memberInfo = {};
let pageURL = $(location).attr("href");
let totalMembers = 0;
let availableMembers = 0;
let activeMembers = 0;
let userInfo = {};
let crimeListUninitiated = []
let crimeListRecruiting = []
let crimeListPlanning = []
let crimeIDListUninitiated = []
let crimeIDListRecruiting = []
let crimeIDListFull = []
let crimeList = []
let availMemberList = []
let myAPIData = null
let itemIDObj = {}
let containerMaxWidth = "784px"
let containerBigMaxWidth = "976px"
let userSettings = {}
let displayIgnoreList = [ ]
var alreadyLoading = false
let skippedMemberCount = 0
let skippedMemberList = []

var OC2_timerID = null

const crimeListShowText = (`[ Show all ]`)
const crimeListHideText = (`[ Hide all ]`)
const crimeButtonShowText = (`Show Crimes`)
const crimeButtonHideText = (`Hide Crimes`)
const lazyMembersButtonShowText = (`Show Members`)
const lazyMembersButtonHideText = (`Hide Members`)
const settingsButtonAscText = (`▴`) //▴ ▴
const settingsButtonDescText = (`▾`) // ▾ ▾
var crimeItemIcon = (`🛠`) //🛠 🛠
if (isPDA()) {
  crimeItemIcon = (`⚒`) // ⚒ $#9874;
}
const defaultUserSettings = {
  "memberShow": "member-show",
  "crimesShow": "crimes-hide",
  "sortType": "time-asc", //time-asc / time-desc / level-asc / level-desc
  "memberSort": "OC-desc", //OC-asc / OC-desc / active-asc / active-desc
  "lastOC_yellow": 24,
  "lastOC_red": 48,
  "lastActivity": 96,
  "memberIgnoreList": [ ],
  "showSidebarOC": "sidebar-show",
  "showNegativeTimes": "negative-timer-hide",
  "warnMissingItems": "warn-items-show",
  "warnLowSuccess": "warn-success-show",
  "warnLowSuccessPercentage": 50,
  "showProgressPercentage": "progress-show"
}

let _isWindowNormal = window.matchMedia("(min-width: 1000px)")
let _isWindowSmallish = window.matchMedia("(min-width: 785px)")
let _isWindowSmall = window.matchMedia("(max-width: 784px) and (min-width: 387px)")
let _isWindowTiny = window.matchMedia("(max-width: 386px)")

let colorObj = {
  "normal_font": {
    "darkmode": "rgb(221, 221, 221)",
    "lightmode": "rgb(51, 51, 51)"
  },
  "dark_bg_link": {
    "darkmode": "rgb(116, 192, 252)",
    "lightmode": "rgb(116, 192, 252)"
  },
  "link": {
    "darkmode": "rgb(116, 192, 252)",
    "lightmode": "#006699"
  },
  "recolor": {
    "green": {
      "darkmode": "rgb(130, 201, 30)",
      "lightmode": "rgb(92, 148, 13)"
    },
    "yellow": {
      "darkmode": "rgb(252, 196, 25)",
      "lightmode": "rgb(252, 196, 25)"
    },
    "red": {
      "darkmode": "rgb(255, 135, 135)",
      "lightmode": "rgb(224, 49, 49)"
    },
    "blue": {
      "darkmode": "rgb(59, 201, 219)",
      "lightmode": "rgb(12, 133, 153)"
    }
  },
  "userindicatorbg": {
    "darkmode": "rgb(63, 68, 45)",
    "lightmode": "rgb(238, 241, 228)"
  },
  "footerbg": {
    "darkmode": "rgb(51, 51, 51)",
    "lightmode": "rgb(242, 242, 242)"
  },
  "crimeselectbg": {
    "darkmode": "rgba(0,0,0,0.2)",
    "lightmode": "rgba(150,150,150,0.1)"
  },
  "fancyBg": {
    "darkmode": "inherit",
    "lightmode": "#fff"
  },
  "crimeIcon": {
    "default": {
      "lightmode": "rgba(100, 100, 100, 0.5)",
      "darkmode": "rgba(221, 221, 221, 0.5)"
    },
    "highlightYellow": {
      "lightmode": "rgb(230, 180, 0)",
      "darkmode": "rgba(252, 196, 25, 0.8)"
    },
    "highlightRed": {
      "lightmode": "rgba(255, 135, 135, 1)",
      "darkmode": "rgba(255, 135, 135, 0.8)"
    },
    "highlightVeryRed": {
      "lightmode": "rgba(200, 33, 33, 1)",
      "darkmode": "rgba(200, 33, 33, 1)"
    }
  },
  "buttons": {
    "background": {
      "lightmode": "linear-gradient(rgb(255, 255, 255) 0%, rgb(221, 221, 221) 100%)",
      "darkmode": "linear-gradient(rgb(85, 85, 85) 0%, rgb(51, 51, 51) 100%)"
    },
    "textcolor": {
      "lightmode": "rgb(102, 102, 102)",
      "darkmode": "rgb(221, 221, 221)"
    },
    "hovercolor": {
      "lightmode": "rgb(200,200,200)",
      "darkmode": "rgb(28,28,28)"
    }
  }
}
let colorDisplayMode = $("body#body").hasClass("dark-mode") ? "darkmode" : "lightmode"

var membersButtonShowText = (`<svg width="8px" height="8px" viewBox="1 1 13 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><polygon class="OC2-triangle" points="13,8 5,16 5,0" fill="${colorObj.normal_font[colorDisplayMode]}" /></svg>`) //⏵ &#9205; <svg width="11px" height="11x" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect width="16" height="16" id="icon-bound" fill="none" /><polygon points="13,8 5,16 5,0" /></svg>
var membersButtonHideText = (`<svg width="8px" height="8px" viewBox="1 1 13 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><polygon class="OC2-triangle" points="8,13 0,5 16,5" fill="${colorObj.normal_font[colorDisplayMode]}" /></svg>`) //⏷ &#9207;

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]
}

//user settings functions
if (!isPDA()) {
  const menu_command_1 = GM_registerMenuCommand("Open Settings Page", showSettingsPage)
}

function showSettingsPage() {
  window.open("https://www.torn.com/OC2_Settings_Page", "_blank")
}

async function getAPIKey() {
  if (isPDA()) {
    APIKey = PDA_APIKey
    return PDA_APIKey
  } else if (APIKey != null && APIKey.length > 0) {
    return APIKey
  } else {
    return await GM.getValue("CMR_OC2_APIKey", null)
      .then(function(data) {
        APIKey = data
        return data
      })
  }
}

async function setUserSettings(_settings) {
  if (!_settings) {
    return
  }
  if (isPDA()) {
    localStorage.setItem("CMR_OC2_userSettings", JSON.stringify(_settings))
    if ( $("#OC2-APIInput").val().length > 5 && $("#OC2-APITestResult-Final").hasClass("color-green") ) {
      localStorage.setItem("CMR_OC2_APIKey", $("#OC2-APIInput").val())
    }
    return
  } else {
    await GM.setValue("CMR_OC2_userSettings", JSON.stringify(_settings))
  }
  if ( $("#OC2-APIInput").val().length > 5 && $("#OC2-APITestResult-Final").hasClass("color-green") ) {
    await GM.setValue("CMR_OC2_APIKey", $("#OC2-APIInput").val())
  }
}

async function getUserSettings(event) {
  if (isPDA()) {
    if (!localStorage.getItem("CMR_OC2_userSettings")) {
      userSettings = defaultUserSettings
    } else {
      userSettings = JSON.parse(localStorage.getItem("CMR_OC2_userSettings"))
    }
    return
  }
  return await GM.getValue("CMR_OC2_userSettings", JSON.stringify(defaultUserSettings))
    .then( (data) => {
      userSettings = JSON.parse(data)
    })
}

//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) || ($('body').attr("data-abroad") == "true") || ($('body').attr("data-abroad") == true) ) {
    if (!myAPIData) {
      try {
        let _successfulGetAPIData = await getAndAnalyzeAPIData()
        if (_successfulGetAPIData.error) {
          //console.log("checktravelfactionpage Error: ", _successfulGetAPIData.error)
          $(".OC2-memberTable").hide()
          $(".OC2-memberTableErrorDisplay").html(`<span style="margin-left: 20px">Error occured: ${_successfulGetAPIData.error.error}. Please visit the <a href="https://www.torn.com/OC2_Settings_Page" target="_new" style="color: inherit; font-weight: bold; text-decoration: underline">Settings Page</a> to set up an API key</span>`)
          $(".OC2-memberTableErrorDisplay").show()
          return _successfulGetAPIData.error
        }
      } catch(_err) {
          //console.log("checktrvelfactionpage caught error:", _err)
          return false
      }
    }
    if (pageURL.search("ID="+myAPIData.basic.id) >= 0) {
      return true;
    }
  }
  return false;
}

//API call functions
async function getAndAnalyzeAPIData() {
  return await $.ajax({
    dataType: "json",
    url: (`https://api.torn.com/v2/faction/basic,crimes,members?cat=available,completed&offset=0&striptags=true&comment=OC2-helper`),
    headers: {
      Authorization: (`ApiKey ${APIKey}`)
    }
  }).done( data => {
    if (data.error) {
      return data
    }
    checkMembersInCrimes(data)
    myAPIData = data
    return data
  }).fail( (error) => {
    return error
  })
}

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 (let 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)
  }
  //this needs more IFs to prevent errors, since the item ID may exist but the span with the warning does not exist
  for (let i = 0; i < $(".OC2-crimeMouseoverWarning").length; i++) {
    let _oldTitle = $(".OC2-crimeMouseoverWarning").eq(i).attr("title")
    if (_oldTitle) {
      let _regexResult = _matchregex.exec($(".OC2-crimeMouseoverWarning").eq(i).attr("title"))
      if (_regexResult) {
        let _newTitle = _oldTitle.replace(_matchregex, `${itemIDObj[_regexResult[1]].name} <$1>`)
        $(".OC2-crimeMouseoverWarning").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"
  }

  if ((laterTimestamp < currentTimestamp) && userSettings?.showNegativeTimes == "negative-timer-show" ) {
    timeDiff = currentTimestamp - laterTimestamp
    _highlightClass = "OC2-highlightRed"
  }

  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
}

function stringifyTimestampOld(_timeDiff) {
  var currentTimestamp = Math.floor(Date.now()/1000)
  var _returnString = ""
  var _highlightClass = ""

  if (_timeDiff == -1) {
    return (`User has not joined the last ${myAPIData.crimes.length} OCs`)
  }

  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

  if (_d > 0) {
    _returnString += (`${_d.toString().padStart(1,'0')}d `)
  }
  if (_d > 0 || _h > 0) {
    _returnString += (`${_h.toString().padStart(2,'0')}h `)
  }
  if (_d > 0 || _h > 0 || _m > 0) {
    _returnString += (`${_m.toString().padStart(2,'0')}m `)
  }
  _returnString += (`ago`)

  return _returnString
}

function timestampOldDiff(olderTimestamp) {
  var currentTimestamp = Math.floor(Date.now()/1000)
  if (olderTimestamp == 0) {
    return -1
  }
  return currentTimestamp - olderTimestamp //time since last OC in seconds
}


const timerTick = () => {
  let _timeList = $("span.OC2-countdown")
  for (let 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": colorObj.recolor.yellow[colorDisplayMode],
    "font-weight": "bold"
  })
  $(".OC2-highlightRed").css({
    "color": colorObj.recolor.red[colorDisplayMode],
    "font-weight": "bold"
  })
}

//the nitty gritty functions
function checkMembersInCrimes(_data) {
  //console.log(`checkMembersInCrimes`, _data)
  //put all member ids into a list
  for (let 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
      skippedMemberCount += 1;
      skippedMemberList.push({
        "id": _data.members[i].id,
        "name": _data.members[i].name
      })
    } else {
      memberInfo[_data.members[i].id] = {
        "id": _data.members[i].id,
        "name": _data.members[i].name,
        "last_action": _data.members[i].last_action,
        "statusDesc": _data.members[i].status.description,
        "status": _data.members[i].status.state,
        "lastCrime": 0
      }
    }
  }
  totalMembers = (_data.members).length - skippedMemberCount;
  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 (let i = 0; i < (_data.crimes).length; i++) {
    if (_data.crimes[i].status == 'Expired') {
      continue; //skip all expired crimes
    }
    //sort members into objects
    if ((_data.crimes[i].status == 'Successful' || _data.crimes[i].status == 'Failure')) { //for crimes that were complete, put that crime's info to member's last crime completed section
      for (let j=0; j<(_data.crimes[i].slots).length; j++) {
        //skip slots that are empty
        if (_data.crimes[i].slots[j].user_id) {
          //ignore members that left the faction
          if (!memberInfo[_data.crimes[i].slots[j].user_id]) {
            continue
          }
          let _lastCrimeTime = _data.crimes[i].ready_at
          if (_data.crimes[i].executed_at) { //take into account executed at, for crimes that were stalled
            _lastCrimeTime = _data.crimes[i].executed_at
          }
          if (memberInfo[_data.crimes[i].slots[j].user_id].lastCrime < _lastCrimeTime) { //make sure that the lastCrime in the memberInfo is the latest crime so far
            memberInfo[_data.crimes[i].slots[j].user_id].lastCrime = _lastCrimeTime
          }
        }
      }
    }
    if ((_data.crimes[i].status == 'Recruiting' || _data.crimes[i].status == 'Planning')) { //only
      if (_data.crimes[i].planning_at) { //if crime is not initiated, it will be null for planning_at. No point looking for members because there won't be any.
        for (let k=0; k<(_data.crimes[i].slots).length; k++) {
          if (_data.crimes[i].slots[k].user_id) {
           //ignore members that left the faction
            if (!memberInfo[_data.crimes[i].slots[k].user_id]) {
              continue
            }
            memberInfo[_data.crimes[i].slots[k].user_id].crimeInfo = {
              "crimeName": _data.crimes[i].name,
              "crimeDifficulty": _data.crimes[i].difficulty,
              "crimeId": _data.crimes[i].id,
              "crimePosition": _data.crimes[i].slots[k].position,
              "crimeSuccess": _data.crimes[i].slots[k].success_chance,
              "crimeProgress": _data.crimes[i].slots[k].user?.progress
            }
            activeMembers = activeMembers + 1;
            if ((_data.crimes[i].slots[k].user_id) == getUserID()) {
              userInfo = memberInfo[_data.crimes[i].slots[k].user_id]
              userInfo.crimeInfo.crimeTime = _data.crimes[i].slots[k].ready_at
            }
          }
        }
      }
    }
    //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 sortCrimeInfo() {
  //remove all crime Lis
  $(".OC2-memberViewer li.OC2-crimeLi").not(".OC2-memberViewer li[class*='OC2-titleLi']").remove()
  $("li.OC2-crimeMemberLi").remove()
  if (userSettings.sortType == "level-desc") {
    crimeListUninitiated.sort( (a,b) => a.difficulty - b.difficulty)
    crimeListRecruiting.sort( (a,b) => a.difficulty - b.difficulty)
    crimeListPlanning.sort( (a,b) => a.difficulty - b.difficulty)
  } else if (userSettings.sortType == "level-asc") {
    crimeListUninitiated.sort( (a,b) => b.difficulty - a.difficulty)
    crimeListRecruiting.sort( (a,b) => b.difficulty - a.difficulty)
    crimeListPlanning.sort( (a,b) => b.difficulty - a.difficulty)
  } else if (userSettings.sortType == "time-desc") { //asc and desc are swapped around. I don't know why but it works. Some math thing. I need to think about it.
    crimeListUninitiated.sort( (a,b) => a.expired_at - b.expired_at)
    crimeListRecruiting.sort( (a,b) => a.ready_at - b.ready_at)
    crimeListPlanning.sort( (a,b) => a.ready_at - b.ready_at)
  } else if (userSettings.sortType == "time-asc") {
    crimeListUninitiated.sort( (a,b) => b.expired_at - a.expired_at)
    crimeListRecruiting.sort( (a,b) => b.ready_at - a.ready_at)
    crimeListPlanning.sort( (a,b) => b.ready_at - a.ready_at)
  }
}

function sortAvailMembers() {
  $("li.OC2-memberAvailable").remove()
  if (userSettings.memberSort == "OC-asc") {
    availMemberList.sort( (a,b) => b.lastCrime - a.lastCrime )
  }
  if (userSettings.memberSort == "OC-desc") {
    availMemberList.sort( (a,b) => a.lastCrime - b.lastCrime )
  }
  if (userSettings.memberSort == "active-asc") {
    availMemberList.sort( (a,b) => a.last_action.timestamp - b.last_action.timestamp )
  }
  if (userSettings.memberSort == "active-desc") {
    availMemberList.sort( (a,b) => b.last_action.timestamp - a.last_action.timestamp )
  }
}

function putAvailMembersIntoTable(_memberArray, _afterElm) {
  if (_memberArray.length < 1) {
    return //don't do anything if there are no members
  }
  let _ignoreTitleText = ""
  let _skipTitleText = ""
  if (skippedMemberList.length > 0) {
    _skipTitleText = "Recruits unavailable for OCs:"
    skippedMemberList.forEach( (_skippedMemberName) => {
      _skipTitleText += (`<br />${_skippedMemberName.name} [${_skippedMemberName.id}]`)
    })
  }
  _memberArray.forEach( (_member) => {
    if (userSettings.memberIgnoreList) {
      if (userSettings.memberIgnoreList.includes((_member.id).toString())) {
        if (_ignoreTitleText == "") {
          _ignoreTitleText = "Ignored members:"
        }
        _ignoreTitleText += `<br />${_member.name} [${_member.id}]`
        return //skip member to be ignored
      }
    }
    let _outputHTML = ""
    let _memberHighlight = "OC2-memberAvailable"
    let _memberInactiveTitle = `Last Action: ${_member.last_action.relative}`
    let _memberCrimeFill = colorObj.crimeIcon.default[colorDisplayMode]
    let _memberNameColor = ""
    if (_member.id == userInfo.id) {
      _memberHighlight += " OC2-userIndicator"
    }
    if (_member.lastCrime == 0) { //user is not found in the crimes obtained from the API
      _memberCrimeFill = colorObj.crimeIcon.highlightVeryRed[colorDisplayMode]
    }
    if (timestampOldDiff(_member.lastCrime) > parseInt(userSettings.lastOC_yellow) * 60 * 60) {
      _memberCrimeFill = colorObj.crimeIcon.highlightYellow[colorDisplayMode]
    }
    if (timestampOldDiff(_member.lastCrime) > parseInt(userSettings.lastOC_red) * 60 * 60) {
      _memberCrimeFill = colorObj.crimeIcon.highlightRed[colorDisplayMode]
    }
    if ( (Math.floor(Date.now()/1000) - _member.last_action.timestamp) > parseInt(userSettings.lastActivity) * 60 * 60) {
      _memberNameColor = "inactive"
    }
    if ( (_member.status).toLowerCase() == "federal" ) {
      _memberNameColor = "federal"
    }
    let _lastCrimeIcon = (`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="11" height="11" viewBox="0 0 30 40"><path fill="${_memberCrimeFill} "d="M397,168c-7.573,0-15,5.76-15,13.333,0,4.589,3.375,10.129,6.512,14.524a10.05,10.05,0,0,1,16.935-.067c3.22-4.457,6.553-9.865,6.553-14.457C412,173.76,404.573,168,397,168Zm-5.83,18.333a4.166,4.166,0,1,1,4.167-4.166A4.167,4.167,0,0,1,391.17,186.333Zm11.667,0A4.166,4.166,0,1,1,407,182.167,4.166,4.166,0,0,1,402.837,186.333ZM397,194.52a6.74,6.74,0,1,0,6.74,6.74A6.741,6.741,0,0,0,397,194.52Zm1.752,2.458a1.07,1.07,0,1,1-1.07,1.07A1.071,1.071,0,0,1,398.752,196.978Zm-3.574,0a1.07,1.07,0,1,1-1.068,1.07A1.071,1.071,0,0,1,395.178,196.978Zm-1.918,5.35a1.07,1.07,0,1,1,1.07-1.07A1.071,1.071,0,0,1,393.26,202.328Zm1.918,3.212a1.069,1.069,0,1,1,1.07-1.07A1.07,1.07,0,0,1,395.178,205.54Zm.752-4.28a1.07,1.07,0,1,1,1.07,1.07A1.071,1.071,0,0,1,395.93,201.26Zm2.822,4.28a1.069,1.069,0,1,1,1.07-1.07A1.071,1.071,0,0,1,398.752,205.54Zm1.988-3.212a1.07,1.07,0,1,1,1.07-1.07A1.069,1.069,0,0,1,400.74,202.328Z" transform="translate(-382 -168)"></path></svg>`)
    _outputHTML = (`<li class="table-cell ${_memberHighlight}">
        <div class="OC2-tableCell OC2-tableMember" title="${_member.name} [${_member.id}]<br />Last OC joined: ${stringifyTimestampOld(timestampOldDiff(_member.lastCrime))}<br />${_memberInactiveTitle}">
          <div class="OC2-tableLastCrime OC2-lazyCountup" data-countup="${timestampOldDiff(_member.lastCrime)}"><span>${_lastCrimeIcon}</span></div>
          <a href="https://www.torn.com/profiles.php?XID=${_member.id}"><span class="OC2-memberName ${_memberNameColor}">${_member.name}</span><span class="screen-reader-hidden">: Last OC joined: ${stringifyTimestampOld(timestampOldDiff(_member.lastCrime))}: ${_memberInactiveTitle}</span></a>
        </div>
        <div class="OC2-tableCell OC2-tableStatus"><span class="screen-reader-hidden">Status:</span>${styleMemberStatus(_member.status,_member.statusDesc)}</div>
      </li>`)
    _afterElm.after(_outputHTML)
  })
  $(".OC2-memberTableFooter").not(".OC2-settingsFooter").html(`<span title="${_skipTitleText}">${availableMembers} / ${totalMembers} members available</span> <span title="${_ignoreTitleText}">(${userSettings.memberIgnoreList.length} member${userSettings.memberIgnoreList.length == 1 ? "":"s"} manually ignored)</span>`)
}

function putMemberInfoIntoTable() {
  //fix for tornPDA, idk why but checking the li.tablecell works but checking the ul.table doesn't
  if ($(".OC2-memberTable .OC2-tableCell")[0]) {
    return
  }
  availMemberList = []
  for (var _key of Object.keys(memberInfo)) {
    if (memberInfo[_key].crimeInfo) {
      continue //skip all members in crimes
    }
    availMemberList.push(memberInfo[_key])
  }
  sortAvailMembers()
  putAvailMembersIntoTable(availMemberList, $(".OC2-memberTable li.OC2-titleLiAvailableMembers"))
  sortCrimeInfo()
  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))

  $(".OC2-hideAtStart").css({
    "display": ""
  })

  $("#OC2-titleUninitiated > span.toggleCatCrimesButton").on("click", (event) => {
    if ((event.currentTarget.attributes.class.value).includes("OC2-crimeListExpand")) {
      //expanded, shut all
      $(event.currentTarget).removeClass("OC2-crimeListExpand")
      $(event.currentTarget).html(crimeListShowText)
      crimeIDListUninitiated.forEach(function(item) {
        $("li.OC2-crimeLi.OC2-crimeID_"+item).removeClass("OC2-crimeLiActive")
        $(".OC2-crimeID_"+item+" .hideMembersButton").html(membersButtonShowText)
        $(".OC2-crimeID_"+item+" .hideMembersButton").removeClass("text-hide")
        $(".OC2-crimeMemberLi.OC2-crimeID_"+item).removeClass("crimeMemberLiShow")
        $(".OC2-crimeMemberLi.OC2-crimeID_"+item).slideUp()
      })
    } else {
      //shut, expand all
      $(event.currentTarget).addClass("OC2-crimeListExpand")
      $(event.currentTarget).html(crimeListHideText)
      crimeIDListUninitiated.forEach(function(item) {
        $("li.OC2-crimeLi.OC2-crimeID_"+item).addClass("OC2-crimeLiActive")
        $(".OC2-crimeID_"+item+" .hideMembersButton").html(membersButtonHideText)
        $(".OC2-crimeID_"+item+" .hideMembersButton").addClass("text-hide")
        $(".OC2-crimeMemberLi.OC2-crimeID_"+item).addClass("crimeMemberLiShow")
        $(".OC2-crimeMemberLi.OC2-crimeID_"+item).slideDown()
      })
    }
    styleCrimeLiActive()
  })

  $("#OC2-titleRecruiting > span.toggleCatCrimesButton").on("click", (event) => {
    if ((event.currentTarget.attributes.class.value).includes("OC2-crimeListExpand")) {
      //expanded, shut all
      $(event.currentTarget).removeClass("OC2-crimeListExpand")
      $(event.currentTarget).html(crimeListShowText)
      crimeIDListRecruiting.forEach(function(item) {
        $("li.OC2-crimeLi.OC2-crimeID_"+item).removeClass("OC2-crimeLiActive")
        $(".OC2-crimeID_"+item+" .hideMembersButton").html(membersButtonShowText)
        $(".OC2-crimeID_"+item+" .hideMembersButton").removeClass("text-hide")
        $(".OC2-crimeMemberLi.OC2-crimeID_"+item).removeClass("crimeMemberLiShow")
        $(".OC2-crimeMemberLi.OC2-crimeID_"+item).slideUp()
      })
    } else {
      //shut, expand all
      $(event.currentTarget).addClass("OC2-crimeListExpand")
      $(event.currentTarget).html(crimeListHideText)
      crimeIDListRecruiting.forEach(function(item) {
        $("li.OC2-crimeLi.OC2-crimeID_"+item).addClass("OC2-crimeLiActive")
        $(".OC2-crimeID_"+item+" .hideMembersButton").html(membersButtonHideText)
        $(".OC2-crimeID_"+item+" .hideMembersButton").addClass("text-hide")
        $(".OC2-crimeMemberLi.OC2-crimeID_"+item).addClass("crimeMemberLiShow")
        $(".OC2-crimeMemberLi.OC2-crimeID_"+item).slideDown()
      })
    }
    styleCrimeLiActive()
  })

  $("#OC2-titleFull > span.toggleCatCrimesButton").on("click", (event) => {
    if ((event.currentTarget.attributes.class.value).includes("OC2-crimeListExpand")) {
      //expanded, shut all
      $(event.currentTarget).removeClass("OC2-crimeListExpand")
      $(event.currentTarget).html(crimeListShowText)
      crimeIDListFull.forEach(function(item) {
        $("li.OC2-crimeLi.OC2-crimeID_"+item).removeClass("OC2-crimeLiActive")
        $(".OC2-crimeID_"+item+" .hideMembersButton").html(membersButtonShowText)
        $(".OC2-crimeID_"+item+" .hideMembersButton").removeClass("text-hide")
        $(".OC2-crimeMemberLi.OC2-crimeID_"+item).removeClass("crimeMemberLiShow")
        $(".OC2-crimeMemberLi.OC2-crimeID_"+item).slideUp()
      })
    } else {
      //shut, expand all
      $(event.currentTarget).addClass("OC2-crimeListExpand")
      $(event.currentTarget).html(crimeListHideText)
      crimeIDListFull.forEach(function(item) {
        $("li.OC2-crimeLi.OC2-crimeID_"+item).addClass("OC2-crimeLiActive")
        $(".OC2-crimeID_"+item+" .hideMembersButton").html(membersButtonHideText)
        $(".OC2-crimeID_"+item+" .hideMembersButton").addClass("text-hide")
        $(".OC2-crimeMemberLi.OC2-crimeID_"+item).addClass("crimeMemberLiShow")
        $(".OC2-crimeMemberLi.OC2-crimeID_"+item).slideDown()
      })
    }
    styleCrimeLiActive()
  })

  convertItemIDArrayToItems()
  styleTable()
  checkDefaultHideState()
  if (OC2_timerID) {
    clearTimeout(OC2_timerID)
  }
  timerTick()
}

function putCrimeInfoIntoTable(_crimeArray, _afterElm) {
  let _countdownText = ""
  let _countdownToTimestamp = ""
  let _countdownMouseover = ""
  let _memberOutputHTML = ""
  let _userIndicatorClass = ""
  let _userIndicatorCrimeClass = ""
  let _crimeListType = ""
  if (_crimeArray.length > 0) {
    for (let 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"
          _crimeListType = "OC2-crimeUninitiated"
          crimeIDListUninitiated.push(_crimeArray[i].id)
        } else {
          _countdownText = "Join in:"
          _countdownToTimestamp = _crimeArray[i].ready_at
          _countdownMouseover = "Time until this crime needs a new member to join to continue planning"
          _crimeListType = "OC2-crimeRecruiting"
          crimeIDListRecruiting.push(_crimeArray[i].id)
          if ((_countdownToTimestamp < Math.floor(Date.now()/1000)) && (userSettings?.showNegativeTimes == "negative-timer-show")) {
            _countdownText = "Waiting:"
          _countdownMouseover = "Delay in planning due to lack of members joining"
        }
        }
      }
      //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"
        _crimeListType = "OC2-crimeFull"
        crimeIDListFull.push(_crimeArray[i].id)
        if ((_countdownToTimestamp < Math.floor(Date.now()/1000)) && (userSettings?.showNegativeTimes == "negative-timer-show")) {
          _countdownText = "Waiting:"
          _countdownMouseover = "Delay since crime should have started"
        }
      }

      _memberOutputHTML = ""
      _userIndicatorCrimeClass = ""
      let _memberCount = 0
      let _crimeWarningIcon = ""
      let _crimeWarningMouseover = ""
      let _warningSuccessChance = ""
      let _warningItemNeeded = ""
      //count number of slots filled
      for (let 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 _crimeItemIconDisplay = crimeItemIcon
        let _crimeItemMouseover = ""
        let _crimeSuccess = ""
        let _crimeSuccessWrapper = ""
        let _crimeProgressString = ""
        _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": ""
              };
          }
          _crimeItemMouseover = (`Required item: <#${_crimeArray[i].slots[j].item_requirement.id}>`)
          if (_crimeArray[i].slots[j].item_requirement.is_reusable) {
            _crimeItemMouseover += (` (reusable)`)
            _crimeItemIconDisplay += (`<span style="vertical-align:top">∞</span>`)
          }
          if (_crimeArray[i].slots[j].item_requirement.is_available) {
             _crimeItemMouseover += (`<br />Item is owned by member`)
          }
          _crimeItem = (`<span class="OC2-itemHave${_crimeArray[i].slots[j].item_requirement.is_available}">${_crimeItemIconDisplay}</span>`)
        }
        if (_crimeArray[i].slots[j].user_id) {
          if (_crimeArray[i].slots[j].user_id == userInfo.id) {
            _userIndicatorClass = "OC2-userIndicator"
            _userIndicatorCrimeClass = "OC2-userIndicator"
          }
          _memberCount = _memberCount + 1
          if (userSettings?.showProgressPercentage == "progress-show") {
            _crimeProgressString = (`(${memberInfo[_crimeArray[i].slots[j].user_id].crimeInfo.crimeProgress}%)`)
          }
          _crimeSlotMemberName = `<a href="https://www.torn.com/profiles.php?XID=${_crimeArray[i].slots[j].user_id}">${memberInfo[_crimeArray[i].slots[j].user_id].name}</a> ${_crimeProgressString}`
          _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 > 75) {
            _crimeSuccessWrapper = "OC2-highSuccess"
          } else if (_crimeArray[i].slots[j].success_chance > 50) {
            _crimeSuccessWrapper = "OC2-midSuccess"
          } else {
            _crimeSuccessWrapper = "OC2-lowSuccess"
          }
          if (_crimeArray[i].slots[j].success_chance < ((userSettings?.warnLowSuccessPercentage) ? userSettings.warnLowSuccessPercentage : 50)) {
            _crimeSuccessWrapper = "OC2-lowSuccess"
          }
          _crimeSuccess = (`<span class="${_crimeSuccessWrapper}">${_crimeArray[i].slots[j].success_chance}</span>`)
          if (_crimeArray[i].slots[j].success_chance < userSettings?.warnLowSuccessPercentage) {
            if (_warningSuccessChance.length < 1) {
              _warningSuccessChance = (`Low Success Chance:<br />`)
            } else {
              _warningSuccessChance += "<br />"
            }
            _warningSuccessChance += `&nbsp;${_crimeArray[i].slots[j].success_chance}% - ${memberInfo[_crimeArray[i].slots[j].user_id].name}`
          }
          if (_crimeArray[i].slots[j].item_requirement?.is_available == false) { //item is not available for member
            if (_warningItemNeeded.length < 1) {
              _warningItemNeeded = (`Item Missing:<br />`)
            } else {
              _warningItemNeeded += "<br />"
            }
            _warningItemNeeded += `&nbsp;<#${_crimeArray[i].slots[j].item_requirement.id}> - ${memberInfo[_crimeArray[i].slots[j].user_id].name}<br />`
          }
        } 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}</div>
          <div class="OC2-tableCell OC2-tableCrimeMemberStatus">${_crimeSlotMemberStatus}</div>
        </li>`)
      }
      _crimeWarningMouseover = _warningSuccessChance + (((_warningItemNeeded.length > 0) && (_warningSuccessChance.length > 0)) ? "<br />": "") + _warningItemNeeded
      if (_warningSuccessChance.length > 0) {
        _crimeWarningIcon += "%"
      }
      if (_warningItemNeeded.length > 0) {
        _crimeWarningIcon += crimeItemIcon
      }
      if (_crimeWarningIcon.length > 0) {
        _crimeWarningIcon = "[" + _crimeWarningIcon + "]&nbsp;"
      }
      let _outputHTML = (`<li class="table-cell OC2-crimeLi ${_crimeListType} 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"><span class="OC2-crimeMouseoverWarning" title="${_crimeWarningMouseover}">${_crimeWarningIcon}</span><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() {
  if ($(".OC2-memberViewer .OC2-memberTable")[0]) {
    return
  }
  let _insertHTML = (`
  <div class="category-wrap OC2-memberViewer m-top10">
    <div class="title-black top-round t-overflow">OC 2.0 Overview <a href="https://www.torn.com/OC2_Settings_Page" target="_blank"><span title="Go to Settings Page" class="extraSettingsButton">&#9881;</span></a><span class="hideLazyMembersButton OC2-hideAtStart">${lazyMembersButtonShowText}</span><span class="hideCrimesButton OC2-hideAtStart">${crimeButtonShowText}</span></div>
    <div class="cont-gray OC2-memberTableErrorDisplay" style="display:none; padding: 5px 0"></div>
    <div class="cont-gray OC2-memberTable" style="display:none"><ul class="table-body">
      <li class="table-cell OC2-availableMembers OC2-titleLiAvailableMembers"><div class="OC2-titleCell OC2-fancyBg">Members not in an OC<div class="OC2-sortTypeMember">Sort: <div id="sortOCButton">OC</div><div id="sortActiveButton">active</div></div></div></li>
      <li class="table-cell OC2-crimeLi OC2-titleLiCrimeSeciton"><div class="OC2-titleCell OC2-fancyBg">Crimes<div class="OC2-sortType">Sort: <div id="sortLevelButton">level</div><div id="sortTimeButton">time</div></div></div></li>
      <li class="table-cell OC2-crimeLi OC2-titleLiUninitiated"><div class="OC2-titleCell" id="OC2-titleUninitiated"><span class="catCrimeTitle">Uninitiated Crimes</span> <span class="toggleCatCrimesButton">${crimeListShowText}<span></div></li>
      <li class="table-cell OC2-crimeLi OC2-horizLine"></li>
      <li class="table-cell OC2-crimeLi OC2-titleLiRecruiting"><div class="OC2-titleCell" id="OC2-titleRecruiting"><span class="catCrimeTitle">Recruiting Crimes</span> <span class="toggleCatCrimesButton">${crimeListShowText}<span></div></li>
      <li class="table-cell OC2-crimeLi OC2-horizLine"></li>
      <li class="table-cell OC2-crimeLi OC2-titleLiPlanning"><div class="OC2-titleCell" id="OC2-titleFull"><span class="catCrimeTitle">Full Crimes</span> <span class="toggleCatCrimesButton">${crimeListShowText}<span></div></li>
    </ul></div>
    <div class="OC2-memberTableFooter"></div>
  </div>`)
  //what to do in normal crimes2.0 page
  $(".OC2-hideAtStart").css({
    "display": "none"
  })
  if (checkCrimesPage()) {
    $("div#faction-crimes").before(_insertHTML)
    checkDefaultSortState()
    styleTable()
    $(".hideCrimesButton").off().on("click", event => {
      toggleCrimeView()
    })
    $(".OC2-sortType div").off().on("click", event => {
      resortCrimeTable(event.currentTarget)
    })
    $(".OC2-sortTypeMember div").off().on("click", event => {
      resortAvailMemberTable(event.currentTarget)
    })
    $(".hideLazyMembersButton").off().on("click", event => {
      toggleLazyMembersView()
    })
  } 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)
      checkDefaultSortState()
      styleTable()
      $(".hideCrimesButton").off().on("click", event => {
        toggleCrimeView()
      })
      $(".OC2-sortType div").off().on("click", event => {
        resortCrimeTable(event.currentTarget)
      })
      $(".OC2-sortTypeMember div").off().on("click", event => {
        resortAvailMemberTable(event.currentTarget)
      })
      $(".hideLazyMembersButton").off().on("click", event => {
        toggleLazyMembersView()
      })
      if (myAPIData) {
        putMemberInfoIntoTable()
      }
    })
  }
}

function checkDefaultHideState() {
  $(".OC2-memberTable").show()
  if (userSettings.memberShow == "member-hide") {
    $(".hideLazyMembersButton").html(lazyMembersButtonShowText)
    $(".OC2-availableMembers").hide()
    $(".OC2-memberAvailable").hide()
  } else {
    $(".hideLazyMembersButton").addClass("text-hide")
    $(".hideLazyMembersButton").html(lazyMembersButtonHideText)
  }
  if (userSettings.crimesShow == "crimes-hide") {
    $(".hideCrimesButton").html(crimeButtonShowText)
    $(".OC2-crimeMemberLi").hide()
    $(".OC2-crimeLi").hide()
  } else {
    $(".hideCrimesButton").addClass("text-hide")
    $(".hideCrimesButton").html(crimeButtonHideText)
    $(".OC2-crimeMemberLi").hide();
  }
}

function checkDefaultSortState() {
  //userSettings.memberSort "OC-desc", //OC-asc / OC-desc / active-asc / active-desc
  //userSettings.sortType "time-asc", //time-asc / time-desc / level-asc / level-desc
  let _memberTarget = userSettings.memberSort.split("-")[0]
  let _memberDirection = userSettings.memberSort.split("-")[1]
  let _crimeTarget = userSettings.sortType.split("-")[0]
  let _crimeDirection = userSettings.sortType.split("-")[1]

  _memberTarget = _memberTarget[0].toUpperCase() + _memberTarget.slice(1)
  _crimeTarget = _crimeTarget[0].toUpperCase() + _crimeTarget.slice(1)

  let _arrowDisplayMember = settingsButtonAscText
  let _arrowDisplayCrime = settingsButtonAscText
  if (_memberDirection == "desc") {
    _arrowDisplayMember = settingsButtonDescText
  }
  if (_crimeDirection == "desc") {
    _arrowDisplayCrime= settingsButtonDescText
  }
  $(`.OC2-sortTypeMember div[id=sort${_memberTarget}Button]`).addClass("text-underline")
  $(`.OC2-sortTypeMember div[id=sort${_memberTarget}Button]`).html(`${_memberTarget.toLowerCase()}${_arrowDisplayMember}`)
  $(`.OC2-sortType div[id=sort${_crimeTarget}Button]`).addClass("text-underline")
  $(`.OC2-sortType div[id=sort${_crimeTarget}Button]`).html(`${_crimeTarget.toLowerCase()}${_arrowDisplayCrime}`)
}

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>`)
  let _userMouseover = (`You are not currently participating in an OC.`)
  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}">Lv ${userInfo.crimeInfo.crimeDifficulty} ${userInfo.crimeInfo.crimeName}</a></span>`)
    _userMouseover = (`${userInfo.crimeInfo.crimePosition} (${userInfo.crimeInfo.crimeSuccess}%)`)
  }
  let _insertHTML = (`<div class="OC2-sidebarNotice" title="${_userMouseover}"><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).removeClass("crimeMemberLiShow")
    $(".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).addClass("crimeMemberLiShow")
    $(".OC2-crimeMemberLi.OC2-crimeID_"+_crimeID).slideDown()

  }
}

//OC2-availableMembers
function toggleLazyMembersView() {
  if ($(".hideLazyMembersButton").hasClass("text-hide")) {
    $(".hideLazyMembersButton").html(lazyMembersButtonShowText)
    $(".hideLazyMembersButton").removeClass("text-hide")
    $(".OC2-availableMembers").slideUp()
    $(".OC2-memberAvailable").slideUp()
  } else {
    $(".hideLazyMembersButton").html(lazyMembersButtonHideText)
    $(".hideLazyMembersButton").addClass("text-hide")
    $(".OC2-availableMembers").slideDown()
    $(".OC2-memberAvailable").slideDown()
  }
}

function resortCrimeTable(_target) {
  $(".OC2-sortType div").removeClass("text-underline")
  $(".OC2-sortType div#sortLevelButton").html("level")
  $(".OC2-sortType div#sortTimeButton").html("time")
  $(_target).addClass("text-underline")
  if ($(_target).attr("id") == "sortLevelButton") {
    if (userSettings.sortType == "level-asc") {
      userSettings.sortType = "level-desc"
      $(_target).html(`level${settingsButtonDescText}`)
    } else {
      userSettings.sortType = "level-asc"
      $(_target).html(`level${settingsButtonAscText}`)
    }
  }
  if ($(_target).attr("id") == "sortTimeButton") {
    if (userSettings.sortType == "time-asc") {
      userSettings.sortType = "time-desc"
      $(_target).html(`time${settingsButtonDescText}`)
    } else {
      userSettings.sortType = "time-asc"
      $(_target).html(`time${settingsButtonAscText}`)
    }
  }
  sortCrimeInfo()
  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()
  $("li.OC2-crimeLi .hideMembersButton").removeClass("text-hide")
  $("li.OC2-crimeMemberLi").hide()
  if ( !$(".hideLazyMembersButton").eq(0).hasClass("text-hide") ) {
    $("li.OC2-availableMembers").hide()
    $("li.OC2-memberAvailable").hide()
  }
}

function resortAvailMemberTable(_target) {
  $(".OC2-sortTypeMember div").removeClass("text-underline")
  $(".OC2-sortTypeMember div#sortOCButton").html("OC")
  $(".OC2-sortTypeMember div#sortActiveButton").html("active")
  $(_target).addClass("text-underline")
  if ($(_target).attr("id") == "sortOCButton") {
    if (userSettings.memberSort == "OC-asc") {
      userSettings.memberSort = "OC-desc"
      $(_target).html(`OC${settingsButtonAscText}`)
    } else {
      userSettings.memberSort = "OC-asc"
      $(_target).html(`OC${settingsButtonDescText}`)
    }
  }
  if ($(_target).attr("id") == "sortActiveButton") {
    if (userSettings.memberSort == "active-desc") {
      userSettings.memberSort = "active-asc"
      $(_target).html(`active${settingsButtonAscText}`)
    } else {
      userSettings.memberSort = "active-desc"
      $(_target).html(`active${settingsButtonDescText}`)
    }
  }
  sortAvailMembers()
  putAvailMembersIntoTable(availMemberList, $(".OC2-memberTable li.OC2-titleLiAvailableMembers"))
  styleTable()
  $("li.OC2-crimeLi .hideMembersButton").removeClass("text-hide")
  $("li.OC2-crimeLi.OC2-crimeLiActive").removeClass("OC2-crimeLiActive")
  styleCrimeLiActive()
  $("li.OC2-crimeMemberLi").hide()
  if ( !$(".hideCrimesButton").eq(0).hasClass("text-hide") ) {
    $("li.OC2-crimeLi").hide()
  }
}

//prettifying functions
function styleCrimeLiActive() {
  $(".OC2-crimeLi.OC2-crimeLiActive").not(".OC2-userIndicator").css({
    "background-color": "rgba(150,150,150,0.2)"
  })
  $(".OC2-crimeLi").not(".OC2-crimeLiActive").not(".OC2-userIndicator").css({
    "background-color": "transparent"
  })
  if ($("#dark-mode-state").prop("checked")) {
    $(".OC2-crimeLi.OC2-crimeLiActive").not(".OC2-userIndicator").css({
      "background-color": "rgba(0,0,0,0.3)"
    })
  }
}

function styleMemberStatus(_statusState, _statusDesc) {
  return (`<span title="${_statusDesc}" class="OC2-statusText ${_statusState.toLowerCase()}">${_statusState}</span><span title="${_statusDesc}" class="OC2-statusText OC2-hideSmall ${_statusState.toLowerCase()}">${_statusDesc}</span>`)
}

function styleTable() {
  /* notes to self
 * Small: 386px
 * Tiny: 320px
 * Normal: 784px
 */
  $(".OC2-memberTable ul.table-body").css({
    "display": "flex",
    "flex-direction": "row",
    "flex-wrap": "wrap",
  })
  $(".OC2-memberTable a").css({
    "color": colorObj.link[colorDisplayMode],
    "text-decoration": "none"
  })
  $(".extraSettingsButton").css({
    "color": colorObj.dark_bg_link[colorDisplayMode],
    "text-decoration": "none"
  })
  $(".extraSettingsButton").on("mouseenter", function(event) {
    $(event.currentTarget).css({
      "color": colorObj.recolor.yellow[colorDisplayMode]
    })
  })
  $(".extraSettingsButton").on("mouseleave", function(event) {
    $(event.currentTarget).css({
      "color": colorObj.dark_bg_link[colorDisplayMode]
    })
  })

  $(".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-titleCell").css({
    "display": "inline",
    "font-family": "Fjalla One",
    "padding-left": "10px",
    "width": containerMaxWidth,
    "box-sizing": "border-box",
  })
  $(".OC2-memberTable div.OC2-titleCell.OC2-fancyBg").css({
    "background": "repeating-linear-gradient(90deg, #2e2e2e, #2e2e2e 2px, #282828 0, #282828 4px)",
    "color": colorObj.fancyBg[colorDisplayMode]
  })
  $(".OC2-memberTable div.OC2-tableLastCrime").css({
    "width": "13px",
    "display": "inline-block",
    "padding": "0"
  })
  $(".OC2-memberTable div.OC2-tableLastCrime svg").css({
    "display": "inline-block",
    "margin": "0 auto -1px auto"
  })
  $(".OC2-memberTable div.OC2-tableMember").css({
    "width": "148px",
  })
  $(".OC2-memberTable div.OC2-tableStatus").css({
    "width": "222px",
    "text-align": "left"
  })
  $(".OC2-statusText.okay").css({
    "color": colorObj.recolor.green[colorDisplayMode]
  })
  $(".OC2-statusText.abroad, .OC2-statusText.traveling").css({
    "color": colorObj.recolor.blue[colorDisplayMode]
  })
  $(".OC2-statusText.hospital").css({
    "color": colorObj.recolor.red[colorDisplayMode]
  })
  $(".OC2-statusText.jail").css({
    "color": colorObj.recolor.red[colorDisplayMode]
  })
  $(".OC2-statusText.federal").css({
    "color": colorObj.recolor.red[colorDisplayMode]
  })
  $(".OC2-memberName.federal").css({
    "text-decoration": "line-through",
    "color": colorObj.recolor.red[colorDisplayMode]
  })
  $(".OC2-memberName.inactive").css({
    "color": colorObj.recolor.red[colorDisplayMode]
  })
  $(".OC2-memberTable div.OC2-tableStatus img").css({
    "height": "11px"
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberCount").css({
    "width": "50px",
  })
  $(".OC2-memberTable div.OC2-tableCrimePosition").css({
    "width": "100px",
    "font-size": "10px",
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberName").css({
    "width": "230px",
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberStatus").css({
    "width": "auto",
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberSuccess").css({
    "width": "25px",
    "text-align": "center"
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberItem").css({
    "width": "40px",
    "text-align": "center"
  })
  $(".OC2-memberTable .OC2-tableCrimeMemberSuccess .OC2-highSuccess").css({
    "color": colorObj.recolor.green[colorDisplayMode]
  })
  $(".OC2-memberTable .OC2-tableCrimeMemberSuccess .OC2-midSuccess").css({
    "color": colorObj.recolor.yellow[colorDisplayMode]
  })
  $(".OC2-memberTable .OC2-tableCrimeMemberSuccess .OC2-lowSuccess").css({
    "color": colorObj.recolor.red[colorDisplayMode]
  })
  $(".OC2-memberTable .OC2-itemHavetrue").css({
    "color": colorObj.recolor.green[colorDisplayMode]
  })
  $(".OC2-memberTable .OC2-itemHavefalse").css({
    "color": colorObj.recolor.red[colorDisplayMode]
  })
  $(".OC2-memberTable div.OC2-tableCrime").css({
    "width": "322px",
  })
  $(".OC2-crimeMouseoverWarning").css({
    "margin-left": "10px",
    "display": "inline-block",
    "color": colorObj.recolor.red[colorDisplayMode]
  })
  $(".OC2-memberTable div.OC2-tableCountdown").css({
    "width": "auto"
  })
  $(".OC2-memberTableFooter").css({
    "border-radius": "0 0 5px 5px",
    "background-color": colorObj.footerbg[colorDisplayMode],
    "padding": "5px 5px 5px 10px",
    "text-align": "center"
  })
  $(".hideCrimesButton").css({
    "position": "absolute",
    "right": "10px",
    "cursor": "pointer"
  })
  $(".hideLazyMembersButton").css({
    "position": "absolute",
    "right": "100px",
    "cursor": "pointer"
  })
  $(".OC2-memberTable li.OC2-memberAvailable").css({
    "padding-left": "20px"
  })
  $(".OC2-memberTable li.OC2-crimeMemberLi").css({
    "padding-left": "25px",
    "background-color": colorObj.crimeselectbg[colorDisplayMode],
    "width": containerMaxWidth
  })
  $(".OC2-memberTable li.OC2-crimeLi").not(".OC2-memberTable li[class*='OC2-titleLi']").css({
    "padding-left": "20px",
    "width": containerMaxWidth
  })
  $(".OC2-crimeLi").prev(".OC2-crimeMemberLi").css({
    "border-radius": "0 0 15px 15px"
  })
  $(".OC2-sortType").css({
    "display": "inline-block",
    "padding-left": parseInt(containerMaxWidth) - 158 + "px",
  })
  $(".OC2-sortType div").css({
    "display": "inline-block",
    "font-family": "Arial",
    "padding": "0 0 0 5px",
    "width": "35px"
  })
  $(".OC2-memberTable .OC2-sortType div").css({
    "text-decoration": "none"
  })
  $(".OC2-memberTable .OC2-sortType div.text-underline").css({
    "text-decoration": "underline"
  })
  $(".OC2-sortTypeMember").css({
    "display": "inline",
    "padding-left": parseInt(containerMaxWidth) - 220 + "px",
  })
  $(".OC2-sortTypeMember div").css({
    "display": "inline-block",
    "font-family": "Arial",
    "padding": "0 0 0 5px",
    "width": "35px"
  })
  $(".OC2-memberTable .OC2-sortTypeMember div").css({
    "text-decoration": "none"
  })
  $(".OC2-memberTable .OC2-sortTypeMember div.text-underline").css({
    "text-decoration": "underline"
  })
  $(".OC2-userIndicator").css({
    "background-color": colorObj.userindicatorbg[colorDisplayMode]
  })
  if (_isWindowTiny.matches) {
    styleTableTinyScreen()
  } else if (_isWindowSmall.matches) {
    styleTableSmallScreen()
  } else {
    styleTableBigScreen()
  }
  $(".OC2-horizLine").css({
    "border-bottom": "1px solid rgb(34,34,34)",
    "width": $(this).parent().width() + "px",
    "box-sizing": "border-box",
    "height": "3px",
  })
  if (($(".OC2-crimeMemberLi").last().next()).length < 1) {
    $(".OC2-crimeMemberLi").last().css({
      "border-radius": "0 0 15px 15px",
    })
  }
  $(".catCrimeTitle").css({
    "width": "120px",
    "display": "inline-block"
  })
  $(".toggleCatCrimesButton").css({
    "font-size": "11px",
    "font-family": "Arial",
    "width": "50px",
    //"margin": "0 0 0 50px",
    "cursor": "pointer",
  })
  //hide screen reader fields
  $(".screen-reader-hidden").css({
    "position": "absolute",
    "overflow": "hidden",
    "width": "1px",
    "height": "1px",
    "clip": "rect(1px, 1px, 1px, 1px)",
    "padding": "0",
    "border": "0",
    "white-space": "nowrap"
  })
}

function styleTableSmallScreen() {
  //console.log("Smallscreen!")
  //total row length: 386
  //margins: 20px <item> 10px
  //OC2-tableMember 260px
  $(".OC2-memberTable li.OC2-memberAvailable").css({
    "padding-left": "15px"
  })
  $(".OC2-hideSmall").css({
    "display": "none"
  })
  $(".OC2-memberTable div.OC2-tableStatus").css({
    "width": "55px",
    "text-align": "left"
  })
  $(".OC2-memberTable div.OC2-tableMember").css({
    "width": "120px",
  })
  $(".OC2-memberTable div.OC2-tableCrime").css({
    "width": "210px",
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberName").css({
    "width": "220px",
  })
  $(".OC2-statusText").not(".OC2-hideSmall").css({
    "display": ""
  })
  $(".OC2-memberTable div.OC2-tableCrimePosition").css({
    "width": "70px",
    "font-size": "10px",
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberName").css({
    "width": "220px",
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberStatus").css({
    "width": "90px",
    "text-align": "left"
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberSuccess").css({
    "width": "18px",
    "text-align": "center"
  })
  $(".OC2-memberTable div.OC2-tableCrimeMemberItem").css({
    "width": "30px",
    "text-align": "center"
  })
}

function styleTableTinyScreen() {
  //console.log("Tinyscreen!")
  $(".OC2-memberTable li.OC2-crimeLi").not(".OC2-memberTable li[class*='OC2-titleLi']").css({
    "padding-left": "15px",
    "width": containerMaxWidth
  })
  $(".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": "165px",
  })
  $(".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 .OC2-memberTable")[0]) {
      generateInsertHTML();
      if (!myAPIData) {
        try {
          let _successfulGetAPIData = await getAndAnalyzeAPIData()
          if (_successfulGetAPIData.error) {
            $(".OC2-memberTable").hide()
            $(".OC2-memberTableErrorDisplay").html(`<span style="margin-left: 20px">Error occured: ${_successfulGetAPIData.error.error}. Please visit the <a href="https://www.torn.com/OC2_Settings_Page" target="_new" style="color: inherit; font-weight: bold; text-decoration: underline">Settings Page</a> to set up an API key</span>`)
            $(".OC2-memberTableErrorDisplay").show()
          } else {
            putMemberInfoIntoTable()
          }
        } catch(_err) {
          //console.log("hashchangefunction caught error:", _err)
          return
        }
      } else {
        if (alreadyLoading == false) {
          putMemberInfoIntoTable()
        }
      }
    } else {
        $(".OC2-memberViewer").show()
    }
  } else {
    if ($(".OC2-memberViewer .OC2-memberTable")[0]) {
      $(".OC2-memberViewer").hide();
    }
  }
}

async function runOnceFunction() {
  //load saved data
  await getUserSettings()
  await getAPIKey()

  //prepare settings page if on correct URL
  if ($(location).attr("pathname").search("OC2_Settings_Page") >= 0) {
    $(document).prop('title', 'OC2 Overview - Settings | TORN');
    $("div.main-wrap").removeClass("error-404")
    $("div.content-title #skip-to-content").text("OC 2.0 Overview: Settings")
    prepareSettingsPage()
    getSavedValues()
    settingsFillSelect()
  }
  //insert member overview
  if (checkCrimesPage() || await checkTravelFactionPage()) {
    if (!$(".OC2-memberViewer .OC2-memberTable")[0]) {
      alreadyLoading = true;
      generateInsertHTML()
      if (!myAPIData) {
        try {
          let _successfulGetAPIData = await getAndAnalyzeAPIData()
          if (_successfulGetAPIData.error) {
            $(".OC2-memberTable").hide()
            $(".OC2-memberTableErrorDisplay").html(`<span style="margin-left: 20px">Error occured: ${_successfulGetAPIData.error.error}. Please visit the <a href="https://www.torn.com/OC2_Settings_Page" target="_new" style="color: inherit; font-weight: bold; text-decoration: underline">Settings Page</a> to set up an API key</span>`)
            $(".OC2-memberTableErrorDisplay").show()
          } else {
            putMemberInfoIntoTable()
          }
        } catch(_err) {
          //console.log("runOnceFunction caught error:", _err)
          return
        }
      }
    } else {
      $(".OC2-memberViewer").show()
    }
  }
  //sidebar notifier, but not if the sidebar doesn't exist
  if (userSettings.showSidebarOC == "sidebar-show") {
    if ( ($("div[class*='sidebar_'][class*='desktop_']")[0]) && (!isPDA()) ) {
      if (!myAPIData) {
        let _successfulGetAPIData = await getAndAnalyzeAPIData()
        if (_successfulGetAPIData.error) {
          return
        }
      }
      insertOCNotifier()
    }
  }
}

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

function checkWindowWidth() {
  if (_isWindowNormal.matches) {
    containerMaxWidth = "784px"
    containerBigMaxWidth = "976px"
  } else if (_isWindowSmallish.matches) {
    containerMaxWidth = "784px"
    containerBigMaxWidth = "578px"
  }else if (_isWindowSmall.matches) {
    containerMaxWidth = "386px"
    containerBigMaxWidth = "578px"
  } else if (_isWindowTiny.matches) {
    containerMaxWidth = "320px"
    containerBigMaxWidth = "320px"
  }
  if (!isPDA()) {
    styleTable()
    $(".OC2-crimeMemberLi").not(".crimeMemberLiShow").css({
      "display": "none"
    })
  }
}

_isWindowNormal.addEventListener("change", function() {
  checkWindowWidth()
});
_isWindowSmallish.addEventListener("change", function() {
  checkWindowWidth()
});
_isWindowSmall.addEventListener("change", function() {
  checkWindowWidth()
});
_isWindowTiny.addEventListener("change", function() {
  checkWindowWidth()
});

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

$("#dark-mode-state").on('change', modeChangeFunction)

function modeChangeFunction() {
  colorDisplayMode = $("body#body").hasClass("dark-mode") ? "darkmode" : "lightmode"
  styleTable()
  checkDefaultHideState()
  styleSettings()
}

/* ====
 * Settings page stuff
 * ====
*/

async function testAPIKey(_APIKey) {
  $(".OC2-APITestResults div[id^=OC2-APITestResult-]").remove()
  $(".OC2-APITestResults").append(`<div>Testing...</div>`)
  let _insertHTML = ""
  return await $.ajax({
    dataType: "json",
    url: (`https://api.torn.com/key/?selections=info&key=${_APIKey}`)
  }).then( data => {
    try {
      let _testResults = {
        "limited": data.access_level >= 2,
        "basic": data.selections.faction.includes("basic"),
        "crimes": data.selections.faction.includes("crimes"),
        "members": data.selections.faction.includes("members"),
        "items": data.selections.torn.includes("items"),
        "final": false
      }
      _testResults.final = (_testResults.limited && _testResults.basic && _testResults.crimes && _testResults.members && _testResults.items)
      let _insertHTML = (`
            <div id="OC2-APITestResult-Limited" class="${_testResults.limited ? "color-green" : "color-red"}">Minimal Key (or higher) ${_testResults.limited ? "OK" : "X"}</div>
            <div id="OC2-APITestResult-Basic" class="${_testResults.basic ? "color-green" : "color-red"}">/v2/factions/basic ${_testResults.basic ? "OK" : "X"}</div>
            <div id="OC2-APITestResult-Crimes" class="${_testResults.crimes ? "color-green" : "color-red"}">/v2/factions/crimes ${_testResults.crimes ? "OK" : "X"}</div>
            <div id="OC2-APITestResult-Members" class="${_testResults.members ? "color-green" : "color-red"}">/v2/factions/members ${_testResults.members ? "OK" : "X"}</div>
            <div id="OC2-APITestResult-Items" class="${_testResults.items ? "color-green" : "color-red"}">/v1/torn/items ${_testResults.items ? "OK" : "X"}</div>
            <div id="OC2-APITestResult-Final" class="${_testResults.final ? "color-green" : "color-red"}">${_testResults.final ? "API Key good to go!" : "API Key not usable!"}</div>
            <div id="OC2-APITestResult-Final" class="${_testResults.final ? "color-green" : "color-red"}">${_testResults.crimes && _testResults.members ? "Save settings to enable Member Ignore List" : `You do not have faction API permission. Please ask your faction leader to give you a role with "Faction API access" permissions.`}</div>
      `)
      $(".OC2-APITestResults div").remove()
      $(".OC2-APITestResults").append(_insertHTML)
    } catch(err) {
      $(".OC2-APITestResults div").remove()
      $(".OC2-APITestResults").append(`<div id="OC2-APITestResult-Fail" class="color-red">Error: API Key not valid</div>`)
    }
    $(".OC2-APITestResults div[id^=OC2-APITestResult-]").css({
      "margin-left": "15px"
    })
    $("div[id^=OC2-APITestResult-].color-green").css({
      "color": colorObj.recolor.green[colorDisplayMode]
    })
    $("div[id^=OC2-APITestResult-].color-red").css({
      "color": colorObj.recolor.red[colorDisplayMode]
    })
    $(".OC2-APITestButton").off().on("click", (event) => {
      APITestClickEvent(event)
    })
  })
}

async function setSavedValues() {
  let _userValues = {
    "memberShow": $("input[name=OC2-display-choice-availMembers]:checked")[0].value,
    "crimesShow": $("input[name=OC2-display-choice-crimes]:checked")[0].value, //crimes-hide / crimes-show
    "sortType": $("input[name=OC2-display-choice-crimes-sort]:checked")[0].value, //time-asc / time-desc / level-asc / level-desc
    "memberSort": $("input[name=OC2-display-choice-availMembers-sort]:checked")[0].value, //OC-asc / OC-desc / active-asc / active-desc
    "lastOC_yellow": $(`input[id=OC-indicator-yellow]`).val(),
    "lastOC_red": $(`input[id=OC-indicator-red]`).val(),
    "lastActivity": $(`input[id=activity-indicator]`).val(),
    "memberIgnoreList": displayIgnoreList,
    "showSidebarOC": $("input[name=OC2-display-choice-sidebarShow]:checked")[0].value, //sidebar-show / sidebar-hide
    "showNegativeTimes": $("input[name=OC2-display-choice-negative-timer]:checked")[0].value, //negative-timer-show / negative-timer-hide
    "warnLowSuccessPercentage": $(`input[id=warn-low-success]`).val(),
    "showProgressPercentage": $("input[name=OC2-display-crime-progress]:checked")[0].value, //progress-show / progress-hide
  }
  await setUserSettings(_userValues)
  await getUserSettings()
  if ( $("#OC2-APITestResult-Final").hasClass("color-green") ) {
    await saveAPIKey()
    $("#OC2-addToIgnoreButton").show()
    $("#OC2-ignoreMemberInput").prop("disabled", false)
    $("#OC2-ignoreMemberSelect").prop("disabled", false)
    $(".OC2-errorAPIKey").remove()
    settingsFillSelect()
  }
  getSavedValues()
}

async function saveAPIKey() {
  let _userKey = $("#OC2-APIInput").val()
  await GM.setValue("CMR_OC2_APIKey", _userKey)
  await getAPIKey()
}

async function replaceSavedValues() {
  displayIgnoreList = []
  userSettings = defaultUserSettings
  getSavedValues()
}

function getSavedValues() {
  $(`input[name=OC2-display-choice-availMembers][id=${userSettings?.memberShow}]`).prop("checked", true)
  $(`input[name=OC2-display-choice-crimes][id=${userSettings?.crimesShow}]`).prop("checked", true)
  $(`input[name=OC2-display-choice-crimes-sort][id=${userSettings?.sortType}]`).prop("checked", true)
  $(`input[name=OC2-display-choice-availMembers-sort][id=${userSettings?.memberSort}]`).prop("checked", true)
  $(`input[name=OC2-display-choice-sidebarShow][id=${userSettings?.showSidebarOC}]`).prop("checked", true)
  $(`input[name=OC2-display-choice-negative-timer][id=${userSettings?.showNegativeTimes}]`).prop("checked", true)
  $(`input[name=OC2-display-crime-progress][id=${userSettings?.showProgressPercentage}]`).prop("checked", true)
  $(`input[id=OC-indicator-yellow]`).val(userSettings?.lastOC_yellow)
  $(`input[id=OC-indicator-red]`).val(userSettings?.lastOC_red)
  $(`input[id=activity-indicator]`).val(userSettings?.lastActivity)
  $(`input[id=warn-low-success]`).val(userSettings?.warnLowSuccessPercentage)
  fillMemberIgnoreList()
}

function APITestClickEvent(_event) {
  $("#OC2-APIInput").val( $("#OC2-APIInput").val().trim() ) //trim trailing spaces
  let _testKey = $("#OC2-APIInput").val()
  testAPIKey(_testKey)
}

async function deleteAPIKey() {
  $(".OC2-errorAPIKey").remove()
  $(".OC2-APITestResults div").remove()
  if (isPDA()) {
    $(".OC2-APITestResults").append(`<div class="color-yellow">TornPDA: Unable to remove API Key via script because it's saved on TornPDA itself.</div>`)
    return
  }
  GM.setValue("CMR_OC2_APIKey", null)
  APIKey = null
  $(".OC2-APITestResults").append(`<div class="color-red">API Key removed from script memory. If you wish to be sure that your API key is secure, delete the provided key from torn's settings page.</div>`)
  $("#OC2-APIInput").val(APIKey? APIKey : "")
  settingsFillSelect()
}

function prepareSettingsPage() {
  let _displayAPIKey = "API Key saved in TornPDA"
  if (!isPDA()) {
    _displayAPIKey = APIKey? APIKey : ""
  }
  let _injectHTML = (`
    <div id="OC2-Settings" class="category-wrap m-top10">
    <div class="title-black top-round t-overflow">OC 2.0 Settings</div>
    <div class="cont-gray OC2-settingsTable"><ul class="table-body">
      <li class="table-cell OC2-settingsTitle"><div class="OC2-titleCell OC2-fancyBg">API Key</div></li>
      <li class="table-cell OC2-settingsSection">
        <div class="OC2-settingsCell"><input id="OC2-APIInput" type="textbox" style="line-height: 14px; padding: 10px 8px" value="${_displayAPIKey}" /><div id="OC2-APITestButton" class="OC2-button">Test API Key</div></div>
      </li>
      <li class="table-cell OC2-settingsSection">
        <div class="OC2-settingsCell"><div id="OC2-deleteAPIKeyButton" class="OC2-button">Delete API Key</div></div>
      </li>
      <li class="table-cell OC2-settingsSection">
          <div class="OC2-settingsCell OC2-APITestResults">
          </div>
      </li>
      <li class="table-cell OC2-horizLine"></li>
      <li class="table-cell OC2-memberAvailable OC2-settingsTitle"><div class="OC2-titleCell OC2-fancyBg">Preferences</div></li>
      <li class="table-cell OC2-settingsSection">
        <div class="OC2-settingsCell">
          <div class="OC2-settingsSubTitle">Default View</div>
            <fieldset class="OC2-choice">
              <legend>Show OC information in sidebar?</legend>
              <div class="OC2-choice-buttons">
                <input type="radio" name="OC2-display-choice-sidebarShow" id="sidebar-show" value="sidebar-show" /><label for="sidebar-show">Yes</label>
                <input type="radio" name="OC2-display-choice-sidebarShow" id="sidebar-hide" value="sidebar-hide" /><label for="sidebar-hide">No</label>
              </div>
            </fieldset>
            <fieldset class="OC2-choice">
              <legend>Allow negative crime countdown timer?</legend>
              <div class="OC2-choice-buttons">
                <input type="radio" name="OC2-display-choice-negative-timer" id="negative-timer-show" value="negative-timer-show" /><label for="negative-timer-show">Yes</label>
                <input type="radio" name="OC2-display-choice-negative-timer" id="negative-timer-hide" value="negative-timer-hide" /><label for="negative-timer-hide">No</label>
              </div>
            </fieldset>
            <fieldset class="OC2-choice">
              <legend>Show "Members not in an OC" section?</legend>
              <div class="OC2-choice-buttons">
                <input type="radio" name="OC2-display-choice-availMembers" id="member-show" value="member-show" /><label for="member-show">Yes</label>
                <input type="radio" name="OC2-display-choice-availMembers" id="member-hide" value="member-hide" /><label for="member-hide">No</label>
              </div>
            </fieldset>
            <fieldset class="OC2-choice">
              <legend>Default sort for "Members not in an OC"?</legend>
              <div class="OC2-choice-buttons">
                <input type="radio" name="OC2-display-choice-availMembers-sort" id="OC-desc" value="OC-desc" /><label for="OC-desc">OC: Asc&nbsp;&nbsp;</label>
                <input type="radio" name="OC2-display-choice-availMembers-sort" id="OC-asc" value="OC-asc" /><label for="OC-asc">OC: Desc</label>
                <input type="radio" name="OC2-display-choice-availMembers-sort" id="active-asc" value="active-asc" /><label for="active-asc">Active: Asc</label>
                <input type="radio" name="OC2-display-choice-availMembers-sort" id="active-desc" value="active-desc" /><label for="active-desc">Active: Desc</label>
              </div>
            </fieldset>
            <fieldset class="OC2-choice">
              <legend>Show "Crimes" section?</legend>
              <div class="OC2-choice-buttons">
                <input type="radio" name="OC2-display-choice-crimes" id="crimes-show" value="crimes-show" /><label for="crimes-show">Yes</label>
                <input type="radio" name="OC2-display-choice-crimes" id="crimes-hide" value="crimes-hide" /><label for="crimes-hide">No</label>
              </div>
            </fieldset>
            <fieldset class="OC2-choice">
              <legend>Default sort for "Crimes"?</legend>
              <div class="OC2-choice-buttons">
                <input type="radio" name="OC2-display-choice-crimes-sort" id="time-asc" value="time-asc" /><label for="time-asc">Time: Asc</label>
                <input type="radio" name="OC2-display-choice-crimes-sort" id="time-desc" value="time-desc" /><label for="time-desc">Time: Desc</label>
                <input type="radio" name="OC2-display-choice-crimes-sort" id="level-asc" value="level-asc" /><label for="level-asc">Level: Asc</label>
                <input type="radio" name="OC2-display-choice-crimes-sort" id="level-desc" value="level-desc" /><label for="level-desc">Level: Desc</label>
              </div>
            </fieldset>
            <fieldset class="OC2-choice">
              <legend>Show member crime progress?</legend>
              <div class="OC2-choice-buttons">
                <input type="radio" name="OC2-display-crime-progress" id="progress-show" value="progress-show" /><label for="progress-show">Yes</label>
                <input type="radio" name="OC2-display-crime-progress" id="progress-hide" value="progress-hide" /><label for="progress-hide">No</label>
              </div>
            </fieldset>
        </div>
      </li>
      <li class="table-cell OC2-horizLine"></li>
      <li class="table-cell OC2-settingsSection">
        <div class="OC2-settingsCell">
          <div class="OC2-settingsSubTitle">Highlight & Indicator settings</div>
          <ul>
            <li><div class="OC2-settingsText"><div class="OC2-settingsLabel">Time since last OC for 'yellow' highlight:</div><input id="OC-indicator-yellow" type="textbox" style="line-height: 12px; padding: 5px" placeholder="24" size="10" /> hours</div></li>
            <li><div class="OC2-settingsText"><div class="OC2-settingsLabel">Time since last OC for 'red' highlight:</div><input id="OC-indicator-red" type="textbox" style="line-height: 12px; padding: 5px" placeholder="48" size="10" /> hours</div></li>
            <li><div class="OC2-settingsText"><div class="OC2-settingsLabel">Time since last activity for 'inactive' indicator:</div><input id="activity-indicator" type="textbox" style="line-height: 12px; padding: 5px" palceholder="96" size="10" /> hours</div></li>
            <li><div class="OC2-settingsText"><div class="OC2-settingsLabel">Member slot 'low success warning' threshold:</div><input id="warn-low-success" type="textbox" style="line-height: 12px; padding: 5px" palceholder="50" size="10" /> %</div></li>
          </ul>
        </div>
      </li>
      <li class="table-cell OC2-horizLine"></li>
      <li class="table-cell OC2-settingsSection">
        <div class="OC2-settingsCell">
          <div class="OC2-settingsSubTitle">Member Ignore List</div>
          <div class="OC2-settingsText">
            <div class="OC2-memberIgnoreWrapper">Add member:
              <input id="OC2-ignoreMemberInput" type="textbox" />
              <select id="OC2-ignoreMemberSelect">
                <option class="default-option" value="" selected></option>
              </select>
              <div id="OC2-addToIgnoreButton" class="OC2-button">Add</div>
            </div>
          </div>
          <div class="OC2-settingsText">Ignored Members:
            <ul class="OC2-memberIgnoreList">
              <li class="OC2-ignoreTitles" style="font-weight: bold; margin-bottom: 5px"><div class="OC2-ignoreName">Member Name [id]</div> <div class="OC2-ignoreTime">Last Active Time</div><div class="OC2-ignoreButtons">Action</div></li>
            </ul>
          </div>
        </div>
      </li>
      <li class="table-cell OC2-horizLine"></li>
      <li class="table-cell OC2-settingsSection">
        <div class="OC2-settingsCell">
          <div id="OC2-buttonResult"></div>
          <div class="OC2-buttonDiv">
            <div id="OC2-APIResetButton" class="OC2-button">Reset to Default</div><div id="OC2-APISaveButton" class="OC2-button">Save Changes</div>
          </div>
        </div>
      </li>
    </ul></div>
    <div class="OC2-memberTableFooter OC2-settingsFooter"></div>
  </div>
  `)
  $("div.main-wrap").html(_injectHTML)
  //onclick functions
  $("#OC2-APITestButton").off().on("click", (event) => {
    APITestClickEvent(event)
  })
  $("#OC2-APISaveButton").off().on("click", (event) => {
    setSavedValues()
      .then( () => {
        $("#OC2-buttonResult").html("Settings saved!")
        if ($("#OC2-buttonResult").is(":visible")) {
          $("#OC2-buttonResult").slideUp("fast")
        }
        $("#OC2-buttonResult").slideDown("slow")
      })
  })
  $("#OC2-APIResetButton").off().on("click", (event) => {
    replaceSavedValues()
      .then( () => {
        $("#OC2-buttonResult").html("Settings reset to default")
        if ($("#OC2-buttonResult").is(":visible")) {
          $("#OC2-buttonResult").slideUp("fast")
        }
        $("#OC2-buttonResult").slideDown("slow")
      })
  })
  $("#OC2-deleteAPIKeyButton").off().on("click", (event) => {
    deleteAPIKey()
  })
  if (isPDA()) {
    $("#OC2-APIInput").prop("disabled", true)
    $("#OC2-APITestButton").off()
    $("#OC2-deleteAPIKeyButton").off()
  }
  $("#OC2-ignoreMemberInput").off()
  $("#OC2-ignoreMemberSelect").off()
  $("#OC2-ignoreMemberInput").on("keyup", (event) => {
    $("#OC2-ignoreMemberSelect option").not(".default-option").remove()
    let _insertOption = ""
    let _displayMemberList = myAPIData.members.filter((member) => (member.name.toLowerCase()).search(event.currentTarget.value.toLowerCase()) > -1)
    _displayMemberList.forEach( (member) => {
      _insertOption += (`<option value="${member.id}">${member.name} [${member.id}]</option>`)
    })
    $("#OC2-ignoreMemberSelect").append(_insertOption)
    $("#OC2-ignoreMemberSelect").attr("size", Math.min(7,_displayMemberList.length+1))
  })
  $("#OC2-ignoreMemberInput").on("focus", (event) => {
    $("#OC2-ignoreMemberSelect").attr("size", 7)
  })
  $("#OC2-ignoreMemberInput").on("blur", (event) => {
    if (event.relatedTarget != $("#OC2-ignoreMemberSelect")[0]) {
      $("#OC2-ignoreMemberSelect").attr("size", 0)
    }
  })
  $("#OC2-ignoreMemberSelect").on("change", (event) => {
    $("#OC2-ignoreMemberInput").val($("#OC2-ignoreMemberSelect :selected").text())
    $("#OC2-ignoreMemberSelect").attr("size", 0)
  })
  $("#OC2-addToIgnoreButton").off().on("click", (event) => {
    let _memberToIgnore = $("#OC2-ignoreMemberSelect :selected").val().toString()
    if (displayIgnoreList.includes(_memberToIgnore)) {
      return //do nothing
    } else {
      displayIgnoreList.push(_memberToIgnore)
      fillMemberIgnoreList()
    }
  })
  styleSettings()
}

function styleSettings() {
  colorDisplayMode = $("body#body").hasClass("dark-mode") ? "darkmode" : "lightmode"
  $(".OC2-buttonDiv").css({
    "display": "flex",
    "flex-direction": "row",
    "justify-content": "flex-end",
    "width": (isPDA)? parseInt(containerBigMaxWidth) - 30 + "px" : $(this).parent().width() - 50 + "px"
  })

  $("#OC2-buttonResult").css({
    "margin": "3px auto 8px -8px",
    "text-align": "center",
    "padding": "5px 0",
    "background-color": colorObj.userindicatorbg[colorDisplayMode],
    "display": "none",
    "width": "100%"
  })
  $(".OC2-APITestResults").css({
    "display": "flex",
    "flex-direction": "row",
    "flex-wrap": "wrap"
  })
  $(".OC2-choice").css({
    "display": "flex",
    "flex-direction": "row",
    "flex-wrap": "wrap"
  })
  $(".OC2-settingsSubTitle").css({
    "margin-bottom": "10px",
    "font-weight": "bold"
  })
  $(".OC2-settingsLabel").css({
    "width": "50%",
    "display": "inline-block"
  })
  $("fieldset.OC2-choice").css({
    "display": "inline-block",
    "width": "100%",
    "margin": "5px 0 5px 10px"
  })
  $("fieldset.OC2-choice .OC2-choice-buttons").css({
    "display": "flex",
    "flex-direction": "row",
  })
  $("fieldset.OC2-choice legend").css({
    "float": "left",
    "width": "50%"
  })
  $("fieldset.OC2-choice label").css({
    "display": "inline-block",
    "margin": "0 10px 0 5px"
  })
  $(".OC2-settingsText").css({
    "margin-left": "11px",
  })
  $("#OC2-Settings ul.table-body").css({
    "display": "flex",
    "flex-direction": "row",
    "flex-wrap": "wrap"
  })
  $("#OC2-Settings li.table-cell").css({
    "display": "flex",
    "flex-direction": "row",
    "width": "100%",
    "font-size": "12px",
  })
  $("#OC2-Settings .OC2-titleCell.OC2-fancyBg").css({
    "width": "100%",
    "background": "repeating-linear-gradient(90deg, #2e2e2e, #2e2e2e 2px, #282828 0, #282828 4px)",
    "padding": "5px 0",
    "padding-left": "10px",
    "font-weight": "bold",
    "color": colorObj.fancyBg[colorDisplayMode]
  })
  $("#OC2-Settings .OC2-settingsCell").css({
    "padding": "5px 0",
    "margin-left": "15px",
    "font-weight": "normal",
    "width": "100%"
  })
  $(".OC2-button").css({
    "margin-left": "5px",
    "padding": "5px 10px",
    "text-align": "center",
    "display": "inline-block",
    "background": colorObj.buttons.background[colorDisplayMode],
    "cursor": "pointer",
    "color": colorObj.buttons.textcolor[colorDisplayMode]
  })
  $(".OC2-button").on("mouseenter", function(event) {
    $(event.currentTarget).css({
      "background": colorObj.buttons.hovercolor[colorDisplayMode]
    })
  })
  $(".OC2-button").on("mouseleave", function(event) {
    $(event.currentTarget).css({
      "background": colorObj.buttons.background[colorDisplayMode]
    })
  })
  $(".OC2-memberIgnoreWrapper").css({
    "position": "relative",
    "margin-bottom": "15px",
    "margin-top": "10px",
    "display": "inline-block"
  })
  $("#OC2-addToIgnoreButton").css({
    "position": "absolute",
    "left": "310px",
    "top": "-4px"
  })
  $("#OC2-ignoreMemberSelect").css({
    "position": "absolute",
    "top": "0px",
    "left": "105px",
    "width": "200px",
  })
  $("#OC2-ignoreMemberInput").css({
    "position": "absolute",
    "top": "-7px",
    "left": "100px",
    "width": "200px",
    "padding": "5px",
    "z-index": "10"
  })
  styleMemberIgnoreList()
  if (_isWindowTiny.matches || _isWindowSmall.matches ) {
    styleSettingsTiny()
  }
  if (isPDA()) {
    $("#OC2-ignoreMemberInput").off()
    $("#OC2-ignoreMemberInput").css({
      "display": "none"
    })
    $("#OC2-APITestButton").hide()
    $("#OC2-deleteAPIKeyButton").hide()
  }
  $(".OC2-horizLine").css({
    "border-bottom": "1px solid rgb(34,34,34)",
    "width": $(this).parent().width() + "px",
    "box-sizing": "border-box",
    "height": "3px",
  })
}

function styleSettingsTiny() {
  $(".OC2-settingsCell").css({
    "margin-left": "10px"
  })
  $("fieldset.OC2-choice").css({
    "margin": "5px 0 5px 5px",
  })
  $("fieldset.OC2-choice legend").css({
    "float": "none",
    "margin-bottom": "5px",
  })
  $("fieldset.OC2-choice .OC2-choice-buttons").css({
    "margin-left": "10px",
  })
  $("fieldset.OC2-choice label").css({
    "margin": "0 3px 0 3px",
    "width": "100%"
  })
  $(".OC2-settingsText").css({
    "margin-left": "5px",
    "margin-bottom": "5px"
  })
  $("#OC2-addToIgnoreButton").css({
    "left": "240px",
    "top": "-4px"
  })
  $("#OC2-ignoreMemberSelect").css({
    "top": "0px",
    "left": "85px",
    "width": "150px",
  })
  $("#OC2-ignoreMemberInput").css({
    "top": "-7px",
    "left": "80px",
    "width": "150px",
    "padding": "5px",
    "z-index": "10"
  })
  $(".OC2-memberIgnoreList").css({
    "margin": "5px 0 0 0",
  })
  $(".OC2-memberIgnoreList li").not("li.OC2-ignoreTitles").css({
    "align-items": "center"
  })
  $(".OC2-ignoreName").css({
    "width": "120px"
  })
  $(".OC2-ignoreTime").css({
    "width": "120px",
  })
  $(".OC2-buttonDiv").css({
    "justify-content": "center",
    "padding-bottom": "50px"
  })
}

function styleMemberIgnoreList() {
  colorDisplayMode = $("body#body").hasClass("dark-mode") ? "darkmode" : "lightmode"
  $(".OC2-memberIgnoreList a").css({
    "color": "inherit"
  })
  $(".OC2-memberIgnoreList").css({
    "display": "flex",
    "flex-direction": "row",
    "margin": "5px 0 0 10px",
    "flex-wrap": "wrap"
  })
  $(".OC2-memberIgnoreList li").css({
    "display": "flex",
    "flex-direction": "row",
    "width": parseInt(containerBigMaxWidth) + "px",
    "padding": "3px 0",
    "align-items": "center"
  })
  $(".OC2-ignoreName").css({
    "width": "200px"
  })
  $(".OC2-ignoreTime").css({
    "width": "200px"
  })
  $(".OC2-ignoreButtons .OC2-button").css({
    "margin-top": "3px",
    "margin-left": "-10px",
    "padding": "5px 10px",
    "text-align": "center",
    "display": "inline-block",
    "background": colorObj.buttons.background[colorDisplayMode],
    "cursor": "pointer",
    "color": colorObj.buttons.textcolor[colorDisplayMode]
  })
  $(".OC2-button").on("mouseenter", function(event) {
    $(event.currentTarget).css({
      "background": colorObj.buttons.hovercolor[colorDisplayMode]
    })
  })
  $(".OC2-button").on("mouseleave", function(event) {
    $(event.currentTarget).css({
      "background": colorObj.buttons.background[colorDisplayMode]
    })
  })
  if (_isWindowSmall.matches || _isWindowTiny.matches ) {
    styleSettingsTiny()
  }
}

function fillMemberIgnoreList() {
  $(".OC2-memberIgnoreList li").not("li.OC2-ignoreTitles").remove()
  //this breaks easily lol
  if (!userSettings.memberIgnoreList) {
    return
  }
  if (!displayIgnoreList) {
    return
  }
  if (displayIgnoreList.length == 0) {
    return
  }
  displayIgnoreList.forEach( (_id) => {
    _id = _id.toString()
    if (!memberInfo[_id]) {
      return
    }
    $(".OC2-memberIgnoreList").append(`<li data-memberid="${_id}">
      <div class="OC2-ignoreName">${memberInfo[_id].name} [${_id}]</div>
      <div class="OC2-ignoreTime">${memberInfo[_id].last_action.relative}</div>
      <div class="OC2-ignoreButtons"><div class="OC2-button">Remove</div></div>
    </li>`)
    $(`li[data-memberid=${_id}] div.OC2-button`).off().on("click", (event) => {
      displayIgnoreList = displayIgnoreList.filter(item => item !== _id)
      fillMemberIgnoreList()
    })
  })
  styleMemberIgnoreList()
}

async function settingsFillSelect() {
  if (!myAPIData) {
    let _APITest = await getAndAnalyzeAPIData()
    if (_APITest.error) {
      $("#OC2-addToIgnoreButton").hide()
      $("#OC2-ignoreMemberInput").prop("disabled", true)
      $("#OC2-ignoreMemberSelect").prop("disabled", true)
      $(".OC2-memberIgnoreWrapper").parent().prepend(`<div class="color-red OC2-errorAPIKey">This function requires an API key to be registered</div>`)
      return
    }
  }
  let _insertOption = ""
  let _displayMemberList = myAPIData.members
  _displayMemberList.sort( (a,b) => (a.name).localeCompare(b.name))
  _displayMemberList.forEach( (member) => {
    _insertOption += (`<option value="${member.id}">${member.name} [${member.id}]</option>`)
  })
  $("#OC2-ignoreMemberSelect").append(_insertOption)
  //fill up displayIgnoreList, also syntax is like this as a PDA fix
  if (userSettings.memberIgnoreList) {
    displayIgnoreList = userSettings.memberIgnoreList
  } else {
    displayIgnoreList = [ ]
  }
  fillMemberIgnoreList()
}
长期地址
遇到问题?请前往 GitHub 提 Issues。