Begin working on gateway v4 support.
Bump websockets requirement to v3.1 Should be squashed...
This commit is contained in:
@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user