Show an estimated fractional level progress on the Torn sidebar.
// ==UserScript==
// @name Accurate Level
// @namespace http://tampermonkey.net/
// @version 2.1.1
// @description Show an estimated fractional level progress on the Torn sidebar.
// @author Maximate
// @icon https://editor.torn.com/33b77f1c-dcd9-4d96-867f-5b578631d137-3441977.png
// @match https://www.torn.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM.setValue
// @grant GM.getValue
// @grant GM_xmlhttpRequest
// @grant GM.xmlHttpRequest
// @grant GM_registerMenuCommand
// @connect api.torn.com
// @license MIT
// ==/UserScript==
(function () {
"use strict";
const STORAGE = {
apiKey: "torn_api_key",
cache: "level_progress_cache_v2",
rateWindowStart: "level_progress_rate_window_start_v2",
rateCount: "level_progress_rate_count_v2",
lastCallAt: "level_progress_last_call_at_v2",
needsRecalc: "level_progress_needs_recalc_v2",
firstRunDone: "level_progress_first_run_done_v2",
};
const PAGE_SIZE = 100;
const CACHE_DURATION_MS = 12 * 60 * 60 * 1000;
const INACTIVE_THRESHOLD_SECONDS = 365 * 24 * 60 * 60;
const RATE_LIMIT = 60;
const RATE_WINDOW_MS = 60 * 1000;
const MIN_INTERVAL_MS = 1100;
const MAX_SCAN_PAGES = 30;
const PDA_APIKEY_PLACEHOLDER = "###PDA-APIKEY###";
const PDA_RETRY_INTERVAL_MS = 2500;
let inflightLevelPromise = null;
let applyTimer = null;
let hasUpdatedThisSession = false;
let runtimePlatform = "desktop";
let periodicRefreshTimer = null;
const storageState = Object.create(null);
function safeLocalStorageGet(key) {
try {
return localStorage.getItem(key);
} catch {
return null;
}
}
function safeLocalStorageSet(key, value) {
try {
localStorage.setItem(key, value);
} catch {}
}
function getRequestFunction() {
if (typeof GM_xmlhttpRequest === "function") {
return GM_xmlhttpRequest;
}
if (typeof GM !== "undefined" && typeof GM.xmlHttpRequest === "function") {
return GM.xmlHttpRequest.bind(GM);
}
return null;
}
function detectPlatform() {
const hasPdaApiKey = Boolean(safeLocalStorageGet("APIKey"));
const injectedKeyPresent = PDA_APIKEY_PLACEHOLDER.charAt(0) !== "#";
const userAgent = String(navigator.userAgent || "");
const looksLikePda =
/torn\s*pda|cordova|capacitor|android.+wv|iphone.+mobile/i.test(
userAgent,
);
return hasPdaApiKey || injectedKeyPresent || looksLikePda
? "pda"
: "desktop";
}
function decodeLocalValue(raw, fallback) {
if (raw === null || raw === undefined || raw === "") {
return fallback;
}
try {
return JSON.parse(raw);
} catch {
return raw;
}
}
async function readPersistentValue(key, fallback) {
if (typeof GM !== "undefined" && typeof GM.getValue === "function") {
const value = await GM.getValue(key, fallback);
return value === undefined ? fallback : value;
}
if (typeof GM_getValue === "function") {
const value = GM_getValue(key, fallback);
return value === undefined ? fallback : value;
}
return decodeLocalValue(safeLocalStorageGet(key), fallback);
}
function writePersistentValue(key, value) {
if (typeof GM !== "undefined" && typeof GM.setValue === "function") {
return GM.setValue(key, value);
}
if (typeof GM_setValue === "function") {
return Promise.resolve(GM_setValue(key, value));
}
safeLocalStorageSet(key, JSON.stringify(value));
return Promise.resolve();
}
function readPdaApiKey() {
const injectedKey =
PDA_APIKEY_PLACEHOLDER.charAt(0) !== "#" ? PDA_APIKEY_PLACEHOLDER : "";
const savedKey = safeLocalStorageGet("APIKey") || "";
return String(savedKey || injectedKey || "").trim();
}
async function initStorage() {
runtimePlatform = detectPlatform();
for (const key of Object.values(STORAGE)) {
storageState[key] = await readPersistentValue(key, "");
}
const savedApiKey = String(storageState[STORAGE.apiKey] || "").trim();
if (!savedApiKey) {
const pdaApiKey = readPdaApiKey();
if (pdaApiKey) {
storageState[STORAGE.apiKey] = pdaApiKey;
await writePersistentValue(STORAGE.apiKey, pdaApiKey);
}
}
}
function getStored(key, fallback) {
const value =
Object.prototype.hasOwnProperty.call(storageState, key) &&
storageState[key] !== ""
? storageState[key]
: fallback;
return value === undefined ? fallback : value;
}
function setStored(key, value) {
storageState[key] = value;
void writePersistentValue(key, value);
}
function getApiKey() {
const storedKey = String(getStored(STORAGE.apiKey, "") || "").trim();
if (storedKey) {
return storedKey;
}
const pdaApiKey = readPdaApiKey();
if (pdaApiKey) {
storageState[STORAGE.apiKey] = pdaApiKey;
void writePersistentValue(STORAGE.apiKey, pdaApiKey);
return pdaApiKey;
}
return "";
}
function getCache() {
const raw = getStored(STORAGE.cache, "");
if (!raw) {
return null;
}
try {
const parsed = JSON.parse(raw);
return parsed && typeof parsed === "object" ? parsed : null;
} catch (error) {
console.warn("[Level Progress] Failed to parse cache.", error);
return null;
}
}
function setCache(value) {
if (!value) {
setStored(STORAGE.cache, "");
return;
}
setStored(STORAGE.cache, JSON.stringify(value));
}
function clearCache() {
setCache(null);
setStored(STORAGE.needsRecalc, false);
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function waitForApiSlot() {
while (true) {
const now = Date.now();
let windowStart = Number(getStored(STORAGE.rateWindowStart, 0)) || 0;
let callCount = Number(getStored(STORAGE.rateCount, 0)) || 0;
let lastCallAt = Number(getStored(STORAGE.lastCallAt, 0)) || 0;
if (!windowStart || now - windowStart >= RATE_WINDOW_MS) {
windowStart = now;
callCount = 0;
setStored(STORAGE.rateWindowStart, windowStart);
setStored(STORAGE.rateCount, callCount);
}
const waitForInterval = Math.max(0, MIN_INTERVAL_MS - (now - lastCallAt));
const waitForWindow =
callCount >= RATE_LIMIT
? Math.max(0, RATE_WINDOW_MS - (now - windowStart))
: 0;
const waitMs = Math.max(waitForInterval, waitForWindow);
if (waitMs <= 0) {
setStored(STORAGE.lastCallAt, now);
setStored(STORAGE.rateCount, callCount + 1);
return;
}
await sleep(waitMs + 25);
}
}
function requestJson(url) {
return new Promise(async (resolve, reject) => {
try {
await waitForApiSlot();
} catch (error) {
reject(error);
return;
}
const requester = getRequestFunction();
if (requester) {
requester({
method: "GET",
url,
onload(response) {
if (response.status !== 200) {
reject(
new Error(
`HTTP ${response.status}: ${response.statusText || "Request failed"}`,
),
);
return;
}
try {
const data = JSON.parse(response.responseText);
if (data && data.error) {
reject(
new Error(
data.error.error || data.error.message || "API error",
),
);
return;
}
resolve(data);
} catch (error) {
reject(new Error("Invalid JSON response"));
}
},
onerror() {
reject(new Error("Network error"));
},
});
return;
}
fetch(url, { method: "GET", credentials: "omit" })
.then(async (response) => {
if (!response.ok) {
throw new Error(
`HTTP ${response.status}: ${response.statusText || "Request failed"}`,
);
}
const data = await response.json();
if (data && data.error) {
throw new Error(
data.error.error || data.error.message || "API error",
);
}
resolve(data);
})
.catch((error) => {
reject(error instanceof Error ? error : new Error("Network error"));
});
});
}
async function getUserLevelData(apiKey) {
const url = `https://api.torn.com/v2/user/hof?key=${encodeURIComponent(apiKey)}`;
const data = await requestJson(url);
const levelNode = data && data.hof && data.hof.level;
if (
!levelNode ||
typeof levelNode.value !== "number" ||
typeof levelNode.rank !== "number"
) {
throw new Error("Unexpected user HOF response");
}
return {
level: levelNode.value,
rank: levelNode.rank,
};
}
async function getHofPage(apiKey, offset) {
const url = `https://api.torn.com/v2/torn/hof?limit=${PAGE_SIZE}&offset=${offset}&cat=level&key=${encodeURIComponent(apiKey)}`;
const data = await requestJson(url);
return Array.isArray(data && data.hof) ? data.hof : [];
}
function isInactivePlayer(player) {
if (!player || typeof player.last_action !== "number") {
return false;
}
const nowSeconds = Math.floor(Date.now() / 1000);
return nowSeconds - player.last_action >= INACTIVE_THRESHOLD_SECONDS;
}
function findNearestCurrentAnchor(players, userLevel, userRank) {
let best = null;
for (const player of players) {
if (!player || player.level !== userLevel || !isInactivePlayer(player)) {
continue;
}
if (typeof player.position !== "number" || player.position >= userRank) {
continue;
}
if (!best || player.position > best.position) {
best = player;
}
}
return best;
}
function findNearestLowerAnchor(players, lowerLevel, userRank) {
let best = null;
for (const player of players) {
if (!player || player.level !== lowerLevel || !isInactivePlayer(player)) {
continue;
}
if (typeof player.position !== "number" || player.position <= userRank) {
continue;
}
if (!best || player.position < best.position) {
best = player;
}
}
return best;
}
async function findInactiveAnchors(apiKey, userLevel, userRank) {
if (userLevel <= 1) {
return {
currentAnchor: null,
lowerAnchor: null,
};
}
const pageCache = new Map();
const startOffset = Math.max(
0,
Math.floor((Math.max(1, userRank) - 1) / PAGE_SIZE) * PAGE_SIZE,
);
const lowerLevel = userLevel - 1;
let currentAnchor = null;
let lowerAnchor = null;
let currentBandSeen = false;
let lowerBandSeen = false;
let currentSearchDone = false;
let lowerSearchDone = false;
async function fetchPage(offset) {
if (offset < 0) {
return [];
}
if (pageCache.has(offset)) {
return pageCache.get(offset);
}
const page = await getHofPage(apiKey, offset);
pageCache.set(offset, page);
return page;
}
for (let distance = 0; distance < MAX_SCAN_PAGES; distance += 1) {
const upOffset = startOffset - distance * PAGE_SIZE;
const downOffset = startOffset + distance * PAGE_SIZE;
if (!currentSearchDone && upOffset >= 0) {
const page = await fetchPage(upOffset);
const containsCurrentLevel = page.some(
(player) => player && player.level === userLevel,
);
if (containsCurrentLevel) {
currentBandSeen = true;
}
const candidate = findNearestCurrentAnchor(page, userLevel, userRank);
if (candidate) {
currentAnchor = candidate;
currentSearchDone = true;
} else if (distance > 0 && currentBandSeen && !containsCurrentLevel) {
currentSearchDone = true;
}
}
if (!lowerSearchDone) {
const page = await fetchPage(downOffset);
const containsLowerLevel = page.some(
(player) => player && player.level === lowerLevel,
);
if (containsLowerLevel) {
lowerBandSeen = true;
}
const candidate = findNearestLowerAnchor(page, lowerLevel, userRank);
if (candidate) {
lowerAnchor = candidate;
lowerSearchDone = true;
} else if (distance > 0 && lowerBandSeen && !containsLowerLevel) {
lowerSearchDone = true;
}
}
if (
(currentAnchor || currentSearchDone) &&
(lowerAnchor || lowerSearchDone)
) {
break;
}
}
return {
currentAnchor,
lowerAnchor,
};
}
function calculateFinalLevel(
userLevel,
userRank,
currentAnchor,
lowerAnchor,
) {
if (userLevel >= 100) {
return 100;
}
if (userLevel <= 1 || !currentAnchor || !lowerAnchor) {
return userLevel;
}
const currentPos = Number(currentAnchor.position);
const lowerPos = Number(lowerAnchor.position);
if (
!Number.isFinite(currentPos) ||
!Number.isFinite(lowerPos) ||
lowerPos <= currentPos
) {
return userLevel;
}
const relative = (lowerPos - userRank) / (lowerPos - currentPos);
const clamped = Math.max(0, Math.min(0.99, relative));
return Number((userLevel + clamped).toFixed(2));
}
function buildCacheRecord(userData, anchors, finalLevel, mode) {
return {
level: userData.level,
rank: userData.rank,
finalLevel,
currentAnchor: anchors.currentAnchor
? {
id: anchors.currentAnchor.id,
position: anchors.currentAnchor.position,
lastAction: anchors.currentAnchor.last_action,
}
: null,
lowerAnchor: anchors.lowerAnchor
? {
id: anchors.lowerAnchor.id,
position: anchors.lowerAnchor.position,
lastAction: anchors.lowerAnchor.last_action,
}
: null,
mode,
updatedAt: Date.now(),
};
}
async function computeDisplayData(forceRecalc = false) {
const apiKey = getApiKey();
if (!apiKey) {
return null;
}
const userData = await getUserLevelData(apiKey);
if (userData.level >= 100) {
const record = buildCacheRecord(
userData,
{ currentAnchor: null, lowerAnchor: null },
100,
"capped",
);
setCache(record);
return record;
}
if (!forceRecalc) {
const cached = getCache();
if (
cached &&
cached.level === userData.level &&
typeof cached.updatedAt === "number" &&
Date.now() - cached.updatedAt < CACHE_DURATION_MS
) {
if (cached.currentAnchor && cached.lowerAnchor) {
const finalLevel = calculateFinalLevel(
userData.level,
userData.rank,
cached.currentAnchor,
cached.lowerAnchor,
);
const refreshed = {
...cached,
rank: userData.rank,
finalLevel,
updatedAt: Date.now(),
mode: "cached",
};
setCache(refreshed);
return refreshed;
}
const fallback = {
...cached,
rank: userData.rank,
finalLevel: userData.level,
updatedAt: Date.now(),
mode: "cached",
};
setCache(fallback);
return fallback;
}
}
const anchors = await findInactiveAnchors(
apiKey,
userData.level,
userData.rank,
);
const hasBothAnchors = Boolean(
anchors.currentAnchor && anchors.lowerAnchor,
);
const finalLevel = calculateFinalLevel(
userData.level,
userData.rank,
anchors.currentAnchor,
anchors.lowerAnchor,
);
const record = buildCacheRecord(
userData,
anchors,
finalLevel,
hasBothAnchors ? "live" : "fallback",
);
setCache(record);
return record;
}
async function getDisplayData() {
if (!getApiKey()) {
return null;
}
if (inflightLevelPromise) {
return inflightLevelPromise;
}
const forceRecalc = Boolean(getStored(STORAGE.needsRecalc, false));
inflightLevelPromise = computeDisplayData(forceRecalc)
.catch((error) => {
console.error(
"[Level Progress] Failed to compute display data.",
error,
);
return null;
})
.finally(() => {
setStored(STORAGE.needsRecalc, false);
inflightLevelPromise = null;
});
return inflightLevelPromise;
}
function wrapClassicLevelTextNode(row, textNode) {
const text = String(textNode.textContent || "");
const match = text.match(/^(\s*Level:?\s*)(\d+(?:\.\d+)?)(.*)$/i);
if (!match) {
return null;
}
const fragment = document.createDocumentFragment();
if (match[1]) {
fragment.appendChild(document.createTextNode(match[1]));
}
const valueSpan = document.createElement("span");
valueSpan.dataset.accurateLevelValue = "1";
valueSpan.textContent = match[2];
fragment.appendChild(valueSpan);
if (match[3]) {
fragment.appendChild(document.createTextNode(match[3]));
}
row.replaceChild(fragment, textNode);
return valueSpan;
}
function findClassicLevelValueElement() {
const rows = document.querySelectorAll("div, li, p, td, tr");
for (const row of rows) {
const rowText = (row.textContent || "").replace(/\s+/g, " ").trim();
if (!/^Level:?\s*\d+(?:\.\d+)?/i.test(rowText)) {
continue;
}
const injected = row.querySelector("[data-accurate-level-value='1']");
if (injected) {
return injected;
}
const cells = Array.from(row.children);
if (cells.length >= 2) {
const labelText = (cells[0].textContent || "")
.replace(/\s+/g, " ")
.trim();
const valueText = (cells[1].textContent || "").trim();
if (/^Level:?$/i.test(labelText) && /^\d+(?:\.\d+)?$/.test(valueText)) {
cells[1].dataset.accurateLevelValue = "1";
return cells[1];
}
}
const directChildren = Array.from(row.children);
for (const child of directChildren) {
const childText = (child.textContent || "").trim();
if (/^\d+(?:\.\d+)?$/.test(childText)) {
child.dataset.accurateLevelValue = "1";
return child;
}
}
for (const node of Array.from(row.childNodes)) {
if (node.nodeType !== Node.TEXT_NODE) {
continue;
}
const wrapped = wrapClassicLevelTextNode(row, node);
if (wrapped) {
return wrapped;
}
}
}
return null;
}
function findLevelValueElement() {
const blocks = document.querySelectorAll("[class*='point-block']");
for (const block of blocks) {
const label = block.querySelector("[class*='name']");
const value = block.querySelector("[class*='value']");
if (!label || !value) {
continue;
}
const labelText = (label.textContent || "").trim().replace(/\s+/g, " ");
if (/^level:?$/i.test(labelText)) {
return value;
}
}
return findClassicLevelValueElement();
}
function extractVisibleLevel(element) {
if (!element) {
return null;
}
const match = (element.textContent || "").trim().match(/^(\d+)/);
return match ? Number(match[1]) : null;
}
function setLevelTooltip(element, data) {
if (!element) {
return;
}
if (!data) {
element.title =
runtimePlatform === "pda"
? "Tap this level value to configure Accurate Level for PDA."
: "Set your Torn API key to enable fractional level progress.";
return;
}
const modeText =
data.mode === "cached"
? "Updated from cached anchors."
: data.mode === "live"
? "Calculated from nearby inactive HOF anchors."
: data.mode === "fallback"
? "Showing integer level because suitable anchors were not found."
: "Level progress calculated.";
const anchorBits = [];
if (data.currentAnchor && typeof data.currentAnchor.position === "number") {
anchorBits.push(
`Current-level anchor: #${data.currentAnchor.position} (ID ${data.currentAnchor.id})`,
);
}
if (data.lowerAnchor && typeof data.lowerAnchor.position === "number") {
anchorBits.push(
`Lower-level anchor: #${data.lowerAnchor.position} (ID ${data.lowerAnchor.id})`,
);
}
const anchorText = anchorBits.length ? ` ${anchorBits.join(". ")}.` : "";
element.title = `${modeText} Rank: ${data.rank}.${anchorText}`;
}
function bindConfigTrigger(element) {
if (!element || element.dataset.accurateLevelConfigBound === "1") {
return;
}
element.dataset.accurateLevelConfigBound = "1";
element.addEventListener("click", () => {
if (runtimePlatform === "pda" || !getApiKey()) {
showConfig();
}
});
}
async function applyLevelDisplay() {
const valueElement = findLevelValueElement();
if (!valueElement) {
return;
}
bindConfigTrigger(valueElement);
const visibleLevel = extractVisibleLevel(valueElement);
const cached = getCache();
if (
visibleLevel &&
cached &&
cached.level &&
visibleLevel !== cached.level
) {
clearCache();
setStored(STORAGE.needsRecalc, true);
hasUpdatedThisSession = false;
}
if (hasUpdatedThisSession && !getStored(STORAGE.needsRecalc, false)) {
if (cached && typeof cached.finalLevel === "number") {
valueElement.textContent = cached.finalLevel.toFixed(2);
}
setLevelTooltip(valueElement, cached);
return;
}
const data = await getDisplayData();
setLevelTooltip(valueElement, data);
if (!data || typeof data.finalLevel !== "number") {
return;
}
valueElement.textContent = data.finalLevel.toFixed(2);
hasUpdatedThisSession = true;
}
function scheduleApply() {
if (applyTimer) {
clearTimeout(applyTimer);
}
applyTimer = setTimeout(() => {
applyLevelDisplay().catch((error) => {
console.error("[Level Progress] Failed to apply sidebar level.", error);
});
}, 150);
}
function startPeriodicRefresh() {
if (periodicRefreshTimer || runtimePlatform !== "pda") {
return;
}
periodicRefreshTimer = setInterval(() => {
const levelElement = findLevelValueElement();
const text = (levelElement?.textContent || "").trim();
const needsAnotherPass =
!/\d+\.\d{2}/.test(text) ||
Boolean(getStored(STORAGE.needsRecalc, false));
if (needsAnotherPass) {
scheduleApply();
}
}, PDA_RETRY_INTERVAL_MS);
}
async function validateKey(key) {
try {
const url = `https://api.torn.com/v2/key/info?key=${encodeURIComponent(key)}`;
const data = await requestJson(url);
if (
data &&
data.info &&
data.info.access &&
Number(data.info.access.level) >= 1
) {
return { valid: true };
}
return { valid: false, error: "Key does not have the required access." };
} catch (error) {
return { valid: false, error: error.message || "Validation failed." };
}
}
function showConfig() {
const overlay = document.createElement("div");
overlay.style.cssText = [
"position:fixed",
"inset:0",
"background:rgba(0,0,0,0.55)",
"z-index:99999",
].join(";");
const modal = document.createElement("div");
modal.style.cssText = [
"position:fixed",
"top:50%",
"left:50%",
"transform:translate(-50%,-50%)",
"width:min(92vw,420px)",
"background:#fff",
"color:#111",
"padding:20px",
"border-radius:10px",
"box-shadow:0 20px 60px rgba(0,0,0,0.25)",
"z-index:100000",
"font:14px/1.4 Arial,sans-serif",
].join(";");
modal.innerHTML = `
<h3 style="margin:0 0 8px;font-size:18px;">Torn API Configuration</h3>
<p style="margin:0 0 12px;">Enter your Torn API key to estimate fractional level progress.${runtimePlatform === "pda" ? " PDA can also use the app API key automatically if it is already saved." : ""}</p>
<input
id="levelProgressKeyInput"
type="text"
value="${escapeHtml(getApiKey())}"
placeholder="Paste API key"
style="width:100%;padding:10px;border:1px solid #c9ced6;border-radius:6px;box-sizing:border-box;"
/>
<div id="levelProgressStatus" style="min-height:20px;margin:10px 0 6px;color:#a33;"></div>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<button id="levelProgressSave" style="${buttonStyle("#0b6efd")}">Validate & Save</button>
<button id="levelProgressClear" style="${buttonStyle("#6c757d")}">Clear Cache</button>
<button id="levelProgressCancel" style="${buttonStyle("#495057")}">Close</button>
</div>
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
const input = modal.querySelector("#levelProgressKeyInput");
const status = modal.querySelector("#levelProgressStatus");
const saveButton = modal.querySelector("#levelProgressSave");
const clearButton = modal.querySelector("#levelProgressClear");
const cancelButton = modal.querySelector("#levelProgressCancel");
function close() {
overlay.remove();
}
saveButton.addEventListener("click", async () => {
const newKey = String(input.value || "").trim();
if (!newKey) {
status.textContent = "Please enter an API key.";
status.style.color = "#b42318";
return;
}
saveButton.disabled = true;
saveButton.textContent = "Validating...";
status.textContent = "Checking key...";
status.style.color = "#0b6efd";
const result = await validateKey(newKey);
if (!result.valid) {
status.textContent = result.error || "Invalid API key.";
status.style.color = "#b42318";
saveButton.disabled = false;
saveButton.textContent = "Validate & Save";
return;
}
setStored(STORAGE.apiKey, newKey);
clearCache();
setStored(STORAGE.needsRecalc, true);
hasUpdatedThisSession = false;
status.textContent = "API key saved.";
status.style.color = "#16794a";
saveButton.textContent = "Saved";
setTimeout(() => {
close();
scheduleApply();
}, 500);
});
clearButton.addEventListener("click", () => {
clearCache();
hasUpdatedThisSession = false;
status.textContent = "Cache cleared.";
status.style.color = "#16794a";
scheduleApply();
});
cancelButton.addEventListener("click", close);
overlay.addEventListener("click", (event) => {
if (event.target === overlay) {
close();
}
});
}
function buttonStyle(background) {
return [
"appearance:none",
"border:none",
"border-radius:6px",
"padding:9px 14px",
`background:${background}`,
"color:#fff",
"cursor:pointer",
"font-weight:600",
].join(";");
}
function escapeHtml(value) {
return String(value || "")
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
}
async function bootstrap() {
await initStorage();
document.addEventListener("keydown", (event) => {
if (event.ctrlKey && event.shiftKey && event.key.toLowerCase() === "t") {
showConfig();
}
});
if (typeof GM_registerMenuCommand === "function") {
GM_registerMenuCommand("Configure API Key", showConfig);
}
if (!getStored(STORAGE.firstRunDone, false) && getApiKey()) {
setStored(STORAGE.firstRunDone, true);
setStored(STORAGE.needsRecalc, true);
}
scheduleApply();
startPeriodicRefresh();
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", scheduleApply, {
once: true,
});
}
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type !== "childList" || !mutation.addedNodes.length) {
continue;
}
for (const node of mutation.addedNodes) {
if (
node.nodeType === 1 &&
(node.matches?.("[class*='point-block']") ||
node.querySelector?.("[class*='point-block']") ||
/\bLevel:\b/i.test(node.textContent || ""))
) {
scheduleApply();
return;
}
}
}
});
function startObserver() {
if (!document.body) {
return;
}
observer.observe(document.body, {
childList: true,
subtree: true,
});
}
if (document.body) {
startObserver();
} else {
document.addEventListener("DOMContentLoaded", startObserver, {
once: true,
});
}
}
bootstrap().catch((error) => {
console.error("[Level Progress] Bootstrap failed.", error);
});
})();