diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 2547ac1..40d2f93 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -12,3 +12,4 @@ TheWeeknd <98106526+ToxicBiohazard@users.noreply.github.com> Dimosthenis Schizas makelarisjr <8687447+makelarisjr@users.noreply.github.com> +Jelle Janssens diff --git a/src/cmds/core/ban.py b/src/cmds/core/ban.py index 3d2385c..9830eed 100644 --- a/src/cmds/core/ban.py +++ b/src/cmds/core/ban.py @@ -1,6 +1,7 @@ import logging from datetime import datetime +import arrow import discord from discord import ApplicationContext, Interaction, WebhookMessage, slash_command from discord.ext import commands @@ -9,7 +10,7 @@ from src.bot import Bot from src.core import settings -from src.database.models import Ban, Infraction +from src.database.models import Ban, Infraction, UserNote from src.database.session import AsyncSessionLocal from src.helpers.ban import add_infraction, ban_member, unban_member from src.helpers.duration import validate_duration @@ -26,11 +27,14 @@ def __init__(self, bot: Bot): @slash_command(guild_ids=settings.guild_ids, description="Ban a user from the server permanently.") @has_any_role(*settings.role_groups.get("ALL_ADMINS"), *settings.role_groups.get("ALL_SR_MODS")) - async def ban(self, ctx: ApplicationContext, user: discord.Member, reason: str) -> Interaction | WebhookMessage: + async def ban( + self, ctx: ApplicationContext, user: discord.Member, reason: str, evidence: str = None + ) -> Interaction | WebhookMessage: """Ban a user from the server permanently.""" member = await self.bot.get_member_or_user(ctx.guild, user.id) if not member: return await ctx.respond(f"User {user} not found.") + await self.add_evidence_note(member.id, reason, evidence, ctx.user.id) response = await ban_member(self.bot, ctx.guild, member, "500w", reason, ctx.user, needs_approval=False) return await ctx.respond(response.message, delete_after=response.delete_after) @@ -42,12 +46,13 @@ async def ban(self, ctx: ApplicationContext, user: discord.Member, reason: str) *settings.role_groups.get("ALL_HTB_STAFF") ) async def tempban( - self, ctx: ApplicationContext, user: discord.Member, duration: str, reason: str + self, ctx: ApplicationContext, user: discord.Member, duration: str, reason: str, evidence: str = None ) -> Interaction | WebhookMessage: """Ban a user from the server temporarily.""" member = await self.bot.get_member_or_user(ctx.guild, user.id) if not member: return await ctx.respond(f"User {user} not found.") + await self.add_evidence_note(member.id, reason, evidence, ctx.user.id) response = await ban_member(self.bot, ctx.guild, member, duration, reason, ctx.user, needs_approval=True) return await ctx.respond(response.message, delete_after=response.delete_after) @@ -185,6 +190,19 @@ async def remove_infraction(self, ctx: ApplicationContext, infraction_id: int) - else: return await ctx.respond(f"Infraction record #{infraction_id} has not been found.") + async def add_evidence_note( + self, user_id: int, reason: str, evidence: str, moderator_id: int + ) -> None: + """Add a note with evidence to the user's history records.""" + if not evidence: + evidence = "none provided" + note = f"Reason for ban: {reason} (Evidence: {evidence})" + today = arrow.utcnow().format("YYYY-MM-DD") + user_note = UserNote(user_id=user_id, note=note, date=today, moderator_id=moderator_id) + async with AsyncSessionLocal() as session: + session.add(user_note) + await session.commit() + def setup(bot: Bot) -> None: """Load the `BanCog` cog.""" diff --git a/tests/src/cmds/core/test_ban.py b/tests/src/cmds/core/test_ban.py index 5d3ef8f..e731976 100644 --- a/tests/src/cmds/core/test_ban.py +++ b/tests/src/cmds/core/test_ban.py @@ -21,17 +21,18 @@ async def test_ban_success(self, ctx, bot): user = helpers.MockMember(id=2, name="Banned User") bot.get_member_or_user.return_value = user - with patch('src.cmds.core.ban.ban_member', new_callable=AsyncMock) as ban_member_mock: + with patch('src.cmds.core.ban.ban_member', new_callable=AsyncMock) as ban_member_mock, \ + patch('src.cmds.core.ban.BanCog.add_evidence_note', new_callable=AsyncMock) as add_evidence_note_mock: ban_response = SimpleResponse( message=f"Member {user.display_name} has been banned permanently.", delete_after=0 ) ban_member_mock.return_value = ban_response cog = ban.BanCog(bot) - await cog.ban.callback(cog, ctx, user, "Any valid reason") + await cog.ban.callback(cog, ctx, user, "Any valid reason", "Some evidence") # Assertions - # get_member_safe_mock.assert_called_once_with(user, ctx.guild) + add_evidence_note_mock.assert_called_once_with(user.id, "Any valid reason", "Some evidence", ctx.user.id) ban_member_mock.assert_called_once_with( bot, ctx.guild, user, "500w", "Any valid reason", ctx.user, needs_approval=False ) @@ -45,24 +46,26 @@ async def test_tempban_success(self, ctx, bot): user = helpers.MockMember(id=2, name="Banned User") bot.get_member_or_user.return_value = user - with ( - patch('src.helpers.ban.validate_duration', new_callable=AsyncMock) as validate_duration_mock, - patch('src.cmds.core.ban.ban_member', new_callable=AsyncMock) as ban_member_mock - ): + with patch('src.helpers.ban.validate_duration', new_callable=AsyncMock) as validate_duration_mock, \ + patch('src.cmds.core.ban.ban_member', new_callable=AsyncMock) as ban_member_mock, \ + patch('src.cmds.core.ban.BanCog.add_evidence_note', new_callable=AsyncMock) as add_evidence_note_mock: validate_duration_mock.return_value = (calendar.timegm(time.gmtime()) + parse_duration_str("5d"), "") ban_response = SimpleResponse( - message=f"Member {user.display_name} has been banned permanently.", delete_after=0 + message=f"Member {user.display_name} has been banned temporarily.", delete_after=0 ) ban_member_mock.return_value = ban_response cog = ban.BanCog(bot) - await cog.tempban.callback(cog, ctx, user, "5d", "Any valid reason") + await cog.tempban.callback(cog, ctx, user, "5d", "Any valid reason", "Some evidence") # Assertions + add_evidence_note_mock.assert_called_once_with(user.id, "Any valid reason", "Some evidence", ctx.user.id) ban_member_mock.assert_called_once_with( bot, ctx.guild, user, "5d", "Any valid reason", ctx.user, needs_approval=True ) - ctx.respond.assert_called_once() + ctx.respond.assert_called_once_with( + f"Member {user.display_name} has been banned temporarily.", delete_after=0 + ) @pytest.mark.asyncio async def test_tempban_failed_with_wrong_duration(self, ctx, bot):