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 PTP Show Stats Change News Item // @description Shows stats since your last visit as a news item // @updateURL https://openuserjs.org/meta/SB100/PTP_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://passthepopcorn.me/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', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']; /** * 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(); const desiredSizeIndex = 0; const currentSizeIndex = SIZE_TYPES.findIndex(size => size.toUpperCase() === type); // unknown type if (currentSizeIndex === -1) { return 0; } const size = parseFloat(s.replace(/[^\d\.]+/g, '')); return parseFloat( (size * 1024 ** (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 statsSpans = document.querySelectorAll('#userinfo_stats li'); const ratioMatch = statsSpans[2].textContent.match(/[\d\.]+/) const ratio = parseFloat(ratioMatch && ratioMatch[0]); const bonusMatch = document.getElementById('nav_bonus').textContent.match(/[\d\,]+/); const bonus = parseFloat(bonusMatch && bonusMatch[0].replaceAll(',', '')); return { up: parseStatFromString(statsSpans[0].querySelector('a').getAttribute('title')), down: parseStatFromString(statsSpans[1].querySelector('a').getAttribute('title')), ratio: isNaN(ratio) ? 0 : ratio, bonus: isNaN(bonus) ? 0 : bonus, 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 a new news item entry */ function displayDelta(change) { const timeDiff = (new Date).getTime() - change.generated; const mainColumn = document.querySelector('.main-column'); const changeElem = document.createElement('div'); changeElem.classList.add('panel'); changeElem.innerHTML = ` <div class='panel__heading'> <span class='panel__heading__title tab'>Stats Update</span> <span class='panel__heading__toggler' ${timeDiff > 1000 ? `title="Values have been cached for ${msToTime(timeDiff)}"` : ''}> Compared to ${msToTime(change.time)}ago ${timeDiff > 1000 ? '*' : ''} </span> </div> <div class='panel__body'> ⬆️ ${formatBytes(change.up)} up ⬇️ ${formatBytes(change.down)} down ↕️ ${formatBytes(change.up - change.down)} buffer 🔄 ${(change.ratio).toFixed(2)} ratio ⏺ ${change.bonus.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")} bp </div> `; mainColumn.prepend(changeElem); } // script runner (function () { 'use strict'; // calculate old and new stats const newStats = getStatsFromHtml(); const oldStats = JsonParseWithDefault(window.localStorage.getItem('lastStats') || newStats, newStats); // calculate the current change as if there were no caching const change = { up: newStats.up - oldStats.up, down: newStats.down - oldStats.down, ratio: newStats.ratio - oldStats.ratio, bonus: newStats.bonus - oldStats.bonus, 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.bonus != 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.bonus == 0) { return; } displayDelta(stats); })();