您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically list items in your Steam inventory.
// ==UserScript== // @name Steam Inventory Auto Sell Script // @description Automatically list items in your Steam inventory. // @version 1.0.0 // @author RLAlpha49 // @namespace https://github.com/RLAlpha49/Steam-Inventory-Auto-Sell-Script // @license MIT // @match https://steamcommunity.com/id/*/inventory* // @grant none // ==/UserScript== (function() { 'use strict'; // Set to true to enable debug logging const DEBUG = false; function log(...args) { // User-facing info logs (not too many) console.log('[Steam Auto Sell Helper]', ...args); } function debug(...args) { if (DEBUG) console.debug('[Steam Auto Sell Helper][DEBUG]', ...args); } function isOwnInventory() { // Get account pulldown text const accountPulldown = document.getElementById('account_pulldown'); if (!accountPulldown) return false; const accountName = accountPulldown.textContent.trim(); // Get persona name text const personaNameElem = document.querySelector('.whiteLink.persona_name_text_content'); if (!personaNameElem) return false; const personaName = personaNameElem.textContent.trim(); // Compare return accountName === personaName; } function addStartStopButton() { if (document.getElementById('my-userscript-toggle-btn')) return; // Prevent duplicate const btn = document.createElement('button'); btn.id = 'my-userscript-toggle-btn'; btn.textContent = 'Start Script'; btn.style.float = 'right'; btn.style.margin = '8px'; btn.style.zIndex = 10000; let running = false; let stopRequested = false; async function waitForMarketableInput(timeout = 5000) { const start = Date.now(); while (Date.now() - start < timeout) { const input = document.querySelector('input[id*="misc_marketable"]'); if (input) return input; await new Promise(res => setTimeout(res, 100)); } return null; } async function waitForPrice(marketActionsDiv, visibleIndex, link, maxRetries = 3, itemInfoDiv = null) { let retries = 0; const waitTime = 15000; // 15 seconds while (retries < maxRetries && !stopRequested) { // Only check price once per retry loop if (retries > 0) { // Re-click the itemHolder link to reload the item info if (link) { link.click(); } // Wait 1 second after click before checking price await new Promise(res => setTimeout(res, 1000)); // Re-select the visible iteminfo div and marketActionsDiv after waiting let itemInfo0 = document.getElementById('iteminfo0'); let itemInfo1 = document.getElementById('iteminfo1'); if (itemInfo0 && itemInfo0.style.display !== 'none') { itemInfoDiv = itemInfo0; } else if (itemInfo1 && itemInfo1.style.display !== 'none') { itemInfoDiv = itemInfo1; } marketActionsDiv = null; if (itemInfoDiv) { marketActionsDiv = itemInfoDiv.querySelector('#iteminfo0_item_market_actions, #iteminfo1_item_market_actions, .item_market_actions'); } } if (marketActionsDiv) { const priceDivs = marketActionsDiv.querySelectorAll('div'); for (const div of priceDivs) { if (div.textContent.includes('Starting at:')) { const match = div.textContent.match(/Starting at:\s*([$€£]?\d+[.,]?\d*)/); if (match) { const price = match[1]; log(`Found price for visible itemHolder #${visibleIndex}: ${price}`); return price; } } } } // On the first failure, check for the alternative sell button // The alternative sell button (a.btn_small.btn_darkblue_white_innerfade) is added by the SteamDB browser extension 'SteamDB Quick Sell'. // It is used as a fallback if the steam website is rate limiting displaying the list price for items. if (retries === 0 && itemInfoDiv) { // Wait half a second before trying to find the alternative sell button await new Promise(res => setTimeout(res, 500)); const altSellBtn = itemInfoDiv.querySelector('a.btn_small.btn_darkblue_white_innerfade'); if (altSellBtn) { // If present, wait up to 10s for it to become enabled let waited = 0; const maxWait = 10000; // 10 seconds const interval = 250; while (altSellBtn.classList.contains('disabled') && waited < maxWait && !stopRequested) { await new Promise(res => setTimeout(res, interval)); waited += interval; } if (!altSellBtn.classList.contains('disabled')) { log(`Clicking alternate sell button for visible itemHolder #${visibleIndex} (early fallback)`); let modalAppeared = false; for (let attempt = 1; attempt <= 3; attempt++) { altSellBtn.click(); debug(`Clicked alternate sell button (attempt ${attempt})`); // Wait up to 1 second for the modal to appear waited = 0; while (waited < 1000) { const modal = document.getElementById('market_sell_dialog'); if (modal && modal.style.display !== 'none') { modalAppeared = true; break; } await new Promise(res => setTimeout(res, 100)); waited += 100; } if (modalAppeared) break; } if (!modalAppeared) { log('Error: Alternative sell modal did not appear after 3 attempts. Skipping item.'); return; } // Skip price input, proceed with SSA and accept const ssaCheckbox = document.getElementById('market_sell_dialog_accept_ssa'); if (ssaCheckbox && !ssaCheckbox.checked) { ssaCheckbox.click(); debug('Checked SSA checkbox.'); } const acceptBtn = document.getElementById('market_sell_dialog_accept'); if (acceptBtn) { acceptBtn.click(); debug('Clicked accept button.'); await new Promise(res => setTimeout(res, 500)); } else { log('Accept button not found (early fallback).'); } const okBtn = document.getElementById('market_sell_dialog_ok'); if (okBtn) { okBtn.click(); debug('Clicked OK button.'); await new Promise(res => setTimeout(res, 500)); // If there is an error, close the modal manually const errorDiv = document.getElementById('market_sell_dialog_error'); if (errorDiv && errorDiv.style.display !== 'none') { if (errorDiv.textContent && errorDiv.textContent.includes('You have too many listings pending confirmation.')) { log('Too many listings pending confirmation. Stopping script.'); stopRequested = true; return; } const closeBtn = document.querySelector('.newmodal_close'); if (closeBtn) { closeBtn.click(); log('Closed modal manually due to error after OK click (early fallback).'); } else { log('Could not find .newmodal_close to close modal after error (early fallback).'); } } // Wait until the modal background is hidden before continuing let modalWaitTries = 0; const maxModalWaitTries = 20; // 20 * 250ms = 5s max while (modalWaitTries < maxModalWaitTries * 2) { const modalBg = document.querySelector('.newmodal_background'); if (!modalBg || modalBg.style.display === 'none') { break; } await new Promise(res => setTimeout(res, 250)); modalWaitTries++; } if (modalWaitTries >= maxModalWaitTries) { // Try to close the modal manually if still open const closeBtn = document.querySelector('.newmodal_close'); if (closeBtn) { closeBtn.click(); log('Modal background did not hide after OK click (early fallback, timeout). Closed modal manually.'); } else { log('Modal background did not hide after OK click (early fallback, timeout). Could not find .newmodal_close to close modal manually.'); } } else { log('Modal background hidden, continuing to next item (early fallback).'); } } else { log('OK button not found (early fallback).'); } // Return a special value to indicate fallback was used return '__FALLBACK_USED__'; } // If still disabled after waiting, continue to retries } } retries++; if (retries < maxRetries) { log(`Price not found for visible itemHolder #${visibleIndex}, retrying in ${waitTime / 1000}s (retry #${retries} of ${maxRetries})...`); await new Promise(res => setTimeout(res, waitTime - 1000)); } } return null; } // Simulate real typing with keyboard events async function simulateTyping(input, text) { input.value = ''; for (const char of text) { const eventOptions = { bubbles: true, cancelable: true, key: char, char, keyCode: char.charCodeAt(0) }; input.dispatchEvent(new KeyboardEvent('keydown', eventOptions)); input.dispatchEvent(new KeyboardEvent('keypress', eventOptions)); input.value += char; input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new KeyboardEvent('keyup', eventOptions)); await new Promise(res => setTimeout(res, 50)); } input.dispatchEvent(new Event('change', { bubbles: true })); } async function clickItemHolders(inventoryPage) { if (!inventoryPage) { log('No inventory_page found for current page!'); return; } const itemHolders = inventoryPage.querySelectorAll('.itemHolder'); log(`Found ${itemHolders.length} .itemHolder elements.`); let visibleIndex = 1; for (let i = 0; i < itemHolders.length && visibleIndex <= 25; i++) { if (stopRequested) { log('Stop requested. Halting immediately.'); break; } const itemHolder = itemHolders[i]; if (itemHolder.style.display === 'none') { debug(`Skipping itemHolder at DOM index ${i} (display: none)`); continue; } const link = itemHolder.querySelector('a.inventory_item_link'); if (link) { debug(`Clicking inventory_item_link in visible itemHolder #${visibleIndex} (DOM index ${i})`); link.click(); // Wait for item info to update await new Promise(res => setTimeout(res, 500)); // Dynamically select the visible iteminfo div let itemInfoDiv = null; const itemInfo0 = document.getElementById('iteminfo0'); const itemInfo1 = document.getElementById('iteminfo1'); if (itemInfo0 && itemInfo0.style.display !== 'none') { itemInfoDiv = itemInfo0; } else if (itemInfo1 && itemInfo1.style.display !== 'none') { itemInfoDiv = itemInfo1; } let marketActionsDiv = null; if (itemInfoDiv) { marketActionsDiv = itemInfoDiv.querySelector('#iteminfo0_item_market_actions, #iteminfo1_item_market_actions, .item_market_actions'); } const price = await waitForPrice(marketActionsDiv, visibleIndex, link, 3, itemInfoDiv); if (stopRequested) { log('Stop requested during price wait. Halting immediately.'); break; } if (!price || price === '__FALLBACK_USED__') { if (!price) { log(`No 'Starting at:' price found for visible itemHolder #${visibleIndex} after retries, attempting fallback.`); } visibleIndex++; continue; } // After finding the price, click the green market action button if (itemInfoDiv) { const sellBtn = itemInfoDiv.querySelector('a.item_market_action_button.item_market_action_button_green'); if (sellBtn) { debug(`Clicking green market action button for visible itemHolder #${visibleIndex}`); sellBtn.click(); // Wait 1 second for the dialog to appear await new Promise(res => setTimeout(res, 1000)); // Set the price in the input const priceInput = document.getElementById('market_sell_buyercurrency_input'); if (priceInput) { await simulateTyping(priceInput, price); await new Promise(res => setTimeout(res, 100)); debug(`Simulated typing price input: ${price} (with keyboard events)`); } else { log('Price input not found.'); } // Check the SSA checkbox const ssaCheckbox = document.getElementById('market_sell_dialog_accept_ssa'); if (ssaCheckbox && !ssaCheckbox.checked) { ssaCheckbox.click(); debug('Checked SSA checkbox.'); } // Click the accept button const acceptBtn = document.getElementById('market_sell_dialog_accept'); if (acceptBtn) { acceptBtn.click(); debug('Clicked accept button.'); // Wait 0.5 second after clicking accept await new Promise(res => setTimeout(res, 500)); } else { log('Accept button not found.'); } // Click the OK button const okBtn = document.getElementById('market_sell_dialog_ok'); if (okBtn) { okBtn.click(); debug('Clicked OK button.'); // If there is an error, close the modal manually const errorDiv = document.getElementById('market_sell_dialog_error'); if (errorDiv && errorDiv.style.display !== 'none') { if (errorDiv.textContent && errorDiv.textContent.includes('You have too many listings pending confirmation.')) { log('Too many listings pending confirmation. Stopping script.'); stopRequested = true; return; } const closeBtn = document.querySelector('.newmodal_close'); if (closeBtn) { closeBtn.click(); log('Closed modal manually due to error after OK click (early fallback).'); } else { log('Could not find .newmodal_close to close modal after error (early fallback).'); } } // Wait until the modal background is hidden before continuing let modalWaitTries = 0; const maxModalWaitTries = 40; // 40 * 250ms = 10s max while (modalWaitTries < maxModalWaitTries) { const modalBg = document.querySelector('.newmodal_background'); if (!modalBg || modalBg.style.display === 'none') { break; } await new Promise(res => setTimeout(res, 250)); modalWaitTries++; } if (modalWaitTries >= maxModalWaitTries) { log('Modal background did not hide after OK click (timeout).'); } else { debug('Modal background hidden, continuing to next item.'); } } else { log('OK button not found.'); } } else { log(`No green market action button found for visible itemHolder #${visibleIndex}`); } } } else { log(`No inventory_item_link found in visible itemHolder #${visibleIndex} (DOM index ${i})`); } await new Promise(res => setTimeout(res, 1000)); visibleIndex++; } log('Item click sequence complete.'); } async function processAllPages() { let page = 1; // Ensure the filter tag is shown and marketable filter is checked only once at the start log('Ensuring filters are set before starting page processing...'); const filterTagCtn = document.querySelector('.filter_tag_button_ctn'); if (filterTagCtn) { const showBtn = filterTagCtn.querySelector('#filter_tag_show'); const hideBtn = filterTagCtn.querySelector('#filter_tag_hide'); if (showBtn && hideBtn) { if (showBtn.style.display !== 'none') { debug('Clicking filter_tag_show to reveal filters...'); showBtn.click(); // Wait for marketable input to appear debug('Waiting for marketable filter input to appear...'); const marketableInput = await waitForMarketableInput(); if (marketableInput) { debug('Marketable filter input appeared.'); } else { log('Timed out waiting for marketable filter input.'); } } else { debug('filter_tag_show is hidden, filters already visible.'); } } else { log('filter_tag_show or filter_tag_hide not found in filter_tag_button_ctn.'); } } else { log('No filter_tag_button_ctn found.'); } // Ensure the marketable filter is checked const marketableInput = document.querySelector('input[id*="misc_marketable"]'); if (marketableInput) { if (!marketableInput.checked) { debug('Checking the marketable filter input...'); marketableInput.click(); await new Promise(res => setTimeout(res, 2000)); debug('Waited 2 seconds after checking marketable filter.'); } else { debug('Marketable filter already checked.'); } } else { log('No marketable filter input found.'); } while (true) { log(`Processing page ${page}...`); // Re-query the current page index and inventory_page each time const pageCurSpan = document.getElementById('pagecontrol_cur'); let inventoryPage = null; if (pageCurSpan) { const pageIndex = parseInt(pageCurSpan.textContent.trim(), 10); const allInventoryPages = document.querySelectorAll('.inventory_page'); if (pageIndex >= 0 && pageIndex < allInventoryPages.length) { inventoryPage = allInventoryPages[pageIndex]; debug(`Using inventory_page at index ${pageIndex}.`); } else { log(`Invalid page index: ${pageIndex}.`); } } else { log('No pagecontrol_cur span found.'); } await clickItemHolders(inventoryPage); if (stopRequested) { log('Stop requested. Stopping immediately.'); break; } const nextBtn = document.getElementById('pagebtn_next'); if (nextBtn && !nextBtn.classList.contains('disabled')) { debug('Clicking next page button...'); nextBtn.click(); debug('Waiting 1.5 seconds for next page to load and styles to update...'); await new Promise(res => setTimeout(res, 1500)); page++; } else { log('No next page or next page button is disabled. Stopping.'); break; } } } btn.onclick = async function() { running = !running; btn.textContent = running ? 'Stop Script' : 'Start Script'; if (running) { stopRequested = false; log('Script started.'); await processAllPages(); running = false; btn.textContent = 'Start Script'; log('Script finished.'); } else { stopRequested = true; log('Script stopped by user.'); } }; const logosDiv = document.getElementById('inventory_logos'); if (logosDiv) { logosDiv.appendChild(btn); } else { // fallback: add to body if not found document.body.appendChild(btn); } } function main() { if (isOwnInventory()) { addStartStopButton(); } } // Wait for DOM to be ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', main); } else { main(); } })();