Skip to content
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

[FEATURE] Added evidence option for ban and tempban #114

Merged
merged 2 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ TheWeeknd <98106526+ToxicBiohazard@users.noreply.github.com>

Dimosthenis Schizas <dimos@hackthebox.eu>
makelarisjr <8687447+makelarisjr@users.noreply.github.com>
Jelle Janssens <janssensjelle@users.noreply.github.com>
24 changes: 21 additions & 3 deletions src/cmds/core/ban.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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)

Expand All @@ -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)

Expand Down Expand Up @@ -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."""
Expand Down
23 changes: 13 additions & 10 deletions tests/src/cmds/core/test_ban.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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):
Expand Down