Browse Source

Fix call handling

pull/10109/head
dolfies 3 years ago
parent
commit
e64d0e71dd
  1. 204
      discord/calls.py
  2. 71
      discord/channel.py
  3. 3
      discord/http.py
  4. 15
      discord/state.py
  5. 1
      discord/types/gateway.py
  6. 3
      discord/types/voice.py
  7. 6
      discord/voice_client.py

204
discord/calls.py

@ -24,21 +24,19 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
import datetime import datetime
from typing import Callable, Dict, List, Optional, TYPE_CHECKING, Union from typing import Callable, Dict, List, Optional, Set, TYPE_CHECKING, Union
from . import utils from . import utils
from .errors import ClientException from .errors import ClientException
from .utils import MISSING from .utils import cached_slot_property, MISSING
if TYPE_CHECKING: if TYPE_CHECKING:
from .abc import Connectable, PrivateChannel, User as abcUser, T as ConnectReturn from .abc import Connectable, PrivateChannel, User as abcUser, Snowflake as abcSnowflake, T as ConnectReturn
from .channel import DMChannel, GroupChannel from .channel import DMChannel, GroupChannel
from .client import Client from .client import Client
from .member import VoiceState from .member import VoiceState
from .message import Message from .message import Message
from .state import ConnectionState from .state import ConnectionState
from .types.snowflake import Snowflake, SnowflakeList
from .types.voice import GuildVoiceState
from .user import User from .user import User
_PrivateChannel = Union[DMChannel, GroupChannel] _PrivateChannel = Union[DMChannel, GroupChannel]
@ -69,7 +67,7 @@ class CallMessage:
Attributes Attributes
----------- -----------
ended_timestamp: Optional[:class:`datetime.datetime`] ended_timestamp: Optional[:class:`datetime.datetime`]
A naive UTC datetime object that represents the time that the call has ended. An aware UTC datetime object that represents the time that the call has ended.
participants: List[:class:`User`] participants: List[:class:`User`]
A list of users that participated in the call. A list of users that participated in the call.
message: :class:`Message` message: :class:`Message`
@ -88,7 +86,7 @@ class CallMessage:
@property @property
def initiator(self) -> User: def initiator(self) -> User:
""":class:`User`: Returns the user that started the call.""" """:class:`.abc.User`: Returns the user that started the call."""
return self.message.author # type: ignore # Cannot be a Member in private messages return self.message.author # type: ignore # Cannot be a Member in private messages
@property @property
@ -119,16 +117,14 @@ class PrivateCall:
This is accompanied with a :class:`CallMessage` denoting the information. This is accompanied with a :class:`CallMessage` denoting the information.
.. versionadded:: 1.9
Attributes Attributes
----------- -----------
channel: :class:`DMChannel` channel: :class:`DMChannel`
The channel the call is in. The channel the call is in.
message: Optional[:class:`Message`]
The message associated with this call (if available).
unavailable: :class:`bool` unavailable: :class:`bool`
Denotes if this call is unavailable. Denotes if this call is unavailable.
ringing: List[:class:`~discord.abc.User`]
A list of users that are currently being rung to join the call.
region: :class:`str` region: :class:`str`
The region the call is being hosted at. The region the call is being hosted at.
@ -136,78 +132,89 @@ class PrivateCall:
The type of this attribute has changed to :class:`str`. The type of this attribute has changed to :class:`str`.
""" """
__slots__ = ('_state', '_ended', 'channel', '_cs_message', '_ringing', '_message_id', 'region', 'unavailable')
if TYPE_CHECKING: if TYPE_CHECKING:
channel: DMChannel channel: DMChannel
ringing: List[abcUser]
region: str
def __init__( def __init__(
self, self,
state: ConnectionState,
*, *,
message_id: Snowflake, data: dict,
channel_id: Snowflake, state: ConnectionState,
message: Optional[Message] = None, message: Optional[Message],
channel: PrivateChannel, channel: PrivateChannel,
unavailable: bool,
voice_states: List[GuildVoiceState] = [],
**kwargs,
) -> None: ) -> None:
self._state = state self._state = state
self._message_id: int = int(message_id) self._cs_message = message
self._channel_id: int = int(channel_id) self.channel = channel # type: ignore # Will always be a DMChannel here
self.message: Optional[Message] = message
self.channel = channel # type: ignore
self.unavailable: bool = unavailable
self._ended: bool = False self._ended: bool = False
for vs in voice_states: self._update(data)
state._update_voice_state(vs, int(channel_id))
self._update(**kwargs) def _delete(self) -> None:
self._ringing = tuple()
def _deleteup(self) -> None:
self.ringing = []
self._ended = True self._ended = True
def _get_recipients(self) -> Set[abcUser]:
channel = self.channel
return {channel.me, channel.recipient}
def _is_participating(self, user: abcUser) -> bool: def _is_participating(self, user: abcUser) -> bool:
state = self.voice_state_for(user) state = self.voice_state_for(user)
return bool(state and state.channel and state.channel.id == self._channel_id) return bool(state and state.channel and state.channel.id == self.channel.id)
def _update(self, data) -> None:
self._message_id = int(data['message_id'])
self.unavailable = data.get('unavailable', False)
try:
self.region: str = data['region']
except KeyError:
pass
def _update(self, *, ringing: SnowflakeList = [], region: str = MISSING) -> None:
if region is not MISSING:
self.region = region
channel = self.channel channel = self.channel
recipients = {channel.me, channel.recipient} recipients = self._get_recipients()
lookup = {u.id: u for u in recipients} lookup = {u.id: u for u in recipients}
self.ringing = list(filter(None, map(lookup.get, ringing))) self._ringing = tuple(filter(None, map(lookup.get, data.get('ringing', []))))
for vs in data.get('voice_states', []):
self._state._update_voice_state(vs, channel.id)
@property
def ringing(self) -> List[abcUser]:
"""List[:class:`.abc.User`]: A list of users that are currently being rung to join the call."""
return list(self._ringing)
@property @property
def initiator(self) -> Optional[User]: def initiator(self) -> User:
"""Optional[:class:`User`]: Returns the user that started the call. The call message must be available to obtain this information.""" """:class:`User`: Returns the user that started the call."""
if self.message: return getattr(self.message, 'author', None)
return self.message.author # type: ignore # Cannot be a Member in private messages
@property @property
def connected(self) -> bool: def connected(self) -> bool:
""":class:`bool`: Returns whether you're in the call (this does not mean you're in the call through the lib).""" """:class:`bool`: Returns whether you're in the call (this does not mean you're in the call through the library)."""
return self._is_participating(self.channel.me) return self._is_participating(self.channel.me)
@property @property
def members(self) -> List[User]: def members(self) -> List[abcUser]:
"""List[:class:`User`]: Returns all users that are currently in this call.""" """List[:class:`.abc.User`]: Returns all users that are currently in this call."""
channel = self.channel recipients = self._get_recipients()
recipients = {channel.me, channel.recipient}
return [u for u in recipients if self._is_participating(u)] return [u for u in recipients if self._is_participating(u)]
@property @property
def voice_states(self) -> Dict[int, VoiceState]: def voice_states(self) -> Dict[int, VoiceState]:
"""Mapping[:class:`int`, :class:`VoiceState`]: Returns a mapping of user IDs who have voice states in this call.""" """Mapping[:class:`int`, :class:`VoiceState`]: Returns a mapping of user IDs who have voice states in this call."""
return { return {
k: v for k, v in self._state._voice_states.items() if bool(v and v.channel and v.channel.id == self._channel_id) k: v for k, v in self._state._voice_states.items() if bool(v and v.channel and v.channel.id == self.channel.id)
} }
async def fetch_message(self) -> Optional[Message]: @cached_slot_property('_cs_message')
def message(self) -> Message:
""":class:`Message`: The message associated with this call."""
# Lying to the type checker for better developer UX, very unlikely for the message to not be received
return self._state._get_message(self._message_id) # type: ignore
async def fetch_message(self) -> Message:
"""|coro| """|coro|
Fetches and caches the message associated with this call. Fetches and caches the message associated with this call.
@ -219,15 +226,15 @@ class PrivateCall:
Returns Returns
------- -------
Optional[:class:`Message`] :class:`Message`
The message associated with this call. The message associated with this call.
""" """
message = await self.channel.fetch_message(self._message_id) message = await self.channel.fetch_message(self._message_id)
state = self._state state = self._state
if message is not None and self.message is None: if self.message is None:
if self._state._messages is not None: if state._messages is not None:
self._state._messages.append(message) state._messages.append(message)
self.message = message self._cs_message = message
return message return message
async def change_region(self, region: str) -> None: async def change_region(self, region: str) -> None:
@ -248,7 +255,7 @@ class PrivateCall:
HTTPException HTTPException
Failed to change the channel's voice region. Failed to change the channel's voice region.
""" """
await self._state.http.change_call_voice_region(self._channel_id, str(region)) await self._state.http.change_call_voice_region(self.channel.id, region)
@_running_only @_running_only
async def ring(self) -> None: async def ring(self) -> None:
@ -258,6 +265,8 @@ class PrivateCall:
Raises Raises
------- -------
Forbidden
Not allowed to ring the other recipient.
HTTPException HTTPException
Ringing failed. Ringing failed.
ClientException ClientException
@ -289,7 +298,6 @@ class PrivateCall:
timeout: float = 60.0, timeout: float = 60.0,
reconnect: bool = True, reconnect: bool = True,
cls: Callable[[Client, Connectable], ConnectReturn] = MISSING, cls: Callable[[Client, Connectable], ConnectReturn] = MISSING,
ring: bool = True,
) -> ConnectReturn: ) -> ConnectReturn:
"""|coro| """|coro|
@ -309,8 +317,6 @@ class PrivateCall:
cls: Type[:class:`~discord.VoiceProtocol`] cls: Type[:class:`~discord.VoiceProtocol`]
A type that subclasses :class:`~discord.VoiceProtocol` to connect with. A type that subclasses :class:`~discord.VoiceProtocol` to connect with.
Defaults to :class:`~discord.VoiceClient`. Defaults to :class:`~discord.VoiceClient`.
ring: :class:`bool`
Whether to ring the other user.
Raises Raises
------- -------
@ -326,7 +332,7 @@ class PrivateCall:
:class:`~discord.VoiceProtocol` :class:`~discord.VoiceProtocol`
A voice client that is fully connected to the voice server. A voice client that is fully connected to the voice server.
""" """
return await self.channel.connect(timeout=timeout, reconnect=reconnect, cls=cls, ring=ring) return await self.channel.connect(timeout=timeout, reconnect=reconnect, cls=cls, ring=False)
@_running_only @_running_only
async def join( async def join(
@ -335,7 +341,6 @@ class PrivateCall:
timeout: float = 60.0, timeout: float = 60.0,
reconnect: bool = True, reconnect: bool = True,
cls: Callable[[Client, Connectable], ConnectReturn] = MISSING, cls: Callable[[Client, Connectable], ConnectReturn] = MISSING,
ring: bool = True,
) -> ConnectReturn: ) -> ConnectReturn:
"""|coro| """|coro|
@ -355,8 +360,6 @@ class PrivateCall:
cls: Type[:class:`~discord.VoiceProtocol`] cls: Type[:class:`~discord.VoiceProtocol`]
A type that subclasses :class:`~discord.VoiceProtocol` to connect with. A type that subclasses :class:`~discord.VoiceProtocol` to connect with.
Defaults to :class:`~discord.VoiceClient`. Defaults to :class:`~discord.VoiceClient`.
ring: :class:`bool`
Whether to ring the other user.
Raises Raises
------- -------
@ -372,10 +375,10 @@ class PrivateCall:
:class:`~discord.VoiceProtocol` :class:`~discord.VoiceProtocol`
A voice client that is fully connected to the voice server. A voice client that is fully connected to the voice server.
""" """
return await self.connect(timeout=timeout, reconnect=reconnect, cls=cls, ring=ring) return await self.connect(timeout=timeout, reconnect=reconnect, cls=cls)
@_running_only @_running_only
async def disconnect(self, **kwargs) -> None: async def disconnect(self, force: bool = False) -> None:
"""|coro| """|coro|
Disconnects this voice client from voice. Disconnects this voice client from voice.
@ -386,19 +389,19 @@ class PrivateCall:
if not (client := state._get_voice_client(self.channel.me.id)): if not (client := state._get_voice_client(self.channel.me.id)):
return return
return await client.disconnect(**kwargs) return await client.disconnect(force=force)
@_running_only @_running_only
async def leave(self, **kwargs) -> None: async def leave(self, force: bool = False) -> None:
"""|coro| """|coro|
Disconnects this voice client from voice. Disconnects this voice client from voice.
This is an alias of :attr:`disconnect`. This is an alias of :attr:`disconnect`.
""" """
return await self.disconnect(**kwargs) return await self.disconnect(force=force)
def voice_state_for(self, user) -> Optional[VoiceState]: def voice_state_for(self, user: abcSnowflake) -> Optional[VoiceState]:
"""Retrieves the :class:`VoiceState` for a specified :class:`User`. """Retrieves the :class:`VoiceState` for a specified :class:`User`.
If the :class:`User` has no voice state then this function returns If the :class:`User` has no voice state then this function returns
@ -406,7 +409,7 @@ class PrivateCall:
Parameters Parameters
------------ ------------
user: :class:`User` user: :class:`~discord.abc.Snowflake`
The user to retrieve the voice state for. The user to retrieve the voice state for.
Returns Returns
@ -426,11 +429,9 @@ class GroupCall(PrivateCall):
----------- -----------
channel: :class:`GroupChannel` channel: :class:`GroupChannel`
The channel the group call is in. The channel the group call is in.
message: Optional[:class:`Message`]
The message associated with this group call (if available).
unavailable: :class:`bool` unavailable: :class:`bool`
Denotes if this group call is unavailable. Denotes if this group call is unavailable.
ringing: List[:class:`~discord.abc.User`] ringing: List[:class:`.abc.User`]
A list of users that are currently being rung to join the call. A list of users that are currently being rung to join the call.
region: :class:`str` region: :class:`str`
The region the group call is being hosted in. The region the group call is being hosted in.
@ -439,35 +440,56 @@ class GroupCall(PrivateCall):
The type of this attribute has changed to :class:`str`. The type of this attribute has changed to :class:`str`.
""" """
__slots__ = tuple()
if TYPE_CHECKING: if TYPE_CHECKING:
channel: GroupChannel channel: GroupChannel
def _update(self, *, ringing: List[int] = [], region: str = MISSING) -> None: def _get_recipients(self) -> Set[abcUser]:
if region is not MISSING: channel = self.channel
self.region = region ret: Set[abcUser] = set(channel.recipients)
ret.add(channel.me)
return ret
lookup: Dict[int, abcUser] = {u.id: u for u in self.channel.recipients} @_running_only
me = self.channel.me async def ring(self, *recipients: abcSnowflake) -> None:
lookup[me.id] = me r"""|coro|
self.ringing = list(filter(None, map(lookup.get, ringing)))
@property Rings the specified recipients.
def members(self) -> List[abcUser]:
"""List[:class:`User`]: Returns all users that are currently in this call."""
ret: List[abcUser] = [u for u in self.channel.recipients if self._is_participating(u)]
me = self.channel.me
if self._is_participating(me):
ret.append(me)
return ret Parameters
-----------
\*recipients: :class:`~discord.abc.Snowflake`
The recipients to ring. The default is to ring all recipients.
@_running_only Raises
async def ring(self, *recipients) -> None: -------
await self._state.http.ring(self._channel_id, *{r.id for r in recipients}) HTTPException
Stopping the ringing failed.
ClientException
The call has ended.
"""
await self._state.http.ring(self.channel.id, *{r.id for r in recipients})
@_running_only @_running_only
async def stop_ringing(self, *recipients) -> None: async def stop_ringing(self, *recipients: abcSnowflake) -> None:
await self._state.http.stop_ringing(self._channel_id, *{r.id for r in recipients}) r"""|coro|
Stops ringing the specified recipients.
Parameters
-----------
\*recipients: :class:`~discord.abc.Snowflake`
The recipients to stop ringing.
Raises
-------
HTTPException
Ringing failed.
ClientException
The call has ended.
"""
await self._state.http.stop_ringing(self.channel.id, *{r.id for r in recipients})
Call = Union[PrivateCall, GroupCall] Call = Union[PrivateCall, GroupCall]

71
discord/channel.py

@ -2249,8 +2249,14 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
await self._state.access_private_channel(self.id) await self._state.access_private_channel(self.id)
return self return self
def _initial_ring(self): async def _initial_ring(self) -> None:
return self._state.http.ring(self.id) ring = self.recipient.is_friend()
if not ring:
data = await self._state.http.get_ringability(self.id)
ring = data['ringable']
if ring:
await self._state.http.ring(self.id)
def __str__(self) -> str: def __str__(self) -> str:
if self.recipient: if self.recipient:
@ -2322,7 +2328,7 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
Parameters Parameters
----------- -----------
obj: :class:`User` obj: :class:`~discord.abc.Snowflake`
The user to check permissions for. This parameter is ignored The user to check permissions for. This parameter is ignored
but kept for compatibility with other ``permissions_for`` methods. but kept for compatibility with other ``permissions_for`` methods.
@ -2380,7 +2386,6 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
""" """
await self._state.http.delete_channel(self.id) await self._state.http.delete_channel(self.id)
@utils.copy_doc(discord.abc.Connectable.connect)
async def connect( async def connect(
self, self,
*, *,
@ -2389,6 +2394,40 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
cls: Callable[[Client, discord.abc.Connectable], ConnectReturn] = MISSING, cls: Callable[[Client, discord.abc.Connectable], ConnectReturn] = MISSING,
ring: bool = True, ring: bool = True,
) -> ConnectReturn: ) -> ConnectReturn:
"""|coro|
Connects to voice and creates a :class:`~discord.VoiceClient` to establish
your connection to the voice server.
Parameters
-----------
timeout: :class:`float`
The timeout in seconds to wait for the voice endpoint.
reconnect: :class:`bool`
Whether the bot should automatically attempt
a reconnect if a part of the handshake fails
or the gateway goes down.
cls: Type[:class:`~discord.VoiceProtocol`]
A type that subclasses :class:`~discord.VoiceProtocol` to connect with.
Defaults to :class:`~discord.VoiceClient`.
ring: :class:`bool`
Whether to ring the other member(s) to join the call, if starting a new call.
Defaults to ``True``.
Raises
-------
asyncio.TimeoutError
Could not connect to the voice channel in time.
~discord.ClientException
You are already connected to a voice channel.
~discord.opus.OpusNotLoaded
The opus library has not been loaded.
Returns
--------
:class:`~discord.VoiceProtocol`
A voice client that is fully connected to the voice server.
"""
await self._get_channel() await self._get_channel()
call = self.call call = self.call
if call is None and ring: if call is None and ring:
@ -2591,7 +2630,7 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
Parameters Parameters
----------- -----------
\*recipients: :class:`User` \*recipients: :class:`~discord.abc.Snowflake`
An argument list of users to add to this group. An argument list of users to add to this group.
Raises Raises
@ -2612,7 +2651,7 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
Parameters Parameters
----------- -----------
\*recipients: :class:`User` \*recipients: :class:`~discord.abc.Snowflake`
An argument list of users to remove from this group. An argument list of users to remove from this group.
Raises Raises
@ -2683,6 +2722,8 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
If you are the only one in the group, this deletes it as well. If you are the only one in the group, this deletes it as well.
There is an alias for this called :func:`close`.
Raises Raises
------- -------
HTTPException HTTPException
@ -2690,6 +2731,22 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
""" """
await self._state.http.delete_channel(self.id) await self._state.http.delete_channel(self.id)
async def close(self) -> None:
"""|coro|
Leave the group.
If you are the only one in the group, this deletes it as well.
This is an alias of :func:`leave`.
Raises
-------
HTTPException
Leaving the group failed.
"""
await self.leave()
async def create_invite(self, *, max_age: int = 86400) -> Invite: async def create_invite(self, *, max_age: int = 86400) -> Invite:
"""|coro| """|coro|
@ -2714,7 +2771,7 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
data = await self._state.http.create_group_invite(self.id, max_age=max_age) data = await self._state.http.create_group_invite(self.id, max_age=max_age)
return Invite.from_incomplete(data=data, state=self._state) return Invite.from_incomplete(data=data, state=self._state)
@utils.copy_doc(discord.abc.Connectable.connect) @utils.copy_doc(DMChannel.connect)
async def connect( async def connect(
self, self,
*, *,

3
discord/http.py

@ -1852,6 +1852,9 @@ class HTTPClient:
) -> Response[member.MemberWithUser]: ) -> Response[member.MemberWithUser]:
return self.edit_member(guild_id=guild_id, user_id=user_id, channel_id=channel_id, reason=reason) return self.edit_member(guild_id=guild_id, user_id=user_id, channel_id=channel_id, reason=reason)
def get_ringability(self, channel_id: Snowflake):
return self.request(Route('GET', '/channels/{channel_id}/call', channel_id=channel_id))
def ring(self, channel_id: Snowflake, *recipients: Snowflake) -> Response[None]: def ring(self, channel_id: Snowflake, *recipients: Snowflake) -> Response[None]:
payload = {'recipients': recipients or None} payload = {'recipients': recipients or None}

15
discord/state.py

@ -79,6 +79,7 @@ from .interactions import Interaction
from .permissions import Permissions, PermissionOverwrite from .permissions import Permissions, PermissionOverwrite
from .member import _ClientStatus from .member import _ClientStatus
from .modal import Modal from .modal import Modal
from .member import VoiceState
if TYPE_CHECKING: if TYPE_CHECKING:
from .abc import PrivateChannel, Snowflake as abcSnowflake from .abc import PrivateChannel, Snowflake as abcSnowflake
@ -89,7 +90,6 @@ if TYPE_CHECKING:
from .client import Client from .client import Client
from .gateway import DiscordWebSocket from .gateway import DiscordWebSocket
from .calls import Call from .calls import Call
from .member import VoiceState
from .types.snowflake import Snowflake from .types.snowflake import Snowflake
from .types.activity import Activity as ActivityPayload from .types.activity import Activity as ActivityPayload
@ -2098,8 +2098,8 @@ class ConnectionState:
if channel is None: if channel is None:
_log.debug('CALL_CREATE referencing unknown channel ID: %s. Discarding.', data['channel_id']) _log.debug('CALL_CREATE referencing unknown channel ID: %s. Discarding.', data['channel_id'])
return return
message = self._call_message_cache.pop((int(data['message_id'])), None) message = self._get_message(int(data['message_id']))
call = channel._add_call(state=self, message=message, channel=channel, **data) call = channel._add_call(data=data, state=self, message=message, channel=channel)
self._calls[channel.id] = call self._calls[channel.id] = call
self.dispatch('call_create', call) self.dispatch('call_create', call)
@ -2109,14 +2109,15 @@ class ConnectionState:
_log.debug('CALL_UPDATE referencing unknown call (channel ID: %s). Discarding.', data['channel_id']) _log.debug('CALL_UPDATE referencing unknown call (channel ID: %s). Discarding.', data['channel_id'])
return return
old_call = copy.copy(call) old_call = copy.copy(call)
call._update(**data) call._update(data)
self.dispatch('call_update', old_call, call) self.dispatch('call_update', old_call, call)
def parse_call_delete(self, data) -> None: def parse_call_delete(self, data) -> None:
call = self._calls.pop(int(data['channel_id']), None) call = self._calls.pop(int(data['channel_id']), None)
if call is not None: if call is not None:
call._deleteup() call._delete()
self.dispatch('call_delete', call) self._call_message_cache.pop(call._message_id, None)
self.dispatch('call_delete', call)
def parse_voice_state_update(self, data: gw.VoiceStateUpdateEvent) -> None: def parse_voice_state_update(self, data: gw.VoiceStateUpdateEvent) -> None:
guild = self._get_guild(utils._get_as_snowflake(data, 'guild_id')) guild = self._get_guild(utils._get_as_snowflake(data, 'guild_id'))
@ -2146,7 +2147,7 @@ class ConnectionState:
user, before, after = self._update_voice_state(data, channel_id) user, before, after = self._update_voice_state(data, channel_id)
self.dispatch('voice_state_update', user, before, after) self.dispatch('voice_state_update', user, before, after)
def parse_voice_server_update(self, data) -> None: def parse_voice_server_update(self, data: gw.VoiceServerUpdateEvent) -> None:
key_id = utils._get_as_snowflake(data, 'guild_id') key_id = utils._get_as_snowflake(data, 'guild_id')
if key_id is None: if key_id is None:
key_id = self.self_id key_id = self.self_id

1
discord/types/gateway.py

@ -328,6 +328,7 @@ VoiceStateUpdateEvent = GuildVoiceState
class VoiceServerUpdateEvent(TypedDict): class VoiceServerUpdateEvent(TypedDict):
token: str token: str
guild_id: Snowflake guild_id: Snowflake
channel_id: Snowflake
endpoint: Optional[str] endpoint: Optional[str]

3
discord/types/voice.py

@ -51,7 +51,7 @@ class GuildVoiceState(_VoiceState):
class VoiceState(_VoiceState, total=False): class VoiceState(_VoiceState, total=False):
channel_id: Optional[Snowflake] channel_id: Optional[Snowflake]
guild_id: Snowflake guild_id: NotRequired[Optional[Snowflake]]
class VoiceRegion(TypedDict): class VoiceRegion(TypedDict):
@ -66,6 +66,7 @@ class VoiceRegion(TypedDict):
class VoiceServerUpdate(TypedDict): class VoiceServerUpdate(TypedDict):
token: str token: str
guild_id: Snowflake guild_id: Snowflake
channel_id: Snowflake
endpoint: Optional[str] endpoint: Optional[str]

6
discord/voice_client.py

@ -51,7 +51,7 @@ from .backoff import ExponentialBackoff
from .gateway import * from .gateway import *
from .errors import ClientException, ConnectionClosed from .errors import ClientException, ConnectionClosed
from .player import AudioPlayer, AudioSource from .player import AudioPlayer, AudioSource
from .utils import MISSING from .utils import _get_as_snowflake, MISSING
if TYPE_CHECKING: if TYPE_CHECKING:
from .client import Client from .client import Client
@ -311,7 +311,7 @@ class VoiceClient(VoiceProtocol):
return return
self.token = data['token'] self.token = data['token']
self.server_id = int(data['guild_id']) self.server_id = _get_as_snowflake(data, 'guild_id') or int(data['channel_id'])
endpoint = data.get('endpoint') endpoint = data.get('endpoint')
if endpoint is None or self.token is None: if endpoint is None or self.token is None:
@ -386,7 +386,7 @@ class VoiceClient(VoiceProtocol):
for i in range(5): for i in range(5):
self.prepare_handshake() self.prepare_handshake()
# This has to be created before we start the flow. # This has to be created before we start the flow
futures = [ futures = [
self._voice_state_complete.wait(), self._voice_state_complete.wait(),
self._voice_server_complete.wait(), self._voice_server_complete.wait(),

Loading…
Cancel
Save