Begin working on gateway v4 support.

Bump websockets requirement to v3.1

Should be squashed...
This commit is contained in:
Rapptz
2016-04-26 19:38:54 -04:00
parent fda0c8cea0
commit 1c623ccf11
10 changed files with 451 additions and 153 deletions

View File

@ -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)
@ -156,11 +151,6 @@ class Client:
filename = hashlib.md5(email.encode('utf-8')).hexdigest()
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:
@ -254,14 +244,6 @@ class Client:
else:
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:
@ -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