-
-
Notifications
You must be signed in to change notification settings - Fork 40
Add snippet banning and unbanning #443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3bad160
727adfd
e3266a8
8b8138e
6af7576
b737ea6
61ce0b3
b3acb6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,117 @@ | ||||||
| import discord | ||||||
| from discord.ext import commands | ||||||
| from loguru import logger | ||||||
|
|
||||||
| from prisma.enums import CaseType | ||||||
| from prisma.models import Case | ||||||
| from tux.database.controllers.case import CaseController | ||||||
| from tux.utils import checks | ||||||
| from tux.utils.constants import Constants as CONST | ||||||
| from tux.utils.flags import SnippetBanFlags | ||||||
|
|
||||||
| from . import ModerationCogBase | ||||||
|
|
||||||
|
|
||||||
| class SnippetBan(ModerationCogBase): | ||||||
| def __init__(self, bot: commands.Bot) -> None: | ||||||
| super().__init__(bot) | ||||||
| self.case_controller = CaseController() | ||||||
|
|
||||||
| @commands.hybrid_command( | ||||||
| name="snippetban", | ||||||
| aliases=["sb"], | ||||||
| usage="snippetban [target]", | ||||||
| ) | ||||||
| @commands.guild_only() | ||||||
| @checks.has_pl(3) | ||||||
| async def snippet_ban( | ||||||
| self, | ||||||
| ctx: commands.Context[commands.Bot], | ||||||
| target: discord.Member, | ||||||
| *, | ||||||
| flags: SnippetBanFlags, | ||||||
| ) -> None: | ||||||
| """ | ||||||
| Ban a user from creating snippets. | ||||||
| Parameters | ||||||
| ---------- | ||||||
| ctx : commands.Context[commands.Bot] | ||||||
| The context object. | ||||||
| target : discord.Member | ||||||
| The member to snippet ban. | ||||||
| flags : SnippetBanFlags | ||||||
| The flags for the command. (reason: str, silent: bool) | ||||||
| """ | ||||||
| if ctx.guild is None: | ||||||
| logger.warning("Snippet ban command used outside of a guild context.") | ||||||
| return | ||||||
|
|
||||||
| if await self.is_snippetbanned(ctx.guild.id, target.id): | ||||||
| await ctx.send("User is already snippet banned.", delete_after=30) | ||||||
| return | ||||||
|
|
||||||
| case = await self.db.case.insert_case( | ||||||
| case_target_id=target.id, | ||||||
| case_moderator_id=ctx.author.id, | ||||||
| case_type=CaseType.SNIPPETBAN, | ||||||
| case_reason=flags.reason, | ||||||
| guild_id=ctx.guild.id, | ||||||
| ) | ||||||
|
|
||||||
| await self.send_dm(ctx, flags.silent, target, flags.reason, "Snippet Banned") | ||||||
| await self.handle_case_response(ctx, case, "created", flags.reason, target) | ||||||
|
|
||||||
| async def handle_case_response( | ||||||
| self, | ||||||
| ctx: commands.Context[commands.Bot], | ||||||
| case: Case | None, | ||||||
| action: str, | ||||||
| reason: str, | ||||||
| target: discord.Member | discord.User, | ||||||
| previous_reason: str | None = None, | ||||||
| ) -> None: | ||||||
| moderator = ctx.author | ||||||
|
|
||||||
| fields = [ | ||||||
| ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), | ||||||
| ("Target", f"__{target}__\n`{target.id}`", True), | ||||||
| ("Reason", f"> {reason}", False), | ||||||
| ] | ||||||
|
|
||||||
| if previous_reason: | ||||||
| fields.append(("Previous Reason", f"> {previous_reason}", False)) | ||||||
|
|
||||||
| if case is not None: | ||||||
| embed = await self.create_embed( | ||||||
| ctx, | ||||||
| title=f"Case #{case.case_number} ({case.case_type}) {action}", | ||||||
| fields=fields, | ||||||
| color=CONST.EMBED_COLORS["CASE"], | ||||||
| icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], | ||||||
| ) | ||||||
| embed.set_thumbnail(url=target.avatar) | ||||||
| else: | ||||||
| embed = await self.create_embed( | ||||||
| ctx, | ||||||
| title=f"Case {action} ({CaseType.SNIPPETBAN})", | ||||||
| fields=fields, | ||||||
| color=CONST.EMBED_COLORS["CASE"], | ||||||
| icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], | ||||||
| ) | ||||||
|
|
||||||
| await self.send_embed(ctx, embed, log_type="mod") | ||||||
| await ctx.send(embed=embed, delete_after=30, ephemeral=True) | ||||||
|
|
||||||
| async def is_snippetbanned(self, guild_id: int, user_id: int) -> bool: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Move is_snippetbanned method to a common location to avoid duplication This method is duplicated in both snippetban.py and snippetunban.py. Consider moving it to a shared utility module or base class to ensure consistency and reduce duplication. |
||||||
| ban_cases = await self.case_controller.get_all_cases_by_type(guild_id, CaseType.SNIPPETBAN) | ||||||
| unban_cases = await self.case_controller.get_all_cases_by_type(guild_id, CaseType.SNIPPETUNBAN) | ||||||
|
|
||||||
| ban_count = sum(1 for case in ban_cases if case.case_target_id == user_id) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (code-quality): Simplify constant sum() call (
Suggested change
ExplanationAssum add the values it treats True as 1, and False as 0. We make useof this fact to simplify the generator expression inside the sum call.
|
||||||
| unban_count = sum(1 for case in unban_cases if case.case_target_id == user_id) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (code-quality): Simplify constant sum() call (
Suggested change
ExplanationAssum add the values it treats True as 1, and False as 0. We make useof this fact to simplify the generator expression inside the sum call.
|
||||||
|
|
||||||
| return ban_count > unban_count | ||||||
|
|
||||||
|
|
||||||
| async def setup(bot: commands.Bot) -> None: | ||||||
| await bot.add_cog(SnippetBan(bot)) | ||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,102 @@ | ||||||
| import discord | ||||||
| from discord.ext import commands | ||||||
| from loguru import logger | ||||||
|
|
||||||
| from prisma.enums import CaseType | ||||||
| from prisma.models import Case | ||||||
| from tux.database.controllers.case import CaseController | ||||||
| from tux.utils import checks | ||||||
| from tux.utils.constants import Constants as CONST | ||||||
| from tux.utils.flags import SnippetUnbanFlags | ||||||
|
|
||||||
| from . import ModerationCogBase | ||||||
|
|
||||||
|
|
||||||
| class SnippetUnban(ModerationCogBase): | ||||||
| def __init__(self, bot: commands.Bot) -> None: | ||||||
| super().__init__(bot) | ||||||
| self.case_controller = CaseController() | ||||||
|
|
||||||
| @commands.hybrid_command( | ||||||
| name="snippetunban", | ||||||
| aliases=["sub"], | ||||||
| usage="snippetunban [target]", | ||||||
| ) | ||||||
| @commands.guild_only() | ||||||
| @checks.has_pl(3) | ||||||
| async def snippet_unban( | ||||||
| self, | ||||||
| ctx: commands.Context[commands.Bot], | ||||||
| target: discord.Member, | ||||||
| *, | ||||||
| flags: SnippetUnbanFlags, | ||||||
| ): | ||||||
| if ctx.guild is None: | ||||||
| logger.warning("Snippet ban command used outside of a guild context.") | ||||||
| return | ||||||
|
|
||||||
| # Check if the user is already snippet banned | ||||||
| if not await self.is_snippetbanned(ctx.guild.id, target.id): | ||||||
| await ctx.send("User is not snippet banned.", delete_after=30) | ||||||
| return | ||||||
|
|
||||||
| case = await self.db.case.insert_case( | ||||||
| case_target_id=target.id, | ||||||
| case_moderator_id=ctx.author.id, | ||||||
| case_type=CaseType.SNIPPETUNBAN, | ||||||
| case_reason=flags.reason, | ||||||
| guild_id=ctx.guild.id, | ||||||
| ) | ||||||
|
|
||||||
| await self.send_dm(ctx, flags.silent, target, flags.reason, "Snippet Unbanned") | ||||||
| await self.handle_case_response(ctx, case, "created", flags.reason, target) | ||||||
|
|
||||||
| async def handle_case_response( | ||||||
| self, | ||||||
| ctx: commands.Context[commands.Bot], | ||||||
| case: Case | None, | ||||||
| action: str, | ||||||
| reason: str, | ||||||
| target: discord.Member | discord.User, | ||||||
| previous_reason: str | None = None, | ||||||
| ) -> None: | ||||||
| moderator = ctx.author | ||||||
|
|
||||||
| fields = [ | ||||||
| ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), | ||||||
| ("Target", f"__{target}__\n`{target.id}`", True), | ||||||
| ("Reason", f"> {reason}", False), | ||||||
| ] | ||||||
|
|
||||||
| if previous_reason: | ||||||
| fields.append(("Previous Reason", f"> {previous_reason}", False)) | ||||||
|
|
||||||
| if case is not None: | ||||||
| embed = await self.create_embed( | ||||||
| ctx, | ||||||
| title=f"Case #{case.case_number} ({case.case_type}) {action}", | ||||||
| fields=fields, | ||||||
| color=CONST.EMBED_COLORS["CASE"], | ||||||
| icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], | ||||||
| ) | ||||||
| embed.set_thumbnail(url=target.avatar) | ||||||
| else: | ||||||
| embed = await self.create_embed( | ||||||
| ctx, | ||||||
| title=f"Case {action} ({CaseType.SNIPPETUNBAN})", | ||||||
| fields=fields, | ||||||
| color=CONST.EMBED_COLORS["CASE"], | ||||||
| icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], | ||||||
| ) | ||||||
|
|
||||||
| await self.send_embed(ctx, embed, log_type="mod") | ||||||
| await ctx.send(embed=embed, delete_after=30, ephemeral=True) | ||||||
|
|
||||||
| async def is_snippetbanned(self, guild_id: int, user_id: int) -> bool: | ||||||
| ban_cases = await self.case_controller.get_all_cases_by_type(guild_id, CaseType.SNIPPETBAN) | ||||||
| unban_cases = await self.case_controller.get_all_cases_by_type(guild_id, CaseType.SNIPPETUNBAN) | ||||||
|
|
||||||
| ban_count = sum(1 for case in ban_cases if case.case_target_id == user_id) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (code-quality): Simplify constant sum() call (
Suggested change
ExplanationAssum add the values it treats True as 1, and False as 0. We make useof this fact to simplify the generator expression inside the sum call.
|
||||||
| unban_count = sum(1 for case in unban_cases if case.case_target_id == user_id) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (code-quality): Simplify constant sum() call (
Suggested change
ExplanationAssum add the values it treats True as 1, and False as 0. We make useof this fact to simplify the generator expression inside the sum call.
|
||||||
|
|
||||||
| return ban_count > unban_count | ||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -8,8 +8,9 @@ | |||||
| from loguru import logger | ||||||
| from reactionmenu import ViewButton, ViewMenu | ||||||
|
|
||||||
| from prisma.enums import CaseType | ||||||
| from prisma.models import Snippet | ||||||
| from tux.database.controllers import DatabaseController | ||||||
| from tux.database.controllers import CaseController, DatabaseController | ||||||
| from tux.utils import checks | ||||||
| from tux.utils.constants import Constants as CONST | ||||||
| from tux.utils.embeds import EmbedCreator, create_embed_footer, create_error_embed | ||||||
|
|
@@ -20,6 +21,16 @@ def __init__(self, bot: commands.Bot) -> None: | |||||
| self.bot = bot | ||||||
| self.db = DatabaseController().snippet | ||||||
| self.config = DatabaseController().guild_config | ||||||
| self.case_controller = CaseController() | ||||||
|
|
||||||
| async def is_snippetbanned(self, guild_id: int, user_id: int) -> bool: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (complexity): Consider separating the user ban check into a utility function or service. The new code introduces additional complexity due to the added dependencies on |
||||||
| ban_cases = await self.case_controller.get_all_cases_by_type(guild_id, CaseType.SNIPPETBAN) | ||||||
| unban_cases = await self.case_controller.get_all_cases_by_type(guild_id, CaseType.SNIPPETUNBAN) | ||||||
|
|
||||||
| ban_count = sum(1 for case in ban_cases if case.case_target_id == user_id) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (code-quality): Simplify constant sum() call (
Suggested change
ExplanationAssum add the values it treats True as 1, and False as 0. We make useof this fact to simplify the generator expression inside the sum call.
|
||||||
| unban_count = sum(1 for case in unban_cases if case.case_target_id == user_id) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (code-quality): Simplify constant sum() call (
Suggested change
ExplanationAssum add the values it treats True as 1, and False as 0. We make useof this fact to simplify the generator expression inside the sum call.
|
||||||
|
|
||||||
| return ban_count > unban_count | ||||||
|
|
||||||
| @commands.command( | ||||||
| name="snippets", | ||||||
|
|
@@ -359,6 +370,10 @@ async def create_snippet(self, ctx: commands.Context[commands.Bot], *, arg: str) | |||||
| await ctx.send("This command cannot be used in direct messages.") | ||||||
| return | ||||||
|
|
||||||
| if await self.is_snippetbanned(ctx.guild.id, ctx.author.id): | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (performance): Consider optimizing the is_snippetbanned check The current implementation of is_snippetbanned fetches all cases and then counts them. Consider optimizing this by using a more efficient database query to directly get the count or the latest status. |
||||||
| await ctx.send("You are banned from using snippets.") | ||||||
| return | ||||||
|
|
||||||
| args = arg.split(" ") | ||||||
| if len(args) < 2: | ||||||
| embed = create_error_embed(error="Please provide a name and content for the snippet.") | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -190,3 +190,33 @@ class WarnFlags(commands.FlagConverter, delimiter=" ", prefix="-"): | |
| aliases=["s", "quiet"], | ||
| default=False, | ||
| ) | ||
|
|
||
|
|
||
| class SnippetBanFlags(commands.FlagConverter, delimiter=" ", prefix="-"): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Consider creating a base class for flag converters to reduce duplication The SnippetBanFlags, SnippetUnbanFlags, and WarnFlags classes share similar structure. Consider creating a base class to reduce code duplication and improve maintainability. |
||
| reason: str = commands.flag( | ||
| name="reason", | ||
| description="The reason for the snippet ban.", | ||
| aliases=["r"], | ||
| default=MISSING, | ||
| ) | ||
| silent: bool = commands.flag( | ||
| name="silent", | ||
| description="Do not send a DM to the target.", | ||
| aliases=["s", "quiet"], | ||
| default=False, | ||
| ) | ||
|
|
||
|
|
||
| class SnippetUnbanFlags(commands.FlagConverter, delimiter=" ", prefix="-"): | ||
| reason: str = commands.flag( | ||
| name="reason", | ||
| description="The reason for the snippet unban.", | ||
| aliases=["r"], | ||
| default=MISSING, | ||
| ) | ||
| silent: bool = commands.flag( | ||
| name="silent", | ||
| description="Do not send a DM to the target.", | ||
| aliases=["s", "quiet"], | ||
| default=False, | ||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Refactor shared functionality into a base class
The handle_case_response method and overall command structure are very similar in both snippetban.py and snippetunban.py. Consider creating a base class or utility functions to reduce code duplication and improve maintainability.