diff options
Diffstat (limited to 'discord/client.py')
| -rw-r--r-- | discord/client.py | 161 |
1 files changed, 18 insertions, 143 deletions
diff --git a/discord/client.py b/discord/client.py index 8e5f1c04..d8dd9b12 100644 --- a/discord/client.py +++ b/discord/client.py @@ -28,7 +28,6 @@ from . import __version__ as library_version from . import endpoints from .user import User from .member import Member -from .game import Game from .channel import Channel, PrivateChannel from .server import Server from .message import Message @@ -38,10 +37,11 @@ from .role import Role from .errors import * from .state import ConnectionState from .permissions import Permissions -from . import utils +from . import utils, compat from .enums import ChannelType, ServerRegion, Status from .voice_client import VoiceClient from .iterators import LogsFromIterator +from .gateway import * import asyncio import aiohttp @@ -51,7 +51,6 @@ import logging, traceback import sys, time, re, json import tempfile, os, hashlib import itertools -import zlib from random import randint as random_integer PY35 = sys.version_info >= (3, 5) @@ -115,11 +114,7 @@ class Client: def __init__(self, *, loop=None, **options): self.ws = None self.token = None - self.gateway = None self.voice = None - self.session_id = None - self.keep_alive = None - self.sequence = 0 self.loop = asyncio.get_event_loop() if loop is None else loop self._listeners = [] self.cache_auth = options.get('cache_auth', True) @@ -157,11 +152,6 @@ class Client: return os.path.join(tempfile.gettempdir(), 'discord_py', filename) @asyncio.coroutine - def _send_ws(self, data): - self.dispatch('socket_raw_send', data) - yield from self.ws.send(data) - - @asyncio.coroutine def _login_via_cache(self, email, password): try: log.info('attempting to login via cache') @@ -255,14 +245,6 @@ class Client: object.__setattr__(self, name, value) @asyncio.coroutine - def _get_gateway(self): - resp = yield from self.session.get(endpoints.GATEWAY, headers=self.headers) - if resp.status != 200: - raise GatewayNotFound() - data = yield from resp.json() - return data.get('url') - - @asyncio.coroutine def _run_event(self, event, *args, **kwargs): try: yield from getattr(self, event)(*args, **kwargs) @@ -283,23 +265,7 @@ class Client: getattr(self, handler)(*args, **kwargs) if hasattr(self, method): - utils.create_task(self._run_event(method, *args, **kwargs), loop=self.loop) - - @asyncio.coroutine - def keep_alive_handler(self, interval): - try: - while not self.is_closed: - payload = { - 'op': 1, - 'd': int(time.time()) - } - - msg = 'Keeping websocket alive with timestamp {}' - log.debug(msg.format(payload['d'])) - yield from self._send_ws(utils.to_json(payload)) - yield from asyncio.sleep(interval) - except asyncio.CancelledError: - pass + compat.create_task(self._run_event(method, *args, **kwargs), loop=self.loop) @asyncio.coroutine def on_error(self, event_method, *args, **kwargs): @@ -352,7 +318,7 @@ class Client: if is_ready or event == 'RESUMED': interval = data['heartbeat_interval'] / 1000.0 - self.keep_alive = utils.create_task(self.keep_alive_handler(interval), loop=self.loop) + self.keep_alive = compat.create_task(self.keep_alive_handler(interval), loop=self.loop) if event == 'VOICE_STATE_UPDATE': user_id = data.get('user_id') @@ -380,64 +346,6 @@ class Client: else: result = func(data) - @asyncio.coroutine - def _make_websocket(self, initial=True): - if not self.is_logged_in: - raise ClientException('You must be logged in to connect') - - self.ws = yield from websockets.connect(self.gateway, loop=self.loop) - self.ws.max_size = None - log.info('Created websocket connected to {0.gateway}'.format(self)) - - if initial: - payload = { - 'op': 2, - 'd': { - 'token': self.token, - 'properties': { - '$os': sys.platform, - '$browser': 'discord.py', - '$device': 'discord.py', - '$referrer': '', - '$referring_domain': '' - }, - 'compress': True, - 'large_threshold': 250, - 'v': 3 - } - } - - yield from self._send_ws(utils.to_json(payload)) - log.info('sent the initial payload to create the websocket') - - @asyncio.coroutine - def redirect_websocket(self, url): - # if we get redirected then we need to recreate the websocket - # when this recreation happens we have to try to do a reconnection - log.info('redirecting websocket from {} to {}'.format(self.gateway, url)) - self.keep_alive_handler.cancel() - - self.gateway = url - yield from self._make_websocket(initial=False) - yield from self._reconnect_ws() - - if self.is_voice_connected(): - # update the websocket reference pointed to by voice - self.voice.main_ws = self.ws - - @asyncio.coroutine - def _reconnect_ws(self): - payload = { - 'op': 6, - 'd': { - 'session_id': self.session_id, - 'seq': self.sequence - } - } - - log.info('sending reconnection frame to websocket {}'.format(payload)) - yield from self._send_ws(utils.to_json(payload)) - # login state management @asyncio.coroutine @@ -553,29 +461,24 @@ class Client: Raises ------- - ClientException - If this is called before :meth:`login` was invoked successfully - or when an unexpected closure of the websocket occurs. GatewayNotFound If the gateway to connect to discord is not found. Usually if this is thrown then there is a discord API outage. + ConnectionClosed + The websocket connection has been terminated. """ - self.gateway = yield from self._get_gateway() - yield from self._make_websocket() + self.ws = yield from DiscordWebSocket.from_client(self) while not self.is_closed: - msg = yield from self.ws.recv() - if msg is None: - if self.ws.close_code == 1012: - yield from self.redirect_websocket(self.gateway) - continue - elif not self._is_ready.is_set(): - raise ClientException('Unexpected websocket closure received') - else: - yield from self.close() - break - - yield from self.received_message(msg) + try: + yield from self.ws.poll_event() + except ReconnectWebSocket: + log.info('Reconnecting the websocket.') + self.ws = yield from DiscordWebSocket.from_client(self) + except ConnectionClosed as e: + yield from self.close() + if e.code != 1000: + raise @asyncio.coroutine def close(self): @@ -593,9 +496,6 @@ class Client: if self.ws is not None and self.ws.open: yield from self.ws.close() - if self.keep_alive is not None: - self.keep_alive.cancel() - yield from self.session.close() self._closed.set() self._is_ready.clear() @@ -1317,7 +1217,7 @@ class Client: } } - yield from self._send_ws(utils.to_json(payload)) + yield from self.ws.send_as_json(payload) @asyncio.coroutine def kick(self, member): @@ -1568,32 +1468,7 @@ class Client: InvalidArgument If the ``game`` parameter is not :class:`Game` or None. """ - - if game is not None and not isinstance(game, Game): - raise InvalidArgument('game must be of Game or None') - - idle_since = None if idle == False else int(time.time() * 1000) - sent_game = game and {'name': game.name} - - payload = { - 'op': 3, - 'd': { - 'game': sent_game, - 'idle_since': idle_since - } - } - - sent = utils.to_json(payload) - log.debug('Sending "{}" to change status'.format(sent)) - yield from self._send_ws(sent) - for server in self.servers: - me = server.me - if me is None: - continue - - me.game = game - status = Status.idle if idle_since else Status.online - me.status = status + yield from self.ws.change_presence(game=game, idle=idle) # Channel management |