Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/Rapptz/discord.py into v2…
Browse files Browse the repository at this point in the history
…-martine
  • Loading branch information
PredaaA committed Apr 21, 2024
2 parents ce4dc49 + d853a3f commit 822cffe
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 38 deletions.
8 changes: 4 additions & 4 deletions discord/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -3694,7 +3694,7 @@ async def kick(self, user: Snowflake, *, reason: Optional[str] = None) -> None:
Parameters
-----------
user: :class:`abc.Snowflake`
The user to kick from their guild.
The user to kick from the guild.
reason: Optional[:class:`str`]
The reason the user got kicked.
Expand Down Expand Up @@ -3726,7 +3726,7 @@ async def ban(
Parameters
-----------
user: :class:`abc.Snowflake`
The user to ban from their guild.
The user to ban from the guild.
delete_message_days: :class:`int`
The number of days worth of messages to delete from the user
in the guild. The minimum is 0 and the maximum is 7.
Expand Down Expand Up @@ -3808,14 +3808,14 @@ async def bulk_ban(
The users must meet the :class:`abc.Snowflake` abc.
You must have :attr:`~Permissions.ban_members` to do this.
You must have :attr:`~Permissions.ban_members` and :attr:`~Permissions.manage_guild` to do this.
.. versionadded:: 2.4
Parameters
-----------
users: Iterable[:class:`abc.Snowflake`]
The user to ban from their guild.
The users to ban from the guild, up to 200 users.
delete_message_seconds: :class:`int`
The number of seconds worth of messages to delete from the user
in the guild. The minimum is 0 and the maximum is 604800 (7 days).
Expand Down
3 changes: 2 additions & 1 deletion discord/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,8 @@ def _do_run(self) -> None:
delay = max(0, self.DELAY + (next_time - time.perf_counter()))
time.sleep(delay)

self.send_silence()
if client.is_connected():
self.send_silence()

def run(self) -> None:
try:
Expand Down
6 changes: 3 additions & 3 deletions discord/raw_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class RawMessageUpdateEvent(_RawReprMixin):
.. versionadded:: 1.7
data: :class:`dict`
The raw data given by the :ddocs:`gateway <topics/gateway#message-update>`
The raw data given by the :ddocs:`gateway <topics/gateway-events#message-update>`
cached_message: Optional[:class:`Message`]
The cached message, if found in the internal message cache. Represents the message before
it is modified by the data in :attr:`RawMessageUpdateEvent.data`.
Expand Down Expand Up @@ -355,7 +355,7 @@ class RawThreadUpdateEvent(_RawReprMixin):
parent_id: :class:`int`
The ID of the channel the thread belongs to.
data: :class:`dict`
The raw data given by the :ddocs:`gateway <topics/gateway#thread-update>`
The raw data given by the :ddocs:`gateway <topics/gateway-events#thread-update>`
thread: Optional[:class:`discord.Thread`]
The thread, if it could be found in the internal cache.
"""
Expand Down Expand Up @@ -414,7 +414,7 @@ class RawThreadMembersUpdate(_RawReprMixin):
member_count: :class:`int`
The approximate number of members in the thread. This caps at 50.
data: :class:`dict`
The raw data given by the :ddocs:`gateway <topics/gateway#thread-members-update>`.
The raw data given by the :ddocs:`gateway <topics/gateway-events#thread-members-update>`.
"""

__slots__ = ('thread_id', 'guild_id', 'member_count', 'data')
Expand Down
16 changes: 8 additions & 8 deletions discord/ui/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,16 +624,15 @@ async def schedule_dynamic_item_call(

view = View.from_message(interaction.message, timeout=None)

base_item_index: Optional[int] = None
for index, child in enumerate(view._children):
if child.type.value == component_type and getattr(child, 'custom_id', None) == custom_id:
base_item_index = index
break

if base_item_index is None:
try:
base_item_index, base_item = next(
(index, child)
for index, child in enumerate(view._children)
if child.type.value == component_type and getattr(child, 'custom_id', None) == custom_id
)
except StopIteration:
return

base_item = view._children[base_item_index]
try:
item = await factory.from_custom_id(interaction, base_item, match)
except Exception:
Expand All @@ -643,6 +642,7 @@ async def schedule_dynamic_item_call(
# Swap the item in the view with our new dynamic item
view._children[base_item_index] = item
item._view = view
item._rendered_row = base_item._rendered_row
item._refresh_state(interaction, interaction.data) # type: ignore

try:
Expand Down
6 changes: 3 additions & 3 deletions discord/voice_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ async def on_voice_server_update(self, data: VoiceServerUpdatePayload, /) -> Non
Parameters
------------
data: :class:`dict`
The raw :ddocs:`voice server update payload <topics/gateway#voice-server-update>`.
The raw :ddocs:`voice server update payload <topics/gateway-events#voice-server-update>`.
"""
raise NotImplementedError

Expand Down Expand Up @@ -337,7 +337,7 @@ async def disconnect(self, *, force: bool = False) -> None:
Disconnects this voice client from voice.
"""
self.stop()
await self._connection.disconnect(force=force)
await self._connection.disconnect(force=force, wait=True)
self.cleanup()

async def move_to(self, channel: Optional[abc.Snowflake], *, timeout: Optional[float] = 30.0) -> None:
Expand Down Expand Up @@ -567,6 +567,6 @@ def send_audio_packet(self, data: bytes, *, encode: bool = True) -> None:
try:
self._connection.send_packet(packet)
except OSError:
_log.info('A packet has been dropped (seq: %s, timestamp: %s)', self.sequence, self.timestamp)
_log.debug('A packet has been dropped (seq: %s, timestamp: %s)', self.sequence, self.timestamp)

self.checked_add('timestamp', opus.Encoder.SAMPLES_PER_FRAME, 4294967295)
72 changes: 56 additions & 16 deletions discord/voice_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@


class SocketReader(threading.Thread):
def __init__(self, state: VoiceConnectionState) -> None:
def __init__(self, state: VoiceConnectionState, *, start_paused: bool = True) -> None:
super().__init__(daemon=True, name=f'voice-socket-reader:{id(self):#x}')
self.state: VoiceConnectionState = state
self.start_paused = start_paused
self._callbacks: List[SocketReaderCallback] = []
self._running = threading.Event()
self._end = threading.Event()
Expand Down Expand Up @@ -130,6 +131,8 @@ def stop(self) -> None:
def run(self) -> None:
self._end.clear()
self._running.set()
if self.start_paused:
self.pause()
try:
self._do_run()
except Exception:
Expand All @@ -148,7 +151,10 @@ def _do_run(self) -> None:
# Since this socket is a non blocking socket, select has to be used to wait on it for reading.
try:
readable, _, _ = select.select([self.state.socket], [], [], 30)
except (ValueError, TypeError):
except (ValueError, TypeError, OSError) as e:
_log.debug(
"Select error handling socket in reader, this should be safe to ignore: %s: %s", e.__class__.__name__, e
)
# The socket is either closed or doesn't exist at the moment
continue

Expand Down Expand Up @@ -305,6 +311,10 @@ async def voice_state_update(self, data: GuildVoiceStatePayload) -> None:
_log.debug('Ignoring unexpected voice_state_update event')

async def voice_server_update(self, data: VoiceServerUpdatePayload) -> None:
previous_token = self.token
previous_server_id = self.server_id
previous_endpoint = self.endpoint

self.token = data['token']
self.server_id = int(data['guild_id'])
endpoint = data.get('endpoint')
Expand Down Expand Up @@ -338,6 +348,10 @@ async def voice_server_update(self, data: VoiceServerUpdatePayload) -> None:
self.state = ConnectionFlowState.got_voice_server_update

elif self.state is not ConnectionFlowState.disconnected:
# eventual consistency
if previous_token == self.token and previous_server_id == self.server_id and previous_token == self.token:
return

_log.debug('Unexpected server update event, attempting to handle')

await self.soft_disconnect(with_state=ConnectionFlowState.got_voice_server_update)
Expand Down Expand Up @@ -422,7 +436,7 @@ async def _connect(self, reconnect: bool, timeout: float, self_deaf: bool, self_
if not self._runner:
self._runner = self.voice_client.loop.create_task(self._poll_voice_ws(reconnect), name='Voice websocket poller')

async def disconnect(self, *, force: bool = True, cleanup: bool = True) -> None:
async def disconnect(self, *, force: bool = True, cleanup: bool = True, wait: bool = False) -> None:
if not force and not self.is_connected():
return

Expand All @@ -433,23 +447,26 @@ async def disconnect(self, *, force: bool = True, cleanup: bool = True) -> None:
except Exception:
_log.debug('Ignoring exception disconnecting from voice', exc_info=True)
finally:
self.ip = MISSING
self.port = MISSING
self.state = ConnectionFlowState.disconnected
self._socket_reader.pause()

# Stop threads before we unlock waiters so they end properly
if cleanup:
self._socket_reader.stop()
self.voice_client.stop()

# Flip the connected event to unlock any waiters
self._connected.set()
self._connected.clear()

if cleanup:
self._socket_reader.stop()

if self.socket:
self.socket.close()

self.ip = MISSING
self.port = MISSING

# Skip this part if disconnect was called from the poll loop task
if self._runner and asyncio.current_task() != self._runner:
if wait and not self._inside_runner():
# Wait for the voice_state_update event confirming the bot left the voice channel.
# This prevents a race condition caused by disconnecting and immediately connecting again.
# The new VoiceConnectionState object receives the voice_state_update event containing channel=None while still
Expand All @@ -458,7 +475,9 @@ async def disconnect(self, *, force: bool = True, cleanup: bool = True) -> None:
async with atimeout(self.timeout):
await self._disconnected.wait()
except TimeoutError:
_log.debug('Timed out waiting for disconnect confirmation event')
_log.debug('Timed out waiting for voice disconnection confirmation')
except asyncio.CancelledError:
pass

if cleanup:
self.voice_client.cleanup()
Expand All @@ -476,23 +495,26 @@ async def soft_disconnect(self, *, with_state: ConnectionFlowState = ConnectionF
except Exception:
_log.debug('Ignoring exception soft disconnecting from voice', exc_info=True)
finally:
self.ip = MISSING
self.port = MISSING
self.state = with_state
self._socket_reader.pause()

if self.socket:
self.socket.close()

self.ip = MISSING
self.port = MISSING

async def move_to(self, channel: Optional[abc.Snowflake], timeout: Optional[float]) -> None:
if channel is None:
await self.disconnect()
# This function should only be called externally so its ok to wait for the disconnect.
await self.disconnect(wait=True)
return

if self.voice_client.channel and channel.id == self.voice_client.channel.id:
return

previous_state = self.state

# this is only an outgoing ws request
# if it fails, nothing happens and nothing changes (besides self.state)
await self._move_to(channel)
Expand All @@ -504,7 +526,6 @@ async def move_to(self, channel: Optional[abc.Snowflake], timeout: Optional[floa
_log.warning('Timed out trying to move to channel %s in guild %s', channel.id, self.guild.id)
if self.state is last_state:
_log.debug('Reverting to previous state %s', previous_state.name)

self.state = previous_state

def wait(self, timeout: Optional[float] = None) -> bool:
Expand All @@ -527,6 +548,9 @@ def remove_socket_listener(self, callback: SocketReaderCallback) -> None:
_log.debug('Unregistering socket listener callback %s', callback)
self._socket_reader.unregister(callback)

def _inside_runner(self) -> bool:
return self._runner is not None and asyncio.current_task() == self._runner

async def _wait_for_state(
self, state: ConnectionFlowState, *other_states: ConnectionFlowState, timeout: Optional[float] = None
) -> None:
Expand Down Expand Up @@ -590,11 +614,21 @@ async def _poll_voice_ws(self, reconnect: bool) -> None:
break

if exc.code == 4014:
# We were disconnected by discord
# This condition is a race between the main ws event and the voice ws closing
if self._disconnected.is_set():
_log.info('Disconnected from voice by discord, close code %d.', exc.code)
await self.disconnect()
break

# We may have been moved to a different channel
_log.info('Disconnected from voice by force... potentially reconnecting.')
successful = await self._potential_reconnect()
if not successful:
_log.info('Reconnect was unsuccessful, disconnecting from voice normally...')
await self.disconnect()
# Don't bother to disconnect if already disconnected
if self.state is not ConnectionFlowState.disconnected:
await self.disconnect()
break
else:
continue
Expand Down Expand Up @@ -626,10 +660,16 @@ async def _poll_voice_ws(self, reconnect: bool) -> None:
async def _potential_reconnect(self) -> bool:
try:
await self._wait_for_state(
ConnectionFlowState.got_voice_server_update, ConnectionFlowState.got_both_voice_updates, timeout=self.timeout
ConnectionFlowState.got_voice_server_update,
ConnectionFlowState.got_both_voice_updates,
ConnectionFlowState.disconnected,
timeout=self.timeout,
)
except asyncio.TimeoutError:
return False
else:
if self.state is ConnectionFlowState.disconnected:
return False

previous_ws = self.ws
try:
Expand Down
2 changes: 2 additions & 0 deletions discord/webhook/async_.py
Original file line number Diff line number Diff line change
Expand Up @@ -1596,6 +1596,7 @@ async def send(
wait: Literal[True],
suppress_embeds: bool = MISSING,
silent: bool = MISSING,
applied_tags: List[ForumTag] = MISSING,
) -> WebhookMessage:
...

Expand All @@ -1619,6 +1620,7 @@ async def send(
wait: Literal[False] = ...,
suppress_embeds: bool = MISSING,
silent: bool = MISSING,
applied_tags: List[ForumTag] = MISSING,
) -> None:
...

Expand Down
Loading

0 comments on commit 822cffe

Please sign in to comment.