Greasy Fork is available in English.

Steam, Card sets viewer

Happy trading 1:1 card sets

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