From 7cbd44a4c5aae4ca023fa7887af83c0275e2d6d7 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Wed, 16 Nov 2016 21:43:57 +0200 Subject: [PATCH] SteamClient: added 'cli_login()' The new method simplfiest the login process from CLI. Examples and recipes are changed to use it. --- docs/user_guide.rst | 39 +++------------- recipes/1.Login/README.rst | 8 +++- recipes/1.Login/diy_one_off_login.py | 42 +++++++++++++++++ recipes/1.Login/one_off_login.py | 36 +++----------- recipes/1.Login/persistent_login.py | 46 +++++++----------- steam/client/__init__.py | 70 ++++++++++++++++++++++++++++ 6 files changed, 147 insertions(+), 94 deletions(-) create mode 100644 recipes/1.Login/diy_one_off_login.py diff --git a/docs/user_guide.rst b/docs/user_guide.rst index 15df65a..182f984 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -156,52 +156,25 @@ The library comes with some Steam client features implemented, see :doc:`api/ste CLI example ----------- -This program will prompt for user and password. -If an authentication code is required, it will additionally prompt for that. -Configuring logging will lets us see the internal interactions. +In this example, the user will be prompted for credential and once logged in will the account name. +After that we logout. .. code:: python - import logging - from getpass import getpass - logging.basicConfig(format='[%(asctime)s] %(levelname)s %(name)s: %(message)s', level=logging.DEBUG) - from steam import SteamClient from steam.enums import EResult from steam.enums.emsg import EMsg - logOnDetails = { - 'username': raw_input("Steam user: "), - 'password': getpass("Password: "), - } - client = SteamClient() - #client.verbose_debug = True - - @client.on('error') - def print_error(result): - print "Error:", EResult(result) - - @client.on('auth_code_required') - def auth_code_prompt(is_2fa, code_mismatch): - if is_2fa: - code = raw_input("Enter 2FA Code: ") - client.login(two_factor_code=code, **logOnDetails) - else: - code = raw_input("Enter Email Code: ") - client.login(auth_code=code, **logOnDetails) - - client.login(**logOnDetails) + client.cli_login() - msg, = client.wait_event(EMsg.ClientAccountInfo) + msg = client.wait_msg(EMsg.ClientAccountInfo) print "Logged on as: %s" % msg.body.persona_name print "SteamID: %s" % repr(client.steam_id) - try: - client.run_forever() - except KeyboardInterrupt: - client.logout() + client.logout() +You can find more examples at https://github.com/ValvePython/steam/tree/master/recipes Sending a message ----------------- diff --git a/recipes/1.Login/README.rst b/recipes/1.Login/README.rst index 507f35e..8211b9a 100644 --- a/recipes/1.Login/README.rst +++ b/recipes/1.Login/README.rst @@ -1,10 +1,14 @@ one_off_login.py ---------------- -Minimal example for login with prompt for email and two factor codes. -After the login, the code will print some account info and exit. +Minimal example to login into Steam, print some account info and exit. No information is persisted to disk. +diy_one_off_login.py +-------------------- + +Same as above, we make our own prompts instead of using ``cli_login()`` + persistent_login.py ------------------- diff --git a/recipes/1.Login/diy_one_off_login.py b/recipes/1.Login/diy_one_off_login.py new file mode 100644 index 0000000..cb84443 --- /dev/null +++ b/recipes/1.Login/diy_one_off_login.py @@ -0,0 +1,42 @@ +from __future__ import print_function +from getpass import getpass +from steam import SteamClient + + +print("One-off login recipe") +print("-"*20) + +LOGON_DETAILS = { + 'username': raw_input("Steam user: "), + 'password': getpass("Password: "), +} + +client = SteamClient() + +@client.on('error') +def error(result): + print("Logon result:", repr(result)) + +@client.on('auth_code_required') +def auth_code_prompt(is_2fa, mismatch): + if is_2fa: + code = raw_input("Enter 2FA Code: ") + client.login(two_factor_code=code, **LOGON_DETAILS) + else: + code = raw_input("Enter Email Code: ") + client.login(auth_code=code, **LOGON_DETAILS) + + +try: + client.login(**LOGON_DETAILS) + client.wait_event('logged_on') +except: + raise SystemExit + +print("-"*20) +print("Logged on as:", client.user.name) +print("Community profile:", client.steam_id.community_url) +print("Last logon:", client.user.last_logon) +print("Last logoff:", client.user.last_logoff) + +client.logout() diff --git a/recipes/1.Login/one_off_login.py b/recipes/1.Login/one_off_login.py index c64ad5e..5682fdb 100644 --- a/recipes/1.Login/one_off_login.py +++ b/recipes/1.Login/one_off_login.py @@ -1,47 +1,25 @@ from __future__ import print_function -from getpass import getpass -import gevent from steam import SteamClient +from steam.enums import EResult +client = SteamClient() print("One-off login recipe") print("-"*20) -logon_details = { - 'username': raw_input("Steam user: "), - 'password': getpass("Password: "), -} - - -client = SteamClient() - -@client.on('error') -def error(result): - print("Logon result:", repr(result)) +result = client.cli_login() -@client.on('auth_code_required') -def auth_code_prompt(is_2fa, mismatch): - if is_2fa: - code = raw_input("Enter 2FA Code: ") - client.login(two_factor_code=code, **logon_details) - else: - code = raw_input("Enter Email Code: ") - client.login(auth_code=code, **logon_details) +if result != EResult.OK: + print("Failed to login: %s" % repr(result)) + raise SystemExit print("-"*20) -client.login(**logon_details) - -try: - client.wait_event('logged_on') -except: - raise SystemExit +client.wait_event('logged_on') -print("-"*20) print("Logged on as:", client.user.name) print("Community profile:", client.steam_id.community_url) print("Last logon:", client.user.last_logon) print("Last logoff:", client.user.last_logoff) -gevent.idle() client.logout() diff --git a/recipes/1.Login/persistent_login.py b/recipes/1.Login/persistent_login.py index 08cbc48..078d600 100644 --- a/recipes/1.Login/persistent_login.py +++ b/recipes/1.Login/persistent_login.py @@ -1,21 +1,13 @@ import logging -import gevent -from getpass import getpass from steam import SteamClient +from steam.enums import EResult +# setup logging logging.basicConfig(format="%(asctime)s | %(message)s", level=logging.INFO) LOG = logging.getLogger() -LOG.info("Persistent logon recipe") -LOG.info("-"*30) - -LOGON_DETAILS = { - "username": raw_input("Steam user: "), - "password": getpass("Password: "), -} - client = SteamClient() -client.set_credential_location(".") +client.set_credential_location(".") # where to store sentry files and other stuff @client.on("error") def handle_error(result): @@ -26,10 +18,6 @@ def send_login(): if client.relogin_available: client.relogin() -@client.on("new_login_key") -def got_login_key(): - LOG.info("got new login key") - @client.on("connected") def handle_connected(): LOG.info("Connected to %s", client.current_server_addr) @@ -42,20 +30,9 @@ def handle_reconnect(delay): def handle_disconnect(): LOG.info("Disconnected.") - LOG.info("Reconnecting...") - client.reconnect(maxdelay=30) - -@client.on("auth_code_required") -def auth_code_prompt(is_2fa, mismatch): - if mismatch: - LOG.info("Previous code was incorrect") - - if is_2fa: - code = raw_input("Enter 2FA Code: ") - client.login(two_factor_code=code, **LOGON_DETAILS) - else: - code = raw_input("Enter Email Code: ") - client.login(auth_code=code, **LOGON_DETAILS) + if client.relogin_available: + LOG.info("Reconnecting...") + client.reconnect(maxdelay=30) @client.on("logged_on") def handle_after_logon(): @@ -68,8 +45,17 @@ def handle_after_logon(): LOG.info("Press ^C to exit") +# main bit +LOG.info("Persistent logon recipe") +LOG.info("-"*30) + try: - client.login(**LOGON_DETAILS) + result = client.cli_login() + + if result != EResult.OK: + LOG.info("Failed to login: %s" % repr(result)) + raise SystemExit + client.run_forever() except KeyboardInterrupt: if client.connected: diff --git a/steam/client/__init__.py b/steam/client/__init__.py index 39fcb01..f189b15 100644 --- a/steam/client/__init__.py +++ b/steam/client/__init__.py @@ -12,6 +12,7 @@ import os import json from time import time from io import open +from getpass import getpass import logging import gevent import gevent.monkey @@ -544,3 +545,72 @@ class SteamClient(CMClient, BuiltinBase): """ while True: gevent.sleep(300) + + def cli_login(self, username='', password=''): + """Generates CLI prompts to complete the login process + + :param username: optionally provide username + :type username: :class:`str` + :param password: optionally provide password + :type password: :class:`str` + :return: logon result, see `CMsgClientLogonResponse.eresult `_ + :rtype: :class:`.EResult` + + Example console output after calling :meth:`cli_login` + + .. code:: python + + In [5]: client.cli_login() + Steam username: myusername + Password: + Steam is down. Keep retrying? [y/n]: y + Invalid password for 'myusername'. Enter password: + Enter email code: 123 + Incorrect code. Enter email code: K6VKF + Out[5]: + """ + if not username: + username = raw_input("Username: ") + if not password: + password = getpass() + + auth_code = two_factor_code = None + prompt_for_unavailable = True + + result = self.login(username, password) + + while result in (EResult.AccountLogonDenied, EResult.InvalidLoginAuthCode, + EResult.AccountLoginDeniedNeedTwoFactor, EResult.TwoFactorCodeMismatch, + EResult.TryAnotherCM, EResult.ServiceUnavailable, + EResult.InvalidPassword, + ): + gevent.sleep(0.1) + + if result == EResult.InvalidPassword: + password = getpass("Invalid password for %s. Enter password: " % repr(username)) + + elif result in (EResult.AccountLogonDenied, EResult.InvalidLoginAuthCode): + prompt = ("Enter email code: " if result == EResult.AccountLogonDenied else + "Incorrect code. Enter email code: ") + auth_code, two_factor_code = raw_input(prompt), None + + elif result in (EResult.AccountLoginDeniedNeedTwoFactor, EResult.TwoFactorCodeMismatch): + prompt = ("Enter 2FA code: " if result == EResult.AccountLoginDeniedNeedTwoFactor else + "Incorrect code. Enter 2FA code: ") + auth_code, two_factor_code = None, raw_input(prompt) + + elif result in (EResult.TryAnotherCM, EResult.ServiceUnavailable): + if prompt_for_unavailable and result == EResult.ServiceUnavailable: + while True: + answer = raw_input("Steam is down. Keep retrying? [y/n]: ").lower() + if answer in 'yn': break + + prompt_for_unavailable = False + if answer == 'n': break + + self.reconnect(maxdelay=15) # implements reconnect throttling + + result = self.login(username, password, None, auth_code, two_factor_code) + + return result +