Steam, Card sets viewer

Happy trading 1:1 card sets

当前为 2018-12-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Steam, Card sets viewer
  3. // @name:ja Steam, Card sets viewer
  4. // @namespace http://tampermonkey.net/
  5. // @version 1.1.1
  6. // @description Happy trading 1:1 card sets
  7. // @description:ja Happy trading 1:1 card sets
  8. // @author You
  9. // @require http://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
  10. // @match https://steamcommunity.com/tradeoffer/*
  11. // @grant GM.xmlHttpRequest
  12. // @runat document-end
  13. // @nowrap
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. var $ = jQuery.noConflict();
  20. var GetBadgeInformationUrl = "https://www.steamcardexchange.net/api/request.php?GetBadgePrices_Member";
  21. var CacheKey = "BadgeInformatinCache";
  22. var MaxBadgeLevel = 5;
  23.  
  24. function escapeHtml (string) {
  25. if(typeof string !== 'string') {
  26. return string;
  27. }
  28. return string.replace(/[&'`"<>]/g, function(match) {
  29. return {
  30. '&': '&amp;',
  31. "'": '&#x27;',
  32. '`': '&#x60;',
  33. '"': '&quot;',
  34. '<': '&lt;',
  35. '>': '&gt;',
  36. }[match]
  37. });
  38. }
  39.  
  40. function functionToMultilineText(func) {
  41. return func.toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1];
  42. }
  43.  
  44. function createSetsObjectFromInventory (user) {
  45. var sets = {};
  46. var rgInventory = user.rgContexts[753][6].inventory.rgInventory;
  47. for (var instanceid in rgInventory) {
  48. var item = rgInventory[instanceid];
  49.  
  50. // Check whether item type is card
  51. var isCard = false;
  52. for (var i = 0; i < item.tags.length; i++) {
  53. if (item.tags[i].category == "item_class") {
  54. // item_class_2 is type of trading card
  55. if (item.tags[i].internal_name == "item_class_2") {
  56. isCard = true;
  57. }
  58. break;
  59. }
  60. }
  61. if (!isCard) continue;
  62.  
  63. if (!sets[item.market_fee_app]) {
  64. sets[item.market_fee_app] = {
  65. appId: item.market_fee_app,
  66. cardsInSet: -1,
  67. items: {}
  68. };
  69. }
  70. if (!sets[item.market_fee_app].items[item.market_hash_name]) {
  71. sets[item.market_fee_app].items[item.market_hash_name] = {
  72. hash: item.market_hash_name,
  73. quantity: 1,
  74. instances: [instanceid],
  75. };
  76. } else {
  77. sets[item.market_fee_app].items[item.market_hash_name].quantity++;
  78. sets[item.market_fee_app].items[item.market_hash_name].instances.push(instanceid);
  79. }
  80. }
  81.  
  82. return sets;
  83. }
  84.  
  85. function isValidSteamInventory() {
  86. var errorUser;
  87. function checkIsLoaded(user) {
  88. if (!user) throw "Error: Not found {0} user object".replace("{0}", errorUser);
  89. var inv = user.rgContexts[753][6].inventory;
  90. if (!inv) throw "Error: {0} Inventory is not found".replace("{0}", errorUser);
  91. if (!inv.initialized) throw "Error: {0} Inventory is unloaded".replace("{0}", errorUser);
  92. if (inv.appid != "753") throw "Error: {0} Inventory isn't Steam Inventory".replace("{0}", errorUser);
  93. if (!inv.rgInventory) throw "Error: {0} rgInventory is unloaded".replace("{0}", errorUser);
  94. }
  95.  
  96. errorUser = "Your";
  97. checkIsLoaded(UserYou);
  98. errorUser = "Partners";
  99. checkIsLoaded(UserThem);
  100.  
  101. console.log("Both Inventory are loaded");
  102. return true;
  103. }
  104.  
  105. function loadBadgeInformation() {
  106. return new Promise(function (resolve, reject) {
  107. GM.xmlHttpRequest({
  108. url: GetBadgeInformationUrl,
  109. method: "GET",
  110. onerror: function () {
  111. reject("Couldn't get badge information. You need to log in to steamcardexchange.net.");
  112. },
  113. onload: function (xhr) {
  114. var badges = JSON.parse(xhr.responseText);
  115. localStorage[CacheKey] = xhr.responseText;
  116. resolve(badges);
  117. }
  118. });
  119. });
  120. }
  121.  
  122. function applyBadgeInformationToSetsObject(badges, sets, isSelfInventory, isExtraOnly) {
  123. var fee, set;
  124. var data = badges.data;
  125.  
  126. // Add badge information to sets variable
  127. for (var i = 0; i < data.length; i++) {
  128. set = sets[data[i][0][0]];
  129. if (!set) continue;
  130.  
  131. // Appid (market_fee_app) have been already added
  132. // data[i][0][0]
  133. set.title = data[i][0][1];
  134. set.cardsInSet = data[i][1];
  135. set.badgeValue = data[i][2];
  136. set.yourLevel = parseInt(data[i][3]);
  137. // Updated timestamp isn't necessary
  138. // data[i][4]
  139. set.fullSetQuantity = 0;
  140. set.hasFullSet = false;
  141. }
  142. // Count complete card sets
  143. for (fee in sets) {
  144. set = sets[fee];
  145.  
  146. var totalCards = 0;
  147. var cardsCount = 0;
  148. var minQty = Number.MAX_VALUE;
  149.  
  150. for (var hash in set.items) {
  151. var item = set.items[hash];
  152. minQty = Math.min(minQty, item.quantity);
  153. cardsCount++;
  154. totalCards += item.quantity;
  155. }
  156. set.totalCards = totalCards;
  157. if (set.cardsInSet > 0 && set.cardsInSet == cardsCount) {
  158. set.hasFullSet = true;
  159. set.fullSetQuantity = minQty;
  160. if (isSelfInventory) {
  161. var extraQuantity = set.fullSetQuantity - (MaxBadgeLevel - set.yourLevel);
  162. set.extraQuantity = extraQuantity > 0 ? extraQuantity : 0;
  163. set.necessaryQuantity = 0;
  164. } else {
  165. set.extraQuantity = 0;
  166. set.necessaryQuantity = Math.min(MaxBadgeLevel - set.yourLevel, set.fullSetQuantity);
  167. }
  168. } else {
  169. set.hasFullSet = false;
  170. set.fullSetQuantity = 0;
  171. set.extraQuantity = 0;
  172. set.necessaryQuantity = 0;
  173. }
  174. }
  175.  
  176. var displayList = [];
  177. for (fee in sets) {
  178. set = sets[fee];
  179. if (!set.hasFullSet) continue;
  180. if (isExtraOnly) {
  181. if (isSelfInventory && set.extraQuantity <= 0) continue;
  182. if (!isSelfInventory && set.necessaryQuantity <= 0) continue;
  183. }
  184.  
  185. displayList.push(set);
  186. }
  187.  
  188. // sort by title
  189. displayList.sort(function (a, b) {
  190. return a.title > b.title ? 1 : -1;
  191. });
  192.  
  193. return displayList;
  194. }
  195.  
  196. function buildList(displayList, owner, isExtraOnly) {
  197. var isYourInventory = owner.strSteamId == UserYou.steamId;
  198. var set, fee;
  199.  
  200. var textBuilder = "";
  201. var markdownBuilder = "";
  202. var htmlBuilder = "";
  203. var steamBuilder = "";
  204.  
  205. for (var k = 0; k < displayList.length; k++) {
  206. set = displayList[k];
  207. var qty = set.fullSetQuantity;
  208. if (isExtraOnly) {
  209. qty = isYourInventory ? set.extraQuantity : set.necessaryQuantity;
  210. }
  211.  
  212. var yourBadgeUrl = UserYou.strProfileURL + "/gamecards/" + set.appId + "/";
  213. var theirBadgeUrl = UserThem.strProfileURL + "/gamecards/" + set.appId + "/";
  214. var perValue = Math.round(parseFloat(set.badgeValue.replace("$", "")) / set.cardsInSet * 1000) / 1000;
  215.  
  216. // Add content as text to pre tag so don't need to html-escape
  217. textBuilder += "{qty}x {title}\n"
  218. .replace("{qty}", qty)
  219. .replace("{title}", set.title);
  220. // Add content as text to pre tag so don't need to html-escape
  221. // but need to escape charactors that is used by markdown
  222. markdownBuilder += "{qty}x [{title}]({url}) ({cards})\n"
  223. .replace("{qty}", qty)
  224. .replace("{title}", set.title.replace("[", "&#91;").replace("]", "&#93;"))
  225. .replace("{url}", yourBadgeUrl)
  226. .replace("{cards}", set.cardsInSet);
  227. // Add content as text to pre tag so don't need to html-escape
  228. // but need to escape charactors that is used by steam code
  229. steamBuilder += "{qty}x [url={url}]{title}[/url]\n"
  230. .replace("{qty}", qty)
  231. .replace("{title}", set.title.replace("[", "&#91;").replace("]", "&#93;"))
  232. .replace("{url}", yourBadgeUrl);
  233. // Append content as html to body so need to html-escape variables
  234. htmlBuilder += functionToMultilineText(function () {/*
  235. <div>
  236. <button data-fee='{fee}' class='AddSetToTradeButton'>Add to trade</button>
  237. {qty}x <a href='{yurl}' target='_blank'>{title}</a> (<a href='{turl}' target='_blank'>partners</a>)
  238. <span>{cards} as {value} / ${pervalue} each</span>
  239. </div>
  240. */})
  241. .replace("{qty}", qty)
  242. .replace("{title}", escapeHtml(set.title))
  243. .replace("{yurl}", yourBadgeUrl)
  244. .replace("{turl}", theirBadgeUrl)
  245. .replace("{fee}", set.appId)
  246. .replace("{cards}", set.cardsInSet)
  247. .replace("{value}", set.badgeValue)
  248. .replace("{pervalue}", perValue);
  249. }
  250.  
  251. return $("<div />")
  252. .append($("<pre />").addClass("SetListText").text(textBuilder))
  253. .append($("<pre />").addClass("SetListMarkdown").text(markdownBuilder))
  254. .append($("<pre />").addClass("SetListSteamCode").text(steamBuilder))
  255. .append($("<div />").addClass("SetListHtml").append(htmlBuilder));
  256. }
  257.  
  258. var lastDisplayedType;
  259.  
  260. async function main() {
  261. console.log("main()");
  262. var isExtraOnly = $("#DisplayExtraOnlyCheckbox")[0].checked;
  263.  
  264. var yours = createSetsObjectFromInventory(UserYou);
  265. var theirs = createSetsObjectFromInventory(UserThem);
  266.  
  267. console.log("Users:", yours, theirs);
  268.  
  269. var badges;
  270. try {
  271. badges = JSON.parse(localStorage[CacheKey]);
  272. } catch (error) {
  273. badges = null;
  274. }
  275. if (!badges) {
  276. badges = await loadBadgeInformation();
  277. }
  278.  
  279. console.log("Badges:", badges);
  280.  
  281. var yourList = applyBadgeInformationToSetsObject(badges, yours, true, isExtraOnly);
  282. var theirList = applyBadgeInformationToSetsObject(badges, theirs, false, isExtraOnly);
  283.  
  284. console.log("Your list:", yourList, yours);
  285. console.log("Their list:", theirList, theirs);
  286.  
  287. var $yourList = buildList(yourList, isExtraOnly);
  288. var $theirList = buildList(theirList, isExtraOnly);
  289.  
  290. console.log("$DisplayList:", $yourList, $theirList);
  291.  
  292. $("#SetListContainer, .CardsInSet").remove();
  293. $("<div />")
  294. .append("<div><a class='SwitchSetList'>Html</a><a class='SwitchSetList'>Text</a><a class='SwitchSetList'>Markdown</a><a class='SwitchSetList'>SteamCode</a><a class='CloseSetList'>Close</a></div>")
  295. .append($yourList.attr({ id: "YoursDisplayList" }))
  296. .append($theirList.attr({ id: "TheirsDisplayList" }))
  297. .attr("id", "SetListContainer")
  298. .addClass(lastDisplayedType || "VisibleHtml")
  299. .appendTo("body");
  300.  
  301. $(".CloseSetList").click(function (ev){
  302. ev.preventDefault();
  303. ev.stopPropagation();
  304.  
  305. $("#SetListContainer").remove();
  306.  
  307. $("#trade_area .item").each(function () {
  308. var fee = this.rgItem.market_fee_app;
  309. for (var i = 0; i < badges.data.length; i++) {
  310. if (badges.data[i][0][0] == fee) {
  311. var cardsInSet = badges.data[i][1];
  312. $("<div />")
  313. .addClass("CardsInSet")
  314. .text(cardsInSet)
  315. .appendTo(this.rgItem.element);
  316. break;
  317. }
  318. }
  319. });
  320. });
  321.  
  322. $(".SwitchSetList").click(function (ev) {
  323. ev.preventDefault();
  324. ev.stopPropagation();
  325.  
  326. $("#SetListContainer")
  327. .removeClass("VisibleText")
  328. .removeClass("VisibleMarkdown")
  329. .removeClass("VisibleHtml")
  330. .removeClass("VisibleSteamCode")
  331. .addClass("Visible" + $(this).text());
  332.  
  333. lastDisplayedType = "Visible" + $(this).text();
  334. });
  335.  
  336. $(".AddSetToTradeButton").click(function (ev) {
  337. ev.preventDefault();
  338. ev.stopPropagation();
  339.  
  340. var isSelfInventory = $(this).parents("#YoursDisplayList").length == 1;
  341. var fee = $(this).attr("data-fee");
  342. var targetSet = isSelfInventory ? yours[fee] : theirs[fee];
  343. var targets = [];
  344. for (var hash in targetSet.items) {
  345. var instances = targetSet.items[hash].instances;
  346. for (var i = 0; i < instances.length; i++) {
  347. var $c = $((isSelfInventory ? "#your_slots" : "#their_slots") + " #item753_6_" + instances[i]);
  348. if ($c.length == 0) {
  349. targets.push(instances[i]);
  350. break;
  351. }
  352. if (i == instances.length - 1) {
  353. alert("Cards aren't enough to add complete set");
  354. return;
  355. }
  356. }
  357. }
  358. for (var n = 0; n < targets.length; n++) {
  359. MoveItemToTrade($("#item753_6_" + targets[n])[0]);
  360. }
  361. });
  362. }
  363.  
  364. var $controllerContainer = $("<div />")
  365. .css({ borderBottom: "solid 1px #fff3" })
  366. .append("<div>Steam, Card sets viewer</div>")
  367. .appendTo("#inventory_box");
  368.  
  369. $("<button />")
  370. .append("<span>List card sets</span>")
  371. .addClass("btn_darkblue_white_innerfade btn_small new_trade_offer_btn")
  372. .css({ margin: "10px 12px" })
  373. .click(function () {
  374. try {
  375. if (isValidSteamInventory()){
  376. main();
  377. }
  378. } catch (error) {
  379. alert(error);
  380. }
  381. })
  382. .appendTo($controllerContainer);
  383.  
  384. $("<button />")
  385. .append("<span>Clear badge cache</span>")
  386. .addClass("btn_darkblue_white_innerfade btn_small new_trade_offer_btn")
  387. .css({ margin: "10px 12px" })
  388. .click(function () {
  389. delete localStorage[CacheKey];
  390. })
  391. .appendTo($controllerContainer);
  392.  
  393. $("<input />").attr({ type: "checkbox", id: "DisplayExtraOnlyCheckbox" }).appendTo($controllerContainer);
  394. $("<label />").attr({ for: "DisplayExtraOnlyCheckbox" }).text("Extra/Necessary only").appendTo($controllerContainer);
  395.  
  396. $("<style />").text(functionToMultilineText(function () {/*
  397. #SetListContainer .SetListText,
  398. #SetListContainer .SetListMarkdown,
  399. #SetListContainer .SetListSteamCode,
  400. #SetListContainer .SetListHtml {
  401. display: none;
  402. }
  403.  
  404. #SetListContainer.VisibleText .SetListText,
  405. #SetListContainer.VisibleMarkdown .SetListMarkdown,
  406. #SetListContainer.VisibleSteamCode .SetListSteamCode,
  407. #SetListContainer.VisibleHtml .SetListHtml {
  408. display: block;
  409. }
  410. #SetListContainer {
  411. position: fixed;
  412. z-index: 10000;
  413. top: 0;
  414. right: 0;
  415. bottom: 0;
  416. left: 0;
  417. background: #000000dd;
  418. overflow-y: scroll;
  419. padding: 24px 40px;
  420. }
  421. #SetListContainer pre {
  422. white-space: pre-wrap;
  423. word-break: break-all;
  424. }
  425. #SetListContainer > div {
  426. margin-bottom: 24px;
  427. }
  428. #YoursDisplayList, #TheirsDisplayList {
  429. position: relative;
  430. width: 48%;
  431. float: left;
  432. }
  433. #YoursDisplayList::before, #TheirsDisplayList::before {
  434. display:block;
  435. position: absolute;
  436. top: -20px;
  437. font-size: 51px;
  438. color: #ff74;
  439. z-index: -1;
  440. }
  441. #YoursDisplayList::before {
  442. content: "Your's";
  443. }
  444. #TheirsDisplayList::before {
  445. content: "Partner's";
  446. }
  447. #TheirsDisplayList::before {
  448. display: block;
  449. break: all;
  450. content: "",
  451. }
  452. .SwitchSetList {
  453. margin-right: 8px;
  454. }
  455. .CardsInSet {
  456. position: absolute;
  457. font-size: 24px;
  458. color: #ff7a;
  459. z-index: 100;
  460. pointer-events: none;
  461. top: 0;
  462. left: 0;
  463. text-shadow: 1px 1px #000;
  464. }
  465. */})).appendTo("head");
  466.  
  467. // Your code here...
  468. // .toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1];
  469. })();