Browse Source

Support for v5 Gateway.

pull/254/head
Rapptz 9 years ago
parent
commit
8b3617111a
  1. 2
      discord/channel.py
  2. 13
      discord/client.py
  3. 37
      discord/gateway.py
  4. 2
      discord/http.py
  5. 40
      discord/server.py
  6. 16
      discord/state.py

2
discord/channel.py

@ -108,7 +108,7 @@ class Channel(Hashable):
self._permission_overwrites = [] self._permission_overwrites = []
everyone_index = 0 everyone_index = 0
everyone_id = self.server.default_role.id everyone_id = self.server.id
for index, overridden in enumerate(kwargs.get('permission_overwrites', [])): for index, overridden in enumerate(kwargs.get('permission_overwrites', [])):
overridden_id = overridden['id'] overridden_id = overridden['id']

13
discord/client.py

@ -138,7 +138,8 @@ class Client:
if max_messages is None or max_messages < 100: if max_messages is None or max_messages < 100:
max_messages = 5000 max_messages = 5000
self.connection = ConnectionState(self.dispatch, self.request_offline_members, max_messages, loop=self.loop) self.connection = ConnectionState(self.dispatch, self.request_offline_members,
self._syncer, max_messages, loop=self.loop)
connector = options.pop('connector', None) connector = options.pop('connector', None)
self.http = HTTPClient(connector, loop=self.loop) self.http = HTTPClient(connector, loop=self.loop)
@ -149,6 +150,10 @@ class Client:
# internals # internals
@asyncio.coroutine
def _syncer(self, guilds):
yield from self.ws.request_sync(guilds)
def _get_cache_filename(self, email): def _get_cache_filename(self, email):
filename = hashlib.md5(email.encode('utf-8')).hexdigest() filename = hashlib.md5(email.encode('utf-8')).hexdigest()
return os.path.join(tempfile.gettempdir(), 'discord_py', filename) return os.path.join(tempfile.gettempdir(), 'discord_py', filename)
@ -295,12 +300,16 @@ class Client:
@asyncio.coroutine @asyncio.coroutine
def _login_1(self, token, **kwargs): def _login_1(self, token, **kwargs):
log.info('logging in using static token') log.info('logging in using static token')
yield from self.http.static_login(token, bot=kwargs.pop('bot', True)) is_bot = kwargs.pop('bot', True)
yield from self.http.static_login(token, bot=is_bot)
self.connection.is_bot = is_bot
self._is_logged_in.set() self._is_logged_in.set()
@asyncio.coroutine @asyncio.coroutine
def _login_2(self, email, password, **kwargs): def _login_2(self, email, password, **kwargs):
# attempt to read the token from cache # attempt to read the token from cache
self.connection.is_bot = False
if self.cache_auth: if self.cache_auth:
token = self._get_cache_token(email, password) token = self._get_cache_token(email, password)
try: try:

37
discord/gateway.py

@ -127,6 +127,13 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
INVALIDATE_SESSION INVALIDATE_SESSION
Receive only. Tells the client to invalidate the session and IDENTIFY Receive only. Tells the client to invalidate the session and IDENTIFY
again. again.
HELLO
Receive only. Tells the client the heartbeat interval.
HEARTBEAT_ACK
Receive only. Confirms receiving of a heartbeat. Not having it implies
a connection issue.
GUILD_SYNC
Send only. Requests a guild sync.
gateway gateway
The gateway we are currently connected to. The gateway we are currently connected to.
token token
@ -143,6 +150,9 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
RECONNECT = 7 RECONNECT = 7
REQUEST_MEMBERS = 8 REQUEST_MEMBERS = 8
INVALIDATE_SESSION = 9 INVALIDATE_SESSION = 9
HELLO = 10
HEARTBEAT_ACK = 11
GUILD_SYNC = 12
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, max_size=None, **kwargs) super().__init__(*args, max_size=None, **kwargs)
@ -172,6 +182,10 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
client.connection._update_references(ws) client.connection._update_references(ws)
log.info('Created websocket connected to {}'.format(gateway)) log.info('Created websocket connected to {}'.format(gateway))
# poll the event for OP HELLO
yield from ws.poll_event()
if not resume: if not resume:
yield from ws.identify() yield from ws.identify()
log.info('sent the identify payload to create the websocket') log.info('sent the identify payload to create the websocket')
@ -232,6 +246,10 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
'v': 3 'v': 3
} }
} }
if not self._connection.is_bot:
payload['d']['synced_guilds'] = []
yield from self.send_as_json(payload) yield from self.send_as_json(payload)
@asyncio.coroutine @asyncio.coroutine
@ -277,6 +295,12 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
yield from self.close() yield from self.close()
raise ReconnectWebSocket() raise ReconnectWebSocket()
if op == self.HELLO:
interval = data['heartbeat_interval'] / 1000.0
self._keep_alive = KeepAliveHandler(ws=self, interval=interval)
self._keep_alive.start()
return
if op == self.INVALIDATE_SESSION: if op == self.INVALIDATE_SESSION:
state.sequence = None state.sequence = None
state.session_id = None state.session_id = None
@ -298,11 +322,6 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
state.sequence = msg['s'] state.sequence = msg['s']
state.session_id = data['session_id'] state.session_id = data['session_id']
if is_ready or event == 'RESUMED':
interval = data['heartbeat_interval'] / 1000.0
self._keep_alive = KeepAliveHandler(ws=self, interval=interval)
self._keep_alive.start()
parser = 'parse_' + event.lower() parser = 'parse_' + event.lower()
try: try:
@ -400,6 +419,14 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
status = Status.idle if idle_since else Status.online status = Status.idle if idle_since else Status.online
me.status = status me.status = status
@asyncio.coroutine
def request_sync(self, guild_ids):
payload = {
'op': self.GUILD_SYNC,
'd': list(guild_ids)
}
yield from self.send_as_json(payload)
@asyncio.coroutine @asyncio.coroutine
def voice_state(self, guild_id, channel_id, self_mute=False, self_deaf=False): def voice_state(self, guild_id, channel_id, self_mute=False, self_deaf=False):
payload = { payload = {

2
discord/http.py

@ -497,4 +497,4 @@ class HTTPClient:
data = yield from self.get(self.GATEWAY, bucket=_func_()) data = yield from self.get(self.GATEWAY, bucket=_func_())
except HTTPException as e: except HTTPException as e:
raise GatewayNotFound() from e raise GatewayNotFound() from e
return data.get('url') + '?encoding=json&v=4' return data.get('url') + '?encoding=json&v=5'

40
discord/server.py

@ -169,7 +169,6 @@ class Server(Hashable):
self._member_count = member_count self._member_count = member_count
self.name = guild.get('name') self.name = guild.get('name')
self.large = guild.get('large', None if member_count is None else self._member_count > 250)
self.region = guild.get('region') self.region = guild.get('region')
try: try:
self.region = ServerRegion(self.region) self.region = ServerRegion(self.region)
@ -181,24 +180,36 @@ class Server(Hashable):
self.unavailable = guild.get('unavailable', False) self.unavailable = guild.get('unavailable', False)
self.id = guild['id'] self.id = guild['id']
self.roles = [Role(server=self, **r) for r in guild.get('roles', [])] self.roles = [Role(server=self, **r) for r in guild.get('roles', [])]
self._sync(guild)
self.large = None if member_count is None else self._member_count > 250
for data in guild.get('members', []): if 'owner_id' in guild:
self.owner_id = guild['owner_id']
self.owner = self.get_member(self.owner_id)
afk_id = guild.get('afk_channel_id')
self.afk_channel = self.get_channel(afk_id)
for obj in guild.get('voice_states', []):
self._update_voice_state(obj)
def _sync(self, data):
if 'large' in data:
self.large = data['large']
for mdata in data.get('members', []):
roles = [self.default_role] roles = [self.default_role]
for role_id in data['roles']: for role_id in mdata['roles']:
role = utils.find(lambda r: r.id == role_id, self.roles) role = utils.find(lambda r: r.id == role_id, self.roles)
if role is not None: if role is not None:
roles.append(role) roles.append(role)
data['roles'] = roles mdata['roles'] = roles
member = Member(**data) member = Member(**mdata)
member.server = self member.server = self
self._add_member(member) self._add_member(member)
if 'owner_id' in guild: for presence in data.get('presences', []):
self.owner_id = guild['owner_id']
self.owner = self.get_member(self.owner_id)
for presence in guild.get('presences', []):
user_id = presence['user']['id'] user_id = presence['user']['id']
member = self.get_member(user_id) member = self.get_member(user_id)
if member is not None: if member is not None:
@ -210,17 +221,12 @@ class Server(Hashable):
game = presence.get('game', {}) game = presence.get('game', {})
member.game = Game(**game) if game else None member.game = Game(**game) if game else None
if 'channels' in guild: if 'channels' in data:
channels = guild['channels'] channels = data['channels']
for c in channels: for c in channels:
channel = Channel(server=self, **c) channel = Channel(server=self, **c)
self._add_channel(channel) self._add_channel(channel)
afk_id = guild.get('afk_channel_id')
self.afk_channel = self.get_channel(afk_id)
for obj in guild.get('voice_states', []):
self._update_voice_state(obj)
@utils.cached_slot_property('_default_role') @utils.cached_slot_property('_default_role')
def default_role(self): def default_role(self):

16
discord/state.py

@ -49,11 +49,13 @@ log = logging.getLogger(__name__)
ReadyState = namedtuple('ReadyState', ('launch', 'servers')) ReadyState = namedtuple('ReadyState', ('launch', 'servers'))
class ConnectionState: class ConnectionState:
def __init__(self, dispatch, chunker, max_messages, *, loop): def __init__(self, dispatch, chunker, syncer, max_messages, *, loop):
self.loop = loop self.loop = loop
self.max_messages = max_messages self.max_messages = max_messages
self.dispatch = dispatch self.dispatch = dispatch
self.chunker = chunker self.chunker = chunker
self.syncer = syncer
self.is_bot = None
self._listeners = [] self._listeners = []
self.clear() self.clear()
@ -165,8 +167,9 @@ class ConnectionState:
launch.set() launch.set()
yield from asyncio.sleep(2) yield from asyncio.sleep(2)
# get all the chunks
servers = self._ready_state.servers servers = self._ready_state.servers
# get all the chunks
chunks = [] chunks = []
for server in servers: for server in servers:
chunks.extend(self.chunks_needed(server)) chunks.extend(self.chunks_needed(server))
@ -194,9 +197,12 @@ class ConnectionState:
servers = self._ready_state.servers servers = self._ready_state.servers
for guild in guilds: for guild in guilds:
server = self._add_server_from_data(guild) server = self._add_server_from_data(guild)
if server.large: if server.large or not self.is_bot:
servers.append(server) servers.append(server)
if not self.is_bot:
compat.create_task(self.syncer([s.id for s in self.servers]), loop=self.loop)
for pm in data.get('private_channels'): for pm in data.get('private_channels'):
self._add_private_channel(PrivateChannel(id=pm['id'], self._add_private_channel(PrivateChannel(id=pm['id'],
user=User(**pm['recipient']))) user=User(**pm['recipient'])))
@ -427,6 +433,10 @@ class ConnectionState:
else: else:
self.dispatch('server_join', server) self.dispatch('server_join', server)
def parse_guild_sync(self, data):
server = self._get_server(data.get('id'))
server._sync(data)
def parse_guild_update(self, data): def parse_guild_update(self, data):
server = self._get_server(data.get('id')) server = self._get_server(data.get('id'))
if server is not None: if server is not None:

Loading…
Cancel
Save