Level Progress Estimation

Displays an estimated fractional level based on Hall of Fame position.

Verze ze dne 17. 04. 2026. Zobrazit nejnovější verzi.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         Level Progress Estimation
// @namespace    http://tampermonkey.net/
// @version      11
// @description  Displays an estimated fractional level based on Hall of Fame position.
// @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";

  async function getApiKey() {
    let key = localStorage.getItem("APIKey") || await GM_getValue("torn_api_key", "");
    if (!key || key.length < 10) {
      key = prompt("Please enter your 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;

    try {
      const userHof = await fetchTorn(`https://api.torn.com/v2/user/hof?key=${key}`);
      const level = userHof.hof.level.value;
      const myRank = userHof.hof.level.rank;

      if (level >= 100) return "100.00";

      const offset = Math.max(0, myRank - 250);
      const hofData = await fetchTorn(`https://api.torn.com/v2/torn/hof?limit=500&offset=${offset}&cat=level&key=${key}`);
      const players = hofData.hof || [];

      const currentLevel = players.filter(p => p.level === level).map(p => p.position);
      const prevLevel = players.filter(p => p.level === (level - 1)).map(p => p.position);

      if (currentLevel.length === 0) return level.toFixed(2);

      const topOfLevel = Math.min(...currentLevel);
      let bottomOfLevel = prevLevel.length > 0 ? Math.min(...prevLevel) : Math.max(...currentLevel) + 1;

      const range = bottomOfLevel - topOfLevel;
      const progress = bottomOfLevel - myRank;

      let fraction = progress / range;
      
      if (fraction <= 0) fraction = 0.01;
      if (fraction >= 1) fraction = 0.99;

      return (level + fraction).toFixed(2);
    } catch (e) {
      console.error(e);
      return null;
    }
  }

  function injectIcon(val) {
    const existing = document.getElementById('acc-lvl-pill');
    if (existing) {
      document.getElementById('acc-lvl-val').textContent = val;
      return;
    }

    const tray = document.querySelector('#header-root [class*="right_"]') || 
                 document.querySelector('[class*="header-navigation_"]') ||
                 document.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; flex-shrink: 0; z-index: 999; order: 1;";
    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 = (e) => {
        e.preventDefault();
        window.location.href = "/halloffame.php#/type=level";
    };

    tray.prepend(pill);
  }

  async function run() {
    let val = await getAccurateLevel();
    if (val) injectIcon(val);
    
    setInterval(async () => { 
      let v = await getAccurateLevel(); 
      if(v) { val = v; injectIcon(val); } 
    }, 600000);

    setInterval(() => { 
      if (!document.getElementById('acc-lvl-pill') && val) injectIcon(val); 
    }, 2000);
  }

  if (document.readyState === "complete") run();
  else window.addEventListener("load", run);
})();