[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.

As of 2016-04-02. See the latest version.

  1. // ==UserScript==
  2. // @name [TS] OUJS-1
  3. // @namespace TimidScript
  4. // @version 1.0.26
  5. // @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.
  6. // @author TimidScript
  7. // @homepageURL https://openuserjs.org/users/TimidScript
  8. // @copyright © 2014 TimidScript, All Rights Reserved.
  9. // @license Creative Commons BY-NC-SA + Read Copyright inside the script
  10. // @include http*://openuserjs.org/*
  11. // @require https://openuserjs.org/src/libs/TimidScript/TSL_-_Generic.js
  12. // @require https://openuserjs.org/src/libs/TimidScript/TSL_-_GM_Update.js
  13. // @homeURL https://openuserjs.org/scripts/TimidScript/[TS]_OUJS-1
  14. // @grant GM_getValue
  15. // @grant GM_setValue
  16. // @grant GM_deleteValue
  17. // @grant GM_xmlhttpRequest
  18. // @grant GM_listValues
  19. // @grant GM_registerMenuCommand
  20. // @grant GM_setClipboard
  21. // @icon data:image/gif;base64,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
  22. // ==/UserScript==
  23.  
  24.  
  25. /* Copyright Notice
  26. ********************************************************************************************
  27. Copyright © TimidScript, All Rights Reserved.
  28. [Creative Commons BY-NC-SA](http://en.wikipedia.org/wiki/Creative_Commons_license)
  29.  
  30. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
  31. following conditions are met:
  32.  
  33. 1) This copyright must be included
  34. 2) Due credits and link to original author's homepage (included in copyright).
  35. 3) Notify the original author of redistribution
  36.  
  37. TimidScript's Homepages: [GitHub](https://github.com/TimidScript)
  38. [OpenUserJS](https://openuserjs.org/users/TimidScript)
  39. [GreasyFork](https://greatest.deepsurf.us/users/1455-timidscript)
  40. */
  41. /*
  42. ********************************************************************************************
  43. Version History
  44. ------------------------------------
  45. 1.0.26 (2016-02-27)
  46. - Ratings bar and "Flaggins" are now separate elements, so removed unnecessary code and notice.
  47. - Removed flagged information as it seems admin hide it so might as well hide it also.
  48. 1.0.25 (2015-09-06)
  49. - Interval check for new issues every 3 minutes
  50. - Option to display new issue count in title. Need to set GM_setValue("EditTitle",1);
  51. - Highlight username in forums.
  52. 1.0.24 (2015-07-16)
  53. - Bug Fix: Correctly handle negative ratings
  54. 1.0.23 (2015-07-16)
  55. - Changed the styling of blockquote
  56. 1.0.22 (2015-06-25)
  57. - Bug Fix: Rating count is wrong when it is 0 and flagged
  58. - Unhide the progress bar elements
  59. - On script replaced the deceptive ratings bar and change the way information if conveyed.
  60. - No longer altering the profile page URL. Removed the search box from profile page
  61. - Columns sort order highlighted in listings
  62. - Removed cloneInto as it's no longer needed
  63. - Script icon now base64
  64. - Removed scripts are no longer listed in the History Chart
  65. 1.0.21 (2015-06-10)
  66. - Changed the colours of the issues on the forum
  67. - Changed progress-bar colouring
  68. - Bug Fix: Total rating in author script listing now shows both negative and positive count
  69. 1.0.20 (2015-05-14)
  70. - Closed issues are now in red while open ones are in a brighter green.
  71. - Small bug fixes
  72. 1.0.19 (2015-04-13)
  73. - Added ability to remove a history record. This is used when certain dates get corrupted.
  74. 1.0.18 (2015-01-22)
  75. - Bug fix for version 1.0.17 which broke support for other browsers.
  76. - Bug fix to support querySelector in Opera and Chrome. "Discussions" links use ".getAttribute" instead of ".href"
  77. 1.0.17 (2015-01-17)
  78. - Bug fixes to handle new changes in OUJS layout
  79. - Issue count now decrements
  80. - Provided a fix for the trash values created due to FireFox 35 breaking of GM_listValues API.
  81. 1.0.16 (2015-01-04)
  82. - Handles Lazy loading of icons in profile page
  83. - Created icon for libraries
  84. 1.0.15 (2014/12/27)
  85. - Bug fix in sorting installs and ratings in other users profiles
  86. - "Discuss" now points to All discussion board.
  87. - Change in forum interface to make it better with one click access to other boards.
  88. - Changed the dual colour scheme on issues forum to one
  89. 1.0.14 (2014/11/29)
  90. - More visible highlight colour for one's own script discussions on the forum
  91. - Bug fixes to deal with new OUJS layout
  92. - Bug fix to total count in history chart
  93. 1.0.13 (2014/11/01)
  94. - Bug Fix: Copy button text changed from @grant to @require
  95. 1.0.12 (2014/11/01)
  96. - Fixed it so it works on Opera and Chrome
  97. - It now displays "History Period Installs Chart" if the length is more than 0 as oppose to 3.
  98. 1.0.11 (2014/10/31)
  99. - Fixed Bug caused by console debug iteration
  100. 1.0.10 (2014/10/24)
  101. - Bug Fix on history cleanup
  102. - Changes in CSS for profile comments
  103. 1.0.9 (2014/10/18)
  104. - Bug Fix in history cleanup
  105. - Stats history takes into account deleted and new scripts
  106. - Negative stat is now shown in brackets
  107. - History Chart always at the bottom of page
  108. - Renamed "Top Installs During Period" chart
  109. - Added frame and margin to images
  110. - Correct handling of singular and plural period nouns
  111. - Changed the history spacing
  112. - Added wait period between new issues checks. Maximum of once every 20 seconds
  113. - Selected table header highlight is more visible and saves last sort order
  114. 1.0.8 Initial public release (2014/10/01)
  115. - Improved table view with numbering.
  116. - All profile scripts listed on one page
  117. - Script are listed on profile page also, so no need to click on Scripts tab
  118. - User scripts ordered by type (Script, Library and defunct)
  119. - Stats history for installs and ratings with sensible time spacing
  120. - Top install listing
  121. - Profile table view sort implemented within the script
  122. - Notification on issues raised on users scripts
  123. - Notification on the forum
  124. - Stores logged username so you do not need to be logged in to get script stats and notifications
  125. - Changed the interface of library scripts
  126. - Added copy button on library
  127. - List all author's scripts in profile tab.
  128. - List all author's scripts in one page
  129. - Limits size of image in frame on forum and icon size in library page
  130. ********************************************************************************************/
  131.  
  132.  
  133. /*=================================================================================================*/
  134. // [VYCS] VARIABLE YOU CAN SET
  135. //----------------------------------------
  136. // GM_setValue("HistoryMIN",5); //Stats history, minimum number of consective days stored. Default 5.
  137. // GM_setValue("HistoryMAX",5); //Stats history, minimum number of mins stored. Default 15
  138. /*=================================================================================================*/
  139.  
  140. if (window !== window.top) return;
  141. //GM_setValue("EditTitle", 1); //Uncomment line to allow issue count to be displayed on title. Once run once, you need to update the script to re-enable auto-update.
  142. var HistoryMIN = parseInt(GM_getValue("HistoryMIN", 5)); //Stats history, minimum number of mins stored
  143. var HistoryMAX = parseInt(GM_getValue("HistoryMAX", 15)); //Stats histry, maximum number of dates stored
  144.  
  145.  
  146. console.info("OUJS-1 is Running");
  147.  
  148.  
  149. var DAY = 86400000;
  150.  
  151. function ToggleIssuesView(discuss, issues)
  152. {
  153. discuss.onmouseover = MouseOver;
  154. discuss.onmouseleave = MouseLeave;
  155. issues.onmouseover = MouseOver;
  156. issues.onmouseleave = MouseLeave;
  157.  
  158. function MouseOver()
  159. {
  160. clearTimeout(document.to);
  161. issues.style.display = "block";
  162. }
  163.  
  164. function MouseLeave()
  165. {
  166. document.to = setTimeout(function () { issues.style.display = "none"; }, 500);
  167. }
  168. }
  169.  
  170. function RemoveNotice(e)
  171. {
  172. var meta = JSON.parse(GM_getValue("USER-P:" + this.getAttribute("username")));
  173. meta[this.postID] = this.postdata;
  174. GM_setValue("USER-P:" + this.getAttribute("username"), JSON.stringify(meta));
  175.  
  176. document.getElementById("newIssues").textContent = (parseInt(document.getElementById("newIssues").textContent) - 1);
  177.  
  178. TSL.removeNode(this.parentElement);
  179. if (!document.querySelector("#IssuesListing li")) TSL.removeNode("IssuesListing");
  180. }
  181.  
  182.  
  183. function ClickedHistory(e)
  184. {
  185. TSL.removeClass(this.parentElement.getElementsByClassName("selected")[0], "selected");
  186. TSL.addClass(this, "selected");
  187. DisplayStats(this.data, this.parentElement.data);
  188. }
  189.  
  190. function DisplayStats(old, current)
  191. {
  192. var totalR, totalI = totalIN = totalNew = totalRp = totalRn = 0, row, sups = document.querySelectorAll(".dStatP, .dStatN, .dStatZ, .dStatNew");
  193.  
  194. var tmpStamp = Date.now();
  195.  
  196. for (var i = 0; sups && i < sups.length; i++) TSL.removeNode(sups[i]);
  197. TSL.removeNode("TopTen");
  198.  
  199. var arr = [];
  200. for (var scriptID in current)
  201. {
  202. row = document.querySelector('tr[scriptid="' + scriptID + '"]');
  203.  
  204. if (scriptID == "timestamp") continue;
  205. else if (!row) arr.push({ name: GM_getValue(scriptID), installs: -1 }); //Removed Items
  206. else if (old[scriptID])
  207. {
  208. row.tempStamp = tmpStamp;
  209. var diff = current[scriptID].installs - old[scriptID].installs;
  210. if (isNaN(diff)) diff = 0;
  211. totalI += diff;
  212. if (diff < 0) totalIN += diff; //Can happen when you remove and then re-add the script
  213. if (diff) row.querySelector("td:nth-child(2) p").appendChild(TSL.createElementHTML("<sup class='dStat" + ((diff > 0) ? "P" : "N") + "'>" + diff + "</sup>"));
  214.  
  215. if (diff) arr.push({ name: row.querySelector("b").textContent, installs: diff });
  216.  
  217. diff = current[scriptID].rating - old[scriptID].rating;
  218. if (isNaN(diff)) diff = 0;
  219. if (diff > 0) totalRp += diff;
  220. else if (diff < 0) totalRn += diff;
  221. if (diff) row.querySelector("td:nth-child(3) p").appendChild(TSL.createElementHTML("<sup class='dStat" + ((diff > 0) ? "P" : "N") + "'>" + diff + "</sup>"));
  222. }
  223. }
  224.  
  225. var rows = document.querySelectorAll(".col-xs-12 .table .tr-link");
  226. for (var i = 0, installs; i < rows.length, row = rows[i]; i++)
  227. {
  228. if (row.tempStamp != tmpStamp)
  229. {
  230. installs = parseInt(row.getAttribute("installs"));
  231. totalI += installs;
  232. totalNew += installs;
  233. row.querySelector("td:nth-child(2) p").appendChild(TSL.createElementHTML("<sup class='dStatNew'>" + installs + "</sup>"));
  234. arr.push({ name: row.querySelector(".tr-link-a > b").textContent, installs: installs, isNew: true });
  235. }
  236. }
  237.  
  238. if (totalI) document.querySelector(".table thead th:nth-child(2) a").appendChild(TSL.createElementHTML("<sup class='dStat" + ((totalI > 0) ? "P" : "N") + "'>"
  239. + totalI + (function ()
  240. {
  241. if (totalI != totalIN && totalIN < 0) return "<span style='color: red;'>[" + totalIN + "]</span>"
  242. return "";
  243. })() + "</sup>"));
  244.  
  245.  
  246.  
  247. totalR = totalRp + totalRn;
  248. if (totalRp || totalRn) document.querySelector(".table thead th:nth-child(3) a").appendChild(TSL.createElementHTML("<sup class='dStat" + ((totalR == 0) ? "Z" : (totalR > 0) ? "P" : "N") + "'>"
  249. + totalR +
  250. (function ()
  251. {
  252. if (totalRp && totalRn)
  253. {
  254. return "<span style='color:black;'>[<span style='color:green;'>" + totalRp + "</span><span style='color:red;'>" + totalRn + "</span>]</span>";
  255. }
  256. return "";
  257. })() + "</sup>"));
  258.  
  259. if (arr.length == 0) return;
  260. arr.sort(function (a, b)
  261. {
  262. if (a.installs < b.installs) return -1;
  263. if (a.installs > b.installs) return 1;
  264.  
  265. return 0;
  266. });
  267. arr.reverse();
  268.  
  269. var panel = document.createElement("div");
  270. panel.className = "panel panel-default";
  271. panel.id = "TopTen";
  272. panel.appendChild(document.createElement("div"));
  273. document.querySelector(".container-fluid.col-sm-4").appendChild(panel);
  274.  
  275. panel = panel.firstElementChild;
  276. panel.className = "panel-body";
  277.  
  278. var el = document.createElement("h3");
  279. el.innerHTML = "History Period Installs Chart (<span style=\"color:green;\">" + totalI + "</span>)";
  280. panel.appendChild(el);
  281.  
  282. var ol = document.createElement("ol");
  283. ol.setAttribute("style", "font-size:14px; font-weight: 500;");
  284. panel.appendChild(ol);
  285.  
  286. while (arr.length > 0)
  287. {
  288. if (arr[0].installs >= 0)
  289. {
  290. el = document.createElement("li");
  291. if (arr[0].installs < 0) el.innerHTML = arr[0].name + " (<span style='color:red; font-weight: 700;'>Removed</span>)";
  292. else if (arr[0].isNew) el.innerHTML = arr[0].name + " (<span style='color:blue; font-weight: 700;'>" + arr[0].installs + "</span>)";
  293. else el.innerHTML = arr[0].name + " (<span style='color:green; font-weight: 700;'>" + arr[0].installs + "</span>)";
  294. ol.appendChild(el);
  295. }
  296. arr.shift();
  297. }
  298. }
  299.  
  300.  
  301.  
  302. function addScriptListingNumbers()
  303. {
  304. TSL.addStyle("ScriptNumbers", ".script_number {display: inline-block; margin: 0 1px 0 0 !important; 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;}"
  305. + ".col-sm-8 .table .tr-link td {vertical-align: middle !important; }"
  306. + ".col-sm-8 .table .tr-link td .progress { margin-bottom: 0px; }"
  307. //+ ".col-sm-8 .table .tr-link td:nth-child(2) p {margin-bottom: 14px;}"
  308. );
  309.  
  310. var sn = document.getElementsByClassName("script_number");
  311. while (sn.length > 0) TSL.removeNode(sn[0]);
  312.  
  313. var start = document.querySelector(".pagination .active a");
  314. if (start) start = (parseInt(start.textContent) - 1) * 25 + 1;
  315. else start = 1;
  316.  
  317. var rows = document.querySelector(".table");
  318. rows = rows.querySelectorAll(".tr-link");
  319. if (!rows) return;
  320. var prefix = "".lPad("0", rows.length.toString().length);
  321. for (var i = 0, row, cell; i < rows, row = rows[i]; i++)
  322. {
  323. var counter = document.createElement("span");
  324. counter.className = "script_number";
  325. counter.textContent = (prefix + (i + start)).slice(-1 * prefix.length);
  326. row.firstElementChild.insertBefore(counter, row.firstElementChild.children[0]);
  327. }
  328. }
  329.  
  330. function SortScriptTable(e)
  331. {
  332. e.stopImmediatePropagation();
  333.  
  334. var sortDescending = !TSL.hasClass(this.parentElement, "descen");
  335. if (document.querySelector(".descen, .ascen")) TSL.removeClass(document.querySelector(".descen, .ascen"), "descen ascen");
  336. TSL.addClass(this.parentElement, ((sortDescending) ? "descen" : "ascen"));
  337.  
  338. var tbody = document.querySelector(".table tbody");
  339. var rows = tbody.getElementsByClassName("tr-link");
  340.  
  341. var idx = this.parentElement.cellIndex;
  342. GM_setValue("SortHeader", idx);
  343. GM_setValue("SortDescending", sortDescending);
  344.  
  345. for (var n, i = 0; i < rows.length - 1; i++)
  346. {
  347. n = i;
  348. for (var j = i + 1; j < rows.length; j++)
  349. {
  350. if ((sortDescending && compareRows(rows[n], rows[j]) < 0) || (!sortDescending && compareRows(rows[n], rows[j]) > 0))
  351. {
  352. n = j;
  353. }
  354. }
  355.  
  356. if (n != i) tbody.insertBefore(rows[n], rows[i]);
  357. }
  358.  
  359. //row1 less it's negative otherwise positive
  360. function compareRows(row1, row2)
  361. {
  362. if (TSL.hasClass(row1, "header_row") || TSL.hasClass(row2, "header_row")) return 0;
  363. if (TSL.hasClass(row1, "_library") || TSL.hasClass(row1, "_defunct")) return 0;
  364. if (TSL.hasClass(row2, "_library") || TSL.hasClass(row2, "_defunct")) return 0;
  365.  
  366. var selector = "b", val1, val2;
  367. if (idx == 1) selector = "td:nth-child(2) p";
  368. else if (idx == 2) selector = "td:nth-child(3) p";
  369. else if (idx == 3) selector = "td:nth-child(4) time";
  370.  
  371.  
  372. if (idx == 0)
  373. {
  374. val1 = row1.querySelector(selector).textContent.toLowerCase();
  375. val2 = row2.querySelector(selector).textContent.toLowerCase();
  376. }
  377. else if (idx == 1)
  378. {
  379. if (row1.hasAttribute("installs"))
  380. {
  381. val1 = parseInt(row1.getAttribute("installs"));
  382. val2 = parseInt(row2.getAttribute("installs"));
  383. }
  384. else
  385. {
  386. val1 = parseInt(row1.querySelector(selector).textContent);
  387. val2 = parseInt(row2.querySelector(selector).textContent);
  388. }
  389. }
  390. else if (idx == 2)
  391. {
  392. if (row1.hasAttribute("rating"))
  393. {
  394. val1 = parseInt(row1.getAttribute("rating"));
  395. val2 = parseInt(row2.getAttribute("rating"));
  396. }
  397. else
  398. {
  399. val1 = parseInt(row1.querySelector(selector).textContent);
  400. val2 = parseInt(row2.querySelector(selector).textContent);
  401. }
  402. }
  403. else if (idx == 3)
  404. {
  405. val1 = new Date(row1.querySelector(selector).getAttribute("datetime")).getTime();
  406. val2 = new Date(row2.querySelector(selector).getAttribute("datetime")).getTime();
  407. }
  408.  
  409. if (val1 < val2) return -1;
  410. if (val1 > val2) return 1;
  411.  
  412. return 0;
  413. }
  414.  
  415. addScriptListingNumbers();
  416. return false;
  417. }
  418.  
  419.  
  420. //Fix broken saved data caused by FireFox 35 security issues
  421. (function ()
  422. {
  423. if (GM_getValue("HasBeenFixed", false)) return;
  424. alert("Due to changes in FireFox 35 GreaseMonkey GM_listValues got broken and with it, it has ruined OUJS-1 save. This will attempt to fix it. If it fails then remove, restart firefox and install again");
  425.  
  426. var scriptIDsPresent = {};
  427.  
  428. var usernames = getSavedUsernames();
  429. for (var i = 0; i < usernames.length; i++)
  430. {
  431. UpdateFireFox35BrokenData(usernames[i]);
  432. }
  433.  
  434.  
  435. var id, ids = GM_listValues();
  436. for (var i = ids.length - 1; i >= 0, id = ids[i]; i--)
  437. {
  438. if (id.match(/s\d\d\d\d/i) && !scriptIDsPresent[id])
  439. {
  440. GM_deleteValue(id);
  441. }
  442. }
  443. GM_setValue("HasBeenFixed", true);
  444.  
  445.  
  446. function UpdateFireFox35BrokenData(username)
  447. {
  448. var name,
  449. remove = false,
  450. updated = false,
  451. scriptNames = {},
  452. meta = JSON.parse(GM_getValue("USER-S:" + username));
  453.  
  454. for (var i = 0; i < meta.history.length; i++)
  455. {
  456. remove = false;
  457. for (var key in meta.history[i])
  458. {
  459. if (key.match(/s\d\d\d\d/i))
  460. {
  461. name = GM_getValue(key);
  462.  
  463. if (!scriptNames[name])
  464. {
  465. //console.log(name, key, scriptNames[name]);
  466. scriptNames[name] = key;
  467. scriptIDsPresent[key] = name;
  468. }
  469. else if (scriptNames[name] != key) //Value introduced after FF35 update and is invalid
  470. {
  471. updated = true;
  472. remove = true;
  473. }
  474. }
  475. }
  476.  
  477. if (remove)
  478. {
  479. meta.history.splice(i, 1);
  480. i = 0;
  481. }
  482. }
  483.  
  484. if (updated)
  485. {
  486. meta.current = meta.history[meta.history.length - 1];
  487. console.log(meta.current);
  488. GM_setValue("USER-S:" + username, JSON.stringify(meta));
  489. }
  490. }
  491.  
  492. function getSavedUsernames()
  493. {
  494. var arr = new Array();
  495. var names = GM_listValues();
  496.  
  497. for (var i = 0; i < names.length; i++)
  498. {
  499. if (names[i].match(/^USER-S:/))
  500. {
  501. arr.push(names[i].substr(7));
  502. }
  503. }
  504.  
  505. return arr;
  506. }
  507. })();
  508.  
  509. (function ()
  510. {
  511. 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>';
  512. var timestamp = Date.now();
  513. var loggedUsername = "";
  514.  
  515. TSL.addStyle("ppp", ".progress {background-color: #E74C3C;} .progress-bar-good {background-color: #499E49;} .progress * {color: white;}");
  516. 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;}"
  517. + ".topic-post-contents img:last-child {margin-bottom: 10px;}"
  518. );
  519. TSL.addStyle("BetterQuotes", 'blockquote {font-size:14px;font-style: italic;border-left: 7px solid #DFE1E1;margin: 10px 30px;padding: 0px 5px; }');
  520.  
  521. TSL.addStyle("OUJS-ORDER", ".descen {background-color: rgba(200,255,255,0.6);} .ascen {background-color: rgba(255,220,255, 0.6);} ");
  522. //#F0DDFD #DAFBF6 #C2F4F7 #FF0
  523.  
  524. if (document.querySelector(".fa-sign-out") != undefined)
  525. {
  526. loggedUsername = document.querySelector('ul li a[href^="/users/"]').textContent;
  527.  
  528. var meta = GM_getValue("USER-S:" + loggedUsername);
  529. if (!meta) //Create new user data.
  530. {
  531. meta = {};
  532. meta.current = {};
  533. meta.history = new Array();
  534. GM_setValue("USER-S:" + loggedUsername, JSON.stringify(meta));
  535. }
  536.  
  537. meta = GM_getValue("USER-P:" + loggedUsername);
  538. if (!meta)
  539. {
  540. GM_setValue("USER-P:" + loggedUsername, JSON.stringify({}));
  541. }
  542.  
  543. //perform cleanup by deleting old post records
  544. var names = getSavedUsernames();
  545. for (var i = 0; i < names.length; i++)
  546. {
  547. var meta = JSON.parse(GM_getValue("USER-P:" + names[i]));
  548.  
  549. for (var key in meta)
  550. {
  551. if (timestamp - meta[key].timestamp > DAY * 14) //30 Days
  552. {
  553. delete meta[key];
  554. GM_deleteValue(key);
  555. }
  556. }
  557.  
  558. GM_setValue("USER-P:" + names[i], JSON.stringify(meta));
  559. }
  560. }
  561.  
  562. document.querySelector("nav a[href='/forum']").href = "/all"; //Change the "Discuss" link to point directly to "All"
  563. var pathname = document.location.pathname;
  564. if (pathname.match(/^\/$|^\/group\/\w+|^\/groups/))
  565. {
  566. console.log("OUJS-1: Scripts/Library/Groups listings");
  567. addLibraryIcons();
  568. addScriptListingNumbers();
  569.  
  570.  
  571. var columns = columns = ["name", "installs", "rating", "updated"],
  572. search = document.location.search,
  573. orderBy = search.match(/orderBy=(\w+)/i);
  574.  
  575. orderBy = (orderBy) ? orderBy[1] : "rating";
  576.  
  577. if (search.match(/library/i)) columns = ["name", "rating", "updated"];
  578. else if (pathname.match(/group/i)) columns = ["name", "size", "rating"];
  579.  
  580. TSL.addClass(document.querySelector(".panel > table > thead th:nth-child(" +
  581. (function ()
  582. {
  583. for (var i = 0; i < columns.length; i++)
  584. {
  585. console.log(i, columns[i].toLowerCase(), orderBy);
  586. if (orderBy.toLowerCase() == columns[i].toLowerCase()) return i + 1;
  587. }
  588. return 3;
  589. }
  590. )() + ")"), ((search.match(/orderDir=asc/i)) ? "ascen" : "descen"));
  591. }
  592. else if (pathname.match(/^\/users\/\w+$/i)) //User profile page
  593. {
  594. console.log("OUJS-1: Profile page");
  595. var container = document.getElementsByClassName("container-fluid")[0];
  596. getScriptListings(document.URL + "/scripts/?p=1");
  597.  
  598. //Search box
  599. //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>';
  600. //window.history.pushState(null, "", document.URL + "/scripts"); //Change document URL
  601. }
  602. else if (pathname.match(/^\/users\/.+\/comments/i)) //User profile comments page
  603. {
  604. console.log("OUJS-1: Profile comments page");
  605. TSL.addStyle("UserScripts", ".topic-title .breadcrumb {margin-bottom: 0px;}");
  606. }
  607. else if (pathname.match(/^\/users\/\w+\/scripts/i)) //User script listing
  608. {
  609. TSL.addStyle("UserScripts", "ul.pagination {display: none;}");
  610. var nextpage = document.querySelector("ul.pagination > .active + li > a");
  611. if (nextpage) getScriptListings(nextpage.href);
  612. else if (document.querySelector(".table .tr-link")) amendUserScriptListing();
  613. }
  614. else if (pathname.match(/^\/scripts\/[^\/]+\/[^\/]+$/i)) //Script page
  615. {
  616. console.log("OUJS-1: Scripts page");
  617. scriptPageAmendRateArea();
  618. }
  619. else if (pathname.match(/^\/(all|issues|garage|corner|discuss|announcements)$/))
  620. {
  621. console.log("OUJS-1: Forum");
  622. TSL.addStyle("NinjaStyle", ".breadcrumb {padding: 8px; } .breadcrumb > li + li {margin-left: 2px;} .breadcrumb > li + li:before { content: ''; padding: 0px;} .breadcrumb > li {width: 120px; text-align:center; border: 1px solid #CFD2D2; border-radius: 5px; } ");
  623. var el = document.querySelector(".breadcrumb");
  624. //el.className = "fishMonkey";
  625. el.innerHTML = '<li><a href="/all" title="Amalgamation of all discussion boards">All</a></li>' +
  626. '<li><a href="/issues" title="Issues raised on scripts">Script Issues</a></li>' +
  627. '<li><a href="/garage" title="Get help with script development">Developer Help</a></li>' +
  628. '<li><a href="/corner" title="Propose ideas and request user-scripts">Script Requests</a></li>' +
  629. '<li><a href="/discuss" title="Off-topic discussions">General</a></li>' +
  630. '<li><a href="/announcements" title="Official site announcements">Announcements</a></li>';
  631.  
  632.  
  633. TSL.addStyle("WoloSD3", ".selectedDiscuss {background-color: #CACAF5; border-color: #00F !important;} .selectedDiscuss a {color: #00F;}");
  634. el.querySelector("li > a[href='" + pathname + "']").parentNode.className = "selectedDiscuss";
  635. }
  636. else if ((pathname.match(/^\/libs\/[^\/]+\/[^\/]+$/i))) amendLibraryPage();
  637.  
  638. displayNewIssues();
  639.  
  640. function displayNewIssues()
  641. {
  642. if (pathname.match(/^\/(issues|forum|all|corner|garage|discuss|announcements)/))
  643. {
  644. var usernames = getSavedUsernames();
  645. for (var i = 0, username; i < usernames.length, username = usernames[i]; i++)
  646. {
  647. var els = document.querySelectorAll('.label-info a[href="/users/' + username + '"]');
  648. for (var j = 0; j < els.length; j++) els[j].parentElement.style.backgroundColor = "#6AB046";
  649. }
  650. }
  651.  
  652. if (pathname.match(/^\/(issues|forum|all)/))
  653. {
  654. TSL.addStyle("ForumHelper", "body .table .scriptIssues td {background-color: #BCF3F3 !important;}"
  655. + ".scriptIssues .tr-link-a, .scriptIssues td:nth-child(2) * {color: #04A263 !important;}"
  656. + "body .table .scriptIssues.closed td {background-color: #ECEDED !important;}"
  657. + ".scriptIssues.closed .tr-link-a, .scriptIssues.closed td:nth-child(2) * {color: gray !important;}"
  658. + "body .table .newposts td:nth-child(4) {color: red;}"
  659. + "body .table .newposts td:nth-child(4) sup {color: green;}"
  660. + "#newIssues {background-color: #D6FDF7; padding: 1px 20px; font-weight: 600; color: green;}"
  661. + "#newIssues span {margin-right: 30px;}"
  662. );
  663.  
  664. var notice = document.createElement("div");
  665. notice.id = "newIssues";
  666. var usernames = getSavedUsernames();
  667. document.querySelector(".table-responsive").parentElement.insertBefore(notice, document.querySelector(".table-responsive"));
  668. for (var i = 0, username; i < usernames.length, username = usernames[i]; i++)
  669. {
  670. var c = getNewPostCount(username);
  671. if (c.length > 0)
  672. {
  673. var el = document.createElement("span");
  674. if (usernames.length > 1) el.textContent = "(" + username + ") ";
  675. el.textContent += "New posts detected on " + c.length + " issues/threads";
  676. notice.appendChild(el);
  677. }
  678. }
  679.  
  680. if (!notice.textContent) notice.textContent = "No new issues detected";
  681. return;
  682. }
  683.  
  684. if (window.sessionStorage.getItem("NewIssuesStamp") && Date.now() - window.sessionStorage.getItem("NewIssuesStamp") < 20000)
  685. {
  686. var doc = document.implementation.createHTMLDocument("OUJS");
  687. doc.documentElement.innerHTML = window.sessionStorage.getItem("NewIssuesDoc");
  688. NewIssues(doc);
  689. }
  690. else
  691. {
  692. GetIssueCount();
  693. setInterval(GetIssueCount, 6000);
  694. //setInterval(GetIssueCount, 180000);
  695. }
  696.  
  697. function GetIssueCount()
  698. {
  699. xhrPage("https://openuserjs.org/issues", function (xhr, doc)
  700. {
  701. if (doc)
  702. {
  703. window.sessionStorage.setItem("NewIssuesDoc", xhr.responseText);
  704. window.sessionStorage.setItem("NewIssuesStamp", Date.now());
  705. NewIssues(doc);
  706. }
  707. });
  708. }
  709.  
  710. function NewIssues(doc)
  711. {
  712. TSL.removeNode("TheIssueCounter");
  713. TSL.removeNode("IssueNoticeBoard");
  714. if (GM_getValue("EditTitle")) document.title = document.title.replace(/^\[\d+\]\s*/, "");
  715.  
  716. var count = 0;
  717. var usernames = getSavedUsernames();
  718. var issues = document.createElement("section");
  719. issues.id = "IssueNoticeBoard";
  720. issues.appendChild(document.createElement("ul"));
  721.  
  722. for (var i = 0, username, arr; i < usernames.length, username = usernames[i]; i++)
  723. {
  724. var meta = JSON.parse(GM_getValue("USER-P:" + username));
  725. var updated = false;
  726. arr = getNewPostCount(username, doc);
  727. count += arr.length;
  728. for (var j = 0, li; j < arr.length; j++)
  729. {
  730. li = TSL.createElementHTML("<li><a href='" + arr[j].url + "'>" + arr[j].postTitle + "</a></li>");
  731. li.appendChild(TSL.createElementHTML("<span>❌</span>"));
  732. issues.firstElementChild.appendChild(li);
  733.  
  734. li.lastElementChild.onclick = RemoveNotice;
  735. li.lastElementChild.postdata = { timestamp: timestamp, replies: arr[j].replies };
  736. li.lastElementChild.postID = arr[j].postID;
  737. li.lastElementChild.setAttribute("username", username);
  738.  
  739. if (loggedUsername == username && decodeURI(document.location.pathname) == decodeURI(arr[j].url))
  740. {
  741. meta[arr[j].postID] = {}
  742. meta[arr[j].postID].timestamp = timestamp;
  743. meta[arr[j].postID].replies = arr[j].replies;
  744. updated = true;
  745. }
  746. }
  747.  
  748. if (updated) GM_setValue("USER-P:" + username, JSON.stringify(meta));
  749. }
  750.  
  751. if (count > 0)
  752. {
  753. if (GM_getValue("EditTitle")) document.title = "[" + count + "] " + document.title;
  754. var discuss = document.querySelector('.nav a[href="/all"]');
  755. var counter = document.createElement("span");
  756. counter.id = "TheIssueCounter";
  757. counter.style.marginLeft = "3px";
  758. discuss.appendChild(counter);
  759. counter.innerHTML += '(<span id="newIssues" style="color: lime; display:inline-block; font-weight: 600;">' + count + '</span>)';
  760.  
  761. TSL.addStyle("OUJS-IL-BT", "#IssuesListing {position: absolute;background-color: #2C3E50; color: white; font-weight: 600; z-index: 99999;"
  762. + "font-size:12px; padding: 3px 8px; color: white; border: 1px solid black; box-sizing: border-box;}"
  763. + "#IssuesListing ul {margin: 0; padding-left: 10px;}"
  764. + "#IssuesListing a {display: inline-block; color: orange; }"
  765. + "#IssuesListing li:hover {background-color: #435A71;}"
  766. + "#IssuesListing span {color: red; margin-left: 15px; cursor: default; display: inline-block;"
  767. );
  768.  
  769. issues.id = "IssuesListing";
  770. document.body.appendChild(issues);
  771.  
  772. var pos = TSL.getAbsolutePosition(discuss.parentElement);
  773. issues.style.top = (discuss.clientHeight + pos.top) + "px";
  774. if (issues.clientWidth + pos.left - 10 > window.innerWidth) issues.style.right = "2px";
  775. else issues.style.left = (pos.left - 30) + "px";
  776.  
  777. TSL.addStyle("OUJS-IL-BT2", "#IssuesListing {display: none;}");
  778.  
  779. ToggleIssuesView(discuss, issues);
  780. }
  781. }
  782. }
  783.  
  784. function getScriptListings(url)
  785. {
  786. xhrPage(url, xhrCallback);
  787.  
  788. function xhrCallback(xhr, doc)
  789. {
  790. if (!doc) return;
  791. var tb = document.querySelector(".table tbody");
  792. var scripts = doc.getElementsByClassName("tr-link");
  793. if (tb)
  794. {
  795. while (scripts.length > 0) tb.appendChild(scripts[0]);
  796. }
  797. else
  798. {
  799. var container = document.getElementsByClassName("col-xs-12")[0];
  800. var scriptPanel = doc.getElementsByClassName("panel panel-default")[0];
  801. container.appendChild(document.importNode(scriptPanel, true));
  802. }
  803.  
  804. var nextpage = doc.querySelector("ul.pagination > .active + li > a");
  805. if (nextpage) getScriptListings(document.location.origin + nextpage.href, xhrCallback);
  806. else amendUserScriptListing();
  807. }
  808. }
  809.  
  810. function amendUserScriptListing()
  811. {
  812. TSL.addStyle("CoolColors", ".header_row {padding: 0; font-size: 16px;}"
  813. + ".header_row > td {padding: 0 10px !important; font-weight: 700;}"
  814. + "tr._defunct {background-color: #FFF5F3;}"
  815. + "tr.header_row._defunct {background-color: pink; color: red;}"
  816. + "tr._library {background-color: #F7FDF7;}"
  817. + "tr.header_row._library {background-color: #ACF9AC; color: green;}"
  818. );
  819.  
  820. var tbody = document.querySelector(".table tbody");
  821. //var tbody = document.createElement("tbody");
  822. var rows = tbody.querySelectorAll(".tr-link");
  823.  
  824. ////Put libraries at the bottom of the table
  825. var added = false;
  826. for (var i = 0, row, row2, cell; i < rows, row = rows[i]; i++)
  827. {
  828. if (!row.querySelector(".script-version"))
  829. {
  830. if (!added)
  831. {
  832. added = true;
  833. row2 = tbody.insertRow(-1);
  834. row2.className = "header_row _library";
  835. cell = row2.insertCell(-1);
  836. cell.setAttribute("colspan", 5);
  837. cell.textContent = "Libraries";
  838. }
  839. TSL.addClass(row, "_library");
  840. tbody.appendChild(row);
  841. }
  842. }
  843.  
  844. //Put defunct scripts at the bottom of the table
  845. added = false;
  846. for (var i = 0, row, row2, cell; i < rows, row = rows[i]; i++)
  847. {
  848. var version = row.querySelector(".script-version")
  849. if (version && version.textContent.match(/defunct|depreciated|obselete/i))
  850. {
  851. if (!added)
  852. {
  853. added = true;
  854. row2 = tbody.insertRow(-1);
  855. row2.className = "header_row _defunct";
  856. cell = row2.insertCell(-1);
  857. cell.setAttribute("colspan", 5);
  858. cell.textContent = "Depreciated scripts that are no longer begin supported";
  859. }
  860. TSL.addClass(row, "_defunct");
  861. tbody.appendChild(row);
  862. }
  863. }
  864.  
  865. TSL.addStyle("HeaderPointer", ".col-xs-12 .table th a {cursor: pointer;}");
  866. var headers = document.querySelectorAll(".table thead th a");
  867. for (var i = 0; i < headers.length; i++)
  868. {
  869. headers[i].removeAttribute("href");
  870. headers[i].onclick = SortScriptTable;
  871. }
  872.  
  873. appendHistory();
  874. addLibraryIcons();
  875.  
  876. //Load missing icons. Occurs in profile page
  877. els = document.querySelectorAll("i.fa.fa-fw.fa-file-code-o");
  878. for (var i = 0, el, img; i < els.length; i++)
  879. {
  880. el = els[i];
  881. img = document.createElement("img");
  882. img.src = el.parentElement.getAttribute("data-icon-src");
  883. el.parentElement.appendChild(img);
  884. TSL.removeNode(el);
  885. }
  886.  
  887. var idx = GM_getValue("SortHeader", 3);
  888. var SortAscending = !GM_getValue("SortDescending", true);
  889. headers[idx].click();
  890. if (SortAscending) headers[idx].click();
  891. //addScriptListingNumbers();
  892. }
  893.  
  894. function addLibraryIcons()
  895. {
  896. TSL.addStyle("IconReplacer", ".script-icon, .script-icon img {display:inline-block; height: 16px; width: 16px;}")
  897. var els = document.querySelectorAll("._library .script-icon.hidden-xs i");
  898. if (document.location.search.match(/^\?library=/i)) els = document.querySelectorAll(".script-icon.hidden-xs i");
  899. for (var i = 0, el, img; i < els.length; i++)
  900. {
  901. el = els[i];
  902. img = document.createElement("img");
  903. img.src = "https://i.imgur.com/pFNqMgL.png"
  904. el.parentElement.appendChild(img);
  905. TSL.removeNode(el);
  906. }
  907. }
  908.  
  909. function getUID(name, prefix)
  910. {
  911. if (!prefix) prefix = "s";
  912. var id, ids = GM_listValues();
  913.  
  914. for (var i = 0; i < ids.length, id = ids[i]; i++)
  915. {
  916. if (id[0] == prefix && id.match(/.\d+$/) && GM_getValue(id) == name) return id;
  917. }
  918.  
  919. while (true)
  920. {
  921. id = (prefix || "s") + ("0000" + Math.floor(Math.random() * 10000 + 1)).slice(-4);
  922. if (GM_getValue(id, 0) == 0)
  923. {
  924. GM_setValue(id, name);
  925. return id;
  926. }
  927. }
  928. }
  929.  
  930. function appendHistory()
  931. {
  932. var username = document.querySelector(".user-name").textContent;
  933. var meta = GM_getValue("USER-S:" + username);
  934. if (!meta) return;
  935.  
  936. TSL.addStyle("HistoricalColors", ".dStatZ {color: blue;} .dStatN {color: red;} .dStatP {color: green;} .dStatNew {color: blue;} .dStatP:before, .dStatNew:before { content: '+';} ");
  937.  
  938. meta = JSON.parse(meta);
  939. var oldCurrent = meta.current;
  940. meta = JSON.parse(GM_getValue("USER-S:" + username));
  941.  
  942. var rows = document.querySelectorAll(".table .tr-link");
  943. for (var i = 0, row; i < rows, row = rows[i]; i++)
  944. {
  945. var scriptname = row.querySelector(".tr-link-a > b").textContent;
  946. var scriptID = getUID(scriptname);
  947. rows[i].setAttribute("ScriptID", scriptID);
  948.  
  949. data = {};
  950. data.installs = parseInt(row.querySelector("td:nth-child(2) p").textContent);
  951. data.rating = parseInt(row.querySelector("td:nth-child(3) p").textContent);
  952. row.setAttribute("installs", data.installs);
  953. row.setAttribute("rating", data.rating);
  954. meta.current[scriptID] = data;
  955. }
  956.  
  957. meta.current.timestamp = timestamp;
  958. if (meta.history.length == 0 || timestamp - meta.history[meta.history.length - 1].timestamp >= DAY)
  959. {
  960. meta.history.push(meta.current);
  961. }
  962.  
  963. DisplayStats(oldCurrent, meta.current);
  964.  
  965. //alert("Bring Up Console");
  966. //for (var i = meta.history.length - 1, j = HistoryMAX - HistoryMIN; i >= 0; i--, j--)
  967. //{
  968. // console.warn(i, timePassed(meta.history[i].timestamp));
  969. //}
  970.  
  971. if (meta.history.length > HistoryMAX) //History Cleanup greater than
  972. {
  973. for (var i = 0, j = HistoryMAX - HistoryMIN; i < HistoryMAX - HistoryMIN; i++, j--)
  974. {
  975. if (timestamp - meta.history[i].timestamp > DAY * 7 * j)
  976. {
  977. //console.log("GREATER", i, timePassed(meta.history[i].timestamp));
  978. meta.history.splice(i, 1);
  979. break;
  980. }
  981. }
  982. }
  983. if (meta.history.length > HistoryMAX) //History Cleanup less than
  984. {
  985. for (var i = HistoryMIN, j = 1; i < HistoryMAX; i++, j++)
  986. {
  987. if (timestamp - meta.history[i].timestamp < DAY * 7 * j)
  988. {
  989. //console.log("LESSER", i, timePassed(meta.history[i].timestamp));
  990. meta.history.splice(i, 1);
  991. break;
  992. }
  993. }
  994. }
  995. //if (meta.history.length > HistoryMAX) console.log("mm", timePassed(meta.history[i].timestamp));
  996. if (meta.history.length > HistoryMAX) meta.history.splice(HistoryMIN - 1, 1); //Last Min Date
  997.  
  998. GM_setValue("USER-S:" + username, JSON.stringify(meta));
  999.  
  1000. if (meta.history.length == 1 && meta.history[0].timestamp == meta.current.timestamp) return;
  1001.  
  1002. TSL.addStyle("Panel-History", "#HistoryList {padding-left: 20px;} "
  1003. + "#HistoryList li {cursor: pointer; background-color: #E0F9FF; border-radius: 5px; margin-bottom: 1px; padding: 0 5px;}"
  1004. + "#HistoryList li:hover {background-color: #E9FFE0;}"
  1005. + "#HistoryList li.selected {background-color: #FBE0FF;}"
  1006. );
  1007.  
  1008. var panel = document.createElement("div");
  1009. panel.className = "panel panel-default";
  1010. panel.appendChild(document.createElement("div"));
  1011. document.querySelector(".container-fluid.col-sm-4").appendChild(panel);
  1012.  
  1013. //Display History List
  1014. panel = panel.firstElementChild;
  1015. panel.className = "panel-body";
  1016.  
  1017. var el = document.createElement("h3");
  1018. el.textContent = "Stats History";
  1019. panel.appendChild(el);
  1020.  
  1021. el = document.createElement("ul");
  1022. el.id = "HistoryList";
  1023. panel.appendChild(el);
  1024.  
  1025. el.data = meta.current;
  1026.  
  1027. li = document.createElement("li");
  1028. li.textContent = "Current: " + timePassed(oldCurrent.timestamp);
  1029. li.data = oldCurrent;
  1030. li.onclick = ClickedHistory;
  1031. el.appendChild(li);
  1032.  
  1033. for (var i = meta.history.length - 1, li, remove; i >= 0; i--)
  1034. {
  1035. if (meta.history[i].timestamp != meta.current.timestamp && meta.history[i].timestamp != oldCurrent.timestamp)
  1036. {
  1037. li = document.createElement("li");
  1038. li.textContent = timePassed(meta.history[i].timestamp);
  1039. li.data = meta.history[i];
  1040. li.onclick = ClickedHistory;
  1041.  
  1042. remove = document.createElement("span");
  1043. remove.textContent = "❌"; //❎❌
  1044. remove.setAttribute("style", "float: right; color: red;");
  1045. remove.onclick = removeRecord;
  1046. li.appendChild(remove);
  1047. el.appendChild(li);
  1048. }
  1049. }
  1050.  
  1051. el.firstElementChild.className = "selected";
  1052. DisplayStats(oldCurrent, meta.current);
  1053.  
  1054. function removeRecord(e)
  1055. {
  1056. e.stopImmediatePropagation();
  1057.  
  1058. if (!confirm("Do you want to remove this history record?")) return;
  1059.  
  1060. var meta = GM_getValue("USER-S:" + username);
  1061. meta = JSON.parse(meta);
  1062. var record = this.parentElement.data.timestamp;
  1063.  
  1064. for (var i = 0; i < meta.history.length; i++)
  1065. {
  1066. if (meta.history[i].timestamp == record)
  1067. {
  1068. meta.history.splice(i, 1);
  1069. GM_setValue("USER-S:" + username, JSON.stringify(meta));
  1070. TSL.removeNode(this.parentElement);
  1071. break;
  1072. }
  1073. }
  1074. }
  1075.  
  1076.  
  1077. function timePassed(old)
  1078. {
  1079. var days, hrs, mins, secs, ms, ret;
  1080.  
  1081. ms = timestamp - old;
  1082. secs = Math.floor(ms / (1000)) % 60;
  1083. mins = Math.floor(ms / (60 * 1000)) % 60;
  1084. hrs = Math.floor(ms / (60 * 60 * 1000)) % 24;
  1085. days = Math.floor(ms / (60 * 60 * 1000 * 24) % 7);
  1086. weeks = Math.floor(ms / (60 * 60 * 1000 * 24) / 7);
  1087.  
  1088. if (weeks) return weeks + "week" + isPlural(weeks) + " and " + days + "day" + isPlural(days);
  1089. if (days) return days + "day" + isPlural(days) + " and " + hrs + "hr" + isPlural(hrs);
  1090. if (hrs) return hrs + "hr" + isPlural(hrs) + " and " + mins + "min" + isPlural(mins);
  1091. return mins + "min" + isPlural(mins) + " and " + secs + "sec" + isPlural(secs);
  1092.  
  1093. function isPlural(val)
  1094. {
  1095. if (val == 1) return "";
  1096.  
  1097. return "s";
  1098. }
  1099. }
  1100. }
  1101.  
  1102. function amendLibraryPage()
  1103. {
  1104. TSL.addStyle("LibOS", ".form-group {margin-bottom: 5px;} #copyBox {text-align: right; } #copyBtn{cursor:pointer; display: inline-block;"
  1105. + "color: #273646; border-radius: 5px; border: 1px solid #273646; background-color: #D6D6F5; width: 140px; text-align:center;}"
  1106. + ".input-group .form-control {cursor: default}"
  1107. + "#masterScripts .script-icon img {width: 26px; height: 26px;}"
  1108. + "#masterScripts ul {padding: 5px; font-size: 12px;}"
  1109. + "#masterScripts li {margin-bottom: 3px;}"
  1110. );
  1111.  
  1112.  
  1113. scriptPageAmendRateArea();
  1114.  
  1115. var require = document.querySelector(".input-group .form-control");
  1116. require.value = require.value.replace(/^http:/i, "https:");
  1117. require.readOnly = true;
  1118.  
  1119. var copyBox = document.createElement("div");
  1120. copyBox.id = "copyBox";
  1121. copyBox.innerHTML = "<span id='copyBtn'>Copy to Clipboard</span>";
  1122.  
  1123. copyBox.firstChild.setAttribute("meta", "// @require\t\t" + require.value);
  1124. copyBox.firstChild.onclick = function (e)
  1125. {
  1126. GM_setClipboard(this.getAttribute("meta"));
  1127. this.textContent = "Copied";
  1128. this.style.backgroundColor = "#FBF6AB";
  1129. setTimeout(function (btn) { btn.textContent = "Copy to Clipboard"; btn.style.backgroundColor = null; }, 2000, this);
  1130. }
  1131.  
  1132. var scriptmeta = document.querySelector(".script-meta");
  1133. scriptmeta.parentElement.insertBefore(copyBox, scriptmeta);
  1134.  
  1135. var panel = document.querySelector(".panel-default .panel-body h4");
  1136. if (panel)
  1137. {
  1138. panel.textContent += " (" + panel.parentElement.getElementsByTagName("li").length + ")";
  1139. panel = panel.parentElement.parentElement;
  1140. panel.id = "masterScripts";
  1141. var side = document.querySelector(".col-sm-4");
  1142. side.appendChild(panel);
  1143. //panel.getElementsByTagName("ul")[0].style.paddingLeft = "30px";
  1144. }
  1145. }
  1146.  
  1147. function scriptPageAmendRateArea()
  1148. {
  1149. var pd = document.querySelector(".col-sm-4.container-fluid .panel-default .panel-body");
  1150.  
  1151. //Encourage rating
  1152. var notice = document.createElement("div");
  1153. notice.textContent = "If you like the script, show your appreciation to the author by rating and favouring it.";
  1154. notice.setAttribute("style", "padding: 0 10px; color: red; font-weight: 700; border-radius: 5px; background-color: yellow;");
  1155. pd.appendChild(notice);
  1156.  
  1157. var rating = parseInt(pd.querySelector(".row p").textContent.match(/-?\d+$/)[0]),
  1158. votes = parseInt(pd.querySelector(".progress-bar-good").textContent),
  1159. votes = isNaN(votes) ? 0 : votes,
  1160. votesDown = (votes - rating) / 2,
  1161. flags = parseInt(pd.querySelector(".progress-bar-danger").textContent),
  1162. bar1 = pd.querySelector(".progress");
  1163.  
  1164. bar1.firstElementChild.innerHTML = "<span style='color: yellow;'>+" + (votes - votesDown) + "</span>";
  1165. pd.querySelector(".row p").innerHTML = pd.querySelector(".row p").innerHTML.replace(/\d+$/, rating + " (" + votes + " Votes)");
  1166. pd.querySelector(".progress-bar-danger").innerHTML = "<span style='color: yellow;'>-" + votesDown + "</span>";
  1167. pd.querySelector(".progress-bar-danger").style.textAlign = "center";
  1168. pd.querySelector(".progress-bar-danger").style.width = (100-parseFloat (pd.querySelector(".progress-bar-good").style.width)) + "%";
  1169. }
  1170.  
  1171. function xhrPage(url, callback)
  1172. {
  1173. GM_xmlhttpRequest({
  1174. url: url,
  1175. method: "GET",
  1176. //overrideMimeType: 'text/plain; charset=x-user-defined',
  1177. //overrideMimeType: 'document',
  1178. //overrideMimeType: "responseXML",
  1179. //headers: { "User-agent": navigator.userAgent},
  1180. headers: { "User-agent": navigator.userAgent, "Accept": "text/xml" },
  1181. onload: function (xhr)
  1182. {
  1183. if (xhr.status == 200)
  1184. {
  1185. //var doc = new DOMParser().parseFromString(xhr.responseText, 'text/xml');
  1186. var doc = document.implementation.createHTMLDocument("OUJS");
  1187. doc.documentElement.innerHTML = xhr.responseText;
  1188. callback(xhr, doc);
  1189. }
  1190. else callback(xhr, null);
  1191. }
  1192. });
  1193. }
  1194.  
  1195. function getSavedUsernames()
  1196. {
  1197. var arr = new Array();
  1198. var names = GM_listValues();
  1199.  
  1200. for (var i = 0; i < names.length; i++)
  1201. {
  1202. if (names[i].match(/^USER-S:/))
  1203. {
  1204. arr.push(names[i].substr(7));
  1205. }
  1206. }
  1207.  
  1208. return arr;
  1209. }
  1210.  
  1211. function getNewPostCount(username, doc)
  1212. {
  1213. var posts;
  1214. var arr = new Array();
  1215.  
  1216. if (doc) posts = doc.querySelectorAll('.table td:nth-child(2) span a[href*="/' + username + '/"]');
  1217. else posts = document.querySelectorAll('.table td:nth-child(2) span a[href*="/' + username + '/"]');
  1218.  
  1219. var meta = JSON.parse(GM_getValue("USER-P:" + username));
  1220.  
  1221. for (var i = 0, post; i < posts.length, post = posts[i]; i++)
  1222. {
  1223. while (post.tagName != "TR") post = post.parentElement;
  1224. if (!doc)
  1225. {
  1226. TSL.addClass(post, "scriptIssues");
  1227. if (post.querySelector(".label-danger")) TSL.addClass(post, "closed");
  1228. }
  1229. var replies = parseInt(post.querySelector("td:nth-child(4)").textContent);
  1230. var postTitle = post.querySelector(".tr-link-a").textContent;
  1231. var postID = getUID(postTitle, "p");
  1232.  
  1233. if (!meta[postID]) meta[postID] = {};
  1234.  
  1235. if (meta[postID].replies != replies)
  1236. {
  1237. TSL.addClass(post, "newposts");
  1238.  
  1239. if (!doc)
  1240. {
  1241. var diff = document.createElement("sup");
  1242. post.querySelector("td:nth-child(4) p").appendChild(diff);
  1243. if (replies == 0) diff.textContent = "new";
  1244. else diff.textContent = "+" + ((meta[postID].replies == undefined) ? replies : replies - meta[postID].replies);
  1245. }
  1246.  
  1247. var val = { postID: postID, postTitle: postTitle, replies: replies, url: post.querySelector(".tr-link-a").getAttribute("href") };
  1248. if (post.querySelector("td:nth-child(3) .label:last-child").textContent != username) arr.push(val);
  1249. else
  1250. {
  1251. meta[postID].replies = replies;
  1252. meta[postID].timestamp = timestamp;
  1253. }
  1254. }
  1255. }
  1256.  
  1257. GM_setValue("USER-P:" + username, JSON.stringify(meta));
  1258. return arr;
  1259. }
  1260. })();