NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @namespace https://openuserjs.org/users/SB100 // @name GGn Show Stats Change News Item // @description Shows stats since your last visit // @updateURL https://openuserjs.org/meta/SB100/GGn_Show_Stats_Change_News_Item.meta.js // @version 1.1.2 // @author SB100 // @copyright 2021, SB100 (https://openuserjs.org/users/SB100) // @license MIT // @include https://gazellegames.net/index.php // ==/UserScript== // ==OpenUserJS== // @author SB100 // ==/OpenUserJS== /* jshint esversion: 6 */ /** * BEGIN SETTINGS: You can edit these values */ // change this number if you want to change the cache time. Currently cached for 1 minute. Set to 0 to turn caching off const CACHE_TIME = 1000 * 60 * 1; // If there have been no stats changes, 'true' will hide the news item and 'false' will show a news item with all stats set to 0 const HIDE_ON_NO_CHANGE = true; /** * END SETTINGS: Don't change below here */ const SIZE_TYPES = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; /** * Try parsing a string into JSON, otherwise fallback */ function JsonParseWithDefault(s, fallback = null) { try { return JSON.parse(s); } catch (e) { return fallback; } } /** * Turn a string such as 77.45 GB [111] to Bytes as a floating number */ function parseStatFromString(s, decimals = 2) { const typeMatch = s.match(/[\d,]+\s([\w]+)/); const type = typeMatch && typeMatch.length >= 2 && typeMatch[1].toUpperCase(); // unknown type if (!SIZE_TYPES.includes(type)) { return 0; } const currentSizeIndex = SIZE_TYPES.findIndex(size => size === type); const desiredSizeIndex = 0; const size = parseFloat(s.replace('/,/g', '')); return parseFloat( (size * 1000 ** (currentSizeIndex - desiredSizeIndex)).toFixed(decimals) ); } /** * Turns bytes into the best representation possible */ function formatBytes(bytes) { if (bytes === 0) { return '0.00'; } const isNegative = bytes < 0; for (let i = 1; i < SIZE_TYPES.length - 1; i += 1) { const size = Math.abs(bytes) / 1024 ** i; if (size < 1) { return `${isNegative ? '-' : ''}${(Math.abs(bytes) / 1024 ** (i - 1)).toFixed(2)} ${SIZE_TYPES[i - 1]}`; } } return '0.00'; } /** * Turns ms into a more readable time */ function msToTime(s) { const ms = s % 1000; s = (s - ms) / 1000; const secs = s % 60; s = (s - secs) / 60; const mins = s % 60; s = (s - mins) / 60; const hrs = s % 60; s = (s - hrs) / 24; const days = s % 24; return `${days > 0 ? `${days} days ` : ''}${hrs > 0 ? `${hrs} hours ` : ''}${mins > 0 ? `${mins} mins ` : ''}${secs > 0 ? `${secs} secs ` : ''}`; } /** * Parse HTML and find up, down and ratio amounts */ function getStatsFromHtml() { const spans = document.querySelectorAll('#userinfo_stats span'); const ratio = parseFloat(spans[3].textContent); const goldMatch = document.querySelector('#stats_gold span').textContent.match(/[\d\,]+/); const gold = parseFloat(goldMatch && goldMatch[0].replaceAll(',', '')); return { up: parseStatFromString(spans[0].textContent), down: parseStatFromString(spans[1].textContent), ratio: isNaN(ratio) ? 0 : ratio, gold: isNaN(gold) ? 0 : gold, time: (new Date).getTime() } } /** * Finds a valid cache cahnged object, and cleans up all of the old ones if they are outdated */ function getCacheChangeObj() { const sortedKeys = Object .keys(window.localStorage) .filter(key => key.startsWith('cacheStats-')) .sort((a, b) => { const aTime = parseInt(a.replace('cacheStats-', ''), 10); const bTime = parseInt(b.replace('cacheStats-', ''), 10); return aTime - bTime; }); for (let i = 0, sortedKeysLength = sortedKeys.length; i < sortedKeysLength; i += 1) { const key = sortedKeys[i]; const keyTime = parseInt(key.replace('cacheStats-', ''), 10); // last entry, and we're still in the cache period if (i === sortedKeysLength - 1 && (new Date).getTime() - keyTime < CACHE_TIME) { return JsonParseWithDefault(window.localStorage.getItem(key)); } window.localStorage.removeItem(key); } return null; } /** * Display the stats change in the header bar next to the user's username */ function displayDelta(change) { const timeDiff = (new Date).getTime() - change.generated; const mainColumn = document.querySelector('.main_column'); const changeElem = document.createElement('div'); changeElem.classList.add('box'); changeElem.innerHTML = ` <div class='head'> Stats Update <div class='newsdate' ${timeDiff > 1000 ? `title="Values have been cached for ${msToTime(timeDiff)}"` : ''}> Compared to ${msToTime(change.time)}ago ${timeDiff > 1000 ? '*' : ''} </div> </div> <div class='pad'> ⬆️ ${formatBytes(change.up)} up ⬇️ ${formatBytes(change.down)} down *️⃣ ${formatBytes(change.up - change.down)} buffer 🔄 ${(change.ratio).toFixed(2)} ratio ⏺ ${change.gold.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")} gold </div> `; mainColumn.prepend(changeElem); } // script runner (function () { 'use strict'; const newStats = getStatsFromHtml(); const oldStats = JsonParseWithDefault(window.localStorage.getItem('lastStats') || newStats, newStats); const change = { up: newStats.up - oldStats.up, down: newStats.down - oldStats.down, ratio: newStats.ratio - oldStats.ratio, gold: newStats.gold - oldStats.gold, time: newStats.time - oldStats.time, generated: newStats.time } // check for caching - if there is an entry, and we are in the cache time, we should use that. // if there wasn't a cached value, we should cache the current one and use that as our latest stats const cached = getCacheChangeObj(); if (!cached) { if (change.up != 0 || change.down != 0 || change.ratio != 0 || change.gold != 0) { window.localStorage.setItem(`cacheStats-${newStats.time}`, JSON.stringify(change)); } // store the new stats in local storage - this will be our new baseline for next time we show results with no cache window.localStorage.setItem('lastStats', JSON.stringify(newStats)); } const stats = cached || change; if (HIDE_ON_NO_CHANGE && stats.up == 0 && stats.down == 0 && stats.ratio == 0 && stats.gold == 0) { return; } displayDelta(stats); })();