Skip to content

Commit

Permalink
Merge pull request #23 from Terbau/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
Terbau committed Aug 22, 2019
2 parents 81ad76f + 324443a commit 7bcb549
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 31 deletions.
20 changes: 20 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@ Changelog
Detailed version changes.


v0.5.2
------

Internal changes 99% of you wont ever notice + some small bug fixes.

Refactored
~~~~~~~~~~

- Reworked :meth:`Client.run()` to use :meth:`asyncio.AbstractEventLoop.run_forever()` and implemented better task cleanup heavily based of discord.py's cleanup method.
- Reworked :meth:`Client.start()` and :meth:`Client.logout()` a work better together when logging out while running.
- Changed some internal data body values related to parties to match fortnite's values.
- The clients XMPP jid will now use a unique id in its resource part.

Bug Fixes
~~~~~~~~~

- Fixed an issue with :meth:`Client.fetch_profiles()` where if ``raw`` was ``True`` and some of the profiles were gotten from cache they would not be returned raw.
- Fixed an issue with :meth:`Client.fetch_profiles()` where if no profiles was retrieved an error would be raised.


v5.0.1
------

Expand Down
2 changes: 1 addition & 1 deletion fortnitepy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
SOFTWARE.
"""

__version__ = '0.5.1'
__version__ = '0.5.2'

from .client import Client
from .friend import Friend, PendingFriend
Expand Down
2 changes: 2 additions & 0 deletions fortnitepy/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ async def authenticate(self):

self.launcher_access_token = data['access_token']
await self.exchange_code('bearer {0}'.format(self.launcher_access_token))
except asyncio.CancelledError:
pass
except Exception as e:
traceback.print_exc()
raise AuthException('Could not authenticate. Error: {}'.format(e))
Expand Down
3 changes: 3 additions & 0 deletions fortnitepy/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ async def schedule_removal(self, key, seconds):
except KeyError:
pass

def clear(self):
self._cache = {}


class WeakrefCache(Cache):
def __init__(self, loop=None):
Expand Down
122 changes: 103 additions & 19 deletions fortnitepy/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,42 @@
# logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger(__name__)

# all credit for this function goes to discord.py. Great task cancelling.
def _cancel_tasks(loop):
task_retriever = asyncio.Task.all_tasks
tasks = {t for t in task_retriever(loop=loop) if not t.done()}

if not tasks:
return

log.info('Cleaning up after %d tasks.', len(tasks))
for task in tasks:
task.cancel()

loop.run_until_complete(asyncio.gather(*tasks, loop=loop, return_exceptions=True))
log.info('All tasks finished cancelling.')

for task in tasks:
if task.cancelled():
continue
if task.exception() is not None:
loop.call_exception_handler({
'message': 'Unhandled exception during Client.run shutdown.',
'exception': task.exception(),
'task': task
})


def _cleanup_loop(loop):
try:
_cancel_tasks(loop)
if sys.version_info >= (3, 6):
loop.run_until_complete(loop.shutdown_asyncgens())
finally:
log.info('Closing the event loop.')
loop.close()


class Client:
"""Represents the client connected to Fortnite and EpicGames' services.
Expand Down Expand Up @@ -174,6 +210,11 @@ def __init__(self, email, password, two_factor_code=None, loop=None, **kwargs):
self._presences = Cache()
self.event_prefix = 'event'
self._ready = asyncio.Event(loop=self.loop)
self._refresh_task = None
self._closed = False
self._closing = False

self.refresh_i = 0

self.update_default_party_config(
kwargs.get('default_party_config')
Expand Down Expand Up @@ -246,22 +287,43 @@ def run(self):
If you have passed an already running event loop to the client, you should start the client
with :meth:`start`.
.. warning::
This function is blocking and should be the last function to run.
Raises
------
AuthException
An error occured when attempting to log in.
"""
loop = self.loop

try:
# bad way of catching some annoying aioxmpp errors, will rework in the future
# self.loop.set_exception_handler(self.exc_handler)
loop.add_signal_handler(signal.SIGINT, lambda: loop.stop())
loop.add_signal_handler(signal.SIGTERM, lambda: loop.stop())
except NotImplementedError:
pass

async def runner():
try:
await self.start()
finally:
if not self._closing:
await self.logout()

asyncio.ensure_future(runner(), loop=loop)

self.loop.run_until_complete(self.start())
log.debug('Successfully launched')
try:
loop.run_forever()
except KeyboardInterrupt:
pass
log.info('Terminating event loop.')
finally:
self.loop.run_until_complete(self.logout())
self.loop.close()
if not self._closing:
log.info('Client not logged out when terminating loop. Logging out now.')
loop.run_until_complete(self.logout())

log.info('Cleaning up loop')
_cleanup_loop(loop)

async def start(self):
"""|coro|
Expand All @@ -279,11 +341,20 @@ async def start(self):
AuthException
An error occured when attempting to log in.
"""
if self._closed:
self.http.create_connection()
self._closed = False

await self._login()

self._set_ready()
self.dispatch_event('ready')
await self.auth.schedule_token_refresh()

self._refresh_task = self.loop.create_task(self.auth.schedule_token_refresh())
try:
await self._refresh_task
except asyncio.CancelledError:
pass

async def _login(self):
self.auth = Auth(self)
Expand Down Expand Up @@ -319,6 +390,9 @@ async def logout(self):
HTTPException
An error occured while logging out.
"""
self._closing = True
if self._refresh_task is not None and not self._refresh_task.cancelled():
self._refresh_task.cancel()

try:
if self.user.party is not None:
Expand All @@ -336,8 +410,15 @@ async def logout(self):
except:
pass

await self.http.close()
self._friends.clear()
self._pending_friends.clear()
self._users.clear()
self._presences.clear()
self._ready.clear()

await self.http.close()
self._closed = True
self._closing = False
log.debug('Successfully logged out')

def _set_ready(self):
Expand Down Expand Up @@ -514,7 +595,10 @@ async def fetch_profiles(self, users, cache=True, raw=False):
if cache:
p = self.get_user(elem)
if p:
profiles.append(p)
if raw:
profiles.append(p.get_raw())
else:
profiles.append(p)
continue
new.append(elem)

Expand All @@ -530,14 +614,15 @@ async def fetch_profiles(self, users, cache=True, raw=False):
task = self.http.get_profiles(chunk)
chunk_tasks.append(task)

d, _ = await asyncio.wait(chunk_tasks)
for results in d:
for result in results.result():
if raw:
profiles.append(result)
else:
u = self.store_user(result)
profiles.append(u)
if len(chunks) > 0:
d, _ = await asyncio.wait(chunk_tasks)
for results in d:
for result in results.result():
if raw:
profiles.append(result)
else:
u = self.store_user(result)
profiles.append(u)
return profiles

async def initialize_friends(self):
Expand Down Expand Up @@ -1158,7 +1243,6 @@ async def _create_party(self, config=None):
data = await self.http.party_create(cf)
break
except HTTPException as exc:
print(exc.message_code)
if exc.message_code != 'errors.com.epicgames.social.party.user_has_party':
raise HTTPException(exc.response, exc.raw)

Expand Down
4 changes: 2 additions & 2 deletions fortnitepy/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ class PartyPrivacy(Enum):
'partyType': 'Private',
'inviteRestriction': 'AnyMember',
'onlyLeaderFriendsCanJoin': False,
'presencePermission': 'None',
'presencePermission': 'Noone',
'invitePermission': 'AnyMember',
'acceptingMembers': False,
}
PRIVATE = {
'partyType': 'Private',
'inviteRestriction': 'LeaderOnly',
'onlyLeaderFriendsCanJoin': True,
'presencePermission': 'None',
'presencePermission': 'Noone',
'invitePermission': 'Leader',
'acceptingMembers': False,
}
Expand Down
17 changes: 11 additions & 6 deletions fortnitepy/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,9 @@ def __init__(self, client, connector=None):
self.client = client
self.connector = connector
self._jar = aiohttp.CookieJar()
self.__session = aiohttp.ClientSession(
connector=self.connector,
loop=self.client.loop,
cookie_jar=self._jar
)
self.headers = {}

self.create_connection()

@classmethod
async def json_or_text(cls, response):
Expand Down Expand Up @@ -79,9 +76,17 @@ def remove_header(self, key):
return self.headers.pop(key)

async def close(self):
self._jar.clear()
if self.__session:
await self.__session.close()

def create_connection(self):
self.__session = aiohttp.ClientSession(
connector=self.connector,
loop=self.client.loop,
cookie_jar=self._jar
)

async def request(self, method, url, is_json=False, **kwargs):
headers = {**kwargs.get('headers', {}), **self.headers}

Expand Down Expand Up @@ -378,7 +383,7 @@ async def party_join_request(self, party_id):
'urn:epic:member:dn_s': self.client.user.display_name,
'urn:epic:member:type_s': 'game',
'urn:epic:member:platform_s': self.client.platform,
'urn:epic:member:joinrequest_j': '{"CrossplayPreference_i":"1"}',
'urn:epic:member:joinrequest_j': '{"CrossplayPreference_i":"1","SubGame_u":"1"}',
},
}

Expand Down
4 changes: 2 additions & 2 deletions fortnitepy/party.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ def set_privacy(self, privacy):

updated['urn:epic:cfg:accepting-members_b'] = self.set_prop(
'urn:epic:cfg:accepting-members_b',
str(bool(privacy['acceptingMembers'])).lower(),
str(privacy['acceptingMembers']).lower(),
)

updated['urn:epic:cfg:invite-perm_s'] = self.set_prop(
Expand Down Expand Up @@ -1135,7 +1135,7 @@ def _remove_member(self, id):

def update_presence(self, text=None, conf={}):
perm = self.config['privacy']['presencePermission']
if perm == 'None' or (perm == 'Leader' and self.me.is_leader):
if perm == 'Noone' or (perm == 'Leader' and (self.me is not None and not self.me.is_leader)):
join_data = {
'bInPrivate': True
}
Expand Down
4 changes: 3 additions & 1 deletion fortnitepy/xmpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import random
import logging
import datetime
import uuid

from .errors import XMPPError, PartyError
from .message import FriendMessage, PartyMessage
Expand Down Expand Up @@ -438,8 +439,9 @@ async def _run(self, future):
await asyncio.sleep(1)

async def run(self):
resource_id = (uuid.uuid4().hex).upper()
self.xmpp_client = aioxmpp.PresenceManagedClient(
aioxmpp.JID(self.client.user.id, self.client.service_host, 'V2:Fortnite:WIN::E10260E2901443F3ABF26FE50D3466D8'),
aioxmpp.JID(self.client.user.id, self.client.service_host, f'V2:Fortnite:WIN::{resource_id}'),
aioxmpp.make_security_layer(
self.client.auth.access_token,
no_verify=True
Expand Down

0 comments on commit 7bcb549

Please sign in to comment.