Repository: Rapptz/discord.py

Issue:
Add self_deaf and self_mute to Voice(Channel/Client/Protocol).connect()
### Summary

`VoiceClient.connect()` calls `Guild.change_voice_state()` to connect to the voice channel so it could set self_deaf/mute kwargs if they were passable to connect()

### What is the feature request for?

The core library

### The Problem

With the current flow, you have to first call `voice_channel.connect()` and only then you can self-deafen/mute with a call to `guild.change_voice_state()`. When using VoiceProtocol, I can choose to call `guild.change_voice_state()` in `voice_client.connect()` with a different default but I can't choose this at the time of the call to `voice_channel.connect()` as it's the one that calls `voice_client.connect()`.

### The Ideal Solution

Add `self_deaf` and `self_mute` keyword arguments to `abc.Connectable.connect()` that will be passed to `VoiceProtocol.connect()` so that it can use them when connecting.

### The Current Solution

factory function that would instantiate a subclass of `VoiceProtocol` with such arguments and `voice_client.connect()` could base its behavior on that. I think this isn't really that great as these arguments are specific to connect action and should be passed when `connect()` method is called, not by changing the instance's attributes whenever you want to have different behavior for connecting.

### Additional Context

_No response_


Discussion:

Resolving patch:
diff --git a/discord/abc.py b/discord/abc.py
--- a/discord/abc.py
+++ b/discord/abc.py
@@ -1783,6 +1783,8 @@ async def connect(
         timeout: float = 60.0,
         reconnect: bool = True,
         cls: Callable[[Client, Connectable], T] = MISSING,
+        self_deaf: bool = False,
+        self_mute: bool = False,
     ) -> T:
         """|coro|
 
@@ -1802,6 +1804,14 @@ async def connect(
         cls: Type[:class:`~discord.VoiceProtocol`]
             A type that subclasses :class:`~discord.VoiceProtocol` to connect with.
             Defaults to :class:`~discord.VoiceClient`.
+        self_mute: :class:`bool`
+            Indicates if the client should be self-muted.
+
+            .. versionadded: 2.0
+        self_deaf: :class:`bool`
+            Indicates if the client should be self-deafened.
+
+            .. versionadded: 2.0
 
         Raises
         -------
@@ -1838,7 +1848,7 @@ async def connect(
         state._add_voice_client(key_id, voice)
 
         try:
-            await voice.connect(timeout=timeout, reconnect=reconnect)
+            await voice.connect(timeout=timeout, reconnect=reconnect, self_deaf=self_deaf, self_mute=self_mute)
         except asyncio.TimeoutError:
             try:
                 await voice.disconnect(force=True)
diff --git a/discord/voice_client.py b/discord/voice_client.py
--- a/discord/voice_client.py
+++ b/discord/voice_client.py
@@ -149,7 +149,7 @@ async def on_voice_server_update(self, data: VoiceServerUpdatePayload) -> None:
         """
         raise NotImplementedError
 
-    async def connect(self, *, timeout: float, reconnect: bool) -> None:
+    async def connect(self, *, timeout: float, reconnect: bool, self_deaf: bool = False, self_mute: bool = False) -> None:
         """|coro|
 
         An abstract method called when the client initiates the connection request.
@@ -169,6 +169,14 @@ async def connect(self, *, timeout: float, reconnect: bool) -> None:
             The timeout for the connection.
         reconnect: :class:`bool`
             Whether reconnection is expected.
+        self_mute: :class:`bool`
+            Indicates if the client should be self-muted.
+
+            .. versionadded: 2.0
+        self_deaf: :class:`bool`
+            Indicates if the client should be self-deafened.
+
+            .. versionadded: 2.0
         """
         raise NotImplementedError
 
@@ -339,8 +347,8 @@ async def on_voice_server_update(self, data: VoiceServerUpdatePayload) -> None:
 
         self._voice_server_complete.set()
 
-    async def voice_connect(self) -> None:
-        await self.channel.guild.change_voice_state(channel=self.channel)
+    async def voice_connect(self, self_deaf: bool = False, self_mute: bool = False) -> None:
+        await self.channel.guild.change_voice_state(channel=self.channel, self_deaf=self_deaf, self_mute=self_mute)
 
     async def voice_disconnect(self) -> None:
         _log.info('The voice handshake is being terminated for Channel ID %s (Guild ID %s)', self.channel.id, self.guild.id)
@@ -367,7 +375,7 @@ async def connect_websocket(self) -> DiscordVoiceWebSocket:
         self._connected.set()
         return ws
 
-    async def connect(self, *, reconnect: bool, timeout: float) -> None:
+    async def connect(self, *, reconnect: bool, timeout: float, self_deaf: bool = False, self_mute: bool = False) -> None:
         _log.info('Connecting to voice...')
         self.timeout = timeout
 
@@ -381,7 +389,7 @@ async def connect(self, *, reconnect: bool, timeout: float) -> None:
             ]
 
             # Start the connection flow
-            await self.voice_connect()
+            await self.voice_connect(self_deaf=self_deaf, self_mute=self_mute)
 
             try:
                 await utils.sane_wait_for(futures, timeout=timeout)


Test patch: