pythonhacktoberfeststeamauthenticationauthenticatorsteam-authenticatorsteam-clientsteam-guard-codessteam-websteamworksvalvewebapi
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
286 lines
8.9 KiB
286 lines
8.9 KiB
import logging
|
|
from eventemitter import EventEmitter
|
|
from steam.steamid import SteamID, intBase
|
|
from steam.enums import EResult, EFriendRelationship, EPersonaState
|
|
from steam.enums.emsg import EMsg
|
|
from steam.core.msg import MsgProto
|
|
|
|
|
|
class Friends(object):
|
|
def __init__(self, *args, **kwargs):
|
|
super(Friends, self).__init__(*args, **kwargs)
|
|
|
|
#: SteamFriendlist instance
|
|
self.friends = SteamFriendlist(self, logger_name="%s.friends" % self.__class__.__name__)
|
|
|
|
class SteamFriendlist(EventEmitter):
|
|
"""SteamFriendlist is an object that keeps state of user's friend list.
|
|
Think of it as a list of :class:`SteamUser`.
|
|
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 updated immediatelly for new user entries
|
|
|
|
"""
|
|
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 = 'friend_new'
|
|
"""Friendship established (after being accepted, or accepting)
|
|
|
|
:param user: steam user instance
|
|
: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
|
|
|
|
: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`
|
|
"""
|
|
|
|
ready = False #: indicates whether friend list is available
|
|
|
|
def __init__(self, client, logger_name='SteamFriendList'):
|
|
self._LOG = logging.getLogger(logger_name)
|
|
self._fr = {}
|
|
self._steam = client
|
|
|
|
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(self._steam.EVENT_DISCONNECTED, self._handle_disconnect)
|
|
|
|
def emit(self, event, *args):
|
|
if event is not None:
|
|
self._LOG.debug("Emit event: %s" % repr(event))
|
|
EventEmitter.emit(self, event, *args)
|
|
|
|
def _handle_disconnect(self):
|
|
self.ready = False
|
|
self._fr.clear()
|
|
|
|
def _handle_add_friend_result(self, message):
|
|
eresult = EResult(message.body.eresult)
|
|
steam_id = SteamID(message.body.steam_id_added)
|
|
self.emit(self.EVENT_FRIEND_ADD_RESULT, eresult, steam_id)
|
|
|
|
def _handle_friends_list(self, message):
|
|
incremental = message.body.bincremental
|
|
if incremental == False:
|
|
self._fr.clear()
|
|
|
|
pstate_check = set()
|
|
|
|
# update internal friends list
|
|
for friend in message.body.friends:
|
|
steamid = friend.ulfriendid
|
|
rel = EFriendRelationship(friend.efriendrelationship)
|
|
|
|
if steamid not in self._fr:
|
|
suser = SteamUser(steamid, rel)
|
|
self._fr[suser] = suser
|
|
|
|
if rel in (2,4):
|
|
if incremental == False:
|
|
pstate_check.add(steamid)
|
|
|
|
if rel == EFriendRelationship.RequestRecipient:
|
|
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(self.EVENT_FRIEND_REMOVED, self._fr.pop(steamid))
|
|
elif oldrel in (2,4) and rel == EFriendRelationship.Friend:
|
|
self.emit(self.EVENT_FRIEND_NEW, self._fr[steamid])
|
|
|
|
# request persona state for any new entries
|
|
if pstate_check:
|
|
m = MsgProto(EMsg.ClientRequestFriendData)
|
|
m.body.persona_state_requested = 4294967295 # request all possible flags
|
|
m.body.friends.extend(pstate_check)
|
|
self._steam.send(m)
|
|
|
|
if not self.ready:
|
|
self.ready = True
|
|
self.emit(self.EVENT_READY)
|
|
|
|
def _handle_persona_state(self, message):
|
|
for friend in message.body.friends:
|
|
steamid = friend.friendid
|
|
|
|
if steamid == self._steam.steam_id:
|
|
continue
|
|
|
|
if steamid in self._fr:
|
|
self._fr[steamid]._data['pstate'] = friend
|
|
self.emit('persona_state_updated', self._fr[steamid])
|
|
|
|
def __repr__(self):
|
|
return "<%s %d users>" % (
|
|
self.__class__.__name__,
|
|
len(self._fr),
|
|
)
|
|
|
|
def __len__(self):
|
|
return len(self._fr)
|
|
|
|
def __iter__(self):
|
|
return iter(self._fr)
|
|
|
|
def __list__(self):
|
|
return list(self._fr)
|
|
|
|
def __getitem__(self, key):
|
|
return self._fr[key]
|
|
|
|
def __contains__(self, friend):
|
|
return friend in self._fr
|
|
|
|
def add(self, steamid_or_accountname_or_email):
|
|
"""
|
|
Add/Accept a steam user to be your friend.
|
|
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:`.SteamID`, :class:`SteamUser`, :class:`str`
|
|
|
|
.. note::
|
|
Adding by email doesn't not work. It's only mentioned for the sake of completeness.
|
|
"""
|
|
m = MsgProto(EMsg.ClientAddFriend)
|
|
|
|
if isinstance(steamid_or_accountname_or_email, (intBase, int)):
|
|
m.body.steamid_to_add = steamid_or_accountname_or_email
|
|
else:
|
|
m.body.accountname_or_email_to_add = steamid_or_accountname_or_email
|
|
|
|
self._steam.send(m)
|
|
|
|
def remove(self, steamid):
|
|
"""
|
|
Remove a friend
|
|
|
|
:param steamid: their steamid
|
|
:type steamid: :class:`int`, :class:`.SteamID`, :class:`SteamUser`
|
|
"""
|
|
m = MsgProto(EMsg.ClientRemoveFriend)
|
|
m.body.friendid = steamid
|
|
self._steam.send(m)
|
|
|
|
class SteamUser(intBase):
|
|
def __new__(cls, steam64, *args, **kwargs):
|
|
return super(SteamUser, cls).__new__(cls, steam64)
|
|
|
|
def __init__(self, steam64, rel):
|
|
self._data = {
|
|
'relationship': EFriendRelationship(rel)
|
|
}
|
|
|
|
def __repr__(self):
|
|
return "<%s (%s) %s %s>" % (
|
|
self.__class__.__name__,
|
|
int(self) if self.name is None else repr(self.name),
|
|
self.relationship.name,
|
|
self.state.name,
|
|
)
|
|
|
|
@property
|
|
def steamid(self):
|
|
"""SteamID instance
|
|
|
|
:rtype: :class:`.SteamID`
|
|
"""
|
|
return SteamID(int(self))
|
|
|
|
@property
|
|
def relationship(self):
|
|
"""Current relationship with the steam user
|
|
|
|
:rtype: :class:`.EFriendRelationship`
|
|
"""
|
|
return self._data['relationship']
|
|
|
|
def get_ps(self, field_name):
|
|
"""Get a value for field in ``CMsgClientPersonaState.Friend``
|
|
|
|
:param field_name: see example list below
|
|
:type field_name: :class:`str`
|
|
:return: value for the field, or ``None`` if not available
|
|
:rtype: :class:`None`, :class:`int`, :class:`str`, :class:`bytes`, :class:`bool`
|
|
|
|
Examples:
|
|
- game_played_app_id
|
|
- last_logoff
|
|
- last_logon
|
|
- game_name
|
|
- avatar_hash
|
|
- facebook_id
|
|
|
|
"""
|
|
pstate = self._data.get('pstate', None)
|
|
if pstate is None or not pstate.HasField(field_name):
|
|
return None
|
|
return getattr(pstate, field_name)
|
|
|
|
@property
|
|
def name(self):
|
|
"""Name of the steam user, or ``None`` if it's not available
|
|
|
|
:rtype: :class:`str`, :class:`None`
|
|
"""
|
|
return self.get_ps('player_name')
|
|
|
|
@property
|
|
def state(self):
|
|
"""State of the steam user
|
|
|
|
:rtype: :class:`.EPersonaState`
|
|
"""
|
|
state = self.get_ps('persona_state')
|
|
if state:
|
|
return EPersonaState(state)
|
|
return EPersonaState.Offline
|
|
|
|
def get_avatar_url(self, size=2):
|
|
"""Get url to the steam avatar
|
|
|
|
:param size: possible values are ``0``, ``1``, or ``2`` corresponding to small, medium, large
|
|
:type size: :class:`int`
|
|
:return: url to avatar
|
|
:rtype: :class:`str`
|
|
"""
|
|
ahash = self.get_ps('avatar_hash')
|
|
if ahash is None:
|
|
return None
|
|
|
|
sizes = {
|
|
0: '',
|
|
1: '_medium',
|
|
2: '_full',
|
|
}
|
|
url = "http://cdn.akamai.steamstatic.com/steamcommunity/public/images/avatars/%s/%s%s.jpg"
|
|
ahash = binascii.hexlify(persona_state_value.avatar_hash).decode('ascii')
|
|
|
|
return url % (ahash[:2], ahash, sizes[size])
|
|
|