Steam Custom Layout, Sorting and Filtering

Allows to custom filter (min/max Price and min Discount) the search results on Steam

  1. // ==UserScript==
  2. // @name Steam Custom Layout, Sorting and Filtering
  3. // @namespace http://null.frisch-live.de/
  4. // @version 0.27
  5. // @description Allows to custom filter (min/max Price and min Discount) the search results on Steam
  6. // @author frisch
  7. // @match http://store.steampowered.com/search/*
  8. // @match http://store.steampowered.com/app/*
  9. // @match http://steamcommunity.com/id/frisch85/wishlist/
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. /*
  14. position: absolute;
  15. top: 0;
  16. right: -102px;
  17. float: right;
  18. width: 100px;
  19. height: 85px;
  20. text-align: center;
  21. vertical-align: middle;
  22. border: 1px solid white;
  23. font-size: 56pt;
  24. margin: 0;
  25. padding: 0;
  26. */
  27. console.log("Initializing Steam Custom Layout and Sorting...");
  28. var jq = document.fExt.jq;
  29.  
  30. if(location.href.indexOf("wishlist") >= 0){
  31. jq("a:contains('Remove')").each(function(){
  32. var jqThis = jq(this);
  33. jqThis.text('X');
  34. jqThis.css('position','absolute');
  35. jqThis.css('top','0');
  36. jqThis.css('right','-102px');
  37. jqThis.css('float','right');
  38. jqThis.css('width','100px');
  39. jqThis.css('height','85px');
  40. jqThis.css('text-align','center');
  41. jqThis.css('vertical-align','middle');
  42. jqThis.css('border','1px solid white');
  43. jqThis.css('font-size','56pt');
  44. jqThis.css('margin','0');
  45. jqThis.css('padding','0');
  46. });
  47. }
  48. else if(location.href.indexOf("search") >= 0) {
  49. var loading = 0;
  50.  
  51. var loadingStep, loadingPages;
  52. var jqSearchResultItems = [];
  53.  
  54. // Custom Elements
  55. var sortContainer = jq("<div id='customSortContainer'>Sort by:</div>");
  56. sortContainer.appendTo("div.searchbar");
  57.  
  58. var srtPrice = jq("<a href='#' id='srtPrice'>Price</a>");
  59. srtPrice.data("sort-type","Price");
  60. srtPrice.appendTo(sortContainer);
  61.  
  62. var srtDiscount = jq("<a href='#' id='srtDiscount'>Discount</a>");
  63. srtDiscount.attr("active","true");
  64. srtDiscount.data("sort-type","Discount");
  65. srtDiscount.addClass("ASC");
  66. srtDiscount.appendTo(sortContainer);
  67.  
  68. var srtDate = jq("<a href='#' id='srtDate'>Release Date</a>");
  69. srtDate.data("sort-type","Date");
  70. srtDate.appendTo(sortContainer);
  71.  
  72. var filterContainer = jq("<div id='customFilter' style='margin: 8px 0;'></div>");
  73. filterContainer.insertBefore(sortContainer);
  74.  
  75. jq("<label for='filterPriceMin' class='filterLabel'>Minimum Price</label><input id='filterPriceMin' type='number' class='floatInput filterInput' value='0.00' /> €").appendTo(filterContainer);
  76. jq("<label for='filterPriceMax' class='filterLabel'>Maximum Price</label><input id='filterPriceMax' type='number' class='floatInput filterInput' value='0.00' /> €").appendTo(filterContainer);
  77. jq("<label for='filterDiscount' class='filterLabel'>Minimum Discount</label><input id='filterDiscount' type='number' class='intInput filterInput' value='0' /> %").appendTo(filterContainer);
  78.  
  79. jq('<button type="submit" class="btnv6_blue_hoverfade btn_small" id="customRefresh" style="float: right;"><span>Refresh</span></button>').appendTo(filterContainer);
  80.  
  81. // Styles
  82. document.fExt.createStyle("#customSortContainer { display: block; width: 90%; padding: 4px; text-align: center; font-size: larger; }");
  83. document.fExt.createStyle("#customSortContainer a { margin: 10px; }");
  84.  
  85. document.fExt.createStyle(".search_name { width: 58% !important; }");
  86. document.fExt.createStyle(".search_capsule { width: 40% !important; }");
  87. document.fExt.createStyle(".search_result_row { width: 267px !important; float: left !important; border: 2px groove rgba(28, 73, 101, 0.5); margin: 2px; }");
  88. document.fExt.createStyle(".search_result_row:hover { border: 2px groove rgba(28, 73, 101, 0.5); margin: 2px; }");
  89. document.fExt.createStyle("#search_result_container { width: 100%; max-width: 100% !important; }");
  90. document.fExt.createStyle(".responsive_search_name_combined { width: 100%; height: 45px; }");
  91. document.fExt.createStyle(".search_price_discount_combined { float: right; margin-right: 10px; width: 59%; }");
  92. document.fExt.createStyle(".search_price { float: right; }");
  93. document.fExt.createStyle(".leftcol.large { width: 1100px !important; }");
  94. document.fExt.createStyle(".filterLabel { width: 110px; float: left; }");
  95. document.fExt.createStyle(".filterInput { width: 100px; float: left; margin-right: 14px; text-align: right; }");
  96. document.fExt.createStyle(".page_content { width: 1400px !important; } ");
  97. document.fExt.createStyle(".search_discount.col span { position: absolute; top: 0; right: 20px; }");
  98. document.fExt.createStyle(".search_review_summary { position: absolute; top: 40px; right: 20px; }");
  99. document.fExt.createStyle(".search_price { position: absolute; top: 0; right: 80px;}");
  100.  
  101. document.fExt.createStyle("#customSortContainer a:after { content: ''; border-left: 5px solid transparent; border-right: 5px solid transparent; bottom: 12px; position: absolute; }");
  102. document.fExt.createStyle("#customSortContainer a.ASC:after { border-top: 12px solid #fff; }");
  103. document.fExt.createStyle("#customSortContainer a.DESC:after { border-bottom: 12px solid #fff; }");
  104.  
  105.  
  106. // Functions
  107. function sort(srtType){
  108. var direction = "ASC";
  109. var modifier = 1;
  110.  
  111. var jqSortElement;
  112. if(srtType !== undefined){
  113. jq("#customSortContainer a").each(function(){
  114. var jqThis = jq(this);
  115. var jqHtml = jqThis.html();
  116.  
  117. if(jqThis.data("sort-type") === srtType){
  118. jqThis.attr("active","true");
  119. direction = jqThis.hasClass("ASC") ? "ASC" : "DESC";
  120. if(direction === "ASC") {
  121. jqThis.removeClass("ASC");
  122. direction = "DESC";
  123. }
  124. else {
  125. jqThis.removeClass("DESC");
  126. direction = "ASC";
  127. }
  128.  
  129. jqSortElement = jqThis;
  130. jqSortElement.addClass(direction);
  131. }
  132. else {
  133. jqThis.removeClass("ASC");
  134. jqThis.removeClass("DESC");
  135. jqThis.removeAttr("active");
  136. }
  137. });
  138. }
  139. else {
  140. jqSortElement = jq("#customSortContainer a[active=true]");
  141. srtType = jqSortElement.data("sort-type");
  142. if(!srtType)
  143. return;
  144. }
  145.  
  146. var container = jq("div#search_result_container");
  147. if(!container || jqSearchResultItems.length === 0)
  148. return;
  149.  
  150. if(direction === "DESC")
  151. modifier = -1;
  152.  
  153. jqSearchResultItems.each(function(itm,ind){ itm.item.prepend(); });
  154. jqSearchResultItems.sort(function(a,b){
  155. var compValA, compValB;
  156. switch(srtType){
  157. case "Price":
  158. return (direction === "DESC") ? a.price > b.price : a.price < b.price;
  159. case "Release Date":
  160. return (direction === "DESC") ? a.releaseDate > b.releaseDate : a.releaseDate < b.releaseDate;
  161. case "Discount":
  162. return (direction === "ASC") ? a.discount > b.discount : a.discount < b.discount;
  163. }
  164. });
  165. jqSearchResultItems.each(function(itm,ind){ itm.item.appendTo("#search_result_container"); });
  166. }
  167.  
  168. function Initialize(){
  169. jq("a.search_result_row").remove();
  170.  
  171. jq("div.search_name").each(function(){
  172. var jqThis = jq(this);
  173. var jqParent = jqThis.parent().parent("a");
  174. var jqAppImg = jqParent.find("div.search_capsule");
  175. jqThis.detach();
  176. jqThis.insertAfter(jqAppImg);
  177. });
  178. //jq("div.search_pagination").clone().insertBefore("div#search_result_container");
  179.  
  180. // http://store.steampowered.com/search/?sort_by=&sort_order=0&category1=998&special_categories=&specials=1&page=pageID
  181. var lastPage = document.fExt.jq("div.search_pagination a:last");
  182. if(lastPage.length === 1 && (lastPage.text() === '>' || lastPage.text() === '&gt;'))
  183. lastPage = lastPage.prev();
  184.  
  185. if(lastPage.length === 1){
  186. var rx = new RegExp(/(page=[0-9]+)/g);
  187. var matches = rx.exec(document.fExt.jq(location)[0].href);
  188. loadingPages = parseInt(lastPage.text());
  189.  
  190. var pageLink = lastPage.attr('href').replace("page=" + loadingPages, "page=PAGENUMBER");
  191.  
  192. jqSearchResultItems.each(function(item,index){
  193. item.item.remove();
  194. });
  195. jqSearchResultItems = [];
  196.  
  197. if(loadingPages > 25)
  198. loadingPages = 25;
  199.  
  200. loadingStep = 0;
  201. document.fExt.message("Loading pages (" + loadingStep + "/" + loadingPages + ")...");
  202. var step = 0;
  203. while(step < loadingPages){
  204. step++;
  205. var link = pageLink.replace("PAGENUMBER", step);
  206. loadPageAsync(link);
  207. }
  208. }
  209. else {
  210. loadingStep = 0;
  211. loadingPages = 1;
  212. document.fExt.message("Loading pages (" + loadingStep + "/" + loadingPages + ")...");
  213. loadPageAsync(window.location.href);
  214. }
  215. }
  216.  
  217. function reInit(){
  218. if(jq("div.search_pagination").length === 2)
  219. setTimeout(function(){ reInit(); }, 500);
  220. else {
  221. jq("#search_result_container").unbind("DOMSubtreeModified");
  222. Initialize();
  223. }
  224. }
  225.  
  226. function loadPageAsync(link){
  227. jq.ajax({
  228. url: link,
  229. type: 'GET',
  230. error: function(data){
  231. loadingStep++;
  232. console.log("Error loading page #" + loadingStep + ": " + data);
  233. },
  234. complete: function(data){
  235. loadingStep++;
  236.  
  237. loading--;
  238. jq(data.responseText).find("a.search_result_row").each(function(){
  239. var jqItem = jq(this);
  240. var game = jqItem.find("span.title").text();
  241. var isUnique = jq.grep(jqSearchResultItems, function(itm){ return itm.game === game; }).length === 0;
  242. jqItem.css("display","none");
  243.  
  244. if(isUnique) {
  245. var itemPrice = parsePrice(jqItem.find(".search_price").html().replace(/.*>/, ''));
  246. var itemDiscount = parseDiscount(jqItem.find(".search_discount span").text());
  247. var itemRelease = jqItem.find("div.search_released").text();
  248.  
  249. if(isNaN(itemPrice))
  250. itemPrice = 0;
  251. if(isNaN(itemDiscount))
  252. itemDiscount = 0;
  253. if(itemRelease)
  254. itemRelease = Date.parse(itemRelease);
  255.  
  256.  
  257. jqItem.appendTo("#search_result_container");
  258.  
  259. jqSearchResultItems.push({
  260. game: game,
  261. item: jqItem,
  262. discount: itemDiscount,
  263. price: itemPrice,
  264. releaseDate: itemRelease,
  265. });
  266. }
  267. });
  268.  
  269. if (loadingStep === loadingPages){
  270. sort();
  271. filter();
  272. document.fExt.popup("Loading completed.");
  273. document.fExt.message(undefined);
  274. }
  275. else document.fExt.message("Loading pages (" + loadingStep + "/" + loadingPages + ")(" + jqSearchResultItems.length + " uniqueItems found)...");
  276. },
  277. });
  278. }
  279.  
  280. function parsePrice(text){
  281. return parseFloat(text.replace("€","").trim().replace(",","."));
  282. }
  283.  
  284. function parseDiscount(text){
  285. return parseInt(text.replace("%","").trim());
  286. }
  287.  
  288. function filter() {
  289. var priceMin = parsePrice(jq("#filterPriceMin").val());
  290. var priceMax = parsePrice(jq("#filterPriceMax").val());
  291. var discountMin = parseDiscount(jq("#filterDiscount").val()) * -1;
  292. var displayingItems = 0;
  293. var totalDisplayingItems = 100;
  294. var exceedingItems = 0;
  295.  
  296. jqSearchResultItems.each(function(item, ind){
  297. var displayItem = true;
  298.  
  299. if(priceMin > 0)
  300. displayItem = displayItem && item.price >= priceMin;
  301. if(priceMax > 0)
  302. displayItem = displayItem && item.price <= priceMax;
  303. if(discountMin < 0)
  304. displayItem = displayItem && item.discount <= discountMin;
  305.  
  306. item.item.css("display", displayingItems < totalDisplayingItems && displayItem ? "block" : "none");
  307. if(displayItem) {
  308. if(displayingItems === totalDisplayingItems)
  309. exceedingItems++;
  310. else
  311. displayingItems++;
  312. }
  313. });
  314.  
  315. if(exceedingItems > 0)
  316. document.fExt.popup("Too many items to display. (" + exceedingItems + " more items)");
  317. }
  318.  
  319. // Events
  320. jq("#srtPrice, #srtDiscount, #srtDate").click(function(e) {
  321. e.preventDefault();
  322. sort(jq(this).data("sort-type"));
  323. return false;
  324. });
  325.  
  326. jq(document).on('click', 'div.search_pagination_right a', function(e){
  327. jq("#search_result_container").bind("DOMSubtreeModified", reInit());
  328. });
  329.  
  330. jq("input.floatInput").keyup(function(e){
  331. if(e.keyCode === 13){
  332. e.preventDefault();
  333. jq(".filterInput").trigger("keyup");
  334. return false;
  335. }
  336. else {
  337. var jqThis = jq(this);
  338. var val = jqThis.val().replace(/[^0-9,.]/g,'');
  339.  
  340. if (val !== jqThis.val())
  341. jqThis.val(val);
  342. }
  343. });
  344.  
  345. jq("input.intInput").keyup(function(e){
  346. if(e.keyCode === 13){
  347. e.preventDefault();
  348. jq(".filterInput").trigger("keyup");
  349. return false;
  350. }
  351. else {
  352. var jqThis = jq(this);
  353. var val = jqThis.val().replace(/[^0-9]/g,'');
  354.  
  355. if (val !== jqThis.val())
  356. jqThis.val(val);
  357. }
  358. });
  359.  
  360. var filterChangeID = 0;
  361. jq(".filterInput").keyup(function(e){
  362. if(e.keyCode === 13)
  363. e.preventDefault();
  364.  
  365. filterChangeID++;
  366. var myChangeID = filterChangeID;
  367. setTimeout(function(){
  368. if (filterChangeID === myChangeID) {
  369. filterChangeID = 0;
  370. filter();
  371. }
  372. }, 1000);
  373. });
  374.  
  375. jq("#customRefresh").click(function(e){
  376. e.preventDefault();
  377. reInit();
  378. return false;
  379. });
  380.  
  381. // Init
  382.  
  383. jq("form#advsearchform").on("submit", function(e){
  384. setTimeout(function(){
  385. Initialize();
  386. }, 500);
  387. });
  388.  
  389. Initialize();
  390. }