From 67e82697780b6885aeffb7f84837853fd8897418 Mon Sep 17 00:00:00 2001 From: Heroslfb <109235048+Heroslfb@users.noreply.github.com> Date: Tue, 26 Dec 2023 02:46:17 -0800 Subject: [PATCH 01/13] Update client.py updated 'make_offer_with_url'. guard is optional now --- steampy/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/steampy/client.py b/steampy/client.py index ece1057..82c82a6 100644 --- a/steampy/client.py +++ b/steampy/client.py @@ -367,6 +367,7 @@ def make_offer_with_url( trade_offer_url: str, message: str = '', case_sensitive: bool = True, + confirm_trade: bool = True, ) -> dict: token = get_key_value_from_url(trade_offer_url, 'token', case_sensitive) partner_account_id = get_key_value_from_url(trade_offer_url, 'partner', case_sensitive) @@ -392,7 +393,7 @@ def make_offer_with_url( } response = self._session.post(url, data=params, headers=headers).json() - if response.get('needs_mobile_confirmation'): + if confirm_trade and response.get('needs_mobile_confirmation'): response.update(self._confirm_transaction(response['tradeofferid'])) return response From 633d72e048a73c8ea565fe84b498230b03c3261d Mon Sep 17 00:00:00 2001 From: Heroslfb <109235048+Heroslfb@users.noreply.github.com> Date: Sat, 14 Feb 2026 18:08:56 +0300 Subject: [PATCH 02/13] Update utils.py order of tables on steam market changed. old code rewrite pending confirmations because the active listings block is after pending now --- steampy/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/steampy/utils.py b/steampy/utils.py index fb4f3bc..4df7102 100644 --- a/steampy/utils.py +++ b/steampy/utils.py @@ -168,7 +168,8 @@ def get_market_listings_from_html(html: str) -> dict: for node in nodes: if 'My sell listings' in node.text: - sell_listings_dict = get_sell_listings_from_node(node) + sell_listings_active = get_sell_listings_from_node(node) + sell_listings_dict.update(sell_listings_active) elif 'My listings awaiting confirmation' in node.text: sell_listings_awaiting_conf = get_sell_listings_from_node(node) for listing in sell_listings_awaiting_conf.values(): From 19892d3a77ca6c433bc351e9d830dac3f3575f40 Mon Sep 17 00:00:00 2001 From: Heroslfb <109235048+Heroslfb@users.noreply.github.com> Date: Sat, 14 Feb 2026 18:54:15 +0300 Subject: [PATCH 03/13] Update client.py --- steampy/client.py | 59 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/steampy/client.py b/steampy/client.py index 82c82a6..f2eed89 100644 --- a/steampy/client.py +++ b/steampy/client.py @@ -4,6 +4,7 @@ import urllib.parse as urlparse from typing import List, Union from decimal import Decimal +from urllib.parse import unquote import requests @@ -61,8 +62,8 @@ def __init__( def set_proxies(self, proxies: dict) -> dict: if not isinstance(proxies, dict): raise TypeError( - 'Proxy must be a dict. Example: ' - '\{"http": "http://login:password@host:port"\, "https": "http://login:password@host:port"\}' + r'Proxy must be a dict. Example: ' + r'\{"http": "http://login:password@host:port"\, "https": "http://login:password@host:port"\}' ) if ping_proxy(proxies): @@ -79,6 +80,18 @@ def set_login_cookies(self, cookies: dict) -> None: self.market._set_login_executed(self.steam_guard, self._get_session_id()) + steam_login_secure_cookies = [cookie for cookie in self._session.cookies if cookie.name == 'steamLoginSecure'] + cookie_value = steam_login_secure_cookies[0].value + decoded_cookie_value = unquote(cookie_value) + access_token_parts = decoded_cookie_value.split('||') + if len(access_token_parts) < 2: + print(decoded_cookie_value) + raise ValueError('Access token not found in steamLoginSecure cookie') + + access_token = access_token_parts[1] + # print(f'access token: {access_token}') + self._access_token = access_token + @login_required def get_steam_id(self) -> int: url = SteamUrl.COMMUNITY_URL @@ -111,6 +124,8 @@ def login(self, username: str = None, password: str = None, steam_guard: str = N self.was_login_executed = True self.market._set_login_executed(self.steam_guard, self._get_session_id()) + + @login_required def logout(self) -> None: url = f'{SteamUrl.STORE_URL}/login/logout/' @@ -152,18 +167,18 @@ def is_invalid_api_key(response: requests.Response) -> bool: return msg in response.text @login_required - def get_my_inventory(self, game: GameOptions, merge: bool = True, count: int = 5000) -> dict: + def get_my_inventory(self, game: GameOptions, merge: bool = True, count: int = 1000) -> dict: steam_id = self.steam_guard['steamid'] return self.get_partner_inventory(steam_id, game, merge, count) @login_required def get_partner_inventory( - self, partner_steam_id: str, game: GameOptions, merge: bool = True, count: int = 5000 + self, partner_steam_id: str, game: GameOptions, merge: bool = True, count: int = 1000 ) -> dict: url = '/'.join((SteamUrl.COMMUNITY_URL, 'inventory', partner_steam_id, game.app_id, game.context_id)) params = {'l': 'english', 'count': count} - - response_dict = self._session.get(url, params=params).json() + response = (self._session.get(url, params=params)) + response_dict = response.json() if response_dict is None or response_dict.get('success') != 1: raise ApiException('Success value should be 1.') @@ -176,16 +191,16 @@ def get_trade_offers_summary(self) -> dict: params = {'key': self._api_key} return self.api_call('GET', 'IEconService', 'GetTradeOffersSummary', 'v1', params).json() - def get_trade_offers(self, merge: bool = True) -> dict: + def get_trade_offers(self, merge: bool = True, use_webtoken=False, active_only=1, historical_only=0, time_historical_cutoff='') -> dict: params = { - 'key': self._api_key, + 'key'if not use_webtoken else 'access_token': self._api_key if not use_webtoken else self._access_token, 'get_sent_offers': 1, 'get_received_offers': 1, 'get_descriptions': 1, 'language': 'english', - 'active_only': 1, - 'historical_only': 0, - 'time_historical_cutoff': '', + 'active_only': active_only, + 'historical_only': historical_only, + 'time_historical_cutoff': time_historical_cutoff, } response = self.api_call('GET', 'IEconService', 'GetTradeOffers', 'v1', params).json() response = self._filter_non_active_offers(response) @@ -206,8 +221,11 @@ def _filter_non_active_offers(offers_response): return offers_response - def get_trade_offer(self, trade_offer_id: str, merge: bool = True) -> dict: - params = {'key': self._api_key, 'tradeofferid': trade_offer_id, 'language': 'english'} + def get_trade_offer(self, trade_offer_id: str, merge: bool = True, use_webtoken=False) -> dict: + params = { + 'key'if not use_webtoken else 'access_token': self._api_key if not use_webtoken else self._access_token, + 'tradeofferid': trade_offer_id, 'language': 'english' + } response = self.api_call('GET', 'IEconService', 'GetTradeOffer', 'v1', params).json() if merge and 'descriptions' in response['response']: @@ -248,8 +266,9 @@ def get_trade_receipt(self, trade_id: str): @login_required def accept_trade_offer(self, trade_offer_id: str) -> dict: - trade = self.get_trade_offer(trade_offer_id) + trade = self.get_trade_offer(trade_offer_id, use_webtoken=True) trade_offer_state = TradeOfferState(trade['response']['offer']['trade_offer_state']) + # print(trade_offer_state, trade_offer_id) if trade_offer_state is not TradeOfferState.Active: raise ApiException(f'Invalid trade offer state: {trade_offer_state.name} ({trade_offer_state.value})') @@ -264,7 +283,6 @@ def accept_trade_offer(self, trade_offer_id: str) -> dict: 'captcha': '', } headers = {'Referer': self._get_trade_offer_url(trade_offer_id)} - response = self._session.post(accept_url, data=params, headers=headers).json() if response.get('needs_mobile_confirmation', False): return self._confirm_transaction(trade_offer_id) @@ -391,8 +409,8 @@ def make_offer_with_url( 'Referer': f'{SteamUrl.COMMUNITY_URL}{urlparse.urlparse(trade_offer_url).path}', 'Origin': SteamUrl.COMMUNITY_URL, } - response = self._session.post(url, data=params, headers=headers).json() + if confirm_trade and response.get('needs_mobile_confirmation'): response.update(self._confirm_transaction(response['tradeofferid'])) @@ -404,7 +422,12 @@ def _get_trade_offer_url(trade_offer_id: str) -> str: @login_required # If convert_to_decimal = False, the price will be returned WITHOUT a decimal point. - def get_wallet_balance(self, convert_to_decimal: bool = True, on_hold: bool = False) -> Union[str, Decimal]: + def get_wallet_balance( + self, + convert_to_decimal: bool = True, + on_hold: bool = False, + return_full : bool = False + ) -> Union[str, Decimal, dict]: response = self._session.get(f'{SteamUrl.COMMUNITY_URL}/market') wallet_info_match = re.search(r'var g_rgWalletInfo = (.*?);', response.text) if wallet_info_match: @@ -413,6 +436,8 @@ def get_wallet_balance(self, convert_to_decimal: bool = True, on_hold: bool = Fa else: raise Exception('Unable to get wallet balance string match') balance_dict_key = 'wallet_delayed_balance' if on_hold else 'wallet_balance' + if return_full: + return balance_dict if convert_to_decimal: return Decimal(balance_dict[balance_dict_key]) / 100 else: From f3ffc2aefaec176b52401aac11cf069cf64e6089 Mon Sep 17 00:00:00 2001 From: Heroslfb <109235048+Heroslfb@users.noreply.github.com> Date: Sat, 14 Feb 2026 18:55:07 +0300 Subject: [PATCH 04/13] Update login.py --- steampy/login.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/steampy/login.py b/steampy/login.py index ec94b8b..bd57662 100644 --- a/steampy/login.py +++ b/steampy/login.py @@ -3,6 +3,7 @@ from rsa import encrypt, PublicKey from requests import Session, Response +from urllib.parse import unquote from steampy import guard from steampy.models import SteamUrl @@ -41,6 +42,17 @@ def login(self) -> Session: self.set_sessionid_cookies() return self.session + steam_login_secure_cookies = [cookie for cookie in self._session.cookies if cookie.name == 'steamLoginSecure'] + cookie_value = steam_login_secure_cookies[0].value + decoded_cookie_value = unquote(cookie_value) + access_token_parts = decoded_cookie_value.split('||') + if len(access_token_parts) < 2: + print(decoded_cookie_value) + raise ValueError('Access token not found in steamLoginSecure cookie') + + access_token = access_token_parts[1] + self._access_token = access_token + def _send_login_request(self) -> Response: rsa_params = self._fetch_rsa_params() encrypted_password = self._encrypt_password(rsa_params) @@ -137,5 +149,6 @@ def _finalize_login(self) -> Response: sessionid = self.session.cookies['sessionid'] redir = f'{SteamUrl.COMMUNITY_URL}/login/home/?goto=' finalized_data = {'nonce': self.refresh_token, 'sessionid': sessionid, 'redir': redir} - response = self.session.post(SteamUrl.LOGIN_URL + '/jwt/finalizelogin', data=finalized_data) + headers = {'Referer': f'{SteamUrl.COMMUNITY_URL}/', 'Origin': SteamUrl.COMMUNITY_URL} + response = self.session.post(SteamUrl.LOGIN_URL + '/jwt/finalizelogin', data=finalized_data, headers=headers) return response From ae2390790fe4ad9cbd5d3f41651ea141dfea593e Mon Sep 17 00:00:00 2001 From: Heroslfb <109235048+Heroslfb@users.noreply.github.com> Date: Sat, 14 Feb 2026 18:55:35 +0300 Subject: [PATCH 05/13] Update market.py --- steampy/market.py | 74 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 12 deletions(-) diff --git a/steampy/market.py b/steampy/market.py index a44178c..aca6059 100644 --- a/steampy/market.py +++ b/steampy/market.py @@ -1,4 +1,6 @@ import json +import time +import random import urllib.parse from decimal import Decimal from http import HTTPStatus @@ -60,13 +62,15 @@ def fetch_price_history(self, item_hash_name: str, game: GameOptions) -> dict: @login_required def get_my_market_listings(self) -> dict: - response = self._session.get(f'{SteamUrl.COMMUNITY_URL}/market') + response = self._session.get(f'{SteamUrl.COMMUNITY_URL}/market/?count=100') if response.status_code != HTTPStatus.OK: raise ApiException(f'There was a problem getting the listings. HTTP code: {response.status_code}') - - assets_descriptions = json.loads(text_between(response.text, 'var g_rgAssets = ', ';\r\n')) + # print(response.text) + assets_descriptions = json.loads(text_between(response.text, "var g_rgAssets = ", ";\n")) + # print(assets_descriptions) listing_id_to_assets_address = get_listing_id_to_assets_address_from_html(response.text) listings = get_market_listings_from_html(response.text) + # print(listings) listings = merge_items_with_descriptions_from_listing( listings, listing_id_to_assets_address, assets_descriptions ) @@ -80,12 +84,13 @@ def get_my_market_listings(self) -> dict: ) if n_showing < n_total < 1000: - url = f'{SteamUrl.COMMUNITY_URL}/market/mylistings/render/?query=&start={n_showing}&count={-1}' + url = f'{SteamUrl.COMMUNITY_URL}/market/mylistings/render/?query=&start={0}&count={-1}' response = self._session.get(url) if response.status_code != HTTPStatus.OK: raise ApiException(f'There was a problem getting the listings. HTTP code: {response.status_code}') jresp = response.json() + # print(len(jresp['assets']['730']['2']), len(jresp['assets']['570']['2'])) listing_id_to_assets_address = get_listing_id_to_assets_address_from_html(jresp.get('hovers')) listings_2 = get_market_sell_listings_from_api(jresp.get('results_html')) listings_2 = merge_items_with_descriptions_from_listing( @@ -111,7 +116,7 @@ def get_my_market_listings(self) -> dict: return listings @login_required - def create_sell_order(self, assetid: str, game: GameOptions, money_to_receive: str) -> dict: + def create_sell_order(self, assetid: str, game: GameOptions, money_to_receive: str, confirm_trade: bool = True) -> dict: data = { 'assetid': assetid, 'sessionid': self._session_id, @@ -123,8 +128,10 @@ def create_sell_order(self, assetid: str, game: GameOptions, money_to_receive: s headers = {'Referer': f'{SteamUrl.COMMUNITY_URL}/profiles/{self._steam_guard["steamid"]}/inventory'} response = self._session.post(f'{SteamUrl.COMMUNITY_URL}/market/sellitem/', data, headers=headers).json() + # print(response) has_pending_confirmation = 'pending confirmation' in response.get('message', '') - if response.get('needs_mobile_confirmation') or (not response.get('success') and has_pending_confirmation): + if confirm_trade and response.get('needs_mobile_confirmation') or ( + not response.get('success') and has_pending_confirmation): return self._confirm_sell_listing(assetid) return response @@ -146,18 +153,59 @@ def create_buy_order( 'price_total': str(Decimal(price_single_item) * Decimal(quantity)), 'quantity': quantity, } + # print(data) headers = { 'Referer': f'{SteamUrl.COMMUNITY_URL}/market/listings/{game.app_id}/{urllib.parse.quote(market_name)}' } - response = self._session.post(f'{SteamUrl.COMMUNITY_URL}/market/createbuyorder/', data, headers=headers).json() + response = self._session.post(f'{SteamUrl.COMMUNITY_URL}/market/createbuyorder/', data, headers=headers, timeout=30) + # print(response, response.json()) + response = response.json() - if (success := response.get('success')) != 1: - raise ApiException( - f'There was a problem creating the order. Are you using the right currency? success: {success}' + # If the order is successful, return immediately + if response.get("success") == 1: + return response + + # If mobile confirmation is required + if response.get("need_confirmation"): + if not self._steam_guard: + raise ApiException("Order requires mobile confirmation, but steam_guard info is not provided") + + confirmation_id = response["confirmation"]["confirmation_id"] + print("Confirmation required, ID:", confirmation_id) + + # Execute mobile confirmation + confirmation_executor = ConfirmationExecutor( + self._steam_guard['identity_secret'], + self._steam_guard['steamid'], + self._session ) + time.sleep(random.uniform(2, 4)) + success = confirmation_executor.confirm_by_id(confirmation_id) + if not success: + raise ApiException("Mobile confirmation failed") + + print("Mobile confirmation succeeded, resending request with confirmation ID") + + # Second request, update confirmation to the confirmed ID + data["confirmation"] = confirmation_id + time.sleep(random.uniform(2, 4)) + response = self._session.post( + SteamUrl.COMMUNITY_URL + "/market/createbuyorder/", + data, + headers=headers + ).json() + print("Second order response:", response) + + if response.get("success") == 1: + print("Buy order effective") + return response + else: + raise ApiException(f"Order failed after confirmation: {response}") + + # Other exceptions + raise ApiException(f"Buy order failed: {response}") - return response @login_required def buy_item( @@ -217,6 +265,8 @@ def cancel_buy_order(self, buy_order_id) -> dict: def _confirm_sell_listing(self, asset_id: str) -> dict: con_executor = ConfirmationExecutor( - self._steam_guard['identity_secret'], self._steam_guard['steamid'], self._session + self._steam_guard['identity_secret'], + self._steam_guard['steamid'], + self._session ) return con_executor.confirm_sell_listing(asset_id) From 67329f4b584850e8bb26ebde8050f143cf769af1 Mon Sep 17 00:00:00 2001 From: Heroslfb <109235048+Heroslfb@users.noreply.github.com> Date: Sat, 14 Feb 2026 18:56:01 +0300 Subject: [PATCH 06/13] Update confirmation.py --- steampy/confirmation.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/steampy/confirmation.py b/steampy/confirmation.py index a9836cb..3b86ff7 100644 --- a/steampy/confirmation.py +++ b/steampy/confirmation.py @@ -13,9 +13,10 @@ class Confirmation: - def __init__(self, data_confid, nonce): + def __init__(self, data_confid, nonce, creator_id): self.data_confid = data_confid self.nonce = nonce + self.creator_id = creator_id class Tag(enum.Enum): @@ -43,6 +44,16 @@ def confirm_sell_listing(self, asset_id: str) -> dict: confirmation = self._select_sell_listing_confirmation(confirmations, asset_id) return self._send_confirmation(confirmation) + def confirm_by_id(self, confirmation_id: str) -> bool: + # Confirm a trade/order based on confirmation_id + confirmations = self._get_confirmations() + for conf in confirmations: + if str(conf.creator_id) == str(confirmation_id): + result = self._send_confirmation(conf) + print(f'confirm_by_id result:{result}') + return result.get("success", False) + return False + def _send_confirmation(self, confirmation: Confirmation) -> dict: tag = Tag.ALLOW params = self._create_confirmation_params(tag.value) @@ -60,7 +71,8 @@ def _get_confirmations(self) -> List[Confirmation]: for conf in confirmations_json['conf']: data_confid = conf['id'] nonce = conf['nonce'] - confirmations.append(Confirmation(data_confid, nonce)) + creator_id = conf['creator_id'] + confirmations.append(Confirmation(data_confid, nonce, creator_id)) return confirmations else: raise ConfirmationExpected From 5815468d765d89f5347a9ab0950d3fe5c8768708 Mon Sep 17 00:00:00 2001 From: Heroslfb <109235048+Heroslfb@users.noreply.github.com> Date: Sat, 14 Feb 2026 19:02:03 +0300 Subject: [PATCH 07/13] Update client.py --- steampy/client.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/steampy/client.py b/steampy/client.py index f2eed89..3adabc1 100644 --- a/steampy/client.py +++ b/steampy/client.py @@ -89,7 +89,6 @@ def set_login_cookies(self, cookies: dict) -> None: raise ValueError('Access token not found in steamLoginSecure cookie') access_token = access_token_parts[1] - # print(f'access token: {access_token}') self._access_token = access_token @login_required @@ -124,8 +123,6 @@ def login(self, username: str = None, password: str = None, steam_guard: str = N self.was_login_executed = True self.market._set_login_executed(self.steam_guard, self._get_session_id()) - - @login_required def logout(self) -> None: url = f'{SteamUrl.STORE_URL}/login/logout/' @@ -191,7 +188,14 @@ def get_trade_offers_summary(self) -> dict: params = {'key': self._api_key} return self.api_call('GET', 'IEconService', 'GetTradeOffersSummary', 'v1', params).json() - def get_trade_offers(self, merge: bool = True, use_webtoken=False, active_only=1, historical_only=0, time_historical_cutoff='') -> dict: + def get_trade_offers( + self, + merge: bool = True, + use_webtoken: bool = False, + active_only: int = 1, + historical_only: int = 0, + time_historical_cutoff: str = '' + ) -> dict: params = { 'key'if not use_webtoken else 'access_token': self._api_key if not use_webtoken else self._access_token, 'get_sent_offers': 1, @@ -221,7 +225,7 @@ def _filter_non_active_offers(offers_response): return offers_response - def get_trade_offer(self, trade_offer_id: str, merge: bool = True, use_webtoken=False) -> dict: + def get_trade_offer(self, trade_offer_id: str, merge: bool = True, use_webtoken: bool = False) -> dict: params = { 'key'if not use_webtoken else 'access_token': self._api_key if not use_webtoken else self._access_token, 'tradeofferid': trade_offer_id, 'language': 'english' @@ -268,7 +272,6 @@ def get_trade_receipt(self, trade_id: str): def accept_trade_offer(self, trade_offer_id: str) -> dict: trade = self.get_trade_offer(trade_offer_id, use_webtoken=True) trade_offer_state = TradeOfferState(trade['response']['offer']['trade_offer_state']) - # print(trade_offer_state, trade_offer_id) if trade_offer_state is not TradeOfferState.Active: raise ApiException(f'Invalid trade offer state: {trade_offer_state.name} ({trade_offer_state.value})') From 27eb748935655c59c8a775aa559c1961153ef83f Mon Sep 17 00:00:00 2001 From: Heroslfb <109235048+Heroslfb@users.noreply.github.com> Date: Sat, 14 Feb 2026 19:09:17 +0300 Subject: [PATCH 08/13] Update client.py --- steampy/client.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/steampy/client.py b/steampy/client.py index 3adabc1..ecfd25b 100644 --- a/steampy/client.py +++ b/steampy/client.py @@ -191,9 +191,9 @@ def get_trade_offers_summary(self) -> dict: def get_trade_offers( self, merge: bool = True, - use_webtoken: bool = False, - active_only: int = 1, - historical_only: int = 0, + use_webtoken: bool = True, + active_only: bool = True, + historical_only: bool = False, time_historical_cutoff: str = '' ) -> dict: params = { @@ -202,8 +202,8 @@ def get_trade_offers( 'get_received_offers': 1, 'get_descriptions': 1, 'language': 'english', - 'active_only': active_only, - 'historical_only': historical_only, + 'active_only': 1 if active_only else 0, + 'historical_only': 1 if historical_only else 0, 'time_historical_cutoff': time_historical_cutoff, } response = self.api_call('GET', 'IEconService', 'GetTradeOffers', 'v1', params).json() @@ -225,7 +225,7 @@ def _filter_non_active_offers(offers_response): return offers_response - def get_trade_offer(self, trade_offer_id: str, merge: bool = True, use_webtoken: bool = False) -> dict: + def get_trade_offer(self, trade_offer_id: str, merge: bool = True, use_webtoken: bool = True) -> dict: params = { 'key'if not use_webtoken else 'access_token': self._api_key if not use_webtoken else self._access_token, 'tradeofferid': trade_offer_id, 'language': 'english' From 072b3ea764dadf10ae50a4a877a9d04dde3881f3 Mon Sep 17 00:00:00 2001 From: Heroslfb <109235048+Heroslfb@users.noreply.github.com> Date: Sat, 14 Feb 2026 19:12:27 +0300 Subject: [PATCH 09/13] Update client.py --- steampy/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/steampy/client.py b/steampy/client.py index ecfd25b..47e9ec9 100644 --- a/steampy/client.py +++ b/steampy/client.py @@ -270,7 +270,7 @@ def get_trade_receipt(self, trade_id: str): @login_required def accept_trade_offer(self, trade_offer_id: str) -> dict: - trade = self.get_trade_offer(trade_offer_id, use_webtoken=True) + trade = self.get_trade_offer(trade_offer_id) trade_offer_state = TradeOfferState(trade['response']['offer']['trade_offer_state']) if trade_offer_state is not TradeOfferState.Active: raise ApiException(f'Invalid trade offer state: {trade_offer_state.name} ({trade_offer_state.value})') From 8099b60100d44aec797a71ab9d9a51782c9a4f55 Mon Sep 17 00:00:00 2001 From: Heroslfb <109235048+Heroslfb@users.noreply.github.com> Date: Sat, 14 Feb 2026 19:15:11 +0300 Subject: [PATCH 10/13] Update market.py --- steampy/market.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/steampy/market.py b/steampy/market.py index aca6059..4266e6f 100644 --- a/steampy/market.py +++ b/steampy/market.py @@ -65,12 +65,9 @@ def get_my_market_listings(self) -> dict: response = self._session.get(f'{SteamUrl.COMMUNITY_URL}/market/?count=100') if response.status_code != HTTPStatus.OK: raise ApiException(f'There was a problem getting the listings. HTTP code: {response.status_code}') - # print(response.text) assets_descriptions = json.loads(text_between(response.text, "var g_rgAssets = ", ";\n")) - # print(assets_descriptions) listing_id_to_assets_address = get_listing_id_to_assets_address_from_html(response.text) listings = get_market_listings_from_html(response.text) - # print(listings) listings = merge_items_with_descriptions_from_listing( listings, listing_id_to_assets_address, assets_descriptions ) @@ -90,7 +87,6 @@ def get_my_market_listings(self) -> dict: raise ApiException(f'There was a problem getting the listings. HTTP code: {response.status_code}') jresp = response.json() - # print(len(jresp['assets']['730']['2']), len(jresp['assets']['570']['2'])) listing_id_to_assets_address = get_listing_id_to_assets_address_from_html(jresp.get('hovers')) listings_2 = get_market_sell_listings_from_api(jresp.get('results_html')) listings_2 = merge_items_with_descriptions_from_listing( @@ -128,7 +124,6 @@ def create_sell_order(self, assetid: str, game: GameOptions, money_to_receive: s headers = {'Referer': f'{SteamUrl.COMMUNITY_URL}/profiles/{self._steam_guard["steamid"]}/inventory'} response = self._session.post(f'{SteamUrl.COMMUNITY_URL}/market/sellitem/', data, headers=headers).json() - # print(response) has_pending_confirmation = 'pending confirmation' in response.get('message', '') if confirm_trade and response.get('needs_mobile_confirmation') or ( not response.get('success') and has_pending_confirmation): @@ -153,13 +148,11 @@ def create_buy_order( 'price_total': str(Decimal(price_single_item) * Decimal(quantity)), 'quantity': quantity, } - # print(data) headers = { 'Referer': f'{SteamUrl.COMMUNITY_URL}/market/listings/{game.app_id}/{urllib.parse.quote(market_name)}' } response = self._session.post(f'{SteamUrl.COMMUNITY_URL}/market/createbuyorder/', data, headers=headers, timeout=30) - # print(response, response.json()) response = response.json() # If the order is successful, return immediately @@ -172,7 +165,7 @@ def create_buy_order( raise ApiException("Order requires mobile confirmation, but steam_guard info is not provided") confirmation_id = response["confirmation"]["confirmation_id"] - print("Confirmation required, ID:", confirmation_id) + # print("Confirmation required, ID:", confirmation_id) # Execute mobile confirmation confirmation_executor = ConfirmationExecutor( @@ -185,7 +178,7 @@ def create_buy_order( if not success: raise ApiException("Mobile confirmation failed") - print("Mobile confirmation succeeded, resending request with confirmation ID") + # print("Mobile confirmation succeeded, resending request with confirmation ID") # Second request, update confirmation to the confirmed ID data["confirmation"] = confirmation_id @@ -195,10 +188,10 @@ def create_buy_order( data, headers=headers ).json() - print("Second order response:", response) + # print("Second order response:", response) if response.get("success") == 1: - print("Buy order effective") + # print("Buy order effective") return response else: raise ApiException(f"Order failed after confirmation: {response}") From 7e32e7601c6583d39bc3679170685717877007c4 Mon Sep 17 00:00:00 2001 From: Heroslfb <109235048+Heroslfb@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:34:02 +0300 Subject: [PATCH 11/13] Update market.py timeouts added --- steampy/market.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/steampy/market.py b/steampy/market.py index 4266e6f..f7051fb 100644 --- a/steampy/market.py +++ b/steampy/market.py @@ -43,7 +43,7 @@ def fetch_price( 'market_hash_name': item_hash_name, } - response = self._session.get(url, params=params) + response = self._session.get(url, params=params, timeout=60) if response.status_code == HTTPStatus.TOO_MANY_REQUESTS: raise TooManyRequests('You can fetch maximum 20 prices in 60s period') @@ -54,7 +54,7 @@ def fetch_price_history(self, item_hash_name: str, game: GameOptions) -> dict: url = f'{SteamUrl.COMMUNITY_URL}/market/pricehistory/' params = {'country': 'PL', 'appid': game.app_id, 'market_hash_name': item_hash_name} - response = self._session.get(url, params=params) + response = self._session.get(url, params=params, timeout=60) if response.status_code == HTTPStatus.TOO_MANY_REQUESTS: raise TooManyRequests('You can fetch maximum 20 prices in 60s period') @@ -62,7 +62,7 @@ def fetch_price_history(self, item_hash_name: str, game: GameOptions) -> dict: @login_required def get_my_market_listings(self) -> dict: - response = self._session.get(f'{SteamUrl.COMMUNITY_URL}/market/?count=100') + response = self._session.get(f'{SteamUrl.COMMUNITY_URL}/market/?count=100', timeout=60) if response.status_code != HTTPStatus.OK: raise ApiException(f'There was a problem getting the listings. HTTP code: {response.status_code}') assets_descriptions = json.loads(text_between(response.text, "var g_rgAssets = ", ";\n")) @@ -82,7 +82,7 @@ def get_my_market_listings(self) -> dict: if n_showing < n_total < 1000: url = f'{SteamUrl.COMMUNITY_URL}/market/mylistings/render/?query=&start={0}&count={-1}' - response = self._session.get(url) + response = self._session.get(url, timeout=60) if response.status_code != HTTPStatus.OK: raise ApiException(f'There was a problem getting the listings. HTTP code: {response.status_code}') @@ -96,7 +96,7 @@ def get_my_market_listings(self) -> dict: else: for i in range(0, n_total, 100): url = f'{SteamUrl.COMMUNITY_URL}/market/mylistings/?query=&start={n_showing + i}&count={100}' - response = self._session.get(url) + response = self._session.get(url, timeout=60) if response.status_code != HTTPStatus.OK: raise ApiException( f'There was a problem getting the listings. HTTP code: {response.status_code}' @@ -123,7 +123,7 @@ def create_sell_order(self, assetid: str, game: GameOptions, money_to_receive: s } headers = {'Referer': f'{SteamUrl.COMMUNITY_URL}/profiles/{self._steam_guard["steamid"]}/inventory'} - response = self._session.post(f'{SteamUrl.COMMUNITY_URL}/market/sellitem/', data, headers=headers).json() + response = self._session.post(f'{SteamUrl.COMMUNITY_URL}/market/sellitem/', data, headers=headers, timeout=60).json() has_pending_confirmation = 'pending confirmation' in response.get('message', '') if confirm_trade and response.get('needs_mobile_confirmation') or ( not response.get('success') and has_pending_confirmation): @@ -186,7 +186,8 @@ def create_buy_order( response = self._session.post( SteamUrl.COMMUNITY_URL + "/market/createbuyorder/", data, - headers=headers + headers=headers, + timeout=60 ).json() # print("Second order response:", response) @@ -222,7 +223,7 @@ def buy_item( 'Referer': f'{SteamUrl.COMMUNITY_URL}/market/listings/{game.app_id}/{urllib.parse.quote(market_name)}' } response = self._session.post( - f'{SteamUrl.COMMUNITY_URL}/market/buylisting/{market_id}', data, headers=headers + f'{SteamUrl.COMMUNITY_URL}/market/buylisting/{market_id}', data, headers=headers, timeout=60 ).json() try: @@ -241,7 +242,7 @@ def cancel_sell_order(self, sell_listing_id: str) -> None: headers = {'Referer': f'{SteamUrl.COMMUNITY_URL}/market/'} url = f'{SteamUrl.COMMUNITY_URL}/market/removelisting/{sell_listing_id}' - response = self._session.post(url, data=data, headers=headers) + response = self._session.post(url, data=data, headers=headers, timeout=60) if response.status_code != HTTPStatus.OK: raise ApiException(f'There was a problem removing the listing. HTTP code: {response.status_code}') @@ -249,7 +250,7 @@ def cancel_sell_order(self, sell_listing_id: str) -> None: def cancel_buy_order(self, buy_order_id) -> dict: data = {'sessionid': self._session_id, 'buy_orderid': buy_order_id} headers = {'Referer': f'{SteamUrl.COMMUNITY_URL}/market'} - response = self._session.post(f'{SteamUrl.COMMUNITY_URL}/market/cancelbuyorder/', data, headers=headers).json() + response = self._session.post(f'{SteamUrl.COMMUNITY_URL}/market/cancelbuyorder/', data, headers=headers, timeout=60).json() if (success := response.get('success')) != 1: raise ApiException(f'There was a problem canceling the order. success: {success}') From 60b49f38978fd7cf916a4891558a03e30eb0d6eb Mon Sep 17 00:00:00 2001 From: Heroslfb <109235048+Heroslfb@users.noreply.github.com> Date: Fri, 6 Mar 2026 09:49:57 +0300 Subject: [PATCH 12/13] Update client.py active tradeoffer status are 2 and 9 now for get_trade_offers --- steampy/client.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/steampy/client.py b/steampy/client.py index 47e9ec9..9afae8d 100644 --- a/steampy/client.py +++ b/steampy/client.py @@ -177,7 +177,7 @@ def get_partner_inventory( response = (self._session.get(url, params=params)) response_dict = response.json() if response_dict is None or response_dict.get('success') != 1: - raise ApiException('Success value should be 1.') + raise ApiException(f'Success value should be 1. response: {response_dict}') return merge_items_with_descriptions_from_inventory(response_dict, game) if merge else response_dict @@ -207,7 +207,8 @@ def get_trade_offers( 'time_historical_cutoff': time_historical_cutoff, } response = self.api_call('GET', 'IEconService', 'GetTradeOffers', 'v1', params).json() - response = self._filter_non_active_offers(response) + if active_only: + response = self._filter_non_active_offers(response) return merge_items_with_descriptions_from_offers(response) if merge else response @@ -217,10 +218,12 @@ def _filter_non_active_offers(offers_response): offers_sent = offers_response['response'].get('trade_offers_sent', []) offers_response['response']['trade_offers_received'] = list( - filter(lambda offer: offer['trade_offer_state'] == TradeOfferState.Active, offers_received) + filter(lambda offer: offer['trade_offer_state'] in [ + TradeOfferState.Active, TradeOfferState.ConfirmationNeed], offers_received) ) offers_response['response']['trade_offers_sent'] = list( - filter(lambda offer: offer['trade_offer_state'] == TradeOfferState.Active, offers_sent) + filter(lambda offer: offer['trade_offer_state'] in [ + TradeOfferState.Active, TradeOfferState.ConfirmationNeed], offers_sent) ) return offers_response From 7f6192043533426588d5f4462fe82505a38acb51 Mon Sep 17 00:00:00 2001 From: Heroslfb <109235048+Heroslfb@users.noreply.github.com> Date: Fri, 6 Mar 2026 09:51:22 +0300 Subject: [PATCH 13/13] Update utils.py added Russian for steam market offers categories --- steampy/utils.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/steampy/utils.py b/steampy/utils.py index 4df7102..df4e812 100644 --- a/steampy/utils.py +++ b/steampy/utils.py @@ -167,22 +167,22 @@ def get_market_listings_from_html(html: str) -> dict: buy_orders_dict = {} for node in nodes: - if 'My sell listings' in node.text: + if 'My sell listings' in node.text or 'Мои лоты на продажу' in node.text: sell_listings_active = get_sell_listings_from_node(node) sell_listings_dict.update(sell_listings_active) - elif 'My listings awaiting confirmation' in node.text: + elif 'My listings awaiting confirmation' in node.text or 'Лоты, ожидающие подтверждения' in node.text: sell_listings_awaiting_conf = get_sell_listings_from_node(node) for listing in sell_listings_awaiting_conf.values(): listing['need_confirmation'] = True sell_listings_dict.update(sell_listings_awaiting_conf) - elif 'My buy orders' in node.text: + elif 'My buy orders' in node.text or 'Мои запросы на покупку' in node.text: buy_orders_dict = get_buy_orders_from_node(node) return {'buy_orders': buy_orders_dict, 'sell_listings': sell_listings_dict} def get_sell_listings_from_node(node: Tag) -> dict: - sell_listings_raw = node.findAll('div', {'id': re.compile('mylisting_\d+')}) + sell_listings_raw = node.findAll('div', {'id': re.compile(r'mylisting_\d+')}) sell_listings_dict = {} for listing_raw in sell_listings_raw: @@ -216,7 +216,6 @@ def get_buy_orders_from_node(node: Tag) -> dict: 'quantity': int(qnt_price_raw[0].strip()), 'price': qnt_price_raw[1].strip(), 'item_name': order.a.text, - 'icon_url': order.select('img[class=market_listing_item_img]')[0].attrs['src'].rsplit('/', 2)[-2], 'game_name': order.select('span[class=market_listing_game_name]')[0].text, } buy_orders_dict[order['order_id']] = order @@ -226,7 +225,7 @@ def get_buy_orders_from_node(node: Tag) -> dict: def get_listing_id_to_assets_address_from_html(html: str) -> dict: listing_id_to_assets_address = {} - regex = "CreateItemHoverFromContainer\( [\w]+, 'mylisting_([\d]+)_[\w]+', ([\d]+), '([\d]+)', '([\d]+)', [\d]+ \);" + regex = r"CreateItemHoverFromContainer\( [\w]+, 'mylisting_([\d]+)_[\w]+', ([\d]+), '([\d]+)', '([\d]+)', [\d]+ \);" for match in re.findall(regex, html): listing_id_to_assets_address[match[0]] = [str(match[1]), match[2], match[3]]