Skip to content

Commit

Permalink
Merge branch 'main' into levels
Browse files Browse the repository at this point in the history
  • Loading branch information
Atmois authored Sep 30, 2024
2 parents b0f124d + 6f1524d commit 9fbb1c7
Show file tree
Hide file tree
Showing 31 changed files with 737 additions and 376 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/linting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ permissions:

jobs:
Linting:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v4
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.4
rev: v8.19.2
hooks:
- id: gitleaks

Expand All @@ -12,7 +12,7 @@ repos:
- id: check-toml

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.4
rev: v0.6.7
hooks:
# Run the linter.
- id: ruff
Expand Down
11 changes: 11 additions & 0 deletions config/settings.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,14 @@ EMBED_ICONS:
KICK: "https://github.com/allthingslinux/tux/blob/main/assets/emojis/kick.png?raw=true"
TIMEOUT: "https://github.com/allthingslinux/tux/blob/main/assets/emojis/timeout.png?raw=true"
WARN: "https://github.com/allthingslinux/tux/blob/main/assets/emojis/warn.png?raw=true"

GIF_LIMITER:
RECENT_GIF_AGE: 60

GIF_LIMIT_EXCLUDE:
- 123456789012345

GIF_LIMITS_USER:
"123456789012345": 2
GIF_LIMITS_CHANNEL:
"123456789012345": 3
306 changes: 154 additions & 152 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ model Reminder {
reminder_expires_at DateTime
reminder_channel_id BigInt
reminder_user_id BigInt
reminder_sent Boolean @default(false)
guild_id BigInt
guild Guild @relation(fields: [guild_id], references: [guild_id])
Expand Down Expand Up @@ -182,4 +183,6 @@ enum CaseType {
UNJAIL
SNIPPETUNBAN
UNTEMPBAN
POLLBAN
POLLUNBAN
}
8 changes: 5 additions & 3 deletions tux/cogs/admin/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ async def register(
delete_after=30,
)

def _generate_password(self) -> str:
@staticmethod
def _generate_password() -> str:
password = "changeme" + "".join(str(random.randint(0, 9)) for _ in range(6))
password += "".join(random.choice("!@#$%^&*") for _ in range(4))
return password
Expand Down Expand Up @@ -161,7 +162,8 @@ async def _handle_response(
delete_after=30,
)

def _extract_mailbox_info(self, result: list[dict[str, str | None]]) -> str | None:
@staticmethod
def _extract_mailbox_info(result: list[dict[str, str | None]]) -> str | None:
for item in result:
if "msg" in item:
msg = item["msg"]
Expand All @@ -173,8 +175,8 @@ def _extract_mailbox_info(self, result: list[dict[str, str | None]]) -> str | No

return None

@staticmethod
async def _send_dm(
self,
interaction: discord.Interaction,
member: discord.Member,
mailbox_info: str,
Expand Down
1 change: 0 additions & 1 deletion tux/cogs/fun/fact.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ def __init__(self, bot: Tux) -> None:
"Linus Torvalds was around 22 years old when he started work on the Linux Kernel in 1991. In the same year, he also released prototypes of the kernel publicly.",
"Linux's 1.0 release was in March 1994.",
"Less than 1% of the latest kernel release includes code written by Linus Torvalds.",
"Linux is used by every major space programme in the world.",
"Approximately 13.3% of the latest Linux kernel is made up of blank lines.",
"Vim has various easter eggs. A notable one is found by typing :help 42 into the command bar.",
"Slackware is the oldest active linux distribution being released on the 17th July 1993.",
Expand Down
114 changes: 59 additions & 55 deletions tux/cogs/fun/imgeffect.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,86 +14,90 @@
class ImgEffect(commands.Cog):
def __init__(self, bot: Tux) -> None:
self.bot = bot
self.allowed_mimetypes = [
"image/jpeg",
"image/png",
]
self.allowed_mimetypes = ["image/jpeg", "image/png"]

imgeffect = app_commands.Group(name="imgeffect", description="Image effects")

@imgeffect.command(
name="deepfry",
description="Deepfry an image",
)
@imgeffect.command(name="deepfry", description="Deepfry an image")
async def deepfry(self, interaction: discord.Interaction, image: discord.Attachment) -> None:
"""
Deepfry an image.
Parameters
----------
interaction : discord.Interaction
The interaction object for the command.
image : discord.File
The image to deepfry.
"""

# check if the image is a image
logger.info(f"Content type: {image.content_type}, Filename: {image.filename}, URL: {image.url}")
if not self.is_valid_image(image):
await self.send_invalid_image_response(interaction)
return

if image.content_type not in self.allowed_mimetypes:
logger.error("The file is not a permitted image.")
await interaction.response.defer(ephemeral=True)

embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url,
title="Invalid File",
description="The file must be an image. Allowed types are PNG, JPEG, and JPG.",
)
pil_image = await self.fetch_image(image.url)

await interaction.response.send_message(embed=embed, ephemeral=True)
return
if pil_image:
deepfried_image = self.deepfry_image(pil_image)
await self.send_deepfried_image(interaction, deepfried_image)

# say that the image is being processed
logger.info("Processing image...")
await interaction.response.defer(ephemeral=True)
else:
await self.send_error_response(interaction)

def is_valid_image(self, image: discord.Attachment) -> bool:
logger.info(f"Content type: {image.content_type}, Filename: {image.filename}, URL: {image.url}")

return image.content_type in self.allowed_mimetypes

@staticmethod
async def fetch_image(url: str) -> Image.Image:
logger.info("Fetching image from URL with HTTPX...")

# open url with PIL
logger.info("Opening image with PIL and HTTPX...")
async with httpx.AsyncClient() as client:
response = await client.get(image.url)
response = await client.get(url)

pil_image = Image.open(io.BytesIO(response.content))
pil_image = pil_image.convert("RGB")
logger.info("Image opened with PIL.")
return Image.open(io.BytesIO(response.content)).convert("RGB")

# resize image to 25% then back to original size
logger.info("Resizing image...")
@staticmethod
def deepfry_image(pil_image: Image.Image) -> Image.Image:
pil_image = pil_image.resize((int(pil_image.width * 0.25), int(pil_image.height * 0.25)))
logger.info("Image resized.")

# increase sharpness
logger.info("Increasing sharpness...")
pil_image = ImageEnhance.Sharpness(pil_image).enhance(100.0)
logger.info("Sharpness increased.")

logger.info("Adjusting color...")
r = pil_image.split()[0]
r = ImageEnhance.Contrast(r).enhance(2.0)
r = ImageEnhance.Brightness(r).enhance(1.5)

colours = ((254, 0, 2), (255, 255, 15))
r = ImageOps.colorize(r, colours[0], colours[1])
pil_image = Image.blend(pil_image, r, 0.75)
logger.info("Color adjustment complete.")

# send image
logger.info("Sending image...")
pil_image = pil_image.resize((int(pil_image.width * 4), int(pil_image.height * 4)))
return pil_image.resize((int(pil_image.width * 4), int(pil_image.height * 4)))

async def send_invalid_image_response(self, interaction: discord.Interaction) -> None:
logger.error("The file is not a permitted image.")

embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url,
title="Invalid File",
description="The file must be an image. Allowed types are PNG, JPEG, and JPG.",
)

await interaction.response.send_message(embed=embed, ephemeral=True)

async def send_error_response(self, interaction: discord.Interaction) -> None:
logger.error("Error processing the image.")

embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url,
title="Error",
description="An error occurred while processing the image.",
)

await interaction.response.send_message(embed=embed, ephemeral=True)

@staticmethod
async def send_deepfried_image(interaction: discord.Interaction, deepfried_image: Image.Image) -> None:
arr = io.BytesIO()
pil_image.save(arr, format="JPEG", quality=1)
deepfried_image.save(arr, format="JPEG", quality=1)
arr.seek(0)

file = discord.File(arr, filename="deepfried.jpg")

await interaction.followup.send(file=file, ephemeral=True)
Expand Down
8 changes: 7 additions & 1 deletion tux/cogs/fun/random.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ async def coinflip(self, ctx: commands.Context[Tux]) -> None:
aliases=["eightball", "8b"],
)
@commands.guild_only()
async def eight_ball(self, ctx: commands.Context[Tux], *, question: str, cow: bool = False) -> None:
async def eight_ball(
self,
ctx: commands.Context[Tux],
*,
question: str,
cow: bool = False,
) -> None:
"""
Ask the magic 8ball a question.
Expand Down
9 changes: 5 additions & 4 deletions tux/cogs/info/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,15 @@ async def server(self, ctx: commands.Context[Tux]) -> None:
custom_color=discord.Color.blurple(),
custom_author_text="Server Information",
custom_author_icon_url=guild.icon.url,
custom_footer_text=f"ID: {guild.id} | Created: {guild.created_at.strftime('%B %d, %Y')}",
custom_footer_text=f"ID: {guild.id} | Created: {guild.created_at.strftime("%B %d, %Y")}",
)
.add_field(name="Owner", value=str(guild.owner.mention) if guild.owner else "Unknown")
.add_field(name="Vanity URL", value=guild.vanity_url_code or "None")
.add_field(name="Boosts", value=guild.premium_subscription_count)
.add_field(name="Text Channels", value=len(guild.text_channels))
.add_field(name="Voice Channels", value=len(guild.voice_channels))
.add_field(name="Forum Channels", value=len(guild.forums))
.add_field(name="Emojis", value=f"{len(guild.emojis)}/{2*guild.emoji_limit}")
.add_field(name="Emojis", value=f"{len(guild.emojis)}/{2 * guild.emoji_limit}")
.add_field(name="Stickers", value=f"{len(guild.stickers)}/{guild.sticker_limit}")
.add_field(name="Roles", value=len(guild.roles))
.add_field(name="Humans", value=sum(not member.bot for member in guild.members))
Expand Down Expand Up @@ -213,7 +213,7 @@ async def paginated_embed(
menu: ViewMenu = ViewMenu(ctx, menu_type=ViewMenu.TypeEmbed)
for chunk in chunks:
page_embed: discord.Embed = embed.copy()
page_embed.description = f"{list_type.capitalize()} list for {guild_name}:\n{' '.join(chunk)}"
page_embed.description = f"{list_type.capitalize()} list for {guild_name}:\n{" ".join(chunk)}"
menu.add_page(page_embed)

buttons = [
Expand All @@ -229,7 +229,8 @@ async def paginated_embed(

await menu.start()

def _chunks(self, it: Iterator[str], size: int) -> Generator[list[str], None, None]:
@staticmethod
def _chunks(it: Iterator[str], size: int) -> Generator[list[str], None, None]:
"""
Split an iterator into chunks of a specified size.
Expand Down
30 changes: 29 additions & 1 deletion tux/cogs/moderation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ async def send_embed(
if isinstance(log_channel, discord.TextChannel):
await log_channel.send(embed=embed)

@staticmethod
async def send_dm(
self,
ctx: commands.Context[Tux],
silent: bool,
user: discord.Member,
Expand Down Expand Up @@ -250,3 +250,31 @@ async def handle_case_response(

await self.send_embed(ctx, embed, log_type="mod")
await ctx.send(embed=embed, delete_after=30, ephemeral=True)

async def is_pollbanned(self, guild_id: int, user_id: int) -> bool:
"""
Check if a user is poll banned.
Parameters
----------
guild_id : int
The ID of the guild to check in.
user_id : int
The ID of the user to check.
Returns
-------
bool
True if the user is poll banned, False otherwise.
"""

# ban_cases = await self.case_controller.get_all_cases_by_type(guild_id, CaseType.POLLBAN)
# unban_cases = await self.case_controller.get_all_cases_by_type(guild_id, CaseType.POLLUNBAN)

ban_cases = await self.db.case.get_all_cases_by_type(guild_id, CaseType.POLLBAN)
unban_cases = await self.db.case.get_all_cases_by_type(guild_id, CaseType.POLLUNBAN)

ban_count = sum(case.case_user_id == user_id for case in ban_cases)
unban_count = sum(case.case_user_id == user_id for case in unban_cases)

return ban_count > unban_count
13 changes: 7 additions & 6 deletions tux/cogs/moderation/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,8 @@ async def _handle_case_list_response(

await menu.start()

@staticmethod
def _create_case_fields(
self,
moderator: discord.Member,
user: discord.Member | discord.User,
reason: str,
Expand Down Expand Up @@ -361,7 +361,8 @@ def _create_case_list_embed(

return embed

def _format_emoji(self, emoji: discord.Emoji | None) -> str:
@staticmethod
def _format_emoji(emoji: discord.Emoji | None) -> str:
return f"<:{emoji.name}:{emoji.id}>" if emoji else ""

def _get_case_status_emoji(self, case_status: bool | None) -> discord.Emoji | None:
Expand Down Expand Up @@ -392,16 +393,16 @@ def _get_case_type_emoji(self, case_type: CaseType) -> discord.Emoji | None:
def _get_case_action_emoji(self, case_type: CaseType) -> discord.Emoji | None:
action = None

if case_type in [
if case_type in {
CaseType.BAN,
CaseType.KICK,
CaseType.TIMEOUT,
CaseType.WARN,
CaseType.JAIL,
CaseType.SNIPPETBAN,
]:
}:
action = "added"
elif case_type in [CaseType.UNBAN, CaseType.UNTIMEOUT, CaseType.UNJAIL, CaseType.SNIPPETUNBAN]:
elif case_type in {CaseType.UNBAN, CaseType.UNTIMEOUT, CaseType.UNJAIL, CaseType.SNIPPETUNBAN}:
action = "removed"

if action is not None:
Expand All @@ -410,8 +411,8 @@ def _get_case_action_emoji(self, case_type: CaseType) -> discord.Emoji | None:
return self.bot.get_emoji(emoji_id)
return None

@staticmethod
def _get_case_description(
self,
case: Case,
case_status_emoji: str,
case_type_emoji: str,
Expand Down
2 changes: 1 addition & 1 deletion tux/cogs/moderation/jail.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ async def jail( # noqa: PLR0911
dm_sent = await self.send_dm(ctx, flags.silent, member, flags.reason, "jailed")
await self.handle_case_response(ctx, CaseType.JAIL, case.case_number, flags.reason, member, dm_sent)

@staticmethod
def _get_manageable_roles(
self,
member: discord.Member,
jail_role: discord.Role,
) -> list[discord.Role]:
Expand Down
Loading

0 comments on commit 9fbb1c7

Please sign in to comment.