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
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
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
"""
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
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
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
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
@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.
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
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
¶
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_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 |