Adds a fractional level pill to the PDA header tray.
Fra og med
// ==UserScript==
// @name Level Progress Estimation
// @namespace http://tampermonkey.net/
// @version 2.1
// @description Adds a fractional level pill to the PDA header tray.
// @author Pint-Shot-Riot
// @match https://www.torn.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @connect api.torn.com
// @license MIT
// ==/UserScript==
(function () {
"use strict";
const INACTIVE_THRESHOLD = 365 * 24 * 60 * 60;
const PAGE_SIZE = 100;
async function getApiKey() {
let key = localStorage.getItem("APIKey");
if (!key || key.length < 10) key = await GM_getValue("torn_api_key", "");
if (!key || key.length < 10) {
key = prompt("Please enter your (limited) Torn API Key:");
if (key) await GM_setValue("torn_api_key", key.trim());
}
return key ? key.trim() : null;
}
async function fetchTorn(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: (res) => {
try {
const data = JSON.parse(res.responseText);
if (data.error) reject(data.error.error);
else resolve(data);
} catch (e) { reject("JSON Error"); }
},
onerror: (err) => reject(err)
});
});
}
async function getAccurateLevel() {
const key = await getApiKey();
if (!key) return null;
let lastKnownFull = await GM_getValue("last_accurate_lvl", "0.00");
try {
const user = await fetchTorn(`https://api.torn.com/v2/user/hof?key=${key}`);
const { value: level, rank } = user.hof.level;
if (level >= 100) return "100.00";
const offset = Math.max(0, Math.floor((rank - 50) / PAGE_SIZE) * PAGE_SIZE);
const hofData = await fetchTorn(`https://api.torn.com/v2/torn/hof?limit=${PAGE_SIZE}&offset=${offset}&cat=level&key=${key}`);
const players = hofData.hof || [];
const currentAnchor = players
.filter(p => p.level === level && (Date.now()/1000 - p.last_action) > INACTIVE_THRESHOLD && p.position < rank)
.sort((a, b) => b.position - a.position)[0];
let lowerAnchor = players
.filter(p => p.level === (level - 1) && (Date.now()/1000 - p.last_action) > INACTIVE_THRESHOLD && p.position > rank)
.sort((a, b) => a.position - b.position)[0];
if (!lowerAnchor) {
const nextHof = await fetchTorn(`https://api.torn.com/v2/torn/hof?limit=${PAGE_SIZE}&offset=${offset + PAGE_SIZE}&cat=level&key=${key}`);
lowerAnchor = (nextHof.hof || [])
.filter(p => p.level === (level - 1) && (Date.now()/1000 - p.last_action) > INACTIVE_THRESHOLD)
.sort((a, b) => a.position - b.position)[0];
}
// Logic check: If anchors are missing, the math results in "level.00"
// We only want to block the update if it's a "fake" .00 caused by missing data
if (!currentAnchor || !lowerAnchor) {
console.log("Anchors missing, showing cached value.");
return lastKnownFull !== "0.00" ? lastKnownFull : level.toFixed(2);
}
const range = lowerAnchor.position - currentAnchor.position;
const yourProgress = lowerAnchor.position - rank;
const fraction = Math.max(0, Math.min(0.99, yourProgress / range));
const newVal = (level + fraction).toFixed(2);
// UPDATE LOGIC:
// We accept the new value if:
// 1. It's actually progress (higher than before)
// 2. OR it's a very slight fluctuation (within 0.02) due to HoF movement
const diff = parseFloat(newVal) - parseFloat(lastKnownFull);
if (diff > -0.02) {
await GM_setValue("last_accurate_lvl", newVal);
return newVal;
}
return lastKnownFull;
} catch (e) {
return lastKnownFull;
}
}
function injectIcon(val) {
const existingPill = document.getElementById('acc-lvl-pill');
if (existingPill) {
document.getElementById('acc-lvl-val').textContent = val;
return;
}
const header = document.querySelector('#header-root') || document.querySelector('.header-wrapper');
if (!header) return;
const tray = header.querySelector('[class*="right_"]') ||
header.querySelector('[class*="header-buttons"]') ||
header.querySelector('.header-navigation');
if (!tray) return;
const pill = document.createElement('div');
pill.id = 'acc-lvl-pill';
pill.style = "display: inline-flex; align-items: center; background: #333; border: 1px solid #444; border-radius: 10px; padding: 2px 8px; margin: 0 4px; height: 22px; vertical-align: middle; cursor: pointer; box-shadow: 0 1px 3px rgba(0,0,0,0.5); flex-shrink: 0; z-index: 999;";
pill.innerHTML = `<span style="color: #85b200; font-size: 10px; font-weight: bold; margin-right: 4px; font-family: sans-serif;">LV</span><span id="acc-lvl-val" style="color: #fff; font-size: 11px; font-family: 'Courier New', monospace; font-weight: bold;">${val}</span>`;
pill.onclick = () => window.location.href = "/halloffame.php#/type=level";
tray.prepend(pill);
}
async function run() {
let currentVal = await GM_getValue("last_accurate_lvl", null);
if (currentVal) injectIcon(currentVal);
const update = async () => {
const val = await getAccurateLevel();
if (val) {
currentVal = val;
injectIcon(val);
}
};
await update();
setInterval(update, 600000);
setInterval(() => { if (currentVal) injectIcon(currentVal); }, 2000);
}
if (document.readyState === "complete") run();
else window.addEventListener("load", run);
})();