[TORN] OC 2.0 Helper with OC Timer

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

  1. // ==UserScript==
  2. // @name [TORN] OC 2.0 Helper with OC Timer
  3. // @namespace Violentmonkey Scripts
  4. // @match https://www.torn.com/*
  5. // @version 4
  6. // @author callmericky [3299880] / whatdoesthespacebardo / zachwozn [2301700]
  7. // @description Adds a list of members available for OC2.0, and adds a notifier to the sidebar if you are not in an OC.
  8. // @require http://code.jquery.com/jquery-3.6.0.min.js
  9. // @grant GM_registerMenuCommand
  10. // @grant GM.setValue
  11. // @grant GM.getValue
  12. // @license GNU GPLv3
  13. // ==/UserScript==
  14.  
  15. /*
  16. * All edits can be done from the dropdown menu from your userscript extension
  17. */
  18.  
  19. //IF DROPDOWN MENU DOESN'T WORK, MANUALLY ADD YOUR API KEY HERE
  20. var APIKey = "";
  21. const PDA_APIKey = "###PDA-APIKEY###"
  22.  
  23. //fix for tampermonkey
  24. var $ = window.jQuery;
  25.  
  26. //STOP CHANGING THINGS FROM HERE
  27. let memberInfo = {};
  28. let pageURL = $(location).attr("href");
  29. let totalMembers = 0;
  30. let availableMembers = 0;
  31. let activeMembers = 0;
  32. let userInfo = {};
  33. let crimeListUninitiated = []
  34. let crimeListRecruiting = []
  35. let crimeListPlanning = []
  36. let crimeList = []
  37. let myFactionInfo = null
  38. let itemIDObj = {}
  39. let containerMaxWidth = "784px"
  40. let userSettings = {
  41. "sortType": "time-asc", //time-asc / time-desc / level-asc / level-desc
  42. "display": "compact" //compact / verbose
  43. }
  44.  
  45. var OC2_timerID = null
  46.  
  47. var membersButtonShowText = (`⏵`) //⏵ ⏵
  48. var membersButtonHideText = (`⏷`) //⏷ ⏷
  49. if (isPDA()) {
  50. membersButtonShowText = (`▶`) //▶ ▶
  51. membersButtonHideText = (`▼`) //▼ ▼
  52. }
  53. const crimeButtonShowText = (`Show Crimes`)
  54. const crimeButtonHideText = (`Hide Crimes`)
  55. const settingsButtonAscText = (`▴`) //▴ ▴
  56. const settingsButtonDescText = (`▾`) // ▾ ▾
  57.  
  58. let _isWindowNormal = window.matchMedia("(min-width: 785px)")
  59. let _isWindowSmall = window.matchMedia("(max-width: 784px) and (min-width: 387px)")
  60. let _isWindowTiny = window.matchMedia("(max-width: 386px)")
  61.  
  62. function getUserID() {
  63. let _profileLink = $(".settings-menu > .link > a")[0]
  64. let _matchregex = /profiles\.php.+XID=(\d+)/i
  65. let _userID = _matchregex.exec(_profileLink)
  66. userInfo.id = _userID[1]
  67. return _userID[1]
  68. }
  69.  
  70. //GM_registerMenuCommand stuff
  71. if (!isPDA()) {
  72. const menu_command_1 = GM_registerMenuCommand("Set API Key (Limited + faction access)", menuCommand_APIKey)
  73. }
  74.  
  75. async function getAPIKey() {
  76. if (isPDA()) {
  77. APIKey = PDA_APIKey
  78. return PDA_APIKey
  79. } else {
  80. return await GM.getValue("CMR_OC2_APIKey", null)
  81. .then(function(data) {
  82. APIKey = data
  83. return data
  84. })
  85. }
  86. }
  87.  
  88. async function menuCommand_APIKey(event) {
  89. await getAPIKey()
  90. let _userKey = window.prompt("OC 2.0 participation and notifier\n- Requires a minimal API key with faction access\n\nSet API Key:", APIKey)
  91. if (APIKey) {
  92. return
  93. }
  94. if ((APIKey == null || APIKey == "")) {
  95. if ((_userKey == null) || (_userKey == "")) {
  96. window.alert("OC 2.0 participation and notifier\nYou did not set an API Key - this script will not run")
  97. }
  98. GM.setValue("CMR_OC2_APIKey", _userKey)
  99. }
  100. }
  101.  
  102. //boolean logic functions
  103. function isPDA() {
  104. const PDATestRegex = !/^(###).+(###)$/.test(PDA_APIKey);
  105. return PDATestRegex;
  106. }
  107.  
  108. function checkCrimesPage() {
  109. let pageURL = $(location).attr("href")
  110. return ((pageURL.search("step=your") >= 0) && (pageURL.search("tab=crimes") >= 0))
  111. }
  112.  
  113. async function checkTravelFactionPage() {
  114. let pageURL = $(location).attr("href")
  115. if ( ($('body').attr("data-traveling") == "true") || ($('body').attr("data-traveling") == true) || ($('body').attr("data-abroad") == "true") || ($('body').attr("data-abroad") == true) ) {
  116. if (!myFactionInfo) {
  117. await getAndAnalyzeAPIData()
  118. }
  119. if (pageURL.search("ID="+myFactionInfo.id) >= 0) {
  120. return true;
  121. }
  122. }
  123. return false;
  124. }
  125.  
  126. //API call functions
  127. async function getAndAnalyzeAPIData() {
  128. await $.getJSON(`https://api.torn.com/v2/faction/basic,crimes,members?key=${APIKey}&cat=available&offset=0&striptags=true&comment=OC2-helper`)
  129. .then(data => {
  130. checkMembersInCrimes(data)
  131. myFactionInfo = data.basic
  132. })
  133.  
  134. }
  135.  
  136. async function getItemNamesFromID(_arrayOfIds) {
  137. return await $.getJSON(`https://api.torn.com/torn/${_arrayOfIds.toString()}?selections=items&key=${APIKey}&comment=OC2-helper`)
  138. }
  139.  
  140. //calculations and conversions
  141. async function convertItemIDArrayToItems() {
  142. let _arrayOfIDs = []
  143. let _matchregex = /\<\#(\d+)\>/i
  144. for (var _key of Object.keys(itemIDObj)) {
  145. _arrayOfIDs.push(_key)
  146. }
  147. await getItemNamesFromID(_arrayOfIDs)
  148. .then( (_data) => {
  149. for (var _itemID of Object.keys(_data.items)) {
  150. itemIDObj[_itemID].name = _data.items[_itemID].name
  151. }
  152. })
  153. for (let i = 0; i < $(".OC2-tableCrimeMemberItem:has(*)").length; i++) {
  154. let _oldTitle = $(".OC2-tableCrimeMemberItem:has(*)").eq(i).attr("title")
  155. let _regexResult = _matchregex.exec($(".OC2-tableCrimeMemberItem:has(*)").eq(i).attr("title"))
  156. let _newTitle = _oldTitle.replace(_matchregex, `${itemIDObj[_regexResult[1]].name} <$1>`)
  157. $(".OC2-tableCrimeMemberItem:has(*)").eq(i).attr("title", _newTitle)
  158. }
  159. }
  160.  
  161. function timestampDiff(laterTimestamp) {
  162. var currentTimestamp = Math.floor(Date.now()/1000)
  163. var _returnString = ""
  164. var timeDiff = 0
  165. var _highlightClass = ""
  166.  
  167. if (laterTimestamp > currentTimestamp) {
  168. timeDiff = laterTimestamp - currentTimestamp
  169. }
  170.  
  171. if (timeDiff < 43200) { //12 hours = 12 * 60 * 60 = 43200
  172. _highlightClass = "OC2-highlightText"
  173. }
  174.  
  175. let _d = timeDiff < 86400 ? 0 : Math.floor(timeDiff/86400)
  176. let _h = timeDiff < 3600 ? 0 : Math.floor(timeDiff/3600) - _d*24 //24h in 1d
  177. let _m = timeDiff < 60 ? 0 : Math.floor(timeDiff/60) - _h*60 - _d*1440 //60m in 1h, 1440m in 1d
  178. let _s = timeDiff - _m*60 - _h*3600 - _d*86400 //60s in 1m, 3600s in 1h, 86400s in 1d
  179.  
  180. _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>`
  181.  
  182. return _returnString
  183. }
  184.  
  185. const timerTick = () => {
  186. let _timeList = $("span.OC2-countdown")
  187. for (let i = 0; i < _timeList.length; i++) {
  188. $(_timeList[i]).html(timestampDiff(parseInt($(_timeList[i]).attr("data-countdown"))))
  189. }
  190. OC2_timerID = setTimeout(timerTick, 1000)
  191. $(".OC2-highlightText").css({
  192. "color": "rgb(252, 196, 25)",
  193. "font-weight": "bold"
  194. })
  195. if ($("body").css("background-color") == "rgb(204, 204, 204)") {
  196. $(".OC2-highlightText").css({
  197. "color": "rgb(230, 119, 0)",
  198. "font-weight": "bold"
  199. })
  200. }
  201. }
  202.  
  203. //the nitty gritty functions
  204. function checkMembersInCrimes(_data) {
  205. //put all member ids into a list
  206. for (let i = 0; i < (_data.members).length; i++) {
  207. if (_data.members[i].status.state == "Fallen") {
  208. //skip fallen members
  209. } else if (_data.members[i].position == "Recruit") {
  210. //skip recruits since they can't join OC
  211. } else {
  212. memberInfo[_data.members[i].id] = {
  213. "name": _data.members[i].name,
  214. "last_action": _data.members[i].last_action.relative,
  215. "statusDesc": _data.members[i].status.description,
  216. "status": _data.members[i].status.state
  217. }
  218. }
  219. }
  220. totalMembers = (_data.members).length;
  221. activeMembers = 0 //if this doesn't reset, hashchange will cause the following part to re-fire and get activemembers count wrong.
  222.  
  223. //go through crime list
  224. for (let i = 0; i < (_data.crimes).length; i++) {
  225. //sort members into objects
  226. //if crime is not initiated, it will be null for planning_at. No point looking for members because there won't be any.
  227. if (_data.crimes[i].planning_at) {
  228. for (let j=0; j<(_data.crimes[i].slots).length; j++) {
  229. if (_data.crimes[i].slots[j].user_id) {
  230. memberInfo[_data.crimes[i].slots[j].user_id].crimeInfo = {
  231. "crimeName": _data.crimes[i].name,
  232. "crimeDifficulty": _data.crimes[i].difficulty,
  233. "crimeId": _data.crimes[i].id,
  234. "crimePosition": _data.crimes[i].slots[j].position,
  235. "crimeSuccess": _data.crimes[i].slots[j].success_chance,
  236. "readyAt": _data.crimes[i].ready_at
  237. }
  238. activeMembers = activeMembers + 1;
  239. if ((_data.crimes[i].slots[j].user_id) == getUserID()) {
  240. userInfo = memberInfo[_data.crimes[i].slots[j].user_id]
  241. }
  242. }
  243. }
  244. }
  245. //sort crimes into arrays
  246. //crimes in recruiting include both crimes with members (has planning_at and with no members (don't have planning_at)
  247. if (_data.crimes[i].status == "Recruiting") {
  248. //get crimes with no members
  249. if (_data.crimes[i].planning_at == null) {
  250. crimeListUninitiated.push(_data.crimes[i])
  251. } else {
  252. crimeListRecruiting.push(_data.crimes[i])
  253. }
  254. }
  255. //crimes filled with members move to status = planning
  256. if (_data.crimes[i].status == "Planning") {
  257. crimeListPlanning.push(_data.crimes[i])
  258. }
  259. }
  260.  
  261. availableMembers = totalMembers - activeMembers;
  262. }
  263.  
  264. function sortCrimeInfo() {
  265. //remove all crime Lis
  266. $(".OC2-memberViewer li.OC2-crimeLi").not(".OC2-memberViewer li[class*='OC2-titleLi']").remove()
  267. $("li.OC2-crimeMemberLi").remove()
  268. if (userSettings.sortType == "level-asc") {
  269. crimeListUninitiated.sort( (a,b) => a.difficulty - b.difficulty)
  270. crimeListRecruiting.sort( (a,b) => a.difficulty - b.difficulty)
  271. crimeListPlanning.sort( (a,b) => a.difficulty - b.difficulty)
  272. } else if (userSettings.sortType == "level-desc") {
  273. crimeListUninitiated.sort( (a,b) => b.difficulty - a.difficulty)
  274. crimeListRecruiting.sort( (a,b) => b.difficulty - a.difficulty)
  275. crimeListPlanning.sort( (a,b) => b.difficulty - a.difficulty)
  276. } 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.
  277. crimeListUninitiated.sort( (a,b) => a.expired_at - b.expired_at)
  278. crimeListRecruiting.sort( (a,b) => a.ready_at - b.ready_at)
  279. crimeListPlanning.sort( (a,b) => a.ready_at - b.ready_at)
  280. } else if (userSettings.sortType == "time-asc") {
  281. crimeListUninitiated.sort( (a,b) => b.expired_at - a.expired_at)
  282. crimeListRecruiting.sort( (a,b) => b.ready_at - a.ready_at)
  283. crimeListPlanning.sort( (a,b) => b.ready_at - a.ready_at)
  284. }
  285. }
  286.  
  287. function putMemberInfoIntoTable() {
  288. //fix for tornPDA, idk why but checking the li.tablecell works but checking the ul.table doesn't
  289. if ($(".OC2-memberTable .OC2-tableCell")[0]) {
  290. return
  291. }
  292. for (var _key of Object.keys(memberInfo)) {
  293. let _outputHTML = ""
  294. let _memberHighlight = "OC2-memberAvailable"
  295. if (_key == userInfo.id) {
  296. _memberHighlight += " OC2-userIndicator"
  297. }
  298. _outputHTML = (`<li class="table-cell ${_memberHighlight}">
  299. <div class="OC2-tableCell OC2-tableMember"><a href="https://www.torn.com/profiles.php?XID=${_key}">${memberInfo[_key].name}</a></div>
  300. <div class="OC2-tableCell OC2-tableStatus">${styleMemberStatus(memberInfo[_key].status,memberInfo[_key].statusDesc)}</div>
  301. </li>`)
  302. //put all the members currently in crimes below
  303. if (!(memberInfo[_key].crimeInfo)) {
  304. $(".OC2-memberTable li.OC2-titleLiAvailableMembers").after(_outputHTML)
  305. }
  306. }
  307. $(".OC2-memberTableFooter").not(".OC2-settingsFooter").text(`${availableMembers} / ${totalMembers} members available (${activeMembers} in an OC)`)
  308.  
  309. sortCrimeInfo()
  310. putCrimeInfoIntoTable(crimeListUninitiated, $(".OC2-memberTable li.OC2-titleLiUninitiated").eq(0))
  311. putCrimeInfoIntoTable(crimeListRecruiting, $(".OC2-memberTable li.OC2-titleLiRecruiting").eq(0))
  312. putCrimeInfoIntoTable(crimeListPlanning, $(".OC2-memberTable li.OC2-titleLiPlanning").eq(0))
  313.  
  314. convertItemIDArrayToItems()
  315. styleTable()
  316. if (OC2_timerID) {
  317. clearTimeout(OC2_timerID)
  318. }
  319. timerTick()
  320. }
  321.  
  322. function putCrimeInfoIntoTable(_crimeArray, _afterElm) {
  323. let _countdownText = ""
  324. let _countdownToTimestamp = ""
  325. let _countdownMouseover = ""
  326. let _memberOutputHTML = ""
  327. let _userIndicatorClass = ""
  328. let _userIndicatorCrimeClass = ""
  329. if (_crimeArray.length > 0) {
  330. for (let i = 0; i < _crimeArray.length; i++) {
  331. //crimes in recruiting include both crimes with members (has planning_at) and with no members (don't have planning_at)
  332. if (_crimeArray[i].status == "Recruiting") {
  333. //get crimes with no members
  334. if (_crimeArray[i].planning_at == null) {
  335. _countdownText = "Expires: "
  336. _countdownToTimestamp = _crimeArray[i].expired_at
  337. _countdownMouseover = "Time until this crime is no longer be available"
  338. } else {
  339. _countdownText = "Join in:"
  340. _countdownToTimestamp = _crimeArray[i].ready_at
  341. _countdownMouseover = "Time until this crime needs a new member to join to continue planning"
  342. }
  343. }
  344. //crimes filled with members move to status = planning
  345. if (_crimeArray[i].status == "Planning") {
  346. _countdownText = "Ready in:"
  347. _countdownToTimestamp = _crimeArray[i].ready_at
  348. _countdownMouseover = "Time until this crime is ready to start"
  349. }
  350.  
  351. _memberOutputHTML = ""
  352. _userIndicatorCrimeClass = ""
  353. let _memberCount = 0
  354. //count number of slots filled
  355. for (let j = 0; j < (_crimeArray[i].slots).length; j++) {
  356. let _crimeSlotMemberName = `<span class="OC2-textGray">&nbsp;&nbsp;&nbsp;&nbsp;N/A</span>`
  357. let _crimeSlotMemberID = ""
  358. let _crimeSlotMemberStatus = ""
  359. let _crimeSlotPosition = ""
  360. let _crimeItem = ""
  361. let _crimeItemMouseover = ""
  362. let _crimeItemIcon = (`&#128736;`) //🛠 &#128736;
  363. if (isPDA()) {
  364. _crimeItemIcon = (`&#9874;`) // ⚒ $#9874;
  365. }
  366. let _crimeSuccess = ""
  367. let _crimeSuccessWrapper = ""
  368. _userIndicatorClass = ""
  369. if (_crimeArray[i].slots[j].item_requirement) {
  370. if(!(_crimeArray[i].slots[j].item_requirement.id in itemIDObj)) {
  371. itemIDObj[_crimeArray[i].slots[j].item_requirement.id] = {
  372. "name": ""
  373. };
  374. }
  375. _crimeItemMouseover = (`Required item: <#${_crimeArray[i].slots[j].item_requirement.id}>`)
  376. if (_crimeArray[i].slots[j].item_requirement.is_reusable) {
  377. _crimeItemMouseover += (` (reusable)`)
  378. _crimeItemIcon += (`<span style="vertical-align:top">∞</span>`)
  379. }
  380. if (_crimeArray[i].slots[j].item_requirement.is_available) {
  381. _crimeItemMouseover += (`<br />Item is owned by member`)
  382. }
  383. _crimeItem = (`<span class="OC2-itemHave${_crimeArray[i].slots[j].item_requirement.is_available}">${_crimeItemIcon}</span>`)
  384. }
  385. if (_crimeArray[i].slots[j].user_id) {
  386. if (_crimeArray[i].slots[j].user_id == userInfo.id) {
  387. _userIndicatorClass = "OC2-userIndicator"
  388. _userIndicatorCrimeClass = "OC2-userIndicator"
  389. }
  390. _memberCount = _memberCount + 1
  391. _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>`
  392. _crimeSlotMemberStatus = styleMemberStatus(memberInfo[_crimeArray[i].slots[j].user_id].status, memberInfo[_crimeArray[i].slots[j].user_id].statusDesc)
  393. if (_crimeArray[i].slots[j].success_chance > 90) {
  394. _crimeSuccessWrapper = "OC2-highSuccess"
  395. } else if (_crimeArray[i].slots[j].success_chance > 50) {
  396. _crimeSuccessWrapper = "OC2-midSuccess"
  397. } else {
  398. _crimeSuccessWrapper = "OC2-lowSuccess"
  399. }
  400. _crimeSuccess = (`<span class="${_crimeSuccessWrapper}">${_crimeArray[i].slots[j].success_chance}</span>`)
  401. } else {
  402. _crimeSuccess = (`<span class="OC2-textGray">-</span>`)
  403. }
  404. _memberOutputHTML += (`<li class="table-cell OC2-crimeMemberLi OC2-crimeID_${_crimeArray[i].id} ${_userIndicatorClass}">
  405. <div class="OC2-tableCell OC2-tableCrimeMemberSuccess">${_crimeSuccess}</div>
  406. <div class="OC2-tableCell OC2-tableCrimeMemberItem" title="${_crimeItemMouseover}" >${_crimeItem}</div>
  407. <div class="OC2-tableCell OC2-hideSmall OC2-tableCrimePosition">${_crimeArray[i].slots[j].position}</div>
  408. <div class="OC2-tableCell OC2-tableCrimeMemberName">${_crimeSlotMemberName}</div>
  409. <div class="OC2-tableCell OC2-tableCrimeMemberStatus">${_crimeSlotMemberStatus}</div>
  410. </li>`)
  411. }
  412. let _outputHTML = (`<li class="table-cell OC2-crimeLi OC2-crimeID_${_crimeArray[i].id} ${_userIndicatorCrimeClass}">
  413. <div class="OC2-tableCell OC2-tableCrimeMemberCount OC2-crimeID_${_crimeArray[i].id}">${_memberCount} / ${(_crimeArray[i].slots).length} <span class="hideMembersButton">${membersButtonShowText}</span></div>
  414. <div class="OC2-tableCell OC2-tableCrime"><a href="https://www.torn.com/factions.php?step=your&type=12#/tab=crimes&crimeId=${_crimeArray[i].id}">Lv${_crimeArray[i].difficulty} ${_crimeArray[i].name}</a></div>
  415. <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>
  416. </li>`)
  417. _afterElm.after(_outputHTML)
  418. $("li.OC2-crimeID_"+_crimeArray[i].id).after(_memberOutputHTML)
  419. $("div.OC2-crimeID_"+_crimeArray[i].id).off().on("click", (event) => {
  420. toggleMemberView(((event.currentTarget.attributes.class.value).split("OC2-crimeID_"))[1])
  421. if ($(event.currentTarget).parent(".OC2-crimeLi").hasClass("OC2-crimeLiActive")) {
  422. $(event.currentTarget).parent(".OC2-crimeLi.OC2-crimeLiActive").removeClass("OC2-crimeLiActive")
  423. } else {
  424. $(event.currentTarget).parent(".OC2-crimeLi").not(".OC2-crimeLiActive").addClass("OC2-crimeLiActive")
  425. }
  426. styleCrimeLiActive()
  427. }).css({"cursor": "pointer"})
  428. }
  429. } else {
  430. _afterElm.after(`<li class="table-cell OC2-crimeLi"><div class="OC2-tableCell OC2-tableCrime">None</div></li>`)
  431. }
  432. }
  433.  
  434. //templating functions
  435. async function generateInsertHTML() {
  436. let _insertHTML = (`
  437. <div class="category-wrap OC2-memberViewer m-top10">
  438. <div class="title-black top-round t-overflow">OC 2.0 Overview <span class="hideCrimesButton">${crimeButtonShowText}</span></div>
  439. <div class="cont-gray OC2-memberTable"><ul class="table-body">
  440. <li class="table-cell OC2-availableMembers OC2-titleLiAvailableMembers"><div class="OC2-titleCell OC2-fancyBg">Members not in an OC</div></li>
  441. <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>
  442. <li class="table-cell OC2-crimeLi OC2-titleLiUninitiated"><div class="OC2-titleCell">Uninitiated Crimes</div></li>
  443. <li class="table-cell OC2-crimeLi OC2-horizLine"></li>
  444. <li class="table-cell OC2-crimeLi OC2-titleLiRecruiting"><div class="OC2-titleCell">Recruiting Crimes</div></li>
  445. <li class="table-cell OC2-crimeLi OC2-horizLine"></li>
  446. <li class="table-cell OC2-crimeLi OC2-titleLiPlanning"><div class="OC2-titleCell">Full Crimes</div></li>
  447. </ul></div>
  448. <div class="OC2-memberTableFooter"></div>
  449. </div>`)
  450. //what to do in normal crimes2.0 page
  451. if (checkCrimesPage()) {
  452. $("div#faction-crimes").before(_insertHTML)
  453. putMemberInfoIntoTable()
  454. styleTable()
  455. $(".hideCrimesButton").off().on("click", event => {
  456. toggleCrimeView()
  457. })
  458. $(".OC2-sortType div").off().on("click", event => {
  459. resortCrimeTable(event.currentTarget)
  460. })
  461. $(".OC2-crimeMemberLi").hide();
  462. $(".OC2-crimeLi").hide();
  463. } 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
  464. //what to do if traveling AND on faction page
  465. if (await checkTravelFactionPage()) {
  466. waitForElm('div#react-root').then((elm) => {
  467. $(elm).before(_insertHTML)
  468. putMemberInfoIntoTable()
  469. styleTable()
  470. $(".hideCrimesButton").off().on("click", event => {
  471. toggleCrimeView()
  472. })
  473. $(".OC2-sortType div").off().on("click", event => {
  474. resortCrimeTable(event.currentTarget)
  475. })
  476. $(".OC2-crimeMemberLi").hide();
  477. $(".OC2-crimeLi").hide();
  478. })
  479. }
  480. }
  481.  
  482. function insertOCNotifier() {
  483. let _userNotice = (`<a href="https://www.torn.com/factions.php?step=your#/tab=crimes"><span class="OC2-redtext">No active OC.</span></a>`)
  484. if (userInfo.crimeInfo) {
  485. _userNotice = (`<a href="https://www.torn.com/factions.php?step=your&type=5#/tab=crimes&crimeId=${userInfo.crimeInfo.crimeId}"><span class="OC2-countdown" data-countdown="${userInfo.crimeInfo.readyAt}"></span> <span class="OC2-normaltext">(Lv ${userInfo.crimeInfo.crimeDifficulty})</a></span> `)
  486. }
  487. let _insertHTML = (`<div class="OC2-sidebarNotice"><a href="https://www.torn.com/factions.php?step=your#/tab=crimes"><span style="font-weight: bold">OC 2.0:</span></a>
  488. ${_userNotice}
  489. </div>`)
  490. $('div[class^="sidebar_"] div[class^="user-information_"] div[class^="toggle-block_"] div[class^="toggle-content_"] div[class^="content_"]').append(_insertHTML)
  491. timerTick()
  492. styleOCNotifier()
  493. }
  494.  
  495. //on click functions
  496. function toggleCrimeView() {
  497. if ($(".hideCrimesButton").hasClass("text-hide")) {
  498. $(".hideCrimesButton").html(crimeButtonShowText)
  499. $(".hideCrimesButton").removeClass("text-hide")
  500. $(".OC2-crimeLi").slideUp()
  501. } else {
  502. $(".hideCrimesButton").html(crimeButtonHideText)
  503. $(".hideCrimesButton").addClass("text-hide")
  504. $(".OC2-crimeLi").slideDown()
  505. }
  506. $(".hideMembersButton").html(membersButtonShowText)
  507. $(".hideMembersButton").removeClass("text-hide")
  508. $(".OC2-crimeMemberLi").hide()
  509. $(".OC2-crimeLi.OC2-crimeLiActive").removeClass("OC2-crimeLiActive")
  510. styleCrimeLiActive()
  511. }
  512.  
  513. function toggleMemberView(_crimeID) {
  514. if ($(".OC2-crimeID_"+_crimeID+" .hideMembersButton").hasClass("text-hide")) {
  515. $(".OC2-crimeID_"+_crimeID+" .hideMembersButton").html(membersButtonShowText)
  516. $(".OC2-crimeID_"+_crimeID+" .hideMembersButton").removeClass("text-hide")
  517. $(".OC2-crimeMemberLi.OC2-crimeID_"+_crimeID).slideUp()
  518. } else {
  519. $(".OC2-crimeID_"+_crimeID+" .hideMembersButton").html(membersButtonHideText)
  520. $(".OC2-crimeID_"+_crimeID+" .hideMembersButton").addClass("text-hide")
  521. $(".OC2-crimeMemberLi.OC2-crimeID_"+_crimeID).slideDown()
  522. }
  523. }
  524.  
  525. //<div class="OC2-sortType">Sort: <span id="sortLevelButton">level</span><span id="sortTimeButton">time</span></div>
  526. function resortCrimeTable(_target) {
  527. $(".OC2-sortType div").removeClass("text-underline")
  528. $(".OC2-sortType div#sortLevelButton").html("level")
  529. $(".OC2-sortType div#sortTimeButton").html("time")
  530. $(_target).addClass("text-underline")
  531. if ($(_target).attr("id") == "sortLevelButton") {
  532. if (userSettings.sortType == "level-asc") {
  533. userSettings.sortType = "level-desc"
  534. $(_target).html(`level${settingsButtonAscText}`)
  535. } else {
  536. userSettings.sortType = "level-asc"
  537. $(_target).html(`level${settingsButtonDescText}`)
  538. }
  539. }
  540. if ($(_target).attr("id") == "sortTimeButton") {
  541. if (userSettings.sortType == "time-asc") {
  542. userSettings.sortType = "time-desc"
  543. $(_target).html(`time${settingsButtonDescText}`)
  544. } else {
  545. userSettings.sortType = "time-asc"
  546. $(_target).html(`time${settingsButtonAscText}`)
  547. }
  548. }
  549. sortCrimeInfo()
  550. putCrimeInfoIntoTable(crimeListUninitiated, $(".OC2-memberTable li.OC2-titleLiUninitiated").eq(0))
  551. putCrimeInfoIntoTable(crimeListRecruiting, $(".OC2-memberTable li.OC2-titleLiRecruiting").eq(0))
  552. putCrimeInfoIntoTable(crimeListPlanning, $(".OC2-memberTable li.OC2-titleLiPlanning").eq(0))
  553. convertItemIDArrayToItems()
  554. styleTable()
  555. timerTick()
  556. $("li.OC2-crimeLi .hideMembersButton").removeClass("text-hide")
  557. $("li.OC2-crimeMemberLi").hide()
  558. }
  559.  
  560. //prettifying functions
  561. function styleCrimeLiActive() {
  562. $(".OC2-crimeLi.OC2-crimeLiActive").not(".OC2-userIndicator").css({
  563. "background-color": "rgba(150,150,150,0.2)"
  564. })
  565. $(".OC2-crimeLi").not(".OC2-crimeLiActive").not(".OC2-userIndicator").css({
  566. "background-color": "transparent"
  567. })
  568. if ($("#dark-mode-state").prop("checked")) {
  569. $(".OC2-crimeLi.OC2-crimeLiActive").not(".OC2-userIndicator").css({
  570. "background-color": "rgba(0,0,0,0.3)"
  571. })
  572. }
  573. }
  574.  
  575. function styleMemberStatus(_statusState, _statusDesc) {
  576. let _outputText = ""
  577. let _country = ""
  578. return (`<span title="${_statusDesc}" class="OC2-statusText ${_statusState.toLowerCase()}">${_statusState}</span><span title="${_statusDesc}" class="OC2-statusText OC2-hideSmall ${_statusState.toLowerCase()}">${_statusDesc}</span>`)
  579. }
  580.  
  581. function styleTable() {
  582. /* notes to self
  583. * Small: 386px
  584. * Tiny: 320px
  585. * Normal: 784px
  586. */
  587. //set colours for light/dark mode styling
  588. let colorObj = {
  589. "link": {
  590. "darkmode": "rgb(116, 192, 252)",
  591. "lightmode": "#006699"
  592. },
  593. "recolor": {
  594. "green": {
  595. "darkmode": "rgb(130, 201, 30)",
  596. "lightmode": "rgb(92, 148, 13)"
  597. },
  598. "yellow": {
  599. "darkmode": "rgb(252, 196, 25)",
  600. "lightmode": "rgb(230, 119, 0)"
  601. },
  602. "red": {
  603. "darkmode": "rgb(255, 135, 135)",
  604. "lightmode": "rgb(255, 121, 76)"
  605. },
  606. "blue": {
  607. "darkmode": "rgb(59, 201, 219)",
  608. "lightmode": "rgb(12, 133, 153)"
  609. }
  610. },
  611. "userindicatorbg": {
  612. "darkmode": "rgb(63, 68, 45)",
  613. "lightmode": "rgb(238, 241, 228)"
  614. },
  615. "footerbg": {
  616. "darkmode": "rgb(51, 51, 51)",
  617. "lightmode": "rgb(242, 242, 242)"
  618. },
  619. "crimeselectbg": {
  620. "darkmode": "rgba(0,0,0,0.2)",
  621. "lightmode": "rgba(150,150,150,0.1)"
  622. },
  623. "fancyBg": {
  624. "darkmode": "inherit",
  625. "lightmode": "#fff"
  626. }
  627.  
  628. }
  629. let colorDisplayMode = $("#dark-mode-state").prop("checked") ? "darkmode" : "lightmode"
  630.  
  631. $(".OC2-memberTable ul.table-body").css({
  632. "display": "flex",
  633. "flex-direction": "row",
  634. "flex-wrap": "wrap"
  635. })
  636. $(".OC2-memberTable a").css({
  637. "color": colorObj.link[colorDisplayMode],
  638. "text-decoration": "none"
  639. })
  640. $(".OC2-memberTable a").hover(
  641. function() {
  642. $(this).css({
  643. "text-decoration": "underline"
  644. })
  645. },
  646. function() {
  647. $(this).css({
  648. "text-decoration": "none"
  649. })
  650. })
  651. $(".OC2-memberTable div.OC2-tableCrimeMemberName a").css({
  652. "color": "inherit",
  653. })
  654. $(".OC2-memberTable div.OC2-tableMember a").css({
  655. "color": "inherit",
  656. })
  657. $(".OC2-memberTable li.table-cell").css({
  658. "display": "flex",
  659. "flex-direction": "row",
  660. })
  661. $(".OC2-memberTable li.OC2-memberAvailable").css({
  662. "font-weight": "normal",
  663. })
  664. $(".OC2-memberTable .OC2-textGray").css({
  665. "color": "rgb(153, 153, 153)",
  666. })
  667. $(".OC2-memberTable div").css({
  668. "font-size": "11px",
  669. "line-height": "12px",
  670. "padding": "5px 0"
  671. })
  672. $(".OC2-memberTable div.OC2-titleCell").css({
  673. "display": "inline",
  674. "font-family": "Fjalla One",
  675. "padding-left": "10px",
  676. "width": containerMaxWidth,
  677. "box-sizing": "border-box",
  678. })
  679. $(".OC2-memberTable div.OC2-titleCell.OC2-fancyBg").css({
  680. "background": "repeating-linear-gradient(90deg, #2e2e2e, #2e2e2e 2px, #282828 0, #282828 4px)",
  681. "color": colorObj.fancyBg[colorDisplayMode]
  682. })
  683. $(".OC2-horizLine").css({
  684. "border-bottom": "1px solid rgb(34,34,34)",
  685. "width": containerMaxWidth,
  686. "box-sizing": "border-box",
  687. "height": "3px",
  688. })
  689. $(".OC2-memberTable div.OC2-tableMember").css({
  690. "width": "150px",
  691. })
  692. $(".OC2-memberTable div.OC2-tableStatus").css({
  693. "width": "220px",
  694. "text-align": "left"
  695. })
  696. //compact mode - COMING SOON
  697. $(".OC2-memberTable div.OC2-tableMember.compact").css({
  698. "width": "150px",
  699. })
  700. $(".OC2-memberTable div.OC2-tableStatus.compact").css({
  701. "width": "150px",
  702. "text-align": "center"
  703. })
  704. $(".OC2-statusText.okay").css({
  705. "color": colorObj.recolor.green[colorDisplayMode]
  706. })
  707. $(".OC2-statusText.abroad, .OC2-statusText.traveling").css({
  708. "color": colorObj.recolor.blue[colorDisplayMode]
  709. })
  710. $(".OC2-statusText.hospital").css({
  711. "color": colorObj.recolor.red[colorDisplayMode]
  712. })
  713. $(".OC2-statusText.jail").css({
  714. "color": colorObj.recolor.red[colorDisplayMode]
  715. })
  716. $(".OC2-memberTable div.OC2-tableStatus img").css({
  717. "height": "11px"
  718. })
  719. $(".OC2-memberTable div.OC2-tableCrimeMemberCount").css({
  720. "width": "50px",
  721. })
  722. $(".OC2-memberTable div.OC2-tableCrimePosition").css({
  723. "width": "100px",
  724. "font-size": "10px",
  725. })
  726. $(".OC2-memberTable div.OC2-tableCrimeMemberName").css({
  727. "width": "230px",
  728. })
  729. $(".OC2-memberTable div.OC2-tableCrimeMemberStatus").css({
  730. "width": "auto",
  731. })
  732. $(".OC2-memberTable div.OC2-tableCrimeMemberSuccess").css({
  733. "width": "25px",
  734. "text-align": "center"
  735. })
  736. $(".OC2-memberTable div.OC2-tableCrimeMemberItem").css({
  737. "width": "40px",
  738. "text-align": "center"
  739. })
  740. $(".OC2-memberTable .OC2-tableCrimeMemberSuccess .OC2-highSuccess").css({
  741. "color": colorObj.recolor.green[colorDisplayMode]
  742. })
  743. $(".OC2-memberTable .OC2-tableCrimeMemberSuccess .OC2-midSuccess").css({
  744. "color": colorObj.recolor.yellow[colorDisplayMode]
  745. })
  746. $(".OC2-memberTable .OC2-tableCrimeMemberSuccess .OC2-lowSuccess").css({
  747. "color": colorObj.recolor.red[colorDisplayMode]
  748. })
  749. $(".OC2-memberTable .OC2-itemHavetrue").css({
  750. "color": colorObj.recolor.green[colorDisplayMode]
  751. })
  752. $(".OC2-memberTable .OC2-itemHavefalse").css({
  753. "color": colorObj.recolor.red[colorDisplayMode]
  754. })
  755. $(".OC2-memberTable div.OC2-tableCrime").css({
  756. "width": "335px",
  757. })
  758. $(".OC2-memberTable div.OC2-tableCountdown").css({
  759. "width": "auto"
  760. })
  761. $(".OC2-memberTableFooter").css({
  762. "border-radius": "0 0 5px 5px",
  763. "background-color": colorObj.footerbg[colorDisplayMode],
  764. "padding": "5px 5px 5px 10px",
  765. "text-align": "center"
  766. })
  767. $(".hideCrimesButton").css({
  768. "position": "absolute",
  769. "right": "10px",
  770. "cursor": "pointer"
  771. })
  772. $(".OC2-memberTable li.OC2-memberAvailable").css({
  773. "padding-left": "20px"
  774. })
  775. $(".OC2-memberTable li.OC2-crimeMemberLi").css({
  776. "padding-left": "25px",
  777. "background-color": colorObj.crimeselectbg[colorDisplayMode],
  778. "width": containerMaxWidth
  779. })
  780. $(".OC2-memberTable li.OC2-crimeLi").not(".OC2-memberTable li[class*='OC2-titleLi']").css({
  781. "padding-left": "20px",
  782. "width": containerMaxWidth
  783. })
  784. $(".OC2-crimeLi").prev(".OC2-crimeMemberLi").css({
  785. "border-radius": "0 0 15px 15px"
  786. })
  787. $(".OC2-sortType").css({
  788. "display": "inline",
  789. "padding-left": parseInt(containerMaxWidth) - 150 + "px",
  790. })
  791. $(".OC2-sortType div").css({
  792. "display": "inline-block",
  793. "font-family": "Arial",
  794. "padding": "0 0 0 5px",
  795. "width": "35px"
  796. })
  797. $(".OC2-memberTable .OC2-sortType div").css({
  798. "text-decoration": "none"
  799. })
  800. $(".OC2-memberTable .OC2-sortType div.text-underline").css({
  801. "text-decoration": "underline"
  802. })
  803. $(".OC2-userIndicator").css({
  804. "background-color": colorObj.userindicatorbg[colorDisplayMode]
  805. })
  806. if (_isWindowTiny.matches) {
  807. styleTableTinyScreen()
  808. } else if (_isWindowSmall.matches) {
  809. styleTableSmallScreen()
  810. } else {
  811. styleTableBigScreen()
  812. }
  813. if (($(".OC2-crimeMemberLi").last().next()).length < 1) {
  814. $(".OC2-crimeMemberLi").last().css({
  815. "border-radius": "0 0 15px 15px",
  816. })
  817. }
  818. }
  819.  
  820. function styleTableSmallScreen() {
  821. //console.log("Smallscreen!")
  822. //total row length: 386
  823. //margins: 20px <item> 10px
  824. //OC2-tableMember 260px
  825. $(".OC2-memberTable li.OC2-memberAvailable").css({
  826. "padding-left": "15px"
  827. })
  828. $(".OC2-hideSmall").css({
  829. "display": "none"
  830. })
  831. $(".OC2-memberTable div.OC2-tableStatus").css({
  832. "width": "55px",
  833. "text-align": "left"
  834. })
  835. $(".OC2-memberTable div.OC2-tableMember").css({
  836. "width": "120px",
  837. })
  838. $(".OC2-memberTable div.OC2-tableCrime").css({
  839. "width": "210px",
  840. })
  841. $(".OC2-memberTable div.OC2-tableCrimeMemberName").css({
  842. "width": "220px",
  843. })
  844. $(".OC2-statusText").not(".OC2-hideSmall").css({
  845. "display": ""
  846. })
  847. $(".OC2-memberTable div.OC2-tableCrimePosition").css({
  848. "width": "70px",
  849. "font-size": "10px",
  850. })
  851. $(".OC2-memberTable div.OC2-tableCrimeMemberName").css({
  852. "width": "220px",
  853. })
  854. $(".OC2-memberTable div.OC2-tableCrimeMemberStatus").css({
  855. "width": "90px",
  856. "text-align": "left"
  857. })
  858. $(".OC2-memberTable div.OC2-tableCrimeMemberSuccess").css({
  859. "width": "18px",
  860. "text-align": "center"
  861. })
  862. $(".OC2-memberTable div.OC2-tableCrimeMemberItem").css({
  863. "width": "30px",
  864. "text-align": "center"
  865. })
  866. }
  867.  
  868. function styleTableTinyScreen() {
  869. //console.log("Tinyscreen!")
  870. $(".OC2-memberTable li.OC2-crimeLi").not(".OC2-memberTable li[class*='OC2-titleLi']").css({
  871. "padding-left": "15px",
  872. "width": containerMaxWidth
  873. })
  874. $(".OC2-hideSmall").css({
  875. "display": "none"
  876. })
  877. $(".OC2-tableCrimeMemberCount").css({
  878. "width": "40px"
  879. })
  880. $(".OC2-memberTable div.OC2-tableStatus").css({
  881. "width": "90px",
  882. "text-align": "left"
  883. })
  884. $(".OC2-memberTable div.OC2-tableCrime").css({
  885. "width": "190px",
  886. })
  887. $(".OC2-memberTable div.OC2-tableCrimeMemberName").css({
  888. "width": "165px",
  889. })
  890. $(".OC2-statusText").not(".OC2-hideSmall").css({
  891. "display": ""
  892. })
  893. $(".OC2-memberTable li.OC2-crimeMemberLi").css({
  894. "padding-left": "18px",
  895. })
  896. $(".OC2-memberTable li.OC2-memberAvailable").css({
  897. "padding-left": "12px"
  898. })
  899. $(".OC2-memberTable li.OC2-crimeLi").not(".OC2-memberTable li[class*='OC2-titleLi']").css({
  900. "padding-left": "12px"
  901. })
  902. }
  903.  
  904. function styleTableBigScreen() {
  905. $(".OC2-hideSmall").css({
  906. "display": ""
  907. })
  908. $(".OC2-statusText").not(".OC2-hideSmall").css({
  909. "display": "none"
  910. })
  911. }
  912.  
  913. function styleOCNotifier() {
  914. $(".OC2-sidebarNotice a").css({
  915. "text-decoration": "none",
  916. "color": "inherit"
  917. })
  918. $(".OC2-sidebarNotice .OC2-redtext").css({
  919. "text-decoration": "none",
  920. "color": "rgb(255, 121, 76)"
  921. })
  922. }
  923.  
  924.  
  925. //main functions
  926. /* if page is the crimes 2.0 page
  927. * -> if memberViewer table does NOT exist, get data and fill table
  928. * -> otherwise, show the table
  929. * -> otherwise, hide the table
  930. */
  931. async function hashChangeFunction() {
  932. if (checkCrimesPage()) {
  933. if (!$(".OC2-memberViewer")[0]) {
  934. if (!myFactionInfo) {
  935. await getAndAnalyzeAPIData()
  936. }
  937. generateInsertHTML();
  938. } else {
  939. $(".OC2-memberViewer").show()
  940. }
  941. } else {
  942. if ($(".OC2-memberViewer")[0]) {
  943. $(".OC2-memberViewer").hide();
  944. }
  945. }
  946. }
  947.  
  948. async function runOnceFunction() {
  949. //exit if API key doesn't exist
  950. await getAPIKey()
  951. .then(function(data) {
  952. if (APIKey == null || APIKey == "") {
  953. return;
  954. }
  955. })
  956. //sidebar notifier, but not if the sidebar doesn't exist
  957. if (($("div[class*='sidebar_'][class*='desktop_']")[0]) && (!isPDA())) {
  958. await getAndAnalyzeAPIData()
  959. insertOCNotifier()
  960. }
  961. //insert member overview
  962. if (checkCrimesPage() || await checkTravelFactionPage()) {
  963. if (!$(".OC2-memberViewer .OC2-tableCell")[0]) {
  964. if (!myFactionInfo) {
  965. await getAndAnalyzeAPIData()
  966. }
  967. generateInsertHTML()
  968. } else {
  969. $(".OC2-memberViewer").show()
  970. }
  971. }
  972. }
  973.  
  974. //taken from stackoverflow https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists because mutation observer confuses me
  975. //needed because the faction page info only loads after the document is ready
  976. function waitForElm(selector) {
  977. return new Promise(resolve => {
  978. if (document.querySelector(selector)) {
  979. return resolve(document.querySelector(selector));
  980. }
  981. const observer = new MutationObserver(mutations => {
  982. if (document.querySelector(selector)) {
  983. observer.disconnect();
  984. resolve(document.querySelector(selector));
  985. }
  986. });
  987. // If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336
  988. observer.observe(document.body, {
  989. childList: true,
  990. subtree: true
  991. });
  992. });
  993. }
  994.  
  995. function checkWindowWidth() {
  996. if (_isWindowNormal.matches) {
  997. containerMaxWidth = "784px"
  998. } else if (_isWindowSmall.matches) {
  999. containerMaxWidth = "386px"
  1000. } else if (_isWindowTiny.matches) {
  1001. containerMaxWidth = "320px"
  1002. }
  1003. if (!isPDA()) {
  1004. styleTable()
  1005. }
  1006. }
  1007.  
  1008. _isWindowNormal.addEventListener("change", function() {
  1009. checkWindowWidth()
  1010. });
  1011. _isWindowSmall.addEventListener("change", function() {
  1012. checkWindowWidth()
  1013. });
  1014. _isWindowTiny.addEventListener("change", function() {
  1015. checkWindowWidth()
  1016. });
  1017.  
  1018. checkWindowWidth()
  1019. runOnceFunction()
  1020. $(window).on('hashchange', hashChangeFunction)
  1021.  
  1022. $("#dark-mode-state").on('change', styleTable)