Torn - Starter Job Upgrade Notice

Checks job points and work stats with an API request (minimal access key will be asked for on Torn, not in the script) at 6:30pm TCT (or later) to see if you are able to upgrade to the next position, and display a message at the top of the page. Change showFalseMessage to true if you would like it to let you know what you're missing. Click the fetch new data button once you upgrade your position to remove the notice.

当前为 2024-12-02 提交的版本,查看 最新版本

// ==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 (minimal access key will be asked for on Torn, not in the script) at 6:30pm TCT (or later) to see if you are able to upgrade to the next position, and display a message at the top of the page. Change showFalseMessage to true if you would like it to let you know what you're missing. Click the fetch new data button once you upgrade your position to remove the notice.
// @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

let logsEnabled = false; // Set to true for debugging



let apiKey = null; // Enter your minimal access key in the input section on Torn.

// 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 } 
    ]
};

// 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或关注我们的公众号极客氢云获取最新地址