Repository: python-discord/bot

Issue:
Have !mute command kick from voice channel
# Abstract

The `!mute` command currently removes voice channel permissions, but does not kick the user from a voice channel they may already be in. Have the `!mute` command check if the user is currently in a voice channel, and if so, kick them from that channel.

# Specification

* The API call for checking whether a user is currently in a voice channel can be found [here](https://discordpy.readthedocs.io/en/latest/api.html#discord.Member.voice). The attribute from the `discord.VoiceState` object that is returned that provides this information is [`channel`](https://discordpy.readthedocs.io/en/latest/api.html#discord.VoiceState.channel)
* The API call for kicking a user can be found [here](https://discordpy.readthedocs.io/en/latest/api.html#discord.Member.move_to). As of d.py version 1.1.0, you can now pass `None` in order to kick them rather than have to move them to a different channel.

# Rationale

The `!mute` command should be expected to remove a users ability to participate in the server in all shapes and forms.

# Motivation

A user can currently misbehave from either being explicitly disruptive in a voice channel, or by repeatedly joining and leaving a voice channel. The only course of action via the bot to stop this behavior is by banning the user.


Discussion:

Resolving patch:
diff --git a/bot/cogs/moderation/infractions.py b/bot/cogs/moderation/infractions.py
--- a/bot/cogs/moderation/infractions.py
+++ b/bot/cogs/moderation/infractions.py
@@ -208,8 +208,13 @@ async def apply_mute(self, ctx: Context, user: Member, reason: str, **kwargs) ->
 
         self.mod_log.ignore(Event.member_update, user.id)
 
-        action = user.add_roles(self._muted_role, reason=reason)
-        await self.apply_infraction(ctx, infraction, user, action)
+        async def action() -> None:
+            await user.add_roles(self._muted_role, reason=reason)
+
+            log.trace(f"Attempting to kick {user} from voice because they've been muted.")
+            await user.move_to(None, reason=reason)
+
+        await self.apply_infraction(ctx, infraction, user, action())
 
     @respect_role_hierarchy()
     async def apply_kick(self, ctx: Context, user: Member, reason: str, **kwargs) -> None:
diff --git a/bot/cogs/moderation/scheduler.py b/bot/cogs/moderation/scheduler.py
--- a/bot/cogs/moderation/scheduler.py
+++ b/bot/cogs/moderation/scheduler.py
@@ -146,14 +146,18 @@ async def apply_infraction(
                 if expiry:
                     # Schedule the expiration of the infraction.
                     self.schedule_task(ctx.bot.loop, infraction["id"], infraction)
-            except discord.Forbidden:
+            except discord.HTTPException as e:
                 # Accordingly display that applying the infraction failed.
                 confirm_msg = f":x: failed to apply"
                 expiry_msg = ""
                 log_content = ctx.author.mention
                 log_title = "failed to apply"
 
-                log.warning(f"Failed to apply {infr_type} infraction #{id_} to {user}.")
+                log_msg = f"Failed to apply {infr_type} infraction #{id_} to {user}"
+                if isinstance(e, discord.Forbidden):
+                    log.warning(f"{log_msg}: bot lacks permissions.")
+                else:
+                    log.exception(log_msg)
 
         # Send a confirmation message to the invoking context.
         log.trace(f"Sending infraction #{id_} confirmation message.")
@@ -324,12 +328,12 @@ async def deactivate_infraction(
                     f"Attempted to deactivate an unsupported infraction #{id_} ({type_})!"
                 )
         except discord.Forbidden:
-            log.warning(f"Failed to deactivate infraction #{id_} ({type_}): bot lacks permissions")
+            log.warning(f"Failed to deactivate infraction #{id_} ({type_}): bot lacks permissions.")
             log_text["Failure"] = f"The bot lacks permissions to do this (role hierarchy?)"
             log_content = mod_role.mention
         except discord.HTTPException as e:
             log.exception(f"Failed to deactivate infraction #{id_} ({type_})")
-            log_text["Failure"] = f"HTTPException with code {e.code}."
+            log_text["Failure"] = f"HTTPException with status {e.status} and code {e.code}."
             log_content = mod_role.mention
 
         # Check if the user is currently being watched by Big Brother.


Test patch: