// ==UserScript==
// @name Torn - Starter Job Upgrade Notice
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Checks job points and work stats with an API request after 6:30pm TCT to see if you meet the requirements for the next position. Enter a minimal access key when asked for ON Torn. Click "Fetch API Data" after upgrading your position to stop the notice appearing.
// @author Baccy
// @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @match https://www.torn.com/*
// @grant none
// ==/UserScript==
let showFalseMessage = false; // Set to true to show a message with your missing stats or job points
// Job positions with the required stats for the NEXT position
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 = null; // Enter your minimal access key in the input section on Torn.
// Function to display input for your minimal API key if you have not entered it before
function getAPIKey() {
let apiKey = localStorage.getItem('minimalAPIKey');
if (!apiKey) {
const container = document.querySelector('#topHeaderBanner');
if (container) {
let existingMessage = document.getElementById('api-key-message');
if (existingMessage) {
existingMessage.remove();
}
const messageDiv = document.createElement('div');
messageDiv.id = 'api-key-message';
messageDiv.innerHTML = "<p>Please enter your minimal access API key to continue.</p>";
messageDiv.style.fontSize = '16px';
messageDiv.style.marginTop = '10px';
messageDiv.style.color = '#ffffff';
messageDiv.style.padding = '10px';
messageDiv.style.backgroundColor = '#1c1b22';
messageDiv.style.borderRadius = '5px';
messageDiv.style.lineHeight = '1.5';
messageDiv.style.textAlign = 'center';
const apiKeyInput = document.createElement('input');
apiKeyInput.type = 'text';
apiKeyInput.placeholder = 'Enter API Key';
apiKeyInput.style.marginTop = '10px';
apiKeyInput.style.padding = '5px';
apiKeyInput.style.width = '150px';
apiKeyInput.style.borderRadius = '5px';
apiKeyInput.style.border = '1px solid #848884';
const saveAPIKeyButton = document.createElement('button');
saveAPIKeyButton.innerText = 'Save API Key';
saveAPIKeyButton.style.marginTop = '10px';
saveAPIKeyButton.style.padding = '5px 10px';
saveAPIKeyButton.style.backgroundColor = '#848884';
saveAPIKeyButton.style.color = '#ffffff';
saveAPIKeyButton.style.border = 'none';
saveAPIKeyButton.style.borderRadius = '5px';
saveAPIKeyButton.style.cursor = 'pointer';
saveAPIKeyButton.addEventListener('click', () => {
apiKey = apiKeyInput.value.trim();
if (apiKey) {
localStorage.setItem('minimalAPIKey', apiKey);
if (logsEnabled) { console.log(`${apiKey} has been saved to local storage`); }
messageDiv.remove();
} else {
alert('API key is required for the script to work.');
}
});
messageDiv.appendChild(apiKeyInput);
messageDiv.appendChild(saveAPIKeyButton);
container.appendChild(messageDiv);
}
}
}
// Function to check if you have the required job points and work stats to upgrade
function checkJobUpgrade(job, currentPosition, jobPoints, workStats) {
if (logsEnabled) { console.log(`Checking job upgrade for job: ${job}, current position: ${currentPosition}, job points: ${jobPoints}, work stats:`, workStats); }
const positions = jobRequirements[job.toLowerCase()];
const currentIndex = positions.findIndex(pos => pos.position === currentPosition);
if (currentIndex === -1 || currentIndex >= positions.length - 1) {
if (logsEnabled) { console.log("User has reached the highest position or job data is incorrect."); }
return "You have reached the highest position or your job data is incorrect.";
}
const nextPositionRequirements = positions[currentIndex];
const pointsRequired = (currentIndex + 1) * 5;
if (logsEnabled) { console.log(`Next position: ${positions[currentIndex + 1].position}, Points required: ${pointsRequired}, Requirements:`, nextPositionRequirements); }
const canUpgrade = jobPoints >= pointsRequired &&
workStats.manual_labor >= nextPositionRequirements.manual_labor &&
workStats.intelligence >= nextPositionRequirements.intelligence &&
workStats.endurance >= nextPositionRequirements.endurance;
if (canUpgrade) {
if (logsEnabled) { console.log(`User can upgrade to: ${positions[currentIndex + 1].position}`); }
return `You can upgrade to ${positions[currentIndex + 1].position}!`;
} else {
if (logsEnabled) { console.log(`User cannot upgrade to: ${positions[currentIndex + 1].position}`); }
if (showFalseMessage) {
const missingStats = [];
if (jobPoints < pointsRequired) {
const missingJP = pointsRequired - jobPoints;
missingStats.push(`${missingJP} JP `);
}
if (workStats.manual_labor < nextPositionRequirements.manual_labor) {
const missingMAN = nextPositionRequirements.manual_labor - workStats.manual_labor;
missingStats.push(`${missingMAN} MAN `);
}
if (workStats.intelligence < nextPositionRequirements.intelligence) {
const missingINT = nextPositionRequirements.intelligence - workStats.intelligence;
missingStats.push(`${missingINT} INT `);
}
if (workStats.endurance < nextPositionRequirements.endurance) {
const missingEND = nextPositionRequirements.endurance - workStats.endurance;
missingStats.push(`${missingEND} END `);
}
const missingMessage = `You cannot upgrade to ${positions[currentIndex + 1].position} yet. You are missing:\n${missingStats.join('\n')}`;
if (logsEnabled) { console.log(missingMessage); }
return missingMessage;
}
}
}
// Function to display the upgrade message
function displayUpgradeMessage(jobUpgradeMessage) {
const container = document.querySelector('#topHeaderBanner');
if (container) {
let existingMessage = document.getElementById('job-upgrade-message');
if (existingMessage) {
existingMessage.remove();
}
const messageDiv = document.createElement('div');
messageDiv.id = 'job-upgrade-message';
messageDiv.innerHTML = jobUpgradeMessage;
messageDiv.style.fontSize = '16px';
messageDiv.style.marginTop = '10px';
messageDiv.style.color = '#ffffff';
messageDiv.style.padding = '10px';
messageDiv.style.backgroundColor = '#1c1b22';
messageDiv.style.borderRadius = '5px';
messageDiv.style.lineHeight = '1.5';
messageDiv.style.textAlign = 'center';
const deleteButton = document.createElement('button');
deleteButton.innerText = 'Fetch API Data';
deleteButton.style.marginTop = '10px';
deleteButton.style.marginLeft = '10px';
deleteButton.style.padding = '5px 10px';
deleteButton.style.backgroundColor = '#848884';
deleteButton.style.color = '#ffffff';
deleteButton.style.border = 'none';
deleteButton.style.borderRadius = '5px';
deleteButton.style.cursor = 'pointer';
deleteButton.addEventListener('click', () => {
localStorage.removeItem('workStats');
localStorage.removeItem('jobPoints');
localStorage.removeItem('profile');
localStorage.removeItem('lastFetchDate');
checkAndFetchAPIData();
if (logsEnabled) { console.log('Saved data has been deleted from local storage'); }
});
messageDiv.appendChild(deleteButton);
container.appendChild(messageDiv);
}
}
// Function to fetch and store the API data
async function fetchAPIData() {
if (logsEnabled) { console.log("Attempting to fetch data"); }
if (!apiKey) {
apiKey = localStorage.getItem('minimalAPIKey');
if (!apiKey) {
getAPIKey();
return;
}
}
if (apiKey) {
try {
const workStatsResponse = await fetch(`https://api.torn.com/user/?selections=workstats&key=${apiKey}&comment=StarterJob`);
const jobPointsResponse = await fetch(`https://api.torn.com/user/?selections=jobpoints&key=${apiKey}&comment=StarterJob`);
const profileResponse = await fetch(`https://api.torn.com/user/?selections=profile&key=${apiKey}&comment=StarterJob`);
const workStats = await workStatsResponse.json();
const jobPoints = await jobPointsResponse.json();
const profile = await profileResponse.json();
const responses = [workStats, jobPoints, profile];
for (const response of responses) {
if (response.error) {
const errorCode = response.error.code;
console.error(`API Error: ${response.error.error} (Code: ${errorCode})`);
if ([2, 13, 18].includes(errorCode)) {
localStorage.removeItem('minimalAPIKey');
apiKey = null;
alert('Your API key is invalid, paused, or inactive. Please enter a new key.');
return;
}
}
}
if (logsEnabled) {
console.log("Fetched workStats:", workStats);
console.log("Fetched jobPoints:", jobPoints);
console.log("Fetched profile:", profile);
}
localStorage.setItem('workStats', JSON.stringify(workStats));
localStorage.setItem('jobPoints', JSON.stringify(jobPoints));
localStorage.setItem('profile', JSON.stringify(profile));
localStorage.setItem('lastFetchDate', new Date().toISOString());
if (logsEnabled) { console.log('lastFetchDate stored:', localStorage.getItem('lastFetchDate')); }
} catch (error) {
console.error("Error fetching API data:", error);
}
}
}
// Function to check if valid data is stored and request new data if not
function checkStoredData() {
if (logsEnabled) { console.log("Checking stored data..."); }
const workStats = JSON.parse(localStorage.getItem('workStats'));
const jobPoints = JSON.parse(localStorage.getItem('jobPoints'));
const profile = JSON.parse(localStorage.getItem('profile'));
if (logsEnabled) {
console.log("Retrieved stored data:");
console.log("Work Stats:", workStats);
console.log("Job Points:", jobPoints);
console.log("Profile:", profile);
}
const invalidWorkStats = workStats && workStats.error && workStats.error.code === 2;
const invalidJobPoints = jobPoints && jobPoints.error && jobPoints.error.code === 2;
const invalidProfile = profile && profile.error && profile.error.code === 2;
if (logsEnabled) {
console.log("Validation results:");
console.log("Work Stats valid:", !invalidWorkStats);
console.log("Job Points valid:", !invalidJobPoints);
console.log("Profile valid:", !invalidProfile);
}
if (!workStats || !jobPoints || !profile || invalidWorkStats || invalidJobPoints || invalidProfile) {
if (logsEnabled) { console.log("One or more data items are missing or invalid. Fetching new data..."); }
fetchAPIData();
} else {
if (logsEnabled) { console.log("All stored data is valid."); }
}
}
// Function to return the latest data timestamp for the script to check if it is passed closing hour in torn
function getLastJobUpdateTime() {
const now = new Date();
const jobUpdateTime = new Date(now);
jobUpdateTime.setUTCHours(18, 30, 0, 0);
if (now < jobUpdateTime) {
jobUpdateTime.setUTCDate(jobUpdateTime.getUTCDate() - 1);
}
return jobUpdateTime;
}
// Function to check and fetch new data at 6:30pm TCT or later
async function checkAndFetchAPIData() {
const lastFetchDateStr = localStorage.getItem('lastFetchDate') || '0';
const lastFetchedJobTime = lastFetchDateStr ? new Date(lastFetchDateStr) : null;
const lastScheduledJobUpdateTime = getLastJobUpdateTime();
if (logsEnabled) {
console.log("Last fetch time:", lastFetchedJobTime);
console.log("Last scheduled time (6:30 PM UTC):", lastScheduledJobUpdateTime);
}
if (!lastFetchedJobTime || lastFetchedJobTime < lastScheduledJobUpdateTime) {
if (logsEnabled) { console.log("Fetching new API data..."); }
await fetchAPIData();
} else {
if (logsEnabled) { console.log("No need to fetch data yet."); }
}
}
// Main function
function onPageLoad() {
if (logsEnabled) { console.log("Page loaded. Checking stored data..."); }
checkStoredData();
const workStats = JSON.parse(localStorage.getItem('workStats'));
const jobPoints = JSON.parse(localStorage.getItem('jobPoints'));
const profile = JSON.parse(localStorage.getItem('profile'));
if (logsEnabled) {
console.log("Retrieved stored data:");
console.log("Work Stats:", workStats);
console.log("Job Points:", jobPoints);
console.log("Profile:", profile);
}
if (workStats && jobPoints && profile) {
const job = profile.job.job;
const currentPosition = profile.job.position;
let jobPointsAvailable = jobPoints.jobpoints.jobs[job.toLowerCase()];
if (!jobPointsAvailable) {
jobPointsAvailable = 0;
}
const workStatsData = {
manual_labor: workStats.manual_labor,
intelligence: workStats.intelligence,
endurance: workStats.endurance,
};
if (logsEnabled) { console.log(`Checking job upgrade for ${job}, current position: ${currentPosition}, available job points: ${jobPointsAvailable}`); }
const upgradeMessage = checkJobUpgrade(job, currentPosition, jobPointsAvailable, workStatsData);
if (upgradeMessage) {
if (logsEnabled) { console.log("Upgrade message found:", upgradeMessage); }
displayUpgradeMessage(upgradeMessage);
} else {
if (logsEnabled) { console.log("No upgrade available at this time."); }
}
} else {
if (logsEnabled) { console.log("One or more data items are missing. Work Stats, Job Points, and Profile must all be available."); }
}
}
checkAndFetchAPIData();
onPageLoad();