├── accounts.txt ├── database ├── logs.txt ├── app.ico ├── sim.ico ├── proxy.ico ├── stats.ico ├── captcha.ico ├── plus-blue.ico ├── tmplcounters ├── imap-hosts.json ├── interface_states.json └── userdata.txt ├── steampy ├── __init__.py ├── .DS_Store ├── __pycache__ │ ├── client.cpython-38.pyc │ ├── client.cpython-39.pyc │ ├── guard.cpython-38.pyc │ ├── guard.cpython-39.pyc │ ├── login.cpython-38.pyc │ ├── login.cpython-39.pyc │ ├── utils.cpython-38.pyc │ ├── utils.cpython-39.pyc │ ├── __init__.cpython-38.pyc │ ├── __init__.cpython-39.pyc │ ├── confirmation.cpython-38.pyc │ └── confirmation.cpython-39.pyc ├── build.bat ├── guard.py ├── confirmation.py ├── utils.py ├── login.py └── client.py ├── start.bat ├── .vscode └── settings.json ├── .DS_Store ├── .idea ├── .gitignore ├── .DS_Store ├── dictionaries │ └── shamanovsky.xml ├── vcs.xml ├── modules.xml ├── misc.xml ├── steamreg.iml └── inspectionProfiles │ └── Project_Default.xml ├── webserver ├── .DS_Store ├── website │ ├── Beard.png │ ├── telegram-group.png │ ├── telegram-logo.png │ ├── templates │ │ └── payment.html │ └── index.html └── server.py ├── __pycache__ ├── enums.cpython-38.pyc ├── enums.cpython-39.pyc ├── controller.cpython-38.pyc ├── controller.cpython-39.pyc ├── steamreg.cpython-38.pyc ├── steamreg.cpython-39.pyc ├── sms_services.cpython-38.pyc └── sms_services.cpython-39.pyc ├── .gitignore ├── enums.py ├── README.md ├── requirements.txt ├── Pipfile ├── .github └── workflows │ └── codeql.yml ├── captcha_snippet.py ├── sms_services.py ├── controller.py └── steamreg.py /accounts.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /database/logs.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /steampy/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | python3 main.py -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "yapf" 3 | } -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/.DS_Store -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/.idea/.DS_Store -------------------------------------------------------------------------------- /database/app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/database/app.ico -------------------------------------------------------------------------------- /database/sim.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/database/sim.ico -------------------------------------------------------------------------------- /steampy/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/steampy/.DS_Store -------------------------------------------------------------------------------- /database/proxy.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/database/proxy.ico -------------------------------------------------------------------------------- /database/stats.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/database/stats.ico -------------------------------------------------------------------------------- /webserver/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/webserver/.DS_Store -------------------------------------------------------------------------------- /database/captcha.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/database/captcha.ico -------------------------------------------------------------------------------- /database/plus-blue.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/database/plus-blue.ico -------------------------------------------------------------------------------- /database/tmplcounters: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/database/tmplcounters -------------------------------------------------------------------------------- /database/imap-hosts.json: -------------------------------------------------------------------------------- 1 | {"imap.yandex.ru":["yandex.ru","yandex.ua","yandex.com","yandex.by","ya.ru","yandex.kz"]} -------------------------------------------------------------------------------- /webserver/website/Beard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/webserver/website/Beard.png -------------------------------------------------------------------------------- /__pycache__/enums.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/__pycache__/enums.cpython-38.pyc -------------------------------------------------------------------------------- /__pycache__/enums.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/__pycache__/enums.cpython-39.pyc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .idea 3 | .vscode 4 | webserver 5 | Pipfile.lock 6 | *.txt 7 | .DS_Store 8 | database/tmplcounters 9 | -------------------------------------------------------------------------------- /.idea/dictionaries/shamanovsky.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /__pycache__/controller.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/__pycache__/controller.cpython-38.pyc -------------------------------------------------------------------------------- /__pycache__/controller.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/__pycache__/controller.cpython-39.pyc -------------------------------------------------------------------------------- /__pycache__/steamreg.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/__pycache__/steamreg.cpython-38.pyc -------------------------------------------------------------------------------- /__pycache__/steamreg.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/__pycache__/steamreg.cpython-39.pyc -------------------------------------------------------------------------------- /webserver/website/telegram-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/webserver/website/telegram-group.png -------------------------------------------------------------------------------- /webserver/website/telegram-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/webserver/website/telegram-logo.png -------------------------------------------------------------------------------- /__pycache__/sms_services.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/__pycache__/sms_services.cpython-38.pyc -------------------------------------------------------------------------------- /__pycache__/sms_services.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/__pycache__/sms_services.cpython-39.pyc -------------------------------------------------------------------------------- /steampy/__pycache__/client.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/steampy/__pycache__/client.cpython-38.pyc -------------------------------------------------------------------------------- /steampy/__pycache__/client.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/steampy/__pycache__/client.cpython-39.pyc -------------------------------------------------------------------------------- /steampy/__pycache__/guard.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/steampy/__pycache__/guard.cpython-38.pyc -------------------------------------------------------------------------------- /steampy/__pycache__/guard.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/steampy/__pycache__/guard.cpython-39.pyc -------------------------------------------------------------------------------- /steampy/__pycache__/login.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/steampy/__pycache__/login.cpython-38.pyc -------------------------------------------------------------------------------- /steampy/__pycache__/login.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/steampy/__pycache__/login.cpython-39.pyc -------------------------------------------------------------------------------- /steampy/__pycache__/utils.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/steampy/__pycache__/utils.cpython-38.pyc -------------------------------------------------------------------------------- /steampy/__pycache__/utils.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/steampy/__pycache__/utils.cpython-39.pyc -------------------------------------------------------------------------------- /steampy/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/steampy/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /steampy/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/steampy/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /steampy/__pycache__/confirmation.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/steampy/__pycache__/confirmation.cpython-38.pyc -------------------------------------------------------------------------------- /steampy/__pycache__/confirmation.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbaisarov/steam-autoreg/HEAD/steampy/__pycache__/confirmation.cpython-39.pyc -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /enums.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class Proxy(enum.IntEnum): 5 | Url = 1 6 | File = 2 7 | 8 | 9 | class SelectionType(enum.IntEnum): 10 | RANDOM = 0 11 | CONSISTENT = 1 12 | 13 | 14 | class SmsService(enum.IntEnum): 15 | OnlineSim = 0 16 | SmsActivate = 1 17 | 18 | 19 | class CaptchaService(enum.IntEnum): 20 | RuCaptcha = 0 21 | AntiCaptcha = 1 22 | -------------------------------------------------------------------------------- /steampy/build.bat: -------------------------------------------------------------------------------- 1 | SET CC=C:\mingw64\bin\gcc.exe 2 | REM call nuitka --remove-output --module --no-pyi-file --recurse-none client.py 3 | REM call nuitka --remove-output --module --no-pyi-file --recurse-none confirmation.py 4 | REM call nuitka --remove-output --module --no-pyi-file --recurse-none guard.py 5 | REM call nuitka --remove-output --module --no-pyi-file --recurse-none login.py 6 | call nuitka --remove-output --module --no-pyi-file --recurse-none utils.py 7 | DEL /F *.lib *.exp *.a -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # steam-autoreg 2 | A commercial multithreaded software with graphical UI for registration and Mobile Steam Guard activation. The UI is based on tkinter python library. The CPython interpreter is needed to succesfully launch the app. 3 | 4 | 07.06.22 Fixing issues 5 | 6 | 08.07.22 Traced the problem. An anticaptcha service can't resolve Recaptcha Enterprise. Trying other anticaptcha service. 7 | 8 | !!! The code base has some isssues and needs to be updated since Steam changed the registration process. I will be very grateful if you pull some requests or open issues 9 | -------------------------------------------------------------------------------- /webserver/website/templates/payment.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Оплата за квоту 6 | 7 | 8 |

Счет успешно оплачен! Квота обновлена

9 | {% if key %} 10 |

Ваш ключ продукта: {{ key }}

11 | {% else %} 12 |

Похоже, что вы неверно ввели ключ продукта. Обратитесь к разработчику с номером вашего счета оплаты для того, 13 | чтобы получить новый ключ продукта и использовать оплаченную квоту

14 | {% endif %} 15 | 16 | -------------------------------------------------------------------------------- /.idea/steamreg.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /database/interface_states.json: -------------------------------------------------------------------------------- 1 | { 2 | "autoreg":{ 3 | "priority": 0, 4 | "entries":{ 5 | "new_accounts_amount_entry":"normal", 6 | "rucaptcha_apikey_entry":"normal" 7 | }, 8 | "checkbuttons":{ 9 | "use_mail_repeatedly_checkbutton": "normal", 10 | "add_money_to_account_checkbutton": "normal" 11 | } 12 | }, 13 | "mobile_bind":{ 14 | "priority": 0, 15 | "entries":{ 16 | "onlinesim_apikey_entry":"normal", 17 | "accounts_per_number_entry":"normal", 18 | "amount_of_binders_field": "normal" 19 | }, 20 | "checkbuttons":{ 21 | "mafile_checkbutton":"normal", 22 | "fold_accounts_checkbutton":"normal" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiodns==3.0.0 2 | aiohttp==3.8.1 3 | aiosignal==1.2.0 4 | asn1crypto==1.4.0 5 | async-timeout==4.0.2 6 | attrs==19.1.0 7 | beautifulsoup4==4.10.0 8 | bs4==0.0.1 9 | cert-human==1.0.7 10 | certifi==2021.10.8 11 | cffi==1.15.0 12 | charset-normalizer==2.0.12 13 | cryptography==36.0.1 14 | distlib==0.3.4 15 | filelock==3.6.0 16 | frozenlist==1.3.0 17 | idna==3.3 18 | maxminddb==2.2.0 19 | multidict==6.0.2 20 | parse==1.19.0 21 | pipenv==2022.1.8 22 | platformdirs==2.5.1 23 | proxybroker==0.3.2 24 | pyasn1==0.4.8 25 | pycares==4.1.2 26 | pycparser==2.21 27 | pyOpenSSL==22.0.0 28 | python-anticaptcha==0.7.1 29 | python-dateutil==2.8.2 30 | qiwipy==2.1.6 31 | requests==2.27.1 32 | rsa==4.8 33 | six==1.16.0 34 | soupsieve==2.3.1 35 | urllib3==1.26.8 36 | virtualenv==20.13.2 37 | virtualenv-clone==0.5.7 38 | yarl==1.7.2 39 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | python-anticaptcha = "==0.7.1" 8 | requests = "==2.27.1" 9 | bs4 = "==0.0.1" 10 | cert-human = "==1.0.7" 11 | proxybroker = "==0.3.2" 12 | rsa = "==4.8" 13 | qiwipy = "==2.1.6" 14 | asn1crypto = "==1.4.0" 15 | pyopenssl = "==22.0.0" 16 | birdseye = "*" 17 | sentry-sdk = "*" 18 | aiodns = "==3.0.0" 19 | aiohttp = "==3.8.1" 20 | aiosignal = "==1.2.0" 21 | async-timeout = "==4.0.2" 22 | attrs = "==19.1.0" 23 | beautifulsoup4 = "==4.10.0" 24 | certifi = "==2022.12.7" 25 | cffi = "==1.15.0" 26 | charset-normalizer = "==2.0.12" 27 | cryptography = "==36.0.1" 28 | distlib = "==0.3.4" 29 | filelock = "==3.6.0" 30 | frozenlist = "==1.3.0" 31 | idna = "==3.3" 32 | maxminddb = "==2.2.0" 33 | multidict = "==6.0.2" 34 | parse = "==1.19.0" 35 | pipenv = "==2022.1.8" 36 | platformdirs = "==2.5.1" 37 | pyasn1 = "==0.4.8" 38 | pycares = "==4.1.2" 39 | pycparser = "==2.21" 40 | python-dateutil = "==2.8.2" 41 | six = "==1.16.0" 42 | soupsieve = "==2.3.1" 43 | urllib3 = "==1.26.8" 44 | virtualenv = "==20.13.2" 45 | virtualenv-clone = "==0.5.7" 46 | yarl = "==1.7.2" 47 | 48 | [dev-packages] 49 | yapf = "*" 50 | -------------------------------------------------------------------------------- /database/userdata.txt: -------------------------------------------------------------------------------- 1 | {"manifest_path": "", "autoreg": 0, "import_mafile": 0, "mobile_bind": 1, "fold_accounts": 0, "onlinesim_api_key": "63f0a7337e357ae5e4db99f0778eedfd", "captcha_api_key": "f2ff9b82ed5eac29d17ce918621f576b", "new_accounts_amount": 1, "accounts_per_number": 1, "temp_mail": 1, "private_email_boxes": 0, "accounts_path": "/Users/shamanovsky/Desktop/code/steam/steamreg/accounts.txt", "email_boxes_path": "/Users/shamanovsky/Desktop/email.txt", "proxy_path": "//Mac/Home/Desktop/proxy.txt", "proxy_urls_path": "", "proxy_type": 0, "use_local_ip": 1, "resolve_login_captcha": 1, "amount_of_binders": 1, "pass_login_captcha": 0, "real_names_path": "", "countries_path": "", "avatars_path": "", "use_mail_repeatedly": 0, "add_money_to_account": 0, "selection_type": 1, "sms_service_type": 0, "captcha_service_type": 0, "qiwi_api_key": "key", "country_code": "\u0420\u043e\u0441\u0441\u0438\u044f", "money_to_add": 23, "captcha_host": "", "onlinesim_host": "onlinesim.ru", "login_template": "footars{num}", "nickname_template": "", "passwd_template": "{num}barweaw", "software_product_key": "eca28ab5-332d-48bd-bf3f-58512b4e5264", "free_games": "329385", "statuses_path": "C:/Users/shamanovsky/Desktop/New Text Document (4).txt", "activation_code": "", "dont_use_local_ip": 1, "status_bar": "", "paid_games": "", "generate_emails": 0, "remove_emails_from_file": 0, "accounts_per_proxy": 100, "activate_profile": 1, "imap_host": "imap.yandex.ru", "email_domains": "blog8412.ru", "imap_hosts": {"sad.imap.ru": ["hey.com", "bla.ru"], "dla.imap.com": ["hey.com", "bla.ru"], "fucker.imap.com": ["asdasdasdasdasdasfasfasfa.com"], "fuckesdr.imap.com": ["as.com", "sadasdaw.ru", "rsefs.asd", "slfalf.ru", "lalalala.com", "viza.xyz"], "imap.yandex.ru": ["blog8412.ru"]}} -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 43 | -------------------------------------------------------------------------------- /steampy/guard.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import struct 4 | import time 5 | import logging 6 | import imaplib 7 | import re 8 | 9 | import hmac 10 | import hashlib 11 | 12 | 13 | logger = logging.getLogger('__main__') 14 | 15 | 16 | def load_steam_guard(steam_guard: str) -> dict: 17 | with open(steam_guard, 'r') as file: 18 | return json.loads(file.read()) 19 | 20 | 21 | def generate_one_time_code(shared_secret: str, timestamp: int) -> str: 22 | time_buffer = struct.pack('>Q', timestamp // 30) # pack as Big endian, uint64 23 | time_hmac = hmac.new(base64.b64decode(shared_secret), time_buffer, digestmod=hashlib.sha1).digest() 24 | begin = ord(time_hmac[19:20]) & 0xf 25 | full_code = struct.unpack('>I', time_hmac[begin:begin + 4])[0] & 0x7fffffff # unpack as Big endian uint32 26 | chars = '23456789BCDFGHJKMNPQRTVWXY' 27 | code = '' 28 | 29 | for _ in range(5): 30 | full_code, i = divmod(full_code, len(chars)) 31 | code += chars[i] 32 | 33 | return code 34 | 35 | 36 | def generate_confirmation_key(identity_secret: str, tag: str, timestamp: int = int(time.time())) -> bytes: 37 | buffer = struct.pack('>Q', timestamp) + tag.encode('ascii') 38 | return base64.b64encode(hmac.new(base64.b64decode(identity_secret), buffer, digestmod=hashlib.sha1).digest()) 39 | 40 | 41 | # It works, however it's different that one generated from mobile app 42 | def generate_device_id(steam_id: str) -> str: 43 | hexed_steam_id = hashlib.sha1(steam_id.encode('ascii')).hexdigest() 44 | return 'android:' + '-'.join([hexed_steam_id[:8], 45 | hexed_steam_id[8:12], 46 | hexed_steam_id[12:16], 47 | hexed_steam_id[16:20], 48 | hexed_steam_id[20:32]]) 49 | 50 | 51 | def fetch_emailauth(email, email_password, imap_host): 52 | server = imaplib.IMAP4_SSL(imap_host) 53 | server.login(email, email_password) 54 | server.select() 55 | result, data = server.uid("search", "", '(HEADER Subject "Access from new web or mobile device")') 56 | uid = str(data[1][0].split()[-1]) 57 | result, data = server.uid("fetch", uid, '(UID BODY[TEXT])') 58 | mail = data[1][0][1].decode('utf-8') 59 | emailauth = re.search(r'Here is the Steam Guard code you need to login to account .+:\s+(\w{5})\s', mail) or None 60 | if emailauth is not None: 61 | emailauth.group(1) 62 | logger.info("EMAILAUTH: %s", emailauth) 63 | 64 | return emailauth 65 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '32 11 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /captcha_snippet.py: -------------------------------------------------------------------------------- 1 | import imaplib 2 | import time 3 | import re 4 | from requests import Session 5 | from steampy.utils import convert_edomain_to_imap 6 | from steampy.login import LoginExecutor 7 | from controller import InvalidEmail, RuCaptcha 8 | 9 | 10 | rucaptcha = RuCaptcha("57a13e679aa1817a1669fca25d677fe9") 11 | 12 | 13 | def authorize_email(email, email_password): 14 | email_domain = re.search(r"@(.+$)", email).group(1) 15 | imap_host = convert_edomain_to_imap(email_domain, LoginExecutor.IMAP_HOSTS) 16 | 17 | if imap_host is None: 18 | raise InvalidEmail("Не удается найти imap host для данного email домена: %s" % email_domain) 19 | server = imaplib.IMAP4_SSL(imap_host) 20 | server.login(email, email_password) 21 | server.select() 22 | return server 23 | 24 | 25 | def fetch_confirmation_link(email, email_password, creationid): 26 | server = authorize_email(email, email_password) 27 | attempts = 0 28 | while attempts < 5: 29 | attempts += 1 30 | typ, data = server.search(None, 'ALL') 31 | uid = data[0].split()[-1] 32 | result, data = server.uid("fetch", uid, '(UID BODY[TEXT])') 33 | mail = data[0][1].decode('utf-8') 34 | link = re.search(r'(https://.+newaccountverification.+?)\r', mail) 35 | if link is None: 36 | time.sleep(5) 37 | continue 38 | link = link.group(1) 39 | creationid_from_link = re.search(r"creationid=(\w+)", link) 40 | if creationid_from_link is not None and creationid == creationid_from_link.group(1): 41 | server.close() 42 | return link 43 | time.sleep(5) 44 | server.close() 45 | raise InvalidEmail("Не удается получить письмо от Steam") 46 | 47 | 48 | def confirm_email(session, gid, token, email: str, sitekey): 49 | email_name, _, email_password = email.partition(":") 50 | while True: 51 | data = { 52 | 'captcha_text': token, 53 | 'captchagid': gid, 54 | 'email': email_name 55 | } 56 | resp = session.post('https://store.steampowered.com/join/ajaxverifyemail', data=data).json() 57 | creationid = resp['sessionid'] 58 | if not creationid: 59 | captcha_id = rucaptcha.generate_recaptcha(sitekey) 60 | token = rucaptcha.resolve_captcha(captcha_id) 61 | continue 62 | 63 | time.sleep(10) # wait some time until email has been received 64 | link = fetch_confirmation_link(email_name, email_password, creationid) 65 | session.get(link) 66 | return creationid 67 | 68 | 69 | def main(): 70 | session = Session() 71 | session.headers.update({'User-Agent': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 72 | 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36'), 73 | 'Accept-Language': 'q=0.8,en-US;q=0.6,en;q=0.4'}) 74 | session.headers.update({'Host': 'store.steampowered.com'}) 75 | response = session.get('https://store.steampowered.com/join/refreshcaptcha/?count=1', timeout=30).json() 76 | gid, sitekey = response['gid'], response['sitekey'] 77 | captcha_id = rucaptcha.generate_recaptcha(sitekey) 78 | token = rucaptcha.resolve_captcha(captcha_id) 79 | email = "awdasdwadawd@yandex.ru:viga9982" # твоя почта 80 | creationid = confirm_email(session, gid, token, email, sitekey) 81 | data = { 82 | 'accountname': "rtuyrturtyr", # логин для стим аккаунта 83 | 'password': "asdgfdш77d4783", # пароль для стим аккаунта 84 | 'count': '32', 85 | 'lt': '0', 86 | 'creation_sessionid': creationid 87 | } 88 | resp = session.post('https://store.steampowered.com/join/createaccount/', 89 | data=data, timeout=25) 90 | print(resp.text) 91 | 92 | 93 | main() 94 | -------------------------------------------------------------------------------- /steampy/confirmation.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import time 3 | from typing import List 4 | 5 | import requests 6 | from bs4 import BeautifulSoup 7 | 8 | from steampy import guard 9 | from steampy.login import InvalidCredentials 10 | 11 | 12 | class Confirmation: 13 | def __init__(self, _id, data_confid, data_key): 14 | self.id = _id.split('conf')[1] 15 | self.data_confid = data_confid 16 | self.data_key = data_key 17 | 18 | 19 | class Tag(enum.Enum): 20 | CONF = 'conf' 21 | DETAILS = 'details' 22 | ALLOW = 'allow' 23 | CANCEL = 'cancel' 24 | 25 | 26 | class ConfirmationExecutor: 27 | CONF_URL = "https://steamcommunity.com/mobileconf" 28 | 29 | def __init__(self, trade_offer_id: str, identity_secret: str, my_steam_id: str, session: requests.Session) -> None: 30 | self._trade_offer_id = trade_offer_id 31 | self._my_steam_id = my_steam_id 32 | self._identity_secret = identity_secret 33 | self._session = session 34 | self._session.headers.update({'User-Agent': ('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 ' 35 | '(KHTML, like Gecko) Chrome/11.0.696.34 Safari/534.24')}) 36 | 37 | def send_trade_allow_request(self) -> dict: 38 | confirmations = self._get_confirmations() 39 | confirmation = self._select_trade_offer_confirmation(confirmations) 40 | conf_status = self._confirm_trans(confirmation) 41 | return conf_status 42 | 43 | def send_markettrans_allow_request(self) -> dict: 44 | confirmations = self._get_confirmations() 45 | resp = self._multi_confimm_trans(confirmations) 46 | return resp.json() 47 | 48 | def _confirm_trans(self, confirmation): 49 | tag = Tag.ALLOW 50 | params = self._create_confirmation_params(tag.value) 51 | params['op'] = tag.value, 52 | params['cid'] = confirmation.data_confid 53 | params['ck'] = confirmation.data_key 54 | headers = {'X-Requested-With': 'XMLHttpRequest'} 55 | resp = self._session.get( 56 | self.CONF_URL + '/ajaxop', params=params, headers=headers, timeout=60).json() 57 | return resp 58 | 59 | def _multi_confimm_trans(self, confirmations): 60 | tag = Tag.ALLOW 61 | params = self._create_confirmation_params(tag.value) 62 | params['op'] = tag.value, 63 | params_list = list(params.items()) 64 | for conf in confirmations: 65 | params_list.append(('cid[]', conf.data_confid)) 66 | params_list.append(('ck[]', conf.data_key)) 67 | headers = {'X-Requested-With': 'XMLHttpRequest'} 68 | resp = self._session.post( 69 | self.CONF_URL + '/multiajaxop', data=params_list, headers=headers, timeout=60) 70 | return resp 71 | 72 | def _get_confirmations(self) -> List[Confirmation]: 73 | confirmations = [] 74 | confirmations_page = self._fetch_confirmations_page() 75 | soup = BeautifulSoup(confirmations_page.text, 'html.parser') 76 | if soup.select('#mobileconf_empty'): 77 | return confirmations 78 | for confirmation_div in soup.select('#mobileconf_list .mobileconf_list_entry'): 79 | _id = confirmation_div['id'] 80 | data_confid = confirmation_div['data-confid'] 81 | data_key = confirmation_div['data-key'] 82 | confirmations.append(Confirmation(_id, data_confid, data_key)) 83 | return confirmations 84 | 85 | def _fetch_confirmations_page(self) -> requests.Response: 86 | tag = Tag.CONF.value 87 | params = self._create_confirmation_params(tag) 88 | headers = {'X-Requested-With': 'com.valvesoftware.android.steam.community'} 89 | response = self._session.get(self.CONF_URL + '/conf', params=params, headers=headers, timeout=60) 90 | if 'Steam Guard Mobile Authenticator is providing incorrect Steam Guard codes.' in response.text: 91 | raise InvalidCredentials('Invalid Steam Guard file') 92 | elif "You've made too many requests recently." in response.text: 93 | print('too many attempts made recently, waiting for 10 minutes') 94 | time.sleep(600) 95 | return response 96 | 97 | def _fetch_confirmation_details_page(self, confirmation: Confirmation) -> str: 98 | tag = 'details' + confirmation.id 99 | params = self._create_confirmation_params(tag) 100 | response = self._session.get(self.CONF_URL + '/details/' + confirmation.id, params=params, timeout=60) 101 | return response.json()['html'] 102 | 103 | def _create_confirmation_params(self, tag_string: str) -> dict: 104 | timestamp = int(time.time()) 105 | confirmation_key = guard.generate_confirmation_key(self._identity_secret, tag_string, timestamp) 106 | android_id = guard.generate_device_id(self._my_steam_id) 107 | return {'p': android_id, 108 | 'a': self._my_steam_id, 109 | 'k': confirmation_key, 110 | 't': timestamp, 111 | 'm': 'android', 112 | 'tag': tag_string} 113 | 114 | def _select_trade_offer_confirmation(self, confirmations: List[Confirmation]) -> Confirmation: 115 | for confirmation in confirmations: 116 | confirmation_details_page = self._fetch_confirmation_details_page(confirmation) 117 | confirmation_id = self._get_confirmation_trade_offer_id(confirmation_details_page) 118 | if confirmation_id == self._trade_offer_id: 119 | return confirmation 120 | raise ConfirmationExpected 121 | 122 | @staticmethod 123 | def _get_confirmation_trade_offer_id(confirmation_details_page: str): 124 | soup = BeautifulSoup(confirmation_details_page, 'html.parser') 125 | full_offer_id = soup.select('.tradeoffer')[0]['id'] 126 | return full_offer_id.split('_')[1] # type: ignore 127 | 128 | 129 | class ConfirmationExpected(Exception): 130 | pass 131 | -------------------------------------------------------------------------------- /steampy/utils.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import struct 3 | import time 4 | import re 5 | import datetime 6 | import logging 7 | from typing import List 8 | from imaplib import IMAP4, IMAP4_SSL 9 | from xml.dom.minidom import Attr 10 | 11 | 12 | class GameOptions(enum.Enum): 13 | DOTA2 = ('570', '2') 14 | CS = ('730', '2') 15 | TF2 = ('440', '2') 16 | GIFTS = ('753', '1') 17 | CARDS = ('753', '6') 18 | PAYDAY2 = ('218620', '2') 19 | H1Z1 = ('433850', '1') 20 | PUBG = ('578080', '2') 21 | 22 | def __init__(self, app_id: str, context_id: str) -> None: 23 | self.app_id = app_id 24 | self.context_id = context_id 25 | 26 | @classmethod 27 | def appid_to_option(cls, appid): 28 | for game_option in cls: 29 | if appid == game_option.value[0]: 30 | return game_option 31 | raise Exception('This appid is not supported by GameOptions: %s' % appid) 32 | 33 | 34 | logger = logging.getLogger("__main__") 35 | 36 | 37 | def fetch_email_code(email, email_passwd, login_name, subject): 38 | email_domain = email.partition('@')[2] 39 | if email_domain == 'yandex.ru' or email_domain == 'bubblemail.xyz': 40 | imap_server = 'imap.yandex.ru' 41 | else: 42 | imap_server = 'imap.mail.ru' # no full search support for this imap server 43 | 44 | date = datetime.datetime.today().strftime("%d-%b-%Y") 45 | regexpr = r'login to account (?:.+):\s+(.+)\s+' 46 | if 'Email address change request' in subject: 47 | regexpr = r'to update your email address:\s+(.+)\s+' 48 | 49 | while True: 50 | try: 51 | server = IMAP4_SSL(imap_server) 52 | server.login(email, email_passwd) 53 | server.select() 54 | time.sleep(10) 55 | attempts = 0 56 | mail_body = None 57 | while attempts < 20: 58 | typ, msgnums = server.search( 59 | None, 'UNSEEN SINCE {} Subject "{}"'.format(date, subject)) 60 | print(msgnums[0]) 61 | print(msgnums[0].split()[-1]) 62 | if msgnums[0]: 63 | mail_body = server.fetch(msgnums[0].split()[-1], '(UID BODY[TEXT])')[1][0] 64 | if mail_body is not None: 65 | mail_body = mail_body[1].decode('utf-8') # type: ignore 66 | if login_name in mail_body: 67 | break 68 | server.select() 69 | time.sleep(15) 70 | attempts += 1 71 | 72 | if not mail_body: 73 | raise Exception('The email with the steam guard code was not found.') 74 | guard_code = re.search(regexpr, mail_body) 75 | if guard_code is not None: 76 | guard_code = guard_code.group(1).rstrip() 77 | print('Email found, guard code:', guard_code) 78 | return guard_code 79 | except (IMAP4.abort, IMAP4.error, ConnectionResetError) as err: 80 | print('Error while connecting to IMAP:', err) 81 | print('Reconnecting...') 82 | time.sleep(5) 83 | 84 | 85 | def fetch_email_code_tempmail(): 86 | pass 87 | 88 | 89 | def text_between(text: str, begin: str, end) -> str: 90 | try: 91 | start = text.index(begin) + len(begin) 92 | except ValueError as err: 93 | print(text) 94 | raise err 95 | end = text.index(end, start) 96 | return text[start:end] 97 | 98 | 99 | def account_id_to_steam_id(account_id: str) -> str: 100 | first_bytes = int(account_id).to_bytes(4, byteorder='big') 101 | last_bytes = 0x1100001.to_bytes(4, byteorder='big') 102 | return str(struct.unpack('>Q', last_bytes + first_bytes)[0]) 103 | 104 | 105 | def steam_id_to_account_id(steam_id: str) -> str: 106 | return str(struct.unpack('>L', int(steam_id).to_bytes(8, byteorder='big')[4:])[0]) 107 | 108 | 109 | def price_to_float(price: str) -> float: 110 | return float(price[1:].split()[0]) 111 | 112 | 113 | def merge_items_with_descriptions_from_inventory(inventory_response: dict, game: GameOptions) -> dict: 114 | inventory: dict = inventory_response['rgInventory'] 115 | if isinstance(inventory, list): 116 | inventory = dict(inventory) 117 | descriptions = inventory_response['rgDescriptions'] 118 | return merge_items(list(inventory.values()), descriptions, context_id=game.context_id) 119 | 120 | 121 | def merge_items_with_descriptions_from_offers(offers_response: dict) -> dict: 122 | descriptions = {get_description_key(offer): offer for offer in offers_response['response'].get('descriptions', [])} 123 | received_offers = offers_response['response'].get('trade_offers_received', []) 124 | sent_offers = offers_response['response'].get('trade_offers_sent', []) 125 | offers_response['response']['trade_offers_received'] = list( 126 | map(lambda offer: merge_items_with_descriptions_from_offer(offer, descriptions), received_offers)) 127 | offers_response['response']['trade_offers_sent'] = list( 128 | map(lambda offer: merge_items_with_descriptions_from_offer(offer, descriptions), sent_offers)) 129 | return offers_response 130 | 131 | 132 | def merge_items_with_descriptions_from_offer(offer: dict, descriptions: dict) -> dict: 133 | merged_items_to_give = merge_items(offer.get('items_to_give', []), descriptions) 134 | merged_items_to_receive = merge_items(offer.get('items_to_receive', []), descriptions) 135 | offer['items_to_give'] = merged_items_to_give 136 | offer['items_to_receive'] = merged_items_to_receive 137 | return offer 138 | 139 | 140 | def merge_items(items: List[dict], descriptions: dict, **kwargs) -> dict: 141 | merged_items = {} 142 | for item in items: 143 | description_key = get_description_key(item) 144 | description = descriptions[description_key] 145 | item_id = item.get('id') or item['assetid'] 146 | description['contextid'] = item.get('contextid') or kwargs['context_id'] 147 | description['id'] = item_id 148 | description['amount'] = item['amount'] 149 | merged_items[item_id] = description 150 | return merged_items 151 | 152 | 153 | def get_description_key(item: dict) -> str: 154 | return item['classid'] + '_' + item['instanceid'] 155 | 156 | 157 | def update_session(client): 158 | client.session.cookies.clear() 159 | client.login(client.login_name, client.password, 160 | client.mafile) 161 | 162 | 163 | def convert_edomain_to_imap(email_domain, additional_hosts={}): 164 | host = None 165 | domains_and_hosts = { 166 | "imap.yandex.ru": ["yandex.ru"], 167 | "imap.mail.ru": ["mail.ru", "bk.ru", "list.ru", "inbox.ru", "mail.ua"], 168 | "imap.rambler.ru": ["rambler.ru", "lenta.ru", "autorambler.ru", "myrambler.ru", "ro.ru", "rambler.ua"], 169 | "imap.gmail.com": ["gmail.com", ], 170 | "imap.mail.yahoo.com": ["yahoo.com", ], 171 | "imap-mail.outlook.com": ["outlook.com", "hotmail.com"], 172 | "imap.aol.com": ["aol.com", ] 173 | } 174 | 175 | for imap_host, domains in additional_hosts.items(): 176 | try: 177 | list(map(lambda domain: domains_and_hosts[imap_host].append(domain), domains)) 178 | except (KeyError, TypeError): 179 | domains_and_hosts[imap_host] = domains 180 | for host, domains in domains_and_hosts.items(): 181 | if email_domain in domains: 182 | return host 183 | 184 | return host 185 | -------------------------------------------------------------------------------- /sms_services.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import re 3 | import time 4 | import json 5 | import logging 6 | 7 | 8 | class OnlineSimError(Exception): pass 9 | class SmsActivateError(Exception): pass 10 | 11 | 12 | logger = logging.getLogger('__main__') 13 | 14 | 15 | class OnlineSimApi: 16 | 17 | def __init__(self, api_key, host="onlinesim.ru"): 18 | self.api_key = api_key 19 | host = re.search(r"(?:https?://)?(.+)/?", host) 20 | if host is not None: 21 | host.group(1).rstrip("/") 22 | assert isinstance(host, str) 23 | self.base_url = "https://" + host + "/%s" 24 | 25 | def _request_new_number(self, country): 26 | url = self.base_url % 'api/getNum.php' 27 | data = { 28 | 'service': 'Steam', 29 | 'apikey': self.api_key, 30 | 'form': '1', 31 | 'country': country 32 | } 33 | resp = self._send_request(url, data) 34 | while True: 35 | try: 36 | tzid = resp['tzid'] 37 | break 38 | except KeyError: 39 | raise OnlineSimError("Ошибка от сервиса sim %s" % resp['response']) 40 | return tzid 41 | 42 | def get_number(self, country='7'): 43 | tzid = self._request_new_number(country) 44 | url = self.base_url % 'api/getState.php' 45 | data = {'message_to_code': 1, 'tzid': tzid, 'apikey': self.api_key} 46 | while True: 47 | resp = self._send_request(url, data) 48 | try: 49 | return tzid, resp[0]['number'] 50 | except (KeyError, IndexError): 51 | if resp[0]['response'] == 'TZ_INPOOL': 52 | time.sleep(3) 53 | continue 54 | raise OnlineSimError(resp['response']) 55 | 56 | def get_sms_code(self, tztoken): 57 | url = self.base_url % 'api/getState.php' 58 | data = {'message_to_code': 1, 'tzid': tztoken, 'apikey': self.api_key} 59 | resp = self._send_request(url, data) 60 | try: 61 | sms_code = resp[0].get('msg', None) 62 | time_left = resp[0].get('time', None) 63 | except IndexError: 64 | raise OnlineSimError(resp.text) 65 | if not time_left: 66 | raise OnlineSimError(resp[0]) 67 | return sms_code, time_left 68 | 69 | def set_operation_ok(self, tztoken, time_start): 70 | now = int(time.time()) 71 | if now - time_start < 125: 72 | time.sleep(125 - (now - time_start)) 73 | url = self.base_url % 'api/setOperationOk.php' 74 | data = {'tzid': tztoken, 'apikey': self.api_key} 75 | self._send_request(url, data) 76 | 77 | def request_repeated_number_usage(self, tztoken): 78 | url = self.base_url % 'api/setOperationRevise.php' 79 | data = {'tzid': tztoken, 'apikey': self.api_key} 80 | self._send_request(url, data) 81 | 82 | def get_balance(self): 83 | url = self.base_url % 'api/getBalance.php' 84 | data = {'apikey': self.api_key} 85 | resp = self._send_request(url, data) 86 | try: 87 | return resp["balance"] 88 | except KeyError: 89 | raise OnlineSimError(resp["response"]) 90 | 91 | @staticmethod 92 | def _send_request(url, data): 93 | resp = None 94 | while True: 95 | try: 96 | resp = requests.post(url, data=data, timeout=5) 97 | except requests.exceptions.Timeout: 98 | logger.error('Не удалось получить ответ от: %s', url) 99 | time.sleep(3) 100 | continue 101 | if resp is not None: 102 | try: 103 | resp = resp.json() 104 | break 105 | except json.decoder.JSONDecodeError: 106 | logger.error('Сработала CloudFlare защита: %s. Код ошибки: %s', url, resp.status_code) 107 | time.sleep(3) 108 | 109 | logger.info(resp) 110 | return resp 111 | 112 | 113 | class SmsActivateApi: 114 | 115 | def __init__(self, api_key, host="sms-activate.ru"): 116 | self.api_key = api_key 117 | host = re.search(r"(?:https?://)?(.+)/?", host) 118 | if host is not None: 119 | host = host.group(1).rstrip("/") 120 | assert isinstance(host, str) 121 | self.base_url = "http://" + host + "/stubs/handler_api.php" 122 | 123 | def get_number_status(self): 124 | """Get number of numbers available""" 125 | resp = requests.get(self.base_url, params={'api_key': self.api_key, 126 | 'action': 'getNumbersStatus'}, timeout=10) 127 | if 'BAD_KEY' in resp.text: 128 | raise SmsActivateError('Неверный API ключ') 129 | 130 | if not resp.json()['ot_0']: 131 | raise SmsActivateError('Закончились номера') 132 | 133 | def get_balance(self): 134 | resp = requests.get(self.base_url, params={'api_key': self.api_key, 135 | 'action': 'getBalance'}, timeout=10) 136 | logger.info(resp.text) 137 | if float(resp.text.partition(':')[2]) < 2: 138 | raise SmsActivateError('Недостаточно баланса для заказа номера') 139 | return resp.text 140 | 141 | def get_number(self, country='0'): 142 | resp = requests.get(self.base_url, params={'api_key': self.api_key, 143 | 'action': 'getNumber', 144 | 'service': 'mt', 145 | 'operator': 'any', 146 | 'country': country}, timeout=10) 147 | logger.info('Ответ от sms-activate на запрос получить новый номер: ' + resp.text) 148 | if "ACCESS_NUMBER" not in resp.text: 149 | raise SmsActivateError(resp.text) 150 | 151 | token, number = resp.text.split(':')[1:] 152 | number = '+' + number 153 | return token, number 154 | 155 | def set_operation_ok(self, token): 156 | self._set_status(token, 8) 157 | 158 | def request_repeated_number_usage(self, token): 159 | self._set_status(token, 3) 160 | 161 | def _set_status(self, token, status): 162 | """ 163 | :param token: activation id 164 | :param status: 1 - number is ready, 3 - request number again, 6 - complete activation 165 | :return: None 166 | """ 167 | set_status_params = {'api_key': self.api_key, 'action': 'setStatus', 'id': token, 'status': status} 168 | resp = requests.get(self.base_url, params=set_status_params, timeout=10) 169 | logger.info('Ответ от sms-activate на запрос установить статус: ' + resp.text) 170 | 171 | def get_sms_code(self, token): 172 | resp = requests.get(self.base_url, params={'api_key': self.api_key, 173 | 'action': 'getStatus', 174 | 'id': token}, timeout=10) 175 | logger.info('Ответ от sms-activate на запрос получить статус: ' + resp.text) 176 | sms_code = None 177 | try: 178 | status, delimeter, smscode_msg = resp.text.partition(':') 179 | sms_code_msg = re.search(r'\d+', smscode_msg) 180 | if sms_code_msg is not None: 181 | sms_code = sms_code_msg.group(1) 182 | except AttributeError: 183 | sms_code = '' 184 | 185 | return sms_code, None 186 | -------------------------------------------------------------------------------- /steampy/login.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import time 3 | import json 4 | 5 | import requests 6 | import rsa 7 | 8 | from steampy import guard 9 | from steampy.utils import convert_edomain_to_imap 10 | 11 | 12 | class LoginExecutor: 13 | COMMUNITY_URL = "https://steamcommunity.com" 14 | STORE_URL = 'https://store.steampowered.com' 15 | API_URL = 'https://api.steampowered.com' 16 | IMAP_HOSTS = dict() 17 | 18 | def __init__(self, username, password, shared_secret, session, 19 | email, email_passwd, captcha_gid='-1', captcha_text=''): 20 | self.username = username 21 | self.password = password 22 | self.email = email 23 | self.email_passwd = email_passwd 24 | self.captcha_gid = captcha_gid 25 | self.captcha_text = captcha_text 26 | self.shared_secret = shared_secret 27 | self.session = session 28 | 29 | def login(self) -> dict: 30 | login_response = self._send_login_request() 31 | self._check_for_captcha(login_response) 32 | self._perform_redirects(login_response) 33 | return login_response 34 | 35 | def mobile_login(self): 36 | login_response = self._send_login_request(mobile_request=True) 37 | self._check_for_captcha(login_response) 38 | return login_response 39 | 40 | def _send_login_request(self, mobile_request=False): 41 | one_time_code = None 42 | emailauth = None 43 | second_attempt = False 44 | attempts = 0 45 | response = None 46 | timestamp = 0 47 | while attempts < 3: 48 | attempts += 1 49 | rsa_params = self._fetch_rsa_params() 50 | encrypted_password = self._encrypt_password(rsa_params) 51 | rsa_timestamp = rsa_params['rsa_timestamp'] 52 | if self.shared_secret: 53 | # resp = requests.post(self.API_URL + '/ITwoFactorService/QueryTime/v0001/', 54 | # data={'steamid': 0}).json() 55 | # print(resp) 56 | # timestamp = int(resp['response']['server_time']) 57 | timestamp = int(time.time()) 58 | one_time_code = guard.generate_one_time_code( 59 | self.shared_secret, timestamp) 60 | if mobile_request: 61 | request_data = self._prepare_mobile_login_request_data(encrypted_password, rsa_timestamp, 62 | one_time_code, emailauth) 63 | else: 64 | request_data = self._prepare_login_request_data(encrypted_password, rsa_timestamp, 65 | one_time_code, emailauth) 66 | try: 67 | response = self.session.post(self.COMMUNITY_URL + '/login/dologin', 68 | data=request_data, attempts=3).json() 69 | except json.decoder.JSONDecodeError as err: 70 | print(err, '/login/dologin') 71 | continue 72 | 73 | if response is None: 74 | continue 75 | 76 | if len(response) == 3 and self.shared_secret and not second_attempt: 77 | time.sleep(30 - timestamp % 30) # wait until the next code is generated 78 | second_attempt = True 79 | continue 80 | 81 | if response.get('emailauth_needed', None): 82 | if self.email and self.email_passwd: 83 | imap_host = convert_edomain_to_imap(self.email, "../database/imap-hosts.json") 84 | time.sleep(10) 85 | emailauth = guard.fetch_emailauth(self.email, self.email_passwd, imap_host) 86 | continue 87 | break 88 | 89 | if response is None: 90 | raise AuthException("Can't log in %s:%s" % (self.username, self.password)) 91 | 92 | return response 93 | 94 | def _fetch_rsa_params(self) -> dict: 95 | while True: 96 | try: 97 | key_response = self.session.post(self.STORE_URL + '/login/getrsakey/', 98 | data={'username': self.username}, attempts=3).json() 99 | rsa_mod = int(key_response['publickey_mod'], 16) 100 | break 101 | except (json.decoder.JSONDecodeError, KeyError) as err: 102 | print(err, '/login/getrsakey/') 103 | time.sleep(3) 104 | 105 | rsa_exp = int(key_response['publickey_exp'], 16) 106 | rsa_timestamp = key_response['timestamp'] 107 | return {'rsa_key': rsa.PublicKey(rsa_mod, rsa_exp), 108 | 'rsa_timestamp': rsa_timestamp} 109 | 110 | def _encrypt_password(self, rsa_params: dict) -> bytes: 111 | return base64.b64encode( 112 | rsa.encrypt(self.password.encode('utf-8'), rsa_params['rsa_key'])) 113 | 114 | def _prepare_login_request_data(self, encrypted_password, rsa_timestamp, 115 | one_time_code, emailauth) -> dict: 116 | return { 117 | 'password': encrypted_password, 118 | 'username': self.username, 119 | 'twofactorcode': one_time_code, 120 | 'emailauth': emailauth, 121 | 'loginfriendlyname': '', 122 | 'captchagid': self.captcha_gid, 123 | 'captcha_text': self.captcha_text, 124 | 'emailsteamid': '', 125 | 'rsatimestamp': rsa_timestamp, 126 | 'remember_login': 'true', 127 | 'donotcache': str(int(time.time() * 1000)) 128 | } 129 | 130 | def _prepare_mobile_login_request_data(self, encrypted_password, rsa_timestamp, 131 | one_time_code, emailauth): 132 | return { 133 | 'username': self.username, 134 | 'password': encrypted_password, 135 | 'twofactorcode': one_time_code, 136 | 'captchagid': self.captcha_gid, 137 | 'captcha_text': self.captcha_text, 138 | 'emailsteamid': '', 139 | 'emailauth': emailauth, 140 | 'rsatimestamp': rsa_timestamp, 141 | 'remember_login': 'false', 142 | 'oauth_client_id': 'DE45CD61', 143 | 'oauth_scope': 'read_profile write_profile read_client write_client', 144 | 'loginfriendlyname': '#login_emailauth_friendlyname_mobile', 145 | 'donotcache': str(int(time.time() * 1000)) 146 | } 147 | 148 | @staticmethod 149 | def _check_for_captcha(login_response: dict) -> None: 150 | if login_response.get('captcha_needed', False): 151 | raise CaptchaRequired(login_response["captcha_gid"]) 152 | 153 | @staticmethod 154 | def _assert_valid_credentials(login_response: requests.Response) -> None: 155 | if not login_response.json()['success']: 156 | raise InvalidCredentials(login_response.json()['message']) 157 | 158 | def _perform_redirects(self, response_dict: dict): 159 | try: 160 | parameters = response_dict['transfer_parameters'] 161 | except KeyError: 162 | return None 163 | for url in response_dict['transfer_urls']: 164 | self.session.post(url, parameters, attempts=3) 165 | 166 | def _fetch_home_page(self, session: requests.Session) -> requests.Response: 167 | return session.post(self.COMMUNITY_URL + '/my/home/') 168 | 169 | 170 | class InvalidCredentials(Exception): 171 | pass 172 | 173 | 174 | class CaptchaRequired(Exception): 175 | pass 176 | 177 | 178 | class AuthException(Exception): 179 | pass 180 | -------------------------------------------------------------------------------- /webserver/server.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import hashlib 3 | import json 4 | import logging.handlers 5 | import shelve 6 | import traceback 7 | import uuid 8 | 9 | import requests 10 | from flask import Flask, request, render_template, jsonify 11 | 12 | # disable flask and requests info logs 13 | logging.getLogger('werkzeug').setLevel(logging.ERROR) 14 | logging.getLogger("requests").setLevel(logging.ERROR) 15 | 16 | logger = logging.getLogger() 17 | logger.setLevel(level=logging.INFO) 18 | file_handler = logging.handlers.RotatingFileHandler( 19 | 'logs.txt', maxBytes=10 * 1024 * 1024, backupCount=1, encoding='utf-8') 20 | formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s') 21 | file_handler.setFormatter(formatter) 22 | logger.addHandler(file_handler) 23 | 24 | app = Flask(__name__) 25 | 26 | 27 | @app.route('/', methods=['GET', 'POST']) 28 | def check_license_autoreg(): 29 | logger.info('Application: autoreg') 30 | success = False 31 | data = {key: value for key, value in request.form.items()} 32 | ip = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) 33 | try: 34 | key = data['key'] 35 | except KeyError: 36 | return success 37 | db = shelve.open("clients") 38 | db_data = {} 39 | try: 40 | if key in db: 41 | db_data = db[key] 42 | success = True 43 | else: 44 | logger.info('WRONG KEY: %s, %s\n', data, ip) 45 | finally: 46 | db.close() 47 | 48 | return jsonify({"success_x001": success, "data": db_data}), 200 49 | 50 | 51 | @app.route('/check_license', methods=['POST']) 52 | def check_license_farmtools(): 53 | with open('farmtools_keys.txt', 'r') as f: 54 | farmtools_keys = [i.rstrip() for i in f.readlines()] 55 | 56 | logger.info('Application: farmtools') 57 | success = False 58 | data = {key: value for key, value in request.form.items()} 59 | ip = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) 60 | try: 61 | key = data['key'] 62 | except KeyError: 63 | return success 64 | db = shelve.open('farmtools') 65 | try: 66 | if key in farmtools_keys: 67 | if not db.get(key, None): 68 | data['ip'] = (ip, get_city_from_ip(ip)) 69 | logger.info('IP : %s', ip) 70 | update_database(data, db, key) 71 | success = True 72 | else: 73 | db_data = db[key] 74 | success = check_device(data, db_data, ip) 75 | else: 76 | logger.info('WRONG KEY: %s, %s\n', data, ip) 77 | finally: 78 | db.close() 79 | 80 | return jsonify({'success': success}), 200 81 | 82 | 83 | @app.route('/showdb', methods=['GET']) 84 | def show_db(): 85 | with shelve.open('farmtools_db') as db: 86 | try: 87 | return render_template('farmtools_db.html', database=dict(db)), 200 88 | except FileExistsError: 89 | logger.error(traceback.print_exc()) 90 | return 'Error', 500 91 | 92 | 93 | @app.route('/catalogue', methods=['GET']) 94 | def get_catalogue(): 95 | key, uid, catalogue_key = request.headers['key'], request.headers['uid'], request.headers['catalogue-key'] 96 | with shelve.open('farmtools_db') as db: 97 | try: 98 | client_data = db[key] 99 | except KeyError: 100 | logger.error("Client is not in the database: %s %s", key, uid) 101 | return 'Not allowed', 403 102 | 103 | if client_data['uid'] != uid: 104 | logger.error("UID is different: %s %s", uid, client_data) 105 | return 'Not allowed. Wrong UID.', 403 106 | 107 | is_valid = check_catalogue_key(catalogue_key) 108 | if not is_valid: 109 | logger.info("Catalogue key is not valid: %s %s", catalogue_key, client_data) 110 | return 'Not allowed. The catalogue key was not provided or it expired.', 403 111 | 112 | logger.info("Successfully returned catalogue: %s", client_data) 113 | with open("catalogue.json", 'r', encoding='utf-8') as f: 114 | return f.read(), 200 115 | 116 | 117 | @app.route('/generate-product-key') 118 | def generate_product_key(): 119 | login = request.args.get("login") 120 | if login is not None: 121 | login = login.strip() 122 | 123 | with shelve.open("clients") as db: 124 | for data in db.values(): 125 | if login == data.get("login", None): 126 | return "Already used", 406 127 | product_key = str(uuid.uuid4()) 128 | db[product_key] = {"login": login, "registration_quota": 0, "binding_quota": 0, "payments": {}} 129 | 130 | return product_key, 200 131 | 132 | 133 | @app.route('/addquota', methods=['POST']) 134 | def add_quota(): 135 | data = {key: value for key, value in request.form.items()} 136 | key, registration_quota, binding_quota = data["key"], data["registration_quota"], data["binding_quota"] 137 | with shelve.open("clients", writeback=True) as db: 138 | client = db[key] 139 | try: 140 | client["registration_quota"] += int(registration_quota) 141 | client["binding_quota"] += int(binding_quota) 142 | except KeyError: 143 | client["registration_quota"] = int(registration_quota) 144 | client["binding_quota"] = int(binding_quota) 145 | client["payments"] = {} 146 | return "OK", 200 147 | 148 | 149 | @app.route('/updatequota', methods=['POST']) 150 | def update_quota(): 151 | data = {key: value for key, value in request.form.items()} 152 | key, registration_quota, binding_quota = data["key"], data["registration_quota"], data["binding_quota"] 153 | with shelve.open("clients", writeback=True) as db: 154 | client = db[key] 155 | client["registration_quota"] = int(registration_quota) 156 | client["binding_quota"] = int(binding_quota) 157 | return "OK", 200 158 | 159 | 160 | @app.route('/validatecode') 161 | def validate_code(): 162 | code = request.args.get("uniquecode") 163 | key = request.args.get("key") 164 | if key is not None: 165 | key = key.strip() 166 | success = False 167 | db = None 168 | try: 169 | db = shelve.open("clients", writeback=True) 170 | try: 171 | client = db[key] 172 | except KeyError: 173 | message = "Не удалось найти ключ продукта программы в базе данных" 174 | return jsonify({"success": success, "message": message}), 200 175 | 176 | try: 177 | used_codes = db["used_codes"] 178 | except KeyError: 179 | used_codes = db["used_codes"] = set() 180 | if code in used_codes: 181 | message = "Этот код уже активирован" 182 | return jsonify({"success": success, "message": message}), 200 183 | resp = requests.post("https://www.oplata.info/xml/check_unique_code.asp", data=json.dumps({ 184 | "id_seller": 479531, 185 | "unique_code": code, 186 | "sign": hashlib.md5("479531:{code}:C9149CDFDC".format(code=code).encode("utf-8")).hexdigest() 187 | }), headers={"Content-Type": "application/json"}).json() 188 | if resp["retval"] == "-2": 189 | message = "Неверно введен код" 190 | return jsonify({"success": success, "message": message}), 200 191 | 192 | quota = None 193 | id_goods = resp["id_goods"] 194 | if id_goods == "2542451": 195 | quota = "registration_quota" 196 | elif id_goods == "2550416": 197 | quota = "binding_quota" 198 | 199 | amount = int(resp["cnt_goods"]) 200 | 201 | if quota is not None: 202 | client[quota] += amount 203 | try: 204 | client["payments"][resp["inv"]] = resp 205 | except KeyError: 206 | client["payments"] = {} 207 | client["payments"][resp["inv"]] = resp 208 | used_codes.add(code) 209 | finally: 210 | db.close() 211 | success = True 212 | message = "Код успешно активирован. Перезапустите программу чтобы изменения вступили в силу" 213 | return jsonify({"success": success, "message": message, "amount": amount, "quota": quota}), 200 214 | 215 | 216 | @app.route('/searchdb') 217 | def search_database(): 218 | method, value = request.args.get("method"), request.args.get("value") 219 | response = None 220 | with shelve.open("clients") as clients: 221 | if method == "login": 222 | for key, data in clients.items(): 223 | if data.get("login", None) == value: 224 | response = data.update({"key": key}) 225 | break 226 | elif method == "paymentid": 227 | for key, data in clients.items(): 228 | for paymentid in data["payments"]: 229 | if paymentid == value: 230 | response = data.update({"key": key}) 231 | break 232 | elif method == "key": 233 | response = clients[value] 234 | 235 | return jsonify(response), 200 236 | 237 | 238 | def check_catalogue_key(catalogue_key): 239 | expire_date = requests.get('http://steamkeys.ovh/get_time.php?key=%s' % catalogue_key).text 240 | if not expire_date: 241 | return False 242 | datetime_obj = datetime.datetime.strptime(expire_date, '%Y-%m-%d') 243 | if datetime.datetime.now() > datetime_obj: 244 | return False 245 | 246 | return True 247 | 248 | 249 | def get_city_from_ip(ip_address): 250 | try: 251 | resp = requests.get('http://ip-api.com/json/%s' % ip_address).json() 252 | except requests.exceptions.ProxyError: 253 | return 'Unknown' 254 | return resp['city'] 255 | 256 | 257 | def update_database(data, db, key): 258 | db[key] = data 259 | logger.info('VALID KEY. Added to the database: %s\n', data) 260 | 261 | 262 | def check_device(data, db_data, ip): 263 | if data['uid'] != db_data['uid']: 264 | logger.warning('UID is different (%s). The request has been declined: %s\n', data['uid'], db_data) 265 | return False 266 | 267 | stored_ip, stored_city = db_data['ip'] 268 | if ip != stored_ip: 269 | city = get_city_from_ip(ip) 270 | if city != stored_city: 271 | logger.warning('The ip and the city are different (%s, %s). ' 272 | 'Data from database: %s', ip, city, db_data) 273 | logger.warning('IPs are different: %s-%s', ip, stored_ip) 274 | 275 | logger.info('The device has been authorized successfully: %s\n', db_data) 276 | return True 277 | -------------------------------------------------------------------------------- /webserver/website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Steam Auto Authenticator 5 | 6 | 7 | 8 | 9 | 10 | 11 | 200 | 201 | 202 | 203 | 214 | 215 |
216 |

Steam Auto Athenticator

217 |

Автоматизированное решение для твоей коммерческой деятельности в Steam

218 |
219 | 220 |
221 |
222 |
223 |

224 | Изначально, авторегистратор аккаунтов Steam делался для личных нужд. Я использовал его для 225 | регистрации новых, личных аккаунтов Steam, которые использовались для закупки гифтов. 226 | Больше я не переживал за то, что мои ценности на аккаунтах будут 227 | украдены из-за купленных у кого-то саморегов. К тому же, я экономил деньги. 228 |

229 |

230 | Позже, я обнаружил потребность в авторегере Steam у фермеров карточек и вещей. Несколько месяцев 231 | я упорно работал над тем, чтобы придать ей удобный интерфейс, а также наладить бесперебойную и непрерывную регистрацию и привязку. 232 |

233 |
234 | 235 |
236 |
237 | 238 |
239 |
240 |
241 | 242 |
243 |

Инструменты:

244 |
245 |
246 |
247 | 248 |

Регистрация аккаунтов Steam

249 |

250 | Регистрирует новые аккаунты Steam в многопоточном режиме. Скорость регистрации - от 2000 до 3000 аккаунтов в час. 251 | Капчи решаются с помощью сервиса rucaptcha.com 252 |

253 |
254 |
255 | 256 |

Привязка Mobile Guard к аккаунтам Steam

257 |

258 | Привязка осуществляется как к уже существующим аккаунтам, так и к аккаунтам, зарегистрированным самой программой. 259 | На выходе генерируются .maFile файлы. 260 | Номера заказываются у сервиса onlinesim.ru

261 |

262 |
263 |
264 |
265 |

266 | 267 |
268 |

Достоинства:

269 |
270 |
271 |

Автоматическая загрузка аккаунтов в Steam Desktop Authenticator.

272 |

Возможность автоматического подтверждения почт во время регистрации аккаунтов, при условии, что используется временная почта.

273 |

Программа успевает привязать до 30 аккаунтов к 1 номеру.

274 |

При регистрации новых аккаунтов активируется профиль и открывается инвентарь.

275 |

Максимально отлаженный алгоритм для беспрерывной работы (можете оставить на ночь, программа будет работать без перебоев).

276 |

Удобный графический интерфейс.

277 |
278 |
279 |
280 | 281 |
282 |
283 |
284 |

О разработчике программы


285 |

286 | Программист-самоучка. Пишу на Python. С коммерческой стороной платформы Steam 287 | знаком несколько лет. Основное направление - автоматизация рутинных процессов внутри Steam. 288 |

289 |
290 |
291 |
292 | 293 |
294 |

295 | Цена - 4949 рублей. 296 |

297 |
298 | 299 |
300 |

Контакты для покупки

301 |
302 |
303 |

Telegram

304 |

Telegram Группа

305 | 306 |
307 |
308 |
309 | 310 | 346 | 347 | 348 | 349 | -------------------------------------------------------------------------------- /steampy/client.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import time 3 | import json 4 | import logging 5 | from typing import Dict, List 6 | 7 | import requests 8 | from bs4 import BeautifulSoup, NavigableString, Tag 9 | 10 | from steampy import guard 11 | from steampy.confirmation import ConfirmationExecutor 12 | from steampy.login import LoginExecutor, InvalidCredentials 13 | from steampy.utils import merge_items_with_descriptions_from_inventory, GameOptions, \ 14 | steam_id_to_account_id, merge_items_with_descriptions_from_offers, get_description_key, \ 15 | merge_items_with_descriptions_from_offer 16 | 17 | logger = logging.getLogger('__main__') 18 | 19 | 20 | class Currency(enum.IntEnum): 21 | USD = 1 22 | GBP = 2 23 | EURO = 3 24 | CHF = 4 25 | 26 | 27 | class Asset: 28 | def __init__(self, asset_id: str, game: GameOptions, amount: int = 1) -> None: 29 | self.asset_id = asset_id 30 | self.game = game 31 | self.amount = amount 32 | 33 | def to_dict(self): 34 | return { 35 | 'appid': int(self.game.app_id), 36 | 'contextid': self.game.context_id, 37 | 'amount': self.amount, 38 | 'assetid': self.asset_id 39 | } 40 | 41 | 42 | class TradeOfferState(enum.IntEnum): 43 | Invalid = 1 44 | Active = 2 45 | Accepted = 3 46 | Countered = 4 47 | Expired = 5 48 | Canceled = 6 49 | Declined = 7 50 | InvalidItems = 8 51 | ConfirmationNeed = 9 52 | CanceledBySecondaryFactor = 10 53 | StateInEscrow = 11 54 | 55 | 56 | def login_required(func): 57 | def func_wrapper(self, *args, **kwargs): 58 | if not self.isLoggedIn: 59 | raise LoginRequired('Use login method first') 60 | else: 61 | return func(*args, **kwargs) 62 | 63 | return func_wrapper 64 | 65 | 66 | class LoginRequired(Exception): 67 | pass 68 | 69 | 70 | class SteamClient: 71 | API_URL = "https://api.steampowered.com" 72 | COMMUNITY_URL = "https://steamcommunity.com" 73 | BROWSER_HEADERS = { 74 | 'User-Agent': ('Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) ' 75 | 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Mobile Safari/537.36'), 76 | 'Accept-Language': 'q=0.8,en-US;q=0.6,en;q=0.4'} 77 | MARKET_CURRENCIES = {'kr': 'Norwegian Krone', 'pуб.': 'Russian Ruble'} 78 | 79 | def __init__(self, api_key=None): 80 | self._api_key = api_key 81 | self._session = requests.Session() 82 | self.isLoggedIn = False 83 | self.was_login_executed = False 84 | self.mafile = dict() 85 | self.login_name: str = "" 86 | self.password = None 87 | self.oauth = None 88 | self.steamid = None 89 | 90 | @property 91 | def session(self): 92 | return self._session 93 | 94 | def _fetch_shared_secret(self, mafile): 95 | shared_secret = None 96 | if mafile is not None: 97 | if isinstance(mafile, str): # if json 98 | self.mafile = guard.load_steam_guard(mafile) 99 | else: 100 | self.mafile = mafile 101 | shared_secret = self.mafile['shared_secret'] 102 | return shared_secret 103 | 104 | def login(self, username: str, password: str, mafile=None, 105 | email=None, email_passwd=None, captcha_gid='-1', captcha_text=''): 106 | shared_secret = self._fetch_shared_secret(mafile) 107 | self._session.headers.update(self.BROWSER_HEADERS) 108 | login_response = LoginExecutor(username, password, shared_secret, 109 | self._session, email, email_passwd, captcha_gid, captcha_text).login() 110 | try: 111 | self.steamid = login_response['transfer_parameters']['steamid'] 112 | except KeyError as err: 113 | logger.info("%s: %s", err, login_response) 114 | # get steamcommunity cookies 115 | # self._session.get('https://steamcommunity.com/market/') 116 | 117 | # set english language 118 | self._session.post('https://steamcommunity.com/actions/SetLanguage/', 119 | data={'language': 'english', 120 | 'sessionid': self.get_session_id()}) 121 | 122 | self.isLoggedIn = True 123 | self.login_name = username 124 | self.password = password 125 | 126 | return login_response 127 | 128 | def mobile_login(self, username, password, mafile=None, 129 | email=None, email_passwd=None, captcha_gid='-1', captcha_text=''): 130 | shared_secret = self._fetch_shared_secret(mafile) 131 | self._session.headers.update(self.BROWSER_HEADERS) 132 | self._session.cookies.set('mobileClientVersion', '0 (2.1.3)') 133 | self._session.cookies.set('Steam_Language', 'english') 134 | self._session.cookies.set('mobileClient', 'android') 135 | login_response = LoginExecutor(username, password, shared_secret, 136 | self._session, email, email_passwd, captcha_gid, captcha_text).mobile_login() 137 | oauth = login_response.get('oauth', None) 138 | if oauth: 139 | self.oauth = json.loads(oauth) 140 | self.steamid = self.oauth['steamid'] 141 | 142 | # get steamcommunity cookies 143 | # self._session.get('http://steamcommunity.com/market/') 144 | 145 | # set english language 146 | del self._session.cookies['Steam_Language'] 147 | self._session.post('https://steamcommunity.com/actions/SetLanguage/', 148 | data={'language': 'english', 149 | 'sessionid': self.get_session_id()}) 150 | 151 | self.isLoggedIn = True 152 | self.login_name = username 153 | self.password = password 154 | 155 | return login_response 156 | 157 | @login_required 158 | def logout(self) -> None: 159 | url = LoginExecutor.STORE_URL + '/logout/' 160 | params = {'sessionid': self.get_session_id()} 161 | self._session.post(url, params) 162 | if self.is_session_alive(): 163 | raise Exception("Logout unsuccessful") 164 | self.was_login_executed = False 165 | 166 | @login_required 167 | def is_session_alive(self): 168 | main_page_response = self._session.get(self.COMMUNITY_URL) 169 | return self.login_name in main_page_response.text 170 | 171 | def api_call(self, request_method: str, interface: str, api_method: str, version: str, 172 | params: dict = {}) -> requests.Response: 173 | url = '/'.join([self.API_URL, interface, api_method, version]) 174 | attempts = 0 175 | response = None 176 | while attempts < 3: 177 | try: 178 | if request_method == 'GET': 179 | response = requests.get(url, params=params, timeout=60).json() 180 | else: 181 | response = requests.post(url, data=params, timeout=60).json() 182 | break 183 | except json.decoder.JSONDecodeError as err: 184 | print(err) 185 | attempts += 1 186 | if not response: 187 | raise Exception('The API server is unreachable') 188 | if self.is_invalid_api_key(response): 189 | raise InvalidCredentials('Invalid API key') 190 | return response 191 | 192 | @staticmethod 193 | def is_invalid_api_key(response: requests.Response) -> bool: 194 | msg = 'Access is denied. Retrying will not help. Please verify your
key=
parameter' 195 | return msg in str(response) 196 | 197 | @login_required 198 | def get_my_inventory(self, game: GameOptions, merge: bool = True) -> dict: 199 | url = self.COMMUNITY_URL + '/my/inventory/json/' + \ 200 | game.app_id + '/' + \ 201 | game.context_id 202 | result = {} 203 | start = 0 204 | more = True 205 | while more: 206 | try: 207 | response_dict = self._session.get(url, params={'start': start}, timeout=60).json() 208 | if not response_dict['success']: 209 | logger.info("No items found for appid %s: %s", game.app_id, response_dict) 210 | return result 211 | more = response_dict['more'] 212 | if merge: 213 | result.update(merge_items_with_descriptions_from_inventory(response_dict, game)) 214 | else: 215 | result.update(response_dict['rgInventory']) 216 | except (json.decoder.JSONDecodeError, KeyError, TypeError) as err: 217 | logger.error('%s error while getting my inventory', err) 218 | time.sleep(5) 219 | continue 220 | 221 | start += 2000 222 | 223 | return result 224 | 225 | @login_required 226 | def get_partner_inventory(self, partner_steam_id: str, game: GameOptions, merge: bool = True) -> dict: 227 | params = {'sessionid': self.get_session_id(), 228 | 'partner': partner_steam_id, 229 | 'appid': int(game.app_id), 230 | 'contextid': game.context_id} 231 | partner_account_id = steam_id_to_account_id(partner_steam_id) 232 | headers = {'X-Requested-With': 'XMLHttpRequest', 233 | 'Referer': self.COMMUNITY_URL + '/tradeoffer/new/?partner=' + partner_account_id, 234 | 'X-Prototype-Version': '1.7'} 235 | response_dict = self._session.get(self.COMMUNITY_URL + '/tradeoffer/new/partnerinventory/', 236 | params=params, 237 | headers=headers).json() 238 | if merge: 239 | return merge_items_with_descriptions_from_inventory(response_dict, game) 240 | return response_dict 241 | 242 | def get_session_id(self) -> str: 243 | return self._session.cookies.get('sessionid', domain='steamcommunity.com') 244 | 245 | def get_trade_offers_summary(self) -> requests.Response: 246 | params = {'key': self._api_key} 247 | return self.api_call('GET', 'IEconService', 'GetTradeOffersSummary', 'v1', params) 248 | 249 | def get_trade_offers(self, merge, get_descriptions=1): 250 | params = {'key': self._api_key, 251 | 'get_sent_offers': 1, 252 | 'get_received_offers': 1, 253 | 'get_descriptions': get_descriptions, 254 | 'language': 'english', 255 | 'active_only': 1, 256 | 'historical_only': 0, 257 | 'time_historical_cutoff': ''} 258 | response = self.api_call('GET', 'IEconService', 'GetTradeOffers', 'v1', params).json() 259 | response = self._filter_non_active_offers(response) 260 | if merge: 261 | response = merge_items_with_descriptions_from_offers(response) 262 | return response 263 | 264 | @staticmethod 265 | def _filter_non_active_offers(offers_response): 266 | offers_received = offers_response['response'].get('trade_offers_received', []) 267 | offers_sent = offers_response['response'].get('trade_offers_sent', []) 268 | offers_response['response']['trade_offers_received'] = list( 269 | filter(lambda offer: offer['trade_offer_state'] == TradeOfferState.Active, offers_received)) 270 | offers_response['response']['trade_offers_sent'] = list( 271 | filter(lambda offer: offer['trade_offer_state'] == TradeOfferState.Active, offers_sent)) 272 | return offers_response 273 | 274 | def get_trade_offer(self, trade_offer_id: str, merge: bool = True) -> dict: 275 | params = {'key': self._api_key, 276 | 'tradeofferid': trade_offer_id, 277 | 'language': 'english'} 278 | response = self.api_call('GET', 'IEconService', 'GetTradeOffer', 'v1', params).json() 279 | if merge: 280 | descriptions = {get_description_key(offer): offer for offer in response['response']['descriptions']} 281 | offer = response['response']['offer'] 282 | response['response']['offer'] = merge_items_with_descriptions_from_offer(offer, descriptions) 283 | return response 284 | 285 | @login_required 286 | def accept_trade_offer(self, trade_offer_id: str, partner: str): 287 | session_id = self.get_session_id() 288 | accept_url = self.COMMUNITY_URL + '/tradeoffer/' + trade_offer_id + '/accept' 289 | params = {'sessionid': session_id, 290 | 'tradeofferid': trade_offer_id, 291 | 'serverid': '1', 292 | 'partner': partner, 293 | 'captcha': ''} 294 | headers = {'Referer': self._get_trade_offer_url(trade_offer_id)} 295 | try: 296 | response = self._session.post(accept_url, data=params, headers=headers, timeout=60).json() 297 | if response.get('needs_mobile_confirmation', False): 298 | return self._confirm_transaction(trade_offer_id) 299 | except (AttributeError, json.decoder.JSONDecodeError) as err: 300 | logger.error("%s %s", err, accept_url) 301 | return None 302 | 303 | return response 304 | 305 | def _get_trade_offer_url(self, trade_offer_id: str) -> str: 306 | return self.COMMUNITY_URL + '/tradeoffer/' + trade_offer_id 307 | 308 | def _confirm_transaction(self, trade_offer_id: str) -> dict: 309 | confirmation_executor = ConfirmationExecutor(trade_offer_id, 310 | self.mafile['identity_secret'], 311 | str(self.mafile['Session']['SteamID']), 312 | self._session) 313 | return confirmation_executor.send_trade_allow_request() 314 | 315 | def confirm_transactions(self): 316 | confirmation_executor = ConfirmationExecutor('', self.mafile['identity_secret'], 317 | str(self.mafile['Session']['SteamID']), 318 | self._session) 319 | return confirmation_executor.send_markettrans_allow_request() 320 | 321 | def decline_trade_offer(self, trade_offer_id: str) -> dict: 322 | params = {'key': self._api_key, 323 | 'tradeofferid': trade_offer_id} 324 | return self.api_call('POST', 'IEconService', 'DeclineTradeOffer', 'v1', params).json() 325 | 326 | def cancel_trade_offer(self, trade_offer_id: str) -> dict: 327 | params = {'key': self._api_key, 328 | 'tradeofferid': trade_offer_id} 329 | return self.api_call('POST', 'IEconService', 'CancelTradeOffer', 'v1', params).json() 330 | 331 | @login_required 332 | def make_offer(self, token: str, items_from_me: List[Asset], items_from_them: List[Asset], partner_steam_id: str, 333 | message: str = '') -> dict: 334 | offer = { 335 | 'newversion': True, 336 | 'version': 4, 337 | 'me': { 338 | 'assets': [asset.to_dict() for asset in items_from_me], 339 | 'currency': [], 340 | 'ready': False 341 | }, 342 | 'them': { 343 | 'assets': [asset.to_dict() for asset in items_from_them], 344 | 'currency': [], 345 | 'ready': False 346 | } 347 | } 348 | 349 | session_id = self.get_session_id() 350 | url = self.COMMUNITY_URL + '/tradeoffer/new/send' 351 | server_id = 1 352 | params = { 353 | 'sessionid': session_id, 354 | 'serverid': server_id, 355 | 'partner': partner_steam_id, 356 | 'tradeoffermessage': message, 357 | 'json_tradeoffer': json.dumps(offer), 358 | 'captcha': '', 359 | 'trade_offer_create_params': json.dumps({"trade_offer_access_token": token}) 360 | } 361 | partner_account_id = steam_id_to_account_id(partner_steam_id) 362 | headers = {'Referer': self.COMMUNITY_URL + '/tradeoffer/new/?partner=' + partner_account_id + '&token=' + token, 363 | 'Origin': self.COMMUNITY_URL} 364 | while True: 365 | try: 366 | response = self._session.post(url, data=params, headers=headers, timeout=60).json() 367 | break 368 | except json.decoder.JSONDecodeError as err: 369 | logger.error("%s %s", err, url) 370 | time.sleep(3) 371 | 372 | if response.get('needs_mobile_confirmation'): 373 | response = self._confirm_transaction(response['tradeofferid']) 374 | return response 375 | 376 | def fetch_price(self, item_hash_name: str, game: GameOptions, currency: int = Currency.USD) -> dict: 377 | url = self.COMMUNITY_URL + '/market/priceoverview/' 378 | params = {'country': 'PL', 379 | 'currency': currency, 380 | 'appid': game.app_id, 381 | 'market_hash_name': item_hash_name} 382 | return self._session.get(url, params=params, timeout=60).json() 383 | 384 | def create_market_listing(self, assetid, price, appid, context_id=2): 385 | def send_marketform(): 386 | store_sessionid = self._session.cookies.get('sessionid', domain='store.steampowered.com') 387 | data = { 388 | 'sessionid': store_sessionid, 389 | 'full_name': self.login_name, 390 | 'permanent_address1': 'City', 391 | 'permanent_state': '', 392 | 'permanent_address2': '', 393 | 'permanent_postalcode': '', 394 | 'mailing_address1': '', 395 | 'mailing_address2': '', 396 | 'mailing_city': '', 397 | 'mailing_state': '', 398 | 'mailing_postalcode': '', 399 | 'permanent_city': 'City', 400 | 'permanent_country': 'RU', 401 | 'mailing_state_select': 'AE', 402 | 'mailing_country': 'RU', 403 | 'full_name_signed': self.login_name 404 | } 405 | store_headers = { 406 | 'Host': 'store.steampowered.com', 407 | 'Origin': 'https://store.steampowered.com', 408 | 'Referer': 'https://store.steampowered.com/account/forms/6050w/' 409 | } 410 | self._session.get('https://store.steampowered.com/account/forms/6050w/') 411 | self._session.post('https://store.steampowered.com/account/forms/submit_6050w_non_us/', 412 | data=data, headers=store_headers) 413 | print('marketform sent') 414 | 415 | sessionid = self.get_session_id() 416 | headers = { 417 | 'Origin': 'https://steamcommunity.com', 418 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 419 | 'Referer': 'https://steamcommunity.com/my/inventory/', 420 | 'Accept-Encoding': 'gzip, deflate', 421 | "X-Requested-With": "XMLHttpRequest" 422 | } 423 | payload = { 424 | 'sessionid': sessionid, 425 | 'appid': appid, 426 | 'contextid': context_id, 427 | 'assetid': assetid, 428 | 'amount': 1, 429 | 'price': price 430 | } 431 | 432 | response = None 433 | while True: 434 | try: 435 | response = self._session.post('https://steamcommunity.com/market/sellitem/', 436 | data=payload, headers=headers, timeout=10).json() 437 | except json.decoder.JSONDecodeError as err: 438 | logger.error('json decode error while putting item on sale: %s', err) 439 | continue 440 | error_msg = response.get('message', None) 441 | if error_msg: 442 | logger.error(str(error_msg)) 443 | if 'You are not allowed to sell more than 200 items' in error_msg: 444 | send_marketform() 445 | time.sleep(900) 446 | continue 447 | elif 'We were unable to contact' in error_msg: 448 | continue 449 | break 450 | 451 | return response 452 | 453 | 454 | class SevenDaysHoldException(Exception): 455 | pass 456 | -------------------------------------------------------------------------------- /controller.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import string 4 | import random 5 | import re 6 | import json 7 | import io 8 | import logging 9 | import shelve 10 | import imaplib 11 | 12 | from requests.exceptions import Timeout, ConnectionError, ProxyError 13 | from python_anticaptcha import AnticaptchaClient, ImageToTextTask, NoCaptchaTaskProxylessTask 14 | 15 | from steampy.client import SteamClient 16 | from steampy.login import CaptchaRequired 17 | from steampy.utils import convert_edomain_to_imap 18 | from steampy import guard 19 | 20 | from enums import CaptchaService, SelectionType 21 | 22 | logger = logging.getLogger('__main__') 23 | 24 | 25 | class SteamAuthError(Exception): pass 26 | 27 | 28 | class RuCaptchaError(Exception): pass 29 | 30 | 31 | class LimitReached(Exception): pass 32 | 33 | 34 | class InvalidEmail(Exception): pass 35 | 36 | 37 | class AddPhoneError(Exception): pass 38 | 39 | 40 | class Controller: 41 | 42 | def __init__(self, client): 43 | self.client = client 44 | self.failed_captchas_counter = 0 45 | self.sucessfull_captchas_counter = 0 46 | self.captchas_expenses_total = 0 47 | 48 | self.captcha_service = self.client.captcha_service_type.get() 49 | 50 | self.counters_db = shelve.open("database/tmplcounters", writeback=True) 51 | 52 | for key in ("login_counters", "password_counters", "nickname_counters"): 53 | if self.counters_db.get(key) is None: 54 | self.counters_db[key] = {} 55 | 56 | def set_captcha_service(self): 57 | api_key = self.client.captcha_api_key.get() 58 | captcha_host = self.client.captcha_host.get() 59 | if self.client.captcha_service_type.get() == CaptchaService.RuCaptcha: 60 | self.captcha_service = RuCaptcha(api_key, captcha_host) 61 | elif self.client.captcha_service_type.get() == CaptchaService.AntiCaptcha: 62 | self.captcha_service = AntiCaptcha(api_key, captcha_host) 63 | 64 | @staticmethod 65 | def request_post(session, url, data=None, headers=None, timeout=30): 66 | if headers is None: 67 | headers = {} 68 | if data is None: 69 | data = {} 70 | while True: 71 | try: 72 | resp = session.post(url, data=data, timeout=timeout, headers=headers, attempts=3).json() 73 | return resp 74 | except json.decoder.JSONDecodeError as err: 75 | logger.error('%s %s', err, url) 76 | except (Timeout, ConnectionError, ProxyError) as err: 77 | logger.error('%s %s', err, url) 78 | if session.proxies: 79 | raise err 80 | 81 | @staticmethod 82 | def request_get(session, url, headers=None, params=None, timeout=30, is_json=False): 83 | if params is None: 84 | params = {} 85 | if headers is None: 86 | headers = {} 87 | while True: 88 | try: 89 | resp = session.get(url, headers=headers, params=params, timeout=timeout, attempts=3) 90 | if is_json: 91 | resp = resp.json() 92 | return resp 93 | except json.decoder.JSONDecodeError as err: 94 | logger.error('%s %s', err, url) 95 | except (Timeout, ConnectionError, ProxyError) as err: 96 | logger.error('%s %s', err, url) 97 | if session.proxies: 98 | raise err 99 | 100 | def login(self, login_name, password, proxy=None, email=None, email_passwd=None, pass_login_captcha=False): 101 | steam_client = SteamClient() 102 | if proxy is not None: 103 | proxy_uri = self.build_uri(proxy) 104 | proxy = { 105 | 'http': proxy_uri, 106 | 'https': proxy_uri, 107 | } 108 | steam_client.session.proxies.update(proxy) 109 | captcha_gid, captcha_text = '-1', '' 110 | while True: 111 | try: 112 | resp = steam_client.login(login_name, password, None, email, email_passwd, captcha_gid, captcha_text) 113 | break 114 | except CaptchaRequired as err: 115 | if pass_login_captcha: 116 | raise err 117 | captcha_gid = str(err) 118 | captcha_id = self.generate_captcha(steam_client.session, captcha_gid, 'COMMUNITY') 119 | captcha_text = self.resolve_captcha(captcha_id) 120 | self.failed_captchas_counter += 1 121 | self.client.captchas_failed_stat.set("Капч не удалось решить: %d" % self.failed_captchas_counter) 122 | 123 | self.sucessfull_captchas_counter += 1 124 | self.client.captchas_resolved_stat.set("Капч решено успешно: %d" % self.sucessfull_captchas_counter) 125 | 126 | resp_message = resp.get('message', '') 127 | 128 | if 'name or password that you have entered is incorrect' in resp_message: 129 | raise SteamAuthError('Неверный логин или пароль: ' + login_name) 130 | 131 | if resp['requires_twofactor']: 132 | raise SteamAuthError('К аккаунту уже привязан Guard: ' + login_name) 133 | 134 | if resp.get('emailauth_needed', None): 135 | raise SteamAuthError('К аккаунту привязан Mail Guard. ' 136 | 'Почта и пароль от него не предоставлены') 137 | 138 | return steam_client 139 | 140 | def mobile_login(self, login_name, password, proxy=None, email=None, email_passwd=None, pass_login_captcha=False): 141 | steam_client = SteamClient() 142 | if proxy: 143 | proxy_uri = self.build_uri(proxy) 144 | proxy = { 145 | 'http': proxy_uri, 146 | 'https': proxy_uri, 147 | } 148 | steam_client.session.proxies.update(proxy) 149 | captcha_gid, captcha_text = '-1', '' 150 | while True: 151 | try: 152 | resp = steam_client.mobile_login(login_name, password, None, email, email_passwd, captcha_gid, 153 | captcha_text) 154 | break 155 | except CaptchaRequired as err: 156 | if pass_login_captcha: 157 | raise err 158 | captcha_gid = str(err) 159 | captcha_id = self.generate_captcha(steam_client.session, captcha_gid, 'COMMUNITY') 160 | captcha_text = self.resolve_captcha(captcha_id) 161 | 162 | resp_message = resp.get('message', '') 163 | 164 | if 'name or password that you have entered is incorrect' in resp_message: 165 | raise SteamAuthError('Неверный логин или пароль: ' + login_name) 166 | 167 | if resp['requires_twofactor']: 168 | raise SteamAuthError('К аккаунту уже привязан Guard: ' + login_name) 169 | 170 | if resp.get('emailauth_needed', None): 171 | raise SteamAuthError('К аккаунту привязан Mail Guard. ' 172 | 'Почта и пароль от него не предоставлены') 173 | 174 | if not steam_client.oauth: 175 | error = 'Не удалось залогиниться в аккаунт: {}:{}'.format( 176 | login_name, password) 177 | raise SteamAuthError(error) 178 | 179 | steam_client.session.get("https://store.steampowered.com") # get store cookies 180 | 181 | return steam_client 182 | 183 | def validate_phone(self, steam_client, phone_num): 184 | sessionid = steam_client.session.cookies.get( 185 | 'sessionid', domain='store.steampowered.com') 186 | url = "https://store.steampowered.com/phone/validate" 187 | data = {"sessionID": sessionid, "phoneNumber": phone_num} 188 | response = self.request_post(steam_client.session, 189 | url, data=data) 190 | if not response.get("is_valid", True): 191 | raise AddPhoneError("Недопустимый номер") 192 | 193 | def addphone_request(self, steam_client, phone_num): 194 | sessionid = steam_client.session.cookies.get( 195 | 'sessionid', domain='steamcommunity.com') 196 | data = { 197 | 'op': 'add_phone_number', 198 | 'arg': phone_num, 199 | 'sessionid': sessionid 200 | } 201 | response = self.request_post(steam_client.session, 202 | 'https://steamcommunity.com/steamguard/phoneajax', data=data) 203 | logger.info(response) 204 | return response["success"] 205 | 206 | def fetch_email_code(self, email, email_password, steam_client): 207 | server = self.authorize_email(email, email_password) 208 | attempts = 0 209 | success = False 210 | while attempts < 5: 211 | attempts += 1 212 | result, data = server.uid('search', '', 'ALL') 213 | uid = data[0].split()[-1] 214 | result, data = server.uid("fetch", uid, '(UID BODY[TEXT])') 215 | try: 216 | mail = data[0][1].decode('utf-8') 217 | link = re.search(r'https://.+ConfirmEmailForAdd.+?"', mail) 218 | if link is not None: 219 | link = link.group().rstrip('"') 220 | steam_client.session.get(link) 221 | success = True 222 | break 223 | except AttributeError: 224 | time.sleep(5) 225 | continue 226 | server.close() 227 | return success 228 | 229 | def email_confirmation(self, steam_client): 230 | sessionid = steam_client.session.cookies.get( 231 | 'sessionid', domain='steamcommunity.com') 232 | data = { 233 | 'op': 'email_confirmation', 234 | 'arg': None, 235 | 'sessionid': sessionid 236 | } 237 | response = self.request_post(steam_client.session, 238 | 'https://steamcommunity.com/steamguard/phoneajax', data=data) 239 | return response["success"] 240 | 241 | def is_phone_attached(self, steam_client): 242 | sessionid = steam_client.session.cookies.get( 243 | 'sessionid', domain='steamcommunity.com') 244 | data = { 245 | 'op': 'has_phone', 246 | 'arg': None, 247 | 'sessionid': sessionid 248 | } 249 | response = self.request_post(steam_client.session, 250 | 'https://steamcommunity.com/steamguard/phoneajax', data=data) 251 | 252 | return response['has_phone'] 253 | 254 | def checksms_request(self, steam_client, sms_code): 255 | sessionid = steam_client.session.cookies.get( 256 | 'sessionid', domain='steamcommunity.com') 257 | data = { 258 | 'op': 'check_sms_code', 259 | 'arg': sms_code, 260 | 'sessionid': sessionid 261 | } 262 | response = self.request_post( 263 | steam_client.session, 'https://steamcommunity.com/steamguard/phoneajax', data=data) 264 | logger.info(response) 265 | return response["success"] 266 | 267 | def add_authenticator_request(self, steam_client): 268 | device_id = guard.generate_device_id(steam_client.oauth['steamid']) 269 | attempts = 0 270 | mobguard_data = None 271 | while attempts < 3: 272 | try: 273 | mobguard_data = self.request_post(steam_client.session, 274 | 'https://api.steampowered.com/ITwoFactorService/AddAuthenticator/v0001/', 275 | data={ 276 | "access_token": steam_client.oauth['oauth_token'], 277 | "steamid": steam_client.oauth['steamid'], 278 | "authenticator_type": 1, 279 | "device_identifier": device_id, 280 | "sms_phone_id": 1 281 | })['response'] 282 | except json.decoder.JSONDecodeError: 283 | time.sleep(3) 284 | attempts += 1 285 | continue 286 | logger.info(str(mobguard_data)) 287 | if mobguard_data['status'] not in (1, 2): 288 | time.sleep(5) 289 | attempts += 1 290 | continue 291 | if not mobguard_data.get("shared_secret", None): 292 | time.sleep(5) 293 | attempts += 1 294 | continue 295 | break 296 | 297 | if mobguard_data is None or not mobguard_data.get("shared_secret", None): 298 | raise SteamAuthError("Steam отвечает ошибкой") 299 | 300 | mobguard_data['device_id'] = device_id 301 | mobguard_data['Session'] = {} 302 | mobguard_data['Session']['WebCookie'] = None 303 | for mafile_key, resp_key in (('SteamID', 'steamid'), ('OAuthToken', 'oauth_token')): 304 | mobguard_data['Session'][mafile_key] = steam_client.oauth[resp_key] 305 | 306 | for mafile_key, resp_key in ( 307 | ('SessionID', 'sessionid'), 308 | ('SteamLogin', 'steamLogin'), 309 | ('SteamLoginSecure', 'steamLoginSecure')): 310 | try: 311 | mobguard_data['Session'][mafile_key] = steam_client.session.cookies.get( 312 | "sessionid", domain="steamcommunity.com") 313 | except KeyError: 314 | mobguard_data['Session'][mafile_key] = '' 315 | 316 | return mobguard_data 317 | 318 | def finalize_authenticator_request(self, steam_client, mobguard_data, sms_code): 319 | one_time_code = guard.generate_one_time_code(mobguard_data['shared_secret'], int(time.time())) 320 | data = { 321 | "steamid": steam_client.oauth['steamid'], 322 | "activation_code": sms_code, 323 | "access_token": steam_client.oauth['oauth_token'], 324 | 'authenticator_time': int(time.time()), 325 | 'authenticator_code': one_time_code 326 | } 327 | attempts = 0 328 | fin_resp = None 329 | while attempts < 5: 330 | try: 331 | fin_resp = self.request_post( 332 | steam_client.session, 333 | 'https://api.steampowered.com/ITwoFactorService/FinalizeAddAuthenticator/v0001/', 334 | data=data, headers={'User-Agent': 'Steam App / Android / 2.3.11 / 5379038'})['response'] 335 | except json.decoder.JSONDecodeError: 336 | logger.error("json error in the FinalizeAddAuthenticator request") 337 | time.sleep(3) 338 | attempts += 1 339 | continue 340 | logger.info(str(fin_resp)) 341 | if fin_resp['status'] not in (1, 2): 342 | time.sleep(5) 343 | attempts += 1 344 | continue 345 | break 346 | 347 | if fin_resp is None or fin_resp['status'] not in (1, 2): 348 | raise SteamAuthError("Steam отвечает ошибкой") 349 | 350 | return fin_resp['success'] 351 | 352 | @staticmethod 353 | def make_account_unlimited(mobguard_data, wallet_code, get_api_key=False): 354 | steam_client = SteamClient() 355 | steam_client.login(mobguard_data['account_name'], mobguard_data['account_password'], mobguard_data) 356 | data = { 357 | 'wallet_code': wallet_code, 358 | 'CreateFromAddress': '1', 359 | 'Address': 'Russia', 360 | 'City': 'Russia', 361 | 'Country': 'RU', 362 | 'State': '', 363 | 'PostCode': '0001' 364 | } 365 | steam_client.session.post('https://store.steampowered.com/account/validatewalletcode/', 366 | data={'wallet_code': wallet_code}) 367 | steam_client.session.post('https://store.steampowered.com/account/createwalletandcheckfunds/', 368 | data=data) 369 | steam_client.session.post('https://store.steampowered.com/account/confirmredeemwalletcode/', 370 | data={'wallet_code': wallet_code}) 371 | 372 | if get_api_key: 373 | sessionid = steam_client.session.cookies.get( 374 | 'sessionid', domain='steamcommunity.com') 375 | data = { 376 | 'domain': 'domain.com', 377 | 'agreeToTerms': 'agreed', 378 | 'sessionid': sessionid, 379 | 'Submit': 'Register' 380 | } 381 | time.sleep(10) 382 | r = steam_client.session.post('https://steamcommunity.com/dev/registerkey', data=data) 383 | key = re.search('Key: (.+)