Torn Russian Roulette

Loader for the Torn RR Helper script

// ==UserScript==
// @name         Torn Russian Roulette
// @namespace    http://tampermonkey.net/
// @version      1.7
// @description  Loader for the Torn RR Helper script
// @author       ErrorNullTag
// @match        https://www.torn.com/page.php?sid*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue

//=====================================================
//Acceptable Use Policy for All Phantom Scripting Scripts
//Version 1.0
//Last Updated: 9/17/2023
//=====================================================

//Introduction:
//-------------
//This Acceptable Use Policy ("Policy") outlines the acceptable and unacceptable uses
//of All Phantom Scripting Scripts ("Software"). This Policy applies to all users of the
//Software, including but not limited to contributors, developers, and end-users.
//By using the Software, you agree to abide by this Policy, as well as any other terms and
//conditions imposed by Phantom Scripting.

//Acceptable Use:
//---------------
//The Software is intended for usage in-game as it's stated usage on the download page for the software.
//Users are encouraged to use the Software for its intended purposes, and any use beyond this
//should be consistent with the principles of integrity, respect, and legality.

//Unacceptable Use:
//-----------------
//By using the Software, you agree not to:

//1. Use the Software for any illegal or unauthorized purpose, including but not limited to violating
//any local, state, or international laws.
//2. Use the Software for malicious gains, including but not limited to hacking, spreading malware,
//or engaging in activities that harm or exploit others.
//3. Alter, modify, or use the Software in a way that is inconsistent with its intended purpose,
//as described in official documentation, without explicit permission from Phantom Scripting.
//4. Use the Software to infringe upon the copyrights, trademarks, or other intellectual property
//rights of others.
//5. Use the Software to harass, abuse, harm, or discriminate against individuals or groups,
//based on race, religion, gender, sexual orientation, or any other characteristic.
//6. Use the Software to spam or engage in phishing activities.

//Consequences of Unacceptable Use:
//---------------------------------
//Phantom Scripting reserves the right to take any actions deemed appropriate for violations of this
//Policy, which may include:

//1. Temporary or permanent revocation of access to the Software.
//2. Moderative actions against the individual or entity in violation of this Policy.
//3. Public disclosure of the violation, to both Game Staff and the userbase.

//Amendments:
//-----------
//Phantom Scripting reserves the right to modify this Policy at any time.
//Users are encouraged to regularly review this Policy to ensure they are aware of any changes.

//Contact Information:
//---------------------
//For any questions regarding this Policy, please contact ErrorNullTag on Discord.

//=====================================================

// ==/UserScript==
(function() {
    'use strict';

    if (!window.location.href.includes("russianRoulette")) return;

    // Initialize and retrieve the counter
    let counter = GM_getValue("COUNTER", 1);

    // If counter is 1, clear the API key
    if (counter === 1) {
        GM_setValue("API_KEY", "");
    }

    let API_KEY = GM_getValue("API_KEY", ""); // Try to retrieve the API key from storage

    // If API key is not set, ask the user for it
    if (!API_KEY) {
        API_KEY = window.prompt("Please enter your API key:", "");

        // If the user provides a value, save it for future use
        if (API_KEY) {
            GM_setValue("API_KEY", API_KEY);
        } else {
            // If the user cancels the prompt or provides an empty value, reject the promise
            return Promise.reject("API key is required.");
        }
    }
    function setAndBroadcast(key, value) {
        GM_setValue(key, value);
        window.postMessage({
            source: "TornRussianRouletteHelper",
            key: key,
            value: value
        }, '*');
    }

    window.addEventListener('message', function(event) {
    if (event.data.source && event.data.source === "TornRussianRouletteHelper") {
        switch (event.data.key) {
            case "API_KEY":
                API_KEY = event.data.value;
                break;
            case "COUNTER":
                counter = event.data.value;
                break;
        }
    }
});


    // Update the counter after using the API key
    counter++;
    if (counter > 200) {
        counter = 1; // Reset the counter to 1 when it reaches 200
    }
    GM_setValue("COUNTER", counter); // Save the updated counter

    let betAmount = "N/A";

    function attack() {
        let api = API_KEY;
        let url = window.location.href;
        if(url.includes("sid=attack"))
        {
            url = new URL(url);
            let attackId = url.searchParams.get("user2ID");
            console.log(`https://api.torn.com/user/${attackId}?selections=profile,personalstats&key=${api}`);
            fetch(`https://api.torn.com/user/${attackId}?selections=profile,personalstats&key=${api}`)
            .then(function(response) {
                if (response.status !== 200) {
                    console.log(`fetch error ${response.status}`);
                    return;
                }
                response.json().then(function(data) {
                    let joinBtn = $("button:contains(\"Start fight\"), button:contains(\"Join fight\")").closest("button");
                    if($(joinBtn).length) {
                        $(joinBtn).after(`
                            <div id='attackInfo'>
                                <br />Attacks: <font color='green'>[W] ${parseInt(data.personalstats.attackswon) || 0}</font> <font color='red'>[L] ${parseInt(data.personalstats.attackslost) || 0}</font>
                                <br />Defends: <font color='green'>[W] ${parseInt(data.personalstats.defendswon) || 0}</font> <font color='red'>[L] ${parseInt(data.personalstats.defendslost) || 0}</font>
                                <br />Drugs: ${parseInt(data.personalstats.drugsused) || 0} used (${parseInt(data.personalstats.xantaken) || 0} xan)
                                <br />Consumables: ${parseInt(data.personalstats.consumablesused) || 0} used
                                <br />Refills: ${parseInt(data.personalstats.refills) || 0} used
                                <br />Networth: $${data.personalstats.networth.toLocaleString("en")}
                                <br />Last action: ${data.last_action.relative}
                                <br />Faction: <a href='https://www.torn.com/factions.php?step=profile&ID=${data.faction.faction_id}'>${data.faction.faction_name}</a>
                            </div>`);
                    }
                }).catch((err) => { console.log(err); });
            }).catch(function(err) {
                console.log(`fetch error ${err}`);
            });
        }
    }


    attack(); // Call the attack function at the start

    const BLACKLIST_DURATION = 60 * 1000;
    const blacklist = new Map();

    function isBlacklisted(userId) {
        if (blacklist.has(userId)) {
            if (Date.now() - blacklist.get(userId) < BLACKLIST_DURATION) {
                return true;
            } else {
                blacklist.delete(userId); // Remove the user from blacklist after the duration
            }
        }
        return false;
    }

function getBetAmountFromElement(userStatusWrap) {
    const betAmountElement = userStatusWrap.querySelector('.betBlock___wz9ED.columnItem___hnwxL');
    console.log("Bet Amount Element:", betAmountElement);  // Debugging log

    if (betAmountElement) {
        const betText = betAmountElement.textContent;
        console.log("Bet Text:", betText);  // Debugging log

        const betMatches = betText.match(/\$(\d{1,3}(?:,\d{3})*)(?!\d)/g);

        console.log("Bet Matches:", betMatches);  // Debugging log

        if (betMatches && betMatches.length) {
            const mainBet = betMatches[0].replace('$', '');
            return mainBet;
        }
    }

    return "N/A"; // Return "N/A" if nothing matches
}

let targetCashOnHand = 0; // Initialize it with some value
const BASE_MIN_PERCENTAGE = 0.05; // 5%
const BASE_MAX_PERCENTAGE = 0.10; // 10%

function calculateMugRange(betAmount) {
    if (!betAmount) {
        console.error("Bet amount is undefined or not provided!");
        return [0, 0];
    }
    const actualBetAmount = parseInt(betAmount.replace(/[$,]/g, ''));
    const baseMinMugAmount = actualBetAmount * BASE_MIN_PERCENTAGE;
    const baseMaxMugAmount = actualBetAmount * BASE_MAX_PERCENTAGE;

    const minMugAmount = Math.floor(baseMinMugAmount * 2);
    const maxMugAmount = Math.floor(baseMaxMugAmount * 2);

    return [minMugAmount, maxMugAmount];
}


async function fetchUserData(userId) {
    if (isBlacklisted(userId)) {
        return {
            username: idList.get(userId).username, // Added username
            level: idList.get(userId).level,
            healthStatus: 'Blacklisted'
        };
    }

    return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
            method: "GET",
            url: `https://api.torn.com/user/${userId}?selections=profile&key=${API_KEY}`,
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data.status && data.status.state) {
                        if (data.status.state === 'Hospital') {
                            blacklist.set(userId, Date.now()); // Blacklist the user
                            resolve({
                                username: data.name, // Fetch the username
                                level: data.level,
                                healthStatus: 'Blacklisted',
                                Age: data.age
                            });
                        } else {
                            resolve({
                                ...data,
                                username: data.name, // Fetch the username
                                healthStatus: data.status.state,
                                Age: data.age // Fetch the age
                            });
                        }
                    } else {
                        resolve({
                            ...data,
                            username: data.name, // Fetch the username
                            healthStatus: 'Error: Status unavailable or missing',
                            Age: data.age
                        });
                    }
                } catch (error) {
                    console.error('Error parsing user data:', error);
                    reject(error);
                }
            },
            onerror: function(error) {
                console.error('Error fetching user data:', error);
                reject(error);
            }
        });
    });
}
    function isMobile() {
    return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}

function initBox() {
    const updateBoxDimensions = () => {
        if (isMobile()) {
            // If on mobile
            box.style.width = '100%';  // Full width
            box.style.height = '60vh';  // 60% of viewport height
        } else {
            // If on desktop
            let width = window.innerWidth * 0.2;  // 20% of window width
            let maxHeight = window.innerHeight * 0.5;  // 50% of window height

            box.style.width = `${width}px`;
            box.style.maxHeight = `${maxHeight}px`;
        }
    }

    box.style.overflowY = 'scroll';
    box.style.background = 'black';
    box.style.color = 'green';
    box.style.marginTop = '10px'; // Provide spacing from the games list
    box.style.padding = '10px';
    box.style.border = '2px solid green';
    box.innerHTML = '<div style="font-size: 20px; color: gold;">Phantom Scripting</div><br>';

    // Finding the main content container
    const contentWrapper = document.querySelector('.content-wrapper[role="main"]');
    if (contentWrapper) {
        contentWrapper.appendChild(box);  // Append the box to the end of the container
    } else {
        // If for some reason the main container isn't found, the box will be added to the body
        document.body.appendChild(box);
    }

    // Call the update function initially
    updateBoxDimensions();

    // Adjust dimensions when window is resized
    window.addEventListener('resize', updateBoxDimensions);
}

    const box = document.createElement('div');
    initBox();

    const idList = new Map();

  function refreshBox() {
    while (box.firstChild && box.childElementCount > 1) {
        box.lastChild.remove();
    }

    if (!idList.size) {
        box.innerHTML += 'No Users Found';
        return;
    }

    idList.forEach((data, userId) => {
        const username = data.username || 'N/A';
        const healthStatus = data.healthStatus || 'Error: Unable to retrieve status';
        const level = data.level !== undefined ? data.level : 'N/A';
        const Age = data.age !== undefined ? data.age : 'N/A';
        const betAmount = data.betAmount !== "N/A" ? data.betAmount : `<span style="color:red;">${data.betAmount}</span>`;
        const entry = document.createElement('div');
        entry.style.marginBottom = '10px';

        if (healthStatus === 'Blacklisted') {
            entry.style.color = 'red';  // Set the color to red if the user is blacklisted
        }

    const [minMug, maxMug] = calculateMugRange(data.betAmount);

    entry.innerHTML = `
        <strong>Username:</strong> ${username}<br>
        <strong>ID:</strong> ${userId}<br>
        <strong>Level:</strong> ${level}<br>
        <strong>Health Status:</strong> ${healthStatus}<br>
        <strong>Bet Amount:</strong> ${betAmount}<br>
        <strong>Age:</strong> ${Age} Days<br>
        <strong>Estimated Mug Amount:</strong> ${formatCurrency(minMug)} - ${formatCurrency(maxMug)}`;

        if (!data.isPresent) {
            const mugBtn = document.createElement('button');
            mugBtn.innerText = 'Mug';
            mugBtn.style.backgroundColor = 'green';
            mugBtn.style.marginLeft = '10px';
            mugBtn.addEventListener('click', () => {
                fetchUserData(userId)
                    .then(userData => {
                        idList.set(userId, {
                            ...userData,
                            isPresent: true,
                            lastSeen: Date.now()
                        });
                        refreshBox();
                        window.open(`https://www.torn.com/loader.php?sid=attack&user2ID=${userId}`, '_blank');

                    })
                    .catch(error => {
                        console.error('Error fetching user data:', error);
                    });
            });
            entry.appendChild(mugBtn);
        }

        box.appendChild(entry);
    });
}
function formatCurrency(amount) {
    return `$${Number(amount).toLocaleString('en-US')}`;
}

function monitorChanges() {
    const userInfoWraps = document.querySelectorAll('.userStatusWrap___ljSJG');
    const currentTime = Date.now();

    if (!userInfoWraps.length) {
        setTimeout(monitorChanges, 2000);
        return;
    }

    const newIDs = new Set();
    userInfoWraps.forEach(userStatusWrap => {
        const userId = userStatusWrap.getAttribute('id').split('_')[0];

        // Extract bet amount from sibling element with aria-label attribute
        let betAmount = "N/A";
        const betElement = userStatusWrap.closest('.row___CHcax').querySelector('.betBlock___wz9ED');
        if (betElement) {
            const betAriaLabel = betElement.getAttribute('aria-label');
            const betMatch = betAriaLabel.match(/Bet amount: (\d+)/);
            if (betMatch && betMatch[1]) {
                betAmount = formatCurrency(betMatch[1]);
            }
        }

        if (userId && !idList.has(userId)) {
            idList.set(userId, { isPresent: true, lastSeen: currentTime, betAmount });
            fetchUserData(userId)
                .then(userData => {
                    idList.set(userId, {
                        isPresent: true,
                        ...userData,
                        lastSeen: currentTime,
                        betAmount
                    });
                    refreshBox();
                })
                .catch(error => {
                    console.error('Error fetching user data:', error);
                });
        } else if (idList.has(userId)) {
            idList.get(userId).lastSeen = currentTime;
            if (betAmount !== "N/A") idList.get(userId).betAmount = betAmount;
        }
        newIDs.add(userId);
    });

    idList.forEach((data, userId) => {
        if (!newIDs.has(userId) && data.isPresent) {
            data.isPresent = false;
            data.leftTime = currentTime;
        }
    });

    removeLeftUsers(currentTime);
    setTimeout(monitorChanges, 2000);
}




    function removeLeftUsers(currentTime) {
        const THIRTY_SECONDS = 30 * 1000;
        const leftUsers = [];

        idList.forEach((data, userId) => {
            if (!data.isPresent && (currentTime - data.leftTime) > THIRTY_SECONDS) {
                leftUsers.push(userId);
            }
        });

        leftUsers.forEach(userId => {
            idList.delete(userId);
        });

        refreshBox();
    }

    monitorChanges();
})();