// ==UserScript== // @name Steam Economy Enhancer // @icon https://upload.wikimedia.org/wikipedia/commons/8/83/Steam_icon_logo.svg // @namespace https://github.com/Nuklon // @author Nuklon // @license MIT // @version 6.9.2 // @description Enhances the Steam Inventory and Steam Market. // @include *://steamcommunity.com/id/*/inventory* // @include *://steamcommunity.com/profiles/*/inventory* // @include *://steamcommunity.com/market* // @include *://steamcommunity.com/tradeoffer* // @require https://code.jquery.com/jquery-3.3.1.min.js // @require https://code.jquery.com/ui/1.12.1/jquery-ui.min.js // @require https://raw.githubusercontent.com/kapetan/jquery-observe/ca67b735bb3ae8d678d1843384ebbe7c02466c61/jquery-observe.js // @require https://cdnjs.cloudflare.com/ajax/libs/paginationjs/2.1.2/pagination.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/async/2.6.0/async.js // @require https://cdnjs.cloudflare.com/ajax/libs/localforage/1.7.1/localforage.min.js // @require https://moment.github.io/luxon/global/luxon.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/list.js/1.5.0/list.js // @require https://raw.githubusercontent.com/rmariuzzo/checkboxes.js/91bec667e9172ceb063df1ecb7505e8ed0bae9ba/src/jquery.checkboxes.js // @grant unsafeWindow // @homepageURL https://github.com/Nuklon/Steam-Economy-Enhancer // @supportURL https://github.com/Nuklon/Steam-Economy-Enhancer/issues // @downloadURL https://raw.githubusercontent.com/Nuklon/Steam-Economy-Enhancer/master/code.user.js // @updateURL https://raw.githubusercontent.com/Nuklon/Steam-Economy-Enhancer/master/code.user.js // ==/UserScript== // jQuery is already added by Steam, force no conflict mode. (function($, async) { $.noConflict(true); var DateTime = luxon.DateTime; const STEAM_INVENTORY_ID = 753; const PAGE_MARKET = 0; const PAGE_MARKET_LISTING = 1; const PAGE_TRADEOFFER = 2; const PAGE_INVENTORY = 3; const COLOR_ERROR = '#8A4243'; const COLOR_SUCCESS = '#407736'; const COLOR_PENDING = '#908F44'; const COLOR_PRICE_FAIR = '#496424'; const COLOR_PRICE_CHEAP = '#837433'; const COLOR_PRICE_EXPENSIVE = '#813030'; const COLOR_PRICE_NOT_CHECKED = '#26566c'; const ERROR_SUCCESS = null; const ERROR_FAILED = 1; const ERROR_DATA = 2; var marketLists = []; var totalNumberOfProcessedQueueItems = 0; var totalNumberOfQueuedItems = 0; var totalPriceWithFeesOnMarket = 0; var totalPriceWithoutFeesOnMarket = 0; var totalScrap = 0; var spinnerBlock = '
 
 
 
 
 
'; var numberOfFailedRequests = 0; var enableConsoleLog = false; var country = typeof unsafeWindow.g_strCountryCode !== 'undefined' ? unsafeWindow.g_strCountryCode : undefined; var isLoggedIn = typeof unsafeWindow.g_rgWalletInfo !== 'undefined' && unsafeWindow.g_rgWalletInfo != null || (typeof unsafeWindow.g_bLoggedIn !== 'undefined' && unsafeWindow.g_bLoggedIn); var currentPage = window.location.href.includes('.com/market') ? (window.location.href.includes('market/listings') ? PAGE_MARKET_LISTING : PAGE_MARKET) : (window.location.href.includes('.com/tradeoffer') ? PAGE_TRADEOFFER : PAGE_INVENTORY); var market = new SteamMarket(unsafeWindow.g_rgAppContextData, typeof unsafeWindow.g_strInventoryLoadURL !== 'undefined' && unsafeWindow.g_strInventoryLoadURL != null ? unsafeWindow.g_strInventoryLoadURL : typeof unsafeWindow.g_strProfileURL !== 'undefined' && unsafeWindow.g_strProfileURL != null ? unsafeWindow.g_strProfileURL + '/inventory/json/' : 'https://steamcommunity.com/my/inventory/json/', isLoggedIn ? unsafeWindow.g_rgWalletInfo : undefined); var currencyId = isLoggedIn && market != null && market.walletInfo != null && market.walletInfo.wallet_currency != null ? market.walletInfo.wallet_currency : 3; var currencySymbol = unsafeWindow.GetCurrencySymbol(unsafeWindow.GetCurrencyCode(currencyId)); function SteamMarket(appContext, inventoryUrl, walletInfo) { this.appContext = appContext; this.inventoryUrl = inventoryUrl; this.walletInfo = walletInfo; this.inventoryUrlBase = inventoryUrl.replace('/inventory/json', ''); if (!this.inventoryUrlBase.endsWith('/')) this.inventoryUrlBase += '/'; } //#region Settings const SETTING_MIN_NORMAL_PRICE = 'SETTING_MIN_NORMAL_PRICE'; const SETTING_MAX_NORMAL_PRICE = 'SETTING_MAX_NORMAL_PRICE'; const SETTING_MIN_FOIL_PRICE = 'SETTING_MIN_FOIL_PRICE'; const SETTING_MAX_FOIL_PRICE = 'SETTING_MAX_FOIL_PRICE'; const SETTING_MIN_MISC_PRICE = 'SETTING_MIN_MISC_PRICE'; const SETTING_MAX_MISC_PRICE = 'SETTING_MAX_MISC_PRICE'; const SETTING_PRICE_OFFSET = 'SETTING_PRICE_OFFSET'; const SETTING_PRICE_MIN_CHECK_PRICE = 'SETTING_PRICE_MIN_CHECK_PRICE'; const SETTING_PRICE_ALGORITHM = 'SETTING_PRICE_ALGORITHM'; const SETTING_PRICE_IGNORE_LOWEST_Q = 'SETTING_PRICE_IGNORE_LOWEST_Q'; const SETTING_PRICE_HISTORY_HOURS = 'SETTING_PRICE_HISTORY_HOURS'; const SETTING_INVENTORY_PRICE_LABELS = 'SETTING_INVENTORY_PRICE_LABELS'; const SETTING_TRADEOFFER_PRICE_LABELS = 'SETTING_TRADEOFFER_PRICE_LABELS'; const SETTING_LAST_CACHE = 'SETTING_LAST_CACHE'; const SETTING_RELIST_AUTOMATICALLY = 'SETTING_RELIST_AUTOMATICALLY'; const SETTING_MARKET_PAGE_COUNT = 'SETTING_MARKET_PAGE_COUNT'; const SETTING_INVENTORY_PRICES = 'SETTING_INVENTORY_PRICES'; var settingDefaults = { SETTING_MIN_NORMAL_PRICE: 0.05, SETTING_MAX_NORMAL_PRICE: 2.50, SETTING_MIN_FOIL_PRICE: 0.15, SETTING_MAX_FOIL_PRICE: 10, SETTING_MIN_MISC_PRICE: 0.05, SETTING_MAX_MISC_PRICE: 10, SETTING_PRICE_OFFSET: 0.00, SETTING_PRICE_MIN_CHECK_PRICE: 0.00, SETTING_PRICE_ALGORITHM: 1, SETTING_PRICE_IGNORE_LOWEST_Q: 1, SETTING_PRICE_HISTORY_HOURS: 12, SETTING_INVENTORY_PRICE_LABELS: 1, SETTING_TRADEOFFER_PRICE_LABELS: 1, SETTING_LAST_CACHE: 0, SETTING_RELIST_AUTOMATICALLY: 0, SETTING_MARKET_PAGE_COUNT: 100 }; function getSettingWithDefault(name) { return getLocalStorageItem(name) || (name in settingDefaults ? settingDefaults[name] : null); } function setSetting(name, value) { setLocalStorageItem(name, value); } //#endregion //#region Storage var storagePersistent = localforage.createInstance({ name: 'see_persistent' }); var storageSession; var currentUrl = new URL(window.location.href); var noCache = currentUrl.searchParams.get('no-cache') != null; // This does not work the same as the 'normal' session storage because opening a new browser session/tab will clear the cache. // For this reason, a rolling cache is used. if (getSessionStorageItem('SESSION') == null || noCache) { var lastCache = getSettingWithDefault(SETTING_LAST_CACHE); if (lastCache > 5) lastCache = 0; setSetting(SETTING_LAST_CACHE, lastCache + 1); storageSession = localforage.createInstance({ name: 'see_session_' + lastCache }); storageSession.clear(); // Clear any previous data. setSessionStorageItem('SESSION', lastCache); } else { storageSession = localforage.createInstance({ name: 'see_session_' + getSessionStorageItem('SESSION') }); } function getLocalStorageItem(name) { try { return localStorage.getItem(name); } catch (e) { return null; } } function setLocalStorageItem(name, value) { try { localStorage.setItem(name, value); return true; } catch (e) { logConsole('Failed to set local storage item ' + name + ', ' + e + '.') return false; } } function getSessionStorageItem(name) { try { return sessionStorage.getItem(name); } catch (e) { return null; } } function setSessionStorageItem(name, value) { try { sessionStorage.setItem(name, value); return true; } catch (e) { logConsole('Failed to set session storage item ' + name + ', ' + e + '.') return false; } } //#endregion //#region Price helpers function getPriceInformationFromItem(item) { var isTradingCard = getIsTradingCard(item); var isFoilTradingCard = getIsFoilTradingCard(item); return getPriceInformation(isTradingCard, isFoilTradingCard); } function getPriceInformation(isTradingCard, isFoilTradingCard) { var maxPrice = 0; var minPrice = 0; if (!isTradingCard) { maxPrice = getSettingWithDefault(SETTING_MAX_MISC_PRICE); minPrice = getSettingWithDefault(SETTING_MIN_MISC_PRICE); } else { maxPrice = isFoilTradingCard ? getSettingWithDefault(SETTING_MAX_FOIL_PRICE) : getSettingWithDefault(SETTING_MAX_NORMAL_PRICE); minPrice = isFoilTradingCard ? getSettingWithDefault(SETTING_MIN_FOIL_PRICE) : getSettingWithDefault(SETTING_MIN_NORMAL_PRICE); } maxPrice = maxPrice * 100.0; minPrice = minPrice * 100.0; var maxPriceBeforeFees = market.getPriceBeforeFees(maxPrice); var minPriceBeforeFees = market.getPriceBeforeFees(minPrice); return { maxPrice, minPrice, maxPriceBeforeFees, minPriceBeforeFees }; } // Calculates the average history price, before the fee. function calculateAverageHistoryPriceBeforeFees(history) { var highest = 0; var total = 0; if (history != null) { // Highest average price in the last xx hours. var timeAgo = Date.now() - (getSettingWithDefault(SETTING_PRICE_HISTORY_HOURS) * 60 * 60 * 1000); history.forEach(function(historyItem) { var d = new Date(historyItem[0]); if (d.getTime() > timeAgo) { highest += historyItem[1] * historyItem[2]; total += historyItem[2]; } }); } if (total == 0) return 0; highest = Math.floor(highest / total); return market.getPriceBeforeFees(highest); } // Calculates the listing price, before the fee. function calculateListingPriceBeforeFees(histogram) { if (typeof histogram === 'undefined' || histogram == null || histogram.lowest_sell_order == null || histogram.sell_order_graph == null) return 0; var listingPrice = market.getPriceBeforeFees(histogram.lowest_sell_order); var shouldIgnoreLowestListingOnLowQuantity = getSettingWithDefault(SETTING_PRICE_IGNORE_LOWEST_Q) == 1; if (shouldIgnoreLowestListingOnLowQuantity && histogram.sell_order_graph.length >= 2) { var listingPrice2ndLowest = market.getPriceBeforeFees(histogram.sell_order_graph[1][0] * 100); if (listingPrice2ndLowest > listingPrice) { var numberOfListingsLowest = histogram.sell_order_graph[0][1]; var numberOfListings2ndLowest = histogram.sell_order_graph[1][1]; var percentageLower = (100 * (numberOfListingsLowest / numberOfListings2ndLowest)); // The percentage should change based on the quantity (for example, 1200 listings vs 5, or 1 vs 25). if (numberOfListings2ndLowest >= 1000 && percentageLower <= 5) { listingPrice = listingPrice2ndLowest; } else if (numberOfListings2ndLowest < 1000 && percentageLower <= 10) { listingPrice = listingPrice2ndLowest; } else if (numberOfListings2ndLowest < 100 && percentageLower <= 15) { listingPrice = listingPrice2ndLowest; } else if (numberOfListings2ndLowest < 50 && percentageLower <= 20) { listingPrice = listingPrice2ndLowest; } else if (numberOfListings2ndLowest < 25 && percentageLower <= 25) { listingPrice = listingPrice2ndLowest; } else if (numberOfListings2ndLowest < 10 && percentageLower <= 30) { listingPrice = listingPrice2ndLowest; } } } return listingPrice; } function calculateBuyOrderPriceBeforeFees(histogram) { if (typeof histogram === 'undefined') return 0; return market.getPriceBeforeFees(histogram.highest_buy_order); } // Calculate the sell price based on the history and listings. // applyOffset specifies whether the price offset should be applied when the listings are used to determine the price. function calculateSellPriceBeforeFees(history, histogram, applyOffset, minPriceBeforeFees, maxPriceBeforeFees) { var historyPrice = calculateAverageHistoryPriceBeforeFees(history); var listingPrice = calculateListingPriceBeforeFees(histogram); var buyPrice = calculateBuyOrderPriceBeforeFees(histogram); var shouldUseAverage = getSettingWithDefault(SETTING_PRICE_ALGORITHM) == 1; var shouldUseBuyOrder = getSettingWithDefault(SETTING_PRICE_ALGORITHM) == 3; // If the highest average price is lower than the first listing, return the offset + that listing. // Otherwise, use the highest average price instead. var calculatedPrice = 0; if (shouldUseBuyOrder && buyPrice !== -2) { calculatedPrice = buyPrice; } else if (historyPrice < listingPrice || !shouldUseAverage) { calculatedPrice = listingPrice; } else { calculatedPrice = historyPrice; } var changedToMax = false; // List for the maximum price if there are no listings yet. if (calculatedPrice == 0) { calculatedPrice = maxPriceBeforeFees; changedToMax = true; } // Apply the offset to the calculated price, but only if the price wasn't changed to the max (as otherwise it's impossible to list for this price). if (!changedToMax && applyOffset) { calculatedPrice = calculatedPrice + (getSettingWithDefault(SETTING_PRICE_OFFSET) * 100); } // Keep our minimum and maximum in mind. calculatedPrice = clamp(calculatedPrice, minPriceBeforeFees, maxPriceBeforeFees); // In case there's a buy order higher than the calculated price. if (typeof histogram !== 'undefined' && histogram != null && histogram.highest_buy_order != null) { var buyOrderPrice = market.getPriceBeforeFees(histogram.highest_buy_order); if (buyOrderPrice > calculatedPrice) calculatedPrice = buyOrderPrice; } return calculatedPrice; } //#endregion //#region Integer helpers function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function getNumberOfDigits(x) { return (Math.log10((x ^ (x >> 31)) - (x >> 31)) | 0) + 1; } function padLeftZero(str, max) { str = str.toString(); return str.length < max ? padLeftZero("0" + str, max) : str; } function replaceNonNumbers(str) { return str.replace(/\D/g, ''); } //#endregion //#region Steam Market // Sell an item with a price in cents. // Price is before fees. SteamMarket.prototype.sellItem = function(item, price, callback /*err, data*/ ) { var sessionId = readCookie('sessionid'); var itemId = item.assetid || item.id; $.ajax({ type: "POST", url: 'https://steamcommunity.com/market/sellitem/', data: { sessionid: sessionId, appid: item.appid, contextid: item.contextid, assetid: itemId, amount: 1, price: price }, success: function(data) { if (data.success === false && isRetryMessage(data.message)) { callback(ERROR_FAILED, data); } else { callback(ERROR_SUCCESS, data); } }, error: function(data) { return callback(ERROR_FAILED, data); }, crossDomain: true, xhrFields: { withCredentials: true }, dataType: 'json' }); }; // Removes an item. // Item is the unique item id. SteamMarket.prototype.removeListing = function(item, callback /*err, data*/ ) { var sessionId = readCookie('sessionid'); $.ajax({ type: "POST", url: window.location.protocol + '//steamcommunity.com/market/removelisting/' + item, data: { sessionid: sessionId }, success: function(data) { callback(ERROR_SUCCESS, data); }, error: function() { return callback(ERROR_FAILED); }, crossDomain: true, xhrFields: { withCredentials: true }, dataType: 'json' }); }; // Get the price history for an item. // // PriceHistory is an array of prices in the form [data, price, number sold]. // Example: [["Fri, 19 Jul 2013 01:00:00 +0000",7.30050206184,362]] // Prices are ordered by oldest to most recent. // Price is inclusive of fees. SteamMarket.prototype.getPriceHistory = function(item, cache, callback) { try { var market_name = getMarketHashName(item); if (market_name == null) { callback(ERROR_FAILED); return; } var appid = item.appid; if (cache) { var storage_hash = 'pricehistory_' + appid + '+' + market_name; storageSession.getItem(storage_hash) .then(function(value) { if (value != null) callback(ERROR_SUCCESS, value, true); else market.getCurrentPriceHistory(appid, market_name, callback); }) .catch(function(error) { market.getCurrentPriceHistory(appid, market_name, callback); }); } else market.getCurrentPriceHistory(appid, market_name, callback); } catch (e) { return callback(ERROR_FAILED); } }; SteamMarket.prototype.getGooValue = function(item, callback) { try { var sessionId = readCookie('sessionid'); $.ajax({ type: "GET", url: this.inventoryUrlBase + 'ajaxgetgoovalue/', data: { sessionid: sessionId, appid: item.market_fee_app, assetid: item.assetid, contextid: item.contextid }, success: function(data) { callback(ERROR_SUCCESS, data); }, error: function(data) { return callback(ERROR_FAILED, data); }, crossDomain: true, xhrFields: { withCredentials: true }, dataType: 'json' }); } catch (e) { return callback(ERROR_FAILED); } //http://steamcommunity.com/auction/ajaxgetgoovalueforitemtype/?appid=582980&item_type=18&border_color=0 // OR //http://steamcommunity.com/my/ajaxgetgoovalue/?sessionid=xyz&appid=535690&assetid=4830605461&contextid=6 //sessionid=xyz //appid = 535690 //assetid = 4830605461 //contextid = 6 } // Grinds the item into gems. SteamMarket.prototype.grindIntoGoo = function(item, callback) { try { var sessionId = readCookie('sessionid'); $.ajax({ type: "POST", url: this.inventoryUrlBase + 'ajaxgrindintogoo/', data: { sessionid: sessionId, appid: item.market_fee_app, assetid: item.assetid, contextid: item.contextid, goo_value_expected: item.goo_value_expected }, success: function(data) { callback(ERROR_SUCCESS, data); }, error: function(data) { return callback(ERROR_FAILED, data); }, crossDomain: true, xhrFields: { withCredentials: true }, dataType: 'json' }); } catch (e) { return callback(ERROR_FAILED); } //sessionid = xyz //appid = 535690 //assetid = 4830605461 //contextid = 6 //goo_value_expected = 10 //http://steamcommunity.com/my/ajaxgrindintogoo/ } // Unpacks the booster pack. SteamMarket.prototype.unpackBoosterPack = function(item, callback) { try { var sessionId = readCookie('sessionid'); $.ajax({ type: "POST", url: this.inventoryUrlBase + 'ajaxunpackbooster/', data: { sessionid: sessionId, appid: item.market_fee_app, communityitemid: item.assetid }, success: function(data) { callback(ERROR_SUCCESS, data); }, error: function(data) { return callback(ERROR_FAILED, data); }, crossDomain: true, xhrFields: { withCredentials: true }, dataType: 'json' }); } catch (e) { return callback(ERROR_FAILED); } //sessionid = xyz //appid = 535690 //communityitemid = 4830605461 //http://steamcommunity.com/my/ajaxunpackbooster/ } // Get the current price history for an item. SteamMarket.prototype.getCurrentPriceHistory = function(appid, market_name, callback) { var url = window.location.protocol + '//steamcommunity.com/market/pricehistory/?appid=' + appid + '&market_hash_name=' + market_name; $.get(url, function(data) { if (!data || !data.success || !data.prices) { callback(ERROR_DATA); return; } // Multiply prices so they're in pennies. for (var i = 0; i < data.prices.length; i++) { data.prices[i][1] *= 100; data.prices[i][2] = parseInt(data.prices[i][2]); } // Store the price history in the session storage. var storage_hash = 'pricehistory_' + appid + '+' + market_name; storageSession.setItem(storage_hash, data.prices); callback(ERROR_SUCCESS, data.prices, false); }, 'json') .fail(function(data) { if (!data || !data.responseJSON) { return callback(ERROR_FAILED); } if (!data.responseJSON.success) { callback(ERROR_DATA); return; } return callback(ERROR_FAILED); }); } // Get the item name id from a market item. // // This id never changes so we can store this in the persistent storage. SteamMarket.prototype.getMarketItemNameId = function(item, callback) { try { var market_name = getMarketHashName(item); if (market_name == null) { callback(ERROR_FAILED); return; } var appid = item.appid; var storage_hash = 'itemnameid_' + appid + '+' + market_name; storagePersistent.getItem(storage_hash) .then(function(value) { if (value != null) callback(ERROR_SUCCESS, value); else return market.getCurrentMarketItemNameId(appid, market_name, callback); }) .catch(function(error) { return market.getCurrentMarketItemNameId(appid, market_name, callback); }); } catch (e) { return callback(ERROR_FAILED); } } // Get the item name id from a market item. SteamMarket.prototype.getCurrentMarketItemNameId = function(appid, market_name, callback) { var url = window.location.protocol + '//steamcommunity.com/market/listings/' + appid + '/' + market_name; $.get(url, function(page) { var matches = /Market_LoadOrderSpread\( (\d+) \);/.exec(page); if (matches == null) { callback(ERROR_DATA); return; } var item_nameid = matches[1]; // Store the item name id in the persistent storage. var storage_hash = 'itemnameid_' + appid + '+' + market_name; storagePersistent.setItem(storage_hash, item_nameid); callback(ERROR_SUCCESS, item_nameid); }) .fail(function(e) { return callback(ERROR_FAILED, e.status); }); }; // Get the sales listings for this item in the market, with more information. // //{ //"success" : 1, //"sell_order_table" : "
Price<\/th>Quantity<\/th><\/tr>
0,04\u20ac<\/td>311<\/td><\/tr>
0,05\u20ac<\/td>895<\/td><\/tr>
0,06\u20ac<\/td>495<\/td><\/tr>
0,07\u20ac<\/td>174<\/td><\/tr>
0,08\u20ac<\/td>49<\/td><\/tr>
0,09\u20ac or more<\/td>41<\/td><\/tr><\/table>", //"sell_order_summary" : "1965<\/span> for sale starting at 0,04\u20ac<\/span>", //"buy_order_table" : "
Price<\/th>Quantity<\/th><\/tr>
0,03\u20ac<\/td>93<\/td><\/tr><\/table>", //"buy_order_summary" : "93<\/span> requests to buy at 0,03\u20ac<\/span> or lower", //"highest_buy_order" : "3", //"lowest_sell_order" : "4", //"buy_order_graph" : [[0.03, 93, "93 buy orders at 0,03\u20ac or higher"]], //"sell_order_graph" : [[0.04, 311, "311 sell orders at 0,04\u20ac or lower"], [0.05, 1206, "1,206 sell orders at 0,05\u20ac or lower"], [0.06, 1701, "1,701 sell orders at 0,06\u20ac or lower"], [0.07, 1875, "1,875 sell orders at 0,07\u20ac or lower"], [0.08, 1924, "1,924 sell orders at 0,08\u20ac or lower"], [0.09, 1934, "1,934 sell orders at 0,09\u20ac or lower"], [0.1, 1936, "1,936 sell orders at 0,10\u20ac or lower"], [0.11, 1937, "1,937 sell orders at 0,11\u20ac or lower"], [0.12, 1944, "1,944 sell orders at 0,12\u20ac or lower"], [0.14, 1945, "1,945 sell orders at 0,14\u20ac or lower"]], //"graph_max_y" : 3000, //"graph_min_x" : 0.03, //"graph_max_x" : 0.14, //"price_prefix" : "", //"price_suffix" : "\u20ac" //} SteamMarket.prototype.getItemOrdersHistogram = function(item, cache, callback) { try { var market_name = getMarketHashName(item); if (market_name == null) { callback(ERROR_FAILED); return; } var appid = item.appid; if (cache) { var storage_hash = 'itemordershistogram_' + appid + '+' + market_name; storageSession.getItem(storage_hash) .then(function(value) { if (value != null) callback(ERROR_SUCCESS, value, true); else { market.getCurrentItemOrdersHistogram(item, market_name, callback); } }) .catch(function(error) { market.getCurrentItemOrdersHistogram(item, market_name, callback); }); } else { market.getCurrentItemOrdersHistogram(item, market_name, callback); } } catch (e) { return callback(ERROR_FAILED); } }; // Get the sales listings for this item in the market, with more information. SteamMarket.prototype.getCurrentItemOrdersHistogram = function(item, market_name, callback) { market.getMarketItemNameId(item, function(error, item_nameid) { if (error) { if (item_nameid != 429) // 429 = Too many requests made. callback(ERROR_DATA); else callback(ERROR_FAILED); return; } var url = window.location.protocol + '//steamcommunity.com/market/itemordershistogram?country=' + country + '&language=english¤cy=' + currencyId + '&item_nameid=' + item_nameid + '&two_factor=0'; $.get(url, function(histogram) { // Store the histogram in the session storage. var storage_hash = 'itemordershistogram_' + item.appid + '+' + market_name; storageSession.setItem(storage_hash, histogram); callback(ERROR_SUCCESS, histogram, false); }) .fail(function() { return callback(ERROR_FAILED, null); }); }); }; // Calculate the price before fees (seller price) from the buyer price SteamMarket.prototype.getPriceBeforeFees = function(price, item) { var publisherFee = -1; if (item != null) { if (item.market_fee != null) publisherFee = item.market_fee; else if (item.description != null && item.description.market_fee != null) publisherFee = item.description.market_fee; } if (publisherFee == -1) { if (this.walletInfo != null) publisherFee = this.walletInfo['wallet_publisher_fee_percent_default']; else publisherFee = 0.10; } price = Math.round(price); var feeInfo = CalculateFeeAmount(price, publisherFee, this.walletInfo); return price - feeInfo.fees; }; // Calculate the buyer price from the seller price SteamMarket.prototype.getPriceIncludingFees = function(price, item) { var publisherFee = -1; if (item != null) { if (item.market_fee != null) publisherFee = item.market_fee; else if (item.description != null && item.description.market_fee != null) publisherFee = item.description.market_fee; } if (publisherFee == -1) { if (this.walletInfo != null) publisherFee = this.walletInfo['wallet_publisher_fee_percent_default']; else publisherFee = 0.10; } price = Math.round(price); var feeInfo = CalculateAmountToSendForDesiredReceivedAmount(price, publisherFee, this.walletInfo); return feeInfo.amount; }; //#endregion function replaceAll(str, find, replace) { return str.replace(new RegExp(find, 'g'), replace); } // Cannot use encodeURI / encodeURIComponent, Steam only escapes certain characters. function escapeURI(name) { var previousName = ''; while (previousName != name) { previousName = name; name = name.replace('?', '%3F') .replace('#', '%23') .replace(' ', '%09'); } return name; } //#region Steam Market / Inventory helpers function getMarketHashName(item) { if (item == null) return null; if (item.description != null && item.description.market_hash_name != null) return escapeURI(item.description.market_hash_name); if (item.description != null && item.description.name != null) return escapeURI(item.description.name); if (item.market_hash_name != null) return escapeURI(item.market_hash_name); if (item.name != null) return escapeURI(item.name); return null; } function getIsCrate(item) { if (item == null) return false; // This is available on the inventory page. var tags = item.tags != null ? item.tags : (item.description != null && item.description.tags != null ? item.description.tags : null); if (tags != null) { var isTaggedAsCrate = false; tags.forEach(function (arrayItem) { if (arrayItem.category == 'Type') if (arrayItem.internal_name == 'Supply Crate') isTaggedAsCrate = true; }); if (isTaggedAsCrate) return true; } } function getIsTradingCard(item) { if (item == null) return false; // This is available on the inventory page. var tags = item.tags != null ? item.tags : (item.description != null && item.description.tags != null ? item.description.tags : null); if (tags != null) { var isTaggedAsTradingCard = false; tags.forEach(function(arrayItem) { if (arrayItem.category == 'item_class') if (arrayItem.internal_name == 'item_class_2') // trading card. isTaggedAsTradingCard = true; }); if (isTaggedAsTradingCard) return true; } // This is available on the market page. if (item.owner_actions != null) { for (var i = 0; i < item.owner_actions.length; i++) { if (item.owner_actions[i].link == null) continue; // Cards include a link to the gamecard page. // For example: "http://steamcommunity.com/my/gamecards/503820/". if (item.owner_actions[i].link.toString().toLowerCase().includes('gamecards')) return true; } } // A fallback for the market page (only works with language on English). if (item.type != null && item.type.toLowerCase().includes('trading card')) return true; return false; } function getIsFoilTradingCard(item) { if (!getIsTradingCard(item)) return false; // This is available on the inventory page. var tags = item.tags != null ? item.tags : (item.description != null && item.description.tags != null ? item.description.tags : null); if (tags != null) { var isTaggedAsFoilTradingCard = false; tags.forEach(function(arrayItem) { if (arrayItem.category == 'cardborder') if (arrayItem.internal_name == 'cardborder_1') // foil border. isTaggedAsFoilTradingCard = true; }); if (isTaggedAsFoilTradingCard) return true; } // This is available on the market page. if (item.owner_actions != null) { for (var i = 0; i < item.owner_actions.length; i++) { if (item.owner_actions[i].link == null) continue; // Cards include a link to the gamecard page. // The border parameter specifies the foil cards. // For example: "http://steamcommunity.com/my/gamecards/503820/?border=1". if (item.owner_actions[i].link.toString().toLowerCase().includes('gamecards') && item.owner_actions[i].link.toString().toLowerCase().includes('border')) return true; } } // A fallback for the market page (only works with language on English). if (item.type != null && item.type.toLowerCase().includes('foil trading card')) return true; return false; } function CalculateFeeAmount(amount, publisherFee, walletInfo) { if (walletInfo == null || !walletInfo['wallet_fee']) { return { fees: 0 }; } publisherFee = (publisherFee == null) ? 0 : publisherFee; // Since CalculateFeeAmount has a Math.floor, we could be off a cent or two. Let's check: var iterations = 0; // shouldn't be needed, but included to be sure nothing unforseen causes us to get stuck var nEstimatedAmountOfWalletFundsReceivedByOtherParty = parseInt((amount - parseInt(walletInfo['wallet_fee_base'])) / (parseFloat(walletInfo['wallet_fee_percent']) + parseFloat(publisherFee) + 1)); var bEverUndershot = false; var fees = CalculateAmountToSendForDesiredReceivedAmount(nEstimatedAmountOfWalletFundsReceivedByOtherParty, publisherFee, walletInfo); while (fees.amount != amount && iterations < 10) { if (fees.amount > amount) { if (bEverUndershot) { fees = CalculateAmountToSendForDesiredReceivedAmount( nEstimatedAmountOfWalletFundsReceivedByOtherParty - 1, publisherFee, walletInfo); fees.steam_fee += (amount - fees.amount); fees.fees += (amount - fees.amount); fees.amount = amount; break; } else { nEstimatedAmountOfWalletFundsReceivedByOtherParty--; } } else { bEverUndershot = true; nEstimatedAmountOfWalletFundsReceivedByOtherParty++; } fees = CalculateAmountToSendForDesiredReceivedAmount(nEstimatedAmountOfWalletFundsReceivedByOtherParty, publisherFee, walletInfo); iterations++; } // fees.amount should equal the passed in amount return fees; } // Clamps cur between min and max (inclusive). function clamp(cur, min, max) { if (cur < min) cur = min; if (cur > max) cur = max; return cur; } // Strangely named function, it actually works out the fees and buyer price for a seller price function CalculateAmountToSendForDesiredReceivedAmount(receivedAmount, publisherFee, walletInfo) { if (walletInfo == null || !walletInfo['wallet_fee']) { return { amount: receivedAmount }; } publisherFee = (publisherFee == null) ? 0 : publisherFee; var nSteamFee = parseInt(Math.floor(Math.max(receivedAmount * parseFloat(walletInfo['wallet_fee_percent']), walletInfo['wallet_fee_minimum']) + parseInt(walletInfo['wallet_fee_base']))); var nPublisherFee = parseInt(Math.floor(publisherFee > 0 ? Math.max(receivedAmount * publisherFee, 1) : 0)); var nAmountToSend = receivedAmount + nSteamFee + nPublisherFee; return { steam_fee: nSteamFee, publisher_fee: nPublisherFee, fees: nSteamFee + nPublisherFee, amount: parseInt(nAmountToSend) }; } function readCookie(name) { var nameEQ = name + "="; var ca = document.cookie.split(';'); for (var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) == ' ') c = c.substring(1, c.length); if (c.indexOf(nameEQ) == 0) return decodeURIComponent(c.substring(nameEQ.length, c.length)); } return null; } function isRetryMessage(message) { var messageList = [ "You cannot sell any items until your previous action completes.", "There was a problem listing your item. Refresh the page and try again.", "We were unable to contact the game's item server. The game's item server may be down or Steam may be experiencing temporary connectivity issues. Your listing has not been created. Refresh the page and try again." ]; return messageList.indexOf(message) !== -1; } //#endregion //#region Logging var userScrolled = false; var logger = document.createElement('div'); logger.setAttribute('id', 'logger'); function updateScroll() { if (!userScrolled) { var element = document.getElementById("logger"); element.scrollTop = element.scrollHeight; } } function logDOM(text) { logger.innerHTML += text + '
'; updateScroll(); } function clearLogDOM() { logger.innerHTML = ''; updateScroll(); } function logConsole(text) { if (enableConsoleLog) { console.log(text); } } //#endregion //#region Inventory if (currentPage == PAGE_INVENTORY) { function onQueueDrain() { if (itemQueue.length() == 0 && sellQueue.length() == 0 && scrapQueue.length() == 0 && boosterQueue.length() == 0) { $('#inventory_items_spinner').remove(); } } function updateTotals() { if ($('#loggerTotal').length == 0) { $(logger).parent().append('
'); } var totals = document.getElementById('loggerTotal'); totals.innerHTML = ''; if (totalPriceWithFeesOnMarket > 0) { totals.innerHTML += '
Total listed for ' + (totalPriceWithFeesOnMarket / 100.0).toFixed(2) + currencySymbol + ', you will receive ' + (totalPriceWithoutFeesOnMarket / 100).toFixed(2) + currencySymbol + '.
'; } if (totalScrap > 0) { totals.innerHTML += '
Total scrap ' + totalScrap + '.
'; } } var sellQueue = async.queue(function(task, next) { market.sellItem(task.item, task.sellPrice, function(err, data) { totalNumberOfProcessedQueueItems++; var digits = getNumberOfDigits(totalNumberOfQueuedItems); var itemId = task.item.assetid || task.item.id; var itemName = task.item.name || task.item.description.name; var padLeft = padLeftZero('' + totalNumberOfProcessedQueueItems, digits) + ' / ' + totalNumberOfQueuedItems; if (!err) { logDOM(padLeft + ' - ' + itemName + ' listed for ' + (market.getPriceIncludingFees(task.sellPrice) / 100.0).toFixed(2) + currencySymbol + ', you will receive ' + (task.sellPrice / 100.0).toFixed(2) + currencySymbol + '.'); $('#' + task.item.appid + '_' + task.item.contextid + '_' + itemId) .css('background', COLOR_SUCCESS); totalPriceWithoutFeesOnMarket += task.sellPrice; totalPriceWithFeesOnMarket += market.getPriceIncludingFees(task.sellPrice); updateTotals(); } else if (data != null && isRetryMessage(data.message)) { logDOM(padLeft + ' - ' + itemName + ' retrying listing because ' + data.message[0].toLowerCase() + data.message.slice(1)); totalNumberOfProcessedQueueItems--; sellQueue.unshift(task); sellQueue.pause(); setTimeout(function() { sellQueue.resume(); }, getRandomInt(30000, 45000)); } else { if (data != null && data.responseJSON != null && data.responseJSON.message != null) { logDOM(padLeft + ' - ' + itemName + ' not added to market because ' + data.responseJSON.message[0].toLowerCase() + data.responseJSON.message.slice(1)); } else logDOM(padLeft + ' - ' + itemName + ' not added to market.'); $('#' + task.item.appid + '_' + task.item.contextid + '_' + itemId) .css('background', COLOR_ERROR); } next(); }); }, 1); sellQueue.drain = function() { onQueueDrain(); } function sellAllItems(appId) { loadAllInventories().then(function() { var items = getInventoryItems(); var filteredItems = []; items.forEach(function(item) { if (!item.marketable) { return; } filteredItems.push(item); }); sellItems(filteredItems); }, function() { logDOM('Could not retrieve the inventory...'); }); } function sellAllDuplicateItems() { loadAllInventories().then(function () { var items = getInventoryItems(); var marketableItems = []; var filteredItems = []; items.forEach(function (item) { if (!item.marketable) { return; } marketableItems.push(item); }); filteredItems = marketableItems.filter((e, i) => marketableItems.map(m => m.classid).indexOf(e.classid) !== i); sellItems(filteredItems); }, function () { logDOM('Could not retrieve the inventory...'); }); } function gemAllDuplicateItems() { loadAllInventories().then(function () { var items = getInventoryItems(); var filteredItems = []; var numberOfQueuedItems = 0; filteredItems = items.filter((e, i) => items.map(m => m.classid).indexOf(e.classid) !== i); filteredItems.forEach(function (item) { if (item.queued != null) { return; } if (item.owner_actions == null) { return; } var canTurnIntoGems = false; for (var owner_action in item.owner_actions) { if (item.owner_actions[owner_action].link != null && item.owner_actions[owner_action].link.includes('GetGooValue')) { canTurnIntoGems = true; } } if (!canTurnIntoGems) return; item.queued = true; scrapQueue.push(item); numberOfQueuedItems++; }); if (numberOfQueuedItems > 0) { totalNumberOfQueuedItems += numberOfQueuedItems; $('#inventory_items_spinner').remove(); $('#inventory_sell_buttons').append('
' + spinnerBlock + '
Processing ' + numberOfQueuedItems + ' items
' + '
'); } }, function () { logDOM('Could not retrieve the inventory...'); }); } function sellAllCards() { loadAllInventories().then(function() { var items = getInventoryItems(); var filteredItems = []; items.forEach(function(item) { if (!getIsTradingCard(item) || !item.marketable) { return; } filteredItems.push(item); }); sellItems(filteredItems); }, function() { logDOM('Could not retrieve the inventory...'); }); } function sellAllCrates() { loadAllInventories().then(function () { var items = getInventoryItems(); var filteredItems = []; items.forEach(function (item) { if (!getIsCrate(item) || !item.marketable) { return; } filteredItems.push(item); }); sellItems(filteredItems); }, function() { logDOM('Could not retrieve the inventory...'); }); } var scrapQueue = async.queue(function(item, next) { scrapQueueWorker(item, function(success) { if (success) { setTimeout(function() { next(); }, 250); } else { var delay = numberOfFailedRequests > 1 ? getRandomInt(30000, 45000) : getRandomInt(1000, 1500); if (numberOfFailedRequests > 3) numberOfFailedRequests = 0; setTimeout(function() { next(); }, delay); } }); }, 1); scrapQueue.drain = function() { onQueueDrain(); } function scrapQueueWorker(item, callback) { var failed = 0; var itemName = item.name || item.description.name; var itemId = item.assetid || item.id; market.getGooValue(item, function(err, goo) { totalNumberOfProcessedQueueItems++; var digits = getNumberOfDigits(totalNumberOfQueuedItems); var padLeft = padLeftZero('' + totalNumberOfProcessedQueueItems, digits) + ' / ' + totalNumberOfQueuedItems; if (err != ERROR_SUCCESS) { logConsole('Failed to get gems value for ' + itemName); logDOM(padLeft + ' - ' + itemName + ' not turned into gems due to missing gems value.'); $('#' + item.appid + '_' + item.contextid + '_' + itemId).css('background', COLOR_ERROR); return callback(false); } item.goo_value_expected = parseInt(goo.goo_value); market.grindIntoGoo(item, function(err, result) { if (err != ERROR_SUCCESS) { logConsole('Failed to turn item into gems for ' + itemName); logDOM(padLeft + ' - ' + itemName + ' not turned into gems due to unknown error.'); $('#' + item.appid + '_' + item.contextid + '_' + itemId).css('background', COLOR_ERROR); return callback(false); } logConsole('============================') logConsole(itemName); logConsole('Turned into ' + goo.goo_value + ' gems'); logDOM(padLeft + ' - ' + itemName + ' turned into ' + item.goo_value_expected + ' gems.'); $('#' + item.appid + '_' + item.contextid + '_' + itemId).css('background', COLOR_SUCCESS); totalScrap += item.goo_value_expected; updateTotals(); callback(true); }); }); } var boosterQueue = async.queue(function(item, next) { boosterQueueWorker(item, function(success) { if (success) { setTimeout(function() { next(); }, 250); } else { var delay = numberOfFailedRequests > 1 ? getRandomInt(30000, 45000) : getRandomInt(1000, 1500); if (numberOfFailedRequests > 3) numberOfFailedRequests = 0; setTimeout(function() { next(); }, delay); } }); }, 1); boosterQueue.drain = function() { onQueueDrain(); } function boosterQueueWorker(item, callback) { var failed = 0; var itemName = item.name || item.description.name; var itemId = item.assetid || item.id; market.unpackBoosterPack(item, function(err, goo) { totalNumberOfProcessedQueueItems++; var digits = getNumberOfDigits(totalNumberOfQueuedItems); var padLeft = padLeftZero('' + totalNumberOfProcessedQueueItems, digits) + ' / ' + totalNumberOfQueuedItems; if (err != ERROR_SUCCESS) { logConsole('Failed to unpack booster pack ' + itemName); logDOM(padLeft + ' - ' + itemName + ' not unpacked.'); $('#' + item.appid + '_' + item.contextid + '_' + itemId).css('background', COLOR_ERROR); return callback(false); } logDOM(padLeft + ' - ' + itemName + ' unpacked.'); $('#' + item.appid + '_' + item.contextid + '_' + itemId).css('background', COLOR_SUCCESS); callback(true); }); } // Turns the selected items into gems. function turnSelectedItemsIntoGems() { var ids = getSelectedItems(); loadAllInventories().then(function() { var items = getInventoryItems(); var numberOfQueuedItems = 0; items.forEach(function(item) { // Ignored queued items. if (item.queued != null) { return; } if (item.owner_actions == null) { return; } var canTurnIntoGems = false; for (var owner_action in item.owner_actions) { if (item.owner_actions[owner_action].link != null && item.owner_actions[owner_action].link.includes('GetGooValue')) { canTurnIntoGems = true; } } if (!canTurnIntoGems) return; var itemId = item.assetid || item.id; if (ids.indexOf(itemId) !== -1) { item.queued = true; scrapQueue.push(item); numberOfQueuedItems++; } }); if (numberOfQueuedItems > 0) { totalNumberOfQueuedItems += numberOfQueuedItems; $('#inventory_items_spinner').remove(); $('#inventory_sell_buttons').append('
' + spinnerBlock + '
Processing ' + numberOfQueuedItems + ' items
' + '
'); } }, function() { logDOM('Could not retrieve the inventory...'); }); } // Unpacks the selected booster packs. function unpackSelectedBoosterPacks() { var ids = getSelectedItems(); loadAllInventories().then(function() { var items = getInventoryItems(); var numberOfQueuedItems = 0; items.forEach(function(item) { // Ignored queued items. if (item.queued != null) { return; } if (item.owner_actions == null) { return; } var canOpenBooster = false; for (var owner_action in item.owner_actions) { if (item.owner_actions[owner_action].link != null && item.owner_actions[owner_action].link.includes('OpenBooster')) { canOpenBooster = true; } } if (!canOpenBooster) return; var itemId = item.assetid || item.id; if (ids.indexOf(itemId) !== -1) { item.queued = true; boosterQueue.push(item); numberOfQueuedItems++; } }); if (numberOfQueuedItems > 0) { totalNumberOfQueuedItems += numberOfQueuedItems; $('#inventory_items_spinner').remove(); $('#inventory_sell_buttons').append('
' + spinnerBlock + '
Processing ' + numberOfQueuedItems + ' items
' + '
'); } }, function() { logDOM('Could not retrieve the inventory...'); }); } function sellSelectedItems() { getInventorySelectedMarketableItems(function(items) { sellItems(items); }); } function canSellSelectedItemsManually(items) { // We have to construct an URL like this // https://steamcommunity.com/market/multisell?appid=730&contextid=2&items[]=Falchion%20Case&qty[]=100 var appid = items[0].appid; var contextid = items[0].contextid; var hasInvalidItem = false; items.forEach(function(item) { if (item.contextid != contextid || item.commodity == false) hasInvalidItem = true; }); return !hasInvalidItem; } function sellSelectedItemsManually() { getInventorySelectedMarketableItems(function(items) { // We have to construct an URL like this // https://steamcommunity.com/market/multisell?appid=730&contextid=2&items[]=Falchion%20Case&qty[]=100 var appid = items[0].appid; var contextid = items[0].contextid; var itemsWithQty = {}; items.forEach(function(item) { itemsWithQty[item.market_hash_name] = itemsWithQty[item.market_hash_name] + 1 || 1; }); var itemsString = ''; for (var itemName in itemsWithQty) { itemsString += '&items[]=' + encodeURI(itemName) + '&qty[]=' + itemsWithQty[itemName]; } var baseUrl = 'https://steamcommunity.com/market/multisell'; var redirectUrl = baseUrl + '?appid=' + appid + '&contextid=' + contextid + itemsString; var dialog = unsafeWindow.ShowDialog('Steam Economy Enhancer', ''); dialog.OnDismiss(function() { items.forEach(function(item) { var itemId = item.assetid || item.id; $('#' + item.appid + '_' + item.contextid + '_' + itemId).css('background', COLOR_PENDING); }); }); }); } function sellItems(items) { if (items.length == 0) { logDOM('These items cannot be added to the market...'); return; } var numberOfQueuedItems = 0; items.forEach(function(item, index, array) { // Ignored queued items. if (item.queued != null) { return; } item.queued = true; var itemId = item.assetid || item.id; item.ignoreErrors = false; itemQueue.push(item); numberOfQueuedItems++; }); if (numberOfQueuedItems > 0) { totalNumberOfQueuedItems += numberOfQueuedItems; $('#inventory_items_spinner').remove(); $('#inventory_sell_buttons').append('
' + spinnerBlock + '
Processing ' + numberOfQueuedItems + ' items
' + '
'); } } var itemQueue = async.queue(function(item, next) { itemQueueWorker(item, item.ignoreErrors, function(success, cached) { if (success) { setTimeout(function() { next(); }, cached ? 0 : getRandomInt(1000, 1500)); } else { if (!item.ignoreErrors) { item.ignoreErrors = true; itemQueue.push(item); } var delay = numberOfFailedRequests > 1 ? getRandomInt(30000, 45000) : getRandomInt(1000, 1500); if (numberOfFailedRequests > 3) numberOfFailedRequests = 0; setTimeout(function() { next(); }, cached ? 0 : delay); } }); }, 1); function itemQueueWorker(item, ignoreErrors, callback) { var priceInfo = getPriceInformationFromItem(item); var failed = 0; var itemName = item.name || item.description.name; market.getPriceHistory(item, true, function(err, history, cachedHistory) { if (err) { logConsole('Failed to get price history for ' + itemName); if (err == ERROR_FAILED) failed += 1; } market.getItemOrdersHistogram(item, true, function(err, histogram, cachedListings) { if (err) { logConsole('Failed to get orders histogram for ' + itemName); if (err == ERROR_FAILED) failed += 1; } if (failed > 0 && !ignoreErrors) { return callback(false, cachedHistory && cachedListings); } logConsole('============================') logConsole(itemName); var sellPrice = calculateSellPriceBeforeFees(history, histogram, true, priceInfo.minPriceBeforeFees, priceInfo.maxPriceBeforeFees); logConsole('Sell price: ' + sellPrice / 100.0 + ' (' + market.getPriceIncludingFees(sellPrice) / 100.0 + ')'); sellQueue.push({ item: item, sellPrice: sellPrice }); return callback(true, cachedHistory && cachedListings); }); }); } // Initialize the inventory UI. function initializeInventoryUI() { var isOwnInventory = unsafeWindow.g_ActiveUser.strSteamId == unsafeWindow.g_steamID; var previousSelection = -1; // To store the index of the previous selection. updateInventoryUI(isOwnInventory); $('.games_list_tabs').on('click', '*', function() { updateInventoryUI(isOwnInventory); }); // Ignore selection on other user's inventories. if (!isOwnInventory) return; // Steam adds 'display:none' to items while searching. These should not be selected while using shift/ctrl. var filter = ".itemHolder:not([style*=none])"; $('#inventories').selectable({ filter: filter, selecting: function(e, ui) { // Get selected item index. var selectedIndex = $(ui.selecting.tagName, e.target).index(ui.selecting); // If shift key was pressed and there is previous - select them all. if (e.shiftKey && previousSelection > -1) { $(ui.selecting.tagName, e.target) .slice(Math.min(previousSelection, selectedIndex), 1 + Math.max(previousSelection, selectedIndex)).each(function() { if ($(this).is(filter)) { $(this).addClass('ui-selected'); } }); previousSelection = -1; // Reset previous. } else { previousSelection = selectedIndex; // Save previous. } }, selected: function(e, ui) { updateButtons(); } }); if (typeof unsafeWindow.CInventory !== 'undefined') { var originalSelectItem = unsafeWindow.CInventory.prototype.SelectItem; unsafeWindow.CInventory.prototype.SelectItem = function(event, elItem, rgItem, bUserAction) { originalSelectItem.apply(this, arguments); updateButtons(); updateInventorySelection(rgItem); } } } // Gets the selected items in the inventory. function getSelectedItems() { var ids = []; $('.inventory_ctn').each(function() { $(this).find('.inventory_page').each(function() { var inventory_page = this; $(inventory_page).find('.itemHolder.ui-selected:not([style*=none])').each(function() { $(this).find('.item').each(function() { var matches = this.id.match(/_(\-?\d+)$/); if (matches) { ids.push(matches[1]); } }); }); }); }); return ids; } // Gets the selected and marketable items in the inventory. function getInventorySelectedMarketableItems(callback) { var ids = getSelectedItems(); loadAllInventories().then(function() { var items = getInventoryItems(); var filteredItems = []; items.forEach(function(item) { if (!item.marketable) { return; } var itemId = item.assetid || item.id; if (ids.indexOf(itemId) !== -1) { filteredItems.push(item); } }); callback(filteredItems); }, function() { logDOM('Could not retrieve the inventory...'); }); } // Gets the selected and gemmable items in the inventory. function getInventorySelectedGemsItems(callback) { var ids = getSelectedItems(); loadAllInventories().then(function() { var items = getInventoryItems(); var filteredItems = []; items.forEach(function(item) { var canTurnIntoGems = false; for (var owner_action in item.owner_actions) { if (item.owner_actions[owner_action].link != null && item.owner_actions[owner_action].link.includes('GetGooValue')) { canTurnIntoGems = true; } } if (!canTurnIntoGems) return; var itemId = item.assetid || item.id; if (ids.indexOf(itemId) !== -1) { filteredItems.push(item); } }); callback(filteredItems); }, function() { logDOM('Could not retrieve the inventory...'); }); } // Gets the selected and booster pack items in the inventory. function getInventorySelectedBoosterPackItems(callback) { var ids = getSelectedItems(); loadAllInventories().then(function() { var items = getInventoryItems(); var filteredItems = []; items.forEach(function(item) { var canOpenBooster = false; for (var owner_action in item.owner_actions) { if (item.owner_actions[owner_action].link != null && item.owner_actions[owner_action].link.includes('OpenBooster')) { canOpenBooster = true; } } if (!canOpenBooster) return; var itemId = item.assetid || item.id; if (ids.indexOf(itemId) !== -1) { filteredItems.push(item); } }); callback(filteredItems); }, function() { logDOM('Could not retrieve the inventory...'); }); } // Updates the (selected) sell ... items button. function updateSellSelectedButton() { getInventorySelectedMarketableItems(function(items) { var selectedItems = items.length; if (items.length == 0) { $('.sell_selected').hide(); $('.sell_manual').hide(); } else { $('.sell_selected').show(); if (canSellSelectedItemsManually(items)) { $('.sell_manual').show(); $('.sell_manual > span').text('Sell ' + selectedItems + (selectedItems == 1 ? ' Item Manual' : ' Items Manual')); } else { $('.sell_manual').hide(); } $('.sell_selected > span').text('Sell ' + selectedItems + (selectedItems == 1 ? ' Item' : ' Items')); } }); } // Updates the (selected) turn into ... gems button. function updateTurnIntoGemsButton() { getInventorySelectedGemsItems(function(items) { var selectedItems = items.length; if (items.length == 0) { $('.turn_into_gems').hide(); } else { $('.turn_into_gems').show(); $('.turn_into_gems > span') .text('Turn ' + selectedItems + (selectedItems == 1 ? ' Item Into Gems' : ' Items Into Gems')); } }); } // Updates the (selected) open ... booster packs button. function updateOpenBoosterPacksButton() { getInventorySelectedBoosterPackItems(function(items) { var selectedItems = items.length; if (items.length == 0) { $('.unpack_booster_packs').hide(); } else { $('.unpack_booster_packs').show(); $('.unpack_booster_packs > span') .text('Unpack ' + selectedItems + (selectedItems == 1 ? ' Booster Pack' : ' Booster Packs')); } }); } function updateButtons() { updateSellSelectedButton(); updateTurnIntoGemsButton(); updateOpenBoosterPacksButton(); } function updateInventorySelection(selectedItem) { var item_info = $('#iteminfo' + unsafeWindow.iActiveSelectView); if (!item_info.length) return; if (item_info.html().indexOf('checkout/sendgift/') > -1) // Gifts have no market information. return; // Use a 'hard' item id instead of relying on the selected item_info (sometimes Steam temporarily changes the correct item (?)). var item_info_id = item_info.attr('id'); // Move scrap to bottom, this is of little interest. var scrap = $('#' + item_info_id + '_scrap_content'); scrap.next().insertBefore(scrap); // Starting at prices are already retrieved in the table. //$('#' + item_info_id + '_item_market_actions > div:nth-child(1) > div:nth-child(2)') // .remove(); // Starting at: x,xx. var market_hash_name = getMarketHashName(selectedItem); if (market_hash_name == null) return; var appid = selectedItem.appid; var item = { appid: parseInt(appid), description: { market_hash_name: market_hash_name } }; market.getItemOrdersHistogram(item, false, function(err, histogram) { if (err) { logConsole('Failed to get orders histogram for ' + (selectedItem.name || selectedItem.description.name)); return; } var groupMain = $('
' + '
Sell
' + histogram.sell_order_table + '
' + '
Buy
' + histogram.buy_order_table + '
' + '
'); $('#' + item_info_id + '_item_market_actions > div').after(groupMain); var ownerActions = $('#' + item_info_id + '_item_owner_actions'); // ownerActions is hidden on other games' inventories, we need to show it to have a "Market" button visible ownerActions.show(); ownerActions.append('View in Community Market'); $('#' + item_info_id + '_item_market_actions > div:nth-child(1) > div:nth-child(1)').hide(); var isBoosterPack = selectedItem.name.toLowerCase().endsWith('booster pack'); if (isBoosterPack) { var tradingCardsUrl = "/market/search?q=&category_753_Game%5B%5D=tag_app_" + selectedItem.market_fee_app + "&category_753_item_class%5B%5D=tag_item_class_2&appid=753"; ownerActions.append('
View trading cards in Community Market'); } // Ignored queued items. if (selectedItem.queued != null) { return; } // Generate quick sell buttons. var prices = []; if (histogram != null && histogram.highest_buy_order != null) { prices.push(parseInt(histogram.highest_buy_order)); } if (histogram != null && histogram.lowest_sell_order != null) { // Transaction volume must be separable into three or more parts (no matter if equal): valve+publisher+seller. if (parseInt(histogram.lowest_sell_order) > 3) { prices.push(parseInt(histogram.lowest_sell_order) - 1); } prices.push(parseInt(histogram.lowest_sell_order)); } prices = prices.filter((v, i) => prices.indexOf(v) === i).sort((a, b) => a - b); var buttons = ' '; prices.forEach(function(e) { buttons += '' + '' + '' + (e / 100.0) + currencySymbol + '' + '' + '' + '' }); $('#' + item_info_id + '_item_market_actions', item_info).append(buttons); $('#' + item_info_id + '_item_market_actions', item_info).append( ''); $('.quick_sell').on('click', function() { var price = $(this).attr('id').replace('quick_sell', ''); price = market.getPriceBeforeFees(price); totalNumberOfQueuedItems++; sellQueue.push({ item: selectedItem, sellPrice: price }); }); $('.quick_sell_custom').on('click', function() { var price = $('#quick_sell_input', $('#' + item_info_id + '_item_market_actions', item_info)).val() * 100; price = market.getPriceBeforeFees(price); totalNumberOfQueuedItems++; sellQueue.push({ item: selectedItem, sellPrice: price }); }); }); } // Update the inventory UI. function updateInventoryUI(isOwnInventory) { // Remove previous containers (e.g., when a user changes inventory). $('#inventory_sell_buttons').remove(); $('#price_options').remove(); $('#inventory_reload_button').remove(); $('#see_settings').remove(); $('#global_action_menu') .prepend('⬖ Steam Economy Enhancer'); $('#see_settings').on('click', '*', () => openSettings()); var appId = getActiveInventory().m_appid; var showMiscOptions = appId == 753; var TF2 = appId == 440; var sellButtons = $(''); var reloadButton = $('Reload Inventory'); $('#inventory_logos')[0].style.height = 'auto'; $('#inventory_applogo').hide(); // Hide the Steam/game logo, we don't need to see it twice. $('#inventory_applogo').after(logger); $("#logger").on('scroll', function() { var hasUserScrolledToBottom = $("#logger").prop('scrollHeight') - $("#logger").prop('clientHeight') <= $("#logger").prop('scrollTop') + 1; userScrolled = !hasUserScrolledToBottom; }); // Only add buttons on the user's inventory. if (isOwnInventory) { $('#inventory_applogo').after(sellButtons); // Add bindings to sell buttons. $('.sell_all').on('click', '*', function() { sellAllItems(appId); }); $('.sell_selected').on('click', '*', sellSelectedItems); $('.sell_all_duplicates').on('click', '*', sellAllDuplicateItems); $('.gem_all_duplicates').on('click', '*', gemAllDuplicateItems); $('.sell_manual').on('click', '*', sellSelectedItemsManually); $('.sell_all_cards').on('click', '*', sellAllCards); $('.sell_all_crates').on('click', '*', sellAllCrates); $('.turn_into_gems').on('click', '*', turnSelectedItemsIntoGems); $('.unpack_booster_packs').on('click', '*', unpackSelectedBoosterPacks); } $('.inventory_rightnav').prepend(reloadButton); $('.reload_inventory').on('click', '*', function() { window.location.reload(); }); loadAllInventories().then(function() { var updateInventoryPrices = function() { if (getSettingWithDefault(SETTING_INVENTORY_PRICE_LABELS) == 1) { setInventoryPrices(getInventoryItems()); } }; // Load after the inventory is loaded. updateInventoryPrices(); $('#inventory_pagecontrols').observe('childlist', '*', function(record) { updateInventoryPrices(); }); }, function() { logDOM('Could not retrieve the inventory...'); }); } // Loads the specified inventories. function loadInventories(inventories) { return new Promise(function(resolve) { inventories.reduce(function(promise, inventory) { return promise.then(function() { return inventory.LoadCompleteInventory().done(function() {}); }); }, Promise.resolve()); resolve(); }); } // Loads all inventories. function loadAllInventories() { var items = []; for (var child in getActiveInventory().m_rgChildInventories) { items.push(getActiveInventory().m_rgChildInventories[child]); } items.push(getActiveInventory()); return loadInventories(items); } // Gets the inventory items from the active inventory. function getInventoryItems() { var arr = []; for (var child in getActiveInventory().m_rgChildInventories) { for (var key in getActiveInventory().m_rgChildInventories[child].m_rgAssets) { var value = getActiveInventory().m_rgChildInventories[child].m_rgAssets[key]; if (typeof value === 'object') { // Merges the description in the normal object, this is done to keep the layout consistent with the market page, which is also flattened. Object.assign(value, value.description); // Includes the id of the inventory item. value['id'] = key; arr.push(value); } } } // Some inventories (e.g. BattleBlock Theater) do not have child inventories, they have just one. for (var key in getActiveInventory().m_rgAssets) { var value = getActiveInventory().m_rgAssets[key]; if (typeof value === 'object') { // Merges the description in the normal object, this is done to keep the layout consistent with the market page, which is also flattened. Object.assign(value, value.description); // Includes the id of the inventory item. value['id'] = key; arr.push(value); } } return arr; } } //#endregion //#region Inventory + Tradeoffer if (currentPage == PAGE_INVENTORY || currentPage == PAGE_TRADEOFFER) { // Gets the active inventory. function getActiveInventory() { return unsafeWindow.g_ActiveInventory; } // Sets the prices for the items. function setInventoryPrices(items) { inventoryPriceQueue.kill(); items.forEach(function(item) { if (!item.marketable) { return; } if (!$(item.element).is(":visible")) { return; } inventoryPriceQueue.push(item); }); } var inventoryPriceQueue = async.queue(function(item, next) { inventoryPriceQueueWorker(item, false, function(success, cached) { if (success) { setTimeout(function() { next(); }, cached ? 0 : getRandomInt(1000, 1500)); } else { if (!item.ignoreErrors) { item.ignoreErrors = true; inventoryPriceQueue.push(item); } numberOfFailedRequests++; var delay = numberOfFailedRequests > 1 ? getRandomInt(30000, 45000) : getRandomInt(1000, 1500); if (numberOfFailedRequests > 3) numberOfFailedRequests = 0; setTimeout(function() { next(); }, cached ? 0 : delay); } }); }, 1); function inventoryPriceQueueWorker(item, ignoreErrors, callback) { var priceInfo = getPriceInformationFromItem(item); var failed = 0; var itemName = item.name || item.description.name; // Only get the market orders here, the history is not important to visualize the current prices. market.getItemOrdersHistogram(item, true, function(err, histogram, cachedListings) { if (err) { logConsole('Failed to get orders histogram for ' + itemName); if (err == ERROR_FAILED) failed += 1; } if (failed > 0 && !ignoreErrors) { return callback(false, cachedListings); } var sellPrice = calculateSellPriceBeforeFees(null, histogram, false, 0, 65535); var itemPrice = sellPrice == 65535 ? '∞' : (market.getPriceIncludingFees(sellPrice) / 100.0).toFixed(2) + currencySymbol; var elementName = (currentPage == PAGE_TRADEOFFER ? '#item' : '#') + item.appid + '_' + item.contextid + '_' + item.id; var element = $(elementName); $('.inventory_item_price', element).remove(); element.append('' + itemPrice + ''); return callback(true, cachedListings); }); } } //#endregion //#region Market if (currentPage == PAGE_MARKET || currentPage == PAGE_MARKET_LISTING) { var marketListingsRelistedAssets = []; var marketListingsQueue = async.queue(function(listing, next) { marketListingsQueueWorker(listing, false, function(success, cached) { if (success) { setTimeout(function() { next(); }, cached ? 0 : getRandomInt(1000, 1500)); } else { setTimeout(function() { marketListingsQueueWorker(listing, true, function(success, cached) { next(); // Go to the next queue item, regardless of success. }); }, cached ? 0 : getRandomInt(30000, 45000)); } }); }, 1); marketListingsQueue.drain = function() { injectJs(function() { g_bMarketWindowHidden = false; }) }; // Gets the price, in cents, from a market listing. function getPriceFromMarketListing(listing) { var priceLabel = listing.trim().replace('--', '00'); // Fixes RUB, which has a dot at the end. if (priceLabel[priceLabel.length - 1] === '.' || priceLabel[priceLabel.length - 1] === ",") priceLabel = priceLabel.slice(0, -1); // For round numbers (e.g., 100 EUR). if (priceLabel.indexOf('.') === -1 && priceLabel.indexOf(',') === -1) { priceLabel = priceLabel + ',00'; } return parseInt(replaceNonNumbers(priceLabel)); } function marketListingsQueueWorker(listing, ignoreErrors, callback) { var asset = unsafeWindow.g_rgAssets[listing.appid][listing.contextid][listing.assetid]; // An asset: //{ // "currency" : 0, // "appid" : 753, // "contextid" : "6", // "id" : "4363079664", // "classid" : "2228526061", // "instanceid" : "0", // "amount" : "1", // "status" : 2, // "original_amount" : "1", // "background_color" : "", // "icon_url" : "xx", // "icon_url_large" : "xxx", // "descriptions" : [{ // "value" : "Their dense, shaggy fur conceals the presence of swams of moogamites, purple scaly skin, and more nipples than one would expect." // } // ], // "tradable" : 1, // "owner_actions" : [{ // "link" : "http://steamcommunity.com/my/gamecards/443880/", // "name" : "View badge progress" // }, { // "link" : "javascript:GetGooValue( '%contextid%', '%assetid%', 443880, 7, 0 )", // "name" : "Turn into Gems..." // } // ], // "name" : "Wook", // "type" : "Loot Rascals Trading Card", // "market_name" : "Wook", // "market_hash_name" : "443880-Wook", // "market_fee_app" : 443880, // "commodity" : 1, // "market_tradable_restriction" : 7, // "market_marketable_restriction" : 7, // "marketable" : 1, // "app_icon" : "xxxx", // "owner" : 0 //} var market_hash_name = getMarketHashName(asset); var appid = listing.appid; var listingUI = $(getListingFromLists(listing.listingid).elm); var game_name = asset.type; var price = getPriceFromMarketListing($('.market_listing_price > span:nth-child(1) > span:nth-child(1)', listingUI).text()); if (price <= getSettingWithDefault(SETTING_PRICE_MIN_CHECK_PRICE) * 100) { $('.market_listing_my_price', listingUI).last().css('background', COLOR_PRICE_NOT_CHECKED); $('.market_listing_my_price', listingUI).last().prop('title', 'The price is not checked.'); listingUI.addClass('not_checked'); return callback(true, true); } var priceInfo = getPriceInformationFromItem(asset); var item = { appid: parseInt(appid), description: { market_hash_name: market_hash_name } }; var failed = 0; market.getPriceHistory(item, true, function(errorPriceHistory, history, cachedHistory) { if (errorPriceHistory) { logConsole('Failed to get price history for ' + game_name); if (errorPriceHistory == ERROR_FAILED) failed += 1; } market.getItemOrdersHistogram(item, true, function(errorHistogram, histogram, cachedListings) { if (errorHistogram) { logConsole('Failed to get orders histogram for ' + game_name); if (errorHistogram == ERROR_FAILED) failed += 1; } if (failed > 0 && !ignoreErrors) { return callback(false, cachedHistory && cachedListings); } // Shows the highest buy order price on the market listings. // The 'histogram.highest_buy_order' is not reliable as Steam is caching this value, but it gives some idea for older titles/listings. var highestBuyOrderPrice = (histogram == null || histogram.highest_buy_order == null ? '-' : ((histogram.highest_buy_order / 100) + currencySymbol)); $('.market_table_value > span:nth-child(1) > span:nth-child(1) > span:nth-child(1)', listingUI).append(' ➤ ' + highestBuyOrderPrice + ''); logConsole('============================') logConsole(JSON.stringify(listing)); logConsole(game_name + ': ' + asset.name); logConsole('Current price: ' + price / 100.0); // Calculate two prices here, one without the offset and one with the offset. // The price without the offset is required to not relist the item constantly when you have the lowest price (i.e., with a negative offset). // The price with the offset should be used for relisting so it will still apply the user-set offset. var sellPriceWithoutOffset = calculateSellPriceBeforeFees(history, histogram, false, priceInfo.minPriceBeforeFees, priceInfo.maxPriceBeforeFees); var sellPriceWithOffset = calculateSellPriceBeforeFees(history, histogram, true, priceInfo.minPriceBeforeFees, priceInfo.maxPriceBeforeFees); var sellPriceWithoutOffsetWithFees = market.getPriceIncludingFees(sellPriceWithoutOffset); logConsole('Calculated price: ' + sellPriceWithoutOffsetWithFees / 100.0 + ' (' + sellPriceWithoutOffset / 100.0 + ')'); listingUI.addClass('price_' + sellPriceWithOffset); $('.market_listing_my_price', listingUI).last().prop('title', 'The best price is ' + (sellPriceWithoutOffsetWithFees / 100.0) + currencySymbol + '.'); if (sellPriceWithoutOffsetWithFees < price) { logConsole('Sell price is too high.'); $('.market_listing_my_price', listingUI).last() .css('background', COLOR_PRICE_EXPENSIVE); listingUI.addClass('overpriced'); if (getSettingWithDefault(SETTING_RELIST_AUTOMATICALLY) == 1) { queueOverpricedItemListing(listing.listingid); } } else if (sellPriceWithoutOffsetWithFees > price) { logConsole('Sell price is too low.'); $('.market_listing_my_price', listingUI).last().css('background', COLOR_PRICE_CHEAP); listingUI.addClass('underpriced'); } else { logConsole('Sell price is fair.'); $('.market_listing_my_price', listingUI).last().css('background', COLOR_PRICE_FAIR); listingUI.addClass('fair'); } return callback(true, cachedHistory && cachedListings); }); }); } var marketOverpricedQueue = async.queue(function(item, next) { marketOverpricedQueueWorker(item, false, function(success) { if (success) { setTimeout(function() { next(); }, getRandomInt(1000, 1500)); } else { setTimeout(function() { marketOverpricedQueueWorker(item, true, function(success) { next(); // Go to the next queue item, regardless of success. }); }, getRandomInt(30000, 45000)); } }); }, 1); function marketOverpricedQueueWorker(item, ignoreErrors, callback) { var listingUI = getListingFromLists(item.listing).elm; market.removeListing(item.listing, function(errorRemove, data) { if (!errorRemove) { $('.actual_content', listingUI).css('background', COLOR_PENDING); setTimeout(function() { var baseUrl = document.querySelector('a.submenuitem[href$="/inventory/"]').href + 'json/'; var itemName = $('.market_listing_item_name_link', listingUI).first().attr('href'); var marketHashNameIndex = itemName.lastIndexOf('/') + 1; var marketHashName = itemName.substring(marketHashNameIndex); var decodedMarketHashName = decodeURIComponent(itemName.substring(marketHashNameIndex)); var newAssetId = -1; unsafeWindow.RequestFullInventory(baseUrl + item.appid + "/" + item.contextid + "/", {}, null, null, function(transport) { if (transport.responseJSON && transport.responseJSON.success) { var inventory = transport.responseJSON.rgInventory; for (var child in inventory) { if (marketListingsRelistedAssets.indexOf(child) == -1 && inventory[child].appid == item.appid && (inventory[child].market_hash_name == decodedMarketHashName || inventory[child].market_hash_name == marketHashName)) { newAssetId = child; break; } } if (newAssetId == -1) { $('.actual_content', listingUI).css('background', COLOR_ERROR); return callback(false); } item.assetid = newAssetId; marketListingsRelistedAssets.push(newAssetId); market.sellItem(item, item.sellPrice, function(errorSell) { if (!errorSell) { $('.actual_content', listingUI).css('background', COLOR_SUCCESS); setTimeout(function() { removeListingFromLists(item.listing) }, 3000); return callback(true); } else { $('.actual_content', listingUI).css('background', COLOR_ERROR); return callback(false); } }); } else { $('.actual_content', listingUI).css('background', COLOR_ERROR); return callback(false); } }); }, getRandomInt(1500, 2500)); // Wait a little to make sure the item is returned to inventory. } else { $('.actual_content', listingUI).css('background', COLOR_ERROR); return callback(false); } }); } // Queue an overpriced item listing to be relisted. function queueOverpricedItemListing(listingid) { var assetInfo = getAssetInfoFromListingId(listingid); var listingUI = $(getListingFromLists(listingid).elm); var price = -1; var items = $(listingUI).attr('class').split(' '); for (var i in items) { if (items[i].toString().includes('price_')) price = parseInt(items[i].toString().replace('price_', '')); } if (price > 0) { marketOverpricedQueue.push({ listing: listingid, assetid: assetInfo.assetid, contextid: assetInfo.contextid, appid: assetInfo.appid, sellPrice: price }); } } var marketRemoveQueue = async.queue(function(listingid, next) { marketRemoveQueueWorker(listingid, false, function(success) { if (success) { setTimeout(function() { next(); }, getRandomInt(50, 100)); } else { setTimeout(function() { marketRemoveQueueWorker(listingid, true, function(success) { next(); // Go to the next queue item, regardless of success. }); }, getRandomInt(30000, 45000)); } }); }, 10); function marketRemoveQueueWorker(listingid, ignoreErrors, callback) { var listingUI = getListingFromLists(listingid).elm; market.removeListing(listingid, function(errorRemove, data) { if (!errorRemove) { $('.actual_content', listingUI).css('background', COLOR_SUCCESS); setTimeout(function() { removeListingFromLists(listingid); var numberOfListings = marketLists[0].size; if (numberOfListings > 0) { $('#my_market_selllistings_number').text((numberOfListings).toString()); // This seems identical to the number of sell listings. $('#my_market_activelistings_number').text((numberOfListings).toString()); } }, 3000); return callback(true); } else { $('.actual_content', listingUI).css('background', COLOR_ERROR); return callback(false); } }); } var marketListingsItemsQueue = async.queue(function(listing, next) { $.get(window.location.protocol + '//steamcommunity.com/market/mylistings?count=100&start=' + listing, function(data) { if (!data || !data.success) { next(); return; } var myMarketListings = $('#tabContentsMyActiveMarketListingsRows'); var nodes = $.parseHTML(data.results_html); var rows = $('.market_listing_row', nodes); myMarketListings.append(rows); // g_rgAssets unsafeWindow.MergeWithAssetArray(data.assets); // This is a method from Steam. next(); }, 'json') .fail(function(data) { next(); return; }); }, 1); marketListingsItemsQueue.drain = function() { var myMarketListings = $('#tabContentsMyActiveMarketListingsRows'); myMarketListings.checkboxes('range', true); // Sometimes the Steam API is returning duplicate entries (especially during item listing), filter these. var seen = {}; $('.market_listing_row', myMarketListings).each(function() { var item_id = $(this).attr('id'); if (seen[item_id]) $(this).remove(); else seen[item_id] = true; // Remove listings awaiting confirmations, they are already listed separately. if ($('.item_market_action_button', this).attr('href').toLowerCase() .includes('CancelMarketListingConfirmation'.toLowerCase())) $(this).remove(); // Remove buy order listings, they are already listed separately. if ($('.item_market_action_button', this).attr('href').toLowerCase() .includes('CancelMarketBuyOrder'.toLowerCase())) $(this).remove(); }); // Now add the market checkboxes. addMarketCheckboxes(); // Show the listings again, rendering is done. $('#market_listings_spinner').remove(); myMarketListings.show(); fillMarketListingsQueue(); injectJs(function() { g_bMarketWindowHidden = true; // Limits the number of requests made to steam by stopping constant polling of popular listings. }); }; function fillMarketListingsQueue() { $('.market_home_listing_table').each(function(e) { // Not for popular / new / recently sold items (bottom of page). if ($('.my_market_header', $(this)).length == 0) return; // Buy orders and listings confirmations are not grouped like the sell listings, add this so pagination works there as well. if (!$(this).attr('id')) { $(this).attr('id', 'market-listing-' + e); $(this).append('
') $('.market_listing_row', $(this)).appendTo($('#market-listing-container-' + e)); } else { $(this).children().last().addClass("market_listing_see"); } addMarketPagination($('.market_listing_see', this).last()); sortMarketListings($(this), false, false, true); }); var totalPriceBuyer = 0; var totalPriceSeller = 0; // Add the listings to the queue to be checked for the price. for (var i = 0; i < marketLists.length; i++) { for (var j = 0; j < marketLists[i].items.length; j++) { var listingid = replaceNonNumbers(marketLists[i].items[j].values().market_listing_item_name); var assetInfo = getAssetInfoFromListingId(listingid); if (!isNaN(assetInfo.priceBuyer)) totalPriceBuyer += assetInfo.priceBuyer; if (!isNaN(assetInfo.priceSeller)) totalPriceSeller += assetInfo.priceSeller; marketListingsQueue.push({ listingid, appid: assetInfo.appid, contextid: assetInfo.contextid, assetid: assetInfo.assetid }); } } $('#my_market_selllistings_number').append(', ' + (totalPriceBuyer / 100.0).toFixed(2) + currencySymbol + ' ➤ ' + (totalPriceSeller / 100.0).toFixed(2) + currencySymbol + ''); } // Gets the asset info (appid/contextid/assetid) based on a listingid. function getAssetInfoFromListingId(listingid) { var listing = getListingFromLists(listingid); if (listing == null) { return {}; } var actionButton = $('.item_market_action_button', listing.elm).attr('href'); // Market buy orders have no asset info. if (actionButton == null || actionButton.toLowerCase().includes('cancelmarketbuyorder')) return {}; var priceBuyer = getPriceFromMarketListing($('.market_listing_price > span:nth-child(1) > span:nth-child(1)', listing.elm).text()); var priceSeller = getPriceFromMarketListing($('.market_listing_price > span:nth-child(1) > span:nth-child(3)', listing.elm).text()); var itemIds = actionButton.split(','); var appid = replaceNonNumbers(itemIds[2]); var contextid = replaceNonNumbers(itemIds[3]); var assetid = replaceNonNumbers(itemIds[4]); return { appid, contextid, assetid, priceBuyer, priceSeller }; } // Adds pagination and search options to the market item listings. function addMarketPagination(market_listing_see) { market_listing_see.addClass('list'); market_listing_see.before('
    '); market_listing_see.after('
      '); $('.market_listing_table_header', market_listing_see.parent()) .append(''); var options = { valueNames: [ 'market_listing_game_name', 'market_listing_item_name_link', 'market_listing_price', 'market_listing_listed_date', { name: 'market_listing_item_name', attr: 'id' } ], pagination: [{ name: "paginationTop", paginationClass: "paginationTop", innerWindow: 100, outerWindow: 100, left: 100, right: 100 }, { name: "paginationBottom", paginationClass: "paginationBottom", innerWindow: 100, outerWindow: 100, left: 100, right: 100 }], page: parseInt(getSettingWithDefault(SETTING_MARKET_PAGE_COUNT)) }; var list = new List(market_listing_see.parent().attr('id'), options); list.on('searchComplete', updateMarketSelectAllButton); marketLists.push(list); } // Adds checkboxes to market listings. function addMarketCheckboxes() { $('.market_listing_row').each(function() { // Don't add it again, one time is enough. if ($('.market_listing_select', this).length == 0) { $('.market_listing_cancel_button', $(this)).append('
      ' + '' + '
      '); $('.market_select_item', this).change(function(e) { updateMarketSelectAllButton(); }); } }); } // Process the market listings. function processMarketListings() { addMarketCheckboxes(); if (currentPage == PAGE_MARKET) { // Load the market listings. var currentCount = 0; var totalCount = 0; if (typeof unsafeWindow.g_oMyListings !== 'undefined' && unsafeWindow.g_oMyListings != null && unsafeWindow.g_oMyListings.m_cTotalCount != null) totalCount = unsafeWindow.g_oMyListings.m_cTotalCount; else { totalCount = parseInt($('#my_market_selllistings_number').text()); } if (isNaN(totalCount) || totalCount == 0) { fillMarketListingsQueue(); return; } $('#tabContentsMyActiveMarketListingsRows').html(''); // Clear the default listings. $('#tabContentsMyActiveMarketListingsRows').hide(); // Hide all listings until everything has been loaded. // Hide Steam's paging controls. $('#tabContentsMyActiveMarketListings_ctn').hide(); $('.market_pagesize_options').hide(); // Show the spinner so the user knows that something is going on. $('.my_market_header').eq(0).append('
      ' + spinnerBlock + '
      Loading market listings
      ' + '
      '); while (currentCount < totalCount) { marketListingsItemsQueue.push(currentCount); currentCount += 100; } } else { // This is on a market item page. $('.market_home_listing_table').each(function(e) { // Not on 'x requests to buy at y,yy or lower'. if ($('#market_buyorder_info_show_details', $(this)).length > 0) return; $(this).children().last().addClass("market_listing_see"); addMarketPagination($('.market_listing_see', this).last()); sortMarketListings($(this), false, false, true); }); $('#tabContentsMyActiveMarketListingsRows > .market_listing_row').each(function() { var listingid = $(this).attr('id').replace('mylisting_', '').replace('mybuyorder_', '').replace('mbuyorder_', ''); var assetInfo = getAssetInfoFromListingId(listingid); // There's only one item in the g_rgAssets on a market listing page. var existingAsset = null; for (var appid in unsafeWindow.g_rgAssets) { for (var contextid in unsafeWindow.g_rgAssets[appid]) { for (var assetid in unsafeWindow.g_rgAssets[appid][contextid]) { existingAsset = unsafeWindow.g_rgAssets[appid][contextid][assetid]; break; } } } // appid and contextid are identical, only the assetid is different for each asset. unsafeWindow.g_rgAssets[appid][contextid][assetInfo.assetid] = existingAsset; marketListingsQueue.push({ listingid, appid: assetInfo.appid, contextid: assetInfo.contextid, assetid: assetInfo.assetid }); }) } } // Update the select/deselect all button on the market. function updateMarketSelectAllButton() { $('.market_listing_buttons').each(function() { var selectionGroup = $(this).parent().parent(); var invert = $('.market_select_item:checked', selectionGroup).length == $('.market_select_item', selectionGroup).length; if ($('.market_select_item', selectionGroup).length == 0) // If there are no items to select, keep it at Select all. invert = false; $('.select_all > span', selectionGroup).text(invert ? 'Deselect all' : 'Select all'); }); } // Sort the market listings. function sortMarketListings(elem, isPrice, isDate, isName) { var list = getListFromContainer(elem); if (list == null) { console.log('Invalid parameter, could not find a list matching elem.'); return; } // Change sort order (asc/desc). var nextSort = isPrice ? 1 : (isDate ? 2 : 3); var asc = true; // (Re)set the asc/desc arrows. const arrow_down = '🡻'; const arrow_up = '🡹'; $('.market_listing_table_header > span', elem).each(function() { if ($(this).hasClass('market_listing_edit_buttons')) return; if ($(this).text().includes(arrow_up)) asc = false; $(this).text($(this).text().replace(' ' + arrow_down, '').replace(' ' + arrow_up, '')); }) var market_listing_selector; if (isPrice) { market_listing_selector = $('.market_listing_table_header', elem).children().eq(1); } else if (isDate) { market_listing_selector = $('.market_listing_table_header', elem).children().eq(2); } else if (isName) { market_listing_selector = $('.market_listing_table_header', elem).children().eq(3); } market_listing_selector.text(market_listing_selector.text() + ' ' + (asc ? arrow_up : arrow_down)); if (list.sort == null) return; if (isName) { list.sort('', { order: asc ? "asc" : "desc", sortFunction: function(a, b) { if (a.values().market_listing_game_name.toLowerCase() .localeCompare(b.values().market_listing_game_name.toLowerCase()) == 0) { return a.values().market_listing_item_name_link.toLowerCase() .localeCompare(b.values().market_listing_item_name_link.toLowerCase()); } return a.values().market_listing_game_name.toLowerCase() .localeCompare(b.values().market_listing_game_name.toLowerCase()); } }); } else if (isDate) { var currentMonth = DateTime.local().month; list.sort('market_listing_listed_date', { order: asc ? "asc" : "desc", sortFunction: function(a, b) { var firstDate = DateTime.fromString((a.values().market_listing_listed_date).trim(), 'd MMM'); var secondDate = DateTime.fromString((b.values().market_listing_listed_date).trim(), 'd MMM'); if (firstDate == null || secondDate == null) { return 0; } if (firstDate.month > currentMonth) firstDate = firstDate.plus({ years: -1}); if (secondDate.month > currentMonth) secondDate = secondDate.plus({ years: -1}); if (firstDate > secondDate) return 1; if (firstDate === secondDate) return 0; return -1; } }) } else if (isPrice) { list.sort('market_listing_price', { order: asc ? "asc" : "desc", sortFunction: function(a, b) { var listingPriceA = $(a.values().market_listing_price).text(); listingPriceA = listingPriceA.substr(0, listingPriceA.indexOf('(')); listingPriceA = listingPriceA.replace('--', '00'); var listingPriceB = $(b.values().market_listing_price).text(); listingPriceB = listingPriceB.substr(0, listingPriceB.indexOf('(')); listingPriceB = listingPriceB.replace('--', '00'); var firstPrice = parseInt(replaceNonNumbers(listingPriceA)); var secondPrice = parseInt(replaceNonNumbers(listingPriceB)); return firstPrice - secondPrice; } }) } } function getListFromContainer(group) { for (var i = 0; i < marketLists.length; i++) { if (group.attr('id') == $(marketLists[i].listContainer).attr('id')) return marketLists[i]; } } function getListingFromLists(listingid) { // Sometimes listing ids are contained in multiple lists (?), use the last one available as this is the one we're most likely interested in. for (var i = marketLists.length - 1; i >= 0; i--) { var values = marketLists[i].get("market_listing_item_name", 'mylisting_' + listingid + '_name'); if (values != null && values.length > 0) { return values[0]; } values = marketLists[i].get("market_listing_item_name", 'mbuyorder_' + listingid + '_name'); if (values != null && values.length > 0) { return values[0]; } } } function removeListingFromLists(listingid) { for (var i = 0; i < marketLists.length; i++) { marketLists[i].remove("market_listing_item_name", 'mylisting_' + listingid + '_name'); marketLists[i].remove("market_listing_item_name", 'mbuyorder_' + listingid + '_name'); } } // Initialize the market UI. function initializeMarketUI() { // Sell orders. $('.my_market_header').first().append( ''); // Listings confirmations and buy orders. $('.my_market_header').slice(1).append( ''); $('.market_listing_table_header').on('click', 'span', function() { if ($(this).hasClass('market_listing_edit_buttons') || $(this).hasClass('item_market_action_button_contents')) return; var isPrice = $('.market_listing_table_header', $(this).parent().parent()).children().eq(1).text() == $(this).text(); var isDate = $('.market_listing_table_header', $(this).parent().parent()).children().eq(2).text() == $(this).text(); var isName = $('.market_listing_table_header', $(this).parent().parent()).children().eq(3).text() == $(this).text(); sortMarketListings($(this).parent().parent(), isPrice, isDate, isName); }); $('.select_all').on('click', '*', function() { var selectionGroup = $(this).parent().parent().parent().parent(); var marketList = getListFromContainer(selectionGroup); var invert = $('.market_select_item:checked', selectionGroup).length == $('.market_select_item', selectionGroup).length; for (var i = 0; i < marketList.matchingItems.length; i++) { $('.market_select_item', marketList.matchingItems[i].elm).prop('checked', !invert); } updateMarketSelectAllButton(); }); $('.select_five_from_page').on('click', '*', function() { var selectionGroup = $(this).parent().parent().parent().parent(); var marketList = getListFromContainer(selectionGroup); var invert = $('.market_select_item:checked', selectionGroup).length == $('.market_select_item', selectionGroup).length; var count = 0 for (var i = 0; i < marketList.matchingItems.length; i++) { if(count == 5){ break; } if(!$('.market_select_item', marketList.matchingItems[i].elm).prop('checked')){ $('.market_select_item', marketList.matchingItems[i].elm).prop('checked', true); count += 1; } } updateMarketSelectAllButton(); }); $('.select_twentyfive_from_page').on('click', '*', function() { var selectionGroup = $(this).parent().parent().parent().parent(); var marketList = getListFromContainer(selectionGroup); var invert = $('.market_select_item:checked', selectionGroup).length == $('.market_select_item', selectionGroup).length; var count = 0 for (var i = 0; i < marketList.matchingItems.length; i++) { if(count == 25){ break; } if(!$('.market_select_item', marketList.matchingItems[i].elm).prop('checked')){ $('.market_select_item', marketList.matchingItems[i].elm).prop('checked', true); count += 1; } } updateMarketSelectAllButton(); }); $('#market_removelisting_dialog_accept').on('click', '*', function() { // This is when a user removed an item through the Remove/Cancel button. // Ideally, it should remove this item from the list (instead of just the UI element which Steam does), but I'm not sure how to get the current item yet. window.location.reload(); }); $('.select_overpriced').on('click', '*', function() { var selectionGroup = $(this).parent().parent().parent().parent(); var marketList = getListFromContainer(selectionGroup); for (var i = 0; i < marketList.matchingItems.length; i++) { if ($(marketList.matchingItems[i].elm).hasClass('overpriced')) { $('.market_select_item', marketList.matchingItems[i].elm).prop('checked', true); } } $('.market_listing_row', selectionGroup).each(function(index) { if ($(this).hasClass('overpriced')) $('.market_select_item', $(this)).prop('checked', true); }); updateMarketSelectAllButton(); }); $('.remove_selected').on('click', '*', function() { var selectionGroup = $(this).parent().parent().parent().parent(); var marketList = getListFromContainer(selectionGroup); for (var i = 0; i < marketList.matchingItems.length; i++) { if ($('.market_select_item', $(marketList.matchingItems[i].elm)).prop('checked')) { var listingid = replaceNonNumbers(marketList.matchingItems[i].values().market_listing_item_name); marketRemoveQueue.push(listingid); } } }); $('.market_relist_auto').change(function() { setSetting(SETTING_RELIST_AUTOMATICALLY, $('.market_relist_auto').is(":checked") ? 1 : 0); }); $('.relist_overpriced').on('click', '*', function() { var selectionGroup = $(this).parent().parent().parent().parent(); var marketList = getListFromContainer(selectionGroup); for (var i = 0; i < marketList.matchingItems.length; i++) { if ($(marketList.matchingItems[i].elm).hasClass('overpriced')) { var listingid = replaceNonNumbers(marketList.matchingItems[i].values().market_listing_item_name); queueOverpricedItemListing(listingid); } } }); $('.relist_selected').on('click', '*', function() { var selectionGroup = $(this).parent().parent().parent().parent(); var marketList = getListFromContainer(selectionGroup); for (var i = 0; i < marketList.matchingItems.length; i++) { if ($(marketList.matchingItems[i].elm) && $('.market_select_item', $(marketList.matchingItems[i].elm)).prop('checked')) { var listingid = replaceNonNumbers(marketList.matchingItems[i].values().market_listing_item_name); queueOverpricedItemListing(listingid); } } }); $('#see_settings').remove(); $('#global_action_menu').prepend('⬖ Steam Economy Enhancer'); $('#see_settings').on('click', '*', () => openSettings()); processMarketListings(); } } //#endregion //#region Tradeoffers if (currentPage == PAGE_TRADEOFFER) { // Gets the trade offer's inventory items from the active inventory. function getTradeOfferInventoryItems() { var arr = []; for (var child in getActiveInventory().rgChildInventories) { for (var key in getActiveInventory().rgChildInventories[child].rgInventory) { var value = getActiveInventory().rgChildInventories[child].rgInventory[key]; if (typeof value === 'object') { // Merges the description in the normal object, this is done to keep the layout consistent with the market page, which is also flattened. Object.assign(value, value.description); // Includes the id of the inventory item. value['id'] = key; arr.push(value); } } } // Some inventories (e.g. BattleBlock Theater) do not have child inventories, they have just one. for (var key in getActiveInventory().rgInventory) { var value = getActiveInventory().rgInventory[key]; if (typeof value === 'object') { // Merges the description in the normal object, this is done to keep the layout consistent with the market page, which is also flattened. Object.assign(value, value.description); // Includes the id of the inventory item. value['id'] = key; arr.push(value); } } return arr; } function sumTradeOfferAssets(assets, user) { var total = {}; var totalPrice = 0; for (var i = 0; i < assets.length; i++) { var rgItem = user.findAsset(assets[i].appid, assets[i].contextid, assets[i].assetid); var text = ''; if (rgItem != null) { if (rgItem.element) { var inventoryPriceElements = $('.inventory_item_price', rgItem.element); if (inventoryPriceElements.length) { var firstPriceElement = inventoryPriceElements[0]; var classes = $(firstPriceElement).attr('class').split(' '); for (var c in classes) { if (classes[c].toString().includes('price_')) { var price = parseInt(classes[c].toString().replace('price_', '')); totalPrice += price; } } } } if (rgItem.original_amount != null && rgItem.amount != null) { var originalAmount = parseInt(rgItem.original_amount); var currentAmount = parseInt(rgItem.amount); var usedAmount = originalAmount - currentAmount; text += usedAmount.toString() + 'x '; } text += rgItem.name; if (rgItem.type != null && rgItem.type.length > 0) { text += ' (' + rgItem.type + ')'; } } else text = 'Unknown Item'; if (text in total) total[text] = total[text] + 1; else total[text] = 1; } var sortable = []; for (var item in total) sortable.push([item, total[item]]) sortable.sort(function(a, b) { return a[1] - b[1]; }).reverse(); var totalText = 'Number of unique items: ' + sortable.length + ', worth ' + (totalPrice / 100).toFixed(2) + currencySymbol + '

      '; var totalNumOfItems = 0; for (var i = 0; i < sortable.length; i++) { totalText += sortable[i][1] + 'x ' + sortable[i][0] + '
      '; totalNumOfItems += sortable[i][1]; } totalText += '
      Total items: ' + totalNumOfItems + '
      '; return totalText; } } var lastTradeOfferSum = 0; function hasLoadedAllTradeOfferItems() { for (var i = 0; i < unsafeWindow.g_rgCurrentTradeStatus.them.assets.length; i++) { var asset = UserThem.findAsset(unsafeWindow.g_rgCurrentTradeStatus.them.assets[i].appid, unsafeWindow.g_rgCurrentTradeStatus.them.assets[i].contextid, unsafeWindow.g_rgCurrentTradeStatus.them.assets[i].assetid); if (asset == null) return false; } for (var i = 0; i < unsafeWindow.g_rgCurrentTradeStatus.me.assets.length; i++) { var asset = UserYou.findAsset(unsafeWindow.g_rgCurrentTradeStatus.me.assets[i].appid, unsafeWindow.g_rgCurrentTradeStatus.me.assets[i].contextid, unsafeWindow.g_rgCurrentTradeStatus.me.assets[i].assetid); if (asset == null) return false; } return true; } function initializeTradeOfferUI() { var updateInventoryPrices = function() { if (getSettingWithDefault(SETTING_TRADEOFFER_PRICE_LABELS) == 1) { setInventoryPrices(getTradeOfferInventoryItems()); } }; var updateInventoryPricesInTrade = function() { var items = []; for (var i = 0; i < unsafeWindow.g_rgCurrentTradeStatus.them.assets.length; i++) { var asset = UserThem.findAsset(unsafeWindow.g_rgCurrentTradeStatus.them.assets[i].appid, unsafeWindow.g_rgCurrentTradeStatus.them.assets[i].contextid, unsafeWindow.g_rgCurrentTradeStatus.them.assets[i].assetid); items.push(asset); } for (var i = 0; i < unsafeWindow.g_rgCurrentTradeStatus.me.assets.length; i++) { var asset = UserYou.findAsset(unsafeWindow.g_rgCurrentTradeStatus.me.assets[i].appid, unsafeWindow.g_rgCurrentTradeStatus.me.assets[i].contextid, unsafeWindow.g_rgCurrentTradeStatus.me.assets[i].assetid); items.push(asset); } setInventoryPrices(items); }; $('.trade_right > div > div > div > .trade_item_box').observe('childlist subtree', function(record) { if (!hasLoadedAllTradeOfferItems()) return; var currentTradeOfferSum = unsafeWindow.g_rgCurrentTradeStatus.me.assets.length + unsafeWindow.g_rgCurrentTradeStatus.them.assets.length; if (lastTradeOfferSum != currentTradeOfferSum) { updateInventoryPricesInTrade(); } lastTradeOfferSum = currentTradeOfferSum; $('#trade_offer_your_sum').remove(); $('#trade_offer_their_sum').remove(); var your_sum = sumTradeOfferAssets(unsafeWindow.g_rgCurrentTradeStatus.me.assets, UserYou); var their_sum = sumTradeOfferAssets(unsafeWindow.g_rgCurrentTradeStatus.them.assets, UserThem); $('div.offerheader:nth-child(1) > div:nth-child(3)').append('
      ' + your_sum + '
      '); $('div.offerheader:nth-child(3) > div:nth-child(3)').append('
      ' + their_sum + '
      '); }); // Load after the inventory is loaded. updateInventoryPrices(); $('#inventory_pagecontrols').observe('childlist', '*', function(record) { updateInventoryPrices(); }); // This only works with a new trade offer. if (!window.location.href.includes('tradeoffer/new')) return; $('#inventory_displaycontrols').append( '
      ' + ''); $('.select_all').on('click', '*', function() { $('.inventory_ctn:visible > .inventory_page:visible > .itemHolder:visible').delayedEach(250, function(i, it) { var item = it.rgItem; if (item.is_stackable) return; if (!item.tradable) return; unsafeWindow.MoveItemToTrade(it); }); }); } //#endregion //#region Settings function openSettings() { var price_options = $('
      ' + '
      ' + 'Calculate prices as the: ' + '
      ' + '
      ' + '
      ' + 'Hours to use for the average history calculated price: ' + '
      ' + '
      ' + 'The value to add to the calculated price (minimum and maximum are respected): ' + '
      ' + '
      ' + '
      ' + 'Use the second lowest sell listing when the lowest sell listing has a low quantity: ' + '
      ' + '
      ' + '
      ' + 'Don\'t check market listings with prices of and below: ' + '
      ' + '
      ' + '
      ' + 'Show price labels in inventory: ' + '
      ' + '
      ' + 'Show price labels in trade offers: ' + '
      ' + '
      ' + '
      ' + 'Minimum:  ' + 'and maximum:  price for normal cards' + '
      ' + '
      ' + '
      ' + 'Minimum:  ' + 'and maximum:  price for foil cards' + '
      ' + '
      ' + '
      ' + 'Minimum:  ' + 'and maximum:  price for other items' + '
      ' + '
      ' + '
      ' + 'Market items per page: ' + '
      ' + '
      ' + 'Automatically relist overpriced market listings (slow on large inventories): ' + '' + '
      ' + '
      ' + '
      '); var dialog = unsafeWindow.ShowConfirmDialog('Steam Economy Enhancer', price_options).done(function() { setSetting(SETTING_MIN_NORMAL_PRICE, $('#' + SETTING_MIN_NORMAL_PRICE, price_options).val()); setSetting(SETTING_MAX_NORMAL_PRICE, $('#' + SETTING_MAX_NORMAL_PRICE, price_options).val()); setSetting(SETTING_MIN_FOIL_PRICE, $('#' + SETTING_MIN_FOIL_PRICE, price_options).val()); setSetting(SETTING_MAX_FOIL_PRICE, $('#' + SETTING_MAX_FOIL_PRICE, price_options).val()); setSetting(SETTING_MIN_MISC_PRICE, $('#' + SETTING_MIN_MISC_PRICE, price_options).val()); setSetting(SETTING_MAX_MISC_PRICE, $('#' + SETTING_MAX_MISC_PRICE, price_options).val()); setSetting(SETTING_PRICE_OFFSET, $('#' + SETTING_PRICE_OFFSET, price_options).val()); setSetting(SETTING_PRICE_MIN_CHECK_PRICE, $('#' + SETTING_PRICE_MIN_CHECK_PRICE, price_options).val()); setSetting(SETTING_PRICE_ALGORITHM, $('#' + SETTING_PRICE_ALGORITHM, price_options).val()); setSetting(SETTING_PRICE_IGNORE_LOWEST_Q, $('#' + SETTING_PRICE_IGNORE_LOWEST_Q, price_options).prop('checked') ? 1 : 0); setSetting(SETTING_PRICE_HISTORY_HOURS, $('#' + SETTING_PRICE_HISTORY_HOURS, price_options).val()); setSetting(SETTING_MARKET_PAGE_COUNT, $('#' + SETTING_MARKET_PAGE_COUNT, price_options).val()); setSetting(SETTING_RELIST_AUTOMATICALLY, $('#' + SETTING_RELIST_AUTOMATICALLY, price_options).prop('checked') ? 1 : 0); setSetting(SETTING_INVENTORY_PRICE_LABELS, $('#' + SETTING_INVENTORY_PRICE_LABELS, price_options).prop('checked') ? 1 : 0); setSetting(SETTING_TRADEOFFER_PRICE_LABELS, $('#' + SETTING_TRADEOFFER_PRICE_LABELS, price_options).prop('checked') ? 1 : 0); window.location.reload(); }); } //#endregion //#region UI injectCss('.ui-selected { outline: 2px dashed #FFFFFF; } ' + '#logger { color: #767676; font-size: 12px;margin-top:16px; max-height: 200px; overflow-y: auto; }' + '.trade_offer_sum { color: #767676; font-size: 12px;margin-top:8px; }' + '.trade_offer_buttons { margin-top: 12px; }' + '.market_commodity_orders_table { font-size:12px; font-family: "Motiva Sans", Sans-serif; font-weight: 300; }' + '.market_commodity_orders_table th { padding-left: 10px; }' + '#listings_group { display: flex; justify-content: space-between; margin-bottom: 8px; }' + '#listings_sell { text-align: right; color: #589328; font-weight:600; }' + '#listings_buy { text-align: right; color: #589328; font-weight:600; }' + '.market_listing_my_price { height: 50px; padding-right:6px; }' + '.market_listing_edit_buttons.actual_content { width:276px; transition-property: background-color, border-color; transition-timing-function: linear; transition-duration: 0.5s;}' + '.market_listing_buttons { margin-top: 6px; background: rgba(0, 0, 0, 0.4); padding: 5px 0px 1px 0px; }' + '.market_listing_button { margin-right: 4px; }' + '.market_listing_button_right { float:right; }' + '.market_listing_button:first-child { margin-left: 4px; }' + '.market_listing_label_right { float:right; font-size:12px; margin-top:1px; }' + '.market_listing_select { position: absolute; top: 16px;right: 10px; display: flex; }' + '#market_listing_relist { vertical-align: middle; position: relative; bottom: -1px; right: 2px; }' + '.pick_and_sell_button > a { vertical-align: middle; }' + '.market_relist_auto { margin-bottom: 8px; }' + '.market_relist_auto_label { margin-right: 6px; }' + '.quick_sell { margin-right: 4px; }' + '.spinner{margin:10px auto;width:50px;height:40px;text-align:center;font-size:10px;}.spinner > div{background-color:#ccc;height:100%;width:6px;display:inline-block;-webkit-animation:sk-stretchdelay 1.2s infinite ease-in-out;animation:sk-stretchdelay 1.2s infinite ease-in-out}.spinner .rect2{-webkit-animation-delay:-1.1s;animation-delay:-1.1s}.spinner .rect3{-webkit-animation-delay:-1s;animation-delay:-1s}.spinner .rect4{-webkit-animation-delay:-.9s;animation-delay:-.9s}.spinner .rect5{-webkit-animation-delay:-.8s;animation-delay:-.8s}@-webkit-keyframes sk-stretchdelay{0%,40%,100%{-webkit-transform:scaleY(0.4)}20%{-webkit-transform:scaleY(1.0)}}@keyframes sk-stretchdelay{0%,40%,100%{transform:scaleY(0.4);-webkit-transform:scaleY(0.4)}20%{transform:scaleY(1.0);-webkit-transform:scaleY(1.0)}}' + '#market_name_search { float: right; background: rgba(0, 0, 0, 0.25); color: white; border: none;height: 25px; padding-left: 6px;}' + '.price_option_price { width: 100px }' + '#see_settings { background: #26566c; margin-right: 10px; height: 24px; line-height:24px; display:inline-block; padding: 0px 6px; }' + '.inventory_item_price { top: 0px;position: absolute;right: 0;background: #3571a5;padding: 2px;color: white; font-size:11px; border: 1px solid #666666;}' + '.separator-large {display:inline-block;width:6px;}' + '.separator-small {display:inline-block;width:1px;}' + '.separator-btn-right {margin-right:12px;}' + '.pagination { padding-left: 0px; }' + '.pagination li { display:inline-block; padding: 5px 10px;background: rgba(255, 255, 255, 0.10); margin-right: 6px; border: 1px solid #666666; }' + '.pagination li.active { background: rgba(255, 255, 255, 0.25); }'); $(document).ready(function() { // Make sure the user is logged in, there's not much we can do otherwise. if (!isLoggedIn) { return; } if (currentPage == PAGE_INVENTORY) { initializeInventoryUI(); } if (currentPage == PAGE_MARKET || currentPage == PAGE_MARKET_LISTING) { initializeMarketUI(); } if (currentPage == PAGE_TRADEOFFER) { initializeTradeOfferUI(); } }); function injectCss(css) { var head, style; head = document.getElementsByTagName('head')[0]; if (!head) { return; } style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = css; head.appendChild(style); } function injectJs(js) { var script = document.createElement('script'); script.setAttribute("type", "application/javascript"); script.textContent = '(' + js + ')();'; document.body.appendChild(script); document.body.removeChild(script); } $.fn.delayedEach = function(timeout, callback, continuous) { var $els, iterator; $els = this; iterator = function(index) { var cur; if (index >= $els.length) { if (!continuous) { return; } index = 0; } cur = $els[index]; callback.call(cur, index, cur); setTimeout(function() { iterator(++index); }, timeout); }; iterator(0); }; String.prototype.replaceAll = function(search, replacement) { var target = this; return target.replace(new RegExp(search, 'g'), replacement); }; //#endregion })(jQuery, async);