[GreasyFork] highlight stats changes in own scripts

try to take over the world!

As of 08.08.2020. See ბოლო ვერსია.

// ==UserScript==
// @name         [GreasyFork] highlight stats changes in own scripts
// @namespace    https://greasyfork.org/users/321857-anakunda
// @version      1.03
// @description  try to take over the world!
// @author       Anakunda
// @iconURL      https://greasyfork.org/assets/blacklogo16-bc64b9f7afdc9be4cbfa58bdd5fc2e5c098ad4bca3ad513a27b15602083fd5bc.png
// @match        https://greasyfork.org/*/users/*
// @match        https://greasyfork.org/users/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// ==/UserScript==

(function() {
  'use strict';

  const pageReloadInterval = 5; // reloan listing after n minutes if no changes detected, 0 to turn off
  const dismissNotificationInterval = 60; // dismiss changes notification after n seconds

  const daysSpan = 4;
  var scripts = document.querySelectorAll('ol#user-script-list > li > article');
  if (scripts.length <= 0) return;
  try { var lastStats = new Map(JSON.parse(GM_getValue('stats'))) } catch(e) { lastStats = new Map() }
  var updateCounter = 0;
  var articles = Array.from(scripts).map(article => queryStats(article.parentNode.dataset.scriptId).then(function(statData) {
	const id = parseInt(article.parentNode.dataset.scriptId);
	const installs = parseInt(article.parentNode.dataset.scriptTotalInstalls);
	const rating = parseFloat(article.parentNode.dataset.scriptRatingScore);
	const todayActiveStyle = 'color: darkorange; font-weight: 900;';
	var ref, _lastStats = lastStats.get(id) || {};
	if (statData.installs_total != installs) console.warn('Script ' + id + ' total installs inconsistency:',
		statData.installs_total, '≠', installs);
	if ((ref = article.querySelector('dd.script-list-total-installs')) != null) {
	  ref.innerHTML = '<span style="' + (statData.installs_today > 0 ? todayActiveStyle : '') +
		'">' + statData.installs_today + '</span> / <span>' + statData.installs_total + '</span>';
	  if (_lastStats.installs != undefined && statData.installs_total /*installs*/ != _lastStats.installs) {
		++updateCounter;
		ref.style.backgroundColor = 'greenyellow';
		var span = document.createElement('span');
		span.innerHTML = ' (<b>+'.concat((statData.installs_total /*installs*/ - (_lastStats.installs || 0)), '</b>)');
		ref.append(span);
	  }
	}
	if (_lastStats.rating != undefined && rating != _lastStats.rating
		&& (ref = article.querySelector('dd.script-list-ratings')) != null) {
	  ++updateCounter;
	  ref.style.backgroundColor = 'greenyellow';
	  let span = document.createElement('span');
	  let delta = rating - _lastStats.rating;
	  if (delta > 0) delta = '+'.concat(delta);
	  span.innerHTML = ' (<b>' + delta + '</b>)';
	  ref.append(span);
	}
	if ((ref = article.querySelector('dl.inline-script-stats')) != null) {
	  var className = 'script-list-update-checks';
	  let elem = document.createElement('dt');
	  elem.className = className;
	  elem.innerHTML = '<span>Kontroly aktualizací</span>';
	  ref.append(elem);
	  elem = document.createElement('dd');
	  elem.className = className;
	  elem.innerHTML = '<span style="' + (statData.update_checks_today > 0 ? todayActiveStyle : '') +
		'">' + statData.update_checks_today + '</span> / <span>' + statData.update_checks_total + '</span>';
	  if (_lastStats.update_checks != undefined && statData.update_checks_total != _lastStats.update_checks) {
		++updateCounter;
		let span = document.createElement('span');
		span.innerHTML = ' (<b>+' + (statData.update_checks_total - (_lastStats.update_checks || 0)) + '</b>)';
		elem.append(span);
		elem.style.backgroundColor = 'greenyellow';
	  }
	  ref.append(elem);
	  if (statData.installs_total > 0 && !isNaN(statData.steady_users)) {
		className = 'script-list-usage-stats';
		elem = document.createElement('dt');
		elem.className = className;
		elem.innerHTML = '<span>Odhad uživatelů</span>';
		ref.append(elem);
		elem = document.createElement('dd');
		elem.className = className;
		elem.innerHTML = '<span>' + statData.steady_users + ' (' +
		  Math.round(statData.steady_users * 100 / statData.installs_total) + '%)</span>';
		ref.append(elem);
	  }
	}
	return lastStats.set(id, {
	  installs: statData.installs_total /*installs*/,
	  update_checks: statData.update_checks_total,
	  rating: rating,
	});
  }));
  Promise.all(articles).then(function(results) {
	if (updateCounter > 0) {
	  let div = document.createElement('div');
	  div.textContent = 'There are updates (' + updateCounter + ')';
	  div.style = 'color: darkorange; font-weight: bolder; font-family: "Segoe UI"; ' +
		'border: solid lightsalmon 4px; background-color: antiquewhite; ' +
		'padding: 10px; position: fixed; top: 20px; right: 20px;';
	  let ref = document.body.querySelector('section.text-content');
	  if (ref != null) {
		ref.append(div);
		if (!(pageReloadInterval > 0) && dismissNotificationInterval > 0)
		  setTimeout(function() { div.remove() }, dismissNotificationInterval * 1000);
	  }
	} else if (pageReloadInterval > 0)
	  setTimeout(function() { document.location.reload() }, pageReloadInterval * 60000);
	GM_setValue('stats', JSON.stringify(Array.from(lastStats.entries())));
  });
  return;

  function queryStats(scriptId) {
	return new Promise(function(resolve, reject) {
	  var xhr = new XMLHttpRequest();
	  xhr.open('GET', '/scripts/' + scriptId + '/stats.json', true);
	  xhr.onload = function() {
		if (xhr.status != 200) return reject(defaultErrorHandler(xhr));
		const values = Array.from(Object.values(xhr.response));
		resolve({
		  installs_total: values.reduce((acc, item) => acc + parseInt(item.installs || 0), 0),
		  installs_today: values[values.length - 1].installs,
		  update_checks_total: values.reduce((acc, item) => acc + parseInt(item.update_checks || 0), 0),
		  update_checks_today: values[values.length - 1].update_checks,
		  steady_users: values.length >= daysSpan ?
		  	values.slice(-daysSpan).reduce((acc, item) => acc + parseInt(item.update_checks || 0), 0) : NaN,
		});
	  };
	  xhr.setRequestHeader('Accept', 'application/json');
	  xhr.responseType = 'json';
	  xhr.timeout = 20000;
	  xhr.send();
	})
  }
})();