From e74043bb1767481d59adf2c7327cd0727eb6ead7 Mon Sep 17 00:00:00 2001
From: abuztrade <58076271+makarworld@users.noreply.github.com>
Date: Thu, 14 Sep 2023 18:41:03 +0300
Subject: [PATCH] Client - Add proxy support #278 and fix documentation
---
README.md | 370 ++++++++++++++++++++++++++----------------
steampy/client.py | 91 +++++++----
steampy/exceptions.py | 3 +
steampy/market.py | 21 +--
steampy/utils.py | 24 ++-
5 files changed, 322 insertions(+), 187 deletions(-)
diff --git a/README.md b/README.md
index d6d9b96..452fc79 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,8 @@ Donate bitcoin: 3PRzESHsTVkCFK7osjwFGQLZjSf7qXP1Ta
`steampy` is a library for Python, inspired by node-steam-tradeoffers, node-steam and other libraries for Node.js.
It was designed as a simple lightweight library, combining features of many steam libraries from Node.js into a single python module.
`steampy` is capable of logging into steam, fetching trade offers and handling them in simple manner, using steam user credentials
-and SteamGuard file(no need to extract and pass sessionID and webCookie).
+and SteamGuard file, or by passing sessionID and webCookie cookies.
+'steampy' is also capable of using proxies.
`steampy` is developed with Python 3 using type hints and many other features its supported for Windows, Linux and MacOs.
Table of Content
@@ -50,6 +51,13 @@ Usage
[Obtaining SteamGuard using Android emulation]( https://github.com/codepath/android_guides/wiki/Genymotion-2.0-Emulators-with-Google-Play-support)
+** __init__(self, api_key: str, username: str = None, password: str = None, steam_guard: str = None,
+ login_cookies: dict = None, proxies: dict = None) -> None:**
+
+
+SteamClient needs at least api_key to provide some functionalities. User can also provide username, password
+and SteamGuard file to be able to log in and use more methods. Proxies are also supported.
+
```python
from steampy.client import SteamClient
@@ -57,6 +65,32 @@ steam_client = SteamClient('MY_API_KEY')
steam_client.login('MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE')
```
+User can also provide login_cookies from browser to log in by cookies.
+
+```python
+from steampy.client import SteamClient
+
+login_cookies = {} # provide dict with cookies
+steam_client = SteamClient('MY_API_KEY',login_cookies=login_cookies)
+assert steam_client.was_login_executed
+```
+
+`proxies` dict can be provided for using proxy for internal SteamClient session.
+
+```python
+from steampy.client import SteamClient
+
+proxies = {
+ "http": "http://login:password@host:port",
+ "https": "http://login:password@host:port"
+}
+
+steam_client = SteamClient('MY_API_KEY', proxies=proxies)
+
+```
+
+
+
If you have `steamid`, `shared_secret` and `identity_secret` you can place it in file `Steamguard.txt` instead of fetching SteamGuard file from device.
```python
{
@@ -91,6 +125,33 @@ SteamClient methods
Unless specified in documentation, the method does not require login to work(it uses API Key from constructor instead)
+**def set_proxy(self, proxy: dict) -> dict**
+
+Set proxy for steampy session, example:
+
+```python
+from steampy.client import SteamClient
+
+steam_client = SteamClient('MY_API_KEY')
+proxies = {
+ "http": "http://login:password@host:port",
+ "https": "http://login:password@host:port"
+}
+steam_client.set_proxies(proxies)
+
+```
+
+**def set_login_cookies(self, cookies: dict) -> None**
+
+Set login cookies, can be used instead of normal `login` method.
+
+```python
+from steampy.client import SteamClient
+
+login_cookies = {} # provide dict with cookies
+steam_client = SteamClient('MY_API_KEY',login_cookies=login_cookies)
+assert steam_client.was_login_executed
+```
**login(username: str, password: str, steam_guard: str) -> requests.Response**
@@ -106,7 +167,9 @@ steam_client.login('MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE')
You can also use `with` statement to automatically login and logout.
```python
-with SteamClient(api_key, login, password, steam_guard_file) as client:
+from steampy.client import SteamClient
+
+with SteamClient('MY_API_KEY', 'MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE') as client:
client.some_method1(...)
client.some_method2(...)
...
@@ -128,7 +191,9 @@ steam_client.logout()
You can also use `with` statement to automatically login and logout.
```python
-with SteamClient(api_key, login, password, steam_guard_file) as client:
+from steampy.client import SteamClient
+
+with SteamClient('MY_API_KEY', 'MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE') as client:
client.some_method1(...)
client.some_method2(...)
...
@@ -254,115 +319,118 @@ and descriptions merged with data are value.
`Count` parameter is default max number of items, that can be fetched.
Inventory entries looks like this:
+
```python
-{'7146788981': {'actions': [{'link': 'steam://rungame/730/76561202255233023/+csgo_econ_action_preview%20S%owner_steamid%A%assetid%D316070896107169653',
- 'name': 'Inspect in Game...'}],
- 'amount': '1',
- 'appid': '730',
- 'background_color': '',
- 'classid': '1304827205',
- 'commodity': 0,
- 'contextid': '2',
- 'descriptions': [{'type': 'html',
- 'value': 'Exterior: Field-Tested'},
- {'type': 'html', 'value': ' '},
- {'type': 'html',
- 'value': 'Powerful and reliable, the AK-47 '
- 'is one of the most popular assault '
- 'rifles in the world. It is most '
- 'deadly in short, controlled bursts '
- 'of fire. It has been painted using '
- 'a carbon fiber hydrographic and a '
- 'dry-transfer decal of a red '
- 'pinstripe.\n'
- '\n'
- 'Never be afraid to push it to '
- 'the limit'},
- {'type': 'html', 'value': ' '},
- {'app_data': {'def_index': '65535',
- 'is_itemset_name': 1},
- 'color': '9da1a9',
- 'type': 'html',
- 'value': 'The Phoenix Collection'},
- {'type': 'html', 'value': ' '},
- {'app_data': {'def_index': '65535'},
- 'type': 'html',
- 'value': '
Sticker: '
- 'PENTA Sports | Katowice 2015, '
- 'PENTA Sports | Katowice 2015, '
- 'PENTA Sports | Katowice 2015, '
- 'mousesports | Cologne '
- '2015 '}],
- 'icon_drag_url': '',
- 'icon_url': '-9a81dlWLwJ2UUGcVs_nsVtzdOEdtWwKGZZLQHTxDZ7I56KU0Zwwo4NUX4oFJZEHLbXH5ApeO4YmlhxYQknCRvCo04DEVlxkKgpot7HxfDhjxszJemkV09-5lpKKqPrxN7LEmyVQ7MEpiLuSrYmnjQO3-UdsZGHyd4_Bd1RvNQ7T_FDrw-_ng5Pu75iY1zI97bhLsvQz',
- 'icon_url_large': '-9a81dlWLwJ2UUGcVs_nsVtzdOEdtWwKGZZLQHTxDZ7I56KU0Zwwo4NUX4oFJZEHLbXH5ApeO4YmlhxYQknCRvCo04DEVlxkKgpot7HxfDhjxszJemkV09-5lpKKqPrxN7LEm1Rd6dd2j6eQ9N2t2wK3-ENsZ23wcIKRdQE2NwyD_FK_kLq9gJDu7p_KyyRr7nNw-z-DyIFJbNUz',
- 'id': '7146788981',
- 'instanceid': '480085569',
- 'market_actions': [{'link': 'steam://rungame/730/76561202255233023/+csgo_econ_action_preview%20M%listingid%A%assetid%D316070896107169653',
- 'name': 'Inspect in Game...'}],
- 'market_hash_name': 'AK-47 | Redline (Field-Tested)',
- 'market_name': 'AK-47 | Redline (Field-Tested)',
- 'market_tradable_restriction': '7',
- 'marketable': 1,
- 'name': 'AK-47 | Redline',
- 'name_color': 'D2D2D2',
- 'owner_descriptions': '',
- 'tags': [{'category': 'Type',
- 'category_name': 'Type',
- 'internal_name': 'CSGO_Type_Rifle',
- 'name': 'Rifle'},
- {'category': 'Weapon',
- 'category_name': 'Weapon',
- 'internal_name': 'weapon_ak47',
- 'name': 'AK-47'},
- {'category': 'ItemSet',
- 'category_name': 'Collection',
- 'internal_name': 'set_community_2',
- 'name': 'The Phoenix Collection'},
- {'category': 'Quality',
- 'category_name': 'Category',
- 'internal_name': 'normal',
- 'name': 'Normal'},
- {'category': 'Rarity',
- 'category_name': 'Quality',
- 'color': 'd32ce6',
- 'internal_name': 'Rarity_Legendary_Weapon',
- 'name': 'Classified'},
- {'category': 'Exterior',
- 'category_name': 'Exterior',
- 'internal_name': 'WearCategory2',
- 'name': 'Field-Tested'},
- {'category': 'Tournament',
- 'category_name': 'Tournament',
- 'internal_name': 'Tournament6',
- 'name': '2015 ESL One Katowice'},
- {'category': 'Tournament',
- 'category_name': 'Tournament',
- 'internal_name': 'Tournament7',
- 'name': '2015 ESL One Cologne'},
- {'category': 'TournamentTeam',
- 'category_name': 'Team',
- 'internal_name': 'Team39',
- 'name': 'PENTA Sports'},
- {'category': 'TournamentTeam',
- 'category_name': 'Team',
- 'internal_name': 'Team29',
- 'name': 'mousesports'}],
- 'tradable': 1,
- 'type': 'Classified Rifle'}}
+inventory_entry = {'7146788981': {'actions': [{
+ 'link': 'steam://rungame/730/76561202255233023/+csgo_econ_action_preview%20S%owner_steamid%A%assetid%D316070896107169653',
+ 'name': 'Inspect in Game...'}],
+ 'amount': '1',
+ 'appid': '730',
+ 'background_color': '',
+ 'classid': '1304827205',
+ 'commodity': 0,
+ 'contextid': '2',
+ 'descriptions': [{'type': 'html',
+ 'value': 'Exterior: Field-Tested'},
+ {'type': 'html', 'value': ' '},
+ {'type': 'html',
+ 'value': 'Powerful and reliable, the AK-47 '
+ 'is one of the most popular assault '
+ 'rifles in the world. It is most '
+ 'deadly in short, controlled bursts '
+ 'of fire. It has been painted using '
+ 'a carbon fiber hydrographic and a '
+ 'dry-transfer decal of a red '
+ 'pinstripe.\n'
+ '\n'
+ 'Never be afraid to push it to '
+ 'the limit'},
+ {'type': 'html', 'value': ' '},
+ {'app_data': {'def_index': '65535',
+ 'is_itemset_name': 1},
+ 'color': '9da1a9',
+ 'type': 'html',
+ 'value': 'The Phoenix Collection'},
+ {'type': 'html', 'value': ' '},
+ {'app_data': {'def_index': '65535'},
+ 'type': 'html',
+ 'value': '
Sticker: '
+ 'PENTA Sports | Katowice 2015, '
+ 'PENTA Sports | Katowice 2015, '
+ 'PENTA Sports | Katowice 2015, '
+ 'mousesports | Cologne '
+ '2015 '}],
+ 'icon_drag_url': '',
+ 'icon_url': '-9a81dlWLwJ2UUGcVs_nsVtzdOEdtWwKGZZLQHTxDZ7I56KU0Zwwo4NUX4oFJZEHLbXH5ApeO4YmlhxYQknCRvCo04DEVlxkKgpot7HxfDhjxszJemkV09-5lpKKqPrxN7LEmyVQ7MEpiLuSrYmnjQO3-UdsZGHyd4_Bd1RvNQ7T_FDrw-_ng5Pu75iY1zI97bhLsvQz',
+ 'icon_url_large': '-9a81dlWLwJ2UUGcVs_nsVtzdOEdtWwKGZZLQHTxDZ7I56KU0Zwwo4NUX4oFJZEHLbXH5ApeO4YmlhxYQknCRvCo04DEVlxkKgpot7HxfDhjxszJemkV09-5lpKKqPrxN7LEm1Rd6dd2j6eQ9N2t2wK3-ENsZ23wcIKRdQE2NwyD_FK_kLq9gJDu7p_KyyRr7nNw-z-DyIFJbNUz',
+ 'id': '7146788981',
+ 'instanceid': '480085569',
+ 'market_actions': [{
+ 'link': 'steam://rungame/730/76561202255233023/+csgo_econ_action_preview%20M%listingid%A%assetid%D316070896107169653',
+ 'name': 'Inspect in Game...'}],
+ 'market_hash_name': 'AK-47 | Redline (Field-Tested)',
+ 'market_name': 'AK-47 | Redline (Field-Tested)',
+ 'market_tradable_restriction': '7',
+ 'marketable': 1,
+ 'name': 'AK-47 | Redline',
+ 'name_color': 'D2D2D2',
+ 'owner_descriptions': '',
+ 'tags': [{'category': 'Type',
+ 'category_name': 'Type',
+ 'internal_name': 'CSGO_Type_Rifle',
+ 'name': 'Rifle'},
+ {'category': 'Weapon',
+ 'category_name': 'Weapon',
+ 'internal_name': 'weapon_ak47',
+ 'name': 'AK-47'},
+ {'category': 'ItemSet',
+ 'category_name': 'Collection',
+ 'internal_name': 'set_community_2',
+ 'name': 'The Phoenix Collection'},
+ {'category': 'Quality',
+ 'category_name': 'Category',
+ 'internal_name': 'normal',
+ 'name': 'Normal'},
+ {'category': 'Rarity',
+ 'category_name': 'Quality',
+ 'color': 'd32ce6',
+ 'internal_name': 'Rarity_Legendary_Weapon',
+ 'name': 'Classified'},
+ {'category': 'Exterior',
+ 'category_name': 'Exterior',
+ 'internal_name': 'WearCategory2',
+ 'name': 'Field-Tested'},
+ {'category': 'Tournament',
+ 'category_name': 'Tournament',
+ 'internal_name': 'Tournament6',
+ 'name': '2015 ESL One Katowice'},
+ {'category': 'Tournament',
+ 'category_name': 'Tournament',
+ 'internal_name': 'Tournament7',
+ 'name': '2015 ESL One Cologne'},
+ {'category': 'TournamentTeam',
+ 'category_name': 'Team',
+ 'internal_name': 'Team39',
+ 'name': 'PENTA Sports'},
+ {'category': 'TournamentTeam',
+ 'category_name': 'Team',
+ 'internal_name': 'Team29',
+ 'name': 'mousesports'}],
+ 'tradable': 1,
+ 'type': 'Classified Rifle'}}
```
**get_partner_inventory(partner_steam_id: str, game: GameOptions, merge: bool = True, count: int = 5000) -> dict**
@@ -380,9 +448,11 @@ to covnert money string to Decimal if `convert_to_decimal` is set to `True`.
Example:
```python
- with SteamClient(api_key, login, password, steam_guard_file) as client:
+from steampy.client import SteamClient
+from decimal import Decimal
+with SteamClient('MY_API_KEY', 'MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE') as client:
wallet_balance = client.get_wallet_balance()
- self.assertTrue(type(wallet_balance), decimal.Decimal)
+ assert type(wallet_balance) == Decimal
```
market methods
@@ -400,10 +470,13 @@ Default currency is USD
May rise `TooManyRequests` exception if used more than 20 times in 60 seconds.
```python
-steam_client = SteamClient(self.credentials.api_key)
+from steampy.client import SteamClient
+from steampy.models import GameOptions
+
+steam_client = SteamClient('API_KEY')
item = 'M4A1-S | Cyrex (Factory New)'
-steam_client.market.fetch_price(item, game=GameOptions.CS)
-{'volume': '208', 'lowest_price': '$11.30 USD', 'median_price': '$11.33 USD', 'success': True}
+price = steam_client.market.fetch_price(item, game=GameOptions.CS)
+# price == {'volume': '208', 'lowest_price': '$11.30 USD', 'median_price': '$11.33 USD', 'success': True}
```
**fetch_price_history(item_hash_name: str, game: GameOptions) -> dict**
@@ -412,8 +485,10 @@ Using `SteamClient.login` method is required before usage
Returns list of price history of and item.
```python
-with SteamClient(self.credentials.api_key, self.credentials.login,
- self.credentials.password, self.steam_guard_file) as client:
+from steampy.client import SteamClient
+from steampy.models import GameOptions
+
+with SteamClient('MY_API_KEY', 'MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE') as client:
item = 'M4A1-S | Cyrex (Factory New)'
response = client.market.fetch_price_history(item, GameOptions.CS)
response['prices'][0]
@@ -429,9 +504,10 @@ Using `SteamClient.login` method is required before usage
Returns market listings posted by user
```python
-steam_client = SteamClient(self.credentials.api_key)
-steam_client.login('MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE')
-listings = steam_client.market.get_my_market_listings()
+from steampy.client import SteamClient
+
+with SteamClient('MY_API_KEY', 'MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE') as client:
+ listings = client.market.get_my_market_listings()
```
@@ -442,11 +518,13 @@ Using `SteamClient.login` method is required before usage
Create sell order of the asset on the steam market.
```python
-steam_client = SteamClient(self.credentials.api_key)
-steam_client.login('MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE')
-asset_id_to_sell = 'some_asset_id'
-game = GameOptions.DOTA2
-sell_response = steam_client.market.create_sell_order(asset_id_to_sell, game, "10000")
+from steampy.client import SteamClient
+from steampy.models import GameOptions
+
+with SteamClient('MY_API_KEY', 'MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE') as client:
+ asset_id_to_sell = 'some_asset_id'
+ game = GameOptions.DOTA2
+ sell_response = client.market.create_sell_order(asset_id_to_sell, game, "10000")
```
⚠️ `money_to_receive` has to be in cents, so "100.00" should be passed has "10000"
@@ -458,10 +536,12 @@ Using `SteamClient.login` method is required before usage
Create buy order of the assets on the steam market.
```python
-steam_client = SteamClient(self.credentials.api_key)
-steam_client.login('MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE')
-response = steam_client.market.create_buy_order("AK-47 | Redline (Field-Tested)", "1034", 2, GameOptions.CS, Currency.EURO)
-buy_order_id = response["buy_orderid"]
+from steampy.client import SteamClient
+from steampy.models import GameOptions, Currency
+
+with SteamClient('MY_API_KEY', 'MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE') as client:
+ response = client.market.create_buy_order("AK-47 | Redline (Field-Tested)", "1034", 2, GameOptions.CS, Currency.EURO)
+ buy_order_id = response["buy_orderid"]
```
⚠️ `price_single_item` has to be in cents, so "10.34" should be passed has "1034"
@@ -472,11 +552,13 @@ Using `SteamClient.login` method is required before usage
Buy a certain item from market listing.
```python
-steam_client = SteamClient(self.credentials.api_key)
-steam_client.login('MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE')
-response = steam_client.market.buy_item('AK-47 | Redline (Field-Tested)', '1942659007774983251', 81, 10,
+from steampy.client import SteamClient
+from steampy.models import Currency, GameOptions
+
+with SteamClient('MY_API_KEY', 'MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE') as client:
+ response = client.market.buy_item('AK-47 | Redline (Field-Tested)', '1942659007774983251', 81, 10,
GameOptions.CS, Currency.RUB)
-wallet_balance = response["wallet_info"]["wallet_balance"]
+ wallet_balance = response["wallet_info"]["wallet_balance"]
```
**cancel_sell_order(sell_listing_id: str) -> None**
@@ -486,10 +568,11 @@ Using `SteamClient.login` method is required before usage
Cancel previously requested sell order on steam market.
```python
-steam_client = SteamClient(self.credentials.api_key)
-steam_client.login('MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE')
-sell_order_id = "some_sell_order_id"
-response = steam_client.market.cancel_sell_order(sell_order_id)
+from steampy.client import SteamClient
+
+with SteamClient('MY_API_KEY', 'MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE') as client:
+ sell_order_id = "some_sell_order_id"
+ response = client.market.cancel_sell_order(sell_order_id)
```
**cancel_buy_order(buy_order_id) -> dict**
@@ -499,10 +582,11 @@ Using `SteamClient.login` method is required before usage
Cancel previously requested buy order on steam market.
```python
-steam_client = SteamClient(self.credentials.api_key)
-steam_client.login('MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE')
-buy_order_id = "some_buy_order_id"
-response = steam_client.market.cancel_buy_order(buy_order_id)
+from steampy.client import SteamClient
+
+with SteamClient('MY_API_KEY', 'MY_USERNAME', 'MY_PASSWORD', 'PATH_TO_STEAMGUARD_FILE') as client:
+ buy_order_id = "some_buy_order_id"
+ response = client.market.cancel_buy_order(buy_order_id)
```
Currencies
@@ -654,7 +738,7 @@ In some tests you also have to obtain `transaction_id`.
You can do it by `SteamClient.get_trade_offers` or by logging manually into steam account in browser and get it from url
In some tests you also have to obtain partner steam id.
-You can do it by by logging manually into steam account in browser and get it from url
+You can do it by logging manually into steam account in browser and get it from url
License
=======
diff --git a/steampy/client.py b/steampy/client.py
index 6337910..dc99246 100644
--- a/steampy/client.py
+++ b/steampy/client.py
@@ -1,5 +1,5 @@
import decimal
-
+import re
import bs4
import urllib.parse as urlparse
from typing import List, Union
@@ -15,35 +15,72 @@
from steampy.models import Asset, TradeOfferState, SteamUrl, GameOptions
from steampy.utils import text_between, texts_between, merge_items_with_descriptions_from_inventory, \
steam_id_to_account_id, merge_items_with_descriptions_from_offers, get_description_key, \
- merge_items_with_descriptions_from_offer, account_id_to_steam_id, get_key_value_from_url, parse_price
-
-
-def login_required(func):
- def func_wrapper(self, *args, **kwargs):
- if not self.was_login_executed:
- raise LoginRequired('Use login method first')
- else:
- return func(self, *args, **kwargs)
-
- return func_wrapper
+ merge_items_with_descriptions_from_offer, account_id_to_steam_id, get_key_value_from_url, parse_price, \
+ ping_proxy, login_required
class SteamClient:
- def __init__(self, api_key: str, username: str = None, password: str = None, steam_guard:str = None) -> None:
+ def __init__(self, api_key: str, username: str = None, password: str = None, steam_guard: str = None,
+ login_cookies: dict = None, proxies: dict = None) -> None:
self._api_key = api_key
self._session = requests.Session()
- self.steam_guard = steam_guard
+ if proxies:
+ self.set_proxies(proxies)
+ self.steam_guard_string = steam_guard
+ if self.steam_guard_string is not None:
+ self.steam_guard = guard.load_steam_guard(self.steam_guard)
+ else:
+ self.steam_guard = None
self.was_login_executed = False
self.username = username
self._password = password
self.market = SteamMarket(self._session)
self.chat = SteamChat(self._session)
+ if login_cookies:
+ self.set_login_cookies(login_cookies)
+
+ 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"\}')
+ proxy_status = ping_proxy(proxies)
+ if proxy_status is True:
+ self._session.proxies.update(proxies)
+ return proxies
+
+ def set_login_cookies(self, cookies: dict) -> None:
+ self._session.cookies.update(cookies)
+ self.was_login_executed = True
+ if self.steam_guard is None:
+ self.steam_guard = {"steam_id": self.get_steam_id()}
+ self.market._set_login_executed(self.steam_guard, self._get_session_id())
- 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()
+ @login_required
+ def get_steam_id(self) -> int:
+ url = SteamUrl.COMMUNITY_URL
+ response = self._session.get(url)
+ steam_id = re.match(r'g_steamID = "(\d+)";', response.text)
+ if steam_id:
+ return int(steam_id.group(1))
+ else:
+ raise ValueError('Invalid steam_id: {}'.format(steam_id))
+
+ def login(self, username: str = None, password: str = None, steam_guard: str = None) -> None:
+ if self.was_login_executed and self.is_session_alive():
+ # session is alive, no need login again
+ return
+
+ if None in [self.username, self._password, self.steam_guard_string] and None in [username, password, steam_guard]:
+ raise InvalidCredentials('You have to pass username, password and steam_guard'
+ 'parameters when using "login" method')
+
+ if None in [self.username, self._password, self.steam_guard_string]:
+ self.steam_guard_string = steam_guard
+ self.steam_guard = guard.load_steam_guard(steam_guard)
+ self.username = username
+ self._password = password
+
+ LoginExecutor(self.username, self._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())
@@ -57,10 +94,7 @@ def logout(self) -> None:
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)
+ self.login(self.username, self._password, self.steam_guard_string)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
@@ -76,9 +110,9 @@ def api_call(self, request_method: str, interface: str, api_method: str, version
params: dict = None) -> requests.Response:
url = '/'.join([SteamUrl.API_URL, interface, api_method, version])
if request_method == 'GET':
- response = requests.get(url, params=params, verify=self._session.verify, auth=self._session.auth)
+ response = self._session.get(url, params=params)
else:
- response = requests.post(url, data=params, verify=self._session.verify, auth=self._session.auth)
+ response = self._session.post(url, data=params)
if self.is_invalid_api_key(response):
raise InvalidCredentials('Invalid API key')
return response
@@ -94,7 +128,8 @@ def get_my_inventory(self, game: GameOptions, merge: bool = True, count: int = 5
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:
+ 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}
@@ -250,7 +285,7 @@ def get_profile(self, steam_id: str) -> dict:
data = response.json()
return data['response']['players'][0]
- def get_friend_list(self, steam_id: str, relationship_filter: str="all") -> dict:
+ def get_friend_list(self, steam_id: str, relationship_filter: str = "all") -> dict:
params = {
'key': self._api_key,
'steamid': steam_id,
@@ -288,7 +323,7 @@ def get_escrow_duration(self, trade_offer_url: str) -> int:
@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:
+ 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)
diff --git a/steampy/exceptions.py b/steampy/exceptions.py
index c40f9f7..0a55123 100644
--- a/steampy/exceptions.py
+++ b/steampy/exceptions.py
@@ -24,3 +24,6 @@ class CaptchaRequired(Exception):
class ConfirmationExpected(Exception):
pass
+
+class ProxyConnectionError(Exception):
+ pass
\ No newline at end of file
diff --git a/steampy/market.py b/steampy/market.py
index b95fb07..6a156ab 100644
--- a/steampy/market.py
+++ b/steampy/market.py
@@ -7,17 +7,7 @@
from steampy.exceptions import ApiException, TooManyRequests, LoginRequired
from steampy.models import Currency, SteamUrl, GameOptions
from steampy.utils import text_between, get_listing_id_to_assets_address_from_html, get_market_listings_from_html, \
- merge_items_with_descriptions_from_listing, get_market_sell_listings_from_api
-
-
-def login_required(func):
- def func_wrapper(self, *args, **kwargs):
- if not self.was_login_executed:
- raise LoginRequired('Use login method first on SteamClient')
- else:
- return func(self, *args, **kwargs)
-
- return func_wrapper
+ merge_items_with_descriptions_from_listing, get_market_sell_listings_from_api, login_required
class SteamMarket:
@@ -32,9 +22,9 @@ def _set_login_executed(self, steamguard: dict, session_id: str):
self._session_id = session_id
self.was_login_executed = True
- def fetch_price(self, item_hash_name: str, game: GameOptions, currency: str = Currency.USD) -> dict:
+ def fetch_price(self, item_hash_name: str, game: GameOptions, currency: str = Currency.USD, country='PL') -> dict:
url = SteamUrl.COMMUNITY_URL + '/market/priceoverview/'
- params = {'country': 'PL',
+ params = {'country': country,
'currency': currency.value,
'appid': game.app_id,
'market_hash_name': item_hash_name}
@@ -104,7 +94,8 @@ def create_sell_order(self, assetid: str, game: GameOptions, money_to_receive: s
}
headers = {'Referer': "%s/profiles/%s/inventory" % (SteamUrl.COMMUNITY_URL, self._steam_guard['steamid'])}
response = self._session.post(SteamUrl.COMMUNITY_URL + "/market/sellitem/", data, headers=headers).json()
- if response.get("needs_mobile_confirmation"):
+ has_pending_confirmation = 'pending confirmation' in response.get('message', '')
+ if response.get("needs_mobile_confirmation") or (response.get('success') is False and has_pending_confirmation):
return self._confirm_sell_listing(assetid)
return response
@@ -119,7 +110,7 @@ def create_buy_order(self, market_name: str, price_single_item: str, quantity: i
"price_total": str(Decimal(price_single_item) * Decimal(quantity)),
"quantity": quantity
}
- headers = {'Referer': "%s/market/listings/%s/%s" % (SteamUrl.COMMUNITY_URL, game.app_id,
+ headers = {'Referer': "%s/market/listings/%s/%s" % (SteamUrl.COMMUNITY_URL, game.app_id,
urllib.parse.quote(market_name))}
response = self._session.post(SteamUrl.COMMUNITY_URL + "/market/createbuyorder/", data,
headers=headers).json()
diff --git a/steampy/utils.py b/steampy/utils.py
index a44c0fe..269f0af 100644
--- a/steampy/utils.py
+++ b/steampy/utils.py
@@ -1,5 +1,7 @@
import os
import re
+import requests
+import re
import copy
import math
import struct
@@ -11,6 +13,17 @@
from requests.structures import CaseInsensitiveDict
from steampy.models import GameOptions
+from steampy.exceptions import ProxyConnectionError, LoginRequired
+
+
+def login_required(func):
+ def func_wrapper(self, *args, **kwargs):
+ if not self.was_login_executed:
+ raise LoginRequired('Use login method first')
+ else:
+ return func(self, *args, **kwargs)
+
+ return func_wrapper
def text_between(text: str, begin: str, end: str) -> str:
@@ -200,7 +213,9 @@ def get_buy_orders_from_node(node: Tag) -> dict:
"order_id": order.attrs["id"].replace("mybuyorder_", ""),
"quantity": int(qnt_price_raw[0].strip()),
"price": qnt_price_raw[1].strip(),
- "item_name": order.a.text
+ "item_name": order.a.text,
+ "icon_url": order.select(f"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
return buy_orders_dict
@@ -237,3 +252,10 @@ def __init__(self, login: str, password: str, api_key: str):
self.login = login
self.password = password
self.api_key = api_key
+
+def ping_proxy(proxies: dict):
+ try:
+ requests.get('https://steamcommunity.com/', proxies = proxies)
+ return True
+ except Exception as e:
+ raise ProxyConnectionError("Proxy not working for steamcommunity.com")