Browse Source

basic SteamClient implementation

pull/18/merge
Rossen Georgiev 9 years ago
parent
commit
7d76a375e2
  1. 8
      steam/__init__.py
  2. 117
      steam/client.py
  3. 55
      steam/core/cm.py
  4. 41
      steam/core/msg.py

8
steam/__init__.py

@ -3,3 +3,11 @@ __author__ = "Rossen Georgiev"
from steam.steamid import SteamID
from steam.webapi import WebAPI
# proxy object
# avoids importing steam.enums.emsg unless it's needed
class SteamClient(object):
def __new__(cls, *args, **kwargs):
from steam.client import SteamClient as SC
return SC(*args, **kwargs)

117
steam/client.py

@ -0,0 +1,117 @@
import logging
import gevent
from steam.util.events import EventEmitter
from steam.enums.emsg import EMsg
from steam.enums import EResult
from steam.core.msg import MsgProto
from steam.core.cm import CMClient
from steam import SteamID
logger = logging.getLogger("SteamClient")
class SteamClient(EventEmitter):
def __init__(self):
self.cm = CMClient()
# re-emit all events from CMClient
self.cm.on(None, self.emit)
# register listners
self.on(EMsg.ClientLogOnResponse, self._handle_logon)
self.on("disconnected", self._handle_disconnect)
self.logged_on = False
def emit(self, event, *args):
if event is not None:
logger.debug("Emit event: %s" % repr(event))
super(SteamClient, self).emit(event, *args)
@property
def steam_id(self):
return self.cm.steam_id
@property
def connected(self):
return self.cm.connected
def connect(self):
self.cm.connect()
def disconnect(self):
self.cm.disconnect()
def _handle_disconnect(self):
self.logged_on = False
def _handle_logon(self, msg):
result = EResult(msg.body.eresult)
if result == EResult.OK:
self.emit("logged_on")
self.logged_on = True
return
# CM kills the connection on error anyway
self.disconnect()
if result in (EResult.AccountLogonDenied,
EResult.AccountLoginDeniedNeedTwoFactor,
EResult.TwoFactorCodeMismatch,
):
self.emit("need_code", result)
else:
self.emit("error", result)
def send(self, message):
self.cm.send_message(message)
def anonymous_login(self):
logger.debug("Attempting Anonymous login")
if self.logged_on:
logger.debug("Aready logged on")
return
if not self.connected:
self.connect()
self.wait_event("channel_secured")
message = MsgProto(EMsg.ClientLogon)
message.header.steamid = SteamID(type='AnonUser', universe='Public')
message.body.protocol_version = 65575
self.send(message)
def login(self, username, password, auth_code=None, two_factor_code=None, remember=False):
logger.debug("Attempting login")
if self.logged_on:
logger.debug("Aready logged on")
return
if not self.connected:
self.connect()
self.wait_event("channel_secured")
message = MsgProto(EMsg.ClientLogon)
message.header.steamid = SteamID(type='Individual', universe='Public')
message.body.protocol_version = 65575
message.body.client_package_version = 1771
message.body.client_os_type = 13
message.body.client_language = "english"
message.body.account_name = username
message.body.password = password
if auth_code:
message.body.auth_code = auth_code
if two_factor_code:
message.body.two_factor_code = two_factor_code
self.send(message)
def logout(self):
if self.logged_on:
self.send(MsgProto(EMsg.ClientLogOff))
self.logged_on = False

55
steam/core/cm.py

@ -61,7 +61,8 @@ class CMClient(EventEmitter):
self.on(EMsg.ClientLogOnResponse, self._handle_logon),
def emit(self, event, *args):
logger.debug("Emit event: %s" % str(event))
if event is not None:
logger.debug("Emit event: %s" % repr(event))
super(CMClient, self).emit(event, *args)
def connect(self):
@ -78,21 +79,26 @@ class CMClient(EventEmitter):
break
self.connected = True
self.emit("connected")
self._recv_loop = gevent.spawn(self._recv_messages)
def disconnect(self):
self.connection.disconnect()
def disconnect(self, reconnect=False):
if reconnect:
gevent.spawn(self.connect)
self._recv_loop.kill(block=False)
self.connection.disconnect()
if self._heartbeat_loop:
self._heartbeat_loop.kill()
self._recv_loop.kill()
self._init_attributes()
self.emit('disconnected')
def _init_attributes(self):
self.connected = False
self.key = None
self.steam_id = None
@ -128,8 +134,7 @@ class CMClient(EventEmitter):
message = self.connection.get_message(timeout=1)
except queue.Empty:
if not self.connection.event_connected.is_set():
self.disconnect()
gevent.spawn(self.connect)
gevent.spawn(self.disconnect)
return
continue
@ -154,12 +159,12 @@ class CMClient(EventEmitter):
msg = MsgProto(emsg, message)
else:
msg = Msg(emsg, message, extended=True)
except:
logger.fatal("Failed to deserialize message: %s %s",
except Exception as e:
logger.fatal("Failed to deserialize message: %s (is_proto: %s)",
str(emsg),
is_proto(emsg_id)
)
raise
logger.exception(e)
if self.verbose_debug:
logger.debug("Incoming: %s\n%s" % (repr(msg), str(msg)))
@ -185,15 +190,15 @@ class CMClient(EventEmitter):
msg = self.wait_event(EMsg.ChannelEncryptResult)
if msg.body.result != EResult.OK:
logger.debug("Failed to secure channel: %s" % msg.body.result)
if msg.body.eresult != EResult.OK:
logger.debug("Failed to secure channel: %s" % msg.body.eresult)
self.disconnect()
return
logger.debug("Channel secured")
self.key = key
self.emit('ready')
self.emit('channel_secured')
def _handle_multi(self, msg):
logger.debug("Unpacking CMsgMulti")
@ -224,23 +229,23 @@ class CMClient(EventEmitter):
def _handle_logon(self, msg):
result = msg.body.eresult
if result != EResult.OK:
self.disconnect()
if result in (EResult.TryAnotherCM, EResult.ServiceUnavailable):
gevent.spawn(self.connect)
if result in (EResult.TryAnotherCM,
EResult.ServiceUnavailable
):
self.disconnect(True)
return
logger.debug("Logon completed")
elif result == EResult.OK:
logger.debug("Logon completed")
self.steam_id = SteamID(msg.header.steamid)
self.session_id = msg.header.client_sessionid
self.steam_id = SteamID(msg.header.steamid)
self.session_id = msg.header.client_sessionid
if self._heartbeat_loop:
self._heartbeat_loop.kill()
if self._heartbeat_loop:
self._heartbeat_loop.kill()
logger.debug("Heartbeat started.")
logger.debug("Heartbeat started.")
interval = msg.body.out_of_game_heartbeat_seconds
self._heartbeat_loop = gevent.spawn(self._heartbeat, interval)
interval = msg.body.out_of_game_heartbeat_seconds
self._heartbeat_loop = gevent.spawn(self._heartbeat, interval)

41
steam/core/msg.py

@ -139,20 +139,20 @@ class Msg(object):
def __init__(self, msg, data=None, extended=False):
self.extended = extended
self.header = ExtendedMsgHdr(data) if extended else MsgHdr(data)
self.header.msg = msg
self.msg = msg
if data:
data = data[self.header._size:]
if msg == EMsg.ChannelEncryptRequest:
self.header.msg = EMsg.ChannelEncryptRequest
self.body = ChannelEncryptRequest(data)
elif msg == EMsg.ChannelEncryptResponse:
self.header.msg = EMsg.ChannelEncryptResponse
self.body = ChannelEncryptResponse(data)
elif msg == EMsg.ChannelEncryptResult:
self.header.msg = EMsg.ChannelEncryptResult
self.body = ChannelEncryptResult(data)
elif msg == EMsg.ClientLogOnResponse:
self.body = ClientLogOnResponse(data)
else:
self.body = None
@ -214,15 +214,17 @@ def get_cmsg(emsg):
if emsg == EMsg.Multi:
return steammessages_base_pb2.CMsgMulti
emsg = "cmsg" + str(emsg).split('.', 1)[1].lower()
elif emsg in (EMsg.ClientToGC, EMsg.ClientFromGC):
return steammessages_clientserver_2_pb2.CMsgGCClient
else:
cmsg_name = "cmsg" + str(emsg).split('.', 1)[1].lower()
if not cmsg_lookup:
cmsg_list = steammessages_clientserver_pb2.__dict__
cmsg_list = fnmatch.filter(cmsg_list, 'CMsg*')
cmsg_lookup = dict(zip(map(lambda x: x.lower(), cmsg_list), cmsg_list))
name = cmsg_lookup.get(emsg, None)
name = cmsg_lookup.get(cmsg_name, None)
if name:
return getattr(steammessages_clientserver_pb2, name)
@ -231,7 +233,7 @@ def get_cmsg(emsg):
cmsg_list = fnmatch.filter(cmsg_list, 'CMsg*')
cmsg_lookup2 = dict(zip(map(lambda x: x.lower(), cmsg_list), cmsg_list))
name = cmsg_lookup2.get(emsg, None)
name = cmsg_lookup2.get(cmsg_name, None)
if name:
return getattr(steammessages_clientserver_2_pb2, name)
@ -356,14 +358,31 @@ class ChannelEncryptResult:
if data:
self.load(data)
else:
self.result
self.eresult = EResult.Invalid
def serialize(self):
return struct.pack("<I", self.eresult)
def load(self, data):
(result,) = struct.unpack_from("<I", data)
self.eresult = EResult(result)
def __str__(self):
return "result: %s" % repr(self.eresult)
class ClientLogOnResponse:
def __init__(self, data=None):
if data:
self.load(data)
else:
self.eresult = EResult.Invalid
def serialize(self):
return struct.pack("<I", self.result)
return struct.pack("<I", self.eresult)
def load(self, data):
(result,) = struct.unpack_from("<I", data)
self.result = EResult(result)
self.eresult = EResult(result)
def __str__(self):
return "result: %s" % repr(self.result)
return "eresult: %s" % repr(self.eresult)

Loading…
Cancel
Save