Advertisement
Guest User

Untitled

a guest
May 25th, 2015
405
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.45 KB | None | 0 0
  1. // ==UserScript==
  2. // @name Steam market seller
  3. // @namespace https://github.com/tboothman/steam-market-seller
  4. // @description Quickly sell items on steam market
  5. // @version 0.7
  6. // @include http://steamcommunity.com/id/*/inventory*
  7. // @include http://steamcommunity.com/profiles/*/inventory*
  8. // @require https://raw.github.com/caolan/async/master/lib/async.js
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function($, async, g_rgAppContextData, g_strInventoryLoadURL, g_rgWalletInfo) {
  13.  
  14. function SteamMarket(appContext, inventoryUrl, walletInfo) {
  15. this.appContext = appContext;
  16. this.inventoryUrl = inventoryUrl;
  17. this.walletInfo = walletInfo;
  18. }
  19.  
  20. // Gets all items in your inventory for a game
  21. // e.g.
  22. // [: { // An item
  23. // id: 1000,
  24. // market_name: "Bloodstone of the Ancestor",
  25. // ....
  26. // }
  27. // ]
  28. //
  29. // Item:
  30. //{"id":"60967810",
  31. // "classid":"171856304",
  32. // "instanceid":"256346122",
  33. // "amount":"1",
  34. // "pos":5,
  35. // "appid":"753",
  36. // "icon_url":"hARaDSYycBddc2R60GxSGDxIkLxiQn5JmLy_bHmWA7dZDHTtcWUnD_Srfoj0TQCLLRODrTUIMlueveFte5cJr00FeKRtJSMM8PdzzfpTH48wAdb4bV1mCsqtt3V2nFOKXgtu9mZgBRL67HKf_kAbwD1Rhas9UGNY1O7sNynAVuwbAy_9aX58Ufi8eI6jGwyHMFjd9WFdMlvbv-o4e8xAqkIFO_tnOSAF69tyhvpECttnXoCtbF1oDdrs7DF7z17iSVMuqDx8dQb6sC-Po0AL32xa3fVrDmcDwqmwZjyPU-s=",
  37. // "icon_url_large":"hARaDSYycBddc2R60GxSGDxIkLxiQn5JmLy_bHmWA7dZDHTtcWUnD_Srfoj0TQCLLRODrTUIMlueveFte5cJr00FeKRtJSMM8PdzzfpTH48wAdb4bV1mCsqtt3V2nFOKXgtu9mZgBRL67HKf_kAbwD1Rhas9UGNY1O7sNynAVuwbAy_9aX58Ufi8eI6jGwyHMFjd9WFdMlvbv-o4e8xAqkIFO_tnOSAF69tyhvpECttnXoCtbF1oDdrs7DF7z17iSVMuqDx8dQb6sC-Po0AL32xa3fVrDmcDwqmwZjyPU-s=",
  38. // "icon_drag_url":"",
  39. // "name":"Prison Architect",
  40. // "market_hash_name":"245070-Prison Architect",
  41. // "market_name":"Prison Architect",
  42. // "name_color":"",
  43. // "background_color":"",
  44. // "type":"Steam Summer Getaway Trading Card",
  45. // "tradable":1,
  46. // "marketable":1,
  47. // "market_fee_app":"233450",
  48. // "descriptions":[{"value":""}],
  49. // "owner_actions":[{"name":"View badge progress","link":"http://steamcommunity.com/my/gamecards/245070/"}],
  50. // "tags":[{"internal_name":"droprate_0","name":"Common","category":"droprate","category_name":"Rarity"},{"internal_name":"app_245070","name":"Steam Summer Getaway","category":"Game","category_name":"Game"},{"internal_name":"item_class_2","name":"Trading Card","category":"item_class","category_name":"Item Type"}],
  51. // "contextid":"6"}
  52. SteamMarket.prototype.getInventory = function(gameId, callback/*(error, inventory)*/) {
  53. var self = this;
  54. var game = this.getGames()[gameId];
  55. var contextId;
  56. var tasks = {};
  57.  
  58. // Build the requests for each inventory context as tasks for async
  59. for (contextId in game.rgContexts) {
  60. tasks[contextId] = (function(contextId) {
  61. return function(next) {
  62. $.get(self.inventoryUrl + gameId + '/' + contextId + '/', function(data) {
  63. if (!data && !data.success) {
  64. return next(true);
  65. }
  66.  
  67. next(null, data);
  68. }, 'json');
  69. }
  70. })(contextId);
  71. }
  72.  
  73. // Request all the inventories
  74. async.parallel(tasks, function(err, results) {
  75. if (err) {
  76. return callback(err);
  77. }
  78.  
  79. var items = [];
  80.  
  81. for (var id in results) {
  82. if (results[id].rgInventory.length === 0) {
  83. continue;
  84. }
  85. results[id] = denormalizeItems(results[id], id);
  86.  
  87. for (var i in results[id]) {
  88. results[id][i].contextid = id;
  89. items.push(results[id][i]);
  90. }
  91. }
  92.  
  93. callback(null, items);
  94. });
  95. };
  96.  
  97. // Sell an item with a price in pennies
  98. // Price is before fees
  99. SteamMarket.prototype.sellItem = function(item, price, callback/*err, data*/) {
  100. var sessionId = readCookie('sessionid');
  101. $.ajax({
  102. type: "POST",
  103. url: 'https://steamcommunity.com/market/sellitem/',
  104. data: {
  105. sessionid: sessionId,
  106. appid: item.appid,
  107. contextid: item.contextid,
  108. assetid: item.id,
  109. amount: 1,
  110. price: price
  111. },
  112. success: function(data) {
  113. callback(null, data);
  114. },
  115. crossDomain: true,
  116. xhrFields: {withCredentials: true},
  117. dataType: 'json'
  118. });
  119. };
  120.  
  121. SteamMarket.prototype.getGames = function() {
  122. return this.appContext;
  123. };
  124.  
  125. // Get the price history for an item
  126. // PriceHistory is an array of prices in the form [data, price, number sold]
  127. // e.g. [["Fri, 19 Jul 2013 01:00:00 +0000",7.30050206184,362]]
  128. // Prices are ordered by oldest to most recent
  129. // Price is inclusive of fees
  130. SteamMarket.prototype.getPriceHistory = function(item, callback/*(err, priceHistory)*/) {
  131. $.get('http://steamcommunity.com/market/pricehistory/', {
  132. appid: item.appid,
  133. market_hash_name: item.market_hash_name
  134. }, function(data) {
  135. if (!data || !data.success || !data.prices) {
  136. return callback(true);
  137. }
  138.  
  139. // Multiply out prices so they're in pennies
  140. for (var i = 0; i < data.prices.length; i++) {
  141. data.prices[i][1] *= 100;
  142. data.prices[i][2] = parseInt(100, 10);
  143. }
  144.  
  145. callback(null, data.prices);
  146. }, 'json');
  147. };
  148.  
  149. // Get the sales listings for this item in the market
  150. // Listings is a list of listing objects.
  151. // converted_price and converted_fee are the useful bits of info
  152. // {"listingid":"2944526023990990820",
  153. // "steamid_lister":"76561198065094510",
  154. // "price":2723,
  155. // "fee":408,
  156. // "steam_fee":136,
  157. // "publisher_fee":272,
  158. // "publisher_fee_app":570,
  159. // "publisher_fee_percent":"0.10000000149011612", (actually a multiplier, not a percentage)
  160. // "currencyid":2005,
  161. // "converted_price":50, (price before fees, amount to pay is price+fee)
  162. // "converted_fee":7, (fee added to price)
  163. // "converted_currencyid":2002,
  164. // "converted_steam_fee":2,
  165. // "converted_publisher_fee":5,
  166. // "asset":{"currency":0,"appid":570,"contextid":"2","id":"1113797403","amount":"1"}
  167. // }
  168. SteamMarket.prototype.getListings = function(item, callback/*err, listings*/) {
  169. $.get('http://steamcommunity.com/market/listings/' + item.appid + '/' + item.market_hash_name, function(page) {
  170. var matches = /var g_rgListingInfo = (.+);/.exec(page);
  171. var listingInfo = JSON.parse(matches[1]);
  172. if (!listingInfo) {
  173. return callback(true);
  174. }
  175. callback(null, listingInfo);
  176. });
  177. };
  178.  
  179. // Calculate the price before fees (seller price) from the buyer price
  180. SteamMarket.prototype.getPriceBeforeFees = function(price, item) {
  181. price = Math.round(price);
  182. // market_fee may or may not exist - this is copied from steam's code
  183. var publisherFee = (item && typeof item.market_fee != 'undefined') ? item.market_fee : this.walletInfo['wallet_publisher_fee_percent_default'];
  184. var feeInfo = CalculateFeeAmount(price, publisherFee, this.walletInfo);
  185.  
  186. return price - feeInfo.fees;
  187. };
  188.  
  189. // Calculate the buyer price from the seller price
  190. SteamMarket.prototype.getPriceIncludingFees = function(price, item) {
  191. price = Math.round(price);
  192. // market_fee may or may not exist - this is copied from steam's code
  193. var publisherFee = (item && typeof item.market_fee != 'undefined') ? item.market_fee : this.walletInfo['wallet_publisher_fee_percent_default'];
  194. var feeInfo = CalculateAmountToSendForDesiredReceivedAmount(price, publisherFee, this.walletInfo);
  195.  
  196. return feeInfo.amount;
  197. };
  198.  
  199. function CalculateFeeAmount(amount, publisherFee, walletInfo) {
  200. if (!walletInfo['wallet_fee'])
  201. return 0;
  202. publisherFee = (typeof publisherFee == 'undefined') ? 0 : publisherFee;
  203. // Since CalculateFeeAmount has a Math.floor, we could be off a cent or two. Let's check:
  204. var iterations = 0; // shouldn't be needed, but included to be sure nothing unforseen causes us to get stuck
  205. var nEstimatedAmountOfWalletFundsReceivedByOtherParty = parseInt((amount - parseInt(walletInfo['wallet_fee_base'])) / (parseFloat(walletInfo['wallet_fee_percent']) + parseFloat(publisherFee) + 1));
  206. var bEverUndershot = false;
  207. var fees = CalculateAmountToSendForDesiredReceivedAmount(nEstimatedAmountOfWalletFundsReceivedByOtherParty, publisherFee, walletInfo);
  208. while (fees.amount != amount && iterations < 10) {
  209. if (fees.amount > amount) {
  210. if (bEverUndershot) {
  211. fees = CalculateAmountToSendForDesiredReceivedAmount(nEstimatedAmountOfWalletFundsReceivedByOtherParty - 1, publisherFee, walletInfo);
  212. fees.steam_fee += (amount - fees.amount);
  213. fees.fees += (amount - fees.amount);
  214. fees.amount = amount;
  215. break;
  216. } else {
  217. nEstimatedAmountOfWalletFundsReceivedByOtherParty--;
  218. }
  219. } else {
  220. bEverUndershot = true;
  221. nEstimatedAmountOfWalletFundsReceivedByOtherParty++;
  222. }
  223. fees = CalculateAmountToSendForDesiredReceivedAmount(nEstimatedAmountOfWalletFundsReceivedByOtherParty, publisherFee, walletInfo);
  224. iterations++;
  225. }
  226. // fees.amount should equal the passed in amount
  227. return fees;
  228. }
  229.  
  230. // Strangely named function, it actually works out the fees and buyer price for a seller price
  231. function CalculateAmountToSendForDesiredReceivedAmount(receivedAmount, publisherFee, walletInfo) {
  232. if (!walletInfo['wallet_fee']) {
  233. return receivedAmount;
  234. }
  235. publisherFee = (typeof publisherFee == 'undefined') ? 0 : publisherFee;
  236. var nSteamFee = parseInt(Math.floor(Math.max(receivedAmount * parseFloat(walletInfo['wallet_fee_percent']), walletInfo['wallet_fee_minimum']) + parseInt(walletInfo['wallet_fee_base'])));
  237. var nPublisherFee = parseInt(Math.floor(publisherFee > 0 ? Math.max(receivedAmount * publisherFee, 1) : 0));
  238. var nAmountToSend = receivedAmount + nSteamFee + nPublisherFee;
  239. return {
  240. steam_fee: nSteamFee,
  241. publisher_fee: nPublisherFee,
  242. fees: nSteamFee + nPublisherFee,
  243. amount: parseInt(nAmountToSend)
  244. };
  245. }
  246.  
  247. // Get a list of items with description data from the inventory json
  248. function denormalizeItems(inventory) {
  249. var id;
  250. var item;
  251. var description;
  252.  
  253. for (id in inventory.rgInventory) {
  254. item = inventory.rgInventory[id];
  255. description = inventory.rgDescriptions[item.classid + '_' + item.instanceid];
  256. for (var key in description) {
  257. item[key] = description[key];
  258. }
  259. }
  260.  
  261. return inventory.rgInventory;
  262. }
  263.  
  264. function readCookie(name) {
  265. var nameEQ = name + "=";
  266. var ca = document.cookie.split(';');
  267. for (var i = 0; i < ca.length; i++) {
  268. var c = ca[i];
  269. while (c.charAt(0) == ' ')
  270. c = c.substring(1, c.length);
  271. if (c.indexOf(nameEQ) == 0)
  272. return decodeURIComponent(c.substring(nameEQ.length, c.length));
  273. }
  274. return null;
  275. }
  276.  
  277.  
  278.  
  279.  
  280.  
  281.  
  282. function log(text) {
  283. logEl.innerHTML += text + '<br/>';
  284. }
  285.  
  286. function clearLog() {
  287. logEl.innerHTML = '';
  288. }
  289.  
  290. function calculateSellPrice(history, listings) {
  291. //return calculateSellPrice_safe(history, listings)-2;
  292. return calculateSellPrice_undercut(history, listings);
  293. //return calculateSellPrice_matchlowest(history, listings);
  294. }
  295.  
  296. function calculateSellPrice_undercut(history, listings) {
  297. // Sell at 1p below the current lowest listing
  298. var firstListing = listings[Object.keys(listings)[0]];
  299. return firstListing.converted_price - 1;
  300. }
  301.  
  302. function calculateSellPrice_matchlowest(history, listings) {
  303. // Sell at the current lowest listing
  304. var firstListing = listings[Object.keys(listings)[0]];
  305. return firstListing.converted_price;
  306. }
  307.  
  308. function calculateSellPrice_safe(history, listings) {
  309. // Fairly safe sell
  310. // Highest average price in the last 24 hours
  311. // Must be at least lowest current listing - 1p
  312.  
  313. var oneDayAgo = Date.now() - (24 * 60 * 60 * 1000);
  314. var highestAverage = 0;
  315. history.forEach(function(historyItem) {
  316. var d = new Date(historyItem[0]);
  317. if (d.getTime() > oneDayAgo) {
  318. if (historyItem[1] > highestAverage) {
  319. highestAverage = historyItem[1];
  320. }
  321. }
  322. });
  323.  
  324. if (!highestAverage) {
  325. return 0;
  326. }
  327.  
  328. if (Object.keys(listings).length === 0) {
  329. return 0;
  330. }
  331.  
  332. var firstListing = listings[Object.keys(listings)[0]];
  333.  
  334. if (highestAverage < (firstListing.converted_price + firstListing.converted_fee - 1)) {
  335. return firstListing.converted_price - 1;
  336. } else {
  337. return market.getPriceBeforeFees(highestAverage);
  338. }
  339. }
  340.  
  341. function sellGameItems(appId) {
  342. clearLog();
  343. console.log('Selling Items: ' + Date());
  344. log('Fetching inventory');
  345. market.getInventory(appId, function(err, items) {
  346. if (err) return log('Something went wrong fetching inventory, try again');
  347. sellItems(items);
  348. });
  349. }
  350.  
  351. function sellFilteredItems() {
  352. clearLog();
  353. log('Fetching inventory');
  354. $('.inventory_ctn').each(function() {
  355. var inventory = this;
  356. if (inventory.style.display == 'none') {
  357. return;
  358. }
  359.  
  360. var idsToSell = [];
  361. $(inventory).find('.itemHolder').each(function() {
  362. if (this.style.display == 'none') return;
  363.  
  364. $(this).find('.item').each(function() {
  365. var item = this;
  366. var matches = item.id.match(/_(\-?\d+)$/);
  367. if (matches) {
  368. idsToSell.push(matches[1]);
  369. }
  370. });
  371. });
  372.  
  373. var appId = $('.games_list_tabs .active')[0].hash.replace(/^#/, '');
  374. market.getInventory(appId, function(err, items) {
  375. if (err) return log('Something went wrong fetching inventory, try again');
  376.  
  377. var filteredItems = [];
  378. items.forEach(function(item) {
  379. if (idsToSell.indexOf(item.id) !== -1) {
  380. filteredItems.push(item);
  381. }
  382. });
  383. sellItems(filteredItems);
  384. });
  385. });
  386. }
  387.  
  388. var processingItems = false;
  389.  
  390. function sellItems(items) {
  391. processingItems = true;
  392. var itemQueue = async.queue(function(item, next) {
  393. if (!item.marketable) {
  394. console.log('Skipping: ' + item.name);
  395. next();
  396. return;
  397. }
  398.  
  399. market.getPriceHistory(item, function(err, history) {
  400. if (err) {
  401. console.log('Failed to get price history for ' + item.name);
  402. next();
  403. return;
  404. }
  405. market.getListings(item, function(err, listings) {
  406. if (err) {
  407. console.log('Failed to get listings for ' + item.name);
  408. next();
  409. return;
  410. }
  411. console.log('============================')
  412. console.log(item.name);
  413. console.log('Average sell price, last hour: ' + market.getPriceBeforeFees(history[history.length - 1][1]) + ' (' + history[history.length - 1][1] + ')');
  414. if (Object.keys(listings).length === 0) {
  415. console.log('Not listed for sale');
  416. next();
  417. return;
  418. }
  419. var firstListing = listings[Object.keys(listings)[0]];
  420. console.log('First listing price: ' + firstListing.converted_price + ' (' + (firstListing.converted_price + firstListing.converted_fee) + ')');
  421.  
  422. var sellPrice = calculateSellPrice(history, listings);
  423. console.log('Calculated sell price: ' + sellPrice + ' (' + market.getPriceIncludingFees(sellPrice) + ')');
  424. if (sellPrice > 0) {
  425. sellQueue.push({
  426. item: item,
  427. sellPrice: sellPrice
  428. });
  429. }
  430. next();
  431. });
  432. });
  433. }, 5);
  434.  
  435. itemQueue.drain = function() {
  436. if (sellQueue.length() === 0) {
  437. log('Done');
  438. }
  439. processingItems = false;
  440. };
  441.  
  442. items.forEach(function(item) {
  443. itemQueue.push(item);
  444. });
  445. }
  446.  
  447. var logEl = document.createElement('div');
  448.  
  449. var market = new SteamMarket(g_rgAppContextData, g_strInventoryLoadURL, g_rgWalletInfo);
  450.  
  451. var sellQueue = async.queue(function(task, next) {
  452. market.sellItem(task.item, task.sellPrice, function(err, data) {
  453. if (!err) {
  454. log(task.item.name + ' put up for sale at ' + task.sellPrice + ' (' + market.getPriceIncludingFees(task.sellPrice) + ')');
  455. }
  456. next();
  457. });
  458. }, 1);
  459.  
  460. sellQueue.drain = function() {
  461. if (!processingItems) {
  462. log('Finished putting items up for sale');
  463. }
  464. }
  465.  
  466. $(document).ready(function() {
  467. var button = '<div style="display: inline-block; line-height: 69px; vertical-align: top; margin-left: 15px;"><a class="btn_green_white_innerfade btn_medium_wide sellall"><span>Sell all items</span></a> <a class="btn_green_white_innerfade btn_medium_wide sellvisible"><span>Sell visible items</span></a></div>';
  468. var $button = $(button);
  469. $button.children('.sellall').click(function() {
  470. var appId = $('.games_list_tabs .active')[0].hash.replace(/^#/, '');
  471. sellGameItems(appId);
  472. });
  473.  
  474. setInterval(function(){
  475. var appId = $('.games_list_tabs .active')[0].hash.replace(/^#/, '');
  476. sellGameItems(appId);
  477. }, 5*60*1000);
  478.  
  479. $button.children('.sellvisible').click(sellFilteredItems);
  480.  
  481. $('#inventory_logos')[0].style.height = 'auto';
  482. $('#inventory_applogo').after(logEl);
  483. $('#inventory_applogo').after($button);
  484.  
  485. });
  486.  
  487.  
  488. })(jQuery, async, g_rgAppContextData, g_strInventoryLoadURL, g_rgWalletInfo);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement