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

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])