Display today's script installations and update checks.
// ==UserScript==// @name Greasyfork/Sleazyfork Update Checks Display// @description Display today's script installations and update checks.// @icon https://greasyfork.org/vite/assets/blacklogo96-CxYTSM_T.png// @version 1.4// @author afkarxyz// @namespace https://github.com/afkarxyz/misc-scripts/// @supportURL https://github.com/afkarxyz/misc-scripts/issues// @license MIT// @match https://greasyfork.org/*// @match https://sleazyfork.org/*// @grant GM_xmlhttpRequest// @grant GM_setValue// @grant GM_getValue// @connect api.greasyfork.org// @connect api.sleazyfork.org// ==/UserScript==(function() {'use strict';const CACHE_DURATION = 10 * 60 * 1000;function formatNumber(num) {return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");}function displayScriptStats() {document.head.appendChild(Object.assign(document.createElement('style'), {textContent: '.script-list-installs, .script-list-update-checks { opacity: 1; }'}));function addStat(element, label, className) {const list = element.querySelector('.inline-script-stats');if (!list) return;const dt = document.createElement('dt');const dd = document.createElement('dd');dt.className = className;dd.className = className;dt.textContent = label;dd.textContent = '...';list.lastElementChild.parentNode.insertBefore(dt, list.lastElementChild.nextSibling);dt.after(dd);return dd;}document.querySelectorAll('li[data-script-id]').forEach(script => {const installsElement = addStat(script, 'Installs', 'script-list-installs');const checksElement = addStat(script, 'Checks', 'script-list-update-checks');script.dataset.installsElement = installsElement.id = `installs-${script.dataset.scriptId}`;script.dataset.checksElement = checksElement.id = `checks-${script.dataset.scriptId}`;});}const collectScriptIds = () =>Array.from(document.querySelectorAll('li[data-script-id]')).map(el => ({scriptId: el.getAttribute('data-script-id'),element: el}));function getCacheKey(scriptId) {const domain = window.location.hostname.includes('sleazyfork') ? 'sleazyfork' : 'greasyfork';return `${domain}_stats_${scriptId}`;}function getFromCache(scriptId) {const cacheKey = getCacheKey(scriptId);const cachedData = GM_getValue(cacheKey);if (!cachedData) return null;const now = Date.now();if (now - cachedData.timestamp > CACHE_DURATION) {return null;}return cachedData.data;}function saveToCache(scriptId, data) {const cacheKey = getCacheKey(scriptId);GM_setValue(cacheKey, {timestamp: Date.now(),data: data});}function getCurrentLanguage() {const pathMatch = window.location.pathname.match(/^\/([a-z]{2}(?:-[A-Z]{2})?)\//);if (pathMatch) {return pathMatch[1];}return document.documentElement.lang || 'en';}function fetchStats(scriptInfo) {return new Promise((resolve) => {const cachedStats = getFromCache(scriptInfo.scriptId);if (cachedStats) {resolve(cachedStats);return;}const domain = window.location.hostname.includes('sleazyfork') ? 'sleazyfork.org' : 'greasyfork.org';const language = getCurrentLanguage();const apiUrl = `https://api.${domain}/${language}/scripts/${scriptInfo.scriptId}/stats.json`;GM_xmlhttpRequest({method: 'GET',url: apiUrl,responseType: 'json',onload: function(response) {try {const data = response.response;if (!data || typeof data !== 'object' || Object.keys(data).length === 0) {resolve({ installs: 0, checks: 0 });return;}const dates = Object.keys(data).sort();const latestDate = dates[dates.length - 1];if (!data[latestDate] || typeof data[latestDate] !== 'object') {resolve({ installs: 0, checks: 0 });return;}const stats = {installs: data[latestDate].installs || 0,checks: data[latestDate].update_checks || 0};saveToCache(scriptInfo.scriptId, stats);resolve(stats);} catch (error) {console.error(error);resolve({ installs: 0, checks: 0 });}},onerror: function(error) {console.error(error);resolve({ installs: 0, checks: 0 });}});});}function updateStats(scriptInfo, stats) {if (!stats) return;const element = scriptInfo.element;const installsElement = document.getElementById(element.dataset.installsElement);const checksElement = document.getElementById(element.dataset.checksElement);if (installsElement) installsElement.textContent = formatNumber(stats.installs);if (checksElement) checksElement.textContent = formatNumber(stats.checks);}async function init() {displayScriptStats();const scriptInfos = collectScriptIds();const fetchPromises = scriptInfos.map(async (scriptInfo) => {try {const stats = await fetchStats(scriptInfo);updateStats(scriptInfo, stats);} catch (error) {console.error(error);updateStats(scriptInfo, { installs: 0, checks: 0 });}});await Promise.all(fetchPromises);}if (document.readyState === 'loading') {document.addEventListener('DOMContentLoaded', init);} else {init();}})();