Repository: Cog-Creators/Red-DiscordBot

Issue:
[Mod Enhancement] DM user with Kick, Ban, and Softban Reason
# Feature request
- [x] Enhancement

From what I can gather, V3 has no feature for the bot to DM the user with the kick/ban reason before they get actually kicked. Seeing RED v3 is also a moderation bot (seeing kick/ban is a core feature), I'd like to suggest this as an enhancement.

If by any chance the user does not allow any DM from the bot and or from any users, as per Slime#3160 words, `pass DM silently`. If a user is being kicked/banned from a server, it would be nice that the user gets to know why and not be stumped if they can no longer see the server icon on their server list or unable to join the server in question. Regardless if they are aware of their actions or not.

It would also be nice that it would be optional, so people can choose to use this function if they desire to do so. Not sure if warn DM the user with the exact warn reason, but if that's not the case, would be nice if this would apply to the warn command as well.


Discussion:

Resolving patch:
diff --git a/redbot/cogs/mod/kickban.py b/redbot/cogs/mod/kickban.py
--- a/redbot/cogs/mod/kickban.py
+++ b/redbot/cogs/mod/kickban.py
@@ -7,7 +7,7 @@
 
 import discord
 from redbot.core import commands, i18n, checks, modlog
-from redbot.core.utils.chat_formatting import pagify, humanize_number
+from redbot.core.utils.chat_formatting import pagify, humanize_number, bold
 from redbot.core.utils.mod import is_allowed_by_hierarchy, get_audit_reason
 from .abc import MixinMeta
 from .converters import RawUserIds
@@ -82,6 +82,19 @@ async def ban_user(
         elif not (0 <= days <= 7):
             return _("Invalid days. Must be between 0 and 7.")
 
+        toggle = await self.settings.guild(guild).dm_on_kickban()
+        if toggle:
+            with contextlib.suppress(discord.HTTPException):
+                em = discord.Embed(
+                    title=bold(_("You have been banned from {guild}.").format(guild=guild))
+                )
+                em.add_field(
+                    name=_("**Reason**"),
+                    value=reason if reason is not None else _("No reason was given."),
+                    inline=False,
+                )
+                await user.send(embed=em)
+
         audit_reason = get_audit_reason(author, reason)
 
         queue_entry = (guild.id, user.id)
@@ -186,6 +199,18 @@ async def kick(self, ctx: commands.Context, user: discord.Member, *, reason: str
             await ctx.send(_("I cannot do that due to discord hierarchy rules"))
             return
         audit_reason = get_audit_reason(author, reason)
+        toggle = await self.settings.guild(guild).dm_on_kickban()
+        if toggle:
+            with contextlib.suppress(discord.HTTPException):
+                em = discord.Embed(
+                    title=bold(_("You have been kicked from {guild}.").format(guild=guild))
+                )
+                em.add_field(
+                    name=_("**Reason**"),
+                    value=reason if reason is not None else _("No reason was given."),
+                    inline=False,
+                )
+                await user.send(embed=em)
         try:
             await guild.kick(user, reason=audit_reason)
             log.info("{}({}) kicked {}({})".format(author.name, author.id, user.name, user.id))
@@ -225,10 +250,13 @@ async def ban(
         """Ban a user from this server and optionally delete days of messages.
 
         If days is not a number, it's treated as the first word of the reason.
+
         Minimum 0 days, maximum 7. If not specified, defaultdays setting will be used instead."""
+        author = ctx.author
         guild = ctx.guild
         if days is None:
             days = await self.settings.guild(guild).default_days()
+
         result = await self.ban_user(
             user=user, ctx=ctx, days=days, reason=reason, create_modlog_case=True
         )
diff --git a/redbot/cogs/mod/mod.py b/redbot/cogs/mod/mod.py
--- a/redbot/cogs/mod/mod.py
+++ b/redbot/cogs/mod/mod.py
@@ -53,6 +53,7 @@ class Mod(
         "delete_delay": -1,
         "reinvite_on_unban": False,
         "current_tempbans": [],
+        "dm_on_kickban": False,
         "default_days": 0,
     }
 
diff --git a/redbot/cogs/mod/settings.py b/redbot/cogs/mod/settings.py
--- a/redbot/cogs/mod/settings.py
+++ b/redbot/cogs/mod/settings.py
@@ -27,6 +27,7 @@ async def modset(self, ctx: commands.Context):
             respect_hierarchy = data["respect_hierarchy"]
             delete_delay = data["delete_delay"]
             reinvite_on_unban = data["reinvite_on_unban"]
+            dm_on_kickban = data["dm_on_kickban"]
             default_days = data["default_days"]
             msg = ""
             msg += _("Delete repeats: {num_repeats}\n").format(
@@ -50,6 +51,9 @@ async def modset(self, ctx: commands.Context):
             msg += _("Reinvite on unban: {yes_or_no}\n").format(
                 yes_or_no=_("Yes") if reinvite_on_unban else _("No")
             )
+            msg += _("Send message to users on kick/ban: {yes_or_no}\n").format(
+                yes_or_no=_("Yes") if dm_on_kickban else _("No")
+            )
             if default_days:
                 msg += _(
                     "Default message history delete on ban: Previous {num_days} days\n"
@@ -208,6 +212,30 @@ async def reinvite(self, ctx: commands.Context):
                 )
             )
 
+    @modset.command()
+    @commands.guild_only()
+    async def dm(self, ctx: commands.Context, enabled: bool = None):
+        """Toggle whether to send a message to a user when they are 
+        kicked/banned.
+
+        If this option is enabled, the bot will attempt to DM the user with the guild name
+        and reason as to why they were kicked/banned.
+        """
+        guild = ctx.guild
+        if enabled is None:
+            setting = await self.settings.guild(guild).dm_on_kickban()
+            await ctx.send(
+                _("DM when kicked/banned is currently set to: {setting}").format(setting=setting)
+            )
+            return
+        await self.settings.guild(guild).dm_on_kickban.set(enabled)
+        if enabled:
+            await ctx.send(_("Bot will now attempt to send a DM to user before kick and ban."))
+        else:
+            await ctx.send(
+                _("Bot will no longer attempt to send a DM to user before kick and ban.")
+            )
+
     @modset.command()
     @commands.guild_only()
     async def defaultdays(self, ctx: commands.Context, days: int = 0):


Test patch: