[TS] OUJS-1

OpenuserJS: New post/issue notification, adds install and ratings history stats, improves table view, list all user scripts in one page, improves library page...

As of 2014-10-01. See the latest version.

  1. // ==UserScript==
  2. // @name [TS] OUJS-1
  3. // @namespace TimidScript
  4. // @version 1.0.7
  5. // @description OpenuserJS: New post/issue notification, adds install and ratings history stats, improves table view, list all user scripts in one page, improves library page...
  6. // @icon https://imgur.com/RCyq4C8.png
  7. // @author TimidScript
  8. // @homepageURL https://openuserjs.org/users/TimidScript
  9. // @copyright © 2014 TimidScript, All Rights Reserved.
  10. // @license Creative Commons BY-NC-SA + Please notify me if distributing
  11. // @include http*://openuserjs.org/*
  12. // @require https://openuserjs.org/src/libs/TimidScript/TSL_-_Generic.js
  13. // @require https://openuserjs.org/src/libs/TimidScript/TSL_-_GM_Update.js
  14. // @homeURL https://openuserjs.org/scripts/TimidScript/[TS]_OUJS-1
  15. // @grant GM_getValue
  16. // @grant GM_setValue
  17. // @grant GM_deleteValue
  18. // @grant GM_xmlhttpRequest
  19. // @grant GM_listValues
  20. // @grant GM_registerMenuCommand
  21. // @grant GM_setClipboard
  22. // ==/UserScript==
  23.  
  24.  
  25. /*
  26. ********************************************************************************************
  27. Copyright © 2014 TimidScript, All Rights Reserved.
  28. Script's Homepage: Check homepages below
  29.  
  30. TimidScript's Homepage: https://openuserjs.org/users/TimidScript
  31. https://greatest.deepsurf.us/users/1455-timidscript
  32. https://monkeyguts.com/author.php?un=timidscript
  33.  
  34.  
  35. http://userscripts.org/users/TimidScript
  36. http://userscripts-mirror.org/users/100610/scripts
  37.  
  38. ------------------------------------
  39. Version History
  40. ------------------------------------
  41. 1.0.8 Initial public release (2014/10/01)
  42. - Improved table view with numbering.
  43. - All profile scripts listed on one page
  44. - Script are listed on profile page also, so no need to click on Scripts tab
  45. - User scripts ordered by type (Script, Library and defunct)
  46. - Stats history for installs and ratings with sensible time spacing
  47. - Top install listing
  48. - Profile table view sort implemented within the script
  49. - Notification on issues raised on users scripts
  50. - Notification on the forum
  51. - Stores logged username so you do not need to be logged in to get script stats and notifications
  52. - Changed the interface of library scripts
  53. - Added copy button on library
  54. - List all author's scripts in profile tab.
  55. - List all author's scripts in one page
  56. - Limits size of image in frame on forum and icon size in library page
  57. ********************************************************************************************/
  58.  
  59. if (window !== window.top) return;
  60. console.info("OUJS-1 is Running");
  61.  
  62. function ToggleIssuesView(discuss, issues)
  63. {
  64. discuss.onmouseover = MouseOver;
  65. discuss.onmouseleave = MouseLeave;
  66. issues.onmouseover = MouseOver;
  67. issues.onmouseleave = MouseLeave;
  68.  
  69. function MouseOver()
  70. {
  71. clearTimeout(document.to);
  72. issues.style.display = "block";
  73. }
  74.  
  75. function MouseLeave()
  76. {
  77. document.to = setTimeout(function () { issues.style.display = "none"; }, 500);
  78. }
  79. }
  80.  
  81. function RemoveNotice(e)
  82. {
  83. var meta = JSON.parse(GM_getValue("USER-P:" + this.getAttribute("username")));
  84. meta[this.postID] = this.postdata;
  85. GM_setValue("USER-P:" + this.getAttribute("username"), JSON.stringify(meta));
  86.  
  87. TSL.removeNode(this.parentElement);
  88. if (!document.querySelector("#IssuesListing li")) TSL.removeNode("IssuesListing");
  89. }
  90.  
  91.  
  92. function ClickedHistory(e)
  93. {
  94. TSL.removeClass(this.parentElement.getElementsByClassName("selected")[0], "selected");
  95. TSL.addClass(this, "selected");
  96. DisplayStats(this.data, this.parentElement.data);
  97. }
  98.  
  99. function DisplayStats(old, current)
  100. {
  101. var totalI = totalR = 0, row, sups = document.querySelectorAll(".dStatP, .dStatN");
  102.  
  103. for (var i = 0; sups && i < sups.length; i++) TSL.removeNode(sups[i]);
  104. TSL.removeNode("TopTen");
  105.  
  106. var arr = [];
  107. for (var scriptID in current)
  108. {
  109. row = document.querySelector('tr[scriptid="' + scriptID + '"]');
  110. if (!row) continue;
  111. if (old[scriptID])
  112. {
  113. var diff = current[scriptID].installs - old[scriptID].installs;
  114. totalI += diff;
  115. if (diff) row.querySelector("td:nth-child(2) p").appendChild(TSL.createElementHTML("<sup class='dStat" + ((diff > 0) ? "P" : "N") + "'>" + diff + "</sup>"));
  116.  
  117. if (diff) arr.push ({name: row.querySelector("b").textContent, installs : diff} );
  118.  
  119. diff = current[scriptID].rating - old[scriptID].rating;
  120. totalR += diff;
  121. if (diff) row.querySelector("td:nth-child(3) p").appendChild(TSL.createElementHTML("<sup class='dStat" + ((diff > 0) ? "P" : "N") + "'>" + diff + "</sup>"));
  122. }
  123. }
  124.  
  125. if (totalI) document.querySelector(".table thead th:nth-child(2) a").appendChild(TSL.createElementHTML("<sup class='dStat" + ((totalI > 0) ? "P" : "N") + "'>" + totalI + "</sup>"));
  126. if (totalR) document.querySelector(".table thead th:nth-child(3) a").appendChild(TSL.createElementHTML("<sup class='dStat" + ((totalR > 0) ? "P" : "N") + "'>" + totalR + "</sup>"));
  127.  
  128. if (arr.length < 4) return;
  129. arr.sort(function(a, b)
  130. {
  131. if (a.installs < b.installs) return -1;
  132. if (a.installs > b.installs) return 1;
  133.  
  134. return 0;
  135. });
  136. arr.reverse();
  137.  
  138. var panel = document.createElement("div");
  139. panel.className = "panel panel-default";
  140. panel.id = "TopTen";
  141. panel.appendChild(document.createElement("div"));
  142. document.querySelector(".container-fluid.col-sm-4").appendChild(panel);
  143.  
  144. panel = panel.firstElementChild;
  145. panel.className = "panel-body";
  146.  
  147. var el = document.createElement("h3");
  148. el.textContent = "Top Installs During Period";
  149. panel.appendChild(el);
  150.  
  151. var ol = document.createElement("ol");
  152. ol.setAttribute("style","font-size:14px; font-weight: 500;");
  153. panel.appendChild(ol);
  154.  
  155. while (arr.length > 0)
  156. {
  157. el = document.createElement("li");
  158. el.innerHTML = arr[0].name + " (<span style='color:green; font-weight: 700;'>" + arr[0].installs + "</span>)";
  159. ol.appendChild(el);
  160. arr.shift();
  161. }
  162. }
  163.  
  164.  
  165.  
  166. function addScriptListingNumbers()
  167. {
  168. 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}"
  169. + ".col-sm-8 .table .tr-link td {vertical-align: middle !important; }"
  170. + ".col-sm-8 .table .tr-link td .progress { margin-bottom: 0px; }"
  171. //+ ".col-sm-8 .table .tr-link td:nth-child(2) p {margin-bottom: 14px;}"
  172. );
  173.  
  174. var sn = document.getElementsByClassName("script_number");
  175. while (sn.length > 0) TSL.removeNode(sn[0]);
  176.  
  177. var start = document.querySelector(".pagination .active a");
  178. if (start) start = (parseInt(start.textContent) - 1) * 25 + 1;
  179. else start = 1;
  180.  
  181. var rows = document.querySelector(".table");
  182. rows = rows.querySelectorAll(".tr-link");
  183. if (!rows) return;
  184. var prefix = "".lPad("0", rows.length.toString().length);
  185. for (var i = 0, row, cell; i < rows, row = rows[i]; i++)
  186. {
  187. var counter = document.createElement("span");
  188. counter.className = "script_number";
  189. counter.textContent = (prefix + (i + start)).slice(-1 * prefix.length);
  190. row.firstElementChild.insertBefore(counter, row.firstElementChild.children[0]);
  191. }
  192. }
  193.  
  194. function SortScriptTable(e)
  195. {
  196. e.stopImmediatePropagation();
  197. TSL.addStyle("OUJS-ORDER",".decen {background-color: rgba(200,255,255,0.2);} .ascen {background-color: rgba(255,220,255, 0.2);} ");
  198. //#F0DDFD #DAFBF6 #C2F4F7 #FF0
  199.  
  200. var descending = TSL.hasClass(this.parentElement,"decen");
  201. if (document.querySelector(".decen, .ascen")) TSL.removeClass(document.querySelector(".decen, .ascen"), "decen ascen");
  202. TSL.addClass(this.parentElement, ((descending) ? "ascen" : "decen"));
  203.  
  204. var tbody = document.querySelector(".table tbody");
  205. var rows = tbody.getElementsByClassName("tr-link");
  206.  
  207. var idx = this.parentElement.cellIndex;
  208.  
  209. for(var n, i = 0; i < rows.length - 1; i++)
  210. {
  211. n = i;
  212. for(var j=i+1; j < rows.length; j++)
  213. {
  214. if ((descending && compareRows(rows[n], rows[j]) > 0) || (!descending && compareRows(rows[n], rows[j]) < 0))
  215. {
  216. n = j;
  217. }
  218. }
  219.  
  220. if (n != i) tbody.insertBefore(rows[n], rows[i]);
  221. }
  222.  
  223. //row1 less it's negative otherwise positive
  224. function compareRows(row1, row2)
  225. {
  226. if (TSL.hasClass(row1, "header_row") || TSL.hasClass(row2, "header_row")) return 0;
  227. if (TSL.hasClass(row1, "_library") || TSL.hasClass(row1, "_defunct")) return 0;
  228. if (TSL.hasClass(row2, "_library") || TSL.hasClass(row2, "_defunct")) return 0;
  229.  
  230. var selector = "b", val1, val2;
  231. if (idx == 1) selector = "td:nth-child(2) p";
  232. else if (idx == 2) selector = "td:nth-child(3) p";
  233. else if (idx == 3) selector = "td:nth-child(4) time";
  234.  
  235.  
  236. if (idx == 0)
  237. {
  238. val1 = row1.querySelector(selector).textContent.toLowerCase();
  239. val2 = row2.querySelector(selector).textContent.toLowerCase();
  240. }
  241. else if (idx == 3)
  242. {
  243. val1 = new Date(row1.querySelector(selector).getAttribute("datetime")).getTime();
  244. val2 = new Date(row2.querySelector(selector).getAttribute("datetime")).getTime();
  245. }
  246. else
  247. {
  248. val1 = parseInt(row1.querySelector(selector).textContent.match(/^\d+/)[0]);
  249. val2 = parseInt(row2.querySelector(selector).textContent.match(/^\d+/)[0]);
  250. }
  251.  
  252. if (val1 < val2) return -1;
  253. if (val1 > val2) return 1;
  254.  
  255. return 0;
  256. }
  257.  
  258. addScriptListingNumbers();
  259.  
  260. return false;
  261. }
  262.  
  263.  
  264. (function ()
  265. {
  266. var timestamp = Date.now();
  267. var loggedUsername = "";
  268. TSL.addStyle("LimitImageSize", "img {max-width: 98%;}");
  269.  
  270. if (document.querySelector(".fa-sign-out") != undefined)
  271. {
  272. loggedUsername = document.querySelector('ul li a[href^="/users/"]').textContent;
  273.  
  274. var meta = GM_getValue("USER-S:" + loggedUsername);
  275. if (!meta) //Create new user data.
  276. {
  277. meta = {};
  278. meta.current = {};
  279. meta.history = new Array();
  280. GM_setValue("USER-S:" + loggedUsername, JSON.stringify(meta));
  281. }
  282.  
  283. meta = GM_getValue("USER-P:" + loggedUsername);
  284. if (!meta)
  285. {
  286. GM_setValue("USER-P:" + loggedUsername, JSON.stringify({}));
  287. }
  288.  
  289. //perform cleanup by deleting old post records
  290. var names = getSavedUsernames();
  291. for (var i = 0; i < names.length; i++)
  292. {
  293. var meta = JSON.parse(GM_getValue("USER-P:" + names[i]));
  294.  
  295. for (var key in meta)
  296. {
  297. if (timestamp - meta[key].timestamp > 86400000 * 14) //30 Days
  298. {
  299. delete meta[key];
  300. GM_deleteValue(key);
  301. }
  302. }
  303.  
  304. GM_setValue("USER-P:" + names[i], JSON.stringify(meta));
  305. }
  306. }
  307.  
  308. var pathname = document.location.pathname;
  309.  
  310. if (pathname.match(/^\/$|^\/group\/\w+/))
  311. {
  312. addScriptListingNumbers();
  313. }
  314. else if (pathname.match(/^\/users\/\w+$/i)) //User profile page
  315. {
  316. var container = document.getElementsByClassName("container-fluid")[0];
  317. 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>';
  318. getScriptListings(document.URL + "/scripts/?p=1");
  319.  
  320. window.history.pushState(null, "", document.URL + "/scripts"); //Change document URL
  321. }
  322. else if (pathname.match(/^\/users\/\w+\/scripts/i)) //User script listing
  323. {
  324. TSL.addStyle("UserScripts", "ul.pagination {display: none;}");
  325. var nextpage = document.querySelector("ul.pagination > .active + li > a");
  326. if (nextpage) getScriptListings(nextpage.href);
  327. else if (document.querySelector(".table .tr-link")) amendUserScriptListing();
  328. }
  329. else if (pathname.match(/^\/scripts\/[^\/]+\/[^\/]+$/i)) //Script page
  330. {
  331. scriptPageAmendRateArea();
  332. }
  333. else if ((pathname.match(/^\/libs\/[^\/]+\/[^\/]+$/i))) amendLibraryPage();
  334.  
  335. displayNewIssues();
  336.  
  337. function displayNewIssues()
  338. {
  339. if (pathname.match(/^\/forum/))
  340. {
  341. TSL.addStyle("ForumHelper", "body .table .userpost:nth-child(2n) td {background-color: #FFD;}"
  342. + "body .table .userpost:nth-child(2n+1) td {background-color: #FFF9DD;}"
  343. + "body .table .newposts td:nth-child(4) {color: red;}"
  344. + "body .table .newposts td:nth-child(4) sup {color: green;}"
  345. + "#newIssues {background-color: #E1F9E6; padding: 1px 20px; font-weight: 600; color: green;}"
  346. + "#newIssues span {margin-right: 30px;}"
  347. );
  348.  
  349. var notice = document.createElement("div");
  350. notice.id = "newIssues";
  351. var usernames = getSavedUsernames();
  352. document.querySelector(".table-responsive").parentElement.insertBefore(notice, document.querySelector(".table-responsive"));
  353. for (var i = 0, username; i < usernames.length, username = usernames[i]; i++)
  354. {
  355. var c = getNewPostCount(username);
  356. if (c.length > 0)
  357. {
  358. var el = document.createElement("span");
  359. if (usernames.length > 1) el.textContent = "(" + username + ") ";
  360. el.textContent += "New posts detected on " + c.length + " issues/threads";
  361. notice.appendChild(el);
  362. }
  363. }
  364.  
  365. if (!notice.textContent) notice.textContent = "No new issues detected";
  366. return;
  367. }
  368.  
  369. xhrPage("https://openuserjs.org/forum", function (xhr, doc)
  370. {
  371. var count = 0;
  372. var usernames = getSavedUsernames();
  373. var issues = document.createElement("section");
  374. issues.appendChild(document.createElement("ul"));
  375.  
  376. for (var i = 0, username, arr; i < usernames.length, username = usernames[i]; i++)
  377. {
  378. var meta = JSON.parse(GM_getValue("USER-P:" + username));
  379. var updated = false;
  380. arr = getNewPostCount(username, doc);
  381. count += arr.length;
  382. for (var j = 0, li; j < arr.length; j++)
  383. {
  384. li = TSL.createElementHTML("<li><a href='" + arr[j].url + "'>" + arr[j].postTitle + "</a></li>");
  385. li.appendChild(TSL.createElementHTML("<span>❌</span>"));
  386. issues.firstElementChild.appendChild(li);
  387.  
  388. li.lastElementChild.onclick = RemoveNotice;
  389. li.lastElementChild.postdata = { timestamp: timestamp, replies: arr[j].replies };
  390. li.lastElementChild.postID = arr[j].postID;
  391. li.lastElementChild.setAttribute("username", username);
  392.  
  393. if (loggedUsername == username && decodeURI(document.location.pathname) == decodeURI(arr[j].url))
  394. {
  395. meta[arr[j].postID] = {}
  396. meta[arr[j].postID].timestamp = timestamp;
  397. meta[arr[j].postID].replies = arr[j].replies;
  398. updated = true;
  399. }
  400. }
  401.  
  402. if (updated) GM_setValue("USER-P:" + username, JSON.stringify(meta));
  403. }
  404.  
  405. if (count > 0)
  406. {
  407. var discuss = document.querySelector('.nav a[title="Discuss"]');
  408. discuss.innerHTML += " (";
  409. var notice = document.createElement("span");
  410. notice.setAttribute("style", "color: lime; display:inline-block; font-weight: 600;");
  411. notice.id = "newIssues";
  412. notice.textContent = count;
  413. discuss.appendChild(notice);
  414. discuss.innerHTML += ")";
  415.  
  416. TSL.addStyle("OUJS-IL-BT", "#IssuesListing {position: absolute;background-color: #2C3E50; color: white; font-weight: 600; z-index: 99999;"
  417. + "padding: 3px 8px; color: white; border: 1px solid black; box-sizing: border-box;}"
  418. + "#IssuesListing ul {margin: 0; padding-left: 10px;}"
  419. + "#IssuesListing a {display: inline-block; color: orange; }"
  420. + "#IssuesListing li:hover {background-color: #435A71;}"
  421. + "#IssuesListing span {color: red; margin-left: 15px; cursor: default; display: inline-block;"
  422. );
  423.  
  424. issues.id = "IssuesListing";
  425. document.body.appendChild(issues);
  426.  
  427. var pos = TSL.getAbsolutePosition(discuss.parentElement);
  428. issues.style.top = (discuss.clientHeight + pos.top) + "px";
  429. if (issues.clientWidth + pos.left - 10 > window.innerWidth) issues.style.right = "2px";
  430. else issues.style.left = (pos.left - 30) + "px";
  431.  
  432. TSL.addStyle("OUJS-IL-BT2", "#IssuesListing {display: none;}");
  433.  
  434. ToggleIssuesView(discuss, issues);
  435. }
  436. });
  437. }
  438.  
  439. function getScriptListings(url)
  440. {
  441. xhrPage(url, xhrCallback);
  442.  
  443. function xhrCallback(xhr, doc)
  444. {
  445. if (!doc) return;
  446. var tb = document.querySelector(".table tbody");
  447. var scripts = doc.getElementsByClassName("tr-link");
  448. if (tb)
  449. {
  450. while (scripts.length > 0) tb.appendChild(scripts[0]);
  451. }
  452. else
  453. {
  454. var container = document.getElementsByClassName("col-xs-12")[0];
  455. var scriptPanel = doc.getElementsByClassName("panel panel-default")[0];
  456. container.appendChild(document.importNode(scriptPanel, true));
  457. }
  458.  
  459. var nextpage = doc.querySelector("ul.pagination > .active + li > a");
  460. if (nextpage) getScriptListings(document.location.origin + nextpage.href, xhrCallback);
  461. else amendUserScriptListing();
  462. }
  463. }
  464.  
  465. function amendUserScriptListing()
  466. {
  467. TSL.addStyle("CoolColors", ".header_row {padding: 0; font-size: 16px;}"
  468. + ".header_row > td {padding: 0 10px !important; font-weight: 700;}"
  469. + "tr._defunct {background-color: #FFF5F3;}"
  470. + "tr.header_row._defunct {background-color: pink; color: red;}"
  471. + "tr._library {background-color: #F7FDF7;}"
  472. + "tr.header_row._library {background-color: #ACF9AC; color: green;}"
  473. );
  474. var tbody = document.querySelector(".table tbody");
  475. //var tbody = document.createElement("tbody");
  476. var rows = tbody.querySelectorAll(".tr-link");
  477.  
  478. ////Put libraries at the bottom of the table
  479. var added = false;
  480. for (var i = 0, row, row2, cell; i < rows, row = rows[i]; i++)
  481. {
  482. if (!row.querySelector(".script-version"))
  483. {
  484. if (!added)
  485. {
  486. added = true;
  487. row2 = tbody.insertRow();
  488. row2.className = "header_row _library";
  489. cell = row2.insertCell();
  490. cell.setAttribute("colspan", 5);
  491. cell.textContent = "Libraries";
  492. }
  493. TSL.addClass(row, "_library");
  494. tbody.appendChild(row);
  495. }
  496. }
  497.  
  498. //Put defunct scripts at the bottom of the table
  499. added = false;
  500. for (var i = 0, row, row2, cell; i < rows, row = rows[i]; i++)
  501. {
  502. var version = row.querySelector(".script-version")
  503. if (version && version.textContent.match(/defunct|depreciated|obselete/i))
  504. {
  505. if (!added)
  506. {
  507. added = true;
  508. row2 = tbody.insertRow();
  509. row2.className = "header_row _defunct";
  510. cell = row2.insertCell();
  511. cell.setAttribute("colspan", 5);
  512. cell.textContent = "Depreciated scripts that are no longer begin supported";
  513. }
  514. TSL.addClass(row, "_defunct");
  515. tbody.appendChild(row);
  516. }
  517. }
  518.  
  519. TSL.addStyle("HeaderPointer",".col-xs-12 .table th a {cursor: pointer;}");
  520. var headers = document.querySelectorAll(".table thead th a");
  521. for(var i = 0; i < headers.length; i++)
  522. {
  523. headers[i].removeAttribute("href");
  524. headers[i].onclick = SortScriptTable;
  525. }
  526.  
  527. addScriptListingNumbers();
  528. appendHistory();
  529. }
  530.  
  531. function getUID(name, prefix)
  532. {
  533. if (!prefix) prefix = "s";
  534. var id, ids = GM_listValues();
  535.  
  536. for (var i = 0; i < ids.length, id = ids[i]; i++)
  537. {
  538. if (id[0] == prefix && id.match(/.\d+$/) && GM_getValue(id) == name) return id;
  539. }
  540.  
  541. while (true)
  542. {
  543. id = (prefix || "s") + ("0000" + Math.floor(Math.random() * 10000 + 1)).slice(-4);
  544. if (GM_getValue(id, 0) == 0)
  545. {
  546. GM_setValue(id, name);
  547. return id;
  548. }
  549. }
  550. }
  551.  
  552. function appendHistory()
  553. {
  554. var username = document.querySelector(".user-name").textContent;
  555. var meta = GM_getValue("USER-S:" + username);
  556.  
  557. if (!meta) return;
  558. TSL.addStyle("HistoricalColors", ".dStatN {color: red;} .dStatP {color: green;} .dStatP:before { content: '+';}");
  559.  
  560. meta = JSON.parse(meta);
  561. var oldCurrent = meta.current;
  562. meta = JSON.parse(GM_getValue("USER-S:" + username));
  563.  
  564. var rows = document.querySelectorAll(".table .tr-link");
  565. for (var i = 0, row; i < rows, row = rows[i]; i++)
  566. {
  567. var scriptname = row.querySelector(".tr-link-a > b").textContent;
  568. var scriptID = getUID(scriptname);
  569. rows[i].setAttribute("ScriptID", scriptID);
  570.  
  571. data = {};
  572. data.installs = parseInt(row.querySelector("td:nth-child(2) p").textContent);
  573. data.rating = parseInt(row.querySelector("td:nth-child(3) p").textContent);
  574. meta.current[scriptID] = data;
  575. }
  576.  
  577. meta.current.timestamp = timestamp;
  578. if (meta.history.length == 0 || timestamp - meta.history[meta.history.length - 1].timestamp >= 86400000)
  579. {
  580. meta.history.push(meta.current);
  581. }
  582.  
  583. DisplayStats(oldCurrent, meta.current);
  584.  
  585. var MIN = 3, MAX = 13;
  586. if (meta.history.length > MAX) //History Cleanup
  587. {
  588. for (var i = 9, j = MAX - MIN; i > 2; i--, j--)
  589. {
  590. if (timestamp - meta.history[i].timestamp > 86400000 * 7 * j)
  591. {
  592. meta.history = meta.history.splice(i, 1);
  593. break;
  594. }
  595. }
  596. }
  597. if (meta.history.length > MAX) //History Cleanup
  598. {
  599. for (var i = MIN, j = 1; i < MAX; i++, j++)
  600. {
  601. if (timestamp - meta.history[i].timestamp < 86400000 * 7 * j)
  602. {
  603. meta.history = meta.history.splice(i, 1);
  604. break;
  605. }
  606. }
  607. }
  608. if (meta.history.length > MAX) meta.history.unshift();
  609.  
  610. GM_setValue("USER-S:" + username, JSON.stringify(meta));
  611.  
  612. if (meta.history.length == 1 && meta.history[0].timestamp == meta.current.timestamp) return;
  613.  
  614. TSL.addStyle("Panel-History", "#HistoryList {padding-left: 20px;} "
  615. + "#HistoryList li {cursor: pointer; background-color: #E0F9FF; border-radius: 5px; margin-bottom: 1px; padding: 0 5px;}"
  616. + "#HistoryList li:hover {background-color: #E9FFE0;}"
  617. + "#HistoryList li.selected {background-color: #FBE0FF;}"
  618. );
  619.  
  620. var panel = document.createElement("div");
  621. panel.className = "panel panel-default";
  622. panel.appendChild(document.createElement("div"));
  623. document.querySelector(".container-fluid.col-sm-4").appendChild(panel);
  624.  
  625. //Display History List
  626. panel = panel.firstElementChild;
  627. panel.className = "panel-body";
  628.  
  629. var el = document.createElement("h3");
  630. el.textContent = "Stats History";
  631. panel.appendChild(el);
  632.  
  633. el = document.createElement("ul");
  634. el.id = "HistoryList";
  635. panel.appendChild(el);
  636.  
  637. el.data = meta.current;
  638.  
  639. li = document.createElement("li");
  640. li.textContent = "Current: " + timePassed(oldCurrent.timestamp);
  641. li.data = oldCurrent;
  642. li.onclick = ClickedHistory;
  643. el.appendChild(li);
  644.  
  645. for (var i = meta.history.length - 1, li; i >= 0; i--)
  646. {
  647. if (meta.history[i].timestamp != meta.current.timestamp && meta.history[i].timestamp != oldCurrent.timestamp)
  648. {
  649. li = document.createElement("li");
  650. li.textContent = timePassed(meta.history[i].timestamp);
  651. li.data = meta.history[i];
  652. li.onclick = ClickedHistory;
  653. el.appendChild(li);
  654. }
  655. }
  656. el.firstElementChild.className = "selected";
  657.  
  658. function timePassed(old)
  659. {
  660. var days, hrs, mins, secs, ms, ret;
  661.  
  662. ms = timestamp - old;
  663. secs = Math.floor(ms / (1000)) % 60;
  664. mins = Math.floor(ms / (60 * 1000)) % 60;
  665. hrs = Math.floor(ms / (60 * 60 * 1000)) % 24;
  666. days = Math.floor(ms / (60 * 60 * 1000 * 24) % 7);
  667. weeks = Math.floor(ms / (60 * 60 * 1000 * 24) / 7);
  668.  
  669. if (weeks) return weeks + "weeks & " + days + "days";
  670. if (days) return days + "days & " + hrs + "hrs";
  671. if (hrs) return hrs + "hrs & " + mins + "mins";
  672. return mins + "mins & " + secs + "secs";
  673. }
  674. }
  675.  
  676. function amendLibraryPage()
  677. {
  678. TSL.addStyle("LibOS", ".form-group {margin-bottom: 5px;} #copyBox {text-align: right; } #copyBtn{cursor:pointer; display: inline-block;"
  679. + "color: #273646; border-radius: 5px; border: 1px solid #273646; background-color: #D6D6F5; width: 140px; text-align:center;}"
  680. + ".input-group .form-control {cursor: default}"
  681. + "#masterScripts .script-icon img {width: 26px; height: 26px;}"
  682. + "#masterScripts ul {padding: 5px; font-size: 12px;}"
  683. + "#masterScripts li {margin-bottom: 3px;}"
  684. );
  685.  
  686.  
  687. scriptPageAmendRateArea();
  688.  
  689. var require = document.querySelector(".input-group .form-control");
  690. require.value = require.value.replace(/^http:/i, "https:");
  691. require.readOnly = true;
  692.  
  693. var copyBox = document.createElement("div");
  694. copyBox.id = "copyBox";
  695. copyBox.innerHTML = "<span id='copyBtn'>Copy to Clipboard</span>";
  696.  
  697. copyBox.firstChild.setAttribute("meta", "// @grant\t\t" + require.value);
  698. copyBox.firstChild.onclick = function (e)
  699. {
  700. GM_setClipboard(this.getAttribute("meta"));
  701. this.textContent = "Copied";
  702. this.style.backgroundColor = "#FBF6AB";
  703. setTimeout(function (btn) { btn.textContent = "Copy to Clipboard"; btn.style.backgroundColor = null; }, 2000, this);
  704. }
  705.  
  706. var scriptmeta = document.querySelector(".script-meta");
  707. scriptmeta.parentElement.insertBefore(copyBox, scriptmeta);
  708.  
  709. var panel = document.querySelector(".panel-default .panel-body h4");
  710. if (panel)
  711. {
  712. panel.textContent += " (" + panel.parentElement.getElementsByTagName("li").length + ")";
  713. panel = panel.parentElement.parentElement;
  714. panel.id = "masterScripts";
  715. var side = document.querySelector(".col-sm-4");
  716. side.appendChild(panel);
  717. //panel.getElementsByTagName("ul")[0].style.paddingLeft = "30px";
  718. }
  719. }
  720. function scriptPageAmendRateArea()
  721. {
  722. var counter = 0;
  723. var vote = document.querySelector(".vote-area");
  724. var fav = document.querySelector(".nav-pills li");
  725.  
  726. var pd = document.querySelector(".col-sm-4.container-fluid .panel-default .panel-body");
  727. var notice = document.createElement("div");
  728. notice.textContent = "If you like the script, show your appreciation to the author by rating and favouring it.";
  729. notice.setAttribute("style", "padding: 0 10px; color: red; font-weight: 700; border-radius: 5px; background-color: yellow;");
  730. pd.appendChild(notice);
  731. }
  732.  
  733. function xhrPage(url, callback)
  734. {
  735. GM_xmlhttpRequest({
  736. url: url,
  737. method: "GET",
  738. headers: { "User-agent": navigator.userAgent, "Accept": "text/xml" },
  739. onload: function (xhr)
  740. {
  741. if (xhr.status == 200)
  742. {
  743. var doc = new DOMParser().parseFromString(xhr.responseText, 'text/xml');
  744.  
  745. dt = document.implementation.createDocumentType("html", "-//W3C//DTD HTML 4.01 Transitional//EN", "http://www.w3.org/TR/html4/loose.dtd"),
  746. doc = document.implementation.createDocument("", "", dt),
  747. documentElement = doc.createElement("html");
  748. documentElement.innerHTML = xhr.responseText;
  749. doc.appendChild(documentElement);
  750.  
  751. callback(xhr, doc);
  752. }
  753. else callback(xhr, null);
  754. }
  755. });
  756. }
  757.  
  758. function getSavedUsernames()
  759. {
  760. var arr = new Array();
  761. var names = GM_listValues();
  762.  
  763. for (var i = 0; i < names.length; i++)
  764. {
  765. if (names[i].match(/^USER-S:/))
  766. {
  767. arr.push(names[i].substr(7));
  768. }
  769. }
  770.  
  771. return arr;
  772. }
  773.  
  774. function getNewPostCount(username, doc)
  775. {
  776. var posts;
  777. var arr = new Array();
  778.  
  779. if (doc) posts = doc.querySelectorAll('.table td:nth-child(2) span a[href*="/' + username + '/"]');
  780. else posts = document.querySelectorAll('.table td:nth-child(2) span a[href*="/' + username + '/"]');
  781.  
  782. var meta = JSON.parse(GM_getValue("USER-P:" + username));
  783.  
  784. for (var i = 0, post; i < posts.length, post = posts[i]; i++)
  785. {
  786. while (post.tagName != "TR") post = post.parentElement;
  787. if (!doc) TSL.addClass(post, "userpost");
  788. var replies = parseInt(post.querySelector("td:nth-child(4)").textContent);
  789. var postTitle = post.querySelector(".tr-link-a").textContent;
  790. var postID = getUID(postTitle, "p");
  791.  
  792. if (!meta[postID]) meta[postID] = {};
  793.  
  794. if (meta[postID].replies != replies)
  795. {
  796. TSL.addClass(post, "newposts");
  797.  
  798. if (!doc)
  799. {
  800. var diff = document.createElement("sup");
  801. post.querySelector("td:nth-child(4) p").appendChild(diff);
  802. if (replies == 0) diff.textContent = "new";
  803. else diff.textContent = "+" + ((meta[postID].replies == undefined) ? replies : replies - meta[postID].replies);
  804. }
  805.  
  806. var val = { postID: postID, postTitle: postTitle, replies: replies, url: post.querySelector(".tr-link-a").href };
  807. if (post.querySelector("td:nth-child(3) .label:last-child").textContent != username) arr.push(val);
  808. else
  809. {
  810. meta[postID].replies = replies;
  811. meta[postID].timestamp = timestamp;
  812. }
  813. }
  814. }
  815.  
  816. GM_setValue("USER-P:" + username, JSON.stringify(meta));
  817. return arr;
  818. }
  819. })();