# -*- coding: utf-8 -*-
"""
MIT License
Copyright (c) 2019-2021 Terbau
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import re
import datetime
from typing import TYPE_CHECKING
from .errors import Forbidden, PartyError
from .enums import Platform
if TYPE_CHECKING:
from .client import Client
from .friend import Friend
from .party import ClientParty
[github][docs] class PresenceGameplayStats:
"""Represents gameplaystats received from presence.
Attributes
----------
friend: :class:`Friend`
The friend these stats belong to.
state: :class:`str`
The state.
.. note::
It's not really known what value this property might
hold. This is pretty much always an empty string.
playlist: :class:`str`
The playlist.
.. note::
The playlist from the gameplay stats property usually
isn't updated. Consider using :attr:`Presence.playlist` instead
as that seems to always be the correct playlist.
players_alive: :class:`int`
The amount of players alive in the current game.
kills: :class:`int`
The amount of kills the friend currently has. Aliased to ``num_kills``
as well for legacy reasons.
fell_to_death: :class:`bool`
``True`` if friend fell to death in its current game, else ``False``
"""
__slots__ = ('friend', 'state', 'playlist', 'players_alive', 'kills',
'num_kills', 'fell_to_death')
[github] def __init__(self, friend: 'Friend',
data: str,
players_alive: int) -> None:
self.friend = friend
self.state = data.get('state')
self.playlist = data.get('playlist')
self.players_alive = players_alive
self.kills = data.get('numKills')
if self.kills is not None:
self.kills = int(self.kills)
self.num_kills = self.kills
self.fell_to_death = True if data.get('bFellToDeath') else False
[github] def __repr__(self) -> str:
return ('<PresenceGameplayStats friend={0.friend!r} '
'players_alive={0.players_alive} num_kills={0.num_kills} '
'playlist={0.playlist!r}>'.format(self))
[github][docs] class PresenceParty:
"""Represents a party received from presence.
Before accessing any of this class' attributes or functions
you should always check if the party is private: ::
@client.event
async def event_friend_presence(before, after):
# after is the newly received presence
presence = after
# check if presence is from the account 'Terbau'
# NOTE: you should always use id over display_name
# but for this example i've use display_name just
# to demonstrate.
if presence.friend.display_name != 'Terbau':
return
# check if party is private
if presence.party.private:
return
# if all the checks above succeeds we join the party
await presence.party.join()
.. note::
If the party is private, all attributes below private will
be ``None``.
Attributes
----------
client: :class:`str`
The client.
private: :class:`bool`
``True`` if the party is private else ``False``.
platform: :class:`Platform`
The platform of the friend.
id: :class:`str`
The party's id.
party_type_id: :class:`str`
The party's type id.
key: :class:`str`
The party's key.
app_id: :class:`str`
The party's app id.
build_id: :class:`str`
The party's build id. Similar format to :attr:`Client.party_build_id`.
net_cl: :class:`str`
The party's net_cl. Similar format to :attr:`Client.net_cl`.
party_flags: :class:`str`
The party's flags.
not_accepting_reason: :class:`str`
The party's not accepting reason.
playercount: :class:`int`
The party's playercount.
"""
__slots__ = ('client', 'private', 'platform', 'id', 'party_type_id',
'key', 'app_id', 'build_id', 'net_cl', 'party_flags',
'not_accepting_reason', 'playercount')
[github] def __init__(self, client: 'Client', data: dict) -> None:
self.client = client
self.private = data.get('bIsPrivate', False)
pl = data.get('sourcePlatform')
self.platform = Platform(pl) if pl is not None else None
self.id = data.get('partyId')
self.party_type_id = data.get('partyTypeId')
self.key = data.get('key')
self.app_id = data.get('appId')
self.build_id = data.get('buildId')
if self.build_id is not None and self.build_id.startswith('1:1:'):
self.net_cl = self.build_id[4:]
else:
self.net_cl = None
self.party_flags = data.get('partyFlags')
self.not_accepting_reason = data.get('notAcceptingReason')
self.playercount = data.get('pc')
if self.playercount is not None:
self.playercount = int(self.playercount)
[github] def __repr__(self) -> str:
return ('<PresenceParty private={0.private} id={0.id!r} '
'playercount={0.playercount}>'.format(self))
[github][docs] async def join(self) -> 'ClientParty':
"""|coro|
Joins the friends' party.
Raises
------
PartyError
You are already a member of this party.
Forbidden
The party is private.
HTTPException
Something else went wrong when trying to join this party.
Returns
-------
:class:`ClientParty`
The party that was just joined.
"""
if self.client.party.id == self.id:
raise PartyError('You are already a member of this party.')
if self.private:
raise Forbidden('You cannot join a private party.')
return await self.client.join_party(self.id)
[github][docs] class Presence:
"""Represents a presence received from a friend
Attributes
----------
client: :class:`Client`
The client.
available: :class:`bool`
Whether or not the user is online. ``True`` if the friend **is** or
**went** online, ``False`` if the friend **went** offline.
away: :class:`AwayStatus`
The users away status.
friend: :class:`Friend`
The friend you received this presence from.
platform: :class:`Platform`
The platform this presence was sent from.
received_at: :class:`datetime.datetime`
The UTC time of when the client received this presence.
status: :class:`str`
The friend's status.
playing: :class:`bool`
Says if friend is playing.
joinable: :class:`bool`
Says if friend is joinable.
session_id: :class:`str`
The friend's current session id. Often referred to as
server key or game key. Returns ``None`` if the friend is not currently
in a game.
has_properties: :class:`bool`
``True`` if the presence has properties else ``False``.
.. warning::
All attributes below this point will be ``None`` if
:attr:`has_properties` is ``False``.
party: :class:`PresenceParty`
The friend's party.
gameplay_stats: Optional[:class:`PresenceGameplayStats`]
The friend's gameplay stats. Will be ``None`` if no gameplay stats
are currently availble.
homebase_rating: :class:`str`
The friend's homebase rating
lfg: :class:`bool`
``True`` if the friend is currently looking for a game.
sub_game: :class:`str`
The friend's current subgame.
in_unjoinable_match: :class:`bool`
``True`` if friend is in unjoinable match else ``False``.
playlist: :class:`str`
The friend's current playlist.
party_size: :class:`int`
The size of the friend's party.
max_party_size: :class:`int`
The max size of the friend's party.
game_session_join_key: :class:`str`
The join key of the friend's session.
server_player_count: :class:`str`
The playercount of the friend's server.
"""
__slots__ = ('client', 'available', 'away', 'friend', 'platform',
'received_at', 'status', 'playing', 'joinable',
'has_voice_support', 'session_id',
'has_properties', 'homebase_rating', 'lfg',
'sub_game', 'in_unjoinable_match', 'playlist', 'party_size',
'max_party_size', 'game_session_join_key',
'server_player_count', 'gameplay_stats', 'party')
[github] def __init__(self, client: 'Client',
from_id: str,
platform: str,
available: bool,
away: bool,
data: dict) -> None:
self.client = client
self.available = available
self.away = away
self.friend = self.client.get_friend(from_id)
self.platform = Platform(platform)
self.received_at = datetime.datetime.utcnow()
self.status = data['Status']
self.playing = data['bIsPlaying']
self.joinable = data['bIsJoinable']
self.has_voice_support = data['bHasVoiceSupport']
self.session_id = (data['SessionId'] if
data['SessionId'] != "" else None)
raw_properties = data.get('Properties', {})
self.has_properties = raw_properties != {}
# All values below will be "None" if properties is empty.
_basic_info = raw_properties.get('FortBasicInfo_j', {})
self.homebase_rating = _basic_info.get('homeBaseRating')
if raw_properties.get('FortLFG_I') is None:
self.lfg = None
else:
self.lfg = int(raw_properties.get('FortLFG_I')) == 1
self.sub_game = raw_properties.get('FortSubGame_i')
self.in_unjoinable_match = raw_properties.get(
'InUnjoinableMatch_b'
)
if self.in_unjoinable_match is not None:
self.in_unjoinable_match = int(self.in_unjoinable_match)
self.playlist = raw_properties.get('GamePlaylistName_s')
players_alive = raw_properties.get('Event_PlayersAlive_s')
if players_alive is not None:
players_alive = int(players_alive)
self.party_size = raw_properties.get('Event_PartySize_s')
if self.party_size is not None:
self.party_size = int(self.party_size)
self.max_party_size = raw_properties.get('Event_PartyMaxSize_s')
if self.max_party_size is not None:
self.max_party_size = int(self.max_party_size)
self.game_session_join_key = raw_properties.get(
'GameSessionJoinKey_s'
)
self.server_player_count = raw_properties.get(
'ServerPlayerCount_i'
)
if self.server_player_count is not None:
self.server_player_count = int(self.server_player_count)
if 'FortGameplayStats_j' in raw_properties:
self.gameplay_stats = PresenceGameplayStats(
self.friend,
raw_properties['FortGameplayStats_j'],
players_alive
)
else:
self.gameplay_stats = None
key = "party.joininfodata.286331153_j"
if key is None:
self.party = None
else:
self.party = PresenceParty(self.client, raw_properties[key])
[github] def __repr__(self) -> str:
return ('<Presence friend={0.friend!r} available={0.available} '
'received_at={0.received_at!r}>'.format(self))