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