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