mirror of
https://github.com/allthingslinux/tux.git
synced 2024-10-03 00:53:12 +00:00
Merge branch 'main' into snippeteditban
This commit is contained in:
commit
84b01c3b5a
27 changed files with 662 additions and 788 deletions
23
.env.example
23
.env.example
|
@ -1,20 +1,27 @@
|
|||
#
|
||||
# Required
|
||||
#
|
||||
|
||||
# If in production mode:
|
||||
|
||||
# DEV=False
|
||||
PROD_DATABASE_URL=""
|
||||
# PROD_SUPABASE_DB_PASSWORD=""
|
||||
PROD_TOKEN=""
|
||||
|
||||
DEV=True
|
||||
# If in development mode:
|
||||
|
||||
# DEV=True
|
||||
DEV_DATABASE_URL=""
|
||||
# DEV_SUPABASE_DB_PASSWORD=""
|
||||
DEV_TOKEN=""
|
||||
|
||||
TZ="UTC"
|
||||
DISCORD_GUILD=
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
|
||||
SUPABASE_KEY=""
|
||||
SENTRY_URL=""
|
||||
|
||||
# PROD_COG_IGNORE_LIST="kick,ban"
|
||||
# DEV_COG_IGNORE_LIST="mail, git"
|
||||
PROD_COG_IGNORE_LIST=
|
||||
DEV_COG_IGNORE_LIST=
|
||||
|
||||
GITHUB_APP_ID=
|
||||
GITHUB_CLIENT_ID=""
|
||||
|
|
42
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
42
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
name: Bug Report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] - "
|
||||
labels: "type: bug"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Describe the Bug
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
## To Reproduce
|
||||
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
Please include code snippets or screenshots where applicable.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
## Screenshots
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
## Environment (please complete the following information)
|
||||
|
||||
- OS: [e.g. Ubuntu 20.04]
|
||||
- Python version: [e.g. Python 3.12]
|
||||
- Discord.py version: [e.g. 2.0.0]
|
||||
- Tux version: [e.g. v1.0.0]
|
||||
|
||||
## Additional Context
|
||||
|
||||
Add any other context about the problem here, such as logs, configuration files, etc.
|
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE] - "
|
||||
labels: "type: feature-request"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Is your feature request related to a problem? Please describe
|
||||
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
## Describe the solution you'd like
|
||||
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
## Describe alternatives you've considered
|
||||
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
## Additional Context
|
||||
|
||||
Add any other context or screenshots about the feature request here.
|
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
# Pull Request Template
|
||||
|
||||
## Description
|
||||
|
||||
Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.
|
||||
|
||||
Fixes # (issue)
|
||||
|
||||
## Type of Change
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] Documentation update
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have made corresponding changes to the documentation
|
||||
- [ ] My changes generate no new warnings
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] New and existing unit tests pass locally with my changes
|
||||
- [ ] Any dependent changes have been merged and published in downstream modules
|
||||
|
||||
## How Has This Been Tested?
|
||||
|
||||
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration.
|
||||
|
||||
- [ ] Test A
|
||||
- [ ] Test B
|
||||
|
||||
## Screenshots (if applicable)
|
||||
|
||||
Please add screenshots to help explain your changes.
|
||||
|
||||
## Additional Information
|
||||
|
||||
Please add any other information that is important to this PR.
|
4
.github/workflows/linting.yml
vendored
4
.github/workflows/linting.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
|||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.11
|
||||
python-version: 3.12
|
||||
|
||||
# Install Ruff
|
||||
- name: Install Ruff
|
||||
|
@ -30,4 +30,4 @@ jobs:
|
|||
run: ruff format && ruff check . --fix
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
commit_message: '[Fix] Linting and formatting via Ruff'
|
||||
commit_message: 'fix: Linting and formatting via Ruff'
|
||||
|
|
BIN
assets/emojis/snippetban.png
Normal file
BIN
assets/emojis/snippetban.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
assets/emojis/snippetunban.png
Normal file
BIN
assets/emojis/snippetunban.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
6
poetry.lock
generated
6
poetry.lock
generated
|
@ -1127,13 +1127,13 @@ pyyaml = ">=5.1"
|
|||
|
||||
[[package]]
|
||||
name = "mkdocs-material"
|
||||
version = "9.5.32"
|
||||
version = "9.5.33"
|
||||
description = "Documentation that simply works"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mkdocs_material-9.5.32-py3-none-any.whl", hash = "sha256:f3704f46b63d31b3cd35c0055a72280bed825786eccaf19c655b44e0cd2c6b3f"},
|
||||
{file = "mkdocs_material-9.5.32.tar.gz", hash = "sha256:38ed66e6d6768dde4edde022554553e48b2db0d26d1320b19e2e2b9da0be1120"},
|
||||
{file = "mkdocs_material-9.5.33-py3-none-any.whl", hash = "sha256:dbc79cf0fdc6e2c366aa987de8b0c9d4e2bb9f156e7466786ba2fd0f9bf7ffca"},
|
||||
{file = "mkdocs_material-9.5.33.tar.gz", hash = "sha256:d23a8b5e3243c9b2f29cdfe83051104a8024b767312dc8fde05ebe91ad55d89d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
|
@ -17,7 +17,9 @@ class Config(commands.Cog):
|
|||
self.bot = bot
|
||||
self.db = DatabaseController().guild_config
|
||||
|
||||
@app_commands.command(name="config_set_logs")
|
||||
config = app_commands.Group(name="config", description="Configure Tux for your server.")
|
||||
|
||||
@config.command(name="set_logs")
|
||||
@app_commands.guild_only()
|
||||
# @checks.ac_has_pl(7)
|
||||
@app_commands.checks.has_permissions(administrator=True)
|
||||
|
@ -45,7 +47,7 @@ class Config(commands.Cog):
|
|||
|
||||
await interaction.response.send_message(view=view, ephemeral=True)
|
||||
|
||||
@app_commands.command(name="config_set_channels")
|
||||
@config.command(name="set_channels")
|
||||
@app_commands.guild_only()
|
||||
# @checks.ac_has_pl(7)
|
||||
@app_commands.checks.has_permissions(administrator=True)
|
||||
|
@ -65,7 +67,7 @@ class Config(commands.Cog):
|
|||
view = ConfigSetChannels()
|
||||
await interaction.response.send_message(view=view, ephemeral=True)
|
||||
|
||||
@app_commands.command(name="config_set_perms")
|
||||
@config.command(name="set_perms")
|
||||
@app_commands.describe(setting="Which permission level to configure")
|
||||
@app_commands.choices(
|
||||
setting=[
|
||||
|
@ -116,7 +118,7 @@ class Config(commands.Cog):
|
|||
delete_after=30,
|
||||
)
|
||||
|
||||
@app_commands.command(name="config_set_roles")
|
||||
@config.command(name="set_roles")
|
||||
@app_commands.guild_only()
|
||||
# @checks.ac_has_pl(7)
|
||||
@app_commands.describe(setting="Which role to configure")
|
||||
|
@ -158,13 +160,45 @@ class Config(commands.Cog):
|
|||
delete_after=30,
|
||||
)
|
||||
|
||||
@app_commands.command(name="config_get_roles")
|
||||
@config.command(name="get_roles")
|
||||
@app_commands.guild_only()
|
||||
# @checks.ac_has_pl(7)
|
||||
@app_commands.checks.has_permissions(administrator=True)
|
||||
async def config_get_roles(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
) -> None:
|
||||
"""
|
||||
Get the basic roles for the guild.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
interaction : discord.Interaction
|
||||
The discord interaction object.
|
||||
"""
|
||||
|
||||
if interaction.guild is None:
|
||||
return
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Config - Roles",
|
||||
color=discord.Color.blue(),
|
||||
timestamp=discord.utils.utcnow(),
|
||||
)
|
||||
|
||||
jail_role_id = await self.db.get_jail_role_id(interaction.guild.id)
|
||||
jail_role = f"<@&{jail_role_id}>" if jail_role_id else "Not set"
|
||||
embed.add_field(name="Jail Role", value=jail_role, inline=False)
|
||||
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=30)
|
||||
|
||||
@config.command(name="get_perms")
|
||||
@app_commands.guild_only()
|
||||
# @checks.ac_has_pl(7)
|
||||
@app_commands.checks.has_permissions(administrator=True)
|
||||
async def config_get_perms(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
) -> None:
|
||||
"""
|
||||
Get the roles for each permission level.
|
||||
|
@ -192,7 +226,7 @@ class Config(commands.Cog):
|
|||
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=30)
|
||||
|
||||
@app_commands.command(name="config_get_channels")
|
||||
@config.command(name="get_channels")
|
||||
@app_commands.guild_only()
|
||||
# @checks.ac_has_pl(7)
|
||||
@app_commands.checks.has_permissions(administrator=True)
|
||||
|
@ -232,7 +266,7 @@ class Config(commands.Cog):
|
|||
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=30)
|
||||
|
||||
@app_commands.command(name="config_get_logs")
|
||||
@config.command(name="get_logs")
|
||||
@app_commands.guild_only()
|
||||
# @checks.ac_has_pl(7)
|
||||
@app_commands.checks.has_permissions(administrator=True)
|
||||
|
|
85
tux/cogs/guild/setup.py
Normal file
85
tux/cogs/guild/setup.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
|
||||
from tux.database.controllers import DatabaseController
|
||||
from tux.utils import checks
|
||||
|
||||
|
||||
class Setup(commands.Cog):
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
self.bot = bot
|
||||
self.db = DatabaseController()
|
||||
self.config = DatabaseController().guild_config
|
||||
|
||||
setup = app_commands.Group(name="setup", description="Set up Tux for your server.")
|
||||
|
||||
@setup.command(name="jail")
|
||||
@commands.guild_only()
|
||||
@checks.ac_has_pl(7)
|
||||
async def setup_jail(self, interaction: discord.Interaction) -> None:
|
||||
"""
|
||||
Set up the jail role channel permissions for the server.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
interaction : discord.Interaction
|
||||
The discord interaction object.
|
||||
"""
|
||||
|
||||
if interaction.guild is None:
|
||||
return
|
||||
|
||||
jail_role_id = await self.config.get_guild_config_field_value(interaction.guild.id, "jail_role_id")
|
||||
if not jail_role_id:
|
||||
await interaction.response.send_message("No jail role has been set up for this server.", ephemeral=True)
|
||||
return
|
||||
|
||||
jail_role = interaction.guild.get_role(jail_role_id)
|
||||
if not jail_role:
|
||||
await interaction.response.send_message("The jail role has been deleted.", ephemeral=True)
|
||||
return
|
||||
|
||||
jail_channel_id = await self.config.get_guild_config_field_value(interaction.guild.id, "jail_channel_id")
|
||||
if not jail_channel_id:
|
||||
await interaction.response.send_message("No jail channel has been set up for this server.", ephemeral=True)
|
||||
return
|
||||
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
|
||||
await self._set_permissions_for_channels(interaction, jail_role, jail_channel_id)
|
||||
|
||||
await interaction.edit_original_response(
|
||||
content="Permissions have been set up for the jail role.",
|
||||
)
|
||||
|
||||
async def _set_permissions_for_channels(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
jail_role: discord.Role,
|
||||
jail_channel_id: int,
|
||||
) -> None:
|
||||
if interaction.guild is None:
|
||||
return
|
||||
|
||||
for channel in interaction.guild.channels:
|
||||
if not isinstance(channel, discord.TextChannel | discord.VoiceChannel | discord.ForumChannel):
|
||||
continue
|
||||
|
||||
if (
|
||||
jail_role in channel.overwrites
|
||||
and channel.overwrites[jail_role].send_messages is False
|
||||
and channel.overwrites[jail_role].read_messages is False
|
||||
and channel.id != jail_channel_id
|
||||
):
|
||||
continue
|
||||
|
||||
await channel.set_permissions(jail_role, send_messages=False, read_messages=False)
|
||||
if channel.id == jail_channel_id:
|
||||
await channel.set_permissions(jail_role, send_messages=True, read_messages=True)
|
||||
|
||||
await interaction.edit_original_response(content=f"Setting up permissions for {channel.name}.")
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
await bot.add_cog(Setup(bot))
|
|
@ -4,7 +4,9 @@ import discord
|
|||
from discord.ext import commands
|
||||
from loguru import logger
|
||||
|
||||
from prisma.enums import CaseType
|
||||
from tux.database.controllers import DatabaseController
|
||||
from tux.utils.constants import Constants as CONST
|
||||
from tux.utils.embeds import create_embed_footer, create_error_embed
|
||||
|
||||
|
||||
|
@ -14,7 +16,7 @@ class ModerationCogBase(commands.Cog):
|
|||
self.db = DatabaseController()
|
||||
self.config = DatabaseController().guild_config
|
||||
|
||||
async def create_embed(
|
||||
def create_embed(
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
title: str,
|
||||
|
@ -22,6 +24,7 @@ class ModerationCogBase(commands.Cog):
|
|||
color: int,
|
||||
icon_url: str,
|
||||
timestamp: datetime | None = None,
|
||||
thumbnail_url: str | None = None,
|
||||
) -> discord.Embed:
|
||||
"""
|
||||
Create an embed for moderation actions.
|
||||
|
@ -49,6 +52,7 @@ class ModerationCogBase(commands.Cog):
|
|||
|
||||
embed = discord.Embed(color=color, timestamp=timestamp or ctx.message.created_at)
|
||||
embed.set_author(name=title, icon_url=icon_url)
|
||||
embed.set_thumbnail(url=thumbnail_url)
|
||||
|
||||
footer_text, footer_icon_url = create_embed_footer(ctx)
|
||||
embed.set_footer(text=footer_text, icon_url=footer_icon_url)
|
||||
|
@ -166,3 +170,42 @@ class ModerationCogBase(commands.Cog):
|
|||
return False
|
||||
|
||||
return True
|
||||
|
||||
async def handle_case_response(
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
case_type: CaseType,
|
||||
case_id: int | None,
|
||||
reason: str,
|
||||
target: discord.Member | discord.User,
|
||||
duration: str | 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 case_id is not None:
|
||||
embed = self.create_embed(
|
||||
ctx,
|
||||
title=f"Case #{case_id} ({duration} {case_type})" if duration else f"Case #{case_id} ({case_type})",
|
||||
fields=fields,
|
||||
color=CONST.EMBED_COLORS["CASE"],
|
||||
icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"],
|
||||
)
|
||||
embed.set_thumbnail(url=target.avatar)
|
||||
|
||||
else:
|
||||
embed = self.create_embed(
|
||||
ctx,
|
||||
title=f"Case #0 ({duration} {case_type})" if duration else f"Case #0 ({case_type})",
|
||||
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)
|
||||
|
|
|
@ -3,10 +3,8 @@ from discord.ext import commands
|
|||
from loguru import logger
|
||||
|
||||
from prisma.enums import CaseType
|
||||
from prisma.models import Case
|
||||
from tux.utils import checks
|
||||
from tux.utils.constants import Constants as CONST
|
||||
from tux.utils.flags import BanFlags
|
||||
from tux.utils.flags import BanFlags, generate_usage
|
||||
|
||||
from . import ModerationCogBase
|
||||
|
||||
|
@ -14,12 +12,9 @@ from . import ModerationCogBase
|
|||
class Ban(ModerationCogBase):
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
super().__init__(bot)
|
||||
self.ban.usage = generate_usage(self.ban, BanFlags)
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="ban",
|
||||
aliases=["b"],
|
||||
usage="ban [target] [reason] <purge_days> <silent>",
|
||||
)
|
||||
@commands.hybrid_command(name="ban", aliases=["b"])
|
||||
@commands.guild_only()
|
||||
@checks.has_pl(3)
|
||||
async def ban(
|
||||
|
@ -48,6 +43,7 @@ class Ban(ModerationCogBase):
|
|||
discord.HTTPException
|
||||
If an error occurs while banning the user.
|
||||
"""
|
||||
|
||||
if ctx.guild is None:
|
||||
logger.warning("Ban command used outside of a guild context.")
|
||||
return
|
||||
|
@ -74,48 +70,7 @@ class Ban(ModerationCogBase):
|
|||
guild_id=ctx.guild.id,
|
||||
)
|
||||
|
||||
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.BAN})",
|
||||
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)
|
||||
await self.handle_case_response(ctx, CaseType.BAN, case.case_id, flags.reason, target)
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
|
|
|
@ -23,8 +23,8 @@ emojis: dict[str, int] = {
|
|||
"timeout": 1268115809083981886,
|
||||
"warn": 1268115764498399264,
|
||||
"jail": 1268115750392954880,
|
||||
"snippetban": 1275782294363312172, # Placeholder
|
||||
"snippetunban": 1275782294363312172, # Placeholder
|
||||
"snippetban": 1277174953950576681,
|
||||
"snippetunban": 1277174953292337222,
|
||||
}
|
||||
|
||||
|
||||
|
@ -269,7 +269,7 @@ class Cases(ModerationCogBase):
|
|||
|
||||
fields = self._create_case_fields(moderator, target, reason)
|
||||
|
||||
embed = await self.create_embed(
|
||||
embed = self.create_embed(
|
||||
ctx,
|
||||
title=f"Case #{case.case_number} ({case.case_type}) {action}",
|
||||
fields=fields,
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from loguru import logger
|
||||
|
||||
from prisma.enums import CaseType
|
||||
from prisma.models import Case
|
||||
from tux.utils import checks
|
||||
from tux.utils.constants import Constants as CONST
|
||||
from tux.utils.flags import JailFlags
|
||||
from tux.utils.flags import JailFlags, generate_usage
|
||||
|
||||
from . import ModerationCogBase
|
||||
|
||||
|
@ -15,83 +12,15 @@ from . import ModerationCogBase
|
|||
class Jail(ModerationCogBase):
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
super().__init__(bot)
|
||||
|
||||
@app_commands.command(
|
||||
name="setup_jail",
|
||||
)
|
||||
@commands.guild_only()
|
||||
@checks.ac_has_pl(7)
|
||||
async def setup_jail(self, interaction: discord.Interaction) -> None:
|
||||
"""
|
||||
Set up the jail role channel permissions for the server.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
interaction : discord.Interaction
|
||||
The discord interaction object.
|
||||
"""
|
||||
|
||||
if interaction.guild is None:
|
||||
return
|
||||
|
||||
jail_role_id = await self.config.get_guild_config_field_value(interaction.guild.id, "jail_role_id")
|
||||
if not jail_role_id:
|
||||
await interaction.response.send_message("No jail role has been set up for this server.", ephemeral=True)
|
||||
return
|
||||
|
||||
jail_role = interaction.guild.get_role(jail_role_id)
|
||||
if not jail_role:
|
||||
await interaction.response.send_message("The jail role has been deleted.", ephemeral=True)
|
||||
return
|
||||
|
||||
jail_channel_id = await self.config.get_guild_config_field_value(interaction.guild.id, "jail_channel_id")
|
||||
if not jail_channel_id:
|
||||
await interaction.response.send_message("No jail channel has been set up for this server.", ephemeral=True)
|
||||
return
|
||||
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
|
||||
await self._set_permissions_for_channels(interaction, jail_role, jail_channel_id)
|
||||
|
||||
await interaction.edit_original_response(
|
||||
content="Permissions have been set up for the jail role.",
|
||||
)
|
||||
|
||||
async def _set_permissions_for_channels(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
jail_role: discord.Role,
|
||||
jail_channel_id: int,
|
||||
) -> None:
|
||||
if interaction.guild is None:
|
||||
return
|
||||
|
||||
for channel in interaction.guild.channels:
|
||||
if not isinstance(channel, discord.TextChannel | discord.VoiceChannel | discord.ForumChannel):
|
||||
continue
|
||||
|
||||
if (
|
||||
jail_role in channel.overwrites
|
||||
and channel.overwrites[jail_role].send_messages is False
|
||||
and channel.overwrites[jail_role].read_messages is False
|
||||
and channel.id != jail_channel_id
|
||||
):
|
||||
continue
|
||||
|
||||
await channel.set_permissions(jail_role, send_messages=False, read_messages=False)
|
||||
if channel.id == jail_channel_id:
|
||||
await channel.set_permissions(jail_role, send_messages=True, read_messages=True)
|
||||
|
||||
await interaction.edit_original_response(content=f"Setting up permissions for {channel.name}.")
|
||||
self.jail.usage = generate_usage(self.jail, JailFlags)
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="jail",
|
||||
aliases=["j"],
|
||||
usage="jail [target] [reason] <silent>",
|
||||
)
|
||||
@commands.guild_only()
|
||||
@checks.has_pl(2)
|
||||
async def jail(
|
||||
async def jail( # noqa: PLR0911
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
target: discord.Member,
|
||||
|
@ -137,38 +66,55 @@ class Jail(ModerationCogBase):
|
|||
|
||||
case_target_roles = [role.id for role in target_roles]
|
||||
|
||||
await self._jail_user(ctx, target, flags, jail_role, target_roles)
|
||||
|
||||
case = await self._insert_jail_case(ctx, target, flags.reason, case_target_roles)
|
||||
|
||||
await self.handle_case_response(ctx, case, "created", flags.reason, target)
|
||||
|
||||
async def _insert_jail_case(
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
target: discord.Member,
|
||||
reason: str,
|
||||
case_target_roles: list[int] | None = None,
|
||||
) -> Case | None:
|
||||
if not ctx.guild:
|
||||
logger.warning("Jail command used outside of a guild context.")
|
||||
return None
|
||||
|
||||
try:
|
||||
return await self.db.case.insert_case(
|
||||
case = await self.db.case.insert_case(
|
||||
case_target_id=target.id,
|
||||
case_moderator_id=ctx.author.id,
|
||||
case_type=CaseType.JAIL,
|
||||
case_reason=reason,
|
||||
case_reason=flags.reason,
|
||||
guild_id=ctx.guild.id,
|
||||
case_target_roles=case_target_roles,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to insert jail case for {target}. {e}")
|
||||
await ctx.send(f"Failed to insert jail case for {target}. {e}", delete_after=30, ephemeral=True)
|
||||
return None
|
||||
|
||||
def _get_manageable_roles(self, target: discord.Member, jail_role: discord.Role) -> list[discord.Role]:
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to jail {target}. {e}")
|
||||
await ctx.send(f"Failed to jail {target}. {e}", delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
||||
try:
|
||||
if target_roles:
|
||||
await target.remove_roles(*target_roles, reason=flags.reason, atomic=False)
|
||||
await target.add_roles(jail_role, reason=flags.reason)
|
||||
|
||||
except (discord.Forbidden, discord.HTTPException) as e:
|
||||
logger.error(f"Failed to jail {target}. {e}")
|
||||
await ctx.send(f"Failed to jail {target}. {e}", delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
||||
await self.send_dm(ctx, flags.silent, target, flags.reason, "jailed")
|
||||
await self.handle_case_response(ctx, CaseType.JAIL, case.case_id, flags.reason, target)
|
||||
|
||||
def _get_manageable_roles(
|
||||
self,
|
||||
target: discord.Member,
|
||||
jail_role: discord.Role,
|
||||
) -> list[discord.Role]:
|
||||
"""
|
||||
Get the roles that can be managed by the bot.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
target : discord.Member
|
||||
The member to jail.
|
||||
jail_role : discord.Role
|
||||
The jail role.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[discord.Role]
|
||||
The roles that can be managed by the bot.
|
||||
"""
|
||||
|
||||
return [
|
||||
role
|
||||
for role in target.roles
|
||||
|
@ -182,70 +128,6 @@ class Jail(ModerationCogBase):
|
|||
and role.is_assignable()
|
||||
]
|
||||
|
||||
async def _jail_user(
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
target: discord.Member,
|
||||
flags: JailFlags,
|
||||
jail_role: discord.Role,
|
||||
target_roles: list[discord.Role],
|
||||
) -> None:
|
||||
try:
|
||||
await self.send_dm(ctx, flags.silent, target, flags.reason, "jailed")
|
||||
|
||||
if target_roles:
|
||||
await target.remove_roles(*target_roles, reason=flags.reason, atomic=False)
|
||||
await target.add_roles(jail_role, reason=flags.reason)
|
||||
|
||||
except (discord.Forbidden, discord.HTTPException) as e:
|
||||
logger.error(f"Failed to jail {target}. {e}")
|
||||
await ctx.send(f"Failed to jail {target}. {e}", delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
||||
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:
|
||||
fields = [
|
||||
("Moderator", f"__{ctx.author}__\n`{ctx.author.id}`", True),
|
||||
("Target", f"__{target}__\n`{target.id}`", True),
|
||||
("Reason", f"> {reason}", False),
|
||||
]
|
||||
|
||||
if previous_reason:
|
||||
fields.append(("Previous Reason", f"> {previous_reason}", False))
|
||||
|
||||
embed = await self._create_case_embed(ctx, case, action, fields, target)
|
||||
|
||||
await self.send_embed(ctx, embed, log_type="mod")
|
||||
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
|
||||
|
||||
async def _create_case_embed(
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
case: Case | None,
|
||||
action: str,
|
||||
fields: list[tuple[str, str, bool]],
|
||||
target: discord.Member | discord.User,
|
||||
) -> discord.Embed:
|
||||
title = f"Case #{case.case_number} ({case.case_type}) {action}" if case else f"Case {action} ({CaseType.JAIL})"
|
||||
|
||||
embed = await self.create_embed(
|
||||
ctx,
|
||||
title=title,
|
||||
fields=fields,
|
||||
color=CONST.EMBED_COLORS["CASE"],
|
||||
icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"],
|
||||
)
|
||||
|
||||
embed.set_thumbnail(url=target.avatar)
|
||||
return embed
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
await bot.add_cog(Jail(bot))
|
||||
|
|
|
@ -3,10 +3,8 @@ from discord.ext import commands
|
|||
from loguru import logger
|
||||
|
||||
from prisma.enums import CaseType
|
||||
from prisma.models import Case
|
||||
from tux.utils import checks
|
||||
from tux.utils.constants import Constants as CONST
|
||||
from tux.utils.flags import KickFlags
|
||||
from tux.utils.flags import KickFlags, generate_usage
|
||||
|
||||
from . import ModerationCogBase
|
||||
|
||||
|
@ -14,11 +12,11 @@ from . import ModerationCogBase
|
|||
class Kick(ModerationCogBase):
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
super().__init__(bot)
|
||||
self.kick.usage = generate_usage(self.kick, KickFlags)
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="kick",
|
||||
aliases=["k"],
|
||||
usage="kick [target] [reason] <silent>",
|
||||
)
|
||||
@commands.guild_only()
|
||||
@checks.has_pl(2)
|
||||
|
@ -75,48 +73,7 @@ class Kick(ModerationCogBase):
|
|||
guild_id=ctx.guild.id,
|
||||
)
|
||||
|
||||
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} {action} ({case.case_type})",
|
||||
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.KICK})",
|
||||
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)
|
||||
await self.handle_case_response(ctx, CaseType.KICK, case.case_id, flags.reason, target)
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
|
|
|
@ -3,11 +3,9 @@ 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 tux.utils.flags import SnippetBanFlags, generate_usage
|
||||
|
||||
from . import ModerationCogBase
|
||||
|
||||
|
@ -16,11 +14,11 @@ class SnippetBan(ModerationCogBase):
|
|||
def __init__(self, bot: commands.Bot) -> None:
|
||||
super().__init__(bot)
|
||||
self.case_controller = CaseController()
|
||||
self.snippet_ban.usage = generate_usage(self.snippet_ban, SnippetBanFlags)
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="snippetban",
|
||||
aliases=["sb"],
|
||||
usage="snippetban [target]",
|
||||
)
|
||||
@commands.guild_only()
|
||||
@checks.has_pl(3)
|
||||
|
@ -49,9 +47,10 @@ class SnippetBan(ModerationCogBase):
|
|||
return
|
||||
|
||||
if await self.is_snippetbanned(ctx.guild.id, target.id):
|
||||
await ctx.send("User is already snippet banned.", delete_after=30)
|
||||
await ctx.send("User is already snippet banned.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
||||
try:
|
||||
case = await self.db.case.insert_case(
|
||||
case_target_id=target.id,
|
||||
case_moderator_id=ctx.author.id,
|
||||
|
@ -60,49 +59,13 @@ class SnippetBan(ModerationCogBase):
|
|||
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)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to ban {target}. {e}")
|
||||
await ctx.send(f"Failed to ban {target}. {e}", delete_after=30)
|
||||
return
|
||||
|
||||
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)
|
||||
await self.send_dm(ctx, flags.silent, target, flags.reason, "snippet banned")
|
||||
await self.handle_case_response(ctx, CaseType.SNIPPETBAN, case.case_id, flags.reason, target)
|
||||
|
||||
async def is_snippetbanned(self, guild_id: int, user_id: int) -> bool:
|
||||
"""
|
||||
|
|
|
@ -3,11 +3,9 @@ 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 tux.utils.flags import SnippetUnbanFlags, generate_usage
|
||||
|
||||
from . import ModerationCogBase
|
||||
|
||||
|
@ -16,11 +14,11 @@ class SnippetUnban(ModerationCogBase):
|
|||
def __init__(self, bot: commands.Bot) -> None:
|
||||
super().__init__(bot)
|
||||
self.case_controller = CaseController()
|
||||
self.snippet_unban.usage = generate_usage(self.snippet_unban, SnippetUnbanFlags)
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="snippetunban",
|
||||
aliases=["sub"],
|
||||
usage="snippetunban [target]",
|
||||
)
|
||||
@commands.guild_only()
|
||||
@checks.has_pl(3)
|
||||
|
@ -48,11 +46,11 @@ class SnippetUnban(ModerationCogBase):
|
|||
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)
|
||||
await ctx.send("User is not snippet banned.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
||||
try:
|
||||
case = await self.db.case.insert_case(
|
||||
case_target_id=target.id,
|
||||
case_moderator_id=ctx.author.id,
|
||||
|
@ -61,49 +59,13 @@ class SnippetUnban(ModerationCogBase):
|
|||
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)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to snippet unban {target}. {e}")
|
||||
await ctx.send(f"Failed to snippet unban {target}. {e}", delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
||||
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)
|
||||
await self.send_dm(ctx, flags.silent, target, flags.reason, "snippet unbanned")
|
||||
await self.handle_case_response(ctx, CaseType.SNIPPETUNBAN, case.case_id, flags.reason, target)
|
||||
|
||||
async def is_snippetbanned(self, guild_id: int, user_id: int) -> bool:
|
||||
"""
|
||||
|
|
|
@ -1,67 +1,25 @@
|
|||
import re
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from datetime import UTC, datetime
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from loguru import logger
|
||||
|
||||
from prisma.enums import CaseType
|
||||
from prisma.models import Case
|
||||
from tux.utils import checks
|
||||
from tux.utils.constants import Constants as CONST
|
||||
from tux.utils.flags import TimeoutFlags
|
||||
from tux.utils.flags import TimeoutFlags, generate_usage
|
||||
from tux.utils.functions import parse_time_string
|
||||
|
||||
from . import ModerationCogBase
|
||||
|
||||
|
||||
def parse_time_string(time_str: str) -> timedelta:
|
||||
"""
|
||||
Convert a string representation of time (e.g., '60s', '1m', '2h', '10d')
|
||||
into a datetime.timedelta object.
|
||||
|
||||
Parameters
|
||||
time_str (str): The string representation of time.
|
||||
|
||||
Returns
|
||||
timedelta: Corresponding timedelta object.
|
||||
"""
|
||||
|
||||
# Define regex pattern to parse time strings
|
||||
time_pattern = re.compile(r"^(?P<value>\d+)(?P<unit>[smhdw])$")
|
||||
|
||||
# Match the input string with the pattern
|
||||
match = time_pattern.match(time_str)
|
||||
|
||||
if not match:
|
||||
msg = f"Invalid time format: '{time_str}'"
|
||||
raise ValueError(msg)
|
||||
|
||||
# Extract the value and unit from the pattern match
|
||||
value = int(match["value"])
|
||||
unit = match["unit"]
|
||||
|
||||
# Define the mapping of units to keyword arguments for timedelta
|
||||
unit_map = {"s": "seconds", "m": "minutes", "h": "hours", "d": "days", "w": "weeks"}
|
||||
|
||||
# Check if the unit is in the map
|
||||
if unit not in unit_map:
|
||||
msg = f"Unknown time unit: '{unit}'"
|
||||
raise ValueError(msg)
|
||||
|
||||
# Create the timedelta with the appropriate keyword argument
|
||||
kwargs = {unit_map[unit]: value}
|
||||
|
||||
return timedelta(**kwargs)
|
||||
|
||||
|
||||
class Timeout(ModerationCogBase):
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
super().__init__(bot)
|
||||
self.timeout.usage = generate_usage(self.timeout, TimeoutFlags)
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="timeout",
|
||||
aliases=["t", "to", "mute"],
|
||||
usage="timeout [target] [duration] [reason]",
|
||||
)
|
||||
@commands.guild_only()
|
||||
@checks.has_pl(2)
|
||||
|
@ -106,7 +64,6 @@ class Timeout(ModerationCogBase):
|
|||
duration = parse_time_string(flags.duration)
|
||||
|
||||
try:
|
||||
await self.send_dm(ctx, flags.silent, target, flags.reason, f"timed out for {flags.duration}")
|
||||
await target.timeout(duration, reason=flags.reason)
|
||||
|
||||
except discord.DiscordException as e:
|
||||
|
@ -122,49 +79,8 @@ class Timeout(ModerationCogBase):
|
|||
guild_id=ctx.guild.id,
|
||||
)
|
||||
|
||||
await self.handle_case_response(ctx, flags, case, "created", flags.reason, target)
|
||||
|
||||
async def handle_case_response(
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
flags: TimeoutFlags,
|
||||
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} {action} ({flags.duration} {case.case_type})",
|
||||
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 #0 {action} ({flags.duration} {CaseType.TIMEOUT})",
|
||||
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)
|
||||
await self.send_dm(ctx, flags.silent, target, flags.reason, f"timed out for {flags.duration}")
|
||||
await self.handle_case_response(ctx, CaseType.TIMEOUT, case.case_id, flags.reason, target, flags.duration)
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
|
|
|
@ -3,10 +3,8 @@ from discord.ext import commands
|
|||
from loguru import logger
|
||||
|
||||
from prisma.enums import CaseType
|
||||
from prisma.models import Case
|
||||
from tux.utils import checks
|
||||
from tux.utils.constants import Constants as CONST
|
||||
from tux.utils.flags import UnbanFlags
|
||||
from tux.utils.flags import UnbanFlags, generate_usage
|
||||
|
||||
from . import ModerationCogBase
|
||||
|
||||
|
@ -14,17 +12,18 @@ from . import ModerationCogBase
|
|||
class Unban(ModerationCogBase):
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
super().__init__(bot)
|
||||
self.unban.usage = generate_usage(self.unban, UnbanFlags)
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="unban",
|
||||
aliases=["ub"],
|
||||
usage="unban [username_or_id] [reason]",
|
||||
)
|
||||
@commands.guild_only()
|
||||
@checks.has_pl(3)
|
||||
async def unban(
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
username_or_id: str,
|
||||
*,
|
||||
flags: UnbanFlags,
|
||||
) -> None:
|
||||
|
@ -35,10 +34,10 @@ class Unban(ModerationCogBase):
|
|||
----------
|
||||
ctx : commands.Context[commands.Bot]
|
||||
The context object for the command.
|
||||
target : discord.Member
|
||||
The member to unban.
|
||||
username_or_id : str
|
||||
The username or ID of the user to unban.
|
||||
flags : UnbanFlags
|
||||
The flags for the command (username_or_id: str, reason: str).
|
||||
The flags for the command (reason: str).
|
||||
|
||||
Raises
|
||||
------
|
||||
|
@ -54,7 +53,7 @@ class Unban(ModerationCogBase):
|
|||
|
||||
# Get the list of banned users in the guild
|
||||
banned_users = [ban.user async for ban in ctx.guild.bans()]
|
||||
user = await commands.UserConverter().convert(ctx, flags.username_or_id)
|
||||
user = await commands.UserConverter().convert(ctx, username_or_id)
|
||||
|
||||
if user not in banned_users:
|
||||
await ctx.send(f"{user} was not found in the guild ban list.", delete_after=30, ephemeral=True)
|
||||
|
@ -76,48 +75,7 @@ class Unban(ModerationCogBase):
|
|||
case_reason=flags.reason,
|
||||
)
|
||||
|
||||
await self.handle_case_response(ctx, case, "created", flags.reason, user)
|
||||
|
||||
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.UNBAN})",
|
||||
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)
|
||||
await self.handle_case_response(ctx, CaseType.UNBAN, case.case_id, flags.reason, user)
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
|
|
|
@ -3,10 +3,8 @@ from discord.ext import commands
|
|||
from loguru import logger
|
||||
|
||||
from prisma.enums import CaseType
|
||||
from prisma.models import Case
|
||||
from tux.utils import checks
|
||||
from tux.utils.constants import Constants as CONST
|
||||
from tux.utils.flags import UnjailFlags
|
||||
from tux.utils.flags import UnjailFlags, generate_usage
|
||||
|
||||
from . import ModerationCogBase
|
||||
|
||||
|
@ -14,15 +12,15 @@ from . import ModerationCogBase
|
|||
class Unjail(ModerationCogBase):
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
super().__init__(bot)
|
||||
self.unjail.usage = generate_usage(self.unjail, UnjailFlags)
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="unjail",
|
||||
aliases=["uj"],
|
||||
usage="unjail [target] [reason] <silent>",
|
||||
)
|
||||
@commands.guild_only()
|
||||
@checks.has_pl(2)
|
||||
async def unjail(
|
||||
async def unjail( # noqa: PLR0911
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
target: discord.Member,
|
||||
|
@ -51,151 +49,55 @@ class Unjail(ModerationCogBase):
|
|||
if not await self.check_conditions(ctx, target, moderator, "unjail"):
|
||||
return
|
||||
|
||||
jail_role = await self._get_jail_role(ctx)
|
||||
if not jail_role:
|
||||
jail_role_id = await self.config.get_jail_role_id(ctx.guild.id)
|
||||
jail_role = ctx.guild.get_role(jail_role_id) if jail_role_id else None
|
||||
|
||||
jail_channel_id = await self.config.get_jail_channel_id(ctx.guild.id)
|
||||
|
||||
if not all([jail_role_id, jail_role, jail_channel_id]):
|
||||
error_msgs = {
|
||||
not jail_role_id: "No jail role has been set up for this server.",
|
||||
not jail_role: "The jail role has been deleted.",
|
||||
not jail_channel_id: "No jail channel has been set up for this server.",
|
||||
}
|
||||
|
||||
for condition, msg in error_msgs.items():
|
||||
if condition:
|
||||
await ctx.send(msg, delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
||||
if jail_role not in target.roles:
|
||||
await ctx.send("The member is not jailed.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
||||
if not await self._check_jail_channel(ctx):
|
||||
return
|
||||
|
||||
case = await self.db.case.get_last_jail_case_by_target_id(ctx.guild.id, target.id)
|
||||
if not case:
|
||||
await ctx.send("No jail case found for this member.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
||||
await self._unjail_user(ctx, target, jail_role, case, flags.reason)
|
||||
|
||||
unjail_case = await self._insert_unjail_case(ctx, target, flags.reason)
|
||||
|
||||
await self.handle_case_response(ctx, unjail_case, "created", flags.reason, target)
|
||||
|
||||
async def _get_jail_role(self, ctx: commands.Context[commands.Bot]) -> discord.Role | None:
|
||||
if ctx.guild is None:
|
||||
logger.warning("Unjail command used outside of a guild context.")
|
||||
return None
|
||||
|
||||
jail_role_id = await self.config.get_jail_role_id(ctx.guild.id)
|
||||
if not jail_role_id:
|
||||
await ctx.send("No jail role has been set up for this server.", delete_after=30, ephemeral=True)
|
||||
return None
|
||||
|
||||
jail_role = ctx.guild.get_role(jail_role_id)
|
||||
if not jail_role:
|
||||
await ctx.send("The jail role has been deleted.", delete_after=30, ephemeral=True)
|
||||
return None
|
||||
|
||||
return jail_role
|
||||
|
||||
async def _check_jail_channel(self, ctx: commands.Context[commands.Bot]) -> bool:
|
||||
if ctx.guild is None:
|
||||
logger.warning("Unjail command used outside of a guild context.")
|
||||
return False
|
||||
|
||||
jail_channel_id = await self.config.get_jail_channel_id(ctx.guild.id)
|
||||
if not jail_channel_id:
|
||||
await ctx.send("No jail channel has been set up for this server.", delete_after=30, ephemeral=True)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
async def _unjail_user(
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
target: discord.Member,
|
||||
jail_role: discord.Role,
|
||||
case: Case,
|
||||
reason: str,
|
||||
) -> None:
|
||||
if ctx.guild is None:
|
||||
logger.warning("Unjail command used outside of a guild context.")
|
||||
return
|
||||
|
||||
try:
|
||||
await target.remove_roles(jail_role, reason=reason)
|
||||
|
||||
previous_roles = [await commands.RoleConverter().convert(ctx, str(role)) for role in case.case_target_roles]
|
||||
|
||||
if previous_roles:
|
||||
await target.add_roles(*previous_roles, reason=reason, atomic=False)
|
||||
await target.remove_roles(jail_role, reason=flags.reason, atomic=True)
|
||||
await target.add_roles(*previous_roles, reason=flags.reason, atomic=True)
|
||||
else:
|
||||
await ctx.send("No previous roles found for the member.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
||||
except (discord.Forbidden, discord.HTTPException) as e:
|
||||
logger.error(f"Failed to unjail member {target}. {e}")
|
||||
await ctx.send(f"Failed to unjail member {target}. {e}", delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
||||
async def _insert_unjail_case(
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
target: discord.Member,
|
||||
reason: str,
|
||||
) -> Case | None:
|
||||
if not ctx.guild:
|
||||
logger.warning("Unjail command used outside of a guild context.")
|
||||
return None
|
||||
|
||||
try:
|
||||
return await self.db.case.insert_case(
|
||||
unjail_case = await self.db.case.insert_case(
|
||||
guild_id=ctx.guild.id,
|
||||
case_target_id=target.id,
|
||||
case_moderator_id=ctx.author.id,
|
||||
case_type=CaseType.UNJAIL,
|
||||
case_reason=reason,
|
||||
case_reason=flags.reason,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to insert unjail case for {target}. {e}")
|
||||
await ctx.send(f"Failed to insert unjail case for {target}. {e}", delete_after=30, ephemeral=True)
|
||||
return None
|
||||
|
||||
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:
|
||||
fields = [
|
||||
("Moderator", f"__{ctx.author}__\n`{ctx.author.id}`", True),
|
||||
("Target", f"__{target}__\n`{target.id}`", True),
|
||||
("Reason", f"> {reason}", False),
|
||||
]
|
||||
|
||||
if previous_reason:
|
||||
fields.append(("Previous Reason", f"> {previous_reason}", False))
|
||||
|
||||
embed = await self._create_case_embed(ctx, case, action, fields, target)
|
||||
await self.send_embed(ctx, embed, log_type="mod")
|
||||
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
|
||||
|
||||
async def _create_case_embed(
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
case: Case | None,
|
||||
action: str,
|
||||
fields: list[tuple[str, str, bool]],
|
||||
target: discord.Member | discord.User,
|
||||
) -> discord.Embed:
|
||||
title = (
|
||||
f"Case #{case.case_number} ({case.case_type}) {action}" if case else f"Case {action} ({CaseType.UNJAIL})"
|
||||
)
|
||||
|
||||
embed = await self.create_embed(
|
||||
ctx,
|
||||
title=title,
|
||||
fields=fields,
|
||||
color=CONST.EMBED_COLORS["CASE"],
|
||||
icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"],
|
||||
)
|
||||
|
||||
embed.set_thumbnail(url=target.avatar)
|
||||
return embed
|
||||
await self.handle_case_response(ctx, CaseType.UNJAIL, unjail_case.case_id, flags.reason, target)
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
|
|
|
@ -3,10 +3,8 @@ from discord.ext import commands
|
|||
from loguru import logger
|
||||
|
||||
from prisma.enums import CaseType
|
||||
from prisma.models import Case
|
||||
from tux.utils import checks
|
||||
from tux.utils.constants import Constants as CONST
|
||||
from tux.utils.flags import UntimeoutFlags
|
||||
from tux.utils.flags import UntimeoutFlags, generate_usage
|
||||
|
||||
from . import ModerationCogBase
|
||||
|
||||
|
@ -14,11 +12,11 @@ from . import ModerationCogBase
|
|||
class Untimeout(ModerationCogBase):
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
super().__init__(bot)
|
||||
self.untimeout.usage = generate_usage(self.untimeout, UntimeoutFlags)
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="untimeout",
|
||||
aliases=["ut", "uto", "unmute"],
|
||||
usage="untimeout [target] [reason] <silent>",
|
||||
)
|
||||
@commands.guild_only()
|
||||
@checks.has_pl(2)
|
||||
|
@ -59,7 +57,6 @@ class Untimeout(ModerationCogBase):
|
|||
await ctx.send(f"{target} is not currently timed out.", delete_after=30, ephemeral=True)
|
||||
|
||||
try:
|
||||
await self.send_dm(ctx, flags.silent, target, flags.reason, "untimed out")
|
||||
await target.timeout(None, reason=flags.reason)
|
||||
except discord.DiscordException as e:
|
||||
await ctx.send(f"Failed to untimeout {target}. {e}", delete_after=30, ephemeral=True)
|
||||
|
@ -74,49 +71,8 @@ class Untimeout(ModerationCogBase):
|
|||
guild_id=ctx.guild.id,
|
||||
)
|
||||
|
||||
await self.handle_case_response(ctx, flags, case, "created", flags.reason, target)
|
||||
|
||||
async def handle_case_response(
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
flags: UntimeoutFlags,
|
||||
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} {action} ({case.case_type})",
|
||||
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 #0 {action} ({CaseType.UNTIMEOUT})",
|
||||
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)
|
||||
await self.send_dm(ctx, flags.silent, target, flags.reason, "untimed out")
|
||||
await self.handle_case_response(ctx, CaseType.UNTIMEOUT, case.case_id, flags.reason, target)
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
|
|
|
@ -3,10 +3,8 @@ from discord.ext import commands
|
|||
from loguru import logger
|
||||
|
||||
from prisma.enums import CaseType
|
||||
from prisma.models import Case
|
||||
from tux.utils import checks
|
||||
from tux.utils.constants import Constants as CONST
|
||||
from tux.utils.flags import WarnFlags
|
||||
from tux.utils.flags import WarnFlags, generate_usage
|
||||
|
||||
from . import ModerationCogBase
|
||||
|
||||
|
@ -14,11 +12,11 @@ from . import ModerationCogBase
|
|||
class Warn(ModerationCogBase):
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
super().__init__(bot)
|
||||
self.warn.usage = generate_usage(self.warn, WarnFlags)
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="warn",
|
||||
aliases=["w"],
|
||||
usage="warn [target] [reason] <silent>",
|
||||
)
|
||||
@commands.guild_only()
|
||||
@checks.has_pl(2)
|
||||
|
@ -61,48 +59,7 @@ class Warn(ModerationCogBase):
|
|||
guild_id=ctx.guild.id,
|
||||
)
|
||||
|
||||
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.WARN})",
|
||||
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)
|
||||
await self.handle_case_response(ctx, CaseType.WARN, case.case_id, flags.reason, target)
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
|
|
|
@ -9,6 +9,103 @@ from loguru import logger
|
|||
from tux.utils.embeds import create_error_embed
|
||||
from tux.utils.exceptions import AppCommandPermissionLevelError, PermissionLevelError
|
||||
|
||||
"""
|
||||
Exception
|
||||
DiscordException
|
||||
GatewayNotFound
|
||||
ClientException
|
||||
CommandRegistrationError
|
||||
InvalidData
|
||||
LoginFailure
|
||||
ConnectionClosed
|
||||
PrivilegedIntentsRequired
|
||||
InteractionResponded
|
||||
HTTPException
|
||||
Forbidden
|
||||
NotFound
|
||||
DiscordServerError
|
||||
app_commands.CommandSyncFailure
|
||||
RateLimited
|
||||
CommandError
|
||||
ConversionError
|
||||
UserInputError
|
||||
MissingRequiredArgument
|
||||
MissingRequiredAttachment
|
||||
TooManyArguments
|
||||
BadArgument
|
||||
MessageNotFound
|
||||
MemberNotFound
|
||||
GuildNotFound
|
||||
UserNotFound
|
||||
ChannelNotFound
|
||||
ChannelNotReadable
|
||||
BadColourArgument
|
||||
RoleNotFound
|
||||
BadInviteArgument
|
||||
EmojiNotFound
|
||||
GuildStickerNotFound
|
||||
ScheduledEventNotFound
|
||||
PartialEmojiConversionFailure
|
||||
BadBoolArgument
|
||||
RangeError
|
||||
ThreadNotFound
|
||||
FlagError
|
||||
BadFlagArgument
|
||||
MissingFlagArgument
|
||||
TooManyFlags
|
||||
MissingRequiredFlag
|
||||
BadUnionArgument
|
||||
BadLiteralArgument
|
||||
ArgumentParsingError
|
||||
UnexpectedQuoteError
|
||||
InvalidEndOfQuotedStringError
|
||||
ExpectedClosingQuoteError
|
||||
CommandNotFound
|
||||
CheckFailure
|
||||
CheckAnyFailure
|
||||
PrivateMessageOnly
|
||||
NoPrivateMessage
|
||||
NotOwner
|
||||
MissingPermissions
|
||||
BotMissingPermissions
|
||||
MissingRole
|
||||
BotMissingRole
|
||||
MissingAnyRole
|
||||
BotMissingAnyRole
|
||||
NSFWChannelRequired
|
||||
DisabledCommand
|
||||
CommandInvokeError
|
||||
CommandOnCooldown
|
||||
MaxConcurrencyReached
|
||||
HybridCommandError
|
||||
ExtensionError
|
||||
ExtensionAlreadyLoaded
|
||||
ExtensionNotLoaded
|
||||
NoEntryPointError
|
||||
ExtensionFailed
|
||||
ExtensionNotFound
|
||||
AppCommandError
|
||||
CommandInvokeError
|
||||
TransformerError
|
||||
TranslationError
|
||||
CheckFailure
|
||||
NoPrivateMessage
|
||||
MissingRole
|
||||
MissingAnyRole
|
||||
MissingPermissions
|
||||
BotMissingPermissions
|
||||
CommandOnCooldown
|
||||
CommandLimitReached
|
||||
CommandAlreadyRegistered
|
||||
CommandSignatureMismatch
|
||||
CommandNotFound
|
||||
MissingApplicationID
|
||||
CommandSyncFailure
|
||||
HTTPException
|
||||
CommandSyncFailure
|
||||
"""
|
||||
|
||||
|
||||
error_map: dict[type[Exception], str] = {
|
||||
# app_commands
|
||||
app_commands.AppCommandError: "An error occurred: {error}",
|
||||
|
@ -29,12 +126,12 @@ error_map: dict[type[Exception], str] = {
|
|||
commands.MissingRole: "User not in sudoers file. This incident will be reported. (Missing Role)",
|
||||
commands.MissingAnyRole: "User not in sudoers file. This incident will be reported. (Missing Roles)",
|
||||
commands.MissingPermissions: "User not in sudoers file. This incident will be reported. (Missing Permissions)",
|
||||
commands.FlagError: "An error occurred with the flags:\n`{error}`",
|
||||
commands.MissingRequiredFlag: "Missing argument: {error}. Correct usage:\n`{ctx.prefix}{ctx.command.usage}`",
|
||||
commands.CheckFailure: "User not in sudoers file. This incident will be reported. (Permission Check Failed)",
|
||||
commands.CommandNotFound: "This command was not found.",
|
||||
commands.CommandOnCooldown: "This command is on cooldown. Try again in {error.retry_after:.2f} seconds.",
|
||||
commands.BadArgument: "Invalid argument passed. Correct usage: `{ctx.prefix}{ctx.command.usage}`",
|
||||
commands.MissingRequiredArgument: "Missing required arg. Correct usage: `{ctx.prefix}{ctx.command.usage}`",
|
||||
commands.MissingRequiredAttachment: "Missing required attachment.",
|
||||
commands.MissingRequiredArgument: "Missing required argument. Correct usage:\n`{ctx.prefix}{ctx.command.usage}`",
|
||||
commands.TooManyArguments: "Too many arguments passed. Correct usage:\n`{ctx.prefix}{ctx.command.usage}`",
|
||||
commands.NotOwner: "User not in sudoers file. This incident will be reported. (Not Owner)",
|
||||
commands.BotMissingPermissions: "User not in sudoers file. This incident will be reported. (Bot Missing Permissions)",
|
||||
# Custom errors
|
||||
|
@ -101,12 +198,12 @@ class ErrorHandler(commands.Cog):
|
|||
The error that occurred.
|
||||
"""
|
||||
|
||||
# # If the command has its own error handler, return
|
||||
# If the command has its own error handler, return
|
||||
# if hasattr(ctx.command, "on_error"):
|
||||
# logger.debug(f"Command {ctx.command} has its own error handler.")
|
||||
# return
|
||||
|
||||
# # If the cog has its own error handler, return
|
||||
# If the cog has its own error handler, return
|
||||
# if ctx.cog and ctx.cog._get_overridden_method(ctx.cog.cog_command_error) is not None:
|
||||
# logger.debug(f"Cog {ctx.cog} has its own error handler.")
|
||||
# return
|
||||
|
|
|
@ -158,10 +158,12 @@ class TuxHelp(commands.HelpCommand):
|
|||
flag_str = self._format_flag_name(flag)
|
||||
|
||||
if flag.aliases:
|
||||
flag_str += f" ({', '.join(flag.aliases)})"
|
||||
flag_str += f" ({', '.join(flag.aliases)}) : {flag_type}"
|
||||
else:
|
||||
flag_str += f" : {flag_type}"
|
||||
|
||||
flag_str += f"\n\t{flag.description or 'No description provided.'}"
|
||||
flag_str += f"\n\tType: `{flag_type}`"
|
||||
|
||||
if flag.default is not discord.utils.MISSING:
|
||||
flag_str += f"\n\tDefault: {flag.default}"
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import inspect
|
||||
from typing import Any
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.utils import MISSING
|
||||
|
@ -6,16 +9,69 @@ from prisma.enums import CaseType
|
|||
from tux.utils.converters import CaseTypeConverter
|
||||
|
||||
|
||||
def generate_usage(
|
||||
command: commands.Command[Any, Any, Any],
|
||||
flag_converter: type[commands.FlagConverter],
|
||||
) -> str:
|
||||
"""
|
||||
Generate a usage string for a command with flags.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
command : commands.Command
|
||||
The command for which to generate the usage string.
|
||||
flag_converter : type[commands.FlagConverter]
|
||||
The flag converter class for the command.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The usage string for the command. Example: "ban [target] -[reason] -<silent>"
|
||||
"""
|
||||
|
||||
# Get the name of the command
|
||||
command_name = command.qualified_name
|
||||
|
||||
# Start the usage string with the command name
|
||||
usage = f"{command_name}"
|
||||
|
||||
# Get the parameters of the command (excluding the `ctx` and `flags` parameters)
|
||||
parameters: dict[str, commands.Parameter] = command.clean_params
|
||||
|
||||
flag_prefix = getattr(flag_converter, "__commands_flag_prefix__", "-")
|
||||
flags: dict[str, commands.Flag] = flag_converter.get_flags()
|
||||
|
||||
# Add non-flag arguments to the usage string
|
||||
for param_name, param in parameters.items():
|
||||
# Ignore these parameters
|
||||
if param_name in ["ctx", "flags"]:
|
||||
continue
|
||||
# Determine if the parameter is required
|
||||
is_required = param.default == inspect.Parameter.empty
|
||||
# Add the parameter to the usage string with required or optional wrapping
|
||||
usage += f" [{param_name}]" if is_required else f" <{param_name}>"
|
||||
|
||||
# Add flag arguments to the usage string
|
||||
for flag_name, flag_obj in flags.items():
|
||||
# Determine if the flag is required or optional
|
||||
if flag_obj.required:
|
||||
usage += f" {flag_prefix}[{flag_name}]"
|
||||
else:
|
||||
usage += f" {flag_prefix}<{flag_name}>"
|
||||
|
||||
return usage
|
||||
|
||||
|
||||
class BanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"):
|
||||
reason: str = commands.flag(
|
||||
name="reason",
|
||||
description="The reason for the ban.",
|
||||
description="Reason for the ban.",
|
||||
aliases=["r"],
|
||||
default=MISSING,
|
||||
)
|
||||
purge_days: int = commands.flag(
|
||||
name="purge_days",
|
||||
description="The number of days (< 7) to purge in messages.",
|
||||
description="Number of days in messages. (< 7)",
|
||||
aliases=["p", "purge"],
|
||||
default=0,
|
||||
)
|
||||
|
@ -30,18 +86,18 @@ class BanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", pre
|
|||
class TempBanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"):
|
||||
reason: str = commands.flag(
|
||||
name="reason",
|
||||
description="The reason for the temp ban.",
|
||||
description="Reason for the temp ban.",
|
||||
aliases=["r"],
|
||||
default=MISSING,
|
||||
)
|
||||
expires_at: int = commands.flag(
|
||||
name="expires_at",
|
||||
description="The time in days the ban will last for.",
|
||||
description="Number of days the ban will last for.",
|
||||
aliases=["t", "d", "e", "duration", "expires", "time"],
|
||||
)
|
||||
purge_days: int = commands.flag(
|
||||
name="purge_days",
|
||||
description="The number of days (< 7) to purge in messages.",
|
||||
description="Number of days in messages. (< 7)",
|
||||
aliases=["p"],
|
||||
default=0,
|
||||
)
|
||||
|
@ -56,7 +112,7 @@ class TempBanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ",
|
|||
class KickFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"):
|
||||
reason: str = commands.flag(
|
||||
name="reason",
|
||||
description="The reason for the kick.",
|
||||
description="Reason for the kick.",
|
||||
aliases=["r"],
|
||||
default=MISSING,
|
||||
)
|
||||
|
@ -71,13 +127,13 @@ class KickFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", pr
|
|||
class TimeoutFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"):
|
||||
duration: str = commands.flag(
|
||||
name="duration",
|
||||
description="The duration of the timeout. (e.g. 1d, 1h, 1m)",
|
||||
description="Duration of the timeout. (e.g. 1d, 1h, 1m)",
|
||||
aliases=["d"],
|
||||
default=MISSING,
|
||||
)
|
||||
reason: str = commands.flag(
|
||||
name="reason",
|
||||
description="The reason for the timeout.",
|
||||
description="Reason for the timeout.",
|
||||
aliases=["r"],
|
||||
default=MISSING,
|
||||
)
|
||||
|
@ -92,7 +148,7 @@ class TimeoutFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ",
|
|||
class UntimeoutFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"):
|
||||
reason: str = commands.flag(
|
||||
name="reason",
|
||||
description="The reason for the untimeout.",
|
||||
description="Reason for the untimeout.",
|
||||
aliases=["r"],
|
||||
default=MISSING,
|
||||
)
|
||||
|
@ -105,16 +161,9 @@ class UntimeoutFlags(commands.FlagConverter, case_insensitive=True, delimiter="
|
|||
|
||||
|
||||
class UnbanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"):
|
||||
username_or_id: str = commands.flag(
|
||||
name="username_or_id",
|
||||
description="The username or ID of the user.",
|
||||
aliases=["u"],
|
||||
default=MISSING,
|
||||
positional=True,
|
||||
)
|
||||
reason: str = commands.flag(
|
||||
name="reason",
|
||||
description="The reason for the unban.",
|
||||
description="Reason for the unban.",
|
||||
aliases=["r"],
|
||||
default=MISSING,
|
||||
)
|
||||
|
@ -123,7 +172,7 @@ class UnbanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", p
|
|||
class JailFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"):
|
||||
reason: str = commands.flag(
|
||||
name="reason",
|
||||
description="The reason for the jail.",
|
||||
description="Reason for the jail.",
|
||||
aliases=["r"],
|
||||
default=MISSING,
|
||||
)
|
||||
|
@ -138,7 +187,7 @@ class JailFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", pr
|
|||
class UnjailFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"):
|
||||
reason: str = commands.flag(
|
||||
name="reason",
|
||||
description="The reason for the unjail.",
|
||||
description="Reason for the unjail.",
|
||||
aliases=["r"],
|
||||
default=MISSING,
|
||||
)
|
||||
|
@ -153,20 +202,20 @@ class UnjailFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ",
|
|||
class CasesViewFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"):
|
||||
type: CaseType = commands.flag(
|
||||
name="case_type",
|
||||
description="The case type to view.",
|
||||
description="Type of case to view.",
|
||||
aliases=["t"],
|
||||
default=None,
|
||||
converter=CaseTypeConverter,
|
||||
)
|
||||
target: discord.User = commands.flag(
|
||||
name="case_target",
|
||||
description="The user to view cases for.",
|
||||
description="User to view cases for.",
|
||||
aliases=["user", "u", "member", "memb", "m"],
|
||||
default=None,
|
||||
)
|
||||
moderator: discord.User = commands.flag(
|
||||
name="case_moderator",
|
||||
description="The moderator to view cases for.",
|
||||
description="Moderator to view cases for.",
|
||||
aliases=["mod"],
|
||||
default=None,
|
||||
)
|
||||
|
@ -175,12 +224,12 @@ class CasesViewFlags(commands.FlagConverter, case_insensitive=True, delimiter="
|
|||
class CaseModifyFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"):
|
||||
status: bool | None = commands.flag(
|
||||
name="case_status",
|
||||
description="The status of the case.",
|
||||
description="Status of the case.",
|
||||
aliases=["s"],
|
||||
)
|
||||
reason: str | None = commands.flag(
|
||||
name="case_reason",
|
||||
description="The modified reason.",
|
||||
description="Modified reason.",
|
||||
aliases=["r"],
|
||||
)
|
||||
|
||||
|
@ -188,7 +237,7 @@ class CaseModifyFlags(commands.FlagConverter, case_insensitive=True, delimiter="
|
|||
class WarnFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"):
|
||||
reason: str = commands.flag(
|
||||
name="reason",
|
||||
description="The reason for the warn.",
|
||||
description="Reason for the warn.",
|
||||
aliases=["r"],
|
||||
default=MISSING,
|
||||
)
|
||||
|
@ -203,7 +252,7 @@ class WarnFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", pr
|
|||
class SnippetBanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"):
|
||||
reason: str = commands.flag(
|
||||
name="reason",
|
||||
description="The reason for the snippet ban.",
|
||||
description="Reason for the snippet ban.",
|
||||
aliases=["r"],
|
||||
default=MISSING,
|
||||
)
|
||||
|
@ -218,7 +267,7 @@ class SnippetBanFlags(commands.FlagConverter, case_insensitive=True, delimiter="
|
|||
class SnippetUnbanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"):
|
||||
reason: str = commands.flag(
|
||||
name="reason",
|
||||
description="The reason for the snippet unban.",
|
||||
description="Reason for the snippet unban.",
|
||||
aliases=["r"],
|
||||
default=MISSING,
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import re
|
||||
from datetime import UTC, datetime
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
import discord
|
||||
|
@ -26,6 +26,49 @@ def strip_formatting(content: str) -> str:
|
|||
return content.strip()
|
||||
|
||||
|
||||
def parse_time_string(time_str: str) -> timedelta:
|
||||
"""
|
||||
Convert a string representation of time into a datetime.timedelta object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
time_str : str
|
||||
The string representation of time to convert. (e.g., '60s', '1m', '2h', '10d')
|
||||
|
||||
Returns
|
||||
-------
|
||||
timedelta
|
||||
The timedelta object representing the time string.
|
||||
"""
|
||||
|
||||
# Define regex pattern to parse time strings
|
||||
time_pattern = re.compile(r"^(?P<value>\d+)(?P<unit>[smhdw])$")
|
||||
|
||||
# Match the input string with the pattern
|
||||
match = time_pattern.match(time_str)
|
||||
|
||||
if not match:
|
||||
msg = f"Invalid time format: '{time_str}'"
|
||||
raise ValueError(msg)
|
||||
|
||||
# Extract the value and unit from the pattern match
|
||||
value = int(match["value"])
|
||||
unit = match["unit"]
|
||||
|
||||
# Define the mapping of units to keyword arguments for timedelta
|
||||
unit_map = {"s": "seconds", "m": "minutes", "h": "hours", "d": "days", "w": "weeks"}
|
||||
|
||||
# Check if the unit is in the map
|
||||
if unit not in unit_map:
|
||||
msg = f"Unknown time unit: '{unit}'"
|
||||
raise ValueError(msg)
|
||||
|
||||
# Create the timedelta with the appropriate keyword argument
|
||||
kwargs = {unit_map[unit]: value}
|
||||
|
||||
return timedelta(**kwargs)
|
||||
|
||||
|
||||
def convert_to_seconds(time_str: str) -> int:
|
||||
"""
|
||||
Converts a formatted time string with the formats Mwdhms
|
||||
|
|
Loading…
Reference in a new issue