NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name oujs.org scripts stats
// @namespace https://openuserjs.org/users/burn
// @author Burn
// @copyright 2014, burn (https://openuserjs.org//users/burn)
// @description Shows how many installs and ratings your scripts have got since last visit. Greasemonkey v4+ compatible
// @license MIT
// @include https://openuserjs.org/users/*
// @version 3.0.3
// @updateURL https://openuserjs.org/meta/burn/oujs.org_scripts_stats.meta.js
// @downloadURL https://openuserjs.org/install/burn/oujs.org_scripts_stats.user.js
// @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.deleteValue
// ==/UserScript==
/*jshint esversion: 8 */
(async function() {
'use strict';
var DBG = false,
storeName = "OUJS.org_scripts_stats",
storeStatsName = "OUJS.org_user_stats";
// helpers functions
var log = function(s){
if (DBG) console.log(s);
}
, qSel = function(q, c){
var d = (typeof c == 'object') ? c : document;
return d.querySelector(q) || false;
}
, qSelAll = function(q, c){
var d = (typeof c == 'object') ? c : document;
return d.querySelectorAll(q) || false;
}
, serialize = function(name, val){
GM.setValue(name, JSON.stringify(val));
}
, deserialize = async function(name, def){
log('in serialize');
def = def || '{}';
var tmpOut = await GM.getValue(name, def);
log("deserialize: " + tmpOut);
return JSON.parse(tmpOut);
}
, getKey = function(prop, val, obj){
var j = 0, jL = obj.length;
for (; j<jL; j++) {
if (obj[j][prop] == val){return j;}
}
return false;
}
, timeDifference = async function(laterDate, earlierDate){
var difference = parseInt((laterDate - earlierDate), 10)
, strOut = ""
, humanDate = (new Date(earlierDate)).toLocaleString()
, arrDiff = []
, daysDifference
, hoursDifference
, minutesDifference
, secondsDifference;
log("diff = " + difference + " - laterD: " + laterDate + " - earlierD: " + earlierDate);
if (difference <= 1) return strOut + "now (" + humanDate + ")";
daysDifference = Math.floor(difference/1000/60/60/24);
if (daysDifference > 0) arrDiff.push( daysDifference + " days");
difference -= daysDifference*1000*60*60*24;
hoursDifference = Math.floor(difference/1000/60/60);
if (hoursDifference > 0) arrDiff.push(hoursDifference + " hours");
difference -= hoursDifference*1000*60*60;
minutesDifference = Math.floor(difference/1000/60);
if (minutesDifference > 0) arrDiff.push(minutesDifference + " min.");
difference -= minutesDifference*1000*60;
secondsDifference = Math.floor(difference/1000);
// only care of seconds within a day ago
if (secondsDifference > 0 && daysDifference < 2)
arrDiff.push(secondsDifference + " sec.");
strOut += arrDiff.join(" ") + " ago (" + humanDate + ")";
return strOut;
}
, setStatsCSS = function() {
var cssOut = "margin:0;\
padding:0;\
text-align:center;\
display:inline;\
color:green;\
line-height:6px;\
font-size:11px;";
return cssOut;
}
, setNegativeStatsCSS = function() {
var cssOut = "margin:0;\
padding:0;\
text-align:center;\
display:inline;\
color:darkred;\
line-height:6px;\
font-size:11px;";
return cssOut;
}
, isUserLogged = async function() {
var menu = qSelAll("nav.navbar .navbar-collapse-top a")
, userN = false
, k = menu.length - 1
// catch username by its index, for both users and admins
, userIdx = menu.length - 2;
for (k; k>=0; k--) {
if (menu[k].innerHTML.indexOf("Login / ") > -1)
return false;
if (k === userIdx) {
userN = menu[k].innerHTML;
log("username: " + userN);
break;
}
}
return userN;
}
, isScriptsPage = async function() {
var loc, usrN = await isUserLogged();
if ( false !== usrN ) {
var RE_loc = new RegExp('https:\/\/openuserjs.org\/users\/' + usrN + '\/scripts\/?', 'i');
loc = window.location.href.toString();
var matches = loc.match(RE_loc);
if (null != matches && matches.length > 0) {
log("We are in Scripts Page");
return true;
}
}
return false;
}
, isUserPage = async function () {
var loc, usrN = await isUserLogged();
if ( false !== usrN ) {
var RE_loc = new RegExp('https:\/\/openuserjs.org\/users\/'+ usrN + '\/?$', 'i');
loc = window.location.href.toString();
var matches = loc.match(RE_loc);
if (null != matches && matches.length > 0) {
log("We are in User Page");
return true;
}
}
return false;
}
, checkFirstExecution = async function() {
// check if we're running the script for the first time (e.g. after upgrading it)
// horrible workaround made for compatibility reasons
var isFirstTime = await GM.getValue("OUJS.org_scripts_stats.spy", 1);
if (1 === isFirstTime) {
GM.deleteValue(storeName);
GM.deleteValue(storeStatsName);
log("first userscript execution");
GM.setValue("OUJS.org_scripts_stats.spy", 0);
}
}
, collectScriptsStats = async function() {
log("Found " + defs.scriptsList.length + " scripts");
for (defs.k=0, defs.kL=defs.scriptsList.length; defs.k<defs.kL; defs.k++) {
defs.tit = qSel( "a.tr-link-a b", defs.scriptsList[defs.k] ).innerHTML;
log("Title found: " + defs.tit);
defs.inst = qSel( "td.text-center.td-fit p", defs.scriptsList[defs.k] ).innerHTML;
log("Installs found: " + defs.inst);
defs.rating = qSel( "td.rating p", defs.scriptsList[defs.k] ).innerHTML;
log("Rating found: " + defs.rating);
defs.url = qSel("a.tr-link-a", defs.scriptsList[defs.k]).getAttribute("href");
log("Url found: " + defs.url);
defs.child = Object.create(defs.objScript);
defs.child.title = defs.tit;
defs.child.url = window.location.protocol + "//"
+ window.location.hostname + defs.url;
defs.child.installs = parseInt(defs.inst, 10);
defs.child.rating = parseInt(defs.rating, 10);
if (false !== (defs.kk = getKey("title", defs.child.title, defs.arrStored))) {
log(defs.child.title + " stats found in archive, calculating deltas");
defs.dInst = ( defs.child.installs - defs.arrStored[defs.kk].installs );
defs.dRating = ( defs.child.rating - defs.arrStored[defs.kk].rating ) ;
defs.gTotInstalls += defs.dInst;
defs.gTotRatings += defs.dRating;
// updating values into archive
defs.arrStored[defs.kk].installs = defs.child.installs;
defs.arrStored[defs.kk].rating = defs.child.rating;
log("Archive entry for " + defs.arrStored[defs.kk].title + " updated: " + defs.arrStored[defs.kk].installs
+ " installs - " + defs.arrStored[defs.kk].rating + " rating");
} else {
log(defs.child.title + " not found in archive, will add it.");
defs.arrScripts.push(defs.child);
}
if (defs.dInst > 0) {
defs.elDelta = document.createElement("sup");
defs.elDelta.style.cssText = setStatsCSS();
defs.elDelta.innerHTML = "+" + defs.dInst;
qSel( "td.text-center.td-fit p", defs.scriptsList[defs.k] )
.appendChild(defs.elDelta);
}
if (defs.dRating > 0) {
defs.elDelta = document.createElement("sup");
defs.elDelta.style.cssText = setStatsCSS();
defs.elDelta.innerHTML = "+" + defs.dRating;
qSel( "td.rating p", defs.scriptsList[defs.k] )
.appendChild(defs.elDelta);
} else if (defs.dRating < 0) {
defs.elDelta = document.createElement("sub");
defs.elDelta.style.cssText = setNegativeStatsCSS();
defs.elDelta.innerHTML = defs.dRating;
qSel( "td.rating p", defs.scriptsList[defs.k] )
.appendChild(defs.elDelta);
}
defs.child = null;
} // end for
// total installs and ratings since last page visit
if (defs.gTotInstalls > 0) {
defs.elTotInst = document.createElement("sup");
defs.elTotInst.style.cssText = setStatsCSS();
defs.elTotInst.innerHTML = "+" + defs.gTotInstalls;
qSel(".container-fluid.col-sm-8 th:nth-of-type(2) a span:first-of-type")
.appendChild(defs.elTotInst);
}
if (defs.gTotRatings > 0) {
defs.elTotRat= document.createElement("sup");
defs.elTotRat.style.cssText = setStatsCSS();
defs.elTotRat.innerHTML = "+" + defs.gTotRatings;
qSel(".container-fluid.col-sm-8 th:nth-of-type(3) a span:first-of-type")
.appendChild(defs.elTotRat);
} else if (defs.gTotRatings < 0) {
defs.elTotRat= document.createElement("sub");
defs.elTotRat.style.cssText = setNegativeStatsCSS();
defs.elTotRat.innerHTML = defs.gTotRatings;
qSel(".container-fluid.col-sm-8 th:nth-of-type(3) a span:first-of-type")
.appendChild(defs.elTotRat);
}
var merged = defs.arrScripts.concat(defs.arrStored);
log("faccio il merge di " + defs.arrScripts.length + " e " + defs.arrStored.length)
log("End. After merging: " + merged.length);
serialize(storeName, merged);
}
, collectUserStats = async function() {
defs.oldStatsDate = (Object.keys(defs.arrStatsStored).length>0)
? defs.arrStatsStored.pop().dateTime : (new Date()).getTime();
log("Found date in arrStatsStored: " + defs.oldStatsDate);
defs.dtList = qSelAll("dt", defs.globalStatsList);
defs.ddList = qSelAll("dd", defs.globalStatsList);
log("Found " + defs.dtList.length + " dt");
log("Found " + defs.ddList.length + " dd");
// user global stats: start from 1 because we do not track the joining date
for (defs.k=1, defs.kL = defs.dtList.length; defs.k < defs.kL; defs.k++) {
defs.lbl = defs.dtList[defs.k].innerHTML;
defs.val = defs.ddList[defs.k].innerHTML;
//log(" ==== " + defs.ddList[defs.k].innerHTML.indexOf("."));
if (defs.val.indexOf(".") > -1) {
log(defs.ddList[defs.k].innerHTML +
" Found decimal separator at index " +
defs.ddList[defs.k].innerHTML.indexOf("."));
defs.val = parseFloat(defs.val).toFixed(1);
}
log(defs.lbl + " : " + defs.val);
defs.child = Object.create(defs.objGlobalStat);
defs.child.label = defs.lbl;
defs.child.value = defs.val;
defs.dValue = 0;
if (false !== (defs.kk = getKey("label", defs.child.label, defs.arrStatsStored))) {
log(defs.child.label + " stats found in archive with value "
+ defs.arrStatsStored[defs.kk].value);
defs.dValue = ( defs.child.value - defs.arrStatsStored[defs.kk].value );
log( defs.child.label + ": " + defs.child.value + "-"
+ defs.arrStatsStored[defs.kk].value + "=" + defs.dValue);
} else {
log("Key for " + defs.child.label + " not found in " +
defs.arrStatsStored);
}
if (defs.dValue > 0) {
if (defs.dValue.toString().indexOf(".") > -1) {
log(defs.dValue +
" Found decimal separator at index " +
defs.dValue.toString().indexOf("."));
defs.dValue = parseFloat(defs.dValue).toFixed(1);
}
defs.elTotInst = document.createElement("sup");
defs.elTotInst.style.cssText = setStatsCSS();
defs.elTotInst.innerHTML = " +" + defs.dValue;
defs.ddList[defs.k].appendChild(defs.elTotInst);
}
defs.arrStats.push(defs.child);
defs.child = null;
}
setDateTime(defs.arrStats, defs.oldStatsDate);
log("End. Storing " + (defs.arrStats.length - 1) + " global stats");
if (defs.arrStats.length > 0) serialize(storeStatsName, defs.arrStats);
}
, setDateTime = async function(arrTarget, oldD) {
defs.nowDate = (new Date()).getTime();
log("Pushing " + defs.nowDate + " into " + arrTarget);
arrTarget.push( {dateTime: defs.nowDate} );
if (false === qSel("#oujs-stats-datetime")) {
log("Creating element to show date difference");
defs.elDiffTimeLabel = document.createElement("dt");
defs.elDiffTimeLabel.setAttribute("id", "oujs-stats-datetime");
defs.elDiffTimeLabel.innerHTML = "Last check";
defs.globalStatsList.appendChild(defs.elDiffTimeLabel);
defs.elDiffTime = document.createElement("dd");
defs.elDiffTime.innerHTML = await timeDifference(defs.nowDate, oldD);
defs.globalStatsList.appendChild(defs.elDiffTime);
}
};
// end helpers
//GM.deleteValue(storeName); // will reset your archive! uncomment when debugging only
//GM.deleteValue(storeStatsName); // will reset your archive! uncomment when debugging only
var defs = {
tit : "",
dInst : 0,
kk : 0,
inst : 0,
child : null,
elDelta : 0,
k : 0,
kL : 1,
rating : 0,
dRating : 0,
url : "",
oldDate : 0,
oldStatsDate : 0,
nowDate : null,
gTotInstalls : 0,
gTotRatings : 0,
elTotInst : 0,
elTotRat : 0,
elDiffTime : null,
elDiffTimeLabel : null,
lbl : "",
val : 0,
dtList : false,
ddList : false,
dValue : 0,
gTotValue : 0,
arrScripts : [], // scripts found on page
arrStats : [], // stats found on page
arrStored : await deserialize(storeName),// scripts previously stored by this userscript
arrStatsStored : await deserialize(storeStatsName),
objScript : { // script object
title : "",
installs : 0,
rating : 0
},
objGlobalStat : { // global stat object
label : "",
value : 0
},
scriptsList : qSelAll(".tr-link"),
globalStatsList : qSel
(".container-fluid.col-sm-4 .panel-default .panel-body .dl-horizontal")
};
// (sort of) main
if (true === await isScriptsPage()) {
await checkFirstExecution();
await collectScriptsStats();
await collectUserStats();
}
if (true === await isUserPage()) {
await checkFirstExecution();
await collectUserStats();
}
})();