// ==UserScript==
// @name Torn - City Job Upgrade Notice
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Makes an api request after 6:30pm TCT to check your current job, work stats and job points to see if you are able to upgrade to the next position, and displays a notice at the top of the page if you can upgrade. Click 'Fetch New Data' after upgrading to remove the notice. You may need to wait 30-60 seconds for the API to update with your new position. Toggle script button on job/company page.
// @author Baccy
// @match https://www.torn.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @grant none
// ==/UserScript==
// An input for a minimal access API key will be displayed at the top of the in-game page if not found in storage
(function() {
'use strict';
const jobRequirements = {
army: [
{ position: 'Private', manual_labor: 50, intelligence: 15, endurance: 20 },
{ position: 'Corporal', manual_labor: 120, intelligence: 35, endurance: 50 },
{ position: 'Sergeant', manual_labor: 325, intelligence: 60, endurance: 115 },
{ position: 'Master Sergeant', manual_labor: 700, intelligence: 160, endurance: 300 },
{ position: 'Warrant Officer', manual_labor: 1300, intelligence: 360, endurance: 595 },
{ position: 'Lieutenant', manual_labor: 2550, intelligence: 490, endurance: 900 },
{ position: 'Major', manual_labor: 4150, intelligence: 600, endurance: 1100 },
{ position: 'Colonel', manual_labor: 7500, intelligence: 1350, endurance: 2530 },
{ position: 'Brigadier', manual_labor: 10000, intelligence: 2000, endurance: 4000 },
{ position: 'General', manual_labor: null, intelligence: null, endurance: null }
],
grocer: [
{ position: 'Bag Boy', manual_labor: 30, intelligence: 15, endurance: 50 },
{ position: 'Price Labeller', manual_labor: 50, intelligence: 35, endurance: 120 },
{ position: 'Cashier', manual_labor: 120, intelligence: 60, endurance: 225 },
{ position: 'Food Delivery', manual_labor: 250, intelligence: 200, endurance: 500 },
{ position: 'Manager', manual_labor: null, intelligence: null, endurance: null }
],
casino: [
{ position: 'Dealer', manual_labor: 35, intelligence: 50, endurance: 120 },
{ position: 'Gaming Consultant', manual_labor: 60, intelligence: 115, endurance: 325 },
{ position: 'Marketing Manager', manual_labor: 360, intelligence: 595, endurance: 1300 },
{ position: 'Revenue Manager', manual_labor: 490, intelligence: 900, endurance: 2550 },
{ position: 'Casino Manager', manual_labor: 755, intelligence: 1100, endurance: 4150 },
{ position: 'Casino President', manual_labor: null, intelligence: null, endurance: null }
],
medical: [
{ position: 'Medical Student', manual_labor: 100, intelligence: 600, endurance: 150 },
{ position: 'Houseman', manual_labor: 175, intelligence: 1000, endurance: 275 },
{ position: 'Senior Houseman', manual_labor: 300, intelligence: 1500, endurance: 500 },
{ position: 'GP', manual_labor: 600, intelligence: 2500, endurance: 1000 },
{ position: 'Consultant', manual_labor: 1300, intelligence: 5000, endurance: 2000 },
{ position: 'Surgeon', manual_labor: 2600, intelligence: 10000, endurance: 4000 },
{ position: 'Brain Surgeon', manual_labor: null, intelligence: null, endurance: null }
],
education: [
{ position: 'Recess Supervisor', manual_labor: 300, intelligence: 750, endurance: 500 },
{ position: 'Substitute Teacher', manual_labor: 600, intelligence: 1000, endurance: 700 },
{ position: 'Elementary Teacher', manual_labor: 1000, intelligence: 1300, endurance: 1000 },
{ position: 'Secondary Teacher', manual_labor: 1500, intelligence: 2000, endurance: 1500 },
{ position: 'Professor', manual_labor: 1500, intelligence: 3000, endurance: 1500 },
{ position: 'Vice Principal', manual_labor: 1500, intelligence: 5000, endurance: 1500 },
{ position: 'Principal', manual_labor: null, intelligence: null, endurance: null }
],
law: [
{ position: 'Law Student', manual_labor: 1750, intelligence: 2500, endurance: 5000 },
{ position: 'Paralegal', manual_labor: 2500, intelligence: 5000, endurance: 7500 },
{ position: 'Probate Lawyer', manual_labor: 3500, intelligence: 6500, endurance: 7750 },
{ position: 'Trial Lawyer', manual_labor: 4000, intelligence: 7250, endurance: 10000 },
{ position: 'Circuit Court Judge', manual_labor: 6000, intelligence: 9000, endurance: 15000 },
{ position: 'Federal Judge', manual_labor: null, intelligence: null, endurance: null }
]
};
let apiKey;
let scriptEnabled;
let cityJobUpgradeData = {};
function checkTime() {
const now = new Date();
const jobUpdateTime = new Date(now);
jobUpdateTime.setUTCHours(18, 30, 0, 0);
if (now < jobUpdateTime) jobUpdateTime.setUTCDate(jobUpdateTime.getUTCDate() - 1);
const lastFetchedJobTime = cityJobUpgradeData.fetchTime ? new Date(cityJobUpgradeData.fetchTime) : null;
if (!lastFetchedJobTime || lastFetchedJobTime < jobUpdateTime) fetchData();
else checkRequirements();
}
function jobPage() {
if (
window.location.href.toLowerCase().includes('https://www.torn.com/jobs.php') ||
window.location.href.toLowerCase().includes('https://www.torn.com/companies.php')
) {
const toggleButton = document.createElement('button');
toggleButton.innerText = 'Toggle Job Upgrade Notice';
toggleButton.classList.add('city-job-upgrade');
toggleButton.style = scriptEnabled
? 'padding: 5px 10px; border-radius: 5px; background-color: #555555; color: lightgreen; border: none; cursor: pointer;'
: 'padding: 5px 10px; border-radius: 5px; background-color: #555555; color: white; border: none; cursor: pointer;';
toggleButton.onclick = () => {
scriptEnabled = !scriptEnabled;
localStorage.setItem('cityJobUpgradeNoticeEnabled', scriptEnabled);
if (scriptEnabled) toggleButton.style.color = 'lightgreen';
else toggleButton.style.color = 'white';
};
const pageTitle = document.querySelector('div.content-title > h4');
if (pageTitle && !document.querySelector('city-job-upgrade')) pageTitle.appendChild(toggleButton);
}
}
function checkRequirements() {
const positions = jobRequirements[cityJobUpgradeData.job_type.toLowerCase()];
if (!positions) return;
const currentIndex = positions.findIndex(pos => pos.position === cityJobUpgradeData.job_position);
if (currentIndex === -1 || currentIndex >= positions.length - 1) return;
const nextPositionRequirements = positions[currentIndex];
const pointsRequired = (currentIndex + 1) * 5;
const canUpgrade = cityJobUpgradeData.job_points >= pointsRequired &&
cityJobUpgradeData.manual_labor >= nextPositionRequirements.manual_labor &&
cityJobUpgradeData.intelligence >= nextPositionRequirements.intelligence &&
cityJobUpgradeData.endurance >= nextPositionRequirements.endurance;
if (canUpgrade) displayNotice(`You can upgrade to ${positions[currentIndex + 1].position}.`);
}
function displayApiKeyInput() {
const banner = document.querySelector('#topHeaderBanner');
if (banner && !document.querySelector('#minimal-api-key-message')) {
const apiKeyInput = document.createElement('div');
apiKeyInput.innerHTML = `<div id="minimal-api-key-message" style="font-size: 16px; color: white; background-color: #222; text-align: center;"><p>Please enter your minimal access API key to continue.</p><input type="text" id="minimal-api-key-input" placeholder="Enter API Key" style="background-color: #333; color: white;"><button id="minimal-api-key-save" style="padding: 3px 10px; margin-left: 5px; background-color: #333; color: white; cursor: pointer;">Save API Key</button></div>`;
banner.appendChild(apiKeyInput);
const saveButton = document.querySelector('#minimal-api-key-save');
saveButton.addEventListener('click', () => {
const inputField = document.querySelector('#minimal-api-key-input');
const apiKeyValue = inputField.value.trim();
if (apiKeyValue) {
apiKey = apiKeyValue;
localStorage.setItem('minimalAPIKey', apiKeyValue);
checkTime();
apiKeyInput.remove();
}
});
}
}
function displayNotice(message) {
const banner = document.querySelector('#topHeaderBanner');
if (banner && !document.querySelector('#city-job-upgrade-notice')) {
const jobNotice = document.createElement('div');
jobNotice.innerHTML = `<label id="city-job-upgrade-notice" style="font-size: 16px; color: white; background-color: #222; text-align: center;">${message}</label><button id="job-notice-fetch" style="padding: 3px 10px; margin-left: 5px; background-color: #333; color: white; cursor: pointer;">Fetch New Data</button>`;
banner.appendChild(jobNotice);
const fetchButton = document.querySelector('#job-notice-fetch');
fetchButton.addEventListener('click', () => {
fetchData();
jobNotice.remove();
});
}
}
async function fetchData() {
const response = await fetch(`https://api.torn.com/v2/user?key=${apiKey}&selections=jobpoints,profile,workstats&comment=JobUpgrade`);
const result = await response.json();
if (result.error && (result.error.code === 2 || result.error.code === 16)) {
await localStorage.removeItem('minimalAPIKey');
alert('Your API key is incorrect or access is not high enough. Please enter a new minimal access key.');
displayApiKeyInput();
return;
}
const job_points = result.jobpoints.jobs[result.job.job.toLowerCase()] || 0;
cityJobUpgradeData = {
manual_labor: result.manual_labor,
intelligence: result.intelligence,
endurance: result.endurance,
job_type: result.job.job,
job_position: result.job.position,
job_points: job_points,
fetchTime: new Date().toISOString(),
};
await localStorage.setItem('cityJobUpgradeData', JSON.stringify(cityJobUpgradeData));
checkRequirements();
}
async function init() {
scriptEnabled = await JSON.parse(localStorage.getItem('cityJobUpgradeNoticeEnabled')) ?? true;
jobPage();
if (scriptEnabled) {
apiKey = await localStorage.getItem('minimalAPIKey') || '';
if (!apiKey) displayApiKeyInput();
else {
cityJobUpgradeData = await JSON.parse(localStorage.getItem('cityJobUpgradeData')) || {};
checkTime();
}
}
}
init();
})();