Skip to content

Instantly share code, notes, and snippets.

@stecman
Last active July 10, 2023 00:37
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save stecman/83a731dc36eae905356d to your computer and use it in GitHub Desktop.
Save stecman/83a731dc36eae905356d to your computer and use it in GitHub Desktop.
Steam auto trader

Maximum Hax

Automates the UI actions (clicks, typing) to sell Steam trading cards from the Steam web interface. To use this:

  1. Log into your Steam account in Chrome
  2. Go to [Username] -> Inventory
  3. Open the Javascript console (View -> Developer -> Javascript Console)
  4. Paste in the entire script below and press enter
  5. To start selling all trading cards in your inventory, type doHax() in the console and press enter
  6. ...
  7. Profit (kind of.. yay! cents!)

The quickest way way to stop the process once it has started is to refresh or close the current tab.

/**
* Schedule a function to run on the next tick
* This is useful to wait for the DOM to render changes that have just been made
*
* @param {function} func
*/
function setImmediate(func) {
setTimeout(func, 0);
}
/**
* Timeout as a promise
* @param {int} time - time in milliseconds to wait
* @return {Promise}
*/
function timeout(time) {
return new Promise(function(resolve, reject) {
setTimeout(resolve, time)
});
}
/**
* Return a promise that resolves once a function returns true
*
* @param {function() : bool} conditionFunc
* @return {Promise}
*/
function wait(conditionFunc) {
return new Promise(function(resolve, reject) {
var interval;
interval = setInterval(function() {
if (conditionFunc()) {
clearInterval(interval);
resolve();
}
}, 100);
});
}
/**
* Round a number to a specified number of decmial places
*
* Javascript's Math.round function doesn't support specifying decimal places
*
* @param {number} number
* @param {int} decimalPlaces
* @return {number}
*/
function round(number, decimalPlaces) {
var offset = Math.pow(10, decimalPlaces);
return Math.round(number * offset) / offset;
}
/**
* Calculate the average of the recent price history for the selected trading card
*
* @return {int} dollars
*/
function calcAveragePrice() {
// Use the last 100 data points displayed in the visible price history graph
// This is equal to about five days most of the time
var data = SellItemDialog.m_plotPriceHistory.data;
var recent = data[0].slice(-100);
var recentTotal = recent.reduce((sum, item) => {
// item is an array in the form: [dateString, priceInDollars, number]
return sum + item[1];
}, 0);
return recentTotal / recent.length;
}
/**
* Calculate the price to sell the current selection for
*
* @return {number} dollars
*/
function calcSalePrice() {
var avg = calcAveragePrice();
var price = round(avg * 0.9, 2);
// Print the amount details in the console
var name = document.getElementById('market_sell_dialog_item_name').innerText;
console.log('Will list %s for %f (average is %f)', name, price, avg);
return price;
}
/**
* Perform all UI actions to sell the selected card
* @return {Promise} - promise to be resolved once the sale operation is complete
*/
function sellSelection() {
return new Promise(function(resolve, reject) {
// Trigger Steam's trade dialog to display for the selected card
SellCurrentSelection();
wait(function() {
// Wait for the price history graph to be rendered
// At this point we know the trading dialog is completely ready to use
return jQuery('#pricehistory:visible').length > 0;
}).then(function() {
// Calculate sale price using the graph data
var price = calcSalePrice();
// Update the "buyer pays" field in the UI (converted to dollars, truncated to 2 dp)
document.getElementById('market_sell_buyercurrency_input').value = price.toFixed(2);
return timeout(50);
}).then(function() {
// Call their event handler to register the amount we're selling for in their code
SellItemDialog.OnBuyerPriceInputKeyUp();
return timeout(50);
}).then(function() {
// Accept terms of sale
document.getElementById('market_sell_dialog_accept_ssa').checked = true;
SellItemDialog.OnAccept(new Event('click'));
// Wait for the UI to update
return timeout(50);
}).then(function() {
// Click the sell button
document.getElementById('market_sell_dialog_accept').click();
// Wait for the final OK button to be visible
return wait(function() {
return jQuery('#market_sell_dialog_ok:visible').length > 0;
});
}).then(function() {
// Click the sell confirmation button
document.getElementById('market_sell_dialog_ok').click();
resolve();
});
});
}
function process(index, cards) {
// Click the "Sell" button in the inventory to bring up the sell dialog for this item
var node = cards[index];
node.click();
timeout(100)
.then(function() {
return sellSelection()
})
.then(function() {
// Once selection is sold, wait for the UI to return
return wait(function() {
return SellItemDialog.m_bWaitingOnServer === false
&& jQuery('#inventories:visible').length > 0;
})
})
.then(function() {
// Process the next item
if (cards[index + 1]) {
process(index + 1, cards);
} else {
console.log('Done!');
}
});
}
function doHax() {
// Get all of the links for trading cards rendered on the current page
// (This may include cards that aren't visible in on the current 25-per-page view)
cards = document.querySelectorAll('.inventory_item_link[href^="#753_6"]');
if (cards.length) {
console.log('Starting to process %d cards', cards.length);
process(0, cards);
} else {
console.log('No cards to process');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment