Fix code style issues with Black

This commit is contained in:
Lint Action
2021-09-05 21:34:20 +00:00
parent a23dae8604
commit 7513c2138f
108 changed files with 5369 additions and 4858 deletions

View File

@ -24,7 +24,20 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations
from typing import TYPE_CHECKING, TypedDict, Any, Optional, List, TypeVar, Type, Dict, Callable, Coroutine, NamedTuple, Deque
from typing import (
TYPE_CHECKING,
TypedDict,
Any,
Optional,
List,
TypeVar,
Type,
Dict,
Callable,
Coroutine,
NamedTuple,
Deque,
)
import asyncio
from collections import deque
@ -42,16 +55,16 @@ import aiohttp
from . import utils
from .activity import BaseActivity
from .enums import SpeakingState
from .errors import ConnectionClosed, InvalidArgument
from .errors import ConnectionClosed, InvalidArgument
if TYPE_CHECKING:
from .client import Client
from .state import ConnectionState
from .voice_client import VoiceClient
T = TypeVar('T')
DWS = TypeVar('DWS', bound='DiscordWebSocket')
DVWS = TypeVar('DVWS', bound='DiscordVoiceWebSocket')
T = TypeVar("T")
DWS = TypeVar("DWS", bound="DiscordWebSocket")
DVWS = TypeVar("DVWS", bound="DiscordVoiceWebSocket")
Coro = Callable[..., Coroutine[Any, Any, Any]]
Predicate = Callable[[Dict[str, Any]], bool]
@ -63,11 +76,11 @@ _log: logging.Logger = logging.getLogger(__name__)
__all__ = (
'DiscordWebSocket',
'KeepAliveHandler',
'VoiceKeepAliveHandler',
'DiscordVoiceWebSocket',
'ReconnectWebSocket',
"DiscordWebSocket",
"KeepAliveHandler",
"VoiceKeepAliveHandler",
"DiscordVoiceWebSocket",
"ReconnectWebSocket",
)
@ -78,14 +91,16 @@ class Heartbeat(TypedDict):
class ReconnectWebSocket(Exception):
"""Signals to safely reconnect the websocket."""
def __init__(self, shard_id: Optional[int], *, resume: bool = True) -> None:
self.shard_id: Optional[int] = shard_id
self.resume: bool = resume
self.op = 'RESUME' if resume else 'IDENTIFY'
self.op = "RESUME" if resume else "IDENTIFY"
class WebSocketClosure(Exception):
"""An exception to make up for the fact that aiohttp doesn't signal closure."""
pass
@ -134,48 +149,50 @@ class GatewayRatelimiter:
async with self.lock:
delta = self.get_delay()
if delta:
_log.warning('WebSocket in shard ID %s is ratelimited, waiting %.2f seconds', self.shard_id, delta)
_log.warning("WebSocket in shard ID %s is ratelimited, waiting %.2f seconds", self.shard_id, delta)
await asyncio.sleep(delta)
class KeepAliveHandler(threading.Thread):
def __init__(self, *args: Any, **kwargs: Any) -> None:
ws = kwargs.pop('ws')
interval = kwargs.pop('interval', None)
shard_id = kwargs.pop('shard_id', None)
ws = kwargs.pop("ws")
interval = kwargs.pop("interval", None)
shard_id = kwargs.pop("shard_id", None)
threading.Thread.__init__(self, *args, **kwargs)
self.ws: DiscordWebSocket = ws
self._main_thread_id: int = ws.thread_id
self.interval: Optional[float] = interval
self.daemon: bool = True
self.shard_id: Optional[int] = shard_id
self.msg: str = 'Keeping shard ID %s websocket alive with sequence %s.'
self.block_msg: str = 'Shard ID %s heartbeat blocked for more than %s seconds.'
self.behind_msg: str = 'Can\'t keep up, shard ID %s websocket is %.1fs behind.'
self.msg: str = "Keeping shard ID %s websocket alive with sequence %s."
self.block_msg: str = "Shard ID %s heartbeat blocked for more than %s seconds."
self.behind_msg: str = "Can't keep up, shard ID %s websocket is %.1fs behind."
self._stop_ev: threading.Event = threading.Event()
self._last_ack: float = time.perf_counter()
self._last_send: float = time.perf_counter()
self._last_recv: float = time.perf_counter()
self.latency: float = float('inf')
self.latency: float = float("inf")
self.heartbeat_timeout: float = ws._max_heartbeat_timeout
def run(self) -> None:
while not self._stop_ev.wait(self.interval):
if self._last_recv + self.heartbeat_timeout < time.perf_counter():
_log.warning("Shard ID %s has stopped responding to the gateway. Closing and restarting.", self.shard_id)
_log.warning(
"Shard ID %s has stopped responding to the gateway. Closing and restarting.", self.shard_id
)
coro = self.ws.close(4000)
f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop)
try:
f.result()
except Exception:
_log.exception('An error occurred while stopping the gateway. Ignoring.')
_log.exception("An error occurred while stopping the gateway. Ignoring.")
finally:
self.stop()
return
data = self.get_payload()
_log.debug(self.msg, self.shard_id, data['d'])
_log.debug(self.msg, self.shard_id, data["d"])
coro = self.ws.send_heartbeat(data)
f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop)
try:
@ -192,8 +209,8 @@ class KeepAliveHandler(threading.Thread):
except KeyError:
msg = self.block_msg
else:
stack = ''.join(traceback.format_stack(frame))
msg = f'{self.block_msg}\nLoop thread traceback (most recent call last):\n{stack}'
stack = "".join(traceback.format_stack(frame))
msg = f"{self.block_msg}\nLoop thread traceback (most recent call last):\n{stack}"
_log.warning(msg, self.shard_id, total)
except Exception:
@ -203,9 +220,9 @@ class KeepAliveHandler(threading.Thread):
def get_payload(self) -> Heartbeat:
return {
'op': self.ws.HEARTBEAT,
"op": self.ws.HEARTBEAT,
# the websocket's sequence won't be None here
'd': self.ws.sequence # type: ignore
"d": self.ws.sequence, # type: ignore
}
def stop(self) -> None:
@ -221,19 +238,17 @@ class KeepAliveHandler(threading.Thread):
if self.latency > 10:
_log.warning(self.behind_msg, self.shard_id, self.latency)
class VoiceKeepAliveHandler(KeepAliveHandler):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.recent_ack_latencies: Deque[float] = deque(maxlen=20)
self.msg = 'Keeping shard ID %s voice websocket alive with timestamp %s.'
self.block_msg = 'Shard ID %s voice heartbeat blocked for more than %s seconds'
self.behind_msg = 'High socket latency, shard ID %s heartbeat is %.1fs behind'
self.msg = "Keeping shard ID %s voice websocket alive with timestamp %s."
self.block_msg = "Shard ID %s voice heartbeat blocked for more than %s seconds"
self.behind_msg = "High socket latency, shard ID %s heartbeat is %.1fs behind"
def get_payload(self) -> Heartbeat:
return {
'op': self.ws.HEARTBEAT,
'd': int(time.time() * 1000)
}
return {"op": self.ws.HEARTBEAT, "d": int(time.time() * 1000)}
def ack(self) -> None:
ack_time = time.perf_counter()
@ -244,7 +259,7 @@ class VoiceKeepAliveHandler(KeepAliveHandler):
class DiscordClientWebSocketResponse(aiohttp.ClientWebSocketResponse):
async def close(self, *, code: int = 4000, message: bytes = b'') -> bool:
async def close(self, *, code: int = 4000, message: bytes = b"") -> bool:
return await super().close(code=code, message=message)
@ -288,19 +303,19 @@ class DiscordWebSocket:
The authentication token for discord.
"""
DISPATCH = 0
HEARTBEAT = 1
IDENTIFY = 2
PRESENCE = 3
VOICE_STATE = 4
VOICE_PING = 5
RESUME = 6
RECONNECT = 7
REQUEST_MEMBERS = 8
DISPATCH = 0
HEARTBEAT = 1
IDENTIFY = 2
PRESENCE = 3
VOICE_STATE = 4
VOICE_PING = 5
RESUME = 6
RECONNECT = 7
REQUEST_MEMBERS = 8
INVALIDATE_SESSION = 9
HELLO = 10
HEARTBEAT_ACK = 11
GUILD_SYNC = 12
HELLO = 10
HEARTBEAT_ACK = 11
GUILD_SYNC = 12
def __init__(self, socket: aiohttp.ClientWebSocketResponse, *, loop: asyncio.AbstractEventLoop) -> None:
self.socket: aiohttp.ClientWebSocketResponse = socket
@ -342,13 +357,23 @@ class DiscordWebSocket:
return self._rate_limiter.is_ratelimited()
def debug_log_receive(self, data, /) -> None:
self._dispatch('socket_raw_receive', data)
self._dispatch("socket_raw_receive", data)
def log_receive(self, _, /) -> None:
pass
@classmethod
async def from_client(cls: Type[DWS], client: Client, *, initial: bool = False, gateway: Optional[str] = None, shard_id: Optional[int] = None, session: Optional[str] = None, sequence: Optional[int] = None, resume: bool = False) -> DWS:
async def from_client(
cls: Type[DWS],
client: Client,
*,
initial: bool = False,
gateway: Optional[str] = None,
shard_id: Optional[int] = None,
session: Optional[str] = None,
sequence: Optional[int] = None,
resume: bool = False,
) -> DWS:
"""Creates a main websocket for Discord from a :class:`Client`.
This is for internal use only.
@ -360,7 +385,7 @@ class DiscordWebSocket:
# dynamically add attributes needed
# the token won't be None here
ws.token = client.http.token # type: ignore
ws.token = client.http.token # type: ignore
ws._connection = client._connection
ws._discord_parsers = client._connection.parsers
ws._dispatch = client.dispatch
@ -380,7 +405,7 @@ class DiscordWebSocket:
client._connection._update_references(ws)
_log.debug('Created websocket connected to %s', gateway)
_log.debug("Created websocket connected to %s", gateway)
# poll event for OP Hello
await ws.poll_event()
@ -420,77 +445,64 @@ class DiscordWebSocket:
async def identify(self) -> None:
"""Sends the IDENTIFY packet."""
payload = {
'op': self.IDENTIFY,
'd': {
'token': self.token,
'properties': {
'$os': sys.platform,
'$browser': 'discord.py',
'$device': 'discord.py',
'$referrer': '',
'$referring_domain': ''
"op": self.IDENTIFY,
"d": {
"token": self.token,
"properties": {
"$os": sys.platform,
"$browser": "discord.py",
"$device": "discord.py",
"$referrer": "",
"$referring_domain": "",
},
'compress': True,
'large_threshold': 250,
'v': 3
}
"compress": True,
"large_threshold": 250,
"v": 3,
},
}
if self.shard_id is not None and self.shard_count is not None:
payload['d']['shard'] = [self.shard_id, self.shard_count]
payload["d"]["shard"] = [self.shard_id, self.shard_count]
state = self._connection
if state._activity is not None or state._status is not None:
payload['d']['presence'] = {
'status': state._status,
'game': state._activity,
'since': 0,
'afk': False
}
payload["d"]["presence"] = {"status": state._status, "game": state._activity, "since": 0, "afk": False}
if state._intents is not None:
payload['d']['intents'] = state._intents.value
payload["d"]["intents"] = state._intents.value
await self.call_hooks('before_identify', self.shard_id, initial=self._initial_identify)
await self.call_hooks("before_identify", self.shard_id, initial=self._initial_identify)
await self.send_as_json(payload)
_log.info('Shard ID %s has sent the IDENTIFY payload.', self.shard_id)
_log.info("Shard ID %s has sent the IDENTIFY payload.", self.shard_id)
async def resume(self) -> None:
"""Sends the RESUME packet."""
payload = {
'op': self.RESUME,
'd': {
'seq': self.sequence,
'session_id': self.session_id,
'token': self.token
}
}
payload = {"op": self.RESUME, "d": {"seq": self.sequence, "session_id": self.session_id, "token": self.token}}
await self.send_as_json(payload)
_log.info('Shard ID %s has sent the RESUME payload.', self.shard_id)
_log.info("Shard ID %s has sent the RESUME payload.", self.shard_id)
async def received_message(self, msg, /) -> None:
async def received_message(self, msg, /) -> None:
if type(msg) is bytes:
self._buffer.extend(msg)
if len(msg) < 4 or msg[-4:] != b'\x00\x00\xff\xff':
if len(msg) < 4 or msg[-4:] != b"\x00\x00\xff\xff":
return
msg = self._zlib.decompress(self._buffer)
msg = msg.decode('utf-8')
msg = msg.decode("utf-8")
self._buffer = bytearray()
self.log_receive(msg)
msg = utils._from_json(msg)
_log.debug('For Shard ID %s: WebSocket Event: %s', self.shard_id, msg)
event = msg.get('t')
_log.debug("For Shard ID %s: WebSocket Event: %s", self.shard_id, msg)
event = msg.get("t")
if event:
self._dispatch('socket_event_type', event)
self._dispatch("socket_event_type", event)
op = msg.get('op')
data = msg.get('d')
seq = msg.get('s')
op = msg.get("op")
data = msg.get("d")
seq = msg.get("s")
if seq is not None:
self.sequence = seq
@ -502,7 +514,7 @@ class DiscordWebSocket:
# "reconnect" can only be handled by the Client
# so we terminate our connection and raise an
# internal exception signalling to reconnect.
_log.debug('Received RECONNECT opcode.')
_log.debug("Received RECONNECT opcode.")
await self.close()
raise ReconnectWebSocket(self.shard_id)
@ -518,7 +530,7 @@ class DiscordWebSocket:
return
if op == self.HELLO:
interval = data['heartbeat_interval'] / 1000.0
interval = data["heartbeat_interval"] / 1000.0
self._keep_alive = KeepAliveHandler(ws=self, interval=interval, shard_id=self.shard_id)
# send a heartbeat immediately
await self.send_as_json(self._keep_alive.get_payload())
@ -532,33 +544,41 @@ class DiscordWebSocket:
self.sequence = None
self.session_id = None
_log.info('Shard ID %s session has been invalidated.', self.shard_id)
_log.info("Shard ID %s session has been invalidated.", self.shard_id)
await self.close(code=1000)
raise ReconnectWebSocket(self.shard_id, resume=False)
_log.warning('Unknown OP code %s.', op)
_log.warning("Unknown OP code %s.", op)
return
if event == 'READY':
self._trace = trace = data.get('_trace', [])
self.sequence = msg['s']
self.session_id = data['session_id']
if event == "READY":
self._trace = trace = data.get("_trace", [])
self.sequence = msg["s"]
self.session_id = data["session_id"]
# pass back shard ID to ready handler
data['__shard_id__'] = self.shard_id
_log.info('Shard ID %s has connected to Gateway: %s (Session ID: %s).',
self.shard_id, ', '.join(trace), self.session_id)
data["__shard_id__"] = self.shard_id
_log.info(
"Shard ID %s has connected to Gateway: %s (Session ID: %s).",
self.shard_id,
", ".join(trace),
self.session_id,
)
elif event == 'RESUMED':
self._trace = trace = data.get('_trace', [])
elif event == "RESUMED":
self._trace = trace = data.get("_trace", [])
# pass back the shard ID to the resumed handler
data['__shard_id__'] = self.shard_id
_log.info('Shard ID %s has successfully RESUMED session %s under trace %s.',
self.shard_id, self.session_id, ', '.join(trace))
data["__shard_id__"] = self.shard_id
_log.info(
"Shard ID %s has successfully RESUMED session %s under trace %s.",
self.shard_id,
self.session_id,
", ".join(trace),
)
try:
func = self._discord_parsers[event]
except KeyError:
_log.debug('Unknown event %s.', event)
_log.debug("Unknown event %s.", event)
else:
func(data)
@ -591,7 +611,7 @@ class DiscordWebSocket:
def latency(self) -> float:
""":class:`float`: Measures latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds."""
heartbeat = self._keep_alive
return float('inf') if heartbeat is None else heartbeat.latency
return float("inf") if heartbeat is None else heartbeat.latency
def _can_handle_close(self) -> bool:
code = self._close_code or self.socket.close_code
@ -612,10 +632,10 @@ class DiscordWebSocket:
elif msg.type is aiohttp.WSMsgType.BINARY:
await self.received_message(msg.data)
elif msg.type is aiohttp.WSMsgType.ERROR:
_log.debug('Received %s', msg)
_log.debug("Received %s", msg)
raise msg.data
elif msg.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSING, aiohttp.WSMsgType.CLOSE):
_log.debug('Received %s', msg)
_log.debug("Received %s", msg)
raise WebSocketClosure
except (asyncio.TimeoutError, WebSocketClosure) as e:
# Ensure the keep alive handler is closed
@ -624,20 +644,20 @@ class DiscordWebSocket:
self._keep_alive = None
if isinstance(e, asyncio.TimeoutError):
_log.info('Timed out receiving packet. Attempting a reconnect.')
_log.info("Timed out receiving packet. Attempting a reconnect.")
raise ReconnectWebSocket(self.shard_id) from None
code = self._close_code or self.socket.close_code
if self._can_handle_close():
_log.info('Websocket closed with %s, attempting a reconnect.', code)
_log.info("Websocket closed with %s, attempting a reconnect.", code)
raise ReconnectWebSocket(self.shard_id) from None
else:
_log.info('Websocket closed with %s, cannot reconnect.', code)
_log.info("Websocket closed with %s, cannot reconnect.", code)
raise ConnectionClosed(self.socket, shard_id=self.shard_id, code=code) from None
async def debug_send(self, data, /) -> None:
await self._rate_limiter.block()
self._dispatch('socket_raw_send', data)
self._dispatch("socket_raw_send", data)
await self.socket.send_str(data)
async def send(self, data, /) -> None:
@ -659,65 +679,57 @@ class DiscordWebSocket:
if not self._can_handle_close():
raise ConnectionClosed(self.socket, shard_id=self.shard_id) from exc
async def change_presence(self, *, activity: Optional[BaseActivity] = None, status: Optional[str] = None, since: float = 0.0) -> None:
async def change_presence(
self, *, activity: Optional[BaseActivity] = None, status: Optional[str] = None, since: float = 0.0
) -> None:
if activity is not None:
if not isinstance(activity, BaseActivity):
raise InvalidArgument('activity must derive from BaseActivity.')
raise InvalidArgument("activity must derive from BaseActivity.")
activities = [activity.to_dict()]
else:
activities = []
if status == 'idle':
if status == "idle":
since = int(time.time() * 1000)
payload = {
'op': self.PRESENCE,
'd': {
'activities': activities,
'afk': False,
'since': since,
'status': status
}
}
payload = {"op": self.PRESENCE, "d": {"activities": activities, "afk": False, "since": since, "status": status}}
sent = utils._to_json(payload)
_log.debug('Sending "%s" to change status', sent)
await self.send(sent)
async def request_chunks(self, guild_id: int, query: Optional[str] = None, *, limit: int, user_ids: Optional[List[int]] = None, presences: bool = False, nonce: Optional[int] = None) -> None:
payload = {
'op': self.REQUEST_MEMBERS,
'd': {
'guild_id': guild_id,
'presences': presences,
'limit': limit
}
}
async def request_chunks(
self,
guild_id: int,
query: Optional[str] = None,
*,
limit: int,
user_ids: Optional[List[int]] = None,
presences: bool = False,
nonce: Optional[int] = None,
) -> None:
payload = {"op": self.REQUEST_MEMBERS, "d": {"guild_id": guild_id, "presences": presences, "limit": limit}}
if nonce:
payload['d']['nonce'] = nonce
payload["d"]["nonce"] = nonce
if user_ids:
payload['d']['user_ids'] = user_ids
payload["d"]["user_ids"] = user_ids
if query is not None:
payload['d']['query'] = query
payload["d"]["query"] = query
await self.send_as_json(payload)
async def voice_state(self, guild_id: int, channel_id: int, self_mute: bool = False, self_deaf: bool = False) -> None:
async def voice_state(
self, guild_id: int, channel_id: int, self_mute: bool = False, self_deaf: bool = False
) -> None:
payload = {
'op': self.VOICE_STATE,
'd': {
'guild_id': guild_id,
'channel_id': channel_id,
'self_mute': self_mute,
'self_deaf': self_deaf
}
"op": self.VOICE_STATE,
"d": {"guild_id": guild_id, "channel_id": channel_id, "self_mute": self_mute, "self_deaf": self_deaf},
}
_log.debug('Updating our voice state to %s.', payload)
_log.debug("Updating our voice state to %s.", payload)
await self.send_as_json(payload)
async def close(self, code: int = 4000) -> None:
@ -728,6 +740,7 @@ class DiscordWebSocket:
self._close_code = code
await self.socket.close(code=code)
class DiscordVoiceWebSocket:
"""Implements the websocket protocol for handling voice connections.
@ -759,20 +772,22 @@ class DiscordVoiceWebSocket:
Receive only. Indicates a user has disconnected from voice.
"""
IDENTIFY = 0
SELECT_PROTOCOL = 1
READY = 2
HEARTBEAT = 3
IDENTIFY = 0
SELECT_PROTOCOL = 1
READY = 2
HEARTBEAT = 3
SESSION_DESCRIPTION = 4
SPEAKING = 5
HEARTBEAT_ACK = 6
RESUME = 7
HELLO = 8
RESUMED = 9
CLIENT_CONNECT = 12
CLIENT_DISCONNECT = 13
SPEAKING = 5
HEARTBEAT_ACK = 6
RESUME = 7
HELLO = 8
RESUMED = 9
CLIENT_CONNECT = 12
CLIENT_DISCONNECT = 13
def __init__(self, socket: aiohttp.ClientWebSocketResponse, loop: asyncio.AbstractEventLoop, *, hook: Optional[Coro] = None) -> None:
def __init__(
self, socket: aiohttp.ClientWebSocketResponse, loop: asyncio.AbstractEventLoop, *, hook: Optional[Coro] = None
) -> None:
self.ws: aiohttp.ClientWebSocketResponse = socket
self.loop: asyncio.AbstractEventLoop = loop
self._keep_alive: VoiceKeepAliveHandler = utils.MISSING
@ -784,14 +799,13 @@ class DiscordVoiceWebSocket:
self.thread_id: int = utils.MISSING
if hook:
# we want to redeclare self._hook
self._hook = hook # type: ignore
self._hook = hook # type: ignore
async def _hook(self, *args: Any) -> Any:
pass
async def send_as_json(self, data) -> None:
_log.debug('Sending voice websocket frame: %s.', data)
_log.debug("Sending voice websocket frame: %s.", data)
await self.ws.send_str(utils._to_json(data))
send_heartbeat = send_as_json
@ -799,32 +813,30 @@ class DiscordVoiceWebSocket:
async def resume(self) -> None:
state = self._connection
payload = {
'op': self.RESUME,
'd': {
'token': state.token,
'server_id': str(state.server_id),
'session_id': state.session_id
}
"op": self.RESUME,
"d": {"token": state.token, "server_id": str(state.server_id), "session_id": state.session_id},
}
await self.send_as_json(payload)
async def identify(self):
state = self._connection
payload = {
'op': self.IDENTIFY,
'd': {
'server_id': str(state.server_id),
'user_id': str(state.user.id),
'session_id': state.session_id,
'token': state.token
}
"op": self.IDENTIFY,
"d": {
"server_id": str(state.server_id),
"user_id": str(state.user.id),
"session_id": state.session_id,
"token": state.token,
},
}
await self.send_as_json(payload)
@classmethod
async def from_client(cls: Type[DVWS], client: VoiceClient, *, resume: bool = False, hook: Optional[Coro] = None) -> DVWS:
async def from_client(
cls: Type[DVWS], client: VoiceClient, *, resume: bool = False, hook: Optional[Coro] = None
) -> DVWS:
"""Creates a voice websocket for the :class:`VoiceClient`."""
gateway = 'wss://' + client.endpoint + '/?v=4'
gateway = "wss://" + client.endpoint + "/?v=4"
http = client._state.http
socket = await http.ws_connect(gateway, compress=15)
ws = cls(socket, loop=client.loop, hook=hook)
@ -842,57 +854,38 @@ class DiscordVoiceWebSocket:
async def select_protocol(self, ip, port, mode) -> None:
payload = {
'op': self.SELECT_PROTOCOL,
'd': {
'protocol': 'udp',
'data': {
'address': ip,
'port': port,
'mode': mode
}
}
"op": self.SELECT_PROTOCOL,
"d": {"protocol": "udp", "data": {"address": ip, "port": port, "mode": mode}},
}
await self.send_as_json(payload)
async def client_connect(self) -> None:
payload = {
'op': self.CLIENT_CONNECT,
'd': {
'audio_ssrc': self._connection.ssrc
}
}
payload = {"op": self.CLIENT_CONNECT, "d": {"audio_ssrc": self._connection.ssrc}}
await self.send_as_json(payload)
async def speak(self, state=SpeakingState.voice) -> None:
payload = {
'op': self.SPEAKING,
'd': {
'speaking': int(state),
'delay': 0
}
}
payload = {"op": self.SPEAKING, "d": {"speaking": int(state), "delay": 0}}
await self.send_as_json(payload)
async def received_message(self, msg) -> None:
_log.debug('Voice websocket frame received: %s', msg)
op = msg['op']
data = msg.get('d')
async def received_message(self, msg) -> None:
_log.debug("Voice websocket frame received: %s", msg)
op = msg["op"]
data = msg.get("d")
if op == self.READY:
await self.initial_connection(data)
elif op == self.HEARTBEAT_ACK:
self._keep_alive.ack()
elif op == self.RESUMED:
_log.info('Voice RESUME succeeded.')
_log.info("Voice RESUME succeeded.")
elif op == self.SESSION_DESCRIPTION:
self._connection.mode = data['mode']
self._connection.mode = data["mode"]
await self.load_secret_key(data)
elif op == self.HELLO:
interval = data['heartbeat_interval'] / 1000.0
interval = data["heartbeat_interval"] / 1000.0
self._keep_alive = VoiceKeepAliveHandler(ws=self, interval=min(interval, 5.0))
self._keep_alive.start()
@ -900,53 +893,52 @@ class DiscordVoiceWebSocket:
async def initial_connection(self, data) -> None:
state = self._connection
state.ssrc = data['ssrc']
state.voice_port = data['port']
state.endpoint_ip = data['ip']
state.ssrc = data["ssrc"]
state.voice_port = data["port"]
state.endpoint_ip = data["ip"]
packet = bytearray(70)
struct.pack_into('>H', packet, 0, 1) # 1 = Send
struct.pack_into('>H', packet, 2, 70) # 70 = Length
struct.pack_into('>I', packet, 4, state.ssrc)
struct.pack_into(">H", packet, 0, 1) # 1 = Send
struct.pack_into(">H", packet, 2, 70) # 70 = Length
struct.pack_into(">I", packet, 4, state.ssrc)
state.socket.sendto(packet, (state.endpoint_ip, state.voice_port))
recv = await self.loop.sock_recv(state.socket, 70)
_log.debug('received packet in initial_connection: %s', recv)
_log.debug("received packet in initial_connection: %s", recv)
# the ip is ascii starting at the 4th byte and ending at the first null
ip_start = 4
ip_end = recv.index(0, ip_start)
state.ip = recv[ip_start:ip_end].decode('ascii')
state.ip = recv[ip_start:ip_end].decode("ascii")
state.port = struct.unpack_from('>H', recv, len(recv) - 2)[0]
_log.debug('detected ip: %s port: %s', state.ip, state.port)
state.port = struct.unpack_from(">H", recv, len(recv) - 2)[0]
_log.debug("detected ip: %s port: %s", state.ip, state.port)
# there *should* always be at least one supported mode (xsalsa20_poly1305)
modes = [mode for mode in data['modes'] if mode in self._connection.supported_modes]
_log.debug('received supported encryption modes: %s', ", ".join(modes))
modes = [mode for mode in data["modes"] if mode in self._connection.supported_modes]
_log.debug("received supported encryption modes: %s", ", ".join(modes))
mode = modes[0]
await self.select_protocol(state.ip, state.port, mode)
_log.info('selected the voice protocol for use (%s)', mode)
_log.info("selected the voice protocol for use (%s)", mode)
@property
def latency(self) -> float:
""":class:`float`: Latency between a HEARTBEAT and its HEARTBEAT_ACK in seconds."""
heartbeat = self._keep_alive
return float('inf') if heartbeat is None else heartbeat.latency
return float("inf") if heartbeat is None else heartbeat.latency
@property
def average_latency(self) -> float:
""":class:`list`: Average of last 20 HEARTBEAT latencies."""
heartbeat = self._keep_alive
if heartbeat is None or not heartbeat.recent_ack_latencies:
return float('inf')
return float("inf")
return sum(heartbeat.recent_ack_latencies) / len(heartbeat.recent_ack_latencies)
async def load_secret_key(self, data) -> None:
_log.info('received secret key for voice connection')
self.secret_key = self._connection.secret_key = data.get('secret_key')
_log.info("received secret key for voice connection")
self.secret_key = self._connection.secret_key = data.get("secret_key")
await self.speak()
await self.speak(False)
@ -956,10 +948,10 @@ class DiscordVoiceWebSocket:
if msg.type is aiohttp.WSMsgType.TEXT:
await self.received_message(utils._from_json(msg.data))
elif msg.type is aiohttp.WSMsgType.ERROR:
_log.debug('Received %s', msg)
_log.debug("Received %s", msg)
raise ConnectionClosed(self.ws, shard_id=None) from msg.data
elif msg.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.CLOSING):
_log.debug('Received %s', msg)
_log.debug("Received %s", msg)
raise ConnectionClosed(self.ws, shard_id=None, code=self._close_code)
async def close(self, code: int = 1000) -> None: