forked from FOSS/Steam-Panel
v 3.0.3
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
__version__ = '1.0.0'
|
||||
__author__ = 'Mike M'
|
||||
@@ -0,0 +1,8 @@
|
||||
from .request import RequestStrategyAbstract
|
||||
from .storage import CookieStorageAbstract
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RequestStrategyAbstract',
|
||||
'CookieStorageAbstract',
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
from abc import (
|
||||
ABC,
|
||||
abstractmethod,
|
||||
)
|
||||
from typing import (
|
||||
Any,
|
||||
Mapping,
|
||||
)
|
||||
|
||||
from aiohttp import ClientResponse
|
||||
|
||||
|
||||
class RequestStrategyAbstract(ABC):
|
||||
|
||||
@abstractmethod
|
||||
async def request(self, url: str, method: str, **kwargs: Any) -> ClientResponse: # noqa:U100
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def text(self, url: str, method: str, **kwargs: Any) -> str: # noqa:U100
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def bytes(self, url: str, method: str, **kwargs: Any) -> bytes: # noqa:U100
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def cookies(self, domain: str = 'steamcommunity.com') -> Mapping[str, str]: # noqa:U100
|
||||
...
|
||||
@@ -0,0 +1,16 @@
|
||||
from abc import (
|
||||
ABC,
|
||||
abstractmethod,
|
||||
)
|
||||
from typing import Mapping
|
||||
|
||||
|
||||
class CookieStorageAbstract(ABC):
|
||||
|
||||
@abstractmethod
|
||||
async def set(self, login: str, cookies: Mapping[str, Mapping[str, str]]) -> None: # noqa:U100
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def get(self, login: str, domain: str) -> Mapping[str, str]: # noqa:U100
|
||||
...
|
||||
@@ -0,0 +1,6 @@
|
||||
from .steam import Steam
|
||||
|
||||
|
||||
__all__ = [
|
||||
'Steam',
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Params(BaseModel):
|
||||
nonce: str
|
||||
auth: str
|
||||
|
||||
|
||||
class TransferInfoItem(BaseModel):
|
||||
url: str
|
||||
params: Params
|
||||
|
||||
|
||||
class FinalizeLoginStatus(BaseModel):
|
||||
steamID: str
|
||||
redir: str
|
||||
transfer_info: List[TransferInfoItem]
|
||||
primary_domain: str
|
||||
@@ -0,0 +1,382 @@
|
||||
import base64
|
||||
import binascii
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import math
|
||||
from struct import pack
|
||||
from typing import (
|
||||
Any,
|
||||
Mapping,
|
||||
Optional,
|
||||
)
|
||||
|
||||
import rsa
|
||||
from aiohttp import FormData
|
||||
from bitstring import BitArray
|
||||
from urllib3.util import parse_url
|
||||
|
||||
from pysteamauth.abstract import (
|
||||
CookieStorageAbstract,
|
||||
RequestStrategyAbstract,
|
||||
)
|
||||
from pysteamauth.base import (
|
||||
BaseCookieStorage,
|
||||
BaseRequestStrategy,
|
||||
)
|
||||
from pysteamauth.pb2.enums_pb2 import ESessionPersistence
|
||||
from pysteamauth.pb2.steammessages_auth.steamclient_pb2 import (
|
||||
CAuthentication_AllowedConfirmation,
|
||||
CAuthentication_BeginAuthSessionViaCredentials_Request,
|
||||
CAuthentication_BeginAuthSessionViaCredentials_Response,
|
||||
CAuthentication_GetPasswordRSAPublicKey_Request,
|
||||
CAuthentication_GetPasswordRSAPublicKey_Response,
|
||||
CAuthentication_PollAuthSessionStatus_Request,
|
||||
CAuthentication_PollAuthSessionStatus_Response,
|
||||
CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request,
|
||||
EAuthSessionGuardType,
|
||||
EAuthTokenPlatformType,
|
||||
)
|
||||
|
||||
from .schemas import FinalizeLoginStatus
|
||||
|
||||
|
||||
class Steam:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
login: str,
|
||||
password: str,
|
||||
steamid: Optional[int] = None,
|
||||
shared_secret: Optional[str] = None,
|
||||
identity_secret: Optional[str] = None,
|
||||
device_id: Optional[str] = None,
|
||||
cookie_storage: Optional[CookieStorageAbstract] = None,
|
||||
request_strategy: Optional[RequestStrategyAbstract] = None,
|
||||
):
|
||||
self._login = login
|
||||
self._steamid = steamid
|
||||
self._password = password
|
||||
self._shared_secret = shared_secret
|
||||
self._identity_secret = identity_secret
|
||||
self._device_id = device_id
|
||||
self._requests = request_strategy if request_strategy is not None else BaseRequestStrategy()
|
||||
self._storage = cookie_storage if cookie_storage is not None else BaseCookieStorage()
|
||||
|
||||
@property
|
||||
def steamid(self) -> int:
|
||||
if not self._steamid:
|
||||
raise ValueError('steamid is not specified')
|
||||
return self._steamid
|
||||
|
||||
@property
|
||||
def shared_secret(self) -> str:
|
||||
if not self._shared_secret:
|
||||
raise ValueError('shared_secret is not specified')
|
||||
return self._shared_secret
|
||||
|
||||
@property
|
||||
def identity_secret(self) -> str:
|
||||
if not self._identity_secret:
|
||||
raise ValueError('identity_secret is not specified')
|
||||
return self._identity_secret
|
||||
|
||||
@property
|
||||
def device_id(self) -> str:
|
||||
if not self._device_id:
|
||||
raise ValueError('device_id is not specified')
|
||||
return self._device_id
|
||||
|
||||
@property
|
||||
def login(self) -> str:
|
||||
return self._login
|
||||
|
||||
@property
|
||||
def partner_id(self) -> int:
|
||||
return self.steamid - 76561197960265728
|
||||
|
||||
async def cookies(self, domain: str = 'steamcommunity.com') -> Mapping[str, str]:
|
||||
return await self._storage.get(
|
||||
login=self._login,
|
||||
domain=domain,
|
||||
)
|
||||
|
||||
async def sessionid(self, domain: str = 'steamcommunity.com') -> str:
|
||||
return (await self.cookies(domain))['sessionid']
|
||||
|
||||
async def request(self, url: str, method: str = 'GET', **kwargs: Any) -> str:
|
||||
cookies = await self._storage.get(
|
||||
login=self._login,
|
||||
domain=parse_url(url).host,
|
||||
)
|
||||
return await self._requests.text(
|
||||
url=url,
|
||||
method=method,
|
||||
cookies={
|
||||
**cookies,
|
||||
**kwargs.pop('cookies', {}),
|
||||
},
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
async def is_authorized(self) -> bool:
|
||||
response: str = await self.request(
|
||||
url='https://steamcommunity.com/chat/clientjstoken',
|
||||
)
|
||||
data = json.loads(response)
|
||||
logged_in = data['logged_in']
|
||||
if logged_in:
|
||||
if not self._steamid:
|
||||
try:
|
||||
self._steamid = int(data['steamid'])
|
||||
except KeyError:
|
||||
...
|
||||
return logged_in
|
||||
|
||||
async def _getrsakey(self) -> CAuthentication_GetPasswordRSAPublicKey_Response:
|
||||
message = CAuthentication_GetPasswordRSAPublicKey_Request(
|
||||
account_name=self._login,
|
||||
)
|
||||
response = await self._requests.bytes(
|
||||
method='GET',
|
||||
url='https://api.steampowered.com/IAuthenticationService/GetPasswordRSAPublicKey/v1',
|
||||
params={
|
||||
'input_protobuf_encoded': str(base64.b64encode(message.SerializeToString()), 'utf8'),
|
||||
},
|
||||
)
|
||||
return CAuthentication_GetPasswordRSAPublicKey_Response.FromString(response)
|
||||
|
||||
async def _begin_auth_session(
|
||||
self,
|
||||
encrypted_password: str,
|
||||
rsa_timestamp: int,
|
||||
) -> CAuthentication_BeginAuthSessionViaCredentials_Response:
|
||||
message = CAuthentication_BeginAuthSessionViaCredentials_Request(
|
||||
account_name=self._login,
|
||||
encrypted_password=encrypted_password,
|
||||
encryption_timestamp=rsa_timestamp,
|
||||
remember_login=True,
|
||||
platform_type=EAuthTokenPlatformType.k_EAuthTokenPlatformType_WebBrowser,
|
||||
website_id='Community',
|
||||
persistence=ESessionPersistence.k_ESessionPersistence_Persistent,
|
||||
device_friendly_name='Mozilla/5.0 (X11; Linux x86_64; rv:1.9.5.20) Gecko/2812-12-10 04:56:28 Firefox/3.8',
|
||||
)
|
||||
response = await self._requests.bytes(
|
||||
method='POST',
|
||||
url='https://api.steampowered.com/IAuthenticationService/BeginAuthSessionViaCredentials/v1',
|
||||
data=FormData(
|
||||
fields=[
|
||||
('input_protobuf_encoded', str(base64.b64encode(message.SerializeToString()), 'utf8')),
|
||||
],
|
||||
),
|
||||
)
|
||||
return CAuthentication_BeginAuthSessionViaCredentials_Response.FromString(response)
|
||||
|
||||
async def get_server_time(self) -> int:
|
||||
response = await self._requests.text(
|
||||
method='POST',
|
||||
url='https://api.steampowered.com/ITwoFactorService/QueryTime/v0001',
|
||||
)
|
||||
data = json.loads(response)
|
||||
return int(data['response']['server_time'])
|
||||
|
||||
@classmethod
|
||||
async def get_steam_guard(cls, shared_secret: str, server_time: int) -> str:
|
||||
data = binascii.unhexlify(
|
||||
hmac.new(
|
||||
key=base64.b64decode(shared_secret),
|
||||
msg=pack('!L', 0) + pack('!L', math.floor(server_time // 30)),
|
||||
digestmod=hashlib.sha1,
|
||||
).hexdigest(),
|
||||
)
|
||||
|
||||
value = data[19] & 0xF
|
||||
full_code = (
|
||||
(data[value] & 0x7F) << 24 | # noqa:W504
|
||||
(data[value + 1] & 0xFF) << 16 | # noqa:W504
|
||||
(data[value + 2] & 0xFF) << 8 | # noqa:W504
|
||||
(data[value + 3] & 0xFF)
|
||||
)
|
||||
|
||||
code = ''
|
||||
chars = '23456789BCDFGHJKMNPQRTVWXY'
|
||||
for _ in range(5):
|
||||
code += chars[full_code % len(chars)]
|
||||
full_code //= len(chars)
|
||||
|
||||
return code
|
||||
|
||||
def get_confirmation_hash(self, server_time: int, tag: str = 'conf') -> str:
|
||||
identitysecret = base64.b64decode(
|
||||
self.identity_secret,
|
||||
)
|
||||
secret = BitArray(
|
||||
bytes=identitysecret,
|
||||
length=len(identitysecret) * 8,
|
||||
)
|
||||
tag_buffer = BitArray(
|
||||
bytes=tag.encode('utf8'),
|
||||
length=len(tag) * 8,
|
||||
)
|
||||
buffer = BitArray(4 * 8)
|
||||
buffer.append(BitArray(int=server_time, length=32))
|
||||
buffer.append(tag_buffer)
|
||||
confirmation = hmac.new(
|
||||
key=secret.tobytes(),
|
||||
msg=buffer.tobytes(),
|
||||
digestmod=hashlib.sha1,
|
||||
)
|
||||
return str(base64.b64encode(confirmation.digest()), 'utf8')
|
||||
|
||||
def _encrypt_password(self, keys: CAuthentication_GetPasswordRSAPublicKey_Response) -> str:
|
||||
publickey_exp = int(keys.publickey_exp, 16) # type:ignore
|
||||
publickey_mod = int(keys.publickey_mod, 16) # type:ignore
|
||||
public_key = rsa.PublicKey(
|
||||
n=publickey_mod,
|
||||
e=publickey_exp,
|
||||
)
|
||||
encrypted_password = rsa.encrypt(
|
||||
message=self._password.encode('ascii'),
|
||||
pub_key=public_key,
|
||||
)
|
||||
return str(base64.b64encode(encrypted_password), 'utf8')
|
||||
|
||||
async def _update_auth_session(
|
||||
self,
|
||||
client_id: int,
|
||||
steamid: int,
|
||||
code: str,
|
||||
code_type: int,
|
||||
) -> None:
|
||||
message = CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request(
|
||||
client_id=client_id,
|
||||
steamid=steamid,
|
||||
code=code,
|
||||
code_type=code_type,
|
||||
)
|
||||
await self._requests.request(
|
||||
method='POST',
|
||||
url='https://api.steampowered.com/IAuthenticationService/UpdateAuthSessionWithSteamGuardCode/v1',
|
||||
data=FormData(
|
||||
fields=[
|
||||
('input_protobuf_encoded', str(base64.b64encode(message.SerializeToString()), 'utf8')),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
async def _poll_auth_session_status(
|
||||
self,
|
||||
client_id: int,
|
||||
request_id: bytes,
|
||||
) -> CAuthentication_PollAuthSessionStatus_Response:
|
||||
message = CAuthentication_PollAuthSessionStatus_Request(
|
||||
client_id=client_id,
|
||||
request_id=request_id,
|
||||
)
|
||||
response = await self._requests.bytes(
|
||||
method='POST',
|
||||
url='https://api.steampowered.com/IAuthenticationService/PollAuthSessionStatus/v1',
|
||||
data=FormData(
|
||||
fields=[
|
||||
('input_protobuf_encoded', str(base64.b64encode(message.SerializeToString()), 'utf8')),
|
||||
],
|
||||
),
|
||||
)
|
||||
return CAuthentication_PollAuthSessionStatus_Response.FromString(response)
|
||||
|
||||
async def _finalize_login(self, refresh_token: str, sessionid: str) -> FinalizeLoginStatus:
|
||||
response = await self._requests.text(
|
||||
method='POST',
|
||||
url='https://login.steampowered.com/jwt/finalizelogin',
|
||||
data=FormData(
|
||||
fields=[
|
||||
('nonce', refresh_token),
|
||||
('sessionid', sessionid),
|
||||
('redir', 'https://steamcommunity.com/login/home/?goto='),
|
||||
],
|
||||
),
|
||||
headers={
|
||||
'Origin': 'https://steamcommunity.com'
|
||||
}
|
||||
)
|
||||
import json as _json
|
||||
from app.core.exceptions import SteamAuthError
|
||||
try:
|
||||
data = _json.loads(response)
|
||||
except Exception:
|
||||
data = {}
|
||||
if not data.get('success', True) or 'steamID' not in data:
|
||||
error_code = data.get('error', 0)
|
||||
from pysteamauth.errors.codes import STEAM_ERROR_CODES
|
||||
error_name = STEAM_ERROR_CODES.get(error_code, f'code {error_code}')
|
||||
raise SteamAuthError(
|
||||
f"finalizelogin failed at step 'nonce': {error_name} (EResult={error_code})"
|
||||
)
|
||||
return FinalizeLoginStatus.model_validate(data)
|
||||
|
||||
async def _set_token(self, url: str, nonce: str, auth: str, steamid: int) -> None:
|
||||
await self._requests.request(
|
||||
method='POST',
|
||||
url=url,
|
||||
data=FormData(
|
||||
fields=[
|
||||
('nonce', nonce),
|
||||
('auth', auth),
|
||||
('steamID', str(steamid)),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def _is_twofactor_required(self, confirmation: CAuthentication_AllowedConfirmation) -> bool:
|
||||
return confirmation.confirmation_type == EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceCode
|
||||
|
||||
async def login_to_steam(self) -> None:
|
||||
if await self.is_authorized():
|
||||
return
|
||||
if not self._requests.cookies().get('sessionid'):
|
||||
await self._requests.bytes(
|
||||
method='GET',
|
||||
url='https://steamcommunity.com',
|
||||
)
|
||||
keys = await self._getrsakey()
|
||||
encrypted_password = self._encrypt_password(keys)
|
||||
auth_session = await self._begin_auth_session(
|
||||
encrypted_password=encrypted_password,
|
||||
rsa_timestamp=keys.timestamp,
|
||||
)
|
||||
if auth_session.allowed_confirmations:
|
||||
if self._is_twofactor_required(auth_session.allowed_confirmations[0]):
|
||||
server_time = await self.get_server_time()
|
||||
code = await Steam.get_steam_guard(self.shared_secret, server_time)
|
||||
await self._update_auth_session(
|
||||
client_id=auth_session.client_id,
|
||||
steamid=auth_session.steamid,
|
||||
code=code,
|
||||
code_type=EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceCode,
|
||||
)
|
||||
session = await self._poll_auth_session_status(
|
||||
client_id=auth_session.client_id,
|
||||
request_id=auth_session.request_id,
|
||||
)
|
||||
tokens = await self._finalize_login(
|
||||
refresh_token=session.refresh_token,
|
||||
sessionid=self._requests.cookies()['sessionid'],
|
||||
)
|
||||
for token in tokens.transfer_info:
|
||||
await self._set_token(
|
||||
url=token.url,
|
||||
nonce=token.params.nonce,
|
||||
auth=token.params.auth,
|
||||
steamid=auth_session.steamid,
|
||||
)
|
||||
if not self._steamid and tokens.steamID:
|
||||
self._steamid = int(tokens.steamID)
|
||||
cookies = {
|
||||
'steamcommunity.com': self._requests.cookies('steamcommunity.com'),
|
||||
}
|
||||
for url in ('https://store.steampowered.com', 'https://help.steampowered.com'):
|
||||
await self._requests.bytes(url, 'GET')
|
||||
cookies.update({
|
||||
parse_url(url).host: self._requests.cookies(parse_url(url).host),
|
||||
})
|
||||
await self._storage.set(login=self._login, cookies=cookies)
|
||||
@@ -0,0 +1,8 @@
|
||||
from .request import BaseRequestStrategy
|
||||
from .storage import BaseCookieStorage
|
||||
|
||||
|
||||
__all__ = [
|
||||
'BaseRequestStrategy',
|
||||
'BaseCookieStorage',
|
||||
]
|
||||
@@ -0,0 +1,65 @@
|
||||
from typing import (
|
||||
Any,
|
||||
Mapping,
|
||||
Optional,
|
||||
)
|
||||
|
||||
import aiohttp
|
||||
from aiohttp import (
|
||||
ClientResponse,
|
||||
ClientSession,
|
||||
ClientTimeout,
|
||||
)
|
||||
|
||||
from pysteamauth.abstract import RequestStrategyAbstract
|
||||
from pysteamauth.errors import check_steam_error
|
||||
|
||||
DEFAULT_REQUEST_TIMEOUT = ClientTimeout(total=30)
|
||||
|
||||
|
||||
class BaseRequestStrategy(RequestStrategyAbstract):
|
||||
|
||||
def __init__(self):
|
||||
self._session: Optional[ClientSession] = None
|
||||
|
||||
def __del__(self):
|
||||
if self._session:
|
||||
self._session.connector.close()
|
||||
|
||||
def _create_session(self) -> ClientSession:
|
||||
"""
|
||||
Create aiohttp session.
|
||||
Aiohttp session saves and stores cookies.
|
||||
It writes cookies from responses after each request that specified
|
||||
in Set-Cookie header.
|
||||
|
||||
:return: aiohttp.ClientSession object.
|
||||
"""
|
||||
return ClientSession(
|
||||
connector=aiohttp.TCPConnector(ssl=False),
|
||||
timeout=DEFAULT_REQUEST_TIMEOUT,
|
||||
)
|
||||
|
||||
async def request(self, url: str, method: str, **kwargs: Any) -> ClientResponse:
|
||||
if self._session is None:
|
||||
self._session = self._create_session()
|
||||
response = await self._session.request(method, url, **kwargs)
|
||||
error = response.headers.get('X-eresult')
|
||||
if error:
|
||||
check_steam_error(int(error))
|
||||
return response
|
||||
|
||||
def cookies(self, domain: str = 'steamcommunity.com') -> Mapping[str, str]:
|
||||
if self._session is None:
|
||||
raise RuntimeError('Session is not initialized')
|
||||
cookies = {}
|
||||
for cookie in self._session.cookie_jar:
|
||||
if cookie['domain'] == domain:
|
||||
cookies[cookie.key] = cookie.value
|
||||
return cookies
|
||||
|
||||
async def text(self, url: str, method: str, **kwargs: Any) -> str:
|
||||
return await (await self.request(url, method, **kwargs)).text()
|
||||
|
||||
async def bytes(self, url: str, method: str, **kwargs: Any) -> bytes:
|
||||
return await (await self.request(url, method, **kwargs)).read()
|
||||
@@ -0,0 +1,21 @@
|
||||
from typing import (
|
||||
Dict,
|
||||
Mapping,
|
||||
)
|
||||
|
||||
from pysteamauth.abstract import CookieStorageAbstract
|
||||
|
||||
|
||||
class BaseCookieStorage(CookieStorageAbstract):
|
||||
|
||||
def __init__(self):
|
||||
self.cookies: Dict[str, Mapping[str, Mapping[str, str]]] = {}
|
||||
|
||||
async def set(self, login: str, cookies: Mapping[str, Mapping[str, str]]) -> None:
|
||||
self.cookies[login] = cookies
|
||||
|
||||
async def get(self, login: str, domain: str) -> Mapping[str, str]:
|
||||
cookies = self.cookies.get(login)
|
||||
if not cookies:
|
||||
return {}
|
||||
return cookies.get(domain, {})
|
||||
@@ -0,0 +1,16 @@
|
||||
from .codes import STEAM_ERROR_CODES
|
||||
from .exceptions import (
|
||||
SteamError,
|
||||
UnknownSteamError,
|
||||
custom_error_exception,
|
||||
)
|
||||
from .response import check_steam_error
|
||||
|
||||
|
||||
__all__ = [
|
||||
'check_steam_error',
|
||||
'SteamError',
|
||||
'UnknownSteamError',
|
||||
'STEAM_ERROR_CODES',
|
||||
'custom_error_exception',
|
||||
]
|
||||
@@ -0,0 +1,125 @@
|
||||
STEAM_ERROR_CODES = {
|
||||
1: 'OK',
|
||||
2: 'Fail',
|
||||
3: 'NoConnection',
|
||||
4: 'NoConnectionRetry',
|
||||
5: 'InvalidPassword',
|
||||
6: 'LoggedInElsewhere',
|
||||
7: 'InvalidProtocolVer',
|
||||
8: 'InvalidParam',
|
||||
9: 'FileNotFound',
|
||||
10: 'Busy',
|
||||
11: 'InvalidState',
|
||||
12: 'InvalidName',
|
||||
13: 'InvalidEmail',
|
||||
14: 'DuplicateName',
|
||||
15: 'AccessDenied',
|
||||
16: 'Timeout',
|
||||
17: 'Banned',
|
||||
18: 'AccountNotFound',
|
||||
19: 'InvalidSteamID',
|
||||
20: 'ServiceUnavailable',
|
||||
21: 'NotLoggedOn',
|
||||
22: 'Pending',
|
||||
23: 'EncryptionFailure',
|
||||
24: 'InsufficientPrivilege',
|
||||
25: 'LimitExceeded',
|
||||
26: 'Revoked',
|
||||
27: 'Expired',
|
||||
28: 'AlreadyRedeemed',
|
||||
29: 'DuplicateRequest',
|
||||
30: 'AlreadyOwned',
|
||||
31: 'IPNotFound',
|
||||
32: 'PersistFailed',
|
||||
33: 'LockingFailed',
|
||||
34: 'LogonSessionReplaced',
|
||||
35: 'ConnectFailed',
|
||||
36: 'HandshakeFailed',
|
||||
37: 'IOFailure',
|
||||
38: 'RemoteDisconnect',
|
||||
39: 'ShoppingCartNotFound',
|
||||
40: 'Blocked',
|
||||
41: 'Ignored',
|
||||
42: 'NoMatch',
|
||||
43: 'AccountDisabled',
|
||||
44: 'ServiceReadOnly',
|
||||
45: 'AccountNotFeatured',
|
||||
46: 'AdministratorOK',
|
||||
47: 'ContentVersion',
|
||||
48: 'TryAnotherCM',
|
||||
49: 'PasswordRequiredToKickSession',
|
||||
50: 'AlreadyLoggedInElsewhere',
|
||||
51: 'Suspended',
|
||||
52: 'Cancelled',
|
||||
53: 'DataCorruption',
|
||||
54: 'DiskFull',
|
||||
55: 'RemoteCallFailed',
|
||||
56: 'PasswordUnset',
|
||||
57: 'ExternalAccountUnlinked',
|
||||
58: 'PSNTicketInvalid',
|
||||
59: 'ExternalAccountAlreadyLinked',
|
||||
60: 'RemoteFileConflict',
|
||||
61: 'IllegalPassword',
|
||||
62: 'SameAsPreviousValue',
|
||||
63: 'AccountLogonDenied',
|
||||
64: 'CannotUseOldPassword',
|
||||
65: 'InvalidLoginAuthCode',
|
||||
66: 'AccountLogonDeniedNoMail',
|
||||
67: 'HardwareNotCapableOfIPT',
|
||||
68: 'IPTInitError',
|
||||
69: 'ParentalControlRestricted',
|
||||
70: 'FacebookQueryError',
|
||||
71: 'ExpiredLoginAuthCode',
|
||||
72: 'IPLoginRestrictionFailed',
|
||||
73: 'AccountLockedDown',
|
||||
74: 'AccountLogonDeniedVerifiedEmailRequired',
|
||||
75: 'NoMatchingURL',
|
||||
76: 'BadResponse',
|
||||
77: 'RequirePasswordReEntry',
|
||||
78: 'ValueOutOfRange',
|
||||
79: 'UnexpectedError',
|
||||
80: 'Disabled',
|
||||
81: 'InvalidCEGSubmission',
|
||||
82: 'RestrictedDevice',
|
||||
83: 'RegionLocked',
|
||||
84: 'RateLimitExceeded',
|
||||
85: 'AccountLoginDeniedNeedTwoFactor',
|
||||
86: 'ItemDeleted',
|
||||
87: 'AccountLoginDeniedThrottle',
|
||||
88: 'TwoFactorCodeMismatch',
|
||||
89: 'TwoFactorActivationCodeMismatch',
|
||||
90: 'AccountAssociatedToMultiplePartners',
|
||||
91: 'NotModified',
|
||||
92: 'NoMobileDevice',
|
||||
93: 'TimeNotSynced',
|
||||
94: 'SmsCodeFailed',
|
||||
95: 'AccountLimitExceeded',
|
||||
96: 'AccountActivityLimitExceeded',
|
||||
97: 'PhoneActivityLimitExceeded',
|
||||
98: 'RefundToWallet',
|
||||
99: 'EmailSendFailure',
|
||||
100: 'NotSettled',
|
||||
101: 'NeedCaptcha',
|
||||
102: 'GSLTDenied',
|
||||
103: 'GSOwnerDenied',
|
||||
104: 'InvalidItemType',
|
||||
105: 'IPBanned',
|
||||
106: 'GSLTExpired',
|
||||
107: 'InsufficientFunds',
|
||||
108: 'TooManyPending',
|
||||
109: 'NoSiteLicensesFound',
|
||||
110: 'WGNetworkSendExceeded',
|
||||
111: 'AccountNotFriends',
|
||||
112: 'LimitedUserAccount',
|
||||
113: 'CantRemoveItem',
|
||||
114: 'AccountDeleted',
|
||||
115: 'ExistingUserCancelledLicense',
|
||||
116: 'CommunityCooldown',
|
||||
117: 'NoLauncherSpecified',
|
||||
118: 'MustAgreeToSSA',
|
||||
119: 'LauncherMigrated',
|
||||
120: 'SteamRealmMismatch',
|
||||
121: 'InvalidSignature',
|
||||
122: 'ParseFailure',
|
||||
123: 'NoVerifiedPhone',
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
from typing import (
|
||||
Dict,
|
||||
Optional,
|
||||
Type,
|
||||
)
|
||||
|
||||
from .codes import STEAM_ERROR_CODES
|
||||
|
||||
|
||||
class SteamError(Exception):
|
||||
|
||||
def __init__(self, error_code: int, error_msg: Optional[str] = None):
|
||||
self.error_code = error_code
|
||||
self.error_msg = error_msg
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str({
|
||||
'error': STEAM_ERROR_CODES.get(self.error_code, self.error_code),
|
||||
'msg': self.error_msg,
|
||||
'code': self.error_code,
|
||||
})
|
||||
|
||||
|
||||
class UnknownSteamError(SteamError):
|
||||
...
|
||||
|
||||
|
||||
_CUSTOM_ERROR_EXCEPTIONS: Dict[int, Type[SteamError]] = {}
|
||||
|
||||
|
||||
def custom_error_exception(errors: Dict[int, Type[SteamError]]) -> None:
|
||||
global _CUSTOM_ERROR_EXCEPTIONS
|
||||
if not isinstance(errors, dict):
|
||||
raise TypeError('The error argument should be dict')
|
||||
|
||||
for _error, _exception in errors.items():
|
||||
if not isinstance(_error, int):
|
||||
raise TypeError('Error should be an integer')
|
||||
if not isinstance(_exception, type) or not issubclass(_exception, SteamError):
|
||||
raise TypeError('Exception should be inherited from SteamError')
|
||||
if _error not in STEAM_ERROR_CODES:
|
||||
raise TypeError(f'Unknown error code {_error}')
|
||||
_CUSTOM_ERROR_EXCEPTIONS[_error] = _exception
|
||||
@@ -0,0 +1,18 @@
|
||||
from typing import Optional
|
||||
|
||||
from .codes import STEAM_ERROR_CODES
|
||||
from .exceptions import (
|
||||
_CUSTOM_ERROR_EXCEPTIONS,
|
||||
SteamError,
|
||||
UnknownSteamError,
|
||||
)
|
||||
|
||||
|
||||
def check_steam_error(error: int, error_msg: Optional[str] = None) -> None:
|
||||
if error in (1, 22):
|
||||
return
|
||||
if error in _CUSTOM_ERROR_EXCEPTIONS:
|
||||
raise _CUSTOM_ERROR_EXCEPTIONS[error](error, error_msg)
|
||||
if error in STEAM_ERROR_CODES:
|
||||
raise SteamError(error_code=error, error_msg=error_msg)
|
||||
raise UnknownSteamError(error_code=error, error_msg=error_msg)
|
||||
@@ -0,0 +1,26 @@
|
||||
from .enums_pb2 import k_ESessionPersistence_Persistent
|
||||
from .steammessages_auth.steamclient_pb2 import (
|
||||
CAuthentication_BeginAuthSessionViaCredentials_Request,
|
||||
CAuthentication_BeginAuthSessionViaCredentials_Response,
|
||||
CAuthentication_GetPasswordRSAPublicKey_Request,
|
||||
CAuthentication_GetPasswordRSAPublicKey_Response,
|
||||
CAuthentication_PollAuthSessionStatus_Request,
|
||||
CAuthentication_PollAuthSessionStatus_Response,
|
||||
CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request,
|
||||
k_EAuthSessionGuardType_DeviceCode,
|
||||
k_EAuthTokenPlatformType_WebBrowser,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'CAuthentication_BeginAuthSessionViaCredentials_Request',
|
||||
'CAuthentication_BeginAuthSessionViaCredentials_Response',
|
||||
'CAuthentication_GetPasswordRSAPublicKey_Request',
|
||||
'CAuthentication_GetPasswordRSAPublicKey_Response',
|
||||
'CAuthentication_PollAuthSessionStatus_Request',
|
||||
'CAuthentication_PollAuthSessionStatus_Response',
|
||||
'CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request',
|
||||
'k_EAuthSessionGuardType_DeviceCode',
|
||||
'k_EAuthTokenPlatformType_WebBrowser',
|
||||
'k_ESessionPersistence_Persistent',
|
||||
]
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
|
||||
from enum import IntEnum
|
||||
|
||||
|
||||
class ESessionPersistence(IntEnum):
|
||||
k_ESessionPersistence_Invalid: int = -1
|
||||
k_ESessionPersistence_Ephemeral: int = 0
|
||||
k_ESessionPersistence_Persistent: int = 1
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,139 @@
|
||||
from enum import IntEnum
|
||||
from typing import (
|
||||
List,
|
||||
Optional,
|
||||
)
|
||||
|
||||
from pysteamauth.pb2.enums_pb2 import ESessionPersistence
|
||||
|
||||
|
||||
class EAuthTokenPlatformType(IntEnum):
|
||||
k_EAuthTokenPlatformType_Unknown: int = 0
|
||||
k_EAuthTokenPlatformType_SteamClient: int = 1
|
||||
k_EAuthTokenPlatformType_WebBrowser: int = 2
|
||||
k_EAuthTokenPlatformType_MobileApp: int = 3
|
||||
|
||||
|
||||
class EAuthSessionGuardType(IntEnum):
|
||||
k_EAuthSessionGuardType_Unknown: int = 0
|
||||
k_EAuthSessionGuardType_None: int = 1
|
||||
k_EAuthSessionGuardType_EmailCode: int = 2
|
||||
k_EAuthSessionGuardType_DeviceCode: int = 3
|
||||
k_EAuthSessionGuardType_DeviceConfirmation: int = 4
|
||||
k_EAuthSessionGuardType_EmailConfirmation: int = 5
|
||||
k_EAuthSessionGuardType_MachineToken: int = 6
|
||||
|
||||
|
||||
class CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client_id: int,
|
||||
steamid: int,
|
||||
code: Optional[str],
|
||||
code_type: Optional[int],
|
||||
):
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def FromString(message: bytes) -> 'CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request':
|
||||
...
|
||||
|
||||
def SerializeToString(self) -> bytes:
|
||||
...
|
||||
|
||||
|
||||
class CAuthentication_AllowedConfirmation:
|
||||
confirmation_type: EAuthSessionGuardType
|
||||
associated_message: Optional[str]
|
||||
|
||||
|
||||
class CAuthentication_BeginAuthSessionViaCredentials_Response:
|
||||
client_id: int
|
||||
request_id: bytes
|
||||
interval: Optional[float]
|
||||
allowed_confirmations: Optional[List[CAuthentication_AllowedConfirmation]]
|
||||
steamid: int
|
||||
weak_token: Optional[str]
|
||||
|
||||
@staticmethod
|
||||
def FromString(message: bytes) -> 'CAuthentication_BeginAuthSessionViaCredentials_Response':
|
||||
...
|
||||
|
||||
def SerializeToString(self) -> bytes:
|
||||
...
|
||||
|
||||
|
||||
|
||||
class CAuthentication_GetPasswordRSAPublicKey_Request:
|
||||
|
||||
def __init__(self, account_name: str):
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def FromString(message: bytes) -> 'CAuthentication_GetPasswordRSAPublicKey_Request':
|
||||
...
|
||||
|
||||
def SerializeToString(self) -> bytes:
|
||||
...
|
||||
|
||||
|
||||
class CAuthentication_BeginAuthSessionViaCredentials_Request:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
account_name: str,
|
||||
encrypted_password: str,
|
||||
encryption_timestamp: int,
|
||||
device_friendly_name: str,
|
||||
website_id: str = 'Unknown',
|
||||
remember_login: bool = True,
|
||||
platform_type: int = EAuthTokenPlatformType.k_EAuthTokenPlatformType_Unknown,
|
||||
persistence: int = ESessionPersistence.k_ESessionPersistence_Persistent,
|
||||
):
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def FromString(message: bytes) -> 'CAuthentication_BeginAuthSessionViaCredentials_Request':
|
||||
...
|
||||
|
||||
def SerializeToString(self) -> bytes:
|
||||
...
|
||||
|
||||
|
||||
class CAuthentication_PollAuthSessionStatus_Request:
|
||||
|
||||
def __init__(self, client_id: int, request_id: bytes, token_to_revoke: Optional[int] = None):
|
||||
...
|
||||
|
||||
def SerializeToString(self) -> bytes:
|
||||
...
|
||||
|
||||
|
||||
class CAuthentication_GetPasswordRSAPublicKey_Response:
|
||||
publickey_mod: str
|
||||
publickey_exp: str
|
||||
timestamp: int
|
||||
|
||||
@staticmethod
|
||||
def FromString(message: bytes) -> 'CAuthentication_GetPasswordRSAPublicKey_Response':
|
||||
...
|
||||
|
||||
def SerializeToString(self) -> bytes:
|
||||
...
|
||||
|
||||
|
||||
class CAuthentication_PollAuthSessionStatus_Response:
|
||||
new_client_id: Optional[int]
|
||||
new_challenge_url: Optional[str]
|
||||
refresh_token: str
|
||||
access_token: str
|
||||
had_remote_interaction: bool
|
||||
account_name: str
|
||||
|
||||
@staticmethod
|
||||
def FromString(message: bytes) -> 'CAuthentication_PollAuthSessionStatus_Response':
|
||||
...
|
||||
|
||||
def SerializeToString(self) -> bytes:
|
||||
...
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,68 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: steammessages_unified_base.steamclient.proto
|
||||
# flake8: noqa
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import message as _message
|
||||
from google.protobuf import reflection as _reflection
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import enum_type_wrapper
|
||||
|
||||
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n,steammessages_unified_base.steamclient.proto\x1a google/protobuf/descriptor.proto\"\x0c\n\nNoResponse*]\n\x13\x45ProtoExecutionSite\x12 \n\x1ck_EProtoExecutionSiteUnknown\x10\x00\x12$\n k_EProtoExecutionSiteSteamClient\x10\x02:4\n\x0b\x64\x65scription\x12\x1d.google.protobuf.FieldOptions\x18\xd0\x86\x03 \x01(\t:>\n\x13service_description\x12\x1f.google.protobuf.ServiceOptions\x18\xd0\x86\x03 \x01(\t:u\n\x16service_execution_site\x12\x1f.google.protobuf.ServiceOptions\x18\xd8\x86\x03 \x01(\x0e\x32\x14.EProtoExecutionSite:\x1ck_EProtoExecutionSiteUnknown:<\n\x12method_description\x12\x1e.google.protobuf.MethodOptions\x18\xd0\x86\x03 \x01(\t:8\n\x10\x65num_description\x12\x1c.google.protobuf.EnumOptions\x18\xd0\x86\x03 \x01(\t:C\n\x16\x65num_value_description\x12!.google.protobuf.EnumValueOptions\x18\xd0\x86\x03 \x01(\tB\x05H\x01\x80\x01\x00')
|
||||
|
||||
_EPROTOEXECUTIONSITE = DESCRIPTOR.enum_types_by_name['EProtoExecutionSite']
|
||||
EProtoExecutionSite = enum_type_wrapper.EnumTypeWrapper(_EPROTOEXECUTIONSITE)
|
||||
k_EProtoExecutionSiteUnknown = 0
|
||||
k_EProtoExecutionSiteSteamClient = 2
|
||||
|
||||
DESCRIPTION_FIELD_NUMBER = 50000
|
||||
description = DESCRIPTOR.extensions_by_name['description']
|
||||
SERVICE_DESCRIPTION_FIELD_NUMBER = 50000
|
||||
service_description = DESCRIPTOR.extensions_by_name['service_description']
|
||||
SERVICE_EXECUTION_SITE_FIELD_NUMBER = 50008
|
||||
service_execution_site = DESCRIPTOR.extensions_by_name['service_execution_site']
|
||||
METHOD_DESCRIPTION_FIELD_NUMBER = 50000
|
||||
method_description = DESCRIPTOR.extensions_by_name['method_description']
|
||||
ENUM_DESCRIPTION_FIELD_NUMBER = 50000
|
||||
enum_description = DESCRIPTOR.extensions_by_name['enum_description']
|
||||
ENUM_VALUE_DESCRIPTION_FIELD_NUMBER = 50000
|
||||
enum_value_description = DESCRIPTOR.extensions_by_name['enum_value_description']
|
||||
|
||||
_NORESPONSE = DESCRIPTOR.message_types_by_name['NoResponse']
|
||||
NoResponse = _reflection.GeneratedProtocolMessageType('NoResponse', (_message.Message,), {
|
||||
'DESCRIPTOR': _NORESPONSE,
|
||||
'__module__': 'steammessages_unified_base.steamclient_pb2'
|
||||
# @@protoc_insertion_point(class_scope:NoResponse)
|
||||
})
|
||||
_sym_db.RegisterMessage(NoResponse)
|
||||
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
google_dot_protobuf_dot_descriptor__pb2.FieldOptions.RegisterExtension(
|
||||
description)
|
||||
google_dot_protobuf_dot_descriptor__pb2.ServiceOptions.RegisterExtension(
|
||||
service_description)
|
||||
google_dot_protobuf_dot_descriptor__pb2.ServiceOptions.RegisterExtension(
|
||||
service_execution_site)
|
||||
google_dot_protobuf_dot_descriptor__pb2.MethodOptions.RegisterExtension(
|
||||
method_description)
|
||||
google_dot_protobuf_dot_descriptor__pb2.EnumOptions.RegisterExtension(
|
||||
enum_description)
|
||||
google_dot_protobuf_dot_descriptor__pb2.EnumValueOptions.RegisterExtension(
|
||||
enum_value_description)
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'H\001\200\001\000'
|
||||
_EPROTOEXECUTIONSITE._serialized_start = 96
|
||||
_EPROTOEXECUTIONSITE._serialized_end = 189
|
||||
_NORESPONSE._serialized_start = 82
|
||||
_NORESPONSE._serialized_end = 94
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
Reference in New Issue
Block a user