diff --git a/config/JSON/strings.json b/config/JSON/strings.json index b0a0dab..ece33eb 100644 --- a/config/JSON/strings.json +++ b/config/JSON/strings.json @@ -169,6 +169,10 @@ "mod_kicked_author": "User Kicked", "mod_kicked_user": "user `{0}` has been kicked.", "mod_kick_dm": "**{0}** you have been kicked from `{1}`.\n\n**Reason:** `{2}`", + "mod_softbanned_author": "User Softbanned", + "mod_softban_dm": "**{0}** you have been softbanned from `{1}`.\n\n**Reason:** `{2}`", + "mod_softbanned_user": "user `{0}` has been softbanned.", + "mod_softban_unban_reason": "Softban by {0}", "ping_author": "I'm online!", "ping_footer": "Latency: {0}ms", "ping_pong": "Pong!", diff --git a/lib/constants.py b/lib/constants.py index 4945564..2f3865e 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -12,7 +12,7 @@ class Constants: TITLE = "Luminara" AUTHOR = "wlinator" LICENSE = "GNU General Public License v3.0" - VERSION = "2.8.5" # "Moderation: Kicks" update + VERSION = "2.8.6" # "Moderation: Softban" update # bot credentials TOKEN: Optional[str] = os.environ.get("TOKEN", None) diff --git a/modules/moderation/__init__.py b/modules/moderation/__init__.py index a286b8c..1f7a988 100644 --- a/modules/moderation/__init__.py +++ b/modules/moderation/__init__.py @@ -1,7 +1,7 @@ import discord from discord.ext import bridge, commands -from modules.moderation import ban, cases, warn, timeout, kick +from modules.moderation import ban, cases, warn, timeout, kick, softban class Moderation(commands.Cog): @@ -134,6 +134,7 @@ class Moderation(commands.Cog): guild_only=True, ) @bridge.has_permissions(moderate_members=True) + @commands.bot_has_permissions(moderate_members=True) @commands.guild_only() async def timeout_command( self, @@ -153,6 +154,7 @@ class Moderation(commands.Cog): guild_only=True, ) @bridge.has_permissions(moderate_members=True) + @commands.bot_has_permissions(moderate_members=True) @commands.guild_only() async def untimeout_command( self, @@ -182,6 +184,25 @@ class Moderation(commands.Cog): ): await kick.kick_user(self, ctx, target, reason) + @bridge.bridge_command( + name="softban", + aliases=["sb"], + description="Softban a user from the server.", + help="Softbans a user from the server (ban and immediately unban to delete messages).", + guild_only=True, + ) + @bridge.has_permissions(ban_members=True) + @commands.bot_has_permissions(ban_members=True) + @commands.guild_only() + async def softban_command( + self, + ctx, + target: discord.Member, + *, + reason: str | None = None, + ): + await softban.softban_user(ctx, target, reason) + def setup(client): client.add_cog(Moderation(client)) diff --git a/modules/moderation/ban.py b/modules/moderation/ban.py index 5174c4a..17500bf 100644 --- a/modules/moderation/ban.py +++ b/modules/moderation/ban.py @@ -43,6 +43,7 @@ async def ban_user(cog, ctx, target: discord.User, reason: Optional[str] = None) ctx.author.name, formatter.shorten(output_reason, 200), ), + delete_message_seconds=86400, ) respond_task = ctx.respond( diff --git a/modules/moderation/softban.py b/modules/moderation/softban.py new file mode 100644 index 0000000..91958a6 --- /dev/null +++ b/modules/moderation/softban.py @@ -0,0 +1,64 @@ +import asyncio +import discord +from typing import Optional +from discord.ext.commands import MemberConverter, UserConverter +from lib import formatter +from lib.constants import CONST +from lib.embed_builder import EmbedBuilder +from modules.moderation.utils.actionable import async_actionable +from modules.moderation.utils.case_handler import create_case + + +async def softban_user(ctx, target: discord.Member, reason: Optional[str] = None): + bot_member = await MemberConverter().convert(ctx, str(ctx.bot.user.id)) + await async_actionable(target, ctx.author, bot_member) + + output_reason = reason or CONST.STRINGS["mod_no_reason"] + + try: + await target.send( + embed=EmbedBuilder.create_warning_embed( + ctx, + author_text=CONST.STRINGS["mod_softbanned_author"], + description=CONST.STRINGS["mod_softban_dm"].format( + target.name, + ctx.guild.name, + output_reason, + ), + show_name=False, + ), + ) + dm_sent = True + except (discord.HTTPException, discord.Forbidden): + dm_sent = False + + await ctx.guild.ban( + target, + reason=CONST.STRINGS["mod_reason"].format( + ctx.author.name, + formatter.shorten(output_reason, 200), + ), + delete_message_seconds=86400, + ) + + await ctx.guild.unban( + target, + reason=CONST.STRINGS["mod_softban_unban_reason"].format( + ctx.author.name, + ), + ) + + respond_task = ctx.respond( + embed=EmbedBuilder.create_success_embed( + ctx, + author_text=CONST.STRINGS["mod_softbanned_author"], + description=CONST.STRINGS["mod_softbanned_user"].format(target.name), + footer_text=CONST.STRINGS["mod_dm_sent"] + if dm_sent + else CONST.STRINGS["mod_dm_not_sent"], + ), + ) + + target_user = await UserConverter().convert(ctx, str(target.id)) + create_case_task = create_case(ctx, target_user, "SOFTBAN", reason) + await asyncio.gather(respond_task, create_case_task, return_exceptions=True)