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