Skip to content

Commit

Permalink
v3.4.0 changes
Browse files Browse the repository at this point in the history
v3.4.0 changes
  • Loading branch information
Terbau authored Jan 19, 2021
2 parents 9093fac + acc5455 commit 91088d8
Show file tree
Hide file tree
Showing 21 changed files with 450 additions and 121 deletions.
30 changes: 30 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,36 @@ Changelog
Detailed version changes.


v3.4.0
------

This update adds Python 3.9 compatibility and other small fixes and features.

Changes
~~~~~~~

- Bumped aioxmpp compatibility requirement to the latest version (``0.12.0``)

Added
~~~~~

- Added a new kwarg ``fetch_user_data_in_events`` to :class:`Client`. This is useful for larger applications that might deal with ratelimiting on their ip.
- Added :meth:`User.fetch()` (applies to all objects derived from ``UserBase`` too) which can be used to refetch userdata like display name and external auths. This is useful if you have the new kwarg set to ``False``.

Bug Fixes
~~~~~~~~~

- Fixed several bugs related to changes in python 3.9 that caused the version to be incompatible with fortnitepy.
- Fixed what I believe to be (hopefully) the last remaining issues that caused :attr:`ClientParty.me` to be ``None`` in some rare cases.
- Party leave and party join actions are now under the same lock which should elliminate some race conditions.
- Fixed an issue that caused aiohttp to spam noisy errors on shutdown when using :class:`asyncio.ProactorEventLoop`.
- Fixed an issue where an error was raised if a meta property was missing.
- Fixed websocket sessions not being correctly closed when reconnecting after an unexpected close.
- Suppressed the ``websocket stream closed by peer`` message.
- Suppressed a noisy asyncio error log on shutdown that was introduced in the latest aioxmpp version.
- Fixed an issue that caused processing of :func:`event_party_team_swap()` to error in some very rare cases.


v3.3.5
------

Expand Down
4 changes: 2 additions & 2 deletions fortnitepy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""
MIT License
Copyright (c) 2019-2020 Terbau
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
Expand All @@ -25,7 +25,7 @@
SOFTWARE.
"""

__version__ = '3.3.5'
__version__ = '3.4.0'

from .client import Client, run_multiple, start_multiple, close_multiple
from .auth import (Auth, EmailAndPasswordAuth, ExchangeCodeAuth,
Expand Down
2 changes: 1 addition & 1 deletion fortnitepy/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
MIT License
Copyright (c) 2019-2020 Terbau
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
Expand Down
66 changes: 43 additions & 23 deletions fortnitepy/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
MIT License
Copyright (c) 2019-2020 Terbau
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
Expand Down Expand Up @@ -63,7 +63,11 @@

# all credit for this function goes to discord.py.
def _cancel_tasks(loop: asyncio.AbstractEventLoop) -> None:
task_retriever = asyncio.Task.all_tasks
try:
task_retriever = asyncio.Task.all_tasks
except AttributeError:
task_retriever = asyncio.all_tasks

tasks = {t for t in task_retriever(loop=loop) if not t.done()}

if not tasks:
Expand Down Expand Up @@ -513,8 +517,24 @@ class Client:
The host used by Fortnite's XMPP services.
service_domain: :class:`str`
The domain used by Fortnite's XMPP services.
serivce_port: :class:`int`
service_port: :class:`int`
The port used by Fortnite's XMPP services.
cache_users: :class:`bool`
Whether or not the library should cache :class:`User` objects. Disable
this if you are running a program with lots of users as this could
potentially take a big hit on the memory usage. Defaults to ``True``.
fetch_user_data_in_events: :class:`bool`
Whether or not user data should be fetched in event processing. Disabling
this might be useful for larger applications that deals with
possibly being rate limited on their ip. Defaults to ``True``.
.. warning::
Keep in mind that if this option is disabled, there is a big
chance that display names, external auths and more might be missing
or simply is ``None`` on objects deriving from :class:`User`. Keep in
mind that :attr:`User.id` always will be available. You can use
:meth:`User.fetch()` to update all missing attributes.
Attributes
----------
Expand All @@ -528,11 +548,9 @@ class Client:

def __init__(self, auth, *,
loop: Optional[asyncio.AbstractEventLoop] = None,
cache_users: bool = True,
**kwargs: Any) -> None:

self.loop = loop or asyncio.get_event_loop()
self.cache_users = cache_users

self.status = kwargs.get('status', 'Battle Royale Lobby - {party_size} / {party_max_size}') # noqa
self.away = kwargs.get('away', AwayStatus.ONLINE)
Expand All @@ -545,10 +563,11 @@ def __init__(self, auth, *,
self.default_party_member_config = kwargs.get('default_party_member_config', DefaultPartyMemberConfig()) # noqa
self.build = kwargs.get('build', '++Fortnite+Release-14.10-CL-14288110') # noqa
self.os = kwargs.get('os', 'Windows/10.0.17134.1.768.64bit')

self.service_host = kwargs.get('xmpp_host', 'prod.ol.epicgames.com')
self.service_domain = kwargs.get('xmpp_domain', 'xmpp-service-prod.ol.epicgames.com') # noqa
self.service_port = kwargs.get('xmpp_port', 5222)
self.cache_users = kwargs.get('cache_users', True)
self.fetch_user_data_in_events = kwargs.get('fetch_user_data_in_events', True) # noqa

self.kill_other_sessions = True
self.accept_eula = True
Expand All @@ -575,7 +594,6 @@ def __init__(self, auth, *,
self._exception_future = self.loop.create_future()
self._ready_event = asyncio.Event()
self._closed_event = asyncio.Event()
self._leave_lock = asyncio.Lock()
self._join_party_lock = LockEvent()
self._internal_join_party_lock = LockEvent()
self._reauth_lock = LockEvent()
Expand Down Expand Up @@ -1190,8 +1208,9 @@ async def fetch_user_by_display_name(self, display_name, *,
if cache:
for u in self._users.values():
try:
if u.display_name.casefold() == display_name.casefold():
return u
if u.display_name is not None:
if u.display_name.casefold() == display_name.casefold(): # noqa
return u
except AttributeError:
pass

Expand Down Expand Up @@ -1346,9 +1365,10 @@ def find_by_display_name(dn):
if cache:
for u in self._users.values():
try:
if u.display_name.casefold() == dn.casefold():
_users.append(u)
return
if u.display_name is not None:
if u.display_name.casefold() == dn.casefold():
_users.append(u)
return
except AttributeError:
pass

Expand Down Expand Up @@ -2902,17 +2922,17 @@ async def _create_party(self,
self.user.id,
priority=priority
)
async with self._leave_lock:
try:
await self.http.party_leave(
data['current'][0]['id'],
priority=priority
)
except HTTPException as e:
m = ('errors.com.epicgames.social.'
'party.party_not_found')
if e.message_code != m:
raise

try:
await self.http.party_leave(
data['current'][0]['id'],
priority=priority
)
except HTTPException as e:
m = ('errors.com.epicgames.social.'
'party.party_not_found')
if e.message_code != m:
raise

await self.xmpp.leave_muc()

Expand Down
2 changes: 1 addition & 1 deletion fortnitepy/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""
MIT License
Copyright (c) 2019-2020 Terbau
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
Expand Down
2 changes: 1 addition & 1 deletion fortnitepy/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
MIT License
Copyright (c) 2019-2020 Terbau
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
Expand Down
40 changes: 1 addition & 39 deletions fortnitepy/friend.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
MIT License
Copyright (c) 2019-2020 Terbau
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
Expand Down Expand Up @@ -53,27 +53,6 @@ def _update(self, data: dict) -> None:
self._direction = data['direction']
self._created_at = self.client.from_iso(data['created'])

@property
def display_name(self) -> str:
""":class:`str`: The friend's displayname"""
return super().display_name

@property
def id(self) -> str:
""":class:`str`: The friend's id"""
return self._id

@property
def external_auths(self) -> List[ExternalAuth]:
""":class:`list`: List containing information about external auths.
Might be empty if the friend does not have any external auths"""
return self._external_auths

@property
def jid(self) -> JID:
""":class:`aioxmpp.JID`: The jid of the friend."""
return super().jid

@property
def status(self) -> str:
""":class:`str`: The friends status to the client. E.g. if the friend
Expand Down Expand Up @@ -161,16 +140,6 @@ def _update_summary(self, data: dict) -> None:
_note = data['note']
self._note = _note if _note != '' else None

@property
def display_name(self) -> str:
""":class:`str`: The friends displayname"""
return super().display_name

@property
def id(self) -> str:
""":class:`str`: The friends id"""
return self._id

@property
def favorite(self) -> bool:
""":class:`bool`: ``True`` if the friend is favorited by :class:`ClientUser`
Expand All @@ -190,13 +159,6 @@ def note(self) -> Optional[str]:
""":class:`str`: The friend's note. ``None`` if no note is set."""
return self._note

@property
def external_auths(self) -> List[ExternalAuth]:
""":class:`list`: List containing information about external auths.
Might be empty if the friend does not have any external auths
"""
return self._external_auths

@property
def last_presence(self) -> Presence:
""":class:`Presence`: The last presence retrieved by the
Expand Down
60 changes: 58 additions & 2 deletions fortnitepy/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
MIT License
Copyright (c) 2019-2020 Terbau
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
Expand All @@ -30,11 +30,12 @@
import json
import re
import time
import functools

from typing import TYPE_CHECKING, List, Optional, Any, Union, Tuple
from urllib.parse import quote
from .utils import MaybeLock

from .utils import MaybeLock
from .errors import HTTPException

if TYPE_CHECKING:
Expand Down Expand Up @@ -323,6 +324,59 @@ class StatsproxyPublicService(Route):
AUTH = 'FORTNITE_ACCESS_TOKEN'


def create_aiohttp_closed_event(session) -> asyncio.Event:
"""Work around aiohttp issue that doesn't properly close transports on exit.
See https://github.com/aio-libs/aiohttp/issues/1925#issuecomment-639080209
Returns:
An event that will be set once all transports have been properly closed.
"""

transports = 0
all_is_lost = asyncio.Event()

def connection_lost(exc, orig_lost):
nonlocal transports

try:
orig_lost(exc)
finally:
transports -= 1
if transports == 0:
all_is_lost.set()

def eof_received(orig_eof_received):
try:
orig_eof_received()
except AttributeError:
# It may happen that eof_received() is called after
# _app_protocol and _transport are set to None.
pass

for conn in session.connector._conns.values():
for handler, _ in conn:
proto = getattr(handler.transport, "_ssl_protocol", None)
if proto is None:
continue

transports += 1
orig_lost = proto.connection_lost
orig_eof_received = proto.eof_received

proto.connection_lost = functools.partial(
connection_lost, orig_lost=orig_lost
)
proto.eof_received = functools.partial(
eof_received, orig_eof_received=orig_eof_received
)

if transports == 0:
all_is_lost.set()

return all_is_lost


class HTTPClient:
def __init__(self, client: 'Client', *,
connector: aiohttp.BaseConnector = None,
Expand Down Expand Up @@ -377,7 +431,9 @@ def remove_header(self, key: str) -> Any:
async def close(self) -> None:
self._jar.clear()
if self.__session:
event = create_aiohttp_closed_event(self.__session)
await self.__session.close()
await event.wait()

def create_connection(self) -> None:
self.__session = aiohttp.ClientSession(
Expand Down
2 changes: 1 addition & 1 deletion fortnitepy/kairos.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
MIT License
Copyright (c) 2019-2020 Terbau
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
Expand Down
2 changes: 1 addition & 1 deletion fortnitepy/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
MIT License
Copyright (c) 2019-2020 Terbau
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
Expand Down
Loading

0 comments on commit 91088d8

Please sign in to comment.