aboutsummaryrefslogtreecommitdiff
path: root/discord/client.py
diff options
context:
space:
mode:
Diffstat (limited to 'discord/client.py')
-rw-r--r--discord/client.py161
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