def __init__( self, username: str = None, password: str = None, api_key: str = None, shared_secret: str = None, identity_secret: str = None, steam_id: str = None, reuse_session: bool = False, session_file: Path = None, sessions_dir: Path = None, ) -> None: self._api_key = api_key self._identity_secret = identity_secret self._password = password self._shared_secret = shared_secret self._session = aiohttp.ClientSession() self._api_session = aiohttp.ClientSession() self.username = username self.steam_id = steam_id self.was_login_executed = False self.market = SteamMarket(self._session) self.chat = SteamChat(self._session) self.reuse_session = reuse_session self.session_file = session_file self.sessions_dir = sessions_dir
def __init__(self, api_key: str) -> None: self._api_key = api_key self._session = requests.Session() self.steam_guard = None self.was_login_executed = False self.username = None self.market = SteamMarket(self._session)
def __init__(self, api_key: str, username: str = None, password: str = None, steam_guard:str = None) -> None: self._api_key = api_key self._session = requests.Session() self.steam_guard = steam_guard self.was_login_executed = False self.username = username self._password = password self.market = SteamMarket(self._session) self.chat = SteamChat(self._session)
def __init__(self, api_key: str, username: str = None, password: str = None, steam_guard: str = None, rucaptcha_key: str = None, proxy: dict = None, rucaptcha_kwargs=None) -> None: self._api_key = api_key self._session = requests.Session() self._session.headers.update({ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, br", "Cache-Control": "max-age=0", "Connection": "keep-alive", "DNT": "1", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" }) if proxy: self._session.proxies.update(proxy) self.steam_guard = steam_guard self.was_login_executed = False self.username = username self._password = password self._rucaptcha_key = rucaptcha_key if rucaptcha_kwargs is None: rucaptcha_kwargs = {} self.rucaptcha_kwargs = rucaptcha_kwargs self.market = SteamMarket(self._session) self.chat = SteamChat(self._session)
class SteamClient: def __init__(self, api_key: str) -> None: self._api_key = api_key self._session = requests.Session() self.steam_guard = None self.was_login_executed = False self.username = None self.market = SteamMarket(self._session) self.chat = SteamChat(self._session) def login(self, username: str, password: str, steam_guard: str) -> None: self.steam_guard = guard.load_steam_guard(steam_guard) self.username = username LoginExecutor(username, password, self.steam_guard['shared_secret'], self._session).login() self.was_login_executed = True self.market._set_login_executed(self.steam_guard, self._get_session_id()) @login_required def logout(self) -> None: url = LoginExecutor.STORE_URL + '/logout/' params = {'sessionid': self._get_session_id()} self._session.post(url, params) if self.is_session_alive(): raise Exception("Logout unsuccessful") self.was_login_executed = False self.chat._logout() @login_required def is_session_alive(self): steam_login = self.username main_page_response = self._session.get(SteamUrl.COMMUNITY_URL) return steam_login.lower() in main_page_response.text.lower() def api_call(self, request_method: str, interface: str, api_method: str, version: str, params: dict = None) -> requests.Response: url = '/'.join([SteamUrl.API_URL, interface, api_method, version]) if request_method == 'GET': response = requests.get(url, params=params) else: response = requests.post(url, data=params) if self.is_invalid_api_key(response): raise InvalidCredentials('Invalid API key') return response @staticmethod def is_invalid_api_key(response: requests.Response) -> bool: msg = 'Access is denied. Retrying will not help. Please verify your <pre>key=</pre> parameter' return msg in response.text @login_required def get_my_inventory(self, game: GameOptions, merge: bool = True) -> dict: url = SteamUrl.COMMUNITY_URL + '/my/inventory/json/' + \ game.app_id + '/' + \ game.context_id response_dict = self._session.get(url).json() if merge: return merge_items_with_descriptions_from_inventory( response_dict, game) return response_dict @login_required def get_partner_inventory(self, partner_steam_id: str, game: GameOptions, merge: bool = True) -> dict: params = { 'sessionid': self._get_session_id(), 'partner': partner_steam_id, 'appid': int(game.app_id), 'contextid': game.context_id } partner_account_id = steam_id_to_account_id(partner_steam_id) headers = { 'X-Requested-With': 'XMLHttpRequest', 'Referer': SteamUrl.COMMUNITY_URL + '/tradeoffer/new/?partner=' + partner_account_id, 'X-Prototype-Version': '1.7' } response_dict = self._session.get(SteamUrl.COMMUNITY_URL + '/tradeoffer/new/partnerinventory/', params=params, headers=headers).json() if merge: return merge_items_with_descriptions_from_inventory( response_dict, game) return response_dict def _get_session_id(self) -> str: return self._session.cookies.get_dict()['sessionid'] 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: params = { 'key': self._api_key, 'get_sent_offers': 1, 'get_received_offers': 1, 'get_descriptions': 1, 'language': 'english', 'active_only': 1, 'historical_only': 0, 'time_historical_cutoff': '' } response = self.api_call('GET', 'IEconService', 'GetTradeOffers', 'v1', params).json() response = self._filter_non_active_offers(response) if merge: response = merge_items_with_descriptions_from_offers(response) return response @staticmethod def _filter_non_active_offers(offers_response): offers_received = offers_response['response'].get( 'trade_offers_received', []) 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)) offers_response['response']['trade_offers_sent'] = list( filter( lambda offer: offer['trade_offer_state'] == TradeOfferState. Active, offers_sent)) 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' } response = self.api_call('GET', 'IEconService', 'GetTradeOffer', 'v1', params).json() if merge and "descriptions" in response['response']: descriptions = { get_description_key(offer): offer for offer in response['response']['descriptions'] } offer = response['response']['offer'] response['response'][ 'offer'] = merge_items_with_descriptions_from_offer( offer, descriptions) return response def get_trade_history(self, max_trades=100, start_after_time=None, start_after_tradeid=None, get_descriptions=True, navigating_back=True, include_failed=True, include_total=True) -> dict: params = { 'key': self._api_key, 'max_trades': max_trades, 'start_after_time': start_after_time, 'start_after_tradeid': start_after_tradeid, 'get_descriptions': get_descriptions, 'navigating_back': navigating_back, 'include_failed': include_failed, 'include_total': include_total } response = self.api_call('GET', 'IEconService', 'GetTradeHistory', 'v1', params).json() return response @login_required def get_trade_receipt(self, trade_id: str) -> list: html = self._session.get( "https://steamcommunity.com/trade/{}/receipt".format( trade_id)).content.decode() items = [] for item in texts_between(html, "oItem = ", ";\r\n\toItem"): items.append(json.loads(item)) return items @login_required def accept_trade_offer(self, trade_offer_id: str) -> dict: 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("Invalid trade offer state: {} ({})".format( trade_offer_state.name, trade_offer_state.value)) partner = self._fetch_trade_partner_id(trade_offer_id) session_id = self._get_session_id() accept_url = SteamUrl.COMMUNITY_URL + '/tradeoffer/' + trade_offer_id + '/accept' params = { 'sessionid': session_id, 'tradeofferid': trade_offer_id, 'serverid': '1', 'partner': partner, '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) return response def _fetch_trade_partner_id(self, trade_offer_id: str) -> str: url = self._get_trade_offer_url(trade_offer_id) offer_response_text = self._session.get(url).text if 'You have logged in from a new device. In order to protect the items' in offer_response_text: raise SevenDaysHoldException( "Account has logged in a new device and can't trade for 7 days" ) return text_between(offer_response_text, "var g_ulTradePartnerSteamID = '", "';") def _confirm_transaction(self, trade_offer_id: str) -> dict: confirmation_executor = ConfirmationExecutor( self.steam_guard['identity_secret'], self.steam_guard['steamid'], self._session) return confirmation_executor.send_trade_allow_request(trade_offer_id) def decline_trade_offer(self, trade_offer_id: str) -> dict: params = {'key': self._api_key, 'tradeofferid': trade_offer_id} return self.api_call('POST', 'IEconService', 'DeclineTradeOffer', 'v1', params).json() def cancel_trade_offer(self, trade_offer_id: str) -> dict: params = {'key': self._api_key, 'tradeofferid': trade_offer_id} return self.api_call('POST', 'IEconService', 'CancelTradeOffer', 'v1', params).json() @login_required def make_offer(self, items_from_me: List[Asset], items_from_them: List[Asset], partner_steam_id: str, message: str = '') -> dict: offer = self._create_offer_dict(items_from_me, items_from_them) session_id = self._get_session_id() url = SteamUrl.COMMUNITY_URL + '/tradeoffer/new/send' server_id = 1 params = { 'sessionid': session_id, 'serverid': server_id, 'partner': partner_steam_id, 'tradeoffermessage': message, 'json_tradeoffer': json.dumps(offer), 'captcha': '', 'trade_offer_create_params': '{}' } partner_account_id = steam_id_to_account_id(partner_steam_id) headers = { 'Referer': SteamUrl.COMMUNITY_URL + '/tradeoffer/new/?partner=' + partner_account_id, 'Origin': SteamUrl.COMMUNITY_URL } response = self._session.post(url, data=params, headers=headers).json() if response.get('needs_mobile_confirmation'): response.update(self._confirm_transaction( response['tradeofferid'])) return response def get_profile(self, steam_id: str) -> dict: params = {'steamids': steam_id, 'key': self._api_key} response = self.api_call('GET', 'ISteamUser', 'GetPlayerSummaries', 'v0002', params) data = response.json() return data['response']['players'][0] def get_friend_list(self, steam_id: str, relationship_filter: str = "all") -> dict: params = { 'key': self._api_key, 'steamid': steam_id, 'relationship': relationship_filter } resp = self.api_call("GET", "ISteamUser", "GetFriendList", "v1", params) data = resp.json() return data['friendslist']['friends'] @staticmethod def _create_offer_dict(items_from_me: List[Asset], items_from_them: List[Asset]) -> dict: return { 'newversion': True, 'version': 4, 'me': { 'assets': [asset.to_dict() for asset in items_from_me], 'currency': [], 'ready': False }, 'them': { 'assets': [asset.to_dict() for asset in items_from_them], 'currency': [], 'ready': False } } @login_required def get_escrow_duration(self, trade_offer_url: str) -> int: headers = { 'Referer': SteamUrl.COMMUNITY_URL + urlparse.urlparse(trade_offer_url).path, 'Origin': SteamUrl.COMMUNITY_URL } response = self._session.get(trade_offer_url, headers=headers).text my_escrow_duration = int( text_between(response, "var g_daysMyEscrow = ", ";")) their_escrow_duration = int( text_between(response, "var g_daysTheirEscrow = ", ";")) return max(my_escrow_duration, their_escrow_duration) @login_required def make_offer_with_url(self, items_from_me: List[Asset], items_from_them: List[Asset], trade_offer_url: str, message: str = '') -> dict: token = get_key_value_from_url(trade_offer_url, 'token') partner_account_id = get_key_value_from_url(trade_offer_url, 'partner') partner_steam_id = account_id_to_steam_id(partner_account_id) offer = self._create_offer_dict(items_from_me, items_from_them) session_id = self._get_session_id() url = SteamUrl.COMMUNITY_URL + '/tradeoffer/new/send' server_id = 1 trade_offer_create_params = {'trade_offer_access_token': token} params = { 'sessionid': session_id, 'serverid': server_id, 'partner': partner_steam_id, 'tradeoffermessage': message, 'json_tradeoffer': json.dumps(offer), 'captcha': '', 'trade_offer_create_params': json.dumps(trade_offer_create_params) } headers = { 'Referer': SteamUrl.COMMUNITY_URL + urlparse.urlparse(trade_offer_url).path, 'Origin': SteamUrl.COMMUNITY_URL } response = self._session.post(url, data=params, headers=headers).json() if response.get('needs_mobile_confirmation'): response.update(self._confirm_transaction( response['tradeofferid'])) return response @staticmethod def _get_trade_offer_url(trade_offer_id: str) -> str: return SteamUrl.COMMUNITY_URL + '/tradeoffer/' + trade_offer_id
class SteamClient: def __init__( self, username: str = None, password: str = None, api_key: str = None, shared_secret: str = None, identity_secret: str = None, steam_id: str = None, reuse_session: bool = False, session_file: Path = None, sessions_dir: Path = None, ) -> None: self._api_key = api_key self._identity_secret = identity_secret self._password = password self._shared_secret = shared_secret self._session = aiohttp.ClientSession() self._api_session = aiohttp.ClientSession() self.username = username self.steam_id = steam_id self.was_login_executed = False self.market = SteamMarket(self._session) self.chat = SteamChat(self._session) self.reuse_session = reuse_session self.session_file = session_file self.sessions_dir = sessions_dir async def login(self) -> None: session_loaded = False if self.reuse_session: session_loaded = await self.load_session() if session_loaded: print('session loaded') elif session_loaded == False: print('session is not alive any more') if not session_loaded: print('do classic login') login_executor = LoginExecutor(username=self.username, password=self._password, shared_secret=self._shared_secret, session=self._session) await login_executor.login() self.was_login_executed = True self.market._set_login_executed(steam_id=self.steam_id, session_id=self._get_session_id(), identity_secret=self._identity_secret) if self.reuse_session: self.save_session() def save_session(self) -> None: if self.session_file and self.session_file.is_file(): self._session.cookie_jar.save(self.session_file) async def load_session(self) -> Optional[bool]: try: self.set_session_file() except InvalidSessionPath as err: self.reuse_session = False raise err if self.session_file.exists(): print('loading session from file: %s' % self.session_file) self._session.cookie_jar.load(self.session_file) return await self.is_session_alive() print('there is no file to load the sesssion') async def close_session(self) -> None: """Saves session if needed. Closes underlying connector. Releases all acquired resources. Call it at the end of your program or when you don't want to use this client amy more """ if self.reuse_session: self.save_session() await self._session.close() await self._api_session.close() @login_required async def logout(self) -> None: await self._session.post(SteamUrl.STORE_URL + '/logout/', data={'sessionid': self._get_session_id()}) if await self.is_session_alive(): raise Exception("Logout unsuccessful") self.was_login_executed = False async def is_session_alive(self): response = await self._session.get(SteamUrl.COMMUNITY_URL) response = await response.text() return '>%s<' % self.username.lower() in response.lower() async def api_call(self, url: URL, params: dict = None, post: bool = False) -> dict: if post: response = await self._api_session.post(url, data=params) else: response = await self._api_session.get(url, params=params) if response.status == 200: return await response.json() response_text = await response.text() if response.status == 403 and self._is_invalid_api_key(response_text): raise InvalidCredentials('Invalid API key') else: raise Exception(response_text) @staticmethod def _is_invalid_api_key(response: str) -> bool: msg = """Access is denied. Retrying will not help. Please verify your <pre>key=</pre> parameter""" return msg in response @login_required def get_my_inventory(self, game: GameOptions, merge: bool = True, count: int = 5000) -> dict: return self.get_partner_inventory(self.steam_id, game, merge, count) @login_required def get_partner_inventory(self, partner_steam_id: str, game: GameOptions, merge: bool = True, count: int = 5000) -> 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() if response_dict['success'] != 1: raise ApiException('Success value should be 1.') if merge: return merge_items_with_descriptions_from_inventory( response_dict, game) return response_dict def _get_session_id(self) -> str: return self._session.cookie_jar.filter_cookies(URL( SteamUrl.HELP_URL)).get('sessionid').value 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: params = { 'key': self._api_key, 'get_sent_offers': 1, 'get_received_offers': 1, 'get_descriptions': 1, 'language': 'english', 'active_only': 1, 'historical_only': 0, 'time_historical_cutoff': '' } response = self.api_call('GET', 'IEconService', 'GetTradeOffers', 'v1', params).json() response = self._filter_non_active_offers(response) if merge: response = merge_items_with_descriptions_from_offers(response) return response @staticmethod def _filter_non_active_offers(offers_response): offers_received = offers_response['response'].get( 'trade_offers_received', []) 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)) offers_response['response']['trade_offers_sent'] = list( filter( lambda offer: offer['trade_offer_state'] == TradeOfferState. Active, offers_sent)) 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' } response = self.api_call('GET', 'IEconService', 'GetTradeOffer', 'v1', params).json() if merge and "descriptions" in response['response']: descriptions = { get_description_key(offer): offer for offer in response['response']['descriptions'] } offer = response['response']['offer'] response['response'][ 'offer'] = merge_items_with_descriptions_from_offer( offer, descriptions) return response def get_trade_history(self, max_trades=100, start_after_time=None, start_after_tradeid=None, get_descriptions=True, navigating_back=True, include_failed=True, include_total=True) -> dict: params = { 'key': self._api_key, 'max_trades': max_trades, 'start_after_time': start_after_time, 'start_after_tradeid': start_after_tradeid, 'get_descriptions': get_descriptions, 'navigating_back': navigating_back, 'include_failed': include_failed, 'include_total': include_total } response = self.api_call('GET', 'IEconService', 'GetTradeHistory', 'v1', params).json() return response @login_required def get_trade_receipt(self, trade_id: str) -> list: html = self._session.get( "https://steamcommunity.com/trade/{}/receipt".format( trade_id)).content.decode() items = [] for item in texts_between(html, "oItem = ", ";\r\n\toItem"): items.append(json.loads(item)) return items @login_required def accept_trade_offer(self, trade_offer_id: str) -> dict: 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("Invalid trade offer state: {} ({})".format( trade_offer_state.name, trade_offer_state.value)) partner = self._fetch_trade_partner_id(trade_offer_id) session_id = self._get_session_id() accept_url = SteamUrl.COMMUNITY_URL + '/tradeoffer/' + trade_offer_id + '/accept' params = { 'sessionid': session_id, 'tradeofferid': trade_offer_id, 'serverid': '1', 'partner': partner, '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) return response def _fetch_trade_partner_id(self, trade_offer_id: str) -> str: url = self._get_trade_offer_url(trade_offer_id) offer_response_text = self._session.get(url).text if 'You have logged in from a new device. In order to protect the items' in offer_response_text: raise SevenDaysHoldException( "Account has logged in a new device and can't trade for 7 days" ) return text_between(offer_response_text, "var g_ulTradePartnerSteamID = '", "';") def _confirm_transaction(self, trade_offer_id: str) -> dict: confirmation_executor = ConfirmationExecutor(self._identity_secret, self.steam_id, self._session) return confirmation_executor.send_trade_allow_request(trade_offer_id) def decline_trade_offer(self, trade_offer_id: str) -> dict: params = {'key': self._api_key, 'tradeofferid': trade_offer_id} return self.api_call('POST', 'IEconService', 'DeclineTradeOffer', 'v1', params).json() def cancel_trade_offer(self, trade_offer_id: str) -> dict: params = {'key': self._api_key, 'tradeofferid': trade_offer_id} return self.api_call('POST', 'IEconService', 'CancelTradeOffer', 'v1', params).json() @login_required def make_offer(self, items_from_me: List[Asset], items_from_them: List[Asset], partner_steam_id: str, message: str = '') -> dict: offer = self._create_offer_dict(items_from_me, items_from_them) session_id = self._get_session_id() url = SteamUrl.COMMUNITY_URL + '/tradeoffer/new/send' server_id = 1 params = { 'sessionid': session_id, 'serverid': server_id, 'partner': partner_steam_id, 'tradeoffermessage': message, 'json_tradeoffer': json.dumps(offer), 'captcha': '', 'trade_offer_create_params': '{}' } partner_account_id = steam_id_to_account_id(partner_steam_id) headers = { 'Referer': SteamUrl.COMMUNITY_URL + '/tradeoffer/new/?partner=' + partner_account_id, 'Origin': SteamUrl.COMMUNITY_URL } response = self._session.post(url, data=params, headers=headers).json() if response.get('needs_mobile_confirmation'): response.update(self._confirm_transaction( response['tradeofferid'])) return response async def get_profile(self, steam_id: str) -> dict: """ https://developer.valvesoftware.com/wiki/Steam_Web_API#GetPlayerSummaries_.28v0002.29 """ response = await self.api_call( SteamUrl.API / 'ISteamUser/GetPlayerSummaries/v0002', { 'steamids': steam_id, 'key': self._api_key }) return response['response']['players'][0] def get_friend_list(self, steam_id: str, relationship_filter: str = "all") -> dict: params = { 'key': self._api_key, 'steamid': steam_id, 'relationship': relationship_filter } resp = self.api_call("GET", "ISteamUser", "GetFriendList", "v1", params) data = resp.json() return data['friendslist']['friends'] @staticmethod def _create_offer_dict(items_from_me: List[Asset], items_from_them: List[Asset]) -> dict: return { 'newversion': True, 'version': 4, 'me': { 'assets': [asset.to_dict() for asset in items_from_me], 'currency': [], 'ready': False }, 'them': { 'assets': [asset.to_dict() for asset in items_from_them], 'currency': [], 'ready': False } } @login_required def get_escrow_duration(self, trade_offer_url: str) -> int: headers = { 'Referer': SteamUrl.COMMUNITY_URL + urlparse.urlparse(trade_offer_url).path, 'Origin': SteamUrl.COMMUNITY_URL } response = self._session.get(trade_offer_url, headers=headers).text my_escrow_duration = int( text_between(response, "var g_daysMyEscrow = ", ";")) their_escrow_duration = int( text_between(response, "var g_daysTheirEscrow = ", ";")) return max(my_escrow_duration, their_escrow_duration) @login_required def make_offer_with_url(self, items_from_me: List[Asset], items_from_them: List[Asset], trade_offer_url: str, message: str = '', case_sensitive: 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) partner_steam_id = account_id_to_steam_id(partner_account_id) offer = self._create_offer_dict(items_from_me, items_from_them) session_id = self._get_session_id() url = SteamUrl.COMMUNITY_URL + '/tradeoffer/new/send' server_id = 1 trade_offer_create_params = {'trade_offer_access_token': token} params = { 'sessionid': session_id, 'serverid': server_id, 'partner': partner_steam_id, 'tradeoffermessage': message, 'json_tradeoffer': json.dumps(offer), 'captcha': '', 'trade_offer_create_params': json.dumps(trade_offer_create_params) } headers = { 'Referer': SteamUrl.COMMUNITY_URL + urlparse.urlparse(trade_offer_url).path, 'Origin': SteamUrl.COMMUNITY_URL } response = self._session.post(url, data=params, headers=headers).json() if response.get('needs_mobile_confirmation'): response.update(self._confirm_transaction( response['tradeofferid'])) return response @staticmethod def _get_trade_offer_url(trade_offer_id: str) -> str: return SteamUrl.COMMUNITY_URL + '/tradeoffer/' + trade_offer_id @login_required def get_wallet_balance(self, convert_to_float: bool = True) -> Union[str, float]: url = SteamUrl.STORE_URL + '/account/history/' response = self._session.get(url) response_soup = bs4.BeautifulSoup(response.text, "html.parser") balance = response_soup.find(id='header_wallet_balance').string if convert_to_float: return price_to_float(balance) else: return balance def set_session_file(self) -> None: try: if self.session_file: if self.session_file.is_dir(): raise InvalidSessionPath( 'session_file should not point to a directory') elif self.sessions_dir: if self.sessions_dir.is_file(): raise InvalidSessionPath( 'sessions_dir should not point to a file') if not self.steam_id: raise InvalidSessionPath( "steam_id is required to save or load sessions from a directory" ) self.session_file = self.sessions_dir / self.steam_id else: raise Exception('No session_file or sessions_dir was provided') except InvalidSessionPath as err: # prevent saving session to invalid file self.reuse_session = False raise err
class SteamClient: def __init__(self, api_key: str) -> None: self._api_key = api_key self._session = requests.Session() self.steam_guard = None self.was_login_executed = False self.username = None self.market = SteamMarket(self._session) def login(self, username: str, password: str, steam_guard: str) -> None: self.steam_guard = guard.load_steam_guard(steam_guard) self.username = username LoginExecutor(username, password, self.steam_guard['shared_secret'], self._session).login() self.was_login_executed = True self.market._set_login_executed(self.steam_guard, self._get_session_id()) @login_required def logout(self) -> None: url = LoginExecutor.STORE_URL + '/logout/' params = {'sessionid': self._get_session_id()} self._session.post(url, params) if self.is_session_alive(): raise Exception("Logout unsuccessful") self.was_login_executed = False @login_required def is_session_alive(self): steam_login = self.username main_page_response = self._session.get(SteamUrl.COMMUNITY_URL) return steam_login.lower() in main_page_response.text.lower() def api_call(self, request_method: str, interface: str, api_method: str, version: str, params: dict = None) -> requests.Response: url = '/'.join([SteamUrl.API_URL, interface, api_method, version]) if request_method == 'GET': response = requests.get(url, params=params) else: response = requests.post(url, data=params) if self.is_invalid_api_key(response): raise InvalidCredentials('Invalid API key') return response @staticmethod def is_invalid_api_key(response: requests.Response) -> bool: msg = 'Access is denied. Retrying will not help. Please verify your <pre>key=</pre> parameter' return msg in response.text @login_required def get_my_inventory(self, game: GameOptions, merge: bool = True) -> dict: url = SteamUrl.COMMUNITY_URL + '/my/inventory/json/' + \ game.app_id + '/' + \ game.context_id response_dict = self._session.get(url).json() if merge: return merge_items_with_descriptions_from_inventory(response_dict, game) return response_dict @login_required def get_partner_inventory(self, partner_steam_id: str, game: GameOptions, merge: bool = True) -> dict: params = {'sessionid': self._get_session_id(), 'partner': partner_steam_id, 'appid': int(game.app_id), 'contextid': game.context_id} partner_account_id = steam_id_to_account_id(partner_steam_id) headers = {'X-Requested-With': 'XMLHttpRequest', 'Referer': SteamUrl.COMMUNITY_URL + '/tradeoffer/new/?partner=' + partner_account_id, 'X-Prototype-Version': '1.7'} response_dict = self._session.get(SteamUrl.COMMUNITY_URL + '/tradeoffer/new/partnerinventory/', params=params, headers=headers).json() if merge: return merge_items_with_descriptions_from_inventory(response_dict, game) return response_dict def _get_session_id(self) -> str: return self._session.cookies.get_dict()['sessionid'] 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: params = {'key': self._api_key, 'get_sent_offers': 1, 'get_received_offers': 1, 'get_descriptions': 1, 'language': 'english', 'active_only': 1, 'historical_only': 0, 'time_historical_cutoff': ''} response = self.api_call('GET', 'IEconService', 'GetTradeOffers', 'v1', params).json() response = self._filter_non_active_offers(response) if merge: response = merge_items_with_descriptions_from_offers(response) return response @staticmethod def _filter_non_active_offers(offers_response): offers_received = offers_response['response'].get('trade_offers_received', []) 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)) offers_response['response']['trade_offers_sent'] = list( filter(lambda offer: offer['trade_offer_state'] == TradeOfferState.Active, offers_sent)) 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'} response = self.api_call('GET', 'IEconService', 'GetTradeOffer', 'v1', params).json() if merge and "descriptions" in response['response']: descriptions = {get_description_key(offer): offer for offer in response['response']['descriptions']} offer = response['response']['offer'] response['response']['offer'] = merge_items_with_descriptions_from_offer(offer, descriptions) return response def get_trade_receipt(self, trade_id: str) -> list: html = self._session.get("https://steamcommunity.com/trade/{}/receipt".format(trade_id)).content.decode() items = [] for item in texts_between(html, "oItem = ", ";\r\n\toItem"): items.append(json.loads(item)) return items @login_required def accept_trade_offer(self, trade_offer_id: str) -> dict: trade = self.get_trade_offer(trade_offer_id) if trade['trade_offer_state'] is not TradeOfferState.Active: raise ApiException("Invalid trade offer state: {}".format(trade['trade_offer_state'])) partner = self._fetch_trade_partner_id(trade_offer_id) session_id = self._get_session_id() accept_url = SteamUrl.COMMUNITY_URL + '/tradeoffer/' + trade_offer_id + '/accept' params = {'sessionid': session_id, 'tradeofferid': trade_offer_id, 'serverid': '1', 'partner': partner, 'captcha': ''} headers = {'Referer': self._get_trade_offer_url(trade_offer_id)} response = self._session.post(accept_url, data=params, headers=headers) if response.json().get('needs_mobile_confirmation', False): return self._confirm_transaction(trade_offer_id) return response
class SteamClient: def __init__(self, api_key: str, username: str = None, password: str = None, steam_guard:str = None) -> None: self._api_key = api_key self._session = requests.Session() self.steam_guard = steam_guard self.was_login_executed = False self.username = username self._password = password self.market = SteamMarket(self._session) self.chat = SteamChat(self._session) def login(self, username: str, password: str, steam_guard: str) -> None: self.steam_guard = guard.load_steam_guard(steam_guard) self.username = username self._password = password LoginExecutor(username, password, self.steam_guard['shared_secret'], self._session).login() self.was_login_executed = True self.market._set_login_executed(self.steam_guard, self._get_session_id()) @login_required def logout(self) -> None: url = SteamUrl.STORE_URL + '/logout/' data = {'sessionid': self._get_session_id()} self._session.post(url, data=data) if self.is_session_alive(): raise Exception("Logout unsuccessful") self.was_login_executed = False def __enter__(self): if None in [self.username, self._password, self.steam_guard]: raise InvalidCredentials('You have to pass username, password and steam_guard' 'parameters when using "with" statement') self.login(self.username, self._password, self.steam_guard) return self def __exit__(self, exc_type, exc_val, exc_tb): self.logout() @login_required def is_session_alive(self): steam_login = self.username main_page_response = self._session.get(SteamUrl.COMMUNITY_URL) return steam_login.lower() in main_page_response.text.lower() def api_call(self, request_method: str, interface: str, api_method: str, version: str, params: dict = None) -> requests.Response: url = '/'.join([SteamUrl.API_URL, interface, api_method, version]) if request_method == 'GET': response = requests.get(url, params=params) else: response = requests.post(url, data=params) if self.is_invalid_api_key(response): raise InvalidCredentials('Invalid API key') return response @staticmethod def is_invalid_api_key(response: requests.Response) -> bool: msg = 'Access is denied. Retrying will not help. Please verify your <pre>key=</pre> parameter' return msg in response.text @login_required def get_my_inventory(self, game: GameOptions, merge: bool = True, count: int = 5000) -> 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) -> 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() if response_dict['success'] != 1: raise ApiException('Success value should be 1.') if merge: return merge_items_with_descriptions_from_inventory(response_dict, game) return response_dict def _get_session_id(self) -> str: return self._session.cookies.get_dict()['sessionid'] 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: params = {'key': self._api_key, 'get_sent_offers': 1, 'get_received_offers': 1, 'get_descriptions': 1, 'language': 'english', 'active_only': 1, 'historical_only': 0, 'time_historical_cutoff': ''} response = self.api_call('GET', 'IEconService', 'GetTradeOffers', 'v1', params).json() response = self._filter_non_active_offers(response) if merge: response = merge_items_with_descriptions_from_offers(response) return response @staticmethod def _filter_non_active_offers(offers_response): offers_received = offers_response['response'].get('trade_offers_received', []) 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)) offers_response['response']['trade_offers_sent'] = list( filter(lambda offer: offer['trade_offer_state'] == TradeOfferState.Active, offers_sent)) 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'} response = self.api_call('GET', 'IEconService', 'GetTradeOffer', 'v1', params).json() if merge and "descriptions" in response['response']: descriptions = {get_description_key(offer): offer for offer in response['response']['descriptions']} offer = response['response']['offer'] response['response']['offer'] = merge_items_with_descriptions_from_offer(offer, descriptions) return response def get_trade_history(self, max_trades=100, start_after_time=None, start_after_tradeid=None, get_descriptions=True, navigating_back=True, include_failed=True, include_total=True) -> dict: params = { 'key': self._api_key, 'max_trades': max_trades, 'start_after_time': start_after_time, 'start_after_tradeid': start_after_tradeid, 'get_descriptions': get_descriptions, 'navigating_back': navigating_back, 'include_failed': include_failed, 'include_total': include_total } response = self.api_call('GET', 'IEconService', 'GetTradeHistory', 'v1', params).json() return response @login_required def get_trade_receipt(self, trade_id: str) -> list: html = self._session.get("https://steamcommunity.com/trade/{}/receipt".format(trade_id)).content.decode() items = [] for item in texts_between(html, "oItem = ", ";\r\n\toItem"): items.append(json.loads(item)) return items @login_required def accept_trade_offer(self, trade_offer_id: str) -> dict: 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("Invalid trade offer state: {} ({})".format(trade_offer_state.name, trade_offer_state.value)) partner = self._fetch_trade_partner_id(trade_offer_id) session_id = self._get_session_id() accept_url = SteamUrl.COMMUNITY_URL + '/tradeoffer/' + trade_offer_id + '/accept' params = {'sessionid': session_id, 'tradeofferid': trade_offer_id, 'serverid': '1', 'partner': partner, '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) return response def _fetch_trade_partner_id(self, trade_offer_id: str) -> str: url = self._get_trade_offer_url(trade_offer_id) offer_response_text = self._session.get(url).text if 'You have logged in from a new device. In order to protect the items' in offer_response_text: raise SevenDaysHoldException("Account has logged in a new device and can't trade for 7 days") return text_between(offer_response_text, "var g_ulTradePartnerSteamID = '", "';") def _confirm_transaction(self, trade_offer_id: str) -> dict: confirmation_executor = ConfirmationExecutor(self.steam_guard['identity_secret'], self.steam_guard['steamid'], self._session) return confirmation_executor.send_trade_allow_request(trade_offer_id) def decline_trade_offer(self, trade_offer_id: str) -> dict: url = 'https://steamcommunity.com/tradeoffer/' + trade_offer_id + '/decline' response = self._session.post(url, data={'sessionid': self._get_session_id()}).json() return response def cancel_trade_offer(self, trade_offer_id: str) -> dict: url = 'https://steamcommunity.com/tradeoffer/' + trade_offer_id + '/cancel' response = self._session.post(url, data={'sessionid': self._get_session_id()}).json() return response @login_required def make_offer(self, items_from_me: List[Asset], items_from_them: List[Asset], partner_steam_id: str, message: str = '') -> dict: offer = self._create_offer_dict(items_from_me, items_from_them) session_id = self._get_session_id() url = SteamUrl.COMMUNITY_URL + '/tradeoffer/new/send' server_id = 1 params = { 'sessionid': session_id, 'serverid': server_id, 'partner': partner_steam_id, 'tradeoffermessage': message, 'json_tradeoffer': json.dumps(offer), 'captcha': '', 'trade_offer_create_params': '{}' } partner_account_id = steam_id_to_account_id(partner_steam_id) headers = {'Referer': SteamUrl.COMMUNITY_URL + '/tradeoffer/new/?partner=' + partner_account_id, 'Origin': SteamUrl.COMMUNITY_URL} response = self._session.post(url, data=params, headers=headers).json() if response.get('needs_mobile_confirmation'): response.update(self._confirm_transaction(response['tradeofferid'])) return response def get_profile(self, steam_id: str) -> dict: params = {'steamids': steam_id, 'key': self._api_key} response = self.api_call('GET', 'ISteamUser', 'GetPlayerSummaries', 'v0002', params) data = response.json() return data['response']['players'][0] def get_friend_list(self, steam_id: str, relationship_filter: str="all") -> dict: params = { 'key': self._api_key, 'steamid': steam_id, 'relationship': relationship_filter } resp = self.api_call("GET", "ISteamUser", "GetFriendList", "v1", params) data = resp.json() return data['friendslist']['friends'] @staticmethod def _create_offer_dict(items_from_me: List[Asset], items_from_them: List[Asset]) -> dict: return { 'newversion': True, 'version': 4, 'me': { 'assets': [asset.to_dict() for asset in items_from_me], 'currency': [], 'ready': False }, 'them': { 'assets': [asset.to_dict() for asset in items_from_them], 'currency': [], 'ready': False } } @login_required def get_escrow_duration(self, trade_offer_url: str) -> int: headers = {'Referer': SteamUrl.COMMUNITY_URL + urlparse.urlparse(trade_offer_url).path, 'Origin': SteamUrl.COMMUNITY_URL} response = self._session.get(trade_offer_url, headers=headers).text my_escrow_duration = int(text_between(response, "var g_daysMyEscrow = ", ";")) their_escrow_duration = int(text_between(response, "var g_daysTheirEscrow = ", ";")) return max(my_escrow_duration, their_escrow_duration) @login_required def make_offer_with_url(self, items_from_me: List[Asset], items_from_them: List[Asset], trade_offer_url: str, message: str = '', case_sensitive: 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) partner_steam_id = account_id_to_steam_id(partner_account_id) offer = self._create_offer_dict(items_from_me, items_from_them) session_id = self._get_session_id() url = SteamUrl.COMMUNITY_URL + '/tradeoffer/new/send' server_id = 1 trade_offer_create_params = {'trade_offer_access_token': token} params = { 'sessionid': session_id, 'serverid': server_id, 'partner': partner_steam_id, 'tradeoffermessage': message, 'json_tradeoffer': json.dumps(offer), 'captcha': '', 'trade_offer_create_params': json.dumps(trade_offer_create_params) } headers = {'Referer': SteamUrl.COMMUNITY_URL + urlparse.urlparse(trade_offer_url).path, 'Origin': SteamUrl.COMMUNITY_URL} response = self._session.post(url, data=params, headers=headers).json() if response.get('needs_mobile_confirmation'): response.update(self._confirm_transaction(response['tradeofferid'])) return response @staticmethod def _get_trade_offer_url(trade_offer_id: str) -> str: return SteamUrl.COMMUNITY_URL + '/tradeoffer/' + trade_offer_id @login_required def get_wallet_balance(self, convert_to_decimal: bool = True) -> Union[str, decimal.Decimal]: url = SteamUrl.STORE_URL + '/account/history/' response = self._session.get(url) response_soup = bs4.BeautifulSoup(response.text, "html.parser") balance = response_soup.find(id='header_wallet_balance').string if convert_to_decimal: return parse_price(balance) else: return balance
class SteamClient: def __init__(self, api_key: str, username: str = None, password: str = None, steam_guard: str = None, rucaptcha_key: str = None, proxy: dict = None, rucaptcha_kwargs=None) -> None: self._api_key = api_key self._session = requests.Session() self._session.headers.update({ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, br", "Cache-Control": "max-age=0", "Connection": "keep-alive", "DNT": "1", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" }) if proxy: self._session.proxies.update(proxy) self.steam_guard = steam_guard self.was_login_executed = False self.username = username self._password = password self._rucaptcha_key = rucaptcha_key if rucaptcha_kwargs is None: rucaptcha_kwargs = {} self.rucaptcha_kwargs = rucaptcha_kwargs self.market = SteamMarket(self._session) self.chat = SteamChat(self._session) def set_proxy(self, proxy: dict): self._session.proxies.update(proxy) def login(self, username: str = None, password: str = None, steam_guard: str = None, rucaptcha_key: str = None) -> None: if steam_guard: self.steam_guard = guard.load_steam_guard(steam_guard) if username: self.username = username if password: self._password = password if rucaptcha_key: self._rucaptcha_key = rucaptcha_key LoginExecutor(self.username, self._password, self.steam_guard['shared_secret'], self._session, self._rucaptcha_key, self.rucaptcha_kwargs).login() self.was_login_executed = True self.market._set_login_executed(self.steam_guard, self._get_session_id()) def load_cookie(self, file_path): with open(file_path, 'rb') as f: self._session.cookies.update(pickle.load(f)) self.was_login_executed = True self.market._set_login_executed(self.steam_guard, self._get_session_id()) def save_cookie(self, file_path): basedir = os.path.dirname(file_path) if not os.path.exists(basedir): os.makedirs(basedir) with open(file_path, 'wb') as f: pickle.dump(self._session.cookies, f) @login_required def logout(self) -> None: url = SteamUrl.STORE_URL + '/logout/' data = {'sessionid': self._get_session_id()} self._session.post(url, data=data) if self.is_session_alive(): raise Exception("Logout unsuccessful") self.was_login_executed = False def __enter__(self): if None in [self.username, self._password, self.steam_guard]: raise InvalidCredentials( 'You have to pass username, password and steam_guard' 'parameters when using "with" statement') self.login(self.username, self._password, self.steam_guard) return self def __exit__(self, exc_type, exc_val, exc_tb): self.logout() @login_required def is_session_alive(self): steam_login = self.username main_page_response = self._session.get(SteamUrl.COMMUNITY_URL) return steam_login.lower() in main_page_response.text.lower() def _check_response(self, response): if response.status_code in (502, 401): raise BadResponse(response) if b"Access Denied" in response.content: raise BannedError(response) def api_call(self, request_method: str, interface: str, api_method: str, version: str, params: dict = None) -> requests.Response: url = '/'.join([SteamUrl.API_URL, interface, api_method, version]) if request_method == 'GET': response = requests.get(url, params=params) else: response = requests.post(url, data=params) if self.is_invalid_api_key(response): raise InvalidCredentials('Invalid API key') if response.status_code == 429: raise BannedError(response.content) self._check_response(response) return response @staticmethod def is_invalid_api_key(response: requests.Response) -> bool: msg = 'Access is denied. Retrying will not help. Please verify your <pre>key=</pre> parameter' return msg in response.text @login_required def get_my_inventory(self, game: GameOptions, merge: bool = True, count: int = 5000) -> dict: steam_id = self.steam_guard['steamid'] return self.get_partner_inventory(steam_id, game, merge, count) def _get_partner_inventory(self, partner_steam_id: str, game: GameOptions, merge: bool = True, count: int = 5000) -> dict: url = '/'.join([ SteamUrl.COMMUNITY_URL, 'inventory', partner_steam_id, game.app_id, game.context_id ]) params = {'l': 'english', 'count': count} response = self._session.get(url, params=params, stream=True) self._check_response(response) response.raise_for_status() response_dict = response.json() if not response_dict: raise NullInventory() if response_dict.get('error'): raise ApiException(response_dict['error']) if response_dict['success'] != 1: raise ApiException('Success value should be 1.') if merge: return merge_items_with_descriptions_from_inventory( response_dict, game) return response_dict @login_required def get_partner_inventory(self, partner_steam_id: str, game: GameOptions, merge: bool = True, count: int = 5000) -> dict: return self._get_partner_inventory(partner_steam_id, game, merge, count) def get_partner_inventory_simple(self, partner_steam_id: str, game: GameOptions, merge: bool = True, count: int = 5000) -> dict: return self._get_partner_inventory(partner_steam_id, game, merge, count) def _get_session_id(self) -> str: return self._session.cookies.get_dict()['sessionid'] 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, get_sent_offers=1, get_received_offers=1, get_descriptions=1, active_only=0, historical_only=0, language="english", time_historical_cutoff="") -> dict: params = { 'key': self._api_key, 'get_sent_offers': get_sent_offers, 'get_received_offers': get_received_offers, 'get_descriptions': get_descriptions, 'language': language, 'active_only': active_only, 'historical_only': historical_only, 'time_historical_cutoff': time_historical_cutoff } response = self.api_call('GET', 'IEconService', 'GetTradeOffers', 'v1', params) response.raise_for_status() response = response.json() # response = self._filter_non_active_offers(response) if merge: response = merge_items_with_descriptions_from_offers(response) return response @staticmethod def _filter_non_active_offers(offers_response): offers_received = offers_response['response'].get( 'trade_offers_received', []) 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)) offers_response['response']['trade_offers_sent'] = list( filter( lambda offer: offer['trade_offer_state'] == TradeOfferState. Active, offers_sent)) 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' } response = self.api_call('GET', 'IEconService', 'GetTradeOffer', 'v1', params).json() if merge and "descriptions" in response['response']: descriptions = { get_description_key(offer): offer for offer in response['response']['descriptions'] } offer = response['response']['offer'] response['response'][ 'offer'] = merge_items_with_descriptions_from_offer( offer, descriptions) return response def get_trade_status(self, trade_id): params = { 'key': self._api_key, 'tradeid': trade_id, 'language': 'english' } response = self.api_call('GET', "IEconService", "GetTradeStatus", 'v1', params).json() if response.get('response', {}).get('trades'): return response['response']['trades'][0] def get_trade_history(self, max_trades=100, start_after_time=None, start_after_tradeid=None, get_descriptions=True, navigating_back=True, include_failed=True, include_total=True) -> dict: params = { 'key': self._api_key, 'max_trades': max_trades, 'start_after_time': start_after_time, 'start_after_tradeid': start_after_tradeid, 'get_descriptions': get_descriptions, 'navigating_back': navigating_back, 'include_failed': include_failed, 'include_total': include_total } response = self.api_call('GET', 'IEconService', 'GetTradeHistory', 'v1', params).json() return response @login_required def get_trade_receipt(self, trade_id: str) -> list: html = self._session.get( "https://steamcommunity.com/trade/{}/receipt".format( trade_id)).content.decode() items = [] for item in texts_between(html, "oItem = ", ";\r\n\toItem"): items.append(json.loads(item)) return items @login_required def accept_trade_offer(self, trade_offer_id: str) -> dict: 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("Invalid trade offer state: {} ({})".format( trade_offer_state.name, trade_offer_state.value)) partner = self._fetch_trade_partner_id(trade_offer_id) session_id = self._get_session_id() accept_url = SteamUrl.COMMUNITY_URL + '/tradeoffer/' + trade_offer_id + '/accept' params = { 'sessionid': session_id, 'tradeofferid': trade_offer_id, 'serverid': '1', 'partner': partner, 'captcha': '' } headers = {'Referer': self._get_trade_offer_url(trade_offer_id)} response = self._session.post(accept_url, data=params, headers=headers) self._check_response(response) response = response.json() if response.get('needs_mobile_confirmation', False): return self._confirm_transaction(trade_offer_id) return response def _fetch_trade_partner_id(self, trade_offer_id: str) -> str: url = self._get_trade_offer_url(trade_offer_id) offer_response_text = self._session.get(url).text if 'You have logged in from a new device. In order to protect the items' in offer_response_text: raise SevenDaysHoldException( "Account has logged in a new device and can't trade for 7 days" ) return text_between(offer_response_text, "var g_ulTradePartnerSteamID = '", "';") def _confirm_transaction(self, trade_offer_id: str) -> dict: confirmation_executor = ConfirmationExecutor( self.steam_guard['identity_secret'], self.steam_guard['steamid'], self._session) return confirmation_executor.send_trade_allow_request(trade_offer_id) def decline_trade_offer(self, trade_offer_id: str) -> dict: url = 'https://steamcommunity.com/tradeoffer/' + trade_offer_id + '/decline' response = self._session.post( url, data={'sessionid': self._get_session_id()}) self._check_response(response) response = response.json() return response def cancel_trade_offer(self, trade_offer_id: str) -> dict: url = 'https://steamcommunity.com/tradeoffer/' + trade_offer_id + '/cancel' response = self._session.post( url, data={'sessionid': self._get_session_id()}) self._check_response(response) response = response.json() return response @login_required def make_offer(self, items_from_me: List[Asset], items_from_them: List[Asset], partner_steam_id: str, message: str = '') -> dict: offer = self._create_offer_dict(items_from_me, items_from_them) session_id = self._get_session_id() url = SteamUrl.COMMUNITY_URL + '/tradeoffer/new/send' server_id = 1 params = { 'sessionid': session_id, 'serverid': server_id, 'partner': partner_steam_id, 'tradeoffermessage': message, 'json_tradeoffer': json.dumps(offer), 'captcha': '', 'trade_offer_create_params': '{}' } partner_account_id = steam_id_to_account_id(partner_steam_id) headers = { 'Referer': SteamUrl.COMMUNITY_URL + '/tradeoffer/new/?partner=' + partner_account_id, 'Origin': SteamUrl.COMMUNITY_URL } response = self._session.post(url, data=params, headers=headers) self._check_response(response) response = response.json() if response.get('needs_mobile_confirmation'): response.update(self._confirm_transaction( response['tradeofferid'])) return response def get_profile(self, steam_id: str) -> dict: params = {'steamids': steam_id, 'key': self._api_key} response = self.api_call('GET', 'ISteamUser', 'GetPlayerSummaries', 'v0002', params) data = response.json() return data['response']['players'][0] def get_friend_list(self, steam_id: str, relationship_filter: str = "all") -> dict: params = { 'key': self._api_key, 'steamid': steam_id, 'relationship': relationship_filter } resp = self.api_call("GET", "ISteamUser", "GetFriendList", "v1", params) data = resp.json() return data['friendslist']['friends'] @staticmethod def _create_offer_dict(items_from_me: List[Asset], items_from_them: List[Asset]) -> dict: return { 'newversion': True, 'version': 4, 'me': { 'assets': [asset.to_dict() for asset in items_from_me], 'currency': [], 'ready': False }, 'them': { 'assets': [asset.to_dict() for asset in items_from_them], 'currency': [], 'ready': False } } @login_required def get_escrow_duration(self, trade_offer_url: str) -> int: headers = { 'Referer': SteamUrl.COMMUNITY_URL + urlparse.urlparse(trade_offer_url).path, 'Origin': SteamUrl.COMMUNITY_URL } response = self._session.get(trade_offer_url, headers=headers).text my_escrow_duration = int( text_between(response, "var g_daysMyEscrow = ", ";")) their_escrow_duration = int( text_between(response, "var g_daysTheirEscrow = ", ";")) return max(my_escrow_duration, their_escrow_duration) @login_required def make_offer_with_url(self, items_from_me: List[Asset], items_from_them: List[Asset], trade_offer_url: str, message: str = '', case_sensitive: 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) partner_steam_id = account_id_to_steam_id(partner_account_id) offer = self._create_offer_dict(items_from_me, items_from_them) session_id = self._get_session_id() url = SteamUrl.COMMUNITY_URL + '/tradeoffer/new/send' server_id = 1 trade_offer_create_params = {'trade_offer_access_token': token} params = { 'sessionid': session_id, 'serverid': server_id, 'partner': partner_steam_id, 'tradeoffermessage': message, 'json_tradeoffer': json.dumps(offer), 'captcha': '', 'trade_offer_create_params': json.dumps(trade_offer_create_params) } headers = { 'Referer': SteamUrl.COMMUNITY_URL + urlparse.urlparse(trade_offer_url).path, 'Origin': SteamUrl.COMMUNITY_URL } response = self._session.post(url, data=params, headers=headers) self._check_response(response) response_dict = response.json() if not response_dict or not response_dict.get('strError'): response.raise_for_status() if response_dict.get('needs_mobile_confirmation'): response_dict.update( self._confirm_transaction(response_dict['tradeofferid'])) return response_dict @staticmethod def _get_trade_offer_url(trade_offer_id: str) -> str: return SteamUrl.COMMUNITY_URL + '/tradeoffer/' + trade_offer_id @login_required def get_wallet_balance( self, convert_to_decimal: bool = True) -> Union[str, decimal.Decimal]: url = SteamUrl.STORE_URL + '/account/history/' response = self._session.get(url) response_soup = bs4.BeautifulSoup(response.text, "html.parser") balance = response_soup.find(id='header_wallet_balance').string if convert_to_decimal: return parse_price(balance) else: return balance