Skip to content

Commit

Permalink
Market listings pagination, inventory pagination, rate limit error.
Browse files Browse the repository at this point in the history
  • Loading branch information
somespecialone committed Aug 2, 2024
1 parent 7da3931 commit 4bdfa56
Show file tree
Hide file tree
Showing 5 changed files with 395 additions and 102 deletions.
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,24 +114,35 @@ with modern async/await syntax.

## TODO 📃

Hard to say **roadmap**. Can be a little changed or updated later, get ready.
> Hard to say **roadmap**. Can be a little changed or updated later, get ready.
Path to first **stable release**. Non-exhaustive list, scheduled tasks can be done earlier than the version mentioned,
but not otherwise.

### v0.6.0

- [ ] Listings, items, offers pagination/iteration
- [ ] Get single item from inventory as browser does
- [ ] Change client username method

### v0.7.0

- [ ] Remove storage methods. Caching entities must be user responsibility
- [ ] Rename `fetch_...` methods to `get_...` to remove annoying methods symantic mess
- [ ] Web browser mechanism to fetch trade offers from `Steam`, avoiding `Steam Web Api`

### v0.7.0
### v0.8.0

- [ ] Context managers as helpers to login/logout, load/dump or get/put cookies
- [ ] Fetch/paginate over market search pages

### v0.9.0

- [ ] `Steam user` model with minimal attrs, retrieving/fetching
- [ ] Web browser mechanism to fetch trade offers from `Steam`, avoiding `Steam Web Api`
- [ ] Refresh `access_token` mechanism

### v1.0.0

Path to first **stable release**

- [ ] Tests with `Steam API` mocking. Target coverage ~70%. Key points (listings, inventory items, trade offers) testing
suits is mandatory
- [ ] Maturity, battle-testing in **more** different cases by **more** participants/users
Expand Down
84 changes: 70 additions & 14 deletions aiosteampy/client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from re import compile, search as re_search
from typing import AsyncIterator
from urllib.parse import quote
from json import loads
from http.cookies import SimpleCookie
Expand All @@ -9,7 +10,7 @@

from .models import Notifications, EconItem
from .typed import WalletInfo, FundWalletInfo
from .constants import STEAM_URL, Currency, GameType, Language, EResult
from .constants import STEAM_URL, Currency, GameType, Language, EResult, T_PARAMS, T_HEADERS
from .exceptions import EResultError, SessionExpired, SteamError
from .utils import get_cookie_value_from_session, steam_id_to_account_id, account_id_to_steam_id

Expand All @@ -19,7 +20,7 @@
from .login import LoginMixin
from .trade import TradeMixin
from .market import MarketMixin
from .public import SteamPublicMixin, INV_PAGE_SIZE, PREDICATE
from .public import SteamPublicMixin, INV_COUNT, PREDICATE, INV_ITEM_DATA

API_KEY_RE = compile(r"<p>Key: (?P<api_key>[0-9A-F]+)</p>")
STEAM_GUARD_REQ_CHECK_RE = compile(r"Your account requires (<a [^>]+>)?Steam Guard Mobile Authenticator")
Expand Down Expand Up @@ -54,7 +55,7 @@ def __init__(
password: str,
# It is possible to get steam id from the cookie and then arg will not be necessary,
# but typical use case of the library means that the user already knows the steam id
steam_id: int,
steam_id: int, # better to place first if I keep it as required arg
*,
shared_secret: str,
identity_secret: str = None,
Expand Down Expand Up @@ -227,7 +228,8 @@ async def fetch_api_key(self) -> str:
if "You must have a validated email address to create a Steam Web API key" in rt:
raise SteamError("Validated email address required to create a Steam Web API key")
elif STEAM_GUARD_REQ_CHECK_RE.search(rt):
raise SteamError("")
# for case when `shared_secret` is "" and mobile authenticator disabled
raise SteamError("Steam Guard Mobile Authenticator is required")
elif "<h2>Access Denied</h2>" in rt:
raise SteamError("Access to Steam Web Api page is denied")

Expand Down Expand Up @@ -303,25 +305,79 @@ async def get_inventory(
self,
game: GameType,
*,
predicate: PREDICATE = None,
page_size=INV_PAGE_SIZE,
) -> list[EconItem]:
last_assetid: int = None,
count=INV_COUNT,
params: T_PARAMS = {},
headers: T_HEADERS = {},
_item_descriptions_map: dict = None,
) -> INV_ITEM_DATA:
"""
Fetches self inventory.
:param game: just Steam Game
:param page_size: max items on page. Current Steam limit is 2000
:param predicate: callable with single arg `EconItem`, must return bool
:return: list of `EconItem`
.. note::
* You can paginate by yourself passing `last_assetid` arg
* `count` arg value that less than 2000 lead to responses with strange amount of assets
:param game: Steam Game
:param last_assetid:
:param count: page size
:param params: extra params to pass to url
:param headers: extra headers to send with request
:return: list of `EconItem`, total count of items in inventory, last asset id of the list
:raises EResultError: for ordinary reasons
:raises SessionExpired:
"""

try:
inv = await self.get_user_inventory(self.steam_id, game, predicate=predicate, page_size=page_size)
return await self.get_user_inventory(
self.steam_id,
game,
last_assetid=last_assetid,
count=count,
params=params,
headers=headers,
_item_descriptions_map=_item_descriptions_map,
)
except SteamError as e:
raise SessionExpired if "private" in e.args[0] else e # self inventory can't be private
return inv
if "private" in e.args[0]: # self inventory can't be private
raise SessionExpired from e
else:
raise e

def inventory(
self,
game: GameType,
*,
last_assetid: int = None,
count=INV_COUNT,
params: T_PARAMS = {},
headers: T_HEADERS = {},
) -> AsyncIterator[INV_ITEM_DATA]:
"""
Fetches self inventory. Return async iterator to paginate over inventory pages.
.. note:: `count` arg value that less than 2000 lead to responses with strange amount of assets
:param game: Steam Game
:param last_assetid:
:param count: page size
:param params: extra params to pass to url
:param headers: extra headers to send with request
:return: `AsyncIterator` that yields list of `EconItem`, total count of items in inventory, last asset id of the list
:raises EResultError: for ordinary reasons
:raises RateLimitExceededError: when you hit rate limit
"""

return self.user_inventory(
self.steam_id,
game,
last_assetid=last_assetid,
count=count,
params=params,
headers=headers,
)

# TODO change nickname method


class SteamClient(SteamCommunityMixin):
Expand Down
6 changes: 5 additions & 1 deletion aiosteampy/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from constants import EResult
from .constants import EResult


class SteamError(Exception):
Expand All @@ -20,3 +20,7 @@ class LoginError(SteamError):

class SessionExpired(SteamError):
"""Raised when session is expired, and you need to do login"""


class RateLimitExceededError(SteamError):
"""Raised when Steam decided you were in need of a bit of a rest :)"""
Loading

0 comments on commit 4bdfa56

Please sign in to comment.