PSNprofiles.net enhancements

On guide pages: adds a button to hide earend trophies, their description and links and uses a new style for earned trophies, On all pages: adds update button, On game pages: persist search string

  1. // ==UserScript==
  2. // @name PSNprofiles.net enhancements
  3. // @author Barokai | www.loigistal.at
  4. // @icon http://psnprofiles.com/forums/favicon.ico
  5. // @namespace http://www.loigistal.at/userscripts/
  6. // @license https://github.com/Barokai/psnprofiles-enhanced/blob/master/LICENSE
  7. // @homepageURL http://barokai.github.io/psnprofiles-enhanced/
  8. // @supportURL https://github.com/Barokai/psnprofiles-enhanced/issues/new
  9. // @version 0.86
  10. // @description On guide pages: adds a button to hide earend trophies, their description and links and uses a new style for earned trophies, On all pages: adds update button, On game pages: persist search string
  11. // @match http*://psnprofiles.com/*
  12. // @grant GM_addStyle
  13. // @require http://code.jquery.com/jquery-latest.js
  14. // @copyright 2016+, Barokai
  15. // ==/UserScript==
  16.  
  17. // better visible green for earned trophies, used before: #57FF3B
  18. /*jshint multistr: true */
  19. GM_addStyle("\
  20. .tableofcontents li.earned { \
  21. background: #bada55 !important; \
  22. } \
  23. .roadmap-trophies li.earned { \
  24. background: #bada55 !important; \
  25. } \
  26. .roadmap-trophies .trophy.earned { \
  27. background: #bada55 !important; \
  28. } \
  29. .section-holder.earned{ \
  30. border: 3px solid #bada55; \
  31. } \
  32. #toggleEarned { \
  33. background-color: #bada55; \
  34. }\
  35. .invisible.tags a.earned{\
  36. text-shadow: 1px 1px 1px #bada55;\
  37. }\
  38. ");
  39.  
  40.  
  41. /* Guide enhancements ------------------------------------------------------- */
  42. // TODO barokai: fix scrolling behavior when trophies are hidden and a trophy link is clicked in Guide Contens column (ID="TOCWrapper")
  43. function addToggleEarnedButton() {
  44. // add class earned to all links which match earned trophies in overview-info box
  45. $('.earned > a').each(function() {
  46. var trophyName = $(this).text().trim();
  47. $('nobr > a:contains(' + trophyName + ')').addClass("earned");
  48. });
  49.  
  50. // adds class "earned" to sections to hide them with the same toggle function
  51. $("img[class*='earned']").each(function() {
  52. // .closest('li').parent().closest('li')
  53. // TODO check if class of img isn't .unearned!
  54. $($(this).closest("div[class*='section-holder']").parent().closest("div[class*='section-holder']")).addClass("earned");
  55. });
  56.  
  57. // workaround, above scripts adds earned to all secionts as img with class .unearned is matched too.
  58. $("img[class*='unearned']").each(function() {
  59. // .closest('li').parent().closest('li')
  60. $($(this).closest("div[class*='section-holder']").parent().closest("div[class*='section-holder']")).removeClass("earned");
  61. });
  62.  
  63. // workaround to hide trophies in overview completely
  64. $(".trophy.earned").each(function(){
  65. $($(this).closest(".col-xs-6")).addClass("earned");
  66. $($(this).closest(".col-xs-12")).addClass("earned");
  67. });
  68.  
  69. // adds button for toggling to overview info box
  70. $(".overview-info").append('<span class="tag" id="toggleEarned" title="click to toggle visiblity of earned trophies"><span class="typo-top">toggle</span><br/><span class="typo-bottom">earned</span>');
  71. $(document).on("click", "#toggleEarned", toggleEarned);
  72. }
  73.  
  74. // TODO: fix this, doesn't work in new version.
  75. function addToggleTypeButton() {
  76. // get trophy types (without spaces) - used as classes to toggle them later.
  77. var trophyTypes = $('table.invisible .tag').map(function(i, el) {
  78. var type = $(el);
  79. var typeName = type.text().replace(/\s+/g, '');
  80. // add toggle visibility to type boxes
  81. type.click(function(e) {
  82. toggleClass(e, typeName);
  83. });
  84. return typeName;
  85. }).get();
  86.  
  87. // get corresponding trophies - start with the ones in overview box
  88. $('table.invisible td small').each(function(index) {
  89. var typeClassName = trophyTypes[index];
  90. var trophyHref = $("nobr a", $(this));
  91. // add class to all found hrefs for this section
  92. trophyHref.addClass(typeClassName);
  93.  
  94. trophyHref.each(function() {
  95. var trophyName = $(this).text();
  96.  
  97. // contents of roadmap
  98. $(".roadmap-trophies li:contains(" + trophyName + ")").addClass(typeClassName);
  99.  
  100. // all the sections
  101. $("div[class*='element section-holder']:contains(" + trophyName + ")").addClass(typeClassName);
  102.  
  103. // next the ones in the guide contents on the right
  104. $('#TOCList li:contains(' + trophyName + ')').addClass(typeClassName);
  105.  
  106. // add class to trophies which are found in multible type sections
  107. $('table.invisible nobr a:contains(' + trophyName + ')').addClass(typeClassName);
  108. });
  109. });
  110. }
  111.  
  112. function toggleEarned(e) {
  113. toggleClass(e, "earned");
  114. }
  115.  
  116. function toggleClass(e, className) {
  117. var element;
  118.  
  119. // get correct element (togglebutton's layout would be destroyed otherwise)
  120. if (e.target.parentNode.id != "toggleEarned") {
  121. element = e.target;
  122. } else {
  123. element = e.target.parentNode;
  124. }
  125.  
  126. if (element.innerHTML.indexOf(" *") >= 0) {
  127. element.innerHTML = element.innerHTML.slice(0, -2);
  128. } else {
  129. element.innerHTML += " *";
  130. }
  131.  
  132. $("." + className).toggle("slow", function(e) { /* Animation complete */ });
  133. }
  134. /* Guide enhancements end --------------------------------------------------- */
  135.  
  136. /* Profile enhancements ----------------------------------------------------- */
  137.  
  138. // thanks to serverTimeout for sort by rank (his profile: http://psnprofiles.com/forums/user/80890-servertimeout/)
  139. // see his post here: http://psnprofiles.com/forums/topic/24324-sort-by-rank/?view=findpost&p=647509
  140. function addSortByRank() {
  141. var dropdown = $('.dropdown-toggle.order');
  142. var buttonNameAsc = "Rank E-S";
  143. dropdown.next().append(
  144. $('<li><a href="">' + buttonNameAsc + '</a></li>').on('click', function(ev) {
  145. sort(ev, '+', buttonNameAsc); // + for ascending sort
  146. })
  147. );
  148. var buttonNameDesc = "Rank S-E";
  149. dropdown.next().append(
  150. $('<li><a href="">' + buttonNameDesc + '</a></li>').on('click', function(ev) {
  151. sort(ev, '-', buttonNameDesc); // + for descending sort
  152. })
  153. );
  154. }
  155.  
  156. function sort(e, order, buttonName) {
  157. e.preventDefault();
  158. var trophyOrder = ['F', 'E', 'D', 'C', 'B', 'A', 'S'];
  159. var r;
  160. if (order === '+') {
  161. for (r = 0; r <= trophyOrder.length; r++) {
  162. // TODO: sort ranks by percent before
  163. $('.' + trophyOrder[r]).each(function() {
  164. $('.'+trophyOrder[r]).each(function() {
  165. $('#content table.zebra').eq(0).append(jQuery(this).closest('tr'));
  166. });
  167. });
  168. }
  169. }
  170. if (order === '-') {
  171. for (r = trophyOrder.length; r >= 0; r--) {
  172. // TODO: sort ranks by percent before
  173. $('.' + trophyOrder[r]).each(function() {
  174. $('.'+trophyOrder[r]).each(function() {
  175. $('#content table.zebra').eq(0).append(jQuery(this).closest('tr'));
  176. });
  177. });
  178. }
  179. }
  180.  
  181. // set dropdown button name when new order was set
  182. $('.dropdown-toggle.order').text("Order (" + buttonName + ")");
  183. }
  184. /* Profile enhancements end ------------------------------------------------- */
  185.  
  186. /* Global enhancements ------------------------------------------------------ */
  187. function addUpdateButton() {
  188. $('.navigation > ul').append("<li><a href='/?update'>Update Profile</a></li>");
  189. // TODO barokai: check if successfully updated and redirect to the last visited page (where update was clicked)
  190. }
  191. /* Global enhancements end ---------------------------------------------------*/
  192.  
  193. /* Games page enhancements -------------------------------------------------- */
  194.  
  195. // thanks to dernop for this searchFix (his profile: http://psnprofiles.com/forums/user/45256-dernop/)
  196. // see his post here: http://psnprofiles.com/forums/topic/32107-bugsoddities-in-the-games-search-feature/#entry777278
  197. function gameSearchFix() {
  198. $('#searchGames').off('keyup');
  199. $('#searchGames').keyup(function() {
  200. window.clearTimeout('searchInt');
  201. var input = $(this);
  202. if (input.val().length > 1) {
  203. $('#loading').show();
  204. $('#closeButton').hide();
  205.  
  206. var searchInt = window.setTimeout(
  207. function() {
  208. $('#pagination').hide();
  209. var q = encodeURIComponent(input.val());
  210. $.ajax({
  211. url: "/php/liveSearch.php?t=g&q=" + q,
  212. success: function(html) {
  213. window.location.hash = '#!' + q;
  214. $('#game_list').html(html);
  215. $('#loading').hide();
  216. $('#closeButton').show();
  217. }
  218. });
  219. }, 500);
  220. } else {
  221. $('#loading').hide();
  222. }
  223. });
  224. var query = decodeURIComponent(window.location.hash.replace('#!', ''));
  225.  
  226. if (query.length > 0) {
  227. $('#searchGames').val(query).keyup();
  228. }
  229. }
  230.  
  231. // TODO barokai: integrate other func in psnp
  232. var psnp = {
  233. id: $('div.user-menu a.dropdown-toggle span').text(),
  234. _gamesTable: $('table#gamesTable'), // game table on user profile
  235. _gameList: $('table#game_list'),
  236. _profileBar: $('div.profile-bar')
  237. };
  238. // add percentage on mouseover or integrate into game row
  239. // add mouse over information like percentage (last row), last played if available etc.
  240. // thanks to dernop (again) - http://psnprofiles.com/forums/topic/35583-add-possibility-to-hide-earned-trophies-in-guides-with-userscript/#entry932561
  241. $(function() {
  242. // initialize psnp page/DOM information
  243.  
  244. // psnp properties
  245. $.extend(psnp, {
  246. isProfile: psnp._gamesTable[0] !== undefined && psnp._profileBar[0] !== undefined,
  247. isOwnProfile: $(psnp._profileBar).find('div.info').text().indexOf(psnp.id) > -1,
  248. isGameList: psnp._gameList.length == 1,
  249. myGames: JSON.parse(localStorage.getItem('_mygames')) || {}
  250. });
  251.  
  252. psnp.updateMyGames = function(games) {
  253. var count = 0;
  254. $.each(games, function(i, e) {
  255. psnp.myGames[e.id] = e;
  256. count++;
  257. });
  258. localStorage.setItem('_mygames', JSON.stringify(psnp.myGames));
  259. console.log(count + " games added/updated to localstorage.");
  260. };
  261.  
  262. // PROFILE / GAME LIST
  263. psnp.gameList = (function() {
  264. if (!psnp.isProfile)
  265. return undefined;
  266. var _games = [];
  267. // register mutationobserver for gameList to handle 'load-on-scroll'
  268. var obs = new MutationObserver(function(mutations) {
  269. parseGames(); // just re-parse all games if list has changed.
  270. });
  271. obs.observe(psnp._gamesTable[0], {
  272. childList: true,
  273. subtree: true
  274. });
  275.  
  276. // parse PSNP games table (id, name, completion, # of trophies)
  277. function parseGames() {
  278. _games = [];
  279. psnp._gamesTable.find('tr:has(a.bold)').each(function(i, row) {
  280. var title = $(row).find('a.bold')[0];
  281. var game = {
  282. id: title.href.match(/\/trophies\/([^\/]+)/)[1],
  283. name: title.innerText,
  284. progress: $(row).find('div.progress_outer span').text(),
  285. completed: $(row).hasClass('completed'),
  286. platinum: $(row).hasClass('platinum'),
  287. gold: $(row).find('li.gold').text(),
  288. silver: $(row).find('li.silver').text(),
  289. bronze: $(row).find('li.bronze').text(),
  290. };
  291.  
  292. if (!psnp.isOwnProfile && psnp.myGames[game.id]) {
  293. var img = $(row).find('img.trophy_image');
  294. img.removeClass('no-border').addClass('earned'); // mark owned games
  295. }
  296. _games.push(game);
  297. });
  298.  
  299. // if this is our own profile, save the games list.
  300. if (psnp.isOwnProfile) {
  301. psnp.updateMyGames(_games);
  302. }
  303. }
  304.  
  305. // parse game list initially
  306. parseGames();
  307.  
  308. return {
  309. games: _games
  310. };
  311. })();
  312.  
  313. // #GAMELIST# global list of games (psnprofiles.com/games)
  314.  
  315. // Mark owned game in "Games" list
  316. if (psnp.isGameList) {
  317. psnp._gameList.find('tr:has(a.bold)').each(function(i, row) {
  318. var title = $(row).find('a.bold')[0];
  319. var id = title.href.match(/\/trophies\/([^\/"]+)/)[1];
  320. var img = $(row).find('img.trophy_image');
  321.  
  322. // mark owned games
  323. if (psnp.myGames[id]) {
  324. if (psnp.myGames[id].platinum)
  325. $(row).addClass('platinum'); // add platinum row style
  326. if (psnp.myGames[id].completed)
  327. $(row).addClass('completed'); // add completed row style
  328.  
  329. img.removeClass('no-border').addClass('earned');
  330. if (psnp.myGames[id].progress != '100%')
  331. img.css('border-color', 'yellow'); //
  332.  
  333. // add completion percentage
  334. var avgProgress = $(row).children('td:eq(4)').find('span.typo-top');
  335. console.log(avgProgress);
  336. avgProgress.text(psnp.myGames[id].progress + " / " + avgProgress.text());
  337.  
  338. }
  339. });
  340. }
  341. });
  342.  
  343. /* Games page enhancements end -----------------------------------------------*/
  344.  
  345. // helperfunction to determine if the url matches a certain segment
  346. function matchesUrl(urlSegment) {
  347. return document.location.pathname.indexOf(urlSegment) === 0;
  348. }
  349.  
  350. /* -------------------------------------------------------------------------- */
  351. /* Apply enhancements to correct pages -------------------------------------- */
  352. /* -------------------------------------------------------------------------- */
  353.  
  354. // add toggle button functionality to all guides (if any earned trophies were found
  355. //matchesUrl("/guide/") && addToggleTypeButton(); //TODO: re-enable after fix
  356. matchesUrl("/guide/") && $('.earned > a').length > 0 && addToggleEarnedButton();
  357. // add searchFix to games page
  358. matchesUrl("/games") && gameSearchFix();
  359. // add update button to navigation on all psnprofile pages (if logged in)
  360. psnp.id && addUpdateButton();
  361. // add only on profile pages and others where a sort dropdown is
  362. matchesUrl("/" + psnp.id) && addSortByRank();
  363.  
  364. // http://stackoverflow.com/a/27363569
  365. (function() {
  366. var origOpen = XMLHttpRequest.prototype.open;
  367.  
  368. XMLHttpRequest.prototype.open = function() {
  369. // console.log('request started!');
  370. this.addEventListener('load', function() {
  371. if (~this.responseURL.indexOf("status?user=")){
  372. // console.log('request completed!');
  373. //console.log(this.readyState); //will always be 4 (ajax is completed successfully)
  374. //console.log(this.responseText); //whatever the response was
  375. var obj = jQuery.parseJSON(this.responseText);
  376. var requestName = this.responseURL.substr(this.responseURL.lastIndexOf('/') + 1).replace(psnp.id, "");
  377. var status = obj.status.replace(psnp.id, "");
  378. if(requestName == "status?user=" && status === " has been updated"){
  379. window.location.href = "https://psnprofiles.com/" + psnp.id;
  380. }
  381. }
  382. });
  383.  
  384. origOpen.apply(this, arguments);
  385. };
  386. })();