diff --git a/.coveragerc b/.coveragerc index 0e530720..6c8265c5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,5 +1,14 @@ + +# Docs: https://coverage.readthedocs.org/en/latest/config.html + [run] concurrency = gevent omit = steam/protobufs/* steam/enums/* + +branch = False + +# If True, stores relative file paths in data file (needed for Github Actions). +# Using this parameter requires coverage>=5.0 +relative_files = True diff --git a/.github/workflows/testing_initiative.yml b/.github/workflows/testing_initiative.yml new file mode 100644 index 00000000..4be4b1e7 --- /dev/null +++ b/.github/workflows/testing_initiative.yml @@ -0,0 +1,105 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Testing Initiative + +on: + push: + branches: [ master ] + paths-ignore: + - '.gitignore' + - '*.md' + - '*.rst' + - 'LICENSE' + - 'Vagrantfile' + - 'protobuf_list.txt' + - 'protobufs/**' + - 'recipes/**' + pull_request: + branches: [ master ] + paths-ignore: + - '.gitignore' + - '*.md' + - '*.rst' + - 'LICENSE' + - 'Vagrantfile' + - 'protobuf_list.txt' + - 'protobufs/**' + - 'recipes/**' +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9] + no-coverage: [0] + include: + - os: ubuntu-latest + python-version: pypy-2.7 + no-coverage: 1 + - os: ubuntu-latest + python-version: pypy-3.6 + no-coverage: 1 + steps: + - uses: actions/checkout@v2 + - name: Set up Python Env + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Display Python version + run: python -c "import sys; print(sys.version)" + - name: Install dependencies + run: | + make init + - name: Run Tests + env: + NOCOV: ${{ matrix.no-coverage }} + run: | + make test + - name: Upload to Coveralls + # pypy + concurrenct=gevent not supported in coveragepy. See https://github.com/nedbat/coveragepy/issues/560 + if: matrix.no-coverage == 0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_PARALLEL: true + COVERALLS_FLAG_NAME: "${{ matrix.os }}_${{ matrix.python-version }}" + run: | + coveralls --service=github + + coveralls: + name: Finish Coveralls + needs: test + runs-on: ubuntu-latest + container: python:3-slim + steps: + - name: Install coveralls + run: | + pip3 install --upgrade coveralls + - name: Send coverage finish to coveralls.io + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + coveralls --finish + + build-docs: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + python-version: [3.6] + steps: + - uses: actions/checkout@v2 + - name: Set up Python Env + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Display Python version + run: python -c "import sys; print(sys.version)" + - name: Install dependencies + run: | + make init + make init_docs + - name: Build Docs + run: make docs diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c89dfed3..00000000 --- a/.travis.yml +++ /dev/null @@ -1,65 +0,0 @@ -language: python -os: linux -jobs: - include: -# docs build - - name: Docs (py36) - python: 3.6 - install: make init - script: make docs - after_script: [] -# linux - - python: 2.7 - - python: 3.5 - - python: 3.6 - - python: 3.7 - - python: 3.8 -# OSX - - name: OSX Python 2.7 - os: osx - language: shell - before_install: - - cp -fv `which python2` `which python` || true - - cp -fv `which pip2` `which pip` || true - - pip install --upgrade pip - after_script: [] - - name: OSX Python 3.7 - os: osx - language: shell - before_install: - - cp -fv `which python3` `which python` || true - - cp -fv `which pip3` `which pip` || true - - pip install --upgrade pip - after_script: [] -# Windows - - name: Win Python 3.6 - language: shell - os: windows - env: PATH=/c/Python36:/c/Python36/Scripts:$PATH - before_install: - - choco install python --version 3.6.8 - - python -m pip install --upgrade pip - after_script: [] - - name: Win Python 3.7 - language: shell - os: windows - env: PATH=/c/Python37:/c/Python37/Scripts:$PATH - before_install: - - choco install python --version 3.7.4 - - python -m pip install --upgrade pip - after_script: [] - - name: Win Python 3.8 - language: shell - os: windows - env: PATH=/c/Python38:/c/Python38/Scripts:$PATH - before_install: - - choco install python --version 3.8.2 - - python -m pip install --upgrade pip - after_script: [] -install: - - pip install -r requirements.txt - - pip install coveralls -script: - - PYTHONHASHSEED=0 pytest --cov=steam tests -after_script: - - coveralls diff --git a/Makefile b/Makefile index 5ab7ae7f..86c4f63e 100644 --- a/Makefile +++ b/Makefile @@ -24,15 +24,21 @@ export HELPBODY help: @echo "$$HELPBODY" -init: init_docs - pip install -r requirements.txt +init: + pip install -r dev_requirements.txt init_docs: pip install sphinx==1.8.5 sphinx_rtd_theme +COVOPTS = --cov-config .coveragerc --cov=steam + +ifeq ($(NOCOV), 1) + COVOPTS = +endif + test: coverage erase - PYTHONHASHSEED=0 pytest --cov=steam tests + PYTHONHASHSEED=0 pytest --tb=short $(COVOPTS) tests webauth_gen: rm -f vcr/webauth* diff --git a/README.rst b/README.rst index 755979df..7ffb9f8e 100644 --- a/README.rst +++ b/README.rst @@ -98,13 +98,13 @@ IRC: irc.freenode.net / #steamkit (`join via webchat =5.0; python_version == '2.7' or python_version >= '3.5' +pytest-cov>=2.7.0; python_version == '2.7' or python_version >= '3.5' + +# coveralls 2.0 has removed support for Python 2.7 and 3.4 +git+https://github.com/andy-maier/coveralls-python.git@andy/add-py27#egg=coveralls; python_version == '2.7' +coveralls>=2.1.2; python_version >= '3.5' diff --git a/requirements.txt b/requirements.txt index dadda5d4..08c187d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,10 @@ six>=1.10.0 -pycryptodomex>=3.7.0 +cryptography>=3.1 requests>=2.9.1 vdf>=3.3 gevent>=1.3.0 protobuf>=3.0.0 -gevent-eventemitter>=2.1 +gevent-eventemitter~=2.1 enum34==1.1.2; python_version < '3.4' -coverage==4.0.3 -pytest==3.2.1 -pytest-cov==2.5.1 -mock==1.3.0 PyYAML==5.1 -vcrpy==2.0.1 cachetools>=3.0.0 diff --git a/setup.py b/setup.py index aa93c38a..47ab1a92 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ install_requires = [ 'six>=1.10', - 'pycryptodomex>=3.7.0', + 'cryptography>=3.1', 'requests>=2.9.1', 'vdf>=3.3', 'cachetools>=3.0.0', @@ -53,6 +53,7 @@ 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: PyPy', ], keywords='valve steam steamid api webapi steamcommunity', diff --git a/steam/__init__.py b/steam/__init__.py index 9ff48997..e737a667 100644 --- a/steam/__init__.py +++ b/steam/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.0.2" +__version__ = "1.2.0" __author__ = "Rossen Georgiev" -version_info = (1, 0, 2) +version_info = (1, 2, 0) diff --git a/steam/client/builtins/apps.py b/steam/client/builtins/apps.py index 64dedb05..68812abb 100644 --- a/steam/client/builtins/apps.py +++ b/steam/client/builtins/apps.py @@ -1,3 +1,4 @@ +from binascii import hexlify import vdf from steam.enums import EResult, EServerType from steam.enums.emsg import EMsg @@ -41,13 +42,19 @@ def get_player_count(self, app_id, timeout=5): else: return EResult(resp.eresult) - def get_product_info(self, apps=[], packages=[], timeout=15): + def get_product_info(self, apps=[], packages=[], meta_data_only=False, raw=False, auto_access_tokens=True, timeout=15): """Get product info for apps and packages :param apps: items in the list should be either just ``app_id``, or :class:`dict` - :type apps: :class:`list` + :type apps: :class:`list` :param packages: items in the list should be either just ``package_id``, or :class:`dict` - :type packages: :class:`list` + :type packages: :class:`list` + :param meta_data_only: only meta data will be returned in the reponse (e.g. change number, missing_token, sha1) + :type meta_data_only: :class:`bool` + :param raw: Data buffer for each app is returned as bytes in its' original form. Apps buffer is text VDF, and package buffer is binary VDF + :type raw: :class:`bool` + :param auto_access_token: automatically request and fill access tokens + :type auto_access_token: :class:`bool` :return: dict with ``apps`` and ``packages`` containing their info, see example below :rtype: :class:`dict`, :class:`None` @@ -90,11 +97,22 @@ def get_product_info(self, apps=[], packages=[], timeout=15): if not apps and not packages: return + if auto_access_tokens: + tokens = self.get_access_tokens(app_ids=list(map(lambda app: app['appid'] if isinstance(app, dict) else app, apps)), + package_ids=list(map(lambda pkg: pkg['packageid'] if isinstance(pkg, dict) else pkg, packages)) + ) + else: + tokens = None + message = MsgProto(EMsg.ClientPICSProductInfoRequest) for app in apps: app_info = message.body.apps.add() app_info.only_public = False + + if tokens: + app_info.access_token = tokens['apps'].get(app['appid'] if isinstance(app, dict) else app, 0) + if isinstance(app, dict): proto_fill_from_dict(app_info, app) else: @@ -102,12 +120,18 @@ def get_product_info(self, apps=[], packages=[], timeout=15): for package in packages: package_info = message.body.packages.add() + + if tokens: + package_info.access_token = tokens['packages'].get(package['packageid'] if isinstance(package, dict) else package, 0) + if isinstance(package, dict): proto_fill_from_dict(package_info, package) else: package_info.packageid = package - message.body.meta_data_only = False + message.body.meta_data_only = meta_data_only + message.body.num_prev_failed = 0 + message.body.supports_package_tokens = 1 job_id = self.send_job(message) @@ -119,11 +143,32 @@ def get_product_info(self, apps=[], packages=[], timeout=15): chunk = chunk[0].body for app in chunk.apps: - data['apps'][app.appid] = vdf.loads(app.buffer[:-1].decode('utf-8', 'replace'))['appinfo'] + if app.buffer and not raw: + data['apps'][app.appid] = vdf.loads(app.buffer[:-1].decode('utf-8', 'replace'))['appinfo'] + else: + data['apps'][app.appid] = {} + data['apps'][app.appid]['_missing_token'] = app.missing_token + data['apps'][app.appid]['_change_number'] = app.change_number + data['apps'][app.appid]['_sha'] = hexlify(app.sha).decode('ascii') + data['apps'][app.appid]['_size'] = app.size + + if app.buffer and raw: + data['apps'][app.appid]['_buffer'] = app.buffer + for pkg in chunk.packages: - data['packages'][pkg.packageid] = vdf.binary_loads(pkg.buffer[4:]).get(str(pkg.packageid), {}) + if pkg.buffer and not raw: + data['packages'][pkg.packageid] = vdf.binary_loads(pkg.buffer[4:]).get(str(pkg.packageid), {}) + else: + data['packages'][pkg.packageid] = {} + data['packages'][pkg.packageid]['_missing_token'] = pkg.missing_token + data['packages'][pkg.packageid]['_change_number'] = pkg.change_number + data['packages'][pkg.packageid]['_sha'] = hexlify(pkg.sha).decode('ascii') + data['packages'][pkg.packageid]['_size'] = pkg.size + + if pkg.buffer and raw: + data['packages'][pkg.packageid]['_buffer'] = pkg.buffer if not chunk.response_pending: break @@ -163,7 +208,7 @@ def get_app_ticket(self, app_id): {'app_id': app_id}, timeout=10 ) - + def get_encrypted_app_ticket(self, app_id, userdata): """Gets the encrypted app ticket :param app_id: app id @@ -246,7 +291,7 @@ def get_access_tokens(self, app_ids=[], package_ids=[]): if resp: return {'apps': dict(map(lambda app: (app.appid, app.access_token), resp.app_access_tokens)), - 'packages': dict(map(lambda pkg: (pkg.appid, pkg.access_token), resp.package_access_tokens)), + 'packages': dict(map(lambda pkg: (pkg.packageid, pkg.access_token), resp.package_access_tokens)), } def register_product_key(self, key): diff --git a/steam/client/builtins/user.py b/steam/client/builtins/user.py index 60fd13a4..dbffe508 100644 --- a/steam/client/builtins/user.py +++ b/steam/client/builtins/user.py @@ -38,7 +38,7 @@ def __handle_message_incoming(self, msg): def __handle_message_incoming2(self, msg): # new chat - if msg.body.chat_entry_type == EChatEntryType.ChatMsg: + if msg.body.chat_entry_type == EChatEntryType.ChatMsg and not msg.body.local_echo: user = self.get_user(msg.body.steamid_friend) self.emit("chat_message", user, msg.body.message) diff --git a/steam/core/crypto.py b/steam/core/crypto.py index 6fb28679..637d95fa 100644 --- a/steam/core/crypto.py +++ b/steam/core/crypto.py @@ -1,21 +1,24 @@ """ All function in this module take and return :class:`bytes` """ +import hashlib import sys +from base64 import b64decode from os import urandom as random_bytes from struct import pack -from base64 import b64decode -from Cryptodome.Hash import SHA1, HMAC -from Cryptodome.PublicKey.RSA import import_key as rsa_import_key, construct as rsa_construct -from Cryptodome.Cipher import PKCS1_OAEP, PKCS1_v1_5 -from Cryptodome.Cipher import AES as AES +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.padding import PKCS7 +from cryptography.hazmat.primitives.asymmetric import rsa, padding +from cryptography.hazmat.primitives.hmac import HMAC +from cryptography.hazmat.primitives.serialization import load_der_public_key class UniverseKey(object): """Public keys for Universes""" - Public = rsa_import_key(b64decode(""" + Public = load_der_public_key(b64decode(""" MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDf7BrWLBBmLBc1OhSwfFkRf53T 2Ct64+AVzRkeRuh7h3SiGEYxqQMUeYKO6UWiSRKpI2hzic9pobFhRr3Bvr/WARvY gdTckPv+T1JzZsuVcNfFjrocejN1oWI0Rrtgt4Bo+hOneoo3S57G9F1fOpn5nsQ6 @@ -39,8 +42,14 @@ def generate_session_key(hmac_secret=b''): :rtype: :class:`tuple` """ session_key = random_bytes(32) - encrypted_session_key = PKCS1_OAEP.new(UniverseKey.Public, SHA1)\ - .encrypt(session_key + hmac_secret) + encrypted_session_key = UniverseKey.Public.encrypt( + session_key + hmac_secret, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA256(), + label=None + ) + ) return (session_key, encrypted_session_key) @@ -49,7 +58,13 @@ def symmetric_encrypt(message, key): return symmetric_encrypt_with_iv(message, key, iv) def symmetric_encrypt_ecb(message, key): - return AES.new(key, AES.MODE_ECB).encrypt(pad(message)) + padder = PKCS7(algorithms.AES.block_size).padder() + plaintext = padder.update(message) + plaintext += padder.finalize() + encryptor = Cipher(algorithms.AES(key), modes.ECB()).encryptor() + cyphertext = encryptor.update(plaintext) + cyphertext += encryptor.finalize() + return cyphertext def symmetric_encrypt_HMAC(message, key, hmac_secret): prefix = random_bytes(3) @@ -58,11 +73,19 @@ def symmetric_encrypt_HMAC(message, key, hmac_secret): return symmetric_encrypt_with_iv(message, key, iv) def symmetric_encrypt_iv(iv, key): - return AES.new(key, AES.MODE_ECB).encrypt(iv) + encryptor = Cipher(algorithms.AES(key), modes.ECB()).encryptor() + cyphertext = encryptor.update(iv) + cyphertext += encryptor.finalize() + return cyphertext def symmetric_encrypt_with_iv(message, key, iv): encrypted_iv = symmetric_encrypt_iv(iv, key) - cyphertext = AES.new(key, AES.MODE_CBC, iv).encrypt(pad(message)) + padder = PKCS7(algorithms.AES.block_size).padder() + plaintext = padder.update(message) + plaintext += padder.finalize() + encryptor = Cipher(algorithms.AES(key), modes.CBC(iv)).encryptor() + cyphertext = encryptor.update(plaintext) + cyphertext += encryptor.finalize() return encrypted_iv + cyphertext def symmetric_decrypt(cyphertext, key): @@ -70,7 +93,13 @@ def symmetric_decrypt(cyphertext, key): return symmetric_decrypt_with_iv(cyphertext, key, iv) def symmetric_decrypt_ecb(cyphertext, key): - return unpad(AES.new(key, AES.MODE_ECB).decrypt(cyphertext)) + decryptor = Cipher(algorithms.AES(key), modes.ECB()).decryptor() + plaintext = decryptor.update(cyphertext) + plaintext += decryptor.finalize() + unpadder = PKCS7(algorithms.AES.block_size).unpadder() + message = unpadder.update(plaintext) + message += unpadder.finalize() + return message def symmetric_decrypt_HMAC(cyphertext, key, hmac_secret): """:raises: :class:`RuntimeError` when HMAC verification fails""" @@ -85,19 +114,33 @@ def symmetric_decrypt_HMAC(cyphertext, key, hmac_secret): return message def symmetric_decrypt_iv(cyphertext, key): - return AES.new(key, AES.MODE_ECB).decrypt(cyphertext[:BS]) + decryptor = Cipher(algorithms.AES(key), modes.ECB()).decryptor() + iv = decryptor.update(cyphertext[:BS]) + iv += decryptor.finalize() + return iv def symmetric_decrypt_with_iv(cyphertext, key, iv): - return unpad(AES.new(key, AES.MODE_CBC, iv).decrypt(cyphertext[BS:])) + decryptor = Cipher(algorithms.AES(key), modes.CBC(iv)).decryptor() + plaintext = decryptor.update(cyphertext[BS:]) + plaintext += decryptor.finalize() + unpadder = PKCS7(algorithms.AES.block_size).unpadder() + message = unpadder.update(plaintext) + message += unpadder.finalize() + return message def hmac_sha1(secret, data): - return HMAC.new(secret, data, SHA1).digest() + h = HMAC(secret, hashes.SHA1()) + h.update(data) + return h.finalize() def sha1_hash(data): - return SHA1.new(data).digest() + return hashlib.sha1(data).digest() def rsa_publickey(mod, exp): - return rsa_construct((mod, exp)) + return rsa.RSAPublicNumbers(e=exp, n=mod).public_key() def pkcs1v15_encrypt(key, message): - return PKCS1_v1_5.new(key).encrypt(message) + key.encrypt( + message, + padding.PKCS1v15, + ) diff --git a/steam/enums/common.py b/steam/enums/common.py index 6cd29302..3d86ee8c 100644 --- a/steam/enums/common.py +++ b/steam/enums/common.py @@ -896,6 +896,53 @@ class EPurchaseResultDetail(SteamIntEnum): PaymentMethodNotSupportedForProduct = 83 +class ELicenseFlags(SteamIntEnum): + NONE = 0 + Renew = 0x01 + RenewalFailed = 0x02 + Pending = 0x04 + Expired = 0x08 + CancelledByUser = 0x10 + CancelledByAdmin = 0x20 + LowViolenceContent = 0x40 + ImportedFromSteam2 = 0x80 + ForceRunRestriction = 0x100 + RegionRestrictionExpired = 0x200 + CancelledByFriendlyFraudLock = 0x400 + NotActivated = 0x800 + + +class ELicenseType(SteamIntEnum): + NoLicense = 0 + SinglePurchase = 1 + SinglePurchaseLimitedUse = 2 + RecurringCharge = 3 + RecurringChargeLimitedUse = 4 + RecurringChargeLimitedUseWithOverages = 5 + RecurringOption = 6 + LimitedUseDelayedActivation = 7 + + +class EBillingType(SteamIntEnum): + NoCost = 0 + BillOnceOnly = 1 + BillMonthly = 2 + ProofOfPrepurchaseOnly = 3 + GuestPass = 4 + HardwarePromo = 5 + Gift = 6 + AutoGrant = 7 + OEMTicket = 8 + RecurringOption = 9 + BillOnceOrCDKey = 10 + Repurchaseable = 11 + FreeOnDemand = 12 + Rental = 13 + CommercialLicense = 14 + FreeCommercialLicense = 15 + NumBillingTypes = 16 + + # Do not remove from enum import EnumMeta diff --git a/steam/guard.py b/steam/guard.py index a15bccc8..4cddc55a 100644 --- a/steam/guard.py +++ b/steam/guard.py @@ -148,8 +148,8 @@ def _send_request(self, action, params): if resp is None: raise SteamAuthenticatorError("Failed. Request timeout") if resp.header.eresult != EResult.OK: - raise SteamAuthenticatorError("Failed: %s (%s)" % str(resp.header.error_message, - repr(resp.header.eresult))) + raise SteamAuthenticatorError("Failed: %s (%s)" % (resp.header.error_message, + repr(resp.header.eresult))) resp = proto_to_dict(resp.body) diff --git a/steam/webauth.py b/steam/webauth.py index d81ce13d..1b2c8fc0 100644 --- a/steam/webauth.py +++ b/steam/webauth.py @@ -61,7 +61,6 @@ import six import requests -from steam import webapi from steam.steamid import SteamID from steam.utils.web import make_requests_session, generate_session_id from steam.core.crypto import rsa_publickey, pkcs1v15_encrypt @@ -323,6 +322,70 @@ def _finalize_login(self, login_response): self.steam_id = SteamID(data['steamid']) self.oauth_token = data['oauth_token'] + def oauth_login(self, oauth_token='', steam_id='', language='english'): + """Attempts a mobile authenticator login using an oauth token, which can be obtained from a previously logged-in + `MobileWebAuth` + + :param oauth_token: oauth token string, if it wasn't provided on instance init + :type oauth_token: :class:`str` + :param steam_id: `SteamID` of the account to log into, if it wasn't provided on instance init + :type steam_id: :class:`str` or :class:`SteamID` + :param language: select language for steam web pages (sets language cookie) + :type language: :class:`str` + :return: a session on success and :class:`None` otherwise + :rtype: :class:`requests.Session`, :class:`None` + :raises HTTPError: any problem with http request, timeouts, 5xx, 4xx etc + :raises LoginIncorrect: Invalid token or SteamID + """ + if oauth_token: + self.oauth_token = oauth_token + else: + if self.oauth_token: + oauth_token = self.oauth_token + else: + raise LoginIncorrect('token is not specified') + + if steam_id: + self.steam_id = SteamID(steam_id) + else: + if not self.steam_id: + raise LoginIncorrect('steam_id is not specified') + + steam_id = self.steam_id.as_64 + + data = { + 'access_token': oauth_token + } + + try: + resp = self.session.post('https://api.steampowered.com/IMobileAuthService/GetWGToken/v0001', data=data) + except requests.exceptions.RequestException as e: + raise HTTPError(str(e)) + + try: + resp_data = resp.json()['response'] + except json.decoder.JSONDecodeError as e: + if 'Please verify your
key=
parameter.' in resp.text: + raise LoginIncorrect('invalid token') + else: + raise e + + self.session_id = generate_session_id() + + for domain in ['store.steampowered.com', 'help.steampowered.com', 'steamcommunity.com']: + self.session.cookies.set('birthtime', '-3333', domain=domain) + self.session.cookies.set('sessionid', self.session_id, domain=domain) + self.session.cookies.set('mobileClientVersion', '0 (2.1.3)', domain=domain) + self.session.cookies.set('mobileClient', 'android', domain=domain) + self.session.cookies.set('steamLogin', str(steam_id) + "%7C%7C" + resp_data['token'], domain=domain) + self.session.cookies.set('steamLoginSecure', str(steam_id) + "%7C%7C" + resp_data['token_secure'], + domain=domain, secure=True) + self.session.cookies.set('Steam_Language', language, domain=domain) + + self.logged_on = True + + return self.session + class WebAuthException(Exception): pass