Steam Wishlist Sorter

Lets you sort your Steam wishlist by comparing two games at a time.

  1. // ==UserScript==
  2. // @name Steam Wishlist Sorter
  3. // @namespace SWS
  4. // @version 1.0.1
  5. // @description Lets you sort your Steam wishlist by comparing two games at a time.
  6. // @author Anxeal
  7. // @license MIT
  8. // @match https://store.steampowered.com/wishlist/*
  9. // @grant GM_addStyle
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @require https://code.jquery.com/jquery-3.3.1.min.js
  13. // ==/UserScript==
  14.  
  15. GM_addStyle(`
  16. .sws-overlay {
  17. display:flex;
  18. flex-direction:column;
  19. justify-content:center;
  20. align-items:center;
  21. position: fixed;
  22. top: 0;
  23. left: 0;
  24. width: 100%;
  25. height: 100%;
  26. background: rgba(0,0,0,.7);
  27. z-index:9999;
  28. }
  29. .sws-close-button {
  30. position:fixed;
  31. top:20px;
  32. right:40px;
  33. font-size:80px;
  34. cursor:pointer;
  35. text-align:center;
  36. }
  37. .sws-choice-button {
  38. box-shadow: 0px 0px 0px 5px #ccc;
  39. margin: 0.5em;
  40. width: 292px;
  41. height: 136px;
  42. cursor: pointer;
  43. transition: transform .3s ease, box-shadow .3s ease;
  44. }
  45. .sws-choice-button:hover {
  46. transform: scale(1.2);
  47. box-shadow: 0px 0px 0px 5px #09c;
  48. }
  49. .sws-choice-button:active {
  50. transform: none;
  51. }
  52. .sws-app-title-text {
  53. font-size: 20px;
  54. line-height: 60px;
  55. }
  56. .sws-sort-button {
  57. display:flex;
  58. justify-content:center;
  59. align-items:center;
  60. margin-left:15px;
  61. }
  62. .sws-progress-outer {
  63. border:5px solid #069;
  64. border-radius: 20px;
  65. background:#999;
  66. width:800px;
  67. height:20px;
  68. margin:30px;
  69. box-shadow: inset 0 0 5px #000;
  70. }
  71. .sws-progress-inner {
  72. border-radius: 10px;
  73. background-image: linear-gradient( -45deg, #09c 25%, #0cf 25%, #0cf 50%, #09c 50%, #09c 75%, #0cf 75%, #0cf );
  74. background-size: 20px 20px;
  75. animation:sws-progress 1s linear 0s infinite;
  76. height:100%;
  77. width:0;
  78. transition: width .5s ease-out;
  79. }
  80. @keyframes sws-progress {
  81. to {
  82. background-position: 0 20px;
  83. }
  84. }
  85. `);
  86.  
  87. (function($, window) {
  88. 'use strict';
  89.  
  90. // Class that does merge sort with manual comparisons from an older project
  91. class ManualSorter {
  92. constructor(array, $leftButton, $rightButton, callback) {
  93. var self = this;
  94. $leftButton.click(function() {
  95. if ($(this).is("[disabled]")) return;
  96. self.compare(-1);
  97. self.sendNext();
  98. });
  99. $rightButton.click(function() {
  100. if ($(this).is('[disabled]')) return;
  101. self.compare(1);
  102. self.sendNext();
  103. });
  104.  
  105. this.arr = this.shuffleArray(array.slice());
  106. this.step = 1;
  107. this.index = 0;
  108. this.done = false;
  109.  
  110. this.compCount = 0;
  111. // approx max comp count
  112. this.maxCompCount = this.arr.length * Math.ceil(Math.log2(this.arr.length));
  113.  
  114. this.cleanVars();
  115. this.callback = callback;
  116. this.sendNext();
  117. }
  118.  
  119. sendNext(){
  120. this.callback(this.getNext());
  121. }
  122.  
  123. cleanVars() {
  124. this.headLeft = 0;
  125. this.headRight = 0;
  126. this.result = [];
  127. }
  128.  
  129. compare(input) {
  130. if (this.done) return;
  131. var rightLimit = Math.min(this.step, this.arr.length-(this.index+1)*this.step);
  132. if (this.headLeft < this.step && this.headRight < rightLimit) {
  133. if (input < 0) {
  134. this.pushLeft();
  135. }
  136. else {
  137. this.pushRight();
  138. }
  139. }
  140. if (!(this.headLeft < this.step && this.headRight < rightLimit)) {
  141. while (this.headLeft < this.step) {
  142. this.pushLeft();
  143. }
  144. while (this.headRight < rightLimit) {
  145. this.pushRight();
  146. }
  147. for (var i = 0; i < this.result.length; i++) {
  148. this.arr[this.index * this.step + i] = this.result[i];
  149. }
  150. this.index += 2;
  151. if ((this.index + 1) * this.step + this.headRight >= this.arr.length) {
  152. this.step *= 2;
  153. this.index = 0;
  154. }
  155. if (this.step >= this.arr.length) {
  156. // We are done sorting
  157. this.done = true;
  158. }
  159. this.cleanVars();
  160. }
  161. }
  162.  
  163. pushLeft() {
  164. this.result.push(this.arr[this.index * this.step + this.headLeft]);
  165. this.headLeft++;
  166. this.compCount++;
  167. }
  168.  
  169. pushRight() {
  170. this.result.push(this.arr[(this.index + 1) * this.step + this.headRight]);
  171. this.headRight++;
  172. this.compCount++;
  173. }
  174.  
  175. getNext() {
  176. if (!this.done) {
  177. return { left: this.arr[this.index * this.step + this.headLeft], right: this.arr[(this.index + 1) * this.step + this.headRight], done: false};
  178. } else {
  179. console.log("[SWS] Done sorting!");
  180. return { result: this.arr, done: true};
  181. }
  182. }
  183.  
  184. shuffleArray(a) {
  185. var j, x, i;
  186. for (i = a.length - 1; i > 0; i--) {
  187. j = Math.floor(Math.random() * (i + 1));
  188. x = a[i];
  189. a[i] = a[j];
  190. a[j] = x;
  191. }
  192. return a;
  193. }
  194.  
  195. serialize() {
  196. return JSON.stringify(this);
  197. }
  198.  
  199. deserialize(json) {
  200. var obj = JSON.parse(json);
  201. console.log(obj);
  202. for(var val in obj) {
  203. this[val] = obj[val];
  204. }
  205. }
  206.  
  207. get progress(){
  208. return this.compCount/this.maxCompCount*100;
  209. }
  210. };
  211.  
  212. var waitForWishlist = $.Deferred();
  213.  
  214. waitForWishlist.then(function(wl){
  215.  
  216. // g_bCanEdit => if wishlist is editable
  217.  
  218. // if it isn't our wishlist, don't bother running
  219. if(!window.g_bCanEdit){
  220. console.log("[SWS] Can't edit wishlist: Stopping.");
  221. return;
  222. }
  223.  
  224. var $overlay = $("<div class='sws-overlay'></div>");
  225. var $closeButton = $("<a class='sws-close-button'>×</a>");
  226. var $appTitleText = $("<div class='sws-app-title-text '></div>");
  227. var $leftButton = $("<div class='sws-choice-button'></div>");
  228. var $rightButton = $leftButton.clone();
  229. var $progress = $("<div class='sws-progress-outer'><div class='sws-progress-inner'></div></div>");
  230.  
  231.  
  232. $(document.body).append($overlay);
  233. $overlay.append($closeButton).append($leftButton).append($appTitleText).append($rightButton).append($progress);
  234. $overlay.hide();
  235.  
  236. $('.sws-choice-button').hover(function(){
  237. $appTitleText.text($(this).attr("data-app-title")).stop().animate({ opacity: 1 }, 200);
  238. }, function(){
  239. $appTitleText.stop().animate({ opacity: 0 }, 200);
  240. }).on("mousedown", function(e){
  241. if(e.which == 2) { // middleclick
  242. window.open('https://store.steampowered.com/app/'+$(this).attr("data-app-id")+'/', '_blank');
  243. }
  244. });
  245.  
  246. // close button behavior
  247. $closeButton.click(function(){
  248. $overlay.fadeOut();
  249. });
  250.  
  251. // main buttons
  252.  
  253. var $sortButton = $("<div class='sws-sort-button'><div class='btnv6_blue_hoverfade btn_medium'><span>Sort!</span></div></div>");
  254. var $saveButton = $sortButton.clone();
  255. var $discardButton = $sortButton.clone();
  256. var $saveProgressButton = $sortButton.clone();
  257. var $loadProgressButton = $sortButton.clone();
  258.  
  259. $sortButton.appendTo(".wishlist_header").children().click(function(){
  260. $overlay.fadeIn();
  261. $discardButton.fadeIn();
  262. $saveProgressButton.fadeIn();
  263. });
  264. $sortButton.hide().fadeIn();
  265.  
  266. $saveButton.hide().children().children().text("Save");
  267. $saveButton.appendTo(".wishlist_header").children().click(function(){
  268. wl.SaveOrder();
  269. location.reload();
  270. });
  271.  
  272. $discardButton.hide().children().children().text("Discard");
  273. $discardButton.appendTo(".wishlist_header").children().click(function(){
  274. location.reload();
  275. });
  276.  
  277. $saveProgressButton.hide().children().children().text("Save Progress");
  278. $saveProgressButton.appendTo(".wishlist_header").children().click(function(){
  279. var data = sorter.serialize();
  280. GM_setValue("sws-sorter-data", data);
  281. alert("Progress Saved!");
  282. });
  283.  
  284. $loadProgressButton.hide().children().children().text("Load Progress");
  285. $loadProgressButton.appendTo(".wishlist_header").children().click(function(){
  286. var data = GM_getValue("sws-sorter-data");
  287. sorter.deserialize(data);
  288. sorter.sendNext();
  289. $sortButton.children().click();
  290. });
  291. if(GM_getValue("sws-sorter-data")) $loadProgressButton.fadeIn();
  292.  
  293. var fadeDuration = 100;
  294. var setChoiceData = function($button, side){
  295. var bgUrl = "url('"+window.g_rgAppInfo[side].capsule+"')";
  296. if($button.attr("data-app-id") == side){
  297. $button.attr("disabled", "disabled")
  298. .delay(2*fadeDuration)
  299. .removeAttr("disabled");
  300. return;
  301. }
  302. $button.attr("disabled", "disabled").fadeOut(fadeDuration, function(){
  303. $button.css("background-image", bgUrl);
  304. $button.attr("data-app-id", side);
  305. $button.attr("data-app-title", window.g_rgAppInfo[side].name);
  306. $button.trigger("mouseenter");
  307. }).fadeIn(fadeDuration, function(){
  308. $button.removeAttr("disabled");
  309. });
  310. }
  311.  
  312.  
  313. var sorter = new ManualSorter(wl.rgAllApps, $leftButton, $rightButton, function(next){
  314. if (next.done) {
  315. wl.rgAllApps = next.result;
  316. wl.Update();
  317. $overlay.fadeOut();
  318. $saveButton.fadeIn();
  319. alert("Done sorting! Please review your wishlist and save or discard.");
  320. } else {
  321. setChoiceData($leftButton, next.left);
  322. setChoiceData($rightButton, next.right);
  323. if(sorter) $progress.children().width((sorter.progress)+"%");
  324. }
  325. });
  326. });
  327.  
  328. var checkWishlist = function(){
  329. if(!window.g_Wishlist || !window.g_Wishlist.rgAllApps){
  330. console.log("[SWS] Waiting for wishlist...");
  331. setTimeout(checkWishlist, 500);
  332. return;
  333. }
  334. console.log("[SWS] Wishlist loaded.");
  335. waitForWishlist.resolve(window.g_Wishlist);
  336. };
  337.  
  338. checkWishlist();
  339.  
  340. })(unsafeWindow.jQuery, unsafeWindow);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址