Skip to content

tux.cogs.moderation.unban

Classes:

Name Description
Unban

Classes

Unban(bot: Tux)

Bases: ModerationCogBase

Methods:

Name Description
resolve_user_from_ban_list

Resolve a user from the ban list using username, ID, or partial info.

get_user_lock

Get or create a lock for operations on a specific user.

clean_user_locks

Remove locks for users that are not currently in use.

execute_user_action_with_lock

Execute an action on a user with a lock to prevent race conditions.

unban

Unban a user from the server.

execute_mod_action

Execute a moderation action with case creation, DM sending, and additional actions.

send_error_response

Send a standardized error response.

create_embed

Create an embed for moderation actions.

send_embed

Send an embed to the log channel.

send_dm

Send a DM to the target user.

check_conditions

Check if the conditions for the moderation action are met.

handle_case_response

Handle the response for a case.

is_pollbanned

Check if a user is poll banned.

is_snippetbanned

Check if a user is snippet banned.

is_jailed

Check if a user is jailed using the optimized latest case method.

Source code in tux/cogs/moderation/unban.py
Python
def __init__(self, bot: Tux) -> None:
    super().__init__(bot)
    self.unban.usage = generate_usage(self.unban, UnbanFlags)

Functions

resolve_user_from_ban_list(ctx: commands.Context[Tux], identifier: str) -> discord.User | None async

Resolve a user from the ban list using username, ID, or partial info.

Parameters:

Name Type Description Default
ctx Context[Tux]

The context of the command.

required
identifier str

The username, ID, or partial identifier to resolve.

required

Returns:

Type Description
Optional[User]

The user if found, None otherwise.

Source code in tux/cogs/moderation/unban.py
Python
async def resolve_user_from_ban_list(self, ctx: commands.Context[Tux], identifier: str) -> discord.User | None:
    """
    Resolve a user from the ban list using username, ID, or partial info.

    Parameters
    ----------
    ctx : commands.Context[Tux]
        The context of the command.
    identifier : str
        The username, ID, or partial identifier to resolve.

    Returns
    -------
    Optional[discord.User]
        The user if found, None otherwise.
    """
    assert ctx.guild

    # Get the list of banned users
    banned_users = [ban.user async for ban in ctx.guild.bans()]

    # Try ID first
    with suppress(ValueError):
        user_id = int(identifier)
        for user in banned_users:
            if user.id == user_id:
                return user

    # Try exact username or username#discriminator matching
    for user in banned_users:
        if user.name.lower() == identifier.lower():
            return user
        if str(user).lower() == identifier.lower():
            return user

    # Try partial name matching
    identifier_lower = identifier.lower()
    matches = [user for user in banned_users if identifier_lower in user.name.lower()]

    return matches[0] if len(matches) == 1 else None
get_user_lock(user_id: int) -> Lock async

Get or create a lock for operations on a specific user. If the number of stored locks exceeds the cleanup threshold, unused locks are removed.

Parameters:

Name Type Description Default
user_id int

The ID of the user to get a lock for.

required

Returns:

Type Description
Lock

The lock for the user.

Source code in tux/cogs/moderation/unban.py
Python
"""
assert ctx.guild

# Get the list of banned users
banned_users = [ban.user async for ban in ctx.guild.bans()]

# Try ID first
with suppress(ValueError):
    user_id = int(identifier)
    for user in banned_users:
        if user.id == user_id:
            return user

# Try exact username or username#discriminator matching
for user in banned_users:
    if user.name.lower() == identifier.lower():
        return user
    if str(user).lower() == identifier.lower():
        return user

# Try partial name matching
identifier_lower = identifier.lower()
clean_user_locks() -> None async

Remove locks for users that are not currently in use. Iterates through the locks and removes any that are not currently locked.

Source code in tux/cogs/moderation/unban.py
Python
    return matches[0] if len(matches) == 1 else None

# New private method extracted from the nested function
async def _perform_unban(
    self,
    ctx: commands.Context[Tux],
    user: discord.User,
    final_reason: str,
    guild: discord.Guild,  # Pass guild explicitly
) -> None:
    """Executes the core unban action and case creation."""
    # We already checked that user is not None in the main command
    assert user is not None, "User cannot be None at this point"
    await self.execute_mod_action(
        ctx=ctx,
        case_type=CaseType.UNBAN,
        user=user,
_perform_unban(ctx: commands.Context[Tux], user: discord.User, final_reason: str, guild: discord.Guild) -> None async

Executes the core unban action and case creation.

Source code in tux/cogs/moderation/unban.py
Python
async def _perform_unban(
    self,
    ctx: commands.Context[Tux],
    user: discord.User,
    final_reason: str,
    guild: discord.Guild,  # Pass guild explicitly
) -> None:
    """Executes the core unban action and case creation."""
    # We already checked that user is not None in the main command
    assert user is not None, "User cannot be None at this point"
    await self.execute_mod_action(
        ctx=ctx,
        case_type=CaseType.UNBAN,
        user=user,
        reason=final_reason,
        silent=True,  # No DM for unbans due to user not being in the guild
        dm_action="",  # No DM for unbans
        actions=[(guild.unban(user, reason=final_reason), type(None))],  # Use passed guild
    )
execute_user_action_with_lock(user_id: int, action_func: Callable[..., Coroutine[Any, Any, R]], *args: Any, **kwargs: Any) -> R async

Execute an action on a user with a lock to prevent race conditions.

Parameters:

Name Type Description Default
user_id int

The ID of the user to lock.

required
action_func Callable[..., Coroutine[Any, Any, R]]

The coroutine function to execute.

required
*args Any

Arguments to pass to the function.

()
**kwargs Any

Keyword arguments to pass to the function.

{}

Returns:

Type Description
R

The result of the action function.

Source code in tux/cogs/moderation/unban.py
Python
        silent=True,  # No DM for unbans due to user not being in the guild
        dm_action="",  # No DM for unbans
        actions=[(guild.unban(user, reason=final_reason), type(None))],  # Use passed guild
    )

@commands.hybrid_command(
    name="unban",
    aliases=["ub"],
)
@commands.guild_only()
@checks.has_pl(3)
async def unban(
    self,
    ctx: commands.Context[Tux],
    username_or_id: str,
    reason: str | None = None,
    *,
    flags: UnbanFlags,
) -> None:
    """
    Unban a user from the server.

    Parameters
    ----------
    ctx : commands.Context[Tux]
        The context object for the command.
    username_or_id : str
        The username or ID of the user to unban.
    reason : Optional[str]
        The reason for the unban.
unban(ctx: commands.Context[Tux], username_or_id: str, reason: str | None = None, *, flags: UnbanFlags) -> None async

Unban a user from the server.

Parameters:

Name Type Description Default
ctx Context[Tux]

The context object for the command.

required
username_or_id str

The username or ID of the user to unban.

required
reason Optional[str]

The reason for the unban.

None
flags UnbanFlags

The flags for the command.

required

Raises:

Type Description
Forbidden

If the bot does not have the necessary permissions.

HTTPException

If an error occurs while unbanning the user.

Source code in tux/cogs/moderation/unban.py
Python
@commands.hybrid_command(
    name="unban",
    aliases=["ub"],
)
@commands.guild_only()
@checks.has_pl(3)
async def unban(
    self,
    ctx: commands.Context[Tux],
    username_or_id: str,
    reason: str | None = None,
    *,
    flags: UnbanFlags,
) -> None:
    """
    Unban a user from the server.

    Parameters
    ----------
    ctx : commands.Context[Tux]
        The context object for the command.
    username_or_id : str
        The username or ID of the user to unban.
    reason : Optional[str]
        The reason for the unban.
    flags : UnbanFlags
        The flags for the command.

    Raises
    ------
    discord.Forbidden
        If the bot does not have the necessary permissions.
    discord.HTTPException
        If an error occurs while unbanning the user.
    """
    assert ctx.guild

    await ctx.defer(ephemeral=True)

    # First, try standard user conversion
    try:
        user = await commands.UserConverter().convert(ctx, username_or_id)
    except commands.UserNotFound:
        # If that fails, try more flexible ban list matching
        user = await self.resolve_user_from_ban_list(ctx, username_or_id)
        if not user:
            await self.send_error_response(
                ctx,
                f"Could not find '{username_or_id}' in the ban list. Try using the exact username or ID.",
            )
            return

    # Check if the user is banned
    try:
        await ctx.guild.fetch_ban(user)
    except discord.NotFound:
        await self.send_error_response(ctx, f"{user} is not banned.")
        return

    # Check if moderator has permission to unban the user
    if not await self.check_conditions(ctx, user, ctx.author, "unban"):
        return

    final_reason = reason or CONST.DEFAULT_REASON
    guild = ctx.guild

    try:
        # Call the lock executor with a lambda referencing the new private method
        await self.execute_user_action_with_lock(
            user.id,
            lambda: self._perform_unban(ctx, user, final_reason, guild),
        )
    except discord.NotFound:
        # This might occur if the user was unbanned between the fetch_ban check and the lock acquisition
        await self.send_error_response(ctx, f"{user} is no longer banned.")
    except discord.HTTPException as e:
        # Catch potential errors during the unban action forwarded by execute_mod_action
        await self.send_error_response(ctx, f"Failed to unban {user}", e)
_dummy_action() -> None async

Dummy coroutine for moderation actions that only create a case without performing Discord API actions. Used by commands like warn, pollban, snippetban etc. that only need case creation.

Source code in tux/cogs/moderation/unban.py
Python
    The flags for the command.

Raises
------
discord.Forbidden
    If the bot does not have the necessary permissions.
execute_mod_action(ctx: commands.Context[Tux], case_type: CaseType, user: discord.Member | discord.User, reason: str, silent: bool, dm_action: str, actions: Sequence[tuple[Any, type[R]]] = (), duration: str | None = None, expires_at: datetime | None = None) -> None async

Execute a moderation action with case creation, DM sending, and additional actions.

Parameters:

Name Type Description Default
ctx Context[Tux]

The context of the command.

required
case_type CaseType

The type of case to create.

required
user Union[Member, User]

The target user of the moderation action.

required
reason str

The reason for the moderation action.

required
silent bool

Whether to send a DM to the user.

required
dm_action str

The action description for the DM.

required
actions Sequence[tuple[Any, type[R]]]

Additional actions to execute and their expected return types.

()
duration Optional[str]

The duration of the action, if applicable (for display/logging).

None
expires_at Optional[datetime]

The specific expiration time, if applicable.

None
Source code in tux/cogs/moderation/unban.py
Python
            If an error occurs while unbanning the user.
        """
        assert ctx.guild

        await ctx.defer(ephemeral=True)

        # First, try standard user conversion
        try:
            user = await commands.UserConverter().convert(ctx, username_or_id)
        except commands.UserNotFound:
            # If that fails, try more flexible ban list matching
            user = await self.resolve_user_from_ban_list(ctx, username_or_id)
            if not user:
                await self.send_error_response(
                    ctx,
                    f"Could not find '{username_or_id}' in the ban list. Try using the exact username or ID.",
                )
                return

        # Check if the user is banned
        try:
            await ctx.guild.fetch_ban(user)
        except discord.NotFound:
            await self.send_error_response(ctx, f"{user} is not banned.")
            return

        # Check if moderator has permission to unban the user
        if not await self.check_conditions(ctx, user, ctx.author, "unban"):
            return

        final_reason = reason or CONST.DEFAULT_REASON
        guild = ctx.guild

        try:
            # Call the lock executor with a lambda referencing the new private method
            await self.execute_user_action_with_lock(
                user.id,
                lambda: self._perform_unban(ctx, user, final_reason, guild),
            )
        except discord.NotFound:
            # This might occur if the user was unbanned between the fetch_ban check and the lock acquisition
            await self.send_error_response(ctx, f"{user} is no longer banned.")
        except discord.HTTPException as e:
            # Catch potential errors during the unban action forwarded by execute_mod_action
            await self.send_error_response(ctx, f"Failed to unban {user}", e)


async def setup(bot: Tux) -> None:
    await bot.add_cog(Unban(bot))
_handle_dm_result(user: discord.Member | discord.User, dm_result: Any) -> bool

Handle the result of sending a DM.

Parameters:

Name Type Description Default
user Union[Member, User]

The user the DM was sent to.

required
dm_result Any

The result of the DM sending operation.

required

Returns:

Type Description
bool

Whether the DM was successfully sent.

send_error_response(ctx: commands.Context[Tux], error_message: str, error_detail: Exception | None = None, ephemeral: bool = True) -> None async

Send a standardized error response.

Parameters:

Name Type Description Default
ctx Context[Tux]

The context of the command.

required
error_message str

The error message to display.

required
error_detail Optional[Exception]

The exception details, if available.

None
ephemeral bool

Whether the message should be ephemeral.

True
create_embed(ctx: commands.Context[Tux], title: str, fields: list[tuple[str, str, bool]], color: int, icon_url: str, timestamp: datetime | None = None, thumbnail_url: str | None = None) -> discord.Embed

Create an embed for moderation actions.

Parameters:

Name Type Description Default
ctx Context[Tux]

The context of the command.

required
title str

The title of the embed.

required
fields list[tuple[str, str, bool]]

The fields to add to the embed.

required
color int

The color of the embed.

required
icon_url str

The icon URL for the embed.

required
timestamp Optional[datetime]

The timestamp for the embed.

None
thumbnail_url Optional[str]

The thumbnail URL for the embed.

None

Returns:

Type Description
Embed

The embed for the moderation action.

send_embed(ctx: commands.Context[Tux], embed: discord.Embed, log_type: str) -> None async

Send an embed to the log channel.

Parameters:

Name Type Description Default
ctx Context[Tux]

The context of the command.

required
embed Embed

The embed to send.

required
log_type str

The type of log to send the embed to.

required
send_dm(ctx: commands.Context[Tux], silent: bool, user: discord.Member | discord.User, reason: str, action: str) -> bool async

Send a DM to the target user.

Parameters:

Name Type Description Default
ctx Context[Tux]

The context of the command.

required
silent bool

Whether the command is silent.

required
user Union[Member, User]

The target of the moderation action.

required
reason str

The reason for the moderation action.

required
action str

The action being performed.

required

Returns:

Type Description
bool

Whether the DM was successfully sent.

check_conditions(ctx: commands.Context[Tux], user: discord.Member | discord.User, moderator: discord.Member | discord.User, action: str) -> bool async

Check if the conditions for the moderation action are met.

Parameters:

Name Type Description Default
ctx Context[Tux]

The context of the command.

required
user Union[Member, User]

The target of the moderation action.

required
moderator Union[Member, User]

The moderator of the moderation action.

required
action str

The action being performed.

required

Returns:

Type Description
bool

Whether the conditions are met.

handle_case_response(ctx: commands.Context[Tux], case_type: CaseType, case_number: int | None, reason: str, user: discord.Member | discord.User, dm_sent: bool, duration: str | None = None) -> None async

Handle the response for a case.

Parameters:

Name Type Description Default
ctx Context[Tux]

The context of the command.

required
case_type CaseType

The type of case.

required
case_number Optional[int]

The case number.

required
reason str

The reason for the case.

required
user Union[Member, User]

The target of the case.

required
dm_sent bool

Whether the DM was sent.

required
duration Optional[str]

The duration of the case.

None
_format_case_title(case_type: CaseType, case_number: int | None, duration: str | None) -> str

Format a case title.

Parameters:

Name Type Description Default
case_type CaseType

The type of case.

required
case_number Optional[int]

The case number.

required
duration Optional[str]

The duration of the case.

required

Returns:

Type Description
str

The formatted case title.

is_pollbanned(guild_id: int, user_id: int) -> bool async

Check if a user is poll banned.

Parameters:

Name Type Description Default
guild_id int

The ID of the guild to check in.

required
user_id int

The ID of the user to check.

required

Returns:

Type Description
bool

True if the user is poll banned, False otherwise.

is_snippetbanned(guild_id: int, user_id: int) -> bool async

Check if a user is snippet banned.

Parameters:

Name Type Description Default
guild_id int

The ID of the guild to check in.

required
user_id int

The ID of the user to check.

required

Returns:

Type Description
bool

True if the user is snippet banned, False otherwise.

is_jailed(guild_id: int, user_id: int) -> bool async

Check if a user is jailed using the optimized latest case method.

Parameters:

Name Type Description Default
guild_id int

The ID of the guild to check in.

required
user_id int

The ID of the user to check.

required

Returns:

Type Description
bool

True if the user is jailed, False otherwise.

Functions