Skip to content

Commit

Permalink
Proxies support, docs, UserAgentService. v0.5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
somespecialone committed May 31, 2024
1 parent 22af4c1 commit 6c8f324
Show file tree
Hide file tree
Showing 17 changed files with 281 additions and 52 deletions.
9 changes: 4 additions & 5 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ authors:
- given-names: Dmytro
family-names: Tkachenko
email: itsme@somespecial.one
repository-code: 'https://github.com/somespecialone/aiosteampy'
url: 'https://aiosteampy.somespecial.one/'
repository-artifact: 'https://pypi.org/project/aiosteampy/'
repository-code: "https://github.com/somespecialone/aiosteampy"
url: "https://aiosteampy.somespecial.one/"
repository-artifact: "https://pypi.org/project/aiosteampy/"
abstract: >-
Simple library to trade and interact with steam market,
webapi, guard.
Trade and interact with steam market, webapi, guard.
keywords:
- python
- asyncio
Expand Down
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,19 @@ pipenv install aiosteampy
poetry add aiosteampy
```

Project have extra [currencies converter](https://aiosteampy.somespecial.one/ext/converter/) with
target dependency `aiosteampy[converter]`. For instance:
Project have some extras [currencies converter](https://aiosteampy.somespecial.one/ext/converter/),
[socks proxies](https://aiosteampy.somespecial.one/proxies).
To install them all, please, use `aiosteampy[all]` install target:

```shell
poetry add aiosteampy[converter]
poetry add aiosteampy[all]
```

<!--install-end-->

> [!TIP]
> [aiohttp docs](https://docs.aiohttp.org/en/stable/#installing-all-speedups-in-one-command) recommends installing speedups (`aiodns`, `cchardet`, ...)
> [aiohttp docs](https://docs.aiohttp.org/en/stable/#installing-all-speedups-in-one-command) recommends installing
> speedups (`aiodns`, `cchardet`, ...)
<!--intro-start-->

Expand All @@ -71,6 +73,7 @@ with modern async/await syntax.
- Declarative: there is models almost for every data.
- Typed: for editor support most things are typed.
- Short: I really tried to fit most important for steam trading methods.
- Connection behind web proxy.

## What can I do with this

Expand Down Expand Up @@ -107,6 +110,8 @@ I will be very grateful for helping me get the things right.
## Credits

- [bukson/steampy](https://github.com/bukson/steampy)
- [aiohttp-socks](https://github.com/romis2012/aiohttp-socks)
- [croniter](https://github.com/kiorky/croniter)
- [DoctorMcKay/node-steamcommunity](https://github.com/DoctorMcKay/node-steamcommunity)
- [Identifying Steam items](https://dev.doctormckay.com/topic/332-identifying-steam-items/)
- [Revadike/InternalSteamWebAPI](https://github.com/Revadike/InternalSteamWebAPI)
Expand Down
2 changes: 1 addition & 1 deletion aiosteampy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Simple library to trade and interact with steam market, webapi, guard.
Trade and interact with steam market, webapi, guard.
"""

from .exceptions import ApiError, LoginError, ConfirmationError, SessionExpired
Expand Down
9 changes: 8 additions & 1 deletion aiosteampy/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def __init__(
lang=Language.ENGLISH,
tz_offset=0.0,
session: ClientSession = None,
proxy: str = None,
user_agent: str = None,
):
self.username = username
Expand All @@ -84,7 +85,13 @@ def __init__(

self._wallet_country = wallet_country

super().__init__(session=session, user_agent=user_agent, access_token=access_token, refresh_token=refresh_token)
super().__init__(
session=session,
user_agent=user_agent,
access_token=access_token,
refresh_token=refresh_token,
proxy=proxy,
)

self._set_init_cookies(lang, tz_offset)

Expand Down
9 changes: 4 additions & 5 deletions aiosteampy/converter.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Currency converter extension."""

import asyncio
from collections import UserDict
from datetime import datetime, timedelta
Expand All @@ -15,10 +17,7 @@
"""
The `aiosteampy.converter` module requires the `croniter` library to be installed
to make the rates synchronization with backend service work.
In order You need this functionality, You can use `aiosteampy[converter]` dependency install target:
`pip install aiosteampy[converter]`
or
`poetry add aiosteampy[converter]`
In order You need this functionality, You can use `aiosteampy[converter]` dependency install target.
""",
category=RuntimeWarning,
)
Expand Down Expand Up @@ -133,7 +132,7 @@ def convert(self, amount: int, currency: Currency, target=Currency.USD) -> int:

# direct conversion
# return round(amount * (target_rate / source_rate))

# with USD in middle step
usd_amount = round(amount * (1 / source_rate))
return round(usd_amount * target_rate)
41 changes: 38 additions & 3 deletions aiosteampy/http.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
from aiohttp import ClientSession
from functools import partial

from yarl import URL
from aiohttp import ClientSession, InvalidURL

try:
from aiohttp_socks import ProxyConnector
except ImportError:
ProxyConnector = None


__all__ = ("SteamHTTPTransportMixin",)
Expand All @@ -16,8 +24,35 @@ class SteamHTTPTransportMixin:

session: ClientSession

def __init__(self, *args, session: ClientSession = None, user_agent: str = None, **kwargs):
self.session = session or ClientSession(raise_for_status=True)
def __init__(self, *args, session: ClientSession = None, proxy: str = None, user_agent: str = None, **kwargs):
if proxy and session:
raise ValueError("You need to handle proxy connection by yourself with predefined session instance.")
elif proxy:
if "socks" in proxy:
if ProxyConnector is None:
raise TypeError(
"""
To use `socks` type proxies you need `aiohttp_socks` package.
You can do this with `aiosteampy[socks]` dependency install target.
"""
)

self.session = ClientSession(connector=ProxyConnector.from_url(proxy), raise_for_status=True)
else: # http/s
self.session = ClientSession(raise_for_status=True)

try:
proxy = URL(proxy)
except ValueError as e:
raise InvalidURL(proxy) from e

self.session._request = partial(self.session._request, proxy=proxy) # patch session instance

elif session:
self.session = session
else:
self.session = ClientSession(raise_for_status=True)

if user_agent:
self.user_agent = user_agent

Expand Down
42 changes: 21 additions & 21 deletions aiosteampy/trade.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, overload, Literal, Type, TypeAlias, Callable
from typing import TYPE_CHECKING, overload, Literal, Type, TypeAlias, Callable, Iterable, Sequence
from datetime import datetime
from json import dumps as jdumps

Expand Down Expand Up @@ -423,7 +423,7 @@ async def accept_trade_offer(self: "SteamCommunityMixin", offer: int | TradeOffe
raise ValueError("You can't accept your offer! Are you trying to cancel outgoing offer?")
offer_id = offer.id
partner = offer.partner_id64
to_remove = TradeOffer
to_remove = offer
else: # int
if not partner:
fetched = await self.get_or_fetch_trade_offer(offer)
Expand Down Expand Up @@ -452,8 +452,8 @@ async def accept_trade_offer(self: "SteamCommunityMixin", offer: int | TradeOffe
async def make_trade_offer(
self,
obj: int,
to_give: list[EconItemType] = ...,
to_receive: list[EconItemType] = ...,
to_give: Sequence[EconItemType] = ...,
to_receive: Sequence[EconItemType] = ...,
message: str = ...,
*,
token: str = ...,
Expand All @@ -466,8 +466,8 @@ async def make_trade_offer(
async def make_trade_offer(
self,
obj: str,
to_give: list[EconItemType] = ...,
to_receive: list[EconItemType] = ...,
to_give: Sequence[EconItemType] = ...,
to_receive: Sequence[EconItemType] = ...,
message: str = ...,
*,
confirm: bool = ...,
Expand All @@ -478,8 +478,8 @@ async def make_trade_offer(
async def make_trade_offer(
self: "SteamCommunityMixin",
obj: int | str,
to_give: list[EconItemType] = (),
to_receive: list[EconItemType] = (),
to_give: Sequence[EconItemType] = (),
to_receive: Sequence[EconItemType] = (),
message="",
*,
token: str = None,
Expand All @@ -493,13 +493,13 @@ async def make_trade_offer(
.. note:: Make sure that partner is in friends list if you not pass trade url or trade token.
:param obj: partner trade url, partner id(id32 or id64)
:param token:
:param to_give:
:param to_receive:
:param message:
:param confirm:
:param countered_id:
:param kwargs:
:param token: trade token (mandatory if `obj` is partner id)
:param to_give: sequence of items that you want to give
:param to_receive: sequence of items that you want to receive
:param message: message to the partner
:param confirm: auto-confirm offer
:param countered_id: id of offer that you want to counter. Use `counter_trade_offer` method for this
:param kwargs: additional data to send in payload
:return: trade offer id
:raises ValueError: trade is empty
"""
Expand Down Expand Up @@ -571,8 +571,8 @@ def _parse_make_offer_args(obj: str | int, token: str | None) -> tuple[str | Non
async def counter_trade_offer(
self,
obj: TradeOffer,
to_give: list[EconItemType] = (),
to_receive: list[EconItemType] = (),
to_give: Sequence[EconItemType] = (),
to_receive: Sequence[EconItemType] = (),
message="",
*,
confirm: bool = ...,
Expand All @@ -583,8 +583,8 @@ async def counter_trade_offer(
async def counter_trade_offer(
self,
obj: int,
to_give: list[EconItemType] = (),
to_receive: list[EconItemType] = (),
to_give: Sequence[EconItemType] = (),
to_receive: Sequence[EconItemType] = (),
message="",
*,
partner_id: int,
Expand All @@ -595,8 +595,8 @@ async def counter_trade_offer(
def counter_trade_offer(
self,
obj: TradeOffer | int,
to_give: list[EconItemType] = (),
to_receive: list[EconItemType] = (),
to_give: Sequence[EconItemType] = (),
to_receive: Sequence[EconItemType] = (),
message="",
*,
partner_id: int = None,
Expand Down
42 changes: 42 additions & 0 deletions aiosteampy/user_agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""User agents service extension."""

from collections import UserList
from random import choice

from yarl import URL
from aiohttp import ClientSession

API_URL = URL("https://randua.somespecial.one")


class UserAgentsService(UserList[str]):
"""
List-like class of user agents responsible for loading and getting random user agents.
.. seealso:: https://github.com/somespecialone/random-user-agent
"""

__slots__ = ("_api_url",)

def __init__(self, *, api_url=API_URL):
"""
:param api_url: url of `random user agent` backend service api
"""

super().__init__()

self._api_url = api_url

@property
def agents(self) -> list[str]:
return self.data

async def load(self):
async with ClientSession(raise_for_status=True) as sess:
r = await sess.get(self._api_url / "all")
agents: list[str] = await r.json()

self.data = agents

def get_random(self) -> str:
return choice(self.agents)
18 changes: 14 additions & 4 deletions aiosteampy/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Useful utils."""

import asyncio
from base64 import b64decode, b64encode
from struct import pack, unpack
Expand All @@ -12,7 +14,7 @@
from re import search as re_search
from json import loads as j_loads

from aiohttp import ClientSession
from aiohttp import ClientSession, ClientResponse
from yarl import URL

from .typed import JWTToken
Expand Down Expand Up @@ -97,7 +99,7 @@ def extract_openid_payload(page_text: str) -> dict[str, str]:
}


async def do_session_steam_auth(session: ClientSession, auth_url: str | URL):
async def do_session_steam_auth(session: ClientSession, auth_url: str | URL) -> ClientResponse:
"""
Request auth page, find specs of steam openid and log in through steam with passed session.
Use it when you need to log in 3rd party site trough Steam using only cookies.
Expand All @@ -106,14 +108,15 @@ async def do_session_steam_auth(session: ClientSession, auth_url: str | URL):
:param session: just session.
:param auth_url: url to site, which redirect you to steam login page.
:return: response with history, headers and data
"""

r = await session.get(auth_url)
rt = await r.text()

data = extract_openid_payload(rt)

await session.post("https://steamcommunity.com/openid/login", data=data, allow_redirects=True)
return await session.post("https://steamcommunity.com/openid/login", data=data, allow_redirects=True)


def get_cookie_value_from_session(session: ClientSession, url: URL, field: str) -> str | None:
Expand Down Expand Up @@ -243,10 +246,11 @@ async def restore_from_cookies(
*,
init_data=True,
**init_kwargs,
):
) -> bool:
"""
Helper func. Restore client session from cookies.
Login if session is not alive.
Return `True` if cookies are valid and not expired.
"""

prepared = []
Expand All @@ -272,9 +276,11 @@ async def restore_from_cookies(
client.session.cookie_jar.update_cookies(c)
if not (await client.is_session_alive()):
await client.login(init_data=init_data, **init_kwargs)
return False
else:
client._is_logged = True
init_data and await client._init_data()
return True


def get_jsonable_cookies(session: ClientSession) -> JSONABLE_COOKIE_JAR:
Expand Down Expand Up @@ -399,3 +405,7 @@ def decode_jwt(token: str) -> JWTToken:
raise ValueError("Invalid JWT", parts)

return j_loads(b64decode(parts[1] + "==", altchars="-_"))


def patch_session_with_proxy(session: ClientSession, proxy: str):
pass
3 changes: 1 addition & 2 deletions docs/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ histogram = await client.fetch_item_orders_histogram(12345687)

### Proxies

For proxies support you can use [aiohttp-socks](https://github.com/romis2012/aiohttp-socks) as you can create `session` by
yourself.
Read more about proxy support on the [dedicated page](./proxies.md)

### Inheritance

Expand Down
Loading

0 comments on commit 6c8f324

Please sign in to comment.