View Points Market Anytime

View Torn's point market from hospital, jail or travelling!

// ==UserScript==
// @name         View Points Market Anytime
// @namespace    heartflower.torn.com
// @version      1.2
// @description  View Torn's point market from hospital, jail or travelling!
// @author       Heartflower [2626587]
// @match        https://www.torn.com/pmarket.php*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // Set to 'true' if you want to highlight your own point listings
    // If set to 'true', you need a full access key, otherwise you need a public key
    let highlightSelf = true;

    // Set to 'true' if you want to show a link to remove your API key
    let removeKeyLink = true;

    // Fetch API key from storage
    let apiKey = '';

    let storedAPIKey;

    if (highlightSelf === true) {
        storedAPIKey = localStorage.getItem('hf-full-access-apiKey');
    } else if (highlightSelf === false) {
        storedAPIKey = localStorage.getItem('hf-public-apiKey');
    }

    if (storedAPIKey) {
        apiKey = storedAPIKey;
    }

    // Set some variables to use later
    let personalListings = [];
    let timestamp = 0;
    let hospitalTimestamp = 0;

    // Find the info message on Torn
    function findMessage() {
        let contentWrapper = document.body.querySelector('.content-wrapper');

        // If you can't find the content wrapper, try again
        if (!contentWrapper) {
            console.log(`Can't find content wrapper`);
            setTimeout(findMessage, 100);
        }

        let messageContainer = document.body.querySelector('.info-msg');
        let message = messageContainer.querySelector('.msg');

        // If you can't find the message, try again
        if (!message) {
            console.log(`Can't find message`);
            setTimeout(findMessage, 100);
        }

        let messageContent = message.textContent;

        // If the points market is available, don't show the table
        if (!messageContent.includes('This area is unavailable')) {
            console.log('Is available');
            return;
        }

        let originalTitle = document.querySelector('h4#skip-to-content');

        if (originalTitle) {
            originalTitle.parentNode.remove();
        }

        // Create the points market title as it's not usually there
        let titleContainer = document.createElement('div');

        let title = document.createElement('p');
        title.id = 'hf-pmv-title';
        title.textContent = 'Points Market';

        // Add a line break after the title
        let hrElement = document.createElement('hr');
        hrElement.className = 'page-head-delimiter m-top10 m-bottom10';

        titleContainer.appendChild(title);
        titleContainer.appendChild(hrElement);
        contentWrapper.insertBefore(titleContainer, contentWrapper.firstChild);

        // Change the message
        message.innerHTML = `The points market is fetched by the <p style='font-style:italic; display:inline'>View Points Market Anytime</p> script with the help of the API!`;
        messageContainer.style.background = 'rgb(85,137,33)';

        if (apiKey !== '') {
            if (highlightSelf === true) {
                fetchLogs();
                fetchHospitalTime();
            } else if (highlightSelf === false) {
                fetchPointsMarket();
                fetchHospitalTime();
            }

            if (removeKeyLink === true) {
                let link = document.createElement('a');
                link.href = '#'; // Set a placeholder href

                if (highlightSelf === true) {
                    link.textContent = ' Click here to remove your (full access) API key!';
                } else if (highlightSelf === false) {
                    console.log('Remove public');
                    link.textContent = ' Click here to remove your (public) API key!';
                }

                // Add click event listener to the link
                link.addEventListener('click', function(event) {
                    event.preventDefault(); // Prevent the default link behavior

                    if (highlightSelf === true) {
                        localStorage.removeItem('hf-full-access-apiKey');
                        alert('API key successfully removed!');
                        link.remove();
                    } else if (highlightSelf === false) {
                        localStorage.removeItem('hf-public-apiKey');
                        alert('API key successfully removed!');
                        link.remove();
                    }

                    let enterLink = document.createElement('a');
                    enterLink.href = '#'; // Set a placeholder href

                    if (highlightSelf === true) {
                        enterLink.textContent = ' Click here to enter your (full access) API key!';
                    } else if (highlightSelf === false) {
                        enterLink.textContent = ' Click here to enter your (public) API key!';
                    }

                    // Add click event listener to the link
                    enterLink.addEventListener('click', function(event) {
                        event.preventDefault(); // Prevent the default link behavior
                        promptAPIKey();
                        link.remove();
                    });

                    message.appendChild(enterLink);
                });

                message.appendChild(link);
            }

        } else {
            // If no API key is found, create a link to set one
            console.error('API key not found!');
            let link = document.createElement('a');
            link.href = '#'; // Set a placeholder href

            if (highlightSelf === true) {
                link.textContent = ' Click here to enter your (full access) API key!';
            } else if (highlightSelf === false) {
                link.textContent = ' Click here to enter your (public) API key!';
            }

            // Add click event listener to the link
            link.addEventListener('click', function(event) {
                event.preventDefault(); // Prevent the default link behavior
                promptAPIKey();
                link.remove();
            });

            message.appendChild(link);
        }

        // Add another line break after the message
        let anotherHrElement = document.createElement('hr');
        anotherHrElement.className = 'page-head-delimiter m-top10 m-bottom10';

        contentWrapper.appendChild(anotherHrElement);
    }

    // Prompt the user to enter their API key
    function promptAPIKey() {
        let enterAPIKey = null;
        if (highlightSelf === true) {
            enterAPIKey = prompt('Enter a full access API key here:');
        } else if (highlightSelf === false) {
            enterAPIKey = prompt('Enter a public API key here:');
        }

        if (enterAPIKey !== null && enterAPIKey.trim() !== '') {
            if (highlightSelf === true) {
                localStorage.setItem('hf-full-access-apiKey', enterAPIKey);
            } else if (highlightSelf === false) {
                localStorage.setItem('hf-public-apiKey', enterAPIKey);
            }

            alert('API key set succesfully');

            if (highlightSelf === true) {
                fetchLogs();
                fetchHospitalTime();
            } else {
                fetchPointsMarket();
                fetchHospitalTime();
            }
        } else {
            alert('No valid API key entered!');
        }
    }

    // Fetch the logs to fetch own listing IDs
    function fetchLogs() {
        let apiUrl = `https://api.torn.com/user/?key=${apiKey}&selections=log&log=5000&comment=ViewPointsMarketAnytime`;

        fetch(apiUrl)
            .then(response => response.json())
            .then(data => {
            let logData = data.log;

            for (let logID in logData) {
                let log = logData[logID];
                let listingID = log.data.listing_id;

                personalListings.push(listingID);
            }

            fetchPointsMarket();
        })
            .catch(error => console.error('Error fetching data: ' + error));
    }

    // Fetch the points market to fetch currently available listings
    function fetchPointsMarket() {
        let apiUrl = `https://api.torn.com/market/?selections=pointsmarket,timestamp&key=${apiKey}&comment=ViewPointsMarketAnytime`;

        fetch(apiUrl)
            .then(response => response.json())
            .then(data => {
            let pointsMarket = data.pointsmarket;
            timestamp = data.timestamp;

            addTimer();
            createTable(pointsMarket);
        })
            .catch(error => console.error('Error fetching data: ' + error));
    }

    // Fetch and display the time remaining in the hospital
    function fetchHospitalTime() {
        console.log('Fetching hospital time');

        let messageContainer = document.body.querySelector('.info-msg');
        let message = messageContainer.querySelector('.msg');
        if (!message) {
            console.error(`Can't find message!`)
            setTimeout(fetchHospitalTime, 100);
            return;
        }

        let apiUrl = `https://api.torn.com/user/?selections=basic&key=${apiKey}&comment=ViewPointsMarketAnytime`;

        fetch(apiUrl)
            .then(response => response.json())
            .then(data => {
            let status = data.status;
            hospitalTimestamp = status.until;

            console.log(messageContainer);
            let timeRemaining = 0;

            let timerElement = document.createElement('div');
            timerElement.id = 'hf-pmv-hospital-timer';
            timerElement.style.display = 'inline';
            timerElement.style.paddingLeft = '4px';

            if (removeKeyLink === true) {
                message.insertBefore(timerElement, message.lastChild);
            } else if (removeKeyLink === false) {
                message.appendChild(timerElement);
            }

            updateTimer();
            setInterval(updateTimer, 100);
        })
            .catch(error => console.error('Error fetching data: ' + error));
    }

    // Function to calculate remaining time
    function calculateTimeRemaining() {
        let currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
        let timeDifference = hospitalTimestamp - currentTime;

        if (timeDifference <= 0) {
            return "You're out of the hospital!";
        }

        let days = Math.floor(timeDifference / (24 * 60 * 60));
        let hours = Math.floor((timeDifference % (24 * 60 * 60)) / (60 * 60));
        let minutes = Math.floor((timeDifference % (60 * 60)) / 60);
        let seconds = timeDifference % 60;

        let remainingTime = "You will be out of the hospital in ";

        // Days
        if (days > 0) {
            remainingTime += `${days} ${days === 1 ? 'day' : 'days'}, `;
        }

        // Hours
        if (hours > 0) {
            remainingTime += `${hours} ${hours === 1 ? 'hour' : 'hours'}, `;
        }

        // Minutes
        if (minutes > 0) {
            remainingTime += `${minutes} ${minutes === 1 ? 'minute' : 'minutes'} and `;
        }

        // Seconds
        remainingTime += `${seconds} ${seconds === 1 ? 'second' : 'seconds'}`;

        remainingTime += '.';

        return remainingTime;
    }

    // Function to update timer display
    function updateTimer(hospitalTimestamp) {
        let timerElement = document.getElementById('hf-pmv-hospital-timer');
        if (timerElement) {
            timerElement.textContent = calculateTimeRemaining(hospitalTimestamp);
        }
    }


    // Create the table
    function createTable(data) {
        let contentWrapper = document.body.querySelector('.content-wrapper');

        // If the page hasn't fully loaded in yet, try again
        if (!contentWrapper) {
            console.log('No content wrapper');
            setTimeout(function () {
                createTable(data);
            }, 100);
        }

        let existingContainer = document.getElementById('hf-pmv-table');

        // If the table already exists, don't add it again
        if (existingContainer) {
            console.log('Table already there');
            return;
        }

        let tableRows = [];

        // Sort listing IDs by cost
        for (let listingID in data) {
            let listing = data[listingID];
            let cost = listing.cost;
            let quantity = listing.quantity;
            let totalCost = listing.total_cost;

            let row = {
                listingID: listingID,
                cost: cost,
                quantity: quantity,
                totalCost: totalCost
            };

            tableRows.push(row);
        }

        tableRows.sort((a, b) => a.cost - b.cost);

        // Create the table container
        let table = document.createElement('ul');
        table.id = 'hf-pmv-table';

        // Create the header row
        let headerRow = document.createElement('li');
        headerRow.className = 'hf-pmv-table-header';
        createTableCell(headerRow, 'hf-pmv-cost', 'Cost');
        createTableCell(headerRow, 'hf-pmv-qty', 'Quantity');
        createTableCell(headerRow, 'hf-pmv-total', 'Total');
        table.appendChild(headerRow);

        // Loop through every listingID to fetch the necessary info
        for (let i = 0; i < tableRows.length; i++) {
            let row = tableRows[i];
            let numericListingID = parseInt(row.listingID);

            // Check if the listing ID is part of the personal listings fetched from the logs
            if (personalListings.includes(numericListingID)) {
                createTableRow(table, 'self', row.listingID, row.cost, row.quantity, row.totalCost);
            } else {
                createTableRow(table, 'other', row.listingID, row.cost, row.quantity, row.totalCost);
            }
        }

        contentWrapper.appendChild(table);
    }

    // Create a table row with the necessary data
    function createTableRow(table, info, listingID, cost, quantity, totalCost) {
        let tableRow = document.createElement('li');
        tableRow.id = 'hf-pmv-' + listingID;
        tableRow.className = 'hf-pmv-table-row';

        let costText = cost.toLocaleString('en-US', {style: 'currency', currency: 'USD', maximumFractionDigits: 0, minimumFractionDigits: 0});
        createTableCell(tableRow, 'hf-pmv-cost', costText)

        let quantityText = quantity.toLocaleString('en-US');
        createTableCell(tableRow, 'hf-pmv-qty', quantityText)

        let totalCostText = totalCost.toLocaleString('en-US', {style: 'currency', currency: 'USD', maximumFractionDigits: 0, minimumFractionDigits: 0});
        createTableCell(tableRow, 'hf-pmv-total', totalCostText)

        table.appendChild(tableRow);

        if (info === 'self') {
            tableRow.style.background = 'rgba(85,137,33,.5)';
        }
    }

    // Create a table cell with the necessary data
    function createTableCell(tableRow, className, textContent) {
        let span = document.createElement('span');
        span.className = className;
        span.textContent = textContent;
        tableRow.appendChild(span);
    }

    // Add a refresh button
    function addRefreshButton() {
        let existingButton = document.getElementById('hf-refresh');
        if (existingButton) {
            return;
        }

        let timer = document.getElementById('hf-refresh-timer');

        if (timer) {
            timer.remove();
        }

        let title = document.getElementById('hf-pmv-title');

        let refreshButton = document.createElement('button');
        refreshButton.id = 'hf-refresh';
        refreshButton.style.float = 'right';
        refreshButton.innerHTML = `<svg class="hf-refresh-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 14.47"><defs><style>.cls-1{opacity:0.35;}.cls-2{fill:#fff;}.cls-3{fill:#777;}</style></defs><g id="Слой_2" data-name="Слой 2"><g id="icons"><g class="cls-1"><path class="cls-2" d="M1.68,7.74A5.05,5.05,0,0,1,11.5,6.05H8.42l3.84,4L16,6.05H13.26A6.74,6.74,0,1,0,11,13l-1.06-1.3A5.06,5.06,0,0,1,1.68,7.74"></path></g><path class="cls-3" d="M1.68,6.74A5.05,5.05,0,0,1,11.5,5.05H8.42l3.84,4L16,5.05H13.26A6.74,6.74,0,1,0,11,12l-1.06-1.3A5.06,5.06,0,0,1,1.68,6.74"></path></g></g></svg>`;

        title.appendChild(refreshButton);

        // Add event listener to the button
        refreshButton.addEventListener('click', function() {
            location.reload();
        });

        let cls2 = refreshButton.querySelector('.cls-2');
        let cls3 = refreshButton.querySelector('.cls-3');

        // Add event listeners for hover
        refreshButton.addEventListener('mouseenter', function() {
            refreshButton.style.color = 'var(--default-blue-color)';
            cls2.style.fill = 'var(--default-blue-color)';
            cls3.style.fill = 'var(--default-blue-color)';
        });

        refreshButton.addEventListener('mouseleave', function() {
            refreshButton.style.color = 'var(--default-color)';
            cls2.style.fill = 'var(--default-color)';
            cls3.style.fill = 'var(--default-color)';
        });
    }

    // Add a timer to count down to refresh time
    function addTimer() {
        let refreshTimestamp = timestamp + 30;

        let intervalID = setInterval(() => {
            let currentTimestamp = Math.floor(Date.now() / 1000);
            let remainingTime = refreshTimestamp - currentTimestamp;

            if (remainingTime <= 0) {
                clearInterval(intervalID);
                addRefreshButton();
                return;
            }

            let title = document.getElementById('hf-pmv-title');
            let timer = document.getElementById('hf-refresh-timer');
            if (!timer) {
                timer = document.createElement('span');
                timer.id = 'hf-refresh-timer';
            }

            timer.textContent = remainingTime;

            title.appendChild(timer);

        }, 1000);
    }

    // Add CSS to the document
    GM_addStyle(`
        #hf-pmv-title {
            padding: 4px;
            color: var(--content-title-color);
            font-weight: 700;
            font-size: 22px;
            margin: 5px 0 -5px -4px
        }

        #hf-pmv-table {
            text-align: right;
            width: fit-content;
            margin: auto;
            color: var(--default-color);
        }

        .hf-pmv-table-header {
            border-bottom: var(--default-panel-divider-outer-side-color) 1px solid;
            background: var(--title-black-gradient);
            border-radius: 5px 5px 0 0;
            width: fit-content;
            font-weight: bold;
            color: var(--tutorial-title-color);
        }

        .hf-pmv-table-row {
            border-bottom: var(--default-panel-divider-outer-side-color) 1px solid;
            border-top: var(--default-panel-divider-inner-side-color) 1px solid;
            background: var(--default-bg-panel-color);
            width: fit-content;
        }

        .hf-pmv-qty {
            border-left = 'var(--default-panel-divider-outer-side-color) 2px solid';
            border-right = 'var(--default-panel-divider-outer-side-color) 2px solid';
        }

        .hf-pmv-cost, .hf-pmv-qty {
            width: 100px;
            padding: 8px;
            display: inline-block;
        }

        .hf-pmv-total {
            width: 150px;
            padding: 8px;
            display: inline-block;
        }

        #hf-refresh {
            z-index: 9999;
            top: 188px;
            right: 475px;
            color: var(--default-color);
            cursor: pointer;
            width: 30px;
        }

        #hf-refresh-timer {
            float: right;
            font-size: 14px;
            margin-top: 6px;
            width: 30px;
            text-align: center;
        }

        .hf-refresh-svg {
            width: 14px
        }

        .hf-refresh-svg {
            margin-top: 4px;
        }

        @media only screen and (max-width: 785px) {
            .hf-pmv-total {
                width: 138px;
            }

            #hf-pmv-title {
                font-size: 14px;
            }

            .hf-refresh-svg, #hf-refresh-timer {
                margin-top: 0px;
            }

            #hf-refresh {
                margin-top: -2px;
            }
        }

        @media only screen and (max-width: 386px) {
            .hf-pmv-total {
               width: 100px;
            }

            .hf-pmv-cost, .hf-pmv-qty {
               width: 86px;
            }
        }
    `);

    // Call the script upon page loading
    findMessage();

})();