[TS] OUJS-1

New post/issue notification, adds install and ratings history stats, improves table view, list all user scripts in one page, improves library page... It now should work on Opera and Chrome.

Verzia zo dňa 01.11.2014. Pozri najnovšiu verziu.

// ==UserScript==
// @name                    [TS] OUJS-1
// @namespace               TimidScript
// @version                 1.0.11
// @description             New post/issue notification, adds install and ratings history stats, improves table view, list all user scripts in one page, improves library page... It now should work on Opera and Chrome.
// @icon                    https://imgur.com/RCyq4C8.png
// @author                  TimidScript
// @homepageURL             https://openuserjs.org/users/TimidScript
// @copyright               © 2014 TimidScript, All Rights Reserved.
// @license                 Creative Commons BY-NC-SA + Please notify me if distributing
// @include                 http*://openuserjs.org/*
// @require                 https://openuserjs.org/src/libs/TimidScript/TSL_-_Generic.js
// @require                 https://openuserjs.org/src/libs/TimidScript/TSL_-_GM_Update.js
// @homeURL                 https://openuserjs.org/scripts/TimidScript/[TS]_OUJS-1
// @grant                   GM_getValue
// @grant                   GM_setValue
// @grant                   GM_deleteValue
// @grant                   GM_xmlhttpRequest
// @grant                   GM_listValues
// @grant                   GM_registerMenuCommand
// @grant                   GM_setClipboard
// ==/UserScript==


/*
********************************************************************************************
Copyright © 2014 TimidScript, All Rights Reserved.
Script's Homepage:              Check homepages below

TimidScript's Homepage:         https://openuserjs.org/users/TimidScript
                                https://greatest.deepsurf.us/users/1455-timidscript
                                https://monkeyguts.com/author.php?un=timidscript


                                http://userscripts.org/users/TimidScript
                                http://userscripts-mirror.org/users/100610/scripts

------------------------------------
 Version History
------------------------------------
1.0.12 (2014/11/01)
 - Fixed it so it works on Opera and Chrome
 - It now displays "History Period Installs Chart" if the length is more than 0 as oppose to 3.
1.0.11 (2014/10/31)
 - Fixed Bug caused by console debug iteration
1.0.10 (2014/10/24)
 - Bug Fix on history cleanup
 - Changes in CSS for profile comments
1.0.9 (2014/10/18)
 - Bug Fix in history cleanup
 - Stats history takes into account deleted and new scripts
 - Negative stat is now shown in brackets
 - History Chart always at the bottom of page
  - Renamed "Top Installs During Period" chart
 - Added frame and margin to images
 - Correct handling of singular and plural period nouns
 - Changed the history spacing
 - Added wait period between new issues checks. Maximum of once every 20 seconds
 - Selected table header highlight is more visible and saves last sort order
1.0.8 Initial public release (2014/10/01)
 - Improved table view with numbering.
 - All profile scripts listed on one page
 - Script are listed on profile page also, so no need to click on Scripts tab
 - User scripts ordered by type (Script, Library and defunct)
 - Stats history for installs and ratings with sensible time spacing
 - Top install listing
 - Profile table view sort implemented within the script
 - Notification on issues raised on users scripts
 - Notification on the forum
 - Stores logged username so you do not need to be logged in to get script stats and notifications
 - Changed the interface of library scripts
 - Added copy button on library
 - List all author's scripts in profile tab.
 - List all author's scripts in one page
 - Limits size of image in frame on forum and icon size in library page
********************************************************************************************/

if (window !== window.top) return;
console.info("OUJS-1 is Running");
var DAY = 86400000;

function ToggleIssuesView(discuss, issues)
{
    discuss.onmouseover = MouseOver;
    discuss.onmouseleave = MouseLeave;
    issues.onmouseover = MouseOver;
    issues.onmouseleave = MouseLeave;

    function MouseOver()
    {
        clearTimeout(document.to);
        issues.style.display = "block";
    }

    function MouseLeave()
    {
        document.to = setTimeout(function () { issues.style.display = "none"; }, 500);
    }
}

function RemoveNotice(e)
{
    var meta = JSON.parse(GM_getValue("USER-P:" + this.getAttribute("username")));
    meta[this.postID] = this.postdata;
    GM_setValue("USER-P:" + this.getAttribute("username"), JSON.stringify(meta));

    TSL.removeNode(this.parentElement);
    if (!document.querySelector("#IssuesListing li")) TSL.removeNode("IssuesListing");
}


function ClickedHistory(e)
{
    TSL.removeClass(this.parentElement.getElementsByClassName("selected")[0], "selected");
    TSL.addClass(this, "selected");
    DisplayStats(this.data, this.parentElement.data);
}

function DisplayStats(old, current)
{
    var totalI = totalIN = totalNew = totalR = totalRN = 0, row, sups = document.querySelectorAll(".dStatP, .dStatN, .dStatNew");

    var tmpStamp = Date.now();

    for (var i = 0; sups && i < sups.length; i++) TSL.removeNode(sups[i]);
    TSL.removeNode("TopTen");

    var arr = [];
    for (var scriptID in current)
    {
        row = document.querySelector('tr[scriptid="' + scriptID + '"]');

        if (scriptID == "timestamp") continue;
        else if (!row) arr.push ({name: GM_getValue(scriptID),  installs : -1}); //Removed Items
        else if (old[scriptID])
        {
            row.tempStamp = tmpStamp;
            var diff = current[scriptID].installs - old[scriptID].installs;
            totalI += diff;
            if (diff < 0) totalIN += diff; //Can happen when you remove and then re-add the script
            if (diff) row.querySelector("td:nth-child(2) p").appendChild(TSL.createElementHTML("<sup class='dStat" + ((diff > 0) ? "P" : "N") + "'>" + diff + "</sup>"));

            if (diff) arr.push ({name: row.querySelector("b").textContent,  installs : diff} );

            diff = current[scriptID].rating - old[scriptID].rating;
            totalR += diff;
            if (diff < 0) totalRN += diff;
            if (diff) row.querySelector("td:nth-child(3) p").appendChild(TSL.createElementHTML("<sup class='dStat" + ((diff > 0) ? "P" : "N") + "'>" + diff + "</sup>"));
        }
    }

    var rows = document.querySelectorAll(".col-xs-12 .table .tr-link");
    for(var i = 0, installs; i < rows.length, row = rows[i]; i++)
    {
        if (row.tempStamp != tmpStamp)
        {
            installs = parseInt(row.getAttribute("installs"));
            totalI += installs;
            totalNew += installs;
            row.querySelector("td:nth-child(2) p").appendChild(TSL.createElementHTML("<sup class='dStatNew'>" + installs+ "</sup>"));
            arr.push({ name: row.querySelector(".tr-link-a > b").textContent, installs : installs, isNew : true});
        }
    }

    if (totalI) document.querySelector(".table thead th:nth-child(2) a").appendChild(TSL.createElementHTML("<sup class='dStat" + ((totalI > 0) ? "P" : "N") +   "'>"
        + totalI + (function()
        {
            if (totalI != totalIN &&  totalIN < 0) return "<span style='color: red;'>[" + totalIN + "]</span>"
            return "";
        })() + "</sup>"));
    if (totalR) document.querySelector(".table thead th:nth-child(3) a").appendChild(TSL.createElementHTML("<sup class='dStat" + ((totalR > 0) ? "P" : "N") +   "'>"
        + totalR +
        (function()
        {
            if (totalR != totalRN &&  totalRN < 0) return "<span style='color: red;'>[" + totalRN + "]</span>"
            return "";
        })() + "</sup>"));

    if (arr.length == 0) return;
    arr.sort(function(a, b)
    {
        if (a.installs < b.installs) return -1;
        if (a.installs > b.installs) return 1;

        return 0;
    });
    arr.reverse();

    var panel = document.createElement("div");
    panel.className = "panel panel-default";
    panel.id = "TopTen";
    panel.appendChild(document.createElement("div"));
    document.querySelector(".container-fluid.col-sm-4").appendChild(panel);

    panel = panel.firstElementChild;
    panel.className = "panel-body";

    var el = document.createElement("h3");
    el.innerHTML = "History Period Installs Chart (<span style=\"color:green;\">" + totalI + "</span>)";
    panel.appendChild(el);

    var ol = document.createElement("ol");
    ol.setAttribute("style","font-size:14px; font-weight: 500;");
    panel.appendChild(ol);

    while (arr.length > 0)
    {
        el = document.createElement("li");
        if (arr[0].installs < 0) el.innerHTML = arr[0].name + " (<span style='color:red; font-weight: 700;'>Removed</span>)";
        else if (arr[0].isNew) el.innerHTML = arr[0].name + " (<span style='color:blue; font-weight: 700;'>" + arr[0].installs + "</span>)";
        else el.innerHTML = arr[0].name + " (<span style='color:green; font-weight: 700;'>" + arr[0].installs + "</span>)";
        ol.appendChild(el);
        arr.shift();
    }
}



function addScriptListingNumbers()
{
    TSL.addStyle("ScriptNumbers", ".script_number {display: inline-block; background-color: #2C3E50; color: white; padding: 1px 2px 0 2px; margin: 0; border-radius: 3px; font-size: 13px; line-height: 13px; font-family: 'Courier New', Courier, monospace}"
        + ".col-sm-8 .table .tr-link td {vertical-align: middle !important; }"
        + ".col-sm-8 .table .tr-link td .progress { margin-bottom: 0px; }"
        //+ ".col-sm-8 .table .tr-link td:nth-child(2) p {margin-bottom: 14px;}"
    );

    var sn = document.getElementsByClassName("script_number");
    while (sn.length > 0) TSL.removeNode(sn[0]);

    var start = document.querySelector(".pagination .active a");
    if (start) start = (parseInt(start.textContent) - 1) * 25 + 1;
    else start = 1;

    var rows = document.querySelector(".table");
    rows = rows.querySelectorAll(".tr-link");
    if (!rows) return;
    var prefix = "".lPad("0", rows.length.toString().length);
    for (var i = 0, row, cell; i < rows, row = rows[i]; i++)
    {
        var counter = document.createElement("span");
        counter.className = "script_number";
        counter.textContent = (prefix + (i + start)).slice(-1 * prefix.length);
        row.firstElementChild.insertBefore(counter, row.firstElementChild.children[0]);
    }
}

function SortScriptTable(e)
{
    e.stopImmediatePropagation();
    TSL.addStyle("OUJS-ORDER",".decen {background-color: rgba(200,255,255,0.6);} .ascen {background-color: rgba(255,220,255, 0.6);} ");
    //#F0DDFD #DAFBF6 #C2F4F7 #FF0

    var sortDescending = !TSL.hasClass(this.parentElement,"decen");
    if (document.querySelector(".decen, .ascen")) TSL.removeClass(document.querySelector(".decen, .ascen"), "decen ascen");
    TSL.addClass(this.parentElement, ((sortDescending) ? "decen" : "ascen"));

    var tbody = document.querySelector(".table tbody");
    var rows = tbody.getElementsByClassName("tr-link");

    var idx = this.parentElement.cellIndex;
    GM_setValue("SortHeader", idx);
    GM_setValue("SortDescending", sortDescending);

    for(var n, i = 0; i < rows.length - 1; i++)
    {
        n = i;
        for(var j=i+1; j < rows.length; j++)
        {
            if ((sortDescending && compareRows(rows[n], rows[j]) < 0) || (!sortDescending && compareRows(rows[n], rows[j]) > 0))
            {
                n = j;
            }
        }

        if (n != i) tbody.insertBefore(rows[n], rows[i]);
    }

    //row1 less it's negative otherwise positive
    function compareRows(row1, row2)
    {
        if (TSL.hasClass(row1, "header_row") || TSL.hasClass(row2, "header_row")) return 0;
        if (TSL.hasClass(row1, "_library") || TSL.hasClass(row1, "_defunct")) return 0;
        if (TSL.hasClass(row2, "_library") || TSL.hasClass(row2, "_defunct")) return 0;

        var selector = "b", val1, val2;
        if (idx == 1) selector = "td:nth-child(2) p";
        else if (idx == 2) selector = "td:nth-child(3) p";
        else if (idx == 3) selector = "td:nth-child(4) time";


        if (idx == 0)
        {
            val1 = row1.querySelector(selector).textContent.toLowerCase();
            val2 = row2.querySelector(selector).textContent.toLowerCase();
        }
        else if (idx == 1)
        {
            val1 = parseInt(row1.getAttribute("installs"));
            val2 = parseInt(row2.getAttribute("installs"));
        }
        else if (idx == 2)
        {
            val1 = parseInt(row1.getAttribute("rating"));
            val2 = parseInt(row2.getAttribute("rating"));
        }
        else if (idx == 3)
        {
            val1 = new Date(row1.querySelector(selector).getAttribute("datetime")).getTime();
            val2 = new Date(row2.querySelector(selector).getAttribute("datetime")).getTime();
        }

        if (val1 < val2) return -1;
        if (val1 > val2) return 1;

        return 0;
    }

    addScriptListingNumbers();

    return false;
}


(function ()
{
    if (document.getElementsByClassName("navbar-brand").length) document.getElementsByClassName("navbar-brand")[0].innerHTML = 'OpenUserJS-1 <sub><a href="/users/TimidScript" style="font-size:0.6em; color: cyan;">TimidScript</a></sub>';
    var timestamp = Date.now();
    var loggedUsername = "";
    TSL.addStyle("Image-Limiter", ".user-content img, .topic-post-contents img {max-width: 98%; border: 1px solid blue; padding: 2px; color: yellow; margin: 5px 0; box-shadow: 5px 5px 2px #888888;}"
        + ".topic-post-contents img:last-child {margin-bottom: 10px;}"
        );

    if (document.querySelector(".fa-sign-out") != undefined)
    {
        loggedUsername = document.querySelector('ul li a[href^="/users/"]').textContent;

        var meta = GM_getValue("USER-S:" + loggedUsername);
        if (!meta) //Create new user data.
        {
            meta = {};
            meta.current = {};
            meta.history = new Array();
            GM_setValue("USER-S:" + loggedUsername, JSON.stringify(meta));
        }

        meta = GM_getValue("USER-P:" + loggedUsername);
        if (!meta)
        {
            GM_setValue("USER-P:" + loggedUsername, JSON.stringify({}));
        }

        //perform cleanup by deleting old post records
        var names = getSavedUsernames();
        for (var i = 0; i < names.length; i++)
        {
            var meta = JSON.parse(GM_getValue("USER-P:" + names[i]));

            for (var key in meta)
            {
                if (timestamp - meta[key].timestamp > DAY * 14) //30 Days
                {
                    delete meta[key];
                    GM_deleteValue(key);
                }
            }

            GM_setValue("USER-P:" + names[i], JSON.stringify(meta));
        }
    }

    var pathname = document.location.pathname;
    if (pathname.match(/^\/$|^\/group\/\w+/))
    {
        addScriptListingNumbers();
    }
    else if (pathname.match(/^\/users\/\w+$/i)) //User profile page
    {
        var container = document.getElementsByClassName("container-fluid")[0];
        document.getElementsByClassName("col-sm-4")[0].innerHTML += '<div class="panel panel-transparent"><div class="input-group col-xs-12"><form action="" method="get"><div class="input-group col-xs-12"><input name="q" placeholder="Search Profile Scripts" class="search form-control" value="" type="text"><span class="input-group-btn"><button class="btn btn-default" type="submit"><i class="fa fa-search"></i></button></span></div></form></div></div>';
        getScriptListings(document.URL + "/scripts/?p=1");

        window.history.pushState(null, "", document.URL + "/scripts"); //Change document URL
    }
    else if (pathname.match(/^\/users\/.+\/comments/i)) //User profile comments page
    {
        TSL.addStyle("UserScripts", ".topic-title .breadcrumb {margin-bottom: 0px;}");
    }
    else if (pathname.match(/^\/users\/\w+\/scripts/i)) //User script listing
    {
        TSL.addStyle("UserScripts", "ul.pagination {display: none;}");
        var nextpage = document.querySelector("ul.pagination > .active + li > a");
        if (nextpage) getScriptListings(nextpage.href);
        else if (document.querySelector(".table .tr-link")) amendUserScriptListing();
    }
    else if (pathname.match(/^\/scripts\/[^\/]+\/[^\/]+$/i)) //Script page
    {
        scriptPageAmendRateArea();
    }
    else if ((pathname.match(/^\/libs\/[^\/]+\/[^\/]+$/i))) amendLibraryPage();

    displayNewIssues();

    function displayNewIssues()
    {
        if (pathname.match(/^\/forum/))
        {
            TSL.addStyle("ForumHelper", "body .table .userpost:nth-child(2n) td {background-color: #FFD;}"
                + "body .table .userpost:nth-child(2n+1) td {background-color: #FFF9DD;}"
                + "body .table .newposts td:nth-child(4) {color: red;}"
                + "body .table .newposts td:nth-child(4) sup {color: green;}"
                + "#newIssues {background-color: #E1F9E6; padding: 1px 20px; font-weight: 600; color: green;}"
                + "#newIssues span {margin-right: 30px;}"
                );

            var notice = document.createElement("div");
            notice.id = "newIssues";
            var usernames = getSavedUsernames();
            document.querySelector(".table-responsive").parentElement.insertBefore(notice, document.querySelector(".table-responsive"));
            for (var i = 0, username; i < usernames.length, username = usernames[i]; i++)
            {
                var c = getNewPostCount(username);
                if (c.length > 0)
                {
                    var el = document.createElement("span");
                    if (usernames.length > 1) el.textContent = "(" + username + ") ";
                    el.textContent += "New posts detected on " + c.length + " issues/threads";
                    notice.appendChild(el);
                }
            }

            if (!notice.textContent) notice.textContent = "No new issues detected";
            return;
        }


        if (window.sessionStorage.getItem("NewIssuesStamp") && Date.now() - window.sessionStorage.getItem("NewIssuesStamp") < 20000)
        {
            var doc = document.implementation.createHTMLDocument('MPIV');
            doc.documentElement.innerHTML = window.sessionStorage.getItem("NewIssuesDoc");
            NewIssues(doc);
        }
        else xhrPage("https://openuserjs.org/forum", function (xhr, doc)
        {
            window.sessionStorage.setItem("NewIssuesDoc", xhr.responseText);
            window.sessionStorage.setItem("NewIssuesStamp", Date.now());
            NewIssues(doc);
        });

        function NewIssues(doc)
        {
            var count = 0;
            var usernames = getSavedUsernames();
            var issues = document.createElement("section");
            issues.appendChild(document.createElement("ul"));

            for (var i = 0, username, arr; i < usernames.length, username = usernames[i]; i++)
            {
                var meta = JSON.parse(GM_getValue("USER-P:" + username));
                var updated = false;
                arr = getNewPostCount(username, doc);
                count += arr.length;
                for (var j = 0, li; j < arr.length; j++)
                {
                    li = TSL.createElementHTML("<li><a href='" + arr[j].url + "'>" + arr[j].postTitle + "</a></li>");
                    li.appendChild(TSL.createElementHTML("<span>❌</span>"));
                    issues.firstElementChild.appendChild(li);

                    li.lastElementChild.onclick = RemoveNotice;
                    li.lastElementChild.postdata = { timestamp: timestamp, replies: arr[j].replies };
                    li.lastElementChild.postID = arr[j].postID;
                    li.lastElementChild.setAttribute("username", username);

                    if (loggedUsername == username && decodeURI(document.location.pathname) == decodeURI(arr[j].url))
                    {
                        meta[arr[j].postID] = {}
                        meta[arr[j].postID].timestamp = timestamp;
                        meta[arr[j].postID].replies = arr[j].replies;
                        updated = true;
                    }
                }

                if (updated) GM_setValue("USER-P:" + username, JSON.stringify(meta));
            }

            if (count > 0)
            {
                var discuss = document.querySelector('.nav a[title="Discuss"]');
                discuss.innerHTML += " (";
                var notice = document.createElement("span");
                notice.setAttribute("style", "color: lime; display:inline-block; font-weight: 600;");
                notice.id = "newIssues";
                notice.textContent = count;
                discuss.appendChild(notice);
                discuss.innerHTML += ")";

                TSL.addStyle("OUJS-IL-BT", "#IssuesListing {position: absolute;background-color: #2C3E50; color: white; font-weight: 600; z-index: 99999;"
                    + "font-size:12px; padding: 3px 8px; color: white; border: 1px solid black; box-sizing: border-box;}"
                    + "#IssuesListing ul {margin: 0; padding-left: 10px;}"
                    + "#IssuesListing a {display: inline-block; color: orange; }"
                    + "#IssuesListing li:hover {background-color: #435A71;}"
                    + "#IssuesListing span {color: red; margin-left: 15px; cursor: default; display: inline-block;"
                    );

                issues.id = "IssuesListing";
                document.body.appendChild(issues);

                var pos = TSL.getAbsolutePosition(discuss.parentElement);
                issues.style.top = (discuss.clientHeight + pos.top) + "px";
                if (issues.clientWidth + pos.left - 10 > window.innerWidth) issues.style.right = "2px";
                else issues.style.left = (pos.left - 30) + "px";

                TSL.addStyle("OUJS-IL-BT2", "#IssuesListing {display: none;}");

                ToggleIssuesView(discuss, issues);
            }
        }
    }

    function getScriptListings(url)
    {
        xhrPage(url, xhrCallback);

        function xhrCallback(xhr, doc)
        {
            if (!doc) return;
            var tb = document.querySelector(".table tbody");
            var scripts = doc.getElementsByClassName("tr-link");
            if (tb)
            {
                while (scripts.length > 0) tb.appendChild(scripts[0]);
            }
            else
            {
                var container = document.getElementsByClassName("col-xs-12")[0];
                var scriptPanel = doc.getElementsByClassName("panel panel-default")[0];
                container.appendChild(document.importNode(scriptPanel, true));
            }

            var nextpage = doc.querySelector("ul.pagination > .active + li > a");
            if (nextpage) getScriptListings(document.location.origin + nextpage.href, xhrCallback);
            else amendUserScriptListing();
        }
    }

    function amendUserScriptListing()
    {
        TSL.addStyle("CoolColors", ".header_row {padding: 0; font-size: 16px;}"
            + ".header_row > td {padding: 0 10px !important; font-weight: 700;}"
            + "tr._defunct {background-color: #FFF5F3;}"
            + "tr.header_row._defunct {background-color: pink; color: red;}"
            + "tr._library {background-color: #F7FDF7;}"
            + "tr.header_row._library {background-color: #ACF9AC; color: green;}"
            );
        var tbody = document.querySelector(".table tbody");
        //var tbody = document.createElement("tbody");
        var rows = tbody.querySelectorAll(".tr-link");

        ////Put libraries at the bottom of the table
        var added = false;
        for (var i = 0, row, row2, cell; i < rows, row = rows[i]; i++)
        {
            if (!row.querySelector(".script-version"))
            {
                if (!added)
                {
                    added = true;
                    row2 = tbody.insertRow(-1);
                    row2.className = "header_row _library";
                    cell = row2.insertCell(-1);
                    cell.setAttribute("colspan", 5);
                    cell.textContent = "Libraries";
                }
                TSL.addClass(row, "_library");
                tbody.appendChild(row);
            }
        }

        //Put defunct scripts at the bottom of the table
        added = false;
        for (var i = 0, row, row2, cell; i < rows, row = rows[i]; i++)
        {
            var version = row.querySelector(".script-version")
            if (version && version.textContent.match(/defunct|depreciated|obselete/i))
            {
                if (!added)
                {
                    added = true;
                    row2 = tbody.insertRow(-1);
                    row2.className = "header_row _defunct";
                    cell = row2.insertCell(-1);
                    cell.setAttribute("colspan", 5);
                    cell.textContent = "Depreciated scripts that are no longer begin supported";
                }
                TSL.addClass(row, "_defunct");
                tbody.appendChild(row);
            }
        }

        TSL.addStyle("HeaderPointer",".col-xs-12 .table th a {cursor: pointer;}");
        var headers = document.querySelectorAll(".table thead th a");
        for(var i = 0; i < headers.length; i++)
        {
            headers[i].removeAttribute("href");
            headers[i].onclick = SortScriptTable;
        }

        appendHistory();

        var idx = GM_getValue("SortHeader", 3);
        var SortAscending =  !GM_getValue("SortDescending", true);
        headers[idx].click();
        if (SortAscending) headers[idx].click();
        //addScriptListingNumbers();
    }

    function getUID(name, prefix)
    {
        if (!prefix) prefix = "s";
        var id, ids = GM_listValues();

        for (var i = 0; i < ids.length, id = ids[i]; i++)
        {
            if (id[0] == prefix && id.match(/.\d+$/) && GM_getValue(id) == name) return id;
        }

        while (true)
        {
            id = (prefix || "s") + ("0000" + Math.floor(Math.random() * 10000 + 1)).slice(-4);
            if (GM_getValue(id, 0) == 0)
            {
                GM_setValue(id, name);
                return id;
            }
        }
    }

    function appendHistory()
    {
        var username = document.querySelector(".user-name").textContent;
        var meta = GM_getValue("USER-S:" + username);

        if (!meta) return;
        TSL.addStyle("HistoricalColors", ".dStatN {color: red;} .dStatP {color: green;} .dStatNew {color: blue;} .dStatP:before, .dStatNew:before { content: '+';}");

        meta = JSON.parse(meta);
        var oldCurrent = meta.current;
        meta = JSON.parse(GM_getValue("USER-S:" + username));

        var rows = document.querySelectorAll(".table .tr-link");
        for (var i = 0, row; i < rows, row = rows[i]; i++)
        {
            var scriptname = row.querySelector(".tr-link-a > b").textContent;
            var scriptID = getUID(scriptname);
            rows[i].setAttribute("ScriptID", scriptID);

            data = {};
            data.installs = parseInt(row.querySelector("td:nth-child(2) p").textContent);
            data.rating = parseInt(row.querySelector("td:nth-child(3) p").textContent);
            row.setAttribute("installs", data.installs);
            row.setAttribute("rating", data.rating);
            meta.current[scriptID] = data;
        }

        meta.current.timestamp = timestamp;
        if (meta.history.length == 0 || timestamp - meta.history[meta.history.length - 1].timestamp >= DAY)
        {
            meta.history.push(meta.current);
        }

        DisplayStats(oldCurrent, meta.current);

        //alert("Bring Up Console");
        var MIN = 5, MAX = 15; //First MIN issues are stored
        for (var i = meta.history.length - 1, j = MAX - MIN; i >= 0; i--, j--)
        {
            console.warn(i, timePassed(meta.history[i].timestamp));
        }

        if (meta.history.length > MAX) //History Cleanup greater than
        {
            for (var i = 0, j = MAX - MIN; i < MAX - MIN; i++, j--)
            {
                if (timestamp - meta.history[i].timestamp > DAY * 7 * j)
                {
                    console.log("GREATER", i, timePassed(meta.history[i].timestamp));
                    meta.history.splice(i, 1);
                    break;
                }
            }
        }
        if (meta.history.length > MAX) //History Cleanup less than
        {
            for (var i = MIN, j = 1; i < MAX; i++, j++)
            {
                if (timestamp - meta.history[i].timestamp < DAY * 7 * j)
                {
                    console.log("LESSER", i, timePassed(meta.history[i].timestamp));
                    meta.history.splice(i, 1);
                    break;
                }
            }
        }
        if (meta.history.length > MAX) console.log("mm", timePassed(meta.history[i].timestamp));
        if (meta.history.length > MAX) meta.history.splice(MIN-1,1); //Last Min Date

        GM_setValue("USER-S:" + username, JSON.stringify(meta));

        if (meta.history.length == 1 && meta.history[0].timestamp == meta.current.timestamp) return;

        TSL.addStyle("Panel-History", "#HistoryList {padding-left: 20px;} "
            + "#HistoryList li {cursor: pointer; background-color: #E0F9FF; border-radius: 5px; margin-bottom: 1px; padding: 0 5px;}"
            + "#HistoryList li:hover {background-color: #E9FFE0;}"
            + "#HistoryList li.selected {background-color: #FBE0FF;}"
            );

        var panel = document.createElement("div");
        panel.className = "panel panel-default";
        panel.appendChild(document.createElement("div"));
        document.querySelector(".container-fluid.col-sm-4").appendChild(panel);

        //Display History List
        panel = panel.firstElementChild;
        panel.className = "panel-body";

        var el = document.createElement("h3");
        el.textContent = "Stats History";
        panel.appendChild(el);

        el = document.createElement("ul");
        el.id = "HistoryList";
        panel.appendChild(el);

        el.data = meta.current;

        li = document.createElement("li");
        li.textContent = "Current: " + timePassed(oldCurrent.timestamp);
        li.data = oldCurrent;
        li.onclick = ClickedHistory;
        el.appendChild(li);

        for (var i = meta.history.length - 1, li; i >= 0; i--)
        {
            if (meta.history[i].timestamp != meta.current.timestamp && meta.history[i].timestamp != oldCurrent.timestamp)
            {
                li = document.createElement("li");
                li.textContent = timePassed(meta.history[i].timestamp);
                li.data = meta.history[i];
                li.onclick = ClickedHistory;
                el.appendChild(li);
            }
        }
        el.firstElementChild.className = "selected";
        DisplayStats(oldCurrent, meta.current);

        function timePassed(old)
        {
            var days, hrs, mins, secs, ms, ret;

            ms = timestamp - old;
            secs = Math.floor(ms / (1000)) % 60;
            mins = Math.floor(ms / (60 * 1000)) % 60;
            hrs = Math.floor(ms / (60 * 60 * 1000)) % 24;
            days = Math.floor(ms / (60 * 60 * 1000 * 24) % 7);
            weeks = Math.floor(ms / (60 * 60 * 1000 * 24) / 7);

            if (weeks) return weeks + "week" + isPlural(weeks) + " and " + days + "day" + isPlural(days);
            if (days) return days + "day" + isPlural(days) + " and " + hrs + "hr" + isPlural(hrs);
            if (hrs) return hrs + "hr" + isPlural(hrs) + " and " + mins + "min" + isPlural(mins);
            return mins + "min" + isPlural(mins) + " and " + secs + "sec" + isPlural(secs);

            function isPlural(val)
            {
                if (val == 1) return "";

                return "s";
            }
        }
    }

    function amendLibraryPage()
    {
        TSL.addStyle("LibOS", ".form-group {margin-bottom: 5px;} #copyBox {text-align: right; } #copyBtn{cursor:pointer; display: inline-block;"
        + "color: #273646; border-radius: 5px; border: 1px solid #273646; background-color: #D6D6F5; width: 140px; text-align:center;}"
        + ".input-group .form-control {cursor: default}"
        + "#masterScripts .script-icon img {width: 26px; height: 26px;}"
        + "#masterScripts ul {padding: 5px; font-size: 12px;}"
        + "#masterScripts li {margin-bottom: 3px;}"
        );


        scriptPageAmendRateArea();

        var require = document.querySelector(".input-group .form-control");
        require.value = require.value.replace(/^http:/i, "https:");
        require.readOnly = true;

        var copyBox = document.createElement("div");
        copyBox.id = "copyBox";
        copyBox.innerHTML = "<span id='copyBtn'>Copy to Clipboard</span>";

        copyBox.firstChild.setAttribute("meta", "// @grant\t\t" + require.value);
        copyBox.firstChild.onclick = function (e)
        {
            GM_setClipboard(this.getAttribute("meta"));
            this.textContent = "Copied";
            this.style.backgroundColor = "#FBF6AB";
            setTimeout(function (btn) { btn.textContent = "Copy to Clipboard"; btn.style.backgroundColor = null; }, 2000, this);
        }

        var scriptmeta = document.querySelector(".script-meta");
        scriptmeta.parentElement.insertBefore(copyBox, scriptmeta);

        var panel = document.querySelector(".panel-default .panel-body h4");
        if (panel)
        {
            panel.textContent += " (" + panel.parentElement.getElementsByTagName("li").length + ")";
            panel = panel.parentElement.parentElement;
            panel.id = "masterScripts";
            var side = document.querySelector(".col-sm-4");
            side.appendChild(panel);
            //panel.getElementsByTagName("ul")[0].style.paddingLeft = "30px";
        }
    }
    function scriptPageAmendRateArea()
    {
        var counter = 0;
        var vote = document.querySelector(".vote-area");
        var fav = document.querySelector(".nav-pills li");

        var pd = document.querySelector(".col-sm-4.container-fluid .panel-default .panel-body");
        var notice = document.createElement("div");
        notice.textContent = "If you like the script, show your appreciation to the author by rating and favouring it.";
        notice.setAttribute("style", "padding: 0 10px; color: red; font-weight: 700; border-radius: 5px; background-color: yellow;");
        pd.appendChild(notice);
    }

    function xhrPage(url, callback)
    {
        GM_xmlhttpRequest({
            url: url,
            method: "GET",
            //overrideMimeType: 'text/plain; charset=x-user-defined',
            //overrideMimeType: 'document',
            //overrideMimeType: "responseXML",
            //headers: { "User-agent": navigator.userAgent},
            headers: { "User-agent": navigator.userAgent, "Accept": "text/xml" },
            onload: function (xhr)
            {
                if (xhr.status == 200)
                {
                    //var doc = new DOMParser().parseFromString(xhr.responseText, 'text/xml');
                    var doc = document.implementation.createHTMLDocument('MPIV');
                    doc.documentElement.innerHTML = xhr.responseText;
                    callback(xhr, doc);
                }
                else callback(xhr, null);
            }
        });
    }

    function getSavedUsernames()
    {
        var arr = new Array();
        var names = GM_listValues();

        for (var i = 0; i < names.length; i++)
        {
            if (names[i].match(/^USER-S:/))
            {
                arr.push(names[i].substr(7));
            }
        }

        return arr;
    }

    function getNewPostCount(username, doc)
    {
        var posts;
        var arr = new Array();

        if (doc) posts = doc.querySelectorAll('.table td:nth-child(2) span a[href*="/' + username + '/"]');
        else posts = document.querySelectorAll('.table td:nth-child(2) span a[href*="/' + username + '/"]');

        var meta = JSON.parse(GM_getValue("USER-P:" + username));

        for (var i = 0, post; i < posts.length, post = posts[i]; i++)
        {
            while (post.tagName != "TR") post = post.parentElement;
            if (!doc) TSL.addClass(post, "userpost");
            var replies = parseInt(post.querySelector("td:nth-child(4)").textContent);
            var postTitle = post.querySelector(".tr-link-a").textContent;
            var postID = getUID(postTitle, "p");

            if (!meta[postID]) meta[postID] = {};

            if (meta[postID].replies != replies)
            {
                TSL.addClass(post, "newposts");

                if (!doc)
                {
                    var diff = document.createElement("sup");
                    post.querySelector("td:nth-child(4) p").appendChild(diff);
                    if (replies == 0) diff.textContent = "new";
                    else diff.textContent = "+" + ((meta[postID].replies == undefined) ? replies : replies - meta[postID].replies);
                }

                var val = { postID: postID, postTitle: postTitle, replies: replies, url: post.querySelector(".tr-link-a").href };
                if (post.querySelector("td:nth-child(3) .label:last-child").textContent != username) arr.push(val);
                else
                {
                    meta[postID].replies = replies;
                    meta[postID].timestamp = timestamp;
                }
            }
        }

        GM_setValue("USER-P:" + username, JSON.stringify(meta));
        return arr;
    }
})();