Torn - Starter Job Upgrade Notice

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.

目前为 2025-01-08 提交的版本。查看 最新版本

// ==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();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址