diff --git a/steam/account.py b/steam/account.py index b3dcc1c..a999c2d 100644 --- a/steam/account.py +++ b/steam/account.py @@ -23,19 +23,28 @@ import os import sys import json import base64 +import re from cryptography.fernet import Fernet from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend from steam.guard import * +import steam.webauth if sys.platform.startswith('win'): BASE_LOCATION = '.' #Windows else: BASE_LOCATION = '.' -ACCOUNT_ATTRIBUTES = ['username', 'password', 'steamid', 'shared_secret', 'identity_secret'] +DEFAULT_MOBILE_HEADERS = { + 'X-Requested-With': 'com.valvesoftware.android.steam.community', + 'User-agent': 'Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Google Nexus 4 - 4.1.1 - API 16 - 768x1280 Build/JRO03S) \ + AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' +} + +ACCOUNT_ATTRIBUTES = ['username', 'password', 'steamid', 'shared_secret', 'identity_secret', 'revocation_code',\ + 'secret_1', 'serial_number', 'deviceid', 'oauth_token'] class SteamAccount(object): username = None @@ -44,6 +53,8 @@ class SteamAccount(object): _file = None _fernet_key = None _fernet_suite = None + _web_auth = None + _session = None def __init__(self, username, password): self.username = username @@ -79,6 +90,141 @@ class SteamAccount(object): except AttributeError: raise IdentitySecretNotSet('Add identity_secret to this instance to generate confirmation keys') + def fetch_mobile_confirmations(self, retries=1): + self._verify_mobile_session() + + if not self._verify_mobile_authenticator(): + raise MobileAuthenticatorException('The steam mobile authenticator is required to access the mobile confirmations.') + + timestamp = get_time_offset() + confirmation_key = self.get_confirmation_key('conf', timestamp) + + confirmation_uri = 'https://steamcommunity.com/mobileconf/conf?p=%s&a=%s&k=%s&t=%s&m=android&tag=conf' %\ + ( self.deviceid, self.steamid, confirmation_key, timestamp) + + response = self.session.get(confirmation_uri, headers=DEFAULT_MOBILE_HEADERS) + + raw_confirmations = [ ] + + if response.status_code == 200: + if 'Invalid authenticator' in response.text: + retries += 1 + return self.fetch_mobile_confirmations(retries) + + confirmation_ids = re.findall(r'data-confid="(\d+)"', response.text) + confirmation_keys = re.findall(r'data-key="(\d+)"', response.text) + confirmation_descriptions = re.findall(r'
((Confirm|Trade with|Sell -) .+)<\/div>', response.text) + + if confirmation_ids and confirmation_keys: + for index, confirmation_id in enumerate(confirmation_ids): + raw_confirmations.append({ + 'id': confirmation_id, + 'key': confirmation_keys[index], + 'description': confirmation_descriptions[index] + }) + return raw_confirmations + return [ ] + + def add_mobile_authenticator(self): + if self._verify_mobile_authenticator(): + raise MobileAuthenticatorException('The steam mobile authenticator is already enabled.') + + self._verify_mobile_session() + + deviceid = getattr(self, 'deviceid') or generate_device_id(self.steamid) + + data = { + 'steamid': self.steamid, + 'sms_phone_id': 1, + 'access_token': self.oauth_token, + 'authenticator_time': get_time_offset(), + 'authenticator_type': 1, + 'device_identifier': deviceid + } + + response = self.session.post('https://api.steampowered.com/ITwoFactorService/AddAuthenticator/v1/', + data, headers=DEFAULT_MOBILE_HEADERS) + if response.status_code == 200: + response_json = json.loads(response.text) + if response_json.get('response').get('status') == 1: + self.set_account_property('shared_secret', response_json.get('response').get('shared_secret')) + self.set_account_property('identity_secret', response_json.get('response').get('identity_secret')) + self.set_account_property('revocation_code', response_json.get('response').get('revocation_code')) + self.set_account_property('secret_1', response_json.get('response').get('secret_1')) + self.set_account_property('serial_number', response_json.get('response').get('serial_number')) + self.set_account_property('deviceid', deviceid) + return True + return False + + def finalize_mobile_authenticator(self, sms_code, retries=1): + if self._verify_mobile_authenticator(): + raise MobileAuthenticatorException('The steam mobile authenticator is already enabled.') + + self._verify_mobile_session() + + if not sms_code: + raise SMSCodeNotProvided('The sms code is required for finalizing the process of adding the mobile\ + authenticator') + + timestamp = get_time_offset() + + data = { + 'steamid': self.steamid, + 'access_token': self.oauth_token, + 'authenticator_time': timestamp, + 'authenticator_code': generate_twofactor_code_for_time(self.shared_secret, timestamp), + 'activation_code': sms_code + } + + response = self.session.post('https://api.steampowered.com/ITwoFactorService/FinalizeAddAuthenticator/v1/', + data, headers=DEFAULT_MOBILE_HEADERS) + + if response.status_code == 200: + response_json = json.loads(response.text) + if response_json.get('response').get('success'): + self.set_account_property('has_mobile_authenticator', True) + return True + else: + if response_json.get('response').get('success') and retries < 30: + retries += 1 + return self._finalize_mobile_authenticator(sms_code, retries) + return False + + def remove_mobile_authenticator(self): + if not self._verify_mobile_authenticator(): + raise MobileAuthenticatorException('The steam mobile authenticator is not enabled.') + + self._verify_mobile_session() + + data = { + 'steamid': self.steamid, + 'steamguard_scheme': 2, + 'revocation_code': self.revocation_code, + 'access_token': self.oauth_token + } + + response = self.session.post('https://api.steampowered.com/ITwoFactorService/RemoveAuthenticator/v1/', + data, headers=DEFAULT_MOBILE_HEADERS) + + if response.status_code == 200: + response_json = json.loads(response.text) + if response_json.get('response').get('success'): + self.set_account_property('has_mobile_authenticator', False) + return True + return False + + def _verify_mobile_session(self): + if not isinstance(self._web_auth, steam.webauth.MobileWebAuth): + raise MobileAuthenticatorException('A mobile session is required.') + + if self._web_auth.complete: + raise MobileAuthenticatorException('The mobile session has to be logged in to steam.') + + def _verify_mobile_authenticator(self): + if getattr(self, 'has_mobile_authenticator') and self.has_mobile_authenticator: + return True + return False + def _setup(self): self._generate_fernet_key() self._spawn_fernet_suite() @@ -129,6 +275,45 @@ class SteamAccount(object): def _spawn_fernet_suite(self): self._fernet_suite = Fernet(self._fernet_key) + def _spawn_web_session(self): + self._web_auth = steam.webauth.WebAuth(self.username, self.password) + + def _spawn_mobile_session(self): + self._web_auth = steam.webauth.MobileWebAuth(self.username, self.password) + + def _login_web_session(self, captcha='', email_code='', twofactor_code=''): + try: + self._web_auth.login() + + except steam.webauth.CaptchaRequired: + if not captcha: + raise CaptchaNotProvided('The steam login captcha is required for logging in, but was not provided.') + self._web_auth.login(captcha=captcha) + + except steam.webauth.EmailCodeRequired: + if not email_code: + raise EMailCodeNotProvided('The email code is required for logging in, but was not provided.') + self._web_auth.login(email_code=email_code) + + except steam.webauth.TwoFactorCodeRequired: + if not twofactor_code: + try: + twofactor_code = self.login_code + except SharedSecretNotSet: + raise TwoFACodeNotProvided('The twofactor code is required for logging in, but was not provided.') + self._web_auth.login(twofactor_code=twofactor_code) + + if self._web_auth.complete: + if not getattr(self, 'steamid'): + self.set_account_property('steamid', self._web_auth.steamid) + + if isinstance(self._web_auth, steam.webauth.MobileWebAuth) and not getattr(self, 'oauth_token'): + self.set_account_property('oauth_token', self._web_auth.oauth_token) + + self._session = self._web_auth.session + else: + raise WebAuthNotComplete('The web authentication could not be completed.') + class SteamAccountException(Exception): pass @@ -136,4 +321,25 @@ class SharedSecretNotSet(SteamAccountException): pass class IdentitySecretNotSet(SteamAccountException): + pass + +class WebAuthNotComplete(SteamAccountException): + pass + +class MobileAuthenticatorException(SteamAccountException): + pass + +class ParameterNotProvidedException(SteamAccountException): + pass + +class CaptchaNotProvided(ParameterNotProvidedException): + pass + +class EMailCodeNotProvided(ParameterNotProvidedException): + pass + +class TwoFACodeNotProvided(ParameterNotProvidedException): + pass + +class SMSCodeNotProvided(ParameterNotProvidedException): pass \ No newline at end of file