// ==UserScript==
// @name [TORN] OC 2.0 Helper
// @namespace Violentmonkey Scripts
// @match https://www.torn.com/*
// @version 1.5
// @author whatdoesthespacebardo / callmericky [3299880]
// @description Adds a list of members available for OC2.0, and adds a notifier to the sidebar if you are not in an OC.
// @grant GM_registerMenuCommand
// @grant GM.setValue
// @grant GM.getValue
// @license GNU GPLv3
// ==/UserScript==
/*
* All edits can be done from the dropdown menu from your browser's userscript extension
* If the dropdown menu doesn't work, you can still manually add your API key here
*/
var APIKey = "";
/* =============================================
* STOP CHANGING THINGS FROM THIS POINT ONWARD
=============================================== */
const PDA_APIKey = "###PDA-APIKEY###"
let memberInfo = {};
let pageURL = $(location).attr("href");
let totalMembers = 0;
let availableMembers = 0;
let activeMembers = 0;
let userInfo = {};
let _isWindowSmall = window.matchMedia("(max-width: 784px)")
if (!isPDA()) {
const menu_command_1 = GM_registerMenuCommand("Set API Key (Limited + faction access)", menuCommand)
}
async function getAPIKey() {
if (isPDA()) {
APIKey = PDA_APIKey
return PDA_APIKey
} else {
return await GM.getValue("CMR_OC2_APIKey", null)
.then(function(data) {
APIKey = data
return data
})
}
}
function isPDA() {
const PDATestRegex = !/^(###).+(###)$/.test(PDA_APIKey);
return PDATestRegex;
}
function menuCommand(event) {
let _userKey = window.prompt("OC 2.0 participation and notifier\n- Requires a minimal API key with faction access\n\nSet API Key:", APIKey)
if ((_userKey == null || _userKey == "")) {
window.alert("OC 2.0 participation and notifier\nYou did not set an API Key - this script will not run")
} else {
GM.setValue("CMR_OC2_APIKey", _userKey)
}
}
function checkCrimesPage(pageURL) {
if (pageURL.search("step=your") > 0 && pageURL.search("tab=crimes") > 0) {
return true;
} else {
return false;
}
}
function getUserID() {
let _profileLink = $(".settings-menu > .link > a")[0]
let _matchregex = /profiles\.php.+XID=(\d+)/i
let _userID = _matchregex.exec(_profileLink)
return _userID[1]
}
async function getFactionMembers() {
return await $.getJSON(`https://api.torn.com/v2/faction/members?key=${APIKey}&striptags=true`)
.then(function(data) {
return data;
});
}
async function getCrimesList() {
return await $.getJSON(`https://api.torn.com/v2/faction/crimes?key=${APIKey}&cat=available&offset=0`)
.then(function(data) {
return data;
});
}
function checkMembersInCrimes(_members, _crimes) {
//put all member ids into a list
for (i = 0; i < (_members.members).length; i++) {
if (_members.members[i].status.state == "Fallen") {
//skip fallen members
} else if (_members.members[i].position == "Recruit") {
//skip recruits since they can't join OC
} else {
memberInfo[_members.members[i].id] = {
"name": _members.members[i].name,
"last_action": _members.members[i].last_action.relative,
"status": _members.members[i].status.description
}
}
}
totalMembers = (_members.members).length;
activeMembers = 0 //if this doesn't reset, hashchange will cause the following part to re-fire and get activemembers count wrong.
//go through crime list
for (i = 0; i < (_crimes.crimes).length; i++) {
//if crime is not initiated, it will be null for initiated_at. No point looking for members because there won't be any.
if (_crimes.crimes[i].initiated_at) {
for (j=0; j<(_crimes.crimes[i].slots).length; j++)
if (_crimes.crimes[i].slots[j].user_id) {
memberInfo[_crimes.crimes[i].slots[j].user_id].crimeInfo = {
"crimeName": _crimes.crimes[i].name,
"crimeDifficulty": _crimes.crimes[i].difficulty,
"crimeId": _crimes.crimes[i].id,
"crimePosition": _crimes.crimes[i].slots[j].position,
"crimeSuccess": _crimes.crimes[i].slots[j].success_chance
}
activeMembers = activeMembers + 1;
if ((_crimes.crimes[i].slots[j].user_id) == getUserID()) {
userInfo = memberInfo[_crimes.crimes[i].slots[j].user_id]
}
}
}
}
availableMembers = totalMembers - activeMembers;
}
function putMemberInfoIntoTable() {
if ($(".OC2-memberTable .OC2-tablecell")[0]) {
return
}
for (var _key of Object.keys(memberInfo)) {
let _outputAvail = "AVAILABLE"
let _outputHTML = ""
let _memberHighlight = "OC2-memberAvailable"
if (memberInfo[_key].crimeInfo) {
_outputAvail = `${memberInfo[_key].crimeInfo.crimePosition} of <a href="https://www.torn.com/factions.php?step=your&type=5#/tab=crimes&crimeId=${memberInfo[_key].crimeInfo.crimeId}">${memberInfo[_key].crimeInfo.crimeName}</a> (Lv ${memberInfo[_key].crimeInfo.crimeDifficulty})`
_memberHighlight = "OC2-memberInCrime"
}
_outputHTML = (`<li class="table-cell ${_memberHighlight}">
<div class="OC2-tablecell OC2-tableMember">${memberInfo[_key].name} [${_key}]</div>
<div class="OC2-tablecell OC2-tableAvailability">${_outputAvail}</div>
<div class="OC2-tablecell OC2-tableStatus">${memberInfo[_key].status}</div>
</li>`)
//put all the members currently in crimes below
if (memberInfo[_key].crimeInfo) {
$(".OC2-memberTable ul.table-body").append(_outputHTML)
} else {
$(".OC2-memberTable ul.table-body").prepend(_outputHTML)
}
$(".OC2-memberTableFooter").text(`${availableMembers} / ${totalMembers} members available (${activeMembers} in an OC)`)
}
styleTable()
}
function generateInsertHTML() {
_insertHTML = (`
<div class="category-wrap OC2-memberViewer m-top10">
<div class="title-black top-round t-overflow">OC 2.0 Member Overview <span class="hideInfoButton">Show all members</span></div>
<div class="cont-gray OC2-memberTable"><ul class="table-body">
</ul></div>
<div class="OC2-memberTableFooter"></div>
</div>`)
$("div#faction-crimes").before(_insertHTML)
$("span.hideInfoButton").off().on("click", event => {
toggleMemberView()
})
styleTable()
}
function toggleMemberView() {
if ($(".hideInfoButton").hasClass("text-hide")) {
$(".hideInfoButton").text("Show all members")
$(".hideInfoButton").removeClass("text-hide")
} else {
$(".hideInfoButton").text("Hide active members")
$(".hideInfoButton").addClass("text-hide")
}
$(".OC2-memberInCrime").toggle()
}
function styleTable() {
$(".OC2-memberTable ul.table-body").css({
"display": "flex",
"flex-direction": "column"
})
$(".OC2-memberTable a").css({
"color": "rgb(116, 192, 252)",
"text-decoration": "none"
})
$(".OC2-memberTable li.table-cell").css({
"display": "flex",
"flex-direction": "row"
})
$(".OC2-memberTable li.OC2-memberAvailable").css({
"font-weight": "bold"
})
$(".OC2-memberTable li.OC2-memberInCrime").css({
"color": "rgb(153, 153, 153)",
})
$(".OC2-memberTable div").css({
"font-size": "11px",
"line-height": "11px",
"padding": "5px 5px 5px 10px"
})
$(".OC2-memberTable div.OC2-tablecell").css({
"display": "inline"
})
$(".OC2-memberTable div.OC2-tableMember").css({
"width": "200px"
})
$(".OC2-memberTable div.OC2-tableAvailability").css({
"width": "250px",
})
$(".OC2-memberTable div.OC2-tableCrimeSlot").css({
"width": "100px"
})
$(".OC2-memberTable div.OC2-tableStatus").css({
"width": "200px"
})
$(".OC2-memberTableFooter").css({
"border-radius": "0 0 5px 5px",
"background-color": "rgb(51, 51, 51)",
"padding": "5px 5px 5px 10px",
"text-align": "center"
})
$(".hideInfoButton").css({
"position": "absolute",
"right": "10px",
"cursor": "pointer"
})
//light mode styling
if ($("body").css("background-color") == "rgb(204, 204, 204)") {
$(".OC2-memberTable a").css({
"color": "#006699",
})
$(".OC2-memberTableFooter").css({
"background-color": "rgb(242, 242, 242)"
})
}
if (_isWindowSmall.matches) {
styleTableSmallScreen()
}
}
function styleTableSmallScreen() {
$(".OC2-memberTable div.OC2-tableCrimeSlot").css({
"display": "none"
})
$(".OC2-memberTable div.OC2-tableStatus").css({
"display": "none"
})
}
function insertOCNotifier() {
let _userNotice = (`<a href="https://www.torn.com/factions.php?step=your#/tab=crimes"><span class="OC2-redtext">No active OC.</span></a>`)
if (userInfo.crimeInfo) {
_userNotice = (`<span class="OC2-normaltext"><a href="https://www.torn.com/factions.php?step=your&type=5#/tab=crimes&crimeId=${userInfo.crimeInfo.crimeId}">${userInfo.crimeInfo.crimePosition} of ${userInfo.crimeInfo.crimeName} (Lv ${userInfo.crimeInfo.crimeDifficulty})</a></span>`)
}
let _insertHTML = (`<div class="OC2-sidebarNotice"><a href="https://www.torn.com/factions.php?step=your#/tab=crimes"><span style="font-weight: bold">OC 2.0:</span></a>
${_userNotice}
</div>`)
$('div[class^="sidebar_"] div[class^="user-information_"] div[class^="toggle-block_"] div[class^="toggle-content_"] div[class^="content_"]').append(_insertHTML)
styleOCNotifier()
}
function styleOCNotifier() {
$(".OC2-sidebarNotice a").css({
"text-decoration": "none",
"color": "inherit"
})
$(".OC2-sidebarNotice .OC2-redtext").css({
"text-decoration": "none",
"color": "rgb(255, 121, 76)"
})
}
/* 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() {
let pageURL = $(location).attr("href");
if (checkCrimesPage(pageURL)) {
//insert member overview
if (!$(".OC2-memberViewer")[0]) {
generateInsertHTML();
putMemberInfoIntoTable();
} else {
$(".OC2-memberViewer").show()
}
} else {
if ($(".OC2-memberViewer")[0]) {
$(".OC2-memberViewer").hide();
}
}
$(".OC2-memberInCrime").hide();
}
async function runOnceFunction() {
//exit if API key doesn't exist
await getAPIKey()
.then(function(data) {
if (APIKey == null || APIKey == "") {
return;
}
})
//get data
const [_memberData, _crimesData] = await Promise.all([
getFactionMembers(),
getCrimesList()
])
//analyze data
checkMembersInCrimes(_memberData, _crimesData)
if (!$(".OC2-sidebarNotice")[0]) {
insertOCNotifier()
}
let pageURL = $(location).attr("href");
if (checkCrimesPage(pageURL)) {
//insert member overview
if (!$(".OC2-memberViewer")[0]) {
generateInsertHTML();
} else {
$(".OC2-memberViewer").show()
}
}
putMemberInfoIntoTable();
$(".OC2-memberInCrime").hide();
}
async function viewTableWhileTraveling() {
await getAPIKey()
.then(function(data) {
if (APIKey == null || APIKey == "") {
return;
}
})
let _factionInfo = await $.getJSON(`https://api.torn.com/v2/faction/basic?key=${APIKey}`)
.then(function(data) {
return data;
});
let pageURL = $(location).attr("href")
waitForElm('div#react-root').then((elm) => {
if ($(".OC2-memberTable")[0]) {
return
}
if (pageURL.search("ID="+_factionInfo.basic.id) > 0) {
var _insertHTML = (`
<div class="category-wrap OC2-memberViewer m-top10">
<div class="title-black top-round t-overflow">OC 2.0 Member Overview <span class="hideInfoButton">Show all members</span></div>
<div class="cont-gray OC2-memberTable"><ul class="table-body">
</ul></div>
<div class="OC2-memberTableFooter"></div>
</div>`)
$(elm).before(_insertHTML)
$("span.hideInfoButton").on("click", event => {
toggleMemberView()
})
styleTable()
}
})
}
//waitForElm from stackoverflow https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists
function waitForElm(selector) {
return new Promise(resolve => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(mutations => {
if (document.querySelector(selector)) {
observer.disconnect();
resolve(document.querySelector(selector));
}
});
// If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336
observer.observe(document.body, {
childList: true,
subtree: true
});
});
}
runOnceFunction()
$(window).on('hashchange', hashChangeFunction);
if (($(body).attr("data-traveling") == "true") || ($(body).attr("data-traveling") == true)) {
viewTableWhileTraveling()
}
_isWindowSmall.addEventListener("change", function() {
styleTable()
});