diff --git a/docs/api/steam.client.rst b/docs/api/steam.client.rst index 33fe6d8..30f750a 100644 --- a/docs/api/steam.client.rst +++ b/docs/api/steam.client.rst @@ -3,6 +3,7 @@ client .. automodule:: steam.client :members: SteamClient + :member-order: alphabetical :undoc-members: :inherited-members: :show-inheritance: diff --git a/steam/client/__init__.py b/steam/client/__init__.py index eb720da..7a34816 100644 --- a/steam/client/__init__.py +++ b/steam/client/__init__.py @@ -1,23 +1,6 @@ """ Implementation of Steam client based on ``gevent`` -Events -^^^^^^ - - | ``connected`` - when successful connection with a CM server is established - | ``disconnected`` - when connection is lost - | ``reconnect`` - when connect attempt is delayed, `delay` argument gives the delay in seconds - | ``channel_secured`` - after channel encryption is complete, client can attempt to login now - | ``error`` - after login failure - | ``auth_code_required`` - either email code or 2FA code is needed for login - | ``logged_on`` - after successful login, client can send messages - | ``new_login_key`` - after new login key has been received and acknowledged - | :class:`.EMsg` - all messages are emitted with their :class:`.EMsg` number - - -.. note:: - Mixins can emitter additional events. See their docs pages for details. - .. note:: Additional features are located in separate submodules. All functionality from :mod:`.builtins` is inherited by default. @@ -47,6 +30,16 @@ from steam.util import ip_from_int class SteamClient(CMClient, BuiltinBase): + EVENT_LOGGED_ON = 'logged_on' + """After successful login + """ + EVENT_AUTH_CODE_REQUIRED = 'auth_code_required' + """When either email or 2FA code is needed for login + """ + EVENT_NEW_LOGIN_KEY = 'new_login_key' + """After a new login key is accepted + """ + _cm_servers_timestamp = None # used to decide when to update CM list on disk _reconnect_backoff_c = 0 current_jobid = 0 @@ -60,8 +53,8 @@ class SteamClient(CMClient, BuiltinBase): self._LOG = logging.getLogger("SteamClient") # register listners self.on(None, self._handle_jobs) - self.on("disconnected", self._handle_disconnect) - self.on("reconnect", self._handle_disconnect) + self.on(self.EVENT_DISCONNECTED, self._handle_disconnect) + self.on(self.EVENT_RECONNECT, self._handle_disconnect) self.on(EMsg.ClientNewLoginKey, self._handle_login_key) self.on(EMsg.ClientUpdateMachineAuth, self._handle_update_machine_auth) @@ -159,7 +152,7 @@ class SteamClient(CMClient, BuiltinBase): self._reconnect_backoff_c = 0 self.logged_on = True self.set_persona(EPersonaState.Online) - self.emit("logged_on") + self.emit(self.EVENT_LOGGED_ON) return # CM kills the connection on error anyway @@ -183,7 +176,7 @@ class SteamClient(CMClient, BuiltinBase): else: code_mismatch = (result == EResult.InvalidLoginAuthCode) - self.emit("auth_code_required", is_2fa, code_mismatch) + self.emit(self.EVENT_AUTH_CODE_REQUIRED, is_2fa, code_mismatch) def _handle_login_key(self, message): resp = MsgProto(EMsg.ClientNewLoginKeyAccepted) @@ -193,7 +186,7 @@ class SteamClient(CMClient, BuiltinBase): self.send(resp) gevent.idle() self.login_key = message.body.login_key - self.emit("new_login_key") + self.emit(self.EVENT_NEW_LOGIN_KEY) def _handle_update_machine_auth(self, message): ok = self.store_sentry(self.username, message.body.bytes) @@ -432,7 +425,7 @@ class SteamClient(CMClient, BuiltinBase): .. code:: python - @steamclient.on('auth_code_required') + @steamclient.on(steamclient.EVENT_AUTH_CODE_REQUIRED) def auth_code_prompt(is_2fa, code_mismatch): if is_2fa: code = raw_input("Enter 2FA Code: ") diff --git a/steam/client/mixins/friends.py b/steam/client/mixins/friends.py index 05347f7..7e1255a 100644 --- a/steam/client/mixins/friends.py +++ b/steam/client/mixins/friends.py @@ -19,28 +19,45 @@ class SteamFriendlist(EventEmitter): You can iterate over it, check if it contains a particular steam id, or get :class:`SteamUser` for a steamid. .. note:: - persona state is not update immediatelly for new user entries + persona state is not updated immediatelly for new user entries - - Event: ``ready`` - friendlist is ready to use - - Event: ``friend_invite`` - new or existing friend invite + """ + EVENT_READY = 'ready' + """Friend list is ready for use + """ + EVENT_FRIEND_INVITE = 'friend_invite' + """New or existing friend invite :param user: steam user instance - :type user: :class:`SteamUser` - - Event: ``friend_new`` - emitted upon accepting a new friend (or being accepted) + :type user: :class:`.SteamUser` + """ + EVENT_FRIEND_NEW = 'friend_new' + """Friendship established (after being accepted, or accepting) :param user: steam user instance - :type user: :class:`SteamUser` + :type user: :class:`.SteamUser` + """ + EVENT_FRIEND_REMOVED = 'friend_removed' + """No longer a friend (removed by either side) + :param user: steam user instance + :type user: :class:`.SteamUser` + """ + EVENT_FRIEND_ADD_RESULT = 'friend_add_result' + """Result response after adding a friend - Event: ``friend_removed`` - no longer a friend (removed by either side) + :param eresult: result + :param type: :class:`.EResult` + :param steam_id: steam id + :param type: :class:`.SteamID` + """ + EVENT_PERSONA_STATE_UPDATED = 'persona_state_updated' + """Upon persona state changes for a user :param user: steam user instance - :type user: :class:`SteamUser` - + :type user: :class:`.SteamUser` """ + ready = False #: indicates whether friend list is available def __init__(self, client, logger_name='SteamFriendList'): @@ -51,7 +68,7 @@ class SteamFriendlist(EventEmitter): self._steam.on(EMsg.ClientAddFriendResponse, self._handle_add_friend_result) self._steam.on(EMsg.ClientFriendsList, self._handle_friends_list) self._steam.on(EMsg.ClientPersonaState, self._handle_persona_state) - self._steam.on('disconnected', self._handle_disconnect) + self._steam.on(self._steam.EVENT_DISCONNECTED, self._handle_disconnect) def emit(self, event, *args): if event is not None: @@ -65,7 +82,7 @@ class SteamFriendlist(EventEmitter): def _handle_add_friend_result(self, message): eresult = EResult(message.body.eresult) steam_id = SteamID(message.body.steam_id_added) - self.emit("friend_add_result", eresult, steam_id) + self.emit(self.EVENT_FRIEND_ADD_RESULT, eresult, steam_id) def _handle_friends_list(self, message): incremental = message.body.bincremental @@ -88,15 +105,15 @@ class SteamFriendlist(EventEmitter): pstate_check.add(steamid) if rel == EFriendRelationship.RequestRecipient: - self.emit('friend_invite', suser) + self.emit(self.EVENT_FRIEND_INVITE, suser) else: oldrel = self._fr[steamid]._data['relationship'] self._fr[steamid]._data['relationship'] = rel if rel == EFriendRelationship.No: - self.emit('friend_removed', self._fr.pop(steamid)) + self.emit(self.EVENT_FRIEND_REMOVED, self._fr.pop(steamid)) elif oldrel in (2,4) and rel == EFriendRelationship.Friend: - self.emit('friend_new', self._fr[steamid]) + self.emit(self.EVENT_FRIEND_NEW, self._fr[steamid]) # request persona state for any new entries if pstate_check: @@ -107,7 +124,7 @@ class SteamFriendlist(EventEmitter): if not self.ready: self.ready = True - self.emit('ready') + self.emit(self.EVENT_READY) def _handle_persona_state(self, message): for friend in message.body.friends: @@ -147,7 +164,7 @@ class SteamFriendlist(EventEmitter): When someone sends you an invite, use this method to accept it. :param steamid_or_accountname_or_email: steamid, account name, or email - :type steamid_or_accountname_or_email: :class:`int`, :class:`steam.steamid.SteamID`, :class:`SteamUser`, :class:`str` + :type steamid_or_accountname_or_email: :class:`int`, :class:`.SteamID`, :class:`SteamUser`, :class:`str` .. note:: Adding by email doesn't not work. It's only mentioned for the sake of completeness. @@ -166,7 +183,7 @@ class SteamFriendlist(EventEmitter): Remove a friend :param steamid: their steamid - :type steamid: :class:`int`, :class:`steam.steamid.SteamID`, :class:`SteamUser` + :type steamid: :class:`int`, :class:`.SteamID`, :class:`SteamUser` """ m = MsgProto(EMsg.ClientRemoveFriend) m.body.friendid = steamid @@ -193,7 +210,7 @@ class SteamUser(intBase): def steamid(self): """SteamID instance - :rtype: :class:`steam.steamid.SteamID` + :rtype: :class:`.SteamID` """ return SteamID(int(self)) @@ -201,7 +218,7 @@ class SteamUser(intBase): def relationship(self): """Current relationship with the steam user - :rtype: :class:`steam.enums.common.EFriendRelationship` + :rtype: :class:`.EFriendRelationship` """ return self._data['relationship'] @@ -239,7 +256,7 @@ class SteamUser(intBase): def state(self): """State of the steam user - :rtype: :class:`steam.enums.common.EPersonaState` + :rtype: :class:`.EPersonaState` """ state = self.get_ps('persona_state') if state: diff --git a/steam/core/cm.py b/steam/core/cm.py index af900ac..d975360 100644 --- a/steam/core/cm.py +++ b/steam/core/cm.py @@ -30,11 +30,36 @@ class CMClient(EventEmitter): their :class:`steam.enums.emsg.EMsg` as event identifier """ - TCP = 0 #: TCP protocol enum - UDP = 1 #: UDP protocol enum + EVENT_CONNECTED = 'connected' + """Connection establed to CM server + """ + EVENT_DISCONNECTED = 'disconnected' + """Connection closed + """ + EVENT_RECONNECT = 'reconnect' + """Delayed connect + + :param delay: delay in seconds + :type delay: int + """ + EVENT_CHANNEL_SECURED = 'channel_secured' + """After successful completion of encryption handshake + """ + EVENT_ERROR = 'error' + """When login is denied + + :param eresult: reason + :type eresult: :class:`.EResult` + """ + EVENT_EMSG = 0 + """All incoming messages are emitted with their :class:`.EMsg` number. + """ + + PROTOCOL_TCP = 0 #: TCP protocol enum + PROTOCOL_UDP = 1 #: UDP protocol enum verbose_debug = False #: print message connects in debug - cm_servers = None #: a instance of :class:`steam.core.cm.CMServerList` + cm_servers = None #: a instance of :class:`.CMServerList` current_server_addr = None #: (ip, port) tuple _seen_logon = False _connecting = False @@ -44,7 +69,7 @@ class CMClient(EventEmitter): channel_key = None #: channel encryption key channel_hmac = None #: HMAC secret - steam_id = SteamID() #: :class:`steam.steamid.SteamID` of the current user + steam_id = SteamID() #: :class:`.SteamID` of the current user session_id = None #: session id when logged in webapi_authenticate_user_nonce = None #: nonce for the getting a web session @@ -52,11 +77,11 @@ class CMClient(EventEmitter): _heartbeat_loop = None _LOG = None - def __init__(self, protocol=0): + def __init__(self, protocol=PROTOCOL_TCP): self._LOG = logging.getLogger("CMClient") self.cm_servers = CMServerList() - if protocol == CMClient.TCP: + if protocol == CMClient.PROTOCOL_TCP: self.connection = TCPConnection() else: raise ValueError("Only TCP is supported") @@ -91,7 +116,7 @@ class CMClient(EventEmitter): if delay: self._LOG.debug("Delayed connect: %d seconds" % delay) - self.emit('reconnect', delay) + self.emit(self.EVENT_RECONNECT, delay) gevent.sleep(delay) self._LOG.debug("Connect initiated.") @@ -114,7 +139,7 @@ class CMClient(EventEmitter): self.current_server_addr = server_addr self.connected = True - self.emit("connected") + self.emit(self.EVENT_CONNECTED) self._recv_loop = gevent.spawn(self._recv_messages) self._connecting = False return True @@ -151,7 +176,7 @@ class CMClient(EventEmitter): self._reset_attributes() - self.emit('disconnected') + self.emit(self.EVENT_DISCONNECTED) def _reset_attributes(self): for name in ['connected', @@ -298,7 +323,7 @@ class CMClient(EventEmitter): self._LOG.debug("Channel secured (legacy mode)") self.channel_secured = True - self.emit('channel_secured') + self.emit(self.EVENT_CHANNEL_SECURED) def __handle_multi(self, msg): self._LOG.debug("Multi: Unpacking") @@ -357,7 +382,7 @@ class CMClient(EventEmitter): interval = msg.body.out_of_game_heartbeat_seconds self._heartbeat_loop = gevent.spawn(self.__heartbeat, interval) else: - self.emit("error", EResult(result)) + self.emit(self.EVENT_ERROR, EResult(result)) self.disconnect() def _handle_cm_list(self, msg):