diff --git a/.gitignore b/.gitignore index 429d31f..3f0e8b8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .vscode site .venv +__pycache__ diff --git a/aurora/aurora.py b/aurora/aurora.py index 15a9b2f..1efb03c 100644 --- a/aurora/aurora.py +++ b/aurora/aurora.py @@ -19,29 +19,22 @@ from redbot.core import app_commands, commands, data_manager from redbot.core.app_commands import Choice from redbot.core.bot import Red from redbot.core.commands.converter import parse_relativedelta, parse_timedelta -from redbot.core.utils.chat_formatting import (box, error, humanize_list, - humanize_timedelta, warning) +from redbot.core.utils.chat_formatting import box, error, humanize_list, humanize_timedelta, warning -from aurora.importers.aurora import ImportAuroraView -from aurora.importers.galacticbot import ImportGalacticBotView -from aurora.menus.addrole import Addrole -from aurora.menus.guild import Guild -from aurora.menus.immune import Immune -from aurora.menus.overrides import Overrides -from aurora.utilities.config import config, register_config -from aurora.utilities.database import (connect, create_guild_table, fetch_case, - mysql_log) -from aurora.utilities.factory import (addrole_embed, case_factory, - changes_factory, evidenceformat_factory, - guild_embed, immune_embed, - message_factory, overrides_embed) -from aurora.utilities.logger import logger -from aurora.utilities.utils import (check_moddable, check_permissions, - convert_timedelta_to_str, - fetch_channel_dict, fetch_user_dict, - generate_dict, get_footer_image, log, - send_evidenceformat, - timedelta_from_relativedelta) +from .importers.aurora import ImportAuroraView +from .importers.galacticbot import ImportGalacticBotView +from .menus.addrole import Addrole +from .menus.guild import Guild +from .menus.immune import Immune +from .menus.overrides import Overrides +from .models.change import Change +from .models.moderation import Moderation +from .utilities.config import config, register_config +from .utilities.database import connect, create_guild_table +from .utilities.factory import addrole_embed, case_factory, changes_factory, evidenceformat_factory, guild_embed, immune_embed, message_factory, overrides_embed +from .utilities.json import dump +from .utilities.logger import logger +from .utilities.utils import check_moddable, check_permissions, get_footer_image, log, send_evidenceformat, timedelta_from_relativedelta class Aurora(commands.Cog): @@ -50,7 +43,7 @@ class Aurora(commands.Cog): This cog stores all of its data in an SQLite database.""" __author__ = ["SeaswimmerTheFsh"] - __version__ = "2.1.2" + __version__ = "2.2.0" __documentation__ = "https://seacogs.coastalcommits.com/aurora/" async def red_delete_data_for_user(self, *, requester, user_id: int): @@ -149,7 +142,7 @@ class Aurora(commands.Cog): if entry.user.id == self.bot.user.id: return - duration = "NULL" + duration = None if entry.reason: reason = entry.reason + " (This action was performed without the bot.)" @@ -182,13 +175,14 @@ class Aurora(commands.Cog): else: return - await mysql_log( + Moderation.log( + self.bot, entry.guild.id, entry.user.id, moderation_type, "USER", entry.target.id, - 0, + None, duration, reason, ) @@ -203,7 +197,7 @@ class Aurora(commands.Cog): interaction: discord.Interaction, target: discord.User, reason: str, - silent: bool = None, + silent: bool | None = None, ): """Add a note to a user. @@ -227,7 +221,8 @@ class Aurora(commands.Cog): if silent is False: try: embed = await message_factory( - await self.bot.get_embed_color(interaction.channel), + bot=interaction.client, + color=await self.bot.get_embed_color(interaction.channel), guild=interaction.guild, moderator=interaction.user, reason=reason, @@ -238,23 +233,22 @@ class Aurora(commands.Cog): except discord.errors.HTTPException: pass - moderation_id = await mysql_log( + moderation = Moderation.log( + interaction.client, interaction.guild.id, interaction.user.id, "NOTE", "USER", target.id, - 0, - "NULL", + None, + None, reason, ) await interaction.edit_original_response( - content=f"{target.mention} has received a note! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`" + content=f"{target.mention} has received a note! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`" ) - await log(interaction, moderation_id) - - case = await fetch_case(moderation_id, interaction.guild.id) - await send_evidenceformat(interaction, case) + await log(interaction, moderation.id) + await send_evidenceformat(interaction, moderation.id) @app_commands.command(name="warn") async def warn( @@ -262,7 +256,7 @@ class Aurora(commands.Cog): interaction: discord.Interaction, target: discord.Member, reason: str, - silent: bool = None, + silent: bool | None = None, ): """Warn a user. @@ -286,7 +280,8 @@ class Aurora(commands.Cog): if silent is False: try: embed = await message_factory( - await self.bot.get_embed_color(interaction.channel), + bot=interaction.client, + color=await self.bot.get_embed_color(interaction.channel), guild=interaction.guild, moderator=interaction.user, reason=reason, @@ -297,23 +292,22 @@ class Aurora(commands.Cog): except discord.errors.HTTPException: pass - moderation_id = await mysql_log( + moderation = Moderation.log( + interaction.client, interaction.guild.id, interaction.user.id, "WARN", "USER", target.id, - 0, - "NULL", + None, + None, reason, ) await interaction.edit_original_response( - content=f"{target.mention} has been warned! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`" + content=f"{target.mention} has been warned! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`" ) - await log(interaction, moderation_id) - - case = await fetch_case(moderation_id, interaction.guild.id) - await send_evidenceformat(interaction, case) + await log(interaction, moderation.id) + await send_evidenceformat(interaction, moderation.id) @app_commands.command(name="addrole") async def addrole( @@ -322,8 +316,8 @@ class Aurora(commands.Cog): target: discord.Member, role: discord.Role, reason: str, - duration: str = None, - silent: bool = None, + duration: str | None = None, + silent: bool | None = None, ): """Add a role to a user. @@ -356,7 +350,7 @@ class Aurora(commands.Cog): ) return else: - parsed_time = "NULL" + parsed_time = None if role.id not in addrole_whitelist: await interaction.response.send_message( @@ -382,7 +376,8 @@ class Aurora(commands.Cog): if silent is False: try: embed = await message_factory( - await self.bot.get_embed_color(interaction.channel), + bot=interaction.client, + color=await self.bot.get_embed_color(interaction.channel), guild=interaction.guild, moderator=interaction.user, reason=reason, @@ -403,7 +398,8 @@ class Aurora(commands.Cog): content=f"{target.mention} has been given the {role.mention} role{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}!\n**Reason** - `{reason}`" ) - moderation_id = await mysql_log( + moderation = Moderation.log( + interaction.client, interaction.guild.id, interaction.user.id, "ADDROLE", @@ -414,12 +410,10 @@ class Aurora(commands.Cog): reason, ) await response.edit( - content=f"{target.mention} has been given the {role.mention} role{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`", + content=f"{target.mention} has been given the {role.mention} role{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`", ) - await log(interaction, moderation_id) - - case = await fetch_case(moderation_id, interaction.guild.id) - await send_evidenceformat(interaction, case) + await log(interaction, moderation.id) + await send_evidenceformat(interaction, moderation.id) @app_commands.command(name="removerole") async def removerole( @@ -428,8 +422,8 @@ class Aurora(commands.Cog): target: discord.Member, role: discord.Role, reason: str, - duration: str = None, - silent: bool = None, + duration: str | None = None, + silent: bool | None = None, ): """Remove a role from a user. @@ -462,7 +456,7 @@ class Aurora(commands.Cog): ) return else: - parsed_time = "NULL" + parsed_time = None if role.id not in addrole_whitelist: await interaction.response.send_message( @@ -488,7 +482,8 @@ class Aurora(commands.Cog): if silent is False: try: embed = await message_factory( - await self.bot.get_embed_color(interaction.channel), + bot=interaction.client, + color=await self.bot.get_embed_color(interaction.channel), guild=interaction.guild, moderator=interaction.user, reason=reason, @@ -509,7 +504,8 @@ class Aurora(commands.Cog): content=f"{target.mention} has had the {role.mention} role removed{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}!\n**Reason** - `{reason}`" ) - moderation_id = await mysql_log( + moderation = Moderation.log( + interaction.client, interaction.guild.id, interaction.user.id, "REMOVEROLE", @@ -520,12 +516,10 @@ class Aurora(commands.Cog): reason, ) await response.edit( - content=f"{target.mention} has had the {role.mention} role removed{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`", + content=f"{target.mention} has had the {role.mention} role removed{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`", ) - await log(interaction, moderation_id) - - case = await fetch_case(moderation_id, interaction.guild.id) - await send_evidenceformat(interaction, case) + await log(interaction, moderation.id) + await send_evidenceformat(interaction, moderation.id) @app_commands.command(name="mute") async def mute( @@ -534,7 +528,7 @@ class Aurora(commands.Cog): target: discord.Member, duration: str, reason: str, - silent: bool = None, + silent: bool | None = None, ): """Mute a user. @@ -585,7 +579,8 @@ class Aurora(commands.Cog): if silent is False: try: embed = await message_factory( - await self.bot.get_embed_color(interaction.channel), + bot=interaction.client, + color=await self.bot.get_embed_color(interaction.channel), guild=interaction.guild, moderator=interaction.user, reason=reason, @@ -597,31 +592,30 @@ class Aurora(commands.Cog): except discord.errors.HTTPException: pass - moderation_id = await mysql_log( + moderation = Moderation.log( + interaction.client, interaction.guild.id, interaction.user.id, "MUTE", "USER", target.id, - 0, + None, parsed_time, reason, ) await interaction.edit_original_response( - content=f"{target.mention} has been muted for {humanize_timedelta(timedelta=parsed_time)}! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`" + content=f"{target.mention} has been muted for {humanize_timedelta(timedelta=parsed_time)}! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`" ) - await log(interaction, moderation_id) - - case = await fetch_case(moderation_id, interaction.guild.id) - await send_evidenceformat(interaction, case) + await log(interaction, moderation.id) + await send_evidenceformat(interaction, moderation.id) @app_commands.command(name="unmute") async def unmute( self, interaction: discord.Interaction, target: discord.Member, - reason: str = None, - silent: bool = None, + reason: str | None = None, + silent: bool | None = None, ): """Unmute a user. @@ -661,7 +655,8 @@ class Aurora(commands.Cog): if silent is False: try: embed = await message_factory( - await self.bot.get_embed_color(interaction.channel), + bot=interaction.client, + color=await self.bot.get_embed_color(interaction.channel), guild=interaction.guild, moderator=interaction.user, reason=reason, @@ -672,23 +667,22 @@ class Aurora(commands.Cog): except discord.errors.HTTPException: pass - moderation_id = await mysql_log( + moderation = await Moderation.log( + interaction.client, interaction.guild.id, interaction.user.id, "UNMUTE", "USER", target.id, - 0, - "NULL", + None, + None, reason, ) await interaction.edit_original_response( - content=f"{target.mention} has been unmuted! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`" + content=f"{target.mention} has been unmuted! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`" ) - await log(interaction, moderation_id) - - case = await fetch_case(moderation_id, interaction.guild.id) - await send_evidenceformat(interaction, case) + await log(interaction, moderation.id) + await send_evidenceformat(interaction, moderation.id) @app_commands.command(name="kick") async def kick( @@ -696,7 +690,7 @@ class Aurora(commands.Cog): interaction: discord.Interaction, target: discord.Member, reason: str, - silent: bool = None, + silent: bool | None = None, ): """Kick a user. @@ -720,7 +714,8 @@ class Aurora(commands.Cog): if silent is False: try: embed = await message_factory( - await self.bot.get_embed_color(interaction.channel), + bot=interaction.client, + color=await self.bot.get_embed_color(interaction.channel), guild=interaction.guild, moderator=interaction.user, reason=reason, @@ -733,23 +728,22 @@ class Aurora(commands.Cog): await target.kick(reason=f"Kicked by {interaction.user.id} for: {reason}") - moderation_id = await mysql_log( + moderation = Moderation.log( + interaction.client, interaction.guild.id, interaction.user.id, "KICK", "USER", target.id, - 0, - "NULL", + None, + None, reason, ) await interaction.edit_original_response( - content=f"{target.mention} has been kicked! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`" + content=f"{target.mention} has been kicked! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`" ) - await log(interaction, moderation_id) - - case = await fetch_case(moderation_id, interaction.guild.id) - await send_evidenceformat(interaction, case) + await log(interaction, moderation.id) + await send_evidenceformat(interaction, moderation.id) @app_commands.command(name="ban") @app_commands.choices( @@ -767,9 +761,9 @@ class Aurora(commands.Cog): interaction: discord.Interaction, target: discord.User, reason: str, - duration: str = None, - delete_messages: Choice[int] = None, - silent: bool = None, + duration: str | None = None, + delete_messages: Choice[int] | None = None, + silent: bool | None = None, ): """Ban a user. @@ -823,7 +817,8 @@ class Aurora(commands.Cog): try: embed = await message_factory( - await self.bot.get_embed_color(interaction.channel), + bot=interaction.client, + color=await self.bot.get_embed_color(interaction.channel), guild=interaction.guild, moderator=interaction.user, reason=reason, @@ -841,23 +836,22 @@ class Aurora(commands.Cog): delete_message_seconds=delete_messages_seconds, ) - moderation_id = await mysql_log( + moderation = Moderation.log( + interaction.client, interaction.guild.id, interaction.user.id, "TEMPBAN", "USER", target.id, - 0, + None, parsed_time, reason, ) await interaction.edit_original_response( - content=f"{target.mention} has been banned for {humanize_timedelta(timedelta=parsed_time)}! (Case `#{moderation_id}`)\n**Reason** - `{reason}`" + content=f"{target.mention} has been banned for {humanize_timedelta(timedelta=parsed_time)}! (Case `#{moderation.id}`)\n**Reason** - `{reason}`" ) - await log(interaction, moderation_id) - - case = await fetch_case(moderation_id, interaction.guild.id) - await send_evidenceformat(interaction, case) + await log(interaction, moderation.id) + await send_evidenceformat(interaction, moderation.id) else: await interaction.response.send_message( content=f"{target.mention} has been banned!\n**Reason** - `{reason}`" @@ -867,8 +861,9 @@ class Aurora(commands.Cog): silent = not await config.guild(interaction.guild).dm_users() if silent is False: try: - embed = embed = await message_factory( - await self.bot.get_embed_color(interaction.channel), + embed = await message_factory( + bot=interaction.client, + color=await self.bot.get_embed_color(interaction.channel), guild=interaction.guild, moderator=interaction.user, reason=reason, @@ -885,7 +880,8 @@ class Aurora(commands.Cog): delete_message_seconds=delete_messages_seconds, ) - moderation_id = await mysql_log( + moderation = Moderation.log( + interaction.client, interaction.guild.id, interaction.user.id, "BAN", @@ -896,20 +892,18 @@ class Aurora(commands.Cog): reason, ) await interaction.edit_original_response( - content=f"{target.mention} has been banned! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`" + content=f"{target.mention} has been banned! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`" ) - await log(interaction, moderation_id) - - case = await fetch_case(moderation_id, interaction.guild.id) - await send_evidenceformat(interaction, case) + await log(interaction, moderation.id) + await send_evidenceformat(interaction, moderation.id) @app_commands.command(name="unban") async def unban( self, interaction: discord.Interaction, target: discord.User, - reason: str = None, - silent: bool = None, + reason: str | None = None, + silent: bool | None = None, ): """Unban a user. @@ -951,7 +945,8 @@ class Aurora(commands.Cog): if silent is False: try: embed = await message_factory( - await self.bot.get_embed_color(interaction.channel), + bot=interaction.client, + color=await self.bot.get_embed_color(interaction.channel), guild=interaction.guild, moderator=interaction.user, reason=reason, @@ -962,34 +957,76 @@ class Aurora(commands.Cog): except discord.errors.HTTPException: pass - moderation_id = await mysql_log( + moderation = Moderation.log( + interaction.client, interaction.guild.id, interaction.user.id, "UNBAN", "USER", target.id, - 0, - "NULL", + None, + None, reason, ) await interaction.edit_original_response( - content=f"{target.mention} has been unbanned! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`" + content=f"{target.mention} has been unbanned! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`" ) - await log(interaction, moderation_id) + await log(interaction, moderation.id) + await send_evidenceformat(interaction, moderation.id) - case = await fetch_case(moderation_id, interaction.guild.id) - await send_evidenceformat(interaction, case) + @app_commands.command(name="slowmode") + async def slowmode( + self, + interaction: discord.Interaction, + interval: int, + channel: discord.TextChannel | None = None, + reason: str | None = None, + ): + """Set the slowmode of a channel. + + Parameters + ----------- + interval: int + The slowmode interval in seconds + channel: discord.TextChannel + The channel to set the slowmode in + reason: str + Why are you setting the slowmode?""" + if channel is None: + channel = interaction.channel + + if not await check_moddable(channel, interaction, ["manage_channels"]): + return + + await channel.edit(slowmode_delay=interval) + await interaction.response.send_message(f"Slowmode in {channel.mention} has been set to {interval} seconds!\n**Reason** - `{reason}`") + + moderation = Moderation.log( + interaction.client, + interaction.guild.id, + interaction.user.id, + "SLOWMODE", + "CHANNEL", + channel.id, + None, + None, + reason, + metadata={"interval": f"{interval} seconds"} + ) + await interaction.edit_original_response(content=f"Slowmode in {channel.mention} has been set to {interval} seconds! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`") + await log(interaction, moderation.id) + await send_evidenceformat(interaction, moderation.id) @app_commands.command(name="history") async def history( self, interaction: discord.Interaction, - target: discord.User = None, - moderator: discord.User = None, - pagesize: app_commands.Range[int, 1, 20] = None, + target: discord.User | None = None, + moderator: discord.User | None = None, + pagesize: app_commands.Range[int, 1, 20] | None = None, page: int = 1, - ephemeral: bool = None, - inline: bool = None, + ephemeral: bool | None = None, + inline: bool | None = None, export: bool = False, ): """List previous infractions. @@ -1055,21 +1092,6 @@ class Aurora(commands.Cog): database = connect() if export: - database.row_factory = sqlite3.Row - cursor = database.cursor() - - query = f"""SELECT * - FROM moderation_{interaction.guild.id} - ORDER BY moderation_id DESC;""" - cursor.execute(query) - - results = cursor.fetchall() - - cases = [] - for result in results: - case = dict(result) - cases.append(case) - try: filename = ( str(data_manager.cog_data_path(cog_instance=self)) @@ -1077,8 +1099,10 @@ class Aurora(commands.Cog): + f"moderation_{interaction.guild.id}.json" ) + cases = Moderation.get_latest(bot=interaction.client, guild_id=interaction.guild.id) + with open(filename, "w", encoding="utf-8") as f: - json.dump(cases, f, indent=2) + dump(obj=cases, fp=f, indent=2) await interaction.followup.send( file=discord.File( @@ -1096,40 +1120,17 @@ class Aurora(commands.Cog): + box(e, "py"), ephemeral=ephemeral, ) - cursor.close() database.close() return - cursor = database.cursor() - if target: - query = f"""SELECT * - FROM moderation_{interaction.guild.id} - WHERE target_id = ? - ORDER BY moderation_id DESC;""" - cursor.execute(query, (target.id,)) + moderations = Moderation.find_by_target(interaction.client, interaction.guild.id, target.id) elif moderator: - query = f"""SELECT * - FROM moderation_{interaction.guild.id} - WHERE moderator_id = ? - ORDER BY moderation_id DESC;""" - cursor.execute(query, (moderator.id,)) + moderations = Moderation.find_by_moderator(interaction.client, interaction.guild.id, moderator.id) else: - query = f"""SELECT * - FROM moderation_{interaction.guild.id} - ORDER BY moderation_id DESC;""" - cursor.execute(query) + moderations = Moderation.get_latest(interaction.client, interaction.guild.id) - results = cursor.fetchall() - result_dict_list = [] - - for result in results: - case_dict = generate_dict(result) - if case_dict["moderation_id"] == 0: - continue - result_dict_list.append(case_dict) - - case_quantity = len(result_dict_list) + case_quantity = len(moderations) page_quantity = ceil(case_quantity / pagesize) start_index = (page - 1) * pagesize end_index = page * pagesize @@ -1142,74 +1143,44 @@ class Aurora(commands.Cog): memory_dict = {} - for case in result_dict_list[start_index:end_index]: - if case["target_id"] not in memory_dict: - if case["target_type"] == "USER": - memory_dict[str(case["target_id"])] = await fetch_user_dict( - interaction.client, case["target_id"] - ) - elif case["target_type"] == "CHANNEL": - memory_dict[str(case["target_id"])] = await fetch_channel_dict( - interaction.guild, case["target_id"] - ) - target_user = memory_dict[str(case["target_id"])] + for mod in moderations[start_index:end_index]: + if mod.target_id not in memory_dict: + memory_dict.update({ + str(mod.target_id): await mod.get_target() + }) + target = memory_dict[str(mod.target_id)] - if case["target_type"] == "USER": - target_name = ( - f"`{target_user['name']}`" - if target_user["discriminator"] == "0" - else f"`{target_user['name']}#{target_user['discriminator']}`" - ) - elif case["target_type"] == "CHANNEL": - target_name = f"`{target_user['mention']}`" + if mod.moderator_id not in memory_dict: + memory_dict.update({ + str(mod.moderator_id): await mod.get_moderator() + }) + moderator = memory_dict[str(mod.moderator_id)] - if case["moderator_id"] not in memory_dict: - memory_dict[str(case["moderator_id"])] = await fetch_user_dict( - interaction.client, case["moderator_id"] - ) - moderator_user = memory_dict[str(case["moderator_id"])] - moderator_name = ( - f"`{moderator_user['name']}`" - if moderator_user["discriminator"] == "0" - else f"`{moderator_user['name']}#{moderator_user['discriminator']}`" - ) + field_name = f"Case #{mod.id:,} ({str.title(mod.type)})" + field_value = f"**Target:** `{target.name}` ({target.id})\n**Moderator:** `{moderator.name}` ({moderator.id})" - field_name = f"Case #{case['moderation_id']:,} ({str.title(case['moderation_type'])})" - field_value = f"**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})" - - if len(case["reason"]) > 125: - field_value += f"\n**Reason:** `{str(case['reason'])[:125]}...`" + if len(str(mod.reason)) > 125: + field_value += f"\n**Reason:** `{str(mod.reason)[:125]}...`" else: - field_value += f"\n**Reason:** `{str(case['reason'])}`" + field_value += f"\n**Reason:** `{str(mod.reason)}`" - if case["duration"] != "NULL": - td = timedelta( - **{ - unit: int(val) - for unit, val in zip( - ["hours", "minutes", "seconds"], case["duration"].split(":") - ) - } - ) + if mod.duration: duration_embed = ( - f"{humanize_timedelta(timedelta=td)} | " - if bool(case["expired"]) is False - else f"{humanize_timedelta(timedelta=td)} | Expired" + f"{humanize_timedelta(timedelta=mod.duration)} | " + if mod.expired is False + else f"{humanize_timedelta(timedelta=mod.duration)} | Expired" ) field_value += f"\n**Duration:** {duration_embed}" field_value += ( - f"\n**Timestamp:** | " + f"\n**Timestamp:** | " ) - if case["role_id"] != "0": - role = interaction.guild.get_role(int(case["role_id"])) - if role is not None: - field_value += f"\n**Role:** {role.mention}" - else: - field_value += f"\n**Role:** Deleted Role ({case['role_id']})" + if mod.role_id: + role = await mod.get_role() + field_value += f"\n**Role:** {role.mention} ({role.id})" - if bool(case["resolved"]): + if mod.resolved: field_value += "\n**Resolved:** True" embed.add_field(name=field_name, value=field_value, inline=inline) @@ -1218,7 +1189,7 @@ class Aurora(commands.Cog): @app_commands.command(name="resolve") async def resolve( - self, interaction: discord.Interaction, case: int, reason: str = None + self, interaction: discord.Interaction, case: int, reason: str | None = None ): """Resolve a specific case. @@ -1230,7 +1201,7 @@ class Aurora(commands.Cog): Reason for resolving case""" permissions = check_permissions( interaction.client.user, - ["embed_links", "moderate_members", "ban_members"], + ("embed_links", "moderate_members", "ban_members"), interaction, ) if permissions: @@ -1242,39 +1213,14 @@ class Aurora(commands.Cog): ) return - database = connect() - cursor = database.cursor() - - query_1 = ( - f"SELECT * FROM moderation_{interaction.guild.id} WHERE moderation_id = ?;" - ) - cursor.execute(query_1, (case,)) - result_1 = cursor.fetchone() - if result_1 is None or case == 0: + try: + moderation = Moderation.find_by_id(interaction.client, case, interaction.guild.id) + except ValueError: await interaction.response.send_message( - content=error(f"There is no moderation with a case number of {case}."), - ephemeral=True, + content=error(f"Case #{case:,} does not exist!"), ephemeral=True ) return - - query_2 = f"SELECT * FROM moderation_{interaction.guild.id} WHERE moderation_id = ? AND resolved = 0;" - cursor.execute(query_2, (case,)) - result_2 = cursor.fetchone() - if result_2 is None: - await interaction.response.send_message( - content=error( - f"This moderation has already been resolved!\nUse `/case {case}` for more information." - ), - ephemeral=True, - ) - return - - case_dict = generate_dict(result_2) - if reason is None: - reason = "No reason given." - - changes: list = case_dict["changes"] - if len(changes) > 25: + if len(moderation.changes) > 25: await interaction.response.send_message( content=error( "Due to limitations with Discord's embed system, you cannot edit a case more than 25 times." @@ -1282,81 +1228,28 @@ class Aurora(commands.Cog): ephemeral=True, ) return - if not changes: - changes.append( - { - "type": "ORIGINAL", - "timestamp": case_dict["timestamp"], - "reason": case_dict["reason"], - "user_id": case_dict["moderator_id"], - } - ) - changes.append( - { - "type": "RESOLVE", - "timestamp": int(time.time()), - "reason": reason, - "user_id": interaction.user.id, - } - ) - if case_dict["moderation_type"] in ["UNMUTE", "UNBAN"]: - await interaction.response.send_message( - content=error("You cannot resolve this type of moderation!"), - ephemeral=True, - ) - return - - if case_dict["moderation_type"] in ["MUTE", "TEMPBAN", "BAN"]: - if case_dict["moderation_type"] == "MUTE": - try: - member = await interaction.guild.fetch_member( - case_dict["target_id"] - ) - - await member.timeout( - None, reason=f"Case #{case:,} resolved by {interaction.user.id}" - ) - except discord.NotFound: - pass - - if case_dict["moderation_type"] in ["TEMPBAN", "BAN"]: - try: - user = await interaction.client.fetch_user(case_dict["target_id"]) - - await interaction.guild.unban( - user, reason=f"Case #{case} resolved by {interaction.user.id}" - ) - except discord.NotFound: - pass - - resolve_query = f"UPDATE `moderation_{interaction.guild.id}` SET resolved = 1, changes = ?, resolved_by = ?, resolve_reason = ? WHERE moderation_id = ?" - else: - resolve_query = f"UPDATE `moderation_{interaction.guild.id}` SET resolved = 1, changes = ?, resolved_by = ?, resolve_reason = ? WHERE moderation_id = ?" - - cursor.execute( - resolve_query, - ( - json.dumps(changes), - interaction.user.id, - reason, - case_dict["moderation_id"], - ), - ) - database.commit() + try: + await moderation.resolve(interaction.user.id, reason) + except (ValueError, TypeError) as e: + if e == ValueError: + await interaction.response.send_message( + content=error("This case has already been resolved!"), ephemeral=True + ) + elif e == TypeError: + await interaction.response.send_message( + content=error("This case type cannot be resolved!"), ephemeral=True + ) embed = await case_factory( interaction=interaction, - case_dict=await fetch_case(case, interaction.guild.id), + moderation=moderation, ) await interaction.response.send_message( content=f"✅ Moderation #{case:,} resolved!", embed=embed ) await log(interaction, case, resolved=True) - cursor.close() - database.close() - @app_commands.command(name="case") @app_commands.choices( export=[ @@ -1368,10 +1261,10 @@ class Aurora(commands.Cog): self, interaction: discord.Interaction, case: int, - ephemeral: bool = None, + ephemeral: bool | None = None, evidenceformat: bool = False, changes: bool = False, - export: Choice[str] = None, + export: Choice[str] | None = None, ): """Check the details of a specific case. @@ -1404,68 +1297,67 @@ class Aurora(commands.Cog): or False ) - if case != 0: - case_dict = await fetch_case(case, interaction.guild.id) - if case_dict: - if export: - if export.value == "file" or len(str(case_dict)) > 1800: - filename = ( - str(data_manager.cog_data_path(cog_instance=self)) - + str(os.sep) - + f"moderation_{interaction.guild.id}_case_{case}.json" - ) + try: + mod = Moderation.find_by_id(interaction.client, case, interaction.guild.id) + except ValueError: + await interaction.response.send_message( + content=error(f"Case #{case:,} does not exist!"), ephemeral=True + ) + return - with open(filename, "w", encoding="utf-8") as f: - json.dump(case_dict, f, indent=2) + if export: + if export.value == "file" or len(mod.to_json(2)) > 1800: + filename = ( + str(data_manager.cog_data_path(cog_instance=self)) + + str(os.sep) + + f"moderation_{interaction.guild.id}_case_{case}.json" + ) - if export.value == "codeblock": - content = f"Case #{case:,} exported.\n" + warning( - "Case was too large to export as codeblock, so it has been uploaded as a `.json` file." - ) - else: - content = f"Case #{case:,} exported." - - await interaction.response.send_message( - content=content, - file=discord.File( - filename, - f"moderation_{interaction.guild.id}_case_{case}.json", - ), - ephemeral=ephemeral, - ) - - os.remove(filename) - return - await interaction.response.send_message( - content=box(json.dumps(case_dict, indent=2), 'json'), - ephemeral=ephemeral, - ) - return - if changes: - embed = await changes_factory( - interaction=interaction, case_dict=case_dict - ) - await interaction.response.send_message( - embed=embed, ephemeral=ephemeral - ) - elif evidenceformat: - content = await evidenceformat_factory( - interaction=interaction, case_dict=case_dict - ) - await interaction.response.send_message( - content=content, ephemeral=ephemeral + with open(filename, "w", encoding="utf-8") as f: + mod.to_json(2, f) + if export.value == "codeblock": + content = f"Case #{case:,} exported.\n" + warning( + "Case was too large to export as codeblock, so it has been uploaded as a `.json` file." ) else: - embed = await case_factory( - interaction=interaction, case_dict=case_dict - ) - await interaction.response.send_message( - embed=embed, ephemeral=ephemeral - ) + content = f"Case #{case:,} exported." + + await interaction.response.send_message( + content=content, + file=discord.File( + filename, + f"moderation_{interaction.guild.id}_case_{case}.json", + ), + ephemeral=ephemeral, + ) + + os.remove(filename) return - await interaction.response.send_message( - content=f"No case with case number `{case}` found.", ephemeral=True - ) + await interaction.response.send_message( + content=box(mod.to_json(2), 'json'), + ephemeral=ephemeral, + ) + return + if changes: + embed = await changes_factory( + interaction=interaction, moderation=mod + ) + await interaction.response.send_message( + embed=embed, ephemeral=ephemeral + ) + elif evidenceformat: + content = await evidenceformat_factory(moderation=mod) + await interaction.response.send_message( + content=content, ephemeral=ephemeral + ) + else: + embed = await case_factory( + interaction=interaction, moderation=mod + ) + await interaction.response.send_message( + embed=embed, ephemeral=ephemeral + ) + return @app_commands.command(name="edit") async def edit( @@ -1473,7 +1365,7 @@ class Aurora(commands.Cog): interaction: discord.Interaction, case: int, reason: str, - duration: str = None, + duration: str | None = None, ): """Edit the reason of a specific case. @@ -1498,122 +1390,96 @@ class Aurora(commands.Cog): ) return - if case != 0: - parsed_time = None - case_dict = await fetch_case(case, interaction.guild.id) - if case_dict: - if duration: - parsed_time = parse_timedelta(duration) - if parsed_time is None: - await interaction.response.send_message( - error("Please provide a valid duration!"), ephemeral=True - ) - return + try: + moderation = Moderation.find_by_id(interaction.client, case, interaction.guild.id) + old_moderation = moderation + except ValueError: + await interaction.response.send_message( + content=error(f"Case #{case:,} does not exist!"), ephemeral=True + ) + return - end_timestamp = case_dict["timestamp"] + parsed_time.total_seconds() + if len(moderation.changes) > 25: + return await interaction.response.send_message( + content=error( + "Due to limitations with Discord's embed system, you cannot edit a case more than 25 times." + ), + ephemeral=True, + ) - if case_dict["moderation_type"] == "MUTE": - if ( - time.time() - case_dict["timestamp"] - ) + parsed_time.total_seconds() > 2419200: - await interaction.response.send_message( - error( - "Please provide a duration that is less than 28 days from the initial moderation." - ) - ) - return - - try: - member = await interaction.guild.fetch_member( - case_dict["target_id"] - ) - - await member.timeout( - parsed_time, - reason=f"Case #{case:,} edited by {interaction.user.id}", - ) - except discord.NotFound: - pass - - changes: list = case_dict["changes"] - if len(changes) > 25: - await interaction.response.send_message( - content=error( - "Due to limitations with Discord's embed system, you cannot edit a case more than 25 times." - ), - ephemeral=True, - ) - return - if not changes: - changes.append( - { - "type": "ORIGINAL", - "timestamp": case_dict["timestamp"], - "reason": case_dict["reason"], - "user_id": case_dict["moderator_id"], - "duration": case_dict["duration"], - "end_timestamp": case_dict["end_timestamp"], - } - ) - if parsed_time: - changes.append( - { - "type": "EDIT", - "timestamp": int(time.time()), - "reason": reason, - "user_id": interaction.user.id, - "duration": convert_timedelta_to_str(parsed_time), - "end_timestamp": end_timestamp, - } - ) - else: - changes.append( - { - "type": "EDIT", - "timestamp": int(time.time()), - "reason": reason, - "user_id": interaction.user.id, - "duration": case_dict["duration"], - "end_timestamp": case_dict["end_timestamp"], - } - ) - - database = connect() - cursor = database.cursor() - - if parsed_time: - update_query = f"UPDATE `moderation_{interaction.guild.id}` SET changes = ?, reason = ?, duration = ?, end_timestamp = ? WHERE moderation_id = ?" - cursor.execute( - update_query, - ( - json.dumps(changes), - reason, - convert_timedelta_to_str(parsed_time), - end_timestamp, - case, - ), - ) - else: - update_query = f"UPDATE `moderation_{interaction.guild.id}` SET changes = ?, reason = ? WHERE moderation_id = ?" - cursor.execute(update_query, (json.dumps(changes), reason, case)) - database.commit() - - new_case = await fetch_case(case, interaction.guild.id) - embed = await case_factory(interaction=interaction, case_dict=new_case) - - await interaction.response.send_message( - content=f"✅ Moderation #{case:,} edited!", - embed=embed, - ephemeral=True, + if duration: + moderation.duration = parse_timedelta(duration) + if moderation.duration is None: + return await interaction.response.send_message( + error("Please provide a valid duration!"), ephemeral=True ) - await log(interaction, case) - cursor.close() - database.close() - return + moderation.end_timestamp = moderation.timestamp + moderation.duration.total_seconds() + + if moderation.type == "MUTE": + if ( + time.time() - moderation.unix_timestamp + ) + moderation.duration.total_seconds() > 2419200: + return await interaction.response.send_message( + error( + "Please provide a duration that is less than 28 days from the initial moderation." + ) + ) + + try: + member = await interaction.guild.fetch_member( + moderation.target_id + ) + + await member.timeout( + moderation.duration, + reason=f"Case #{case:,} edited by {interaction.user.id}", + ) + except discord.NotFound: + pass + + if reason: + moderation.reason = reason + + if not moderation.changes: + moderation.changes.append(Change.from_dict(interaction.client, { + "type": "ORIGINAL", + "timestamp": old_moderation.timestamp, + "reason": old_moderation.reason, + "user_id": old_moderation.moderator_id, + "duration": old_moderation.duration, + "end_timestamp": old_moderation.end_timestamp, + })) + if duration: + moderation.changes.append(Change.from_dict(interaction.client, { + "type": "EDIT", + "timestamp": int(time.time()), + "reason": reason, + "user_id": interaction.user.id, + "duration": moderation.duration, + "end_timestamp": moderation.end_timestamp, + })) + else: + moderation.changes.append(Change.from_dict(interaction.client, { + "type": "EDIT", + "timestamp": int(time.time()), + "reason": reason, + "user_id": interaction.user.id, + "duration": moderation.duration, + "end_timestamp": moderation.end_timestamp, + })) + + moderation.update() + embed = await case_factory(interaction=interaction, moderation=moderation) + await interaction.response.send_message( - content=error(f"No case with case number `{case}` found."), ephemeral=True + content=f"✅ Moderation #{case:,} edited!", + embed=embed, + ephemeral=True, ) + await log(interaction, case) + + return @tasks.loop(minutes=1) async def handle_expiry(self): @@ -1630,7 +1496,7 @@ class Aurora(commands.Cog): if not await self.bot.cog_disabled_in_guild(self, guild): time_per_guild = time.time() - tempban_query = f"SELECT target_id, moderation_id FROM moderation_{guild.id} WHERE end_timestamp != 0 AND end_timestamp <= ? AND moderation_type = 'TEMPBAN' AND expired = 0" + tempban_query = f"SELECT target_id, moderation_id FROM moderation_{guild.id} WHERE end_timestamp IS NOT NULL 0 AND end_timestamp <= ? AND moderation_type = 'TEMPBAN' AND expired = 0" try: cursor.execute(tempban_query, (time.time(),)) @@ -1655,7 +1521,8 @@ class Aurora(commands.Cog): ) embed = await message_factory( - await self.bot.get_embed_color(guild.channels[0]), + bot=self.bot, + color=await self.bot.get_embed_color(guild.channels[0]), guild=guild, reason=f"Automatic unban from case #{moderation_id}", moderation_type="unbanned", @@ -1689,7 +1556,7 @@ class Aurora(commands.Cog): ) removerole_num = 0 - addrole_query = f"SELECT target_id, moderation_id, role_id FROM moderation_{guild.id} WHERE end_timestamp != 0 AND end_timestamp <= ? AND moderation_type = 'ADDROLE' AND expired = 0" + addrole_query = f"SELECT target_id, moderation_id, role_id FROM moderation_{guild.id} WHERE end_timestamp IS NOT NULL AND end_timestamp <= ? AND moderation_type = 'ADDROLE' AND expired = 0" try: cursor.execute(addrole_query, (time.time(),)) result = cursor.fetchall() @@ -1724,7 +1591,7 @@ class Aurora(commands.Cog): continue addrole_num = 0 - removerole_query = f"SELECT target_id, moderation_id, role_id FROM moderation_{guild.id} WHERE end_timestamp != 0 AND end_timestamp <= ? AND moderation_type = 'REMOVEROLE' AND expired = 0" + removerole_query = f"SELECT target_id, moderation_id, role_id FROM moderation_{guild.id} WHERE end_timestamp IS NOT NULL 0 AND end_timestamp <= ? AND moderation_type = 'REMOVEROLE' AND expired = 0" try: cursor.execute(removerole_query, (time.time(),)) result = cursor.fetchall() @@ -1753,7 +1620,7 @@ class Aurora(commands.Cog): logger.error("Adding the role %s to user %s failed due to: \n%s", role_id, target_id, e) continue - expiry_query = f"UPDATE `moderation_{guild.id}` SET expired = 1 WHERE (end_timestamp != 0 AND end_timestamp <= ? AND expired = 0) OR (expired = 0 AND resolved = 1);" + expiry_query = f"UPDATE `moderation_{guild.id}` SET expired = 1 WHERE (end_timestamp IS NOT NULL AND end_timestamp <= ? AND expired = 0) OR (expired = 0 AND resolved = 1);" cursor.execute(expiry_query, (time.time(),)) per_guild_completion_time = (time.time() - time_per_guild) * 1000 diff --git a/aurora/importers/aurora.py b/aurora/importers/aurora.py index 44cab98..c184c1c 100644 --- a/aurora/importers/aurora.py +++ b/aurora/importers/aurora.py @@ -1,13 +1,15 @@ # pylint: disable=duplicate-code import json from datetime import timedelta +from time import time from typing import Dict from discord import ButtonStyle, Interaction, Message, ui from redbot.core import commands from redbot.core.utils.chat_formatting import box, warning -from ..utilities.database import connect, create_guild_table, mysql_log +from ..models.moderation import Moderation +from ..utilities.database import connect, create_guild_table class ImportAuroraView(ui.View): @@ -62,10 +64,23 @@ class ImportAuroraView(ui.View): case["target_type"] = "USER" if "role_id" not in case or not case["role_id"]: - case["role_id"] = 0 + case["role_id"] = None + else: + case["role_id"] = int(case["role_id"]) + + case["target_id"] = int(case["target_id"]) + case["moderator_id"] = int(case["moderator_id"]) if "changes" not in case or not case["changes"]: - case["changes"] = [] + changes = [] + else: + changes = json.loads(case["changes"]) + if isinstance(changes, str): + changes: list[dict] = json.loads(changes) + + for change in changes: + if change.get("bot"): + del change["bot"] if "metadata" not in case: metadata = {} @@ -73,28 +88,30 @@ class ImportAuroraView(ui.View): metadata: Dict[str, any] = json.loads(case["metadata"]) if not metadata.get("imported_from"): metadata.update({"imported_from": "Aurora"}) + metadata.update({"imported_timestamp": int(time())}) - if case["duration"] != "NULL": + if case["duration"] != "NULL" and case["duration"] is not None: hours, minutes, seconds = map(int, case["duration"].split(":")) duration = timedelta(hours=hours, minutes=minutes, seconds=seconds) else: - duration = "NULL" + duration = None - await mysql_log( - self.ctx.guild.id, - case["moderator_id"], - case["moderation_type"], - case["target_type"], - case["target_id"], - case["role_id"], - duration, - case["reason"], + Moderation.log( + bot=interaction.client, + guild_id=self.ctx.guild.id, + moderator_id=case["moderator_id"], + moderation_type=case["moderation_type"], + target_type=case["target_type"], + target_id=case["target_id"], + role_id=case["role_id"], + duration=duration, + reason=case["reason"], timestamp=case["timestamp"], resolved=case["resolved"], resolved_by=case["resolved_by"], resolved_reason=case["resolve_reason"], expired=case["expired"], - changes=case["changes"], + changes=changes, metadata=metadata, database=database, ) diff --git a/aurora/importers/galacticbot.py b/aurora/importers/galacticbot.py index c6e0b99..a7c2d27 100644 --- a/aurora/importers/galacticbot.py +++ b/aurora/importers/galacticbot.py @@ -1,12 +1,14 @@ # pylint: disable=duplicate-code import json from datetime import timedelta +from time import time from discord import ButtonStyle, Interaction, Message, ui from redbot.core import commands from redbot.core.utils.chat_formatting import box, warning -from ..utilities.database import connect, create_guild_table, mysql_log +from ..models.moderation import Change, Moderation +from ..utilities.database import connect, create_guild_table class ImportGalacticBotView(ui.View): @@ -67,12 +69,12 @@ class ImportGalacticBotView(ui.View): if case["duration"] is not None and float(case["duration"]) != 0: duration = timedelta(seconds=round(float(case["duration"]) / 1000)) else: - duration = "NULL" + duration = None except OverflowError: failed_cases.append(case["case"]) continue - metadata = {"imported_from": "GalacticBot"} + metadata = {"imported_from": "GalacticBot", "imported_timestamp": int(time())} if case["type"] == "SLOWMODE": metadata["seconds"] = case["data"]["seconds"] @@ -98,37 +100,37 @@ class ImportGalacticBotView(ui.View): if resolved_timestamp is None: resolved_timestamp = timestamp changes = [ - { + Change.from_dict(interaction.client, { "type": "ORIGINAL", "reason": case["reason"], "user_id": case["executor"], "timestamp": timestamp, - }, - { + }), + Change.from_dict(interaction.client, { "type": "RESOLVE", "reason": resolved_reason, "user_id": resolved_by, "timestamp": resolved_timestamp, - }, + }), ] else: - resolved = 0 - resolved_by = "NULL" - resolved_reason = "NULL" - changes = [] + resolved = None + resolved_by = None + resolved_reason = None + changes = None if case["reason"] and case["reason"] != "N/A": reason = case["reason"] else: - reason = "NULL" + reason = None - await mysql_log( + Moderation.log( self.ctx.guild.id, case["executor"], case["type"], case["targetType"], case["target"], - 0, + None, duration, reason, timestamp=timestamp, diff --git a/aurora/info.json b/aurora/info.json index 52a577e..d232f7e 100644 --- a/aurora/info.json +++ b/aurora/info.json @@ -9,6 +9,7 @@ "disabled": false, "min_bot_version": "3.5.0", "min_python_version": [3, 10, 0], + "requirements": ["pydantic"], "tags": [ "mod", "moderate", diff --git a/aurora/menus/addrole.py b/aurora/menus/addrole.py index 11f6b32..36c9056 100644 --- a/aurora/menus/addrole.py +++ b/aurora/menus/addrole.py @@ -2,12 +2,12 @@ from discord import ButtonStyle, Interaction, Message, ui from redbot.core import commands from redbot.core.utils.chat_formatting import error -from aurora.utilities.config import config -from aurora.utilities.factory import addrole_embed +from ..utilities.config import config +from ..utilities.factory import addrole_embed class Addrole(ui.View): - def __init__(self, ctx: commands.Context, message: Message, timeout: int = None): + def __init__(self, ctx: commands.Context, message: Message, timeout: int | None = None): super().__init__() self.ctx = ctx self.message = message diff --git a/aurora/menus/guild.py b/aurora/menus/guild.py index f99e552..71186b7 100644 --- a/aurora/menus/guild.py +++ b/aurora/menus/guild.py @@ -1,13 +1,13 @@ from discord import ButtonStyle, Interaction, Message, ui from redbot.core import commands -from aurora.utilities.config import config -from aurora.utilities.factory import guild_embed -from aurora.utilities.utils import create_pagesize_options +from ..utilities.config import config +from ..utilities.factory import guild_embed +from ..utilities.utils import create_pagesize_options class Guild(ui.View): - def __init__(self, ctx: commands.Context, message: Message, timeout: int = None): + def __init__(self, ctx: commands.Context, message: Message, timeout: int | None = None): super().__init__() self.ctx = ctx self.message = message diff --git a/aurora/menus/immune.py b/aurora/menus/immune.py index 2a5c007..dd710b1 100644 --- a/aurora/menus/immune.py +++ b/aurora/menus/immune.py @@ -2,12 +2,12 @@ from discord import ButtonStyle, Interaction, Message, ui from redbot.core import commands from redbot.core.utils.chat_formatting import error -from aurora.utilities.config import config -from aurora.utilities.factory import immune_embed +from ..utilities.config import config +from ..utilities.factory import immune_embed class Immune(ui.View): - def __init__(self, ctx: commands.Context, message: Message, timeout: int = None): + def __init__(self, ctx: commands.Context, message: Message, timeout: int | None = None): super().__init__() self.ctx = ctx self.message = message diff --git a/aurora/menus/overrides.py b/aurora/menus/overrides.py index c5d0e68..4327131 100644 --- a/aurora/menus/overrides.py +++ b/aurora/menus/overrides.py @@ -1,13 +1,13 @@ from discord import ButtonStyle, Interaction, Message, ui from redbot.core import commands -from aurora.utilities.config import config -from aurora.utilities.factory import overrides_embed -from aurora.utilities.utils import create_pagesize_options +from ..utilities.config import config +from ..utilities.factory import overrides_embed +from ..utilities.utils import create_pagesize_options class Overrides(ui.View): - def __init__(self, ctx: commands.Context, message: Message, timeout: int = None): + def __init__(self, ctx: commands.Context, message: Message, timeout: int | None = None): super().__init__() self.ctx = ctx self.message = message diff --git a/aurora/models/__init__.py b/aurora/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aurora/models/base.py b/aurora/models/base.py new file mode 100644 index 0000000..e6005ee --- /dev/null +++ b/aurora/models/base.py @@ -0,0 +1,28 @@ +from typing import Any + +from pydantic import BaseModel, ConfigDict +from redbot.core.bot import Red + + +class AuroraBaseModel(BaseModel): + """Base class for all models in Aurora.""" + model_config = ConfigDict(ignored_types=(Red,), arbitrary_types_allowed=True) + bot: Red + + def dump(self) -> dict: + return self.model_dump(exclude={"bot"}) + + def to_json(self, indent: int | None = None, file: Any | None = None, **kwargs): + from ..utilities.json import dump, dumps # pylint: disable=cyclic-import + return dump(self.dump(), file, indent=indent, **kwargs) if file else dumps(self.model_dump(exclude={"bot"}), indent=indent, **kwargs) + +class AuroraGuildModel(AuroraBaseModel): + """Subclass of AuroraBaseModel that includes a guild_id attribute, and a modified to_json() method to match.""" + guild_id: int + + def dump(self) -> dict: + return self.model_dump(exclude={"bot", "guild_id"}) + + def to_json(self, indent: int | None = None, file: Any | None = None, **kwargs): + from ..utilities.json import dump, dumps # pylint: disable=cyclic-import + return dump(self.dump(), file, indent=indent, **kwargs) if file else dumps(self.model_dump(exclude={"bot", "guild_id"}), indent=indent, **kwargs) diff --git a/aurora/models/change.py b/aurora/models/change.py new file mode 100644 index 0000000..0c7148a --- /dev/null +++ b/aurora/models/change.py @@ -0,0 +1,62 @@ +import json +from datetime import datetime, timedelta +from typing import Literal, Optional + +from redbot.core.bot import Red + +from ..utilities.logger import logger +from .base import AuroraBaseModel +from .partials import PartialUser + + +class Change(AuroraBaseModel): + type: Literal["ORIGINAL", "RESOLVE", "EDIT"] + timestamp: datetime + reason: str + user_id: int + duration: Optional[timedelta] = None + end_timestamp: Optional[datetime] = None + + @property + def unix_timestamp(self) -> int: + return int(self.timestamp.timestamp()) + + def __str__(self): + return f"{self.type} {self.user_id} {self.reason}" + + async def get_user(self) -> "PartialUser": + return await PartialUser.from_id(self.bot, self.user_id) + + @classmethod + def from_dict(cls, bot: Red, data: dict) -> "Change": + logger.trace("Creating Change from dict (%s): %s", type(data), data) + if isinstance(data, str): + data = json.loads(data) + logger.trace("Change data was a string, converted to dict: %s", data) + if "duration" in data and data["duration"] and not isinstance(data["duration"], timedelta): + hours, minutes, seconds = map(int, data["duration"].split(':')) + duration = timedelta(hours=hours, minutes=minutes, seconds=seconds) + elif "duration" in data and isinstance(data["duration"], timedelta): + duration = data["duration"] + else: + duration = None + + if "end_timestamp" in data and data["end_timestamp"] and not isinstance(data["end_timestamp"], datetime): + end_timestamp = datetime.fromtimestamp(data["end_timestamp"]) + elif "end_timestamp" in data and isinstance(data["end_timestamp"], datetime): + end_timestamp = data["end_timestamp"] + else: + end_timestamp = None + + if not isinstance(data["timestamp"], datetime): + timestamp = datetime.fromtimestamp(data["timestamp"]) + else: + timestamp = data["timestamp"] + + data.update({ + "timestamp": timestamp, + "end_timestamp": end_timestamp, + "duration": duration, + "user_id": int(data["user_id"]) + }) + return cls(bot=bot, **data) diff --git a/aurora/models/moderation.py b/aurora/models/moderation.py new file mode 100644 index 0000000..5462ba3 --- /dev/null +++ b/aurora/models/moderation.py @@ -0,0 +1,387 @@ +import json +import sqlite3 +from datetime import datetime, timedelta +from sqlite3 import Cursor +from time import time +from typing import Dict, Iterable, List, Optional, Tuple, Union + +import discord +from discord import NotFound +from redbot.core.bot import Red + +from ..utilities.logger import logger +from .base import AuroraGuildModel +from .change import Change +from .partials import PartialChannel, PartialRole, PartialUser + + +class Moderation(AuroraGuildModel): + moderation_id: int + timestamp: datetime + moderation_type: str + target_type: str + target_id: int + moderator_id: int + role_id: Optional[int] = None + duration: Optional[timedelta] = None + end_timestamp: Optional[datetime] = None + reason: Optional[str] = None + resolved: bool + resolved_by: Optional[int] = None + resolve_reason: Optional[str] = None + expired: bool + changes: List["Change"] + metadata: Dict + + @property + def id(self) -> int: + return self.moderation_id + + @property + def type(self) -> str: + return self.moderation_type + + @property + def unix_timestamp(self) -> int: + return int(self.timestamp.timestamp()) + + async def get_moderator(self) -> "PartialUser": + return await PartialUser.from_id(self.bot, self.moderator_id) + + async def get_target(self) -> Union["PartialUser", "PartialChannel"]: + if self.target_type == "USER": + return await PartialUser.from_id(self.bot, self.target_id) + return await PartialChannel.from_id(self.bot, self.target_id) + + async def get_resolved_by(self) -> Optional["PartialUser"]: + if self.resolved_by: + return await PartialUser.from_id(self.bot, self.resolved_by) + return None + + async def get_role(self) -> Optional["PartialRole"]: + if self.role_id: + return await PartialRole.from_id(self.bot, self.guild_id, self.role_id) + return None + + def __str__(self) -> str: + return f"{self.moderation_type} {self.target_type} {self.target_id} {self.reason}" + + async def resolve(self, resolved_by: int, reason: str) -> None: + if self.resolved: + raise ValueError("Case is already resolved!") + + self.resolved = True + self.resolved_by = resolved_by + self.resolve_reason = reason + + if self.type in ["UNMUTE", "UNBAN"]: + raise TypeError("Cannot resolve an unmute or unban case!") + + if self.type == "MUTE": + try: + guild: discord.Guild = await self.bot.fetch_guild(self.guild_id) + member = await guild.fetch_member(self.target_id) + + await member.timeout( + None, reason=f"Case {self.moderation_id} resolved by {resolved_by}{' for '+ reason if reason else ''}" + ) + except NotFound: + pass + + if self.type in ["BAN", "TEMPBAN"]: + try: + guild: discord.Guild = await self.bot.fetch_guild(self.guild_id) + await guild.unban(await self.get_target(), reason=f"Case {self.moderation_id} resolved by {resolved_by}{' for '+ reason if reason else ''}") + except NotFound: + pass + + if not self.changes: + self.changes.append(Change.from_dict(self.bot, { + "type": "ORIGINAL", + "timestamp": self.timestamp, + "reason": self.reason, + "user_id": self.moderator_id, + "duration": self.duration, + "end_timestamp": self.end_timestamp, + })) + self.changes.append(Change.from_dict(self.bot, { + "type": "RESOLVE", + "timestamp": datetime.now(), + "reason": reason, + "user_id": resolved_by, + })) + + self.update() + + def update(self) -> None: + from ..utilities.database import connect + from ..utilities.json import dumps + query = f"UPDATE moderation_{self.guild_id} SET timestamp = ?, moderation_type = ?, target_type = ?, moderator_id = ?, role_id = ?, duration = ?, end_timestamp = ?, reason = ?, resolved = ?, resolved_by = ?, resolve_reason = ?, expired = ?, changes = ?, metadata = ? WHERE moderation_id = ?;" + + with connect() as database: + database.execute(query, ( + self.timestamp.timestamp(), + self.moderation_type, + self.target_type, + self.moderator_id, + self.role_id, + str(self.duration) if self.duration else None, + self.end_timestamp.timestamp() if self.end_timestamp else None, + self.reason, + self.resolved, + self.resolved_by, + self.resolve_reason, + self.expired, + dumps(self.changes), + dumps(self.metadata), + self.moderation_id, + )) + + logger.debug("Row updated in moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s", + self.moderation_id, + self.guild_id, + self.timestamp.timestamp(), + self.moderation_type, + self.target_type, + self.moderator_id, + self.role_id, + str(self.duration) if self.duration else None, + self.end_timestamp.timestamp() if self.end_timestamp else None, + self.reason, + self.resolved, + self.resolved_by, + self.resolve_reason, + self.expired, + dumps(self.changes), + dumps(self.metadata), + ) + + @classmethod + def from_dict(cls, bot: Red, data: dict) -> "Moderation": + return cls(bot=bot, **data) + + @classmethod + def from_result(cls, bot: Red, result: Iterable, guild_id: int) -> "Moderation": + if result[7] is not None: + hours, minutes, seconds = map(int, result[7].split(':')) + duration = timedelta(hours=hours, minutes=minutes, seconds=seconds) + else: + duration = None + + if result[14] is not None: + changes = json.loads(result[14]) + change_obj_list = [] + if changes: + for change in changes: + change_obj_list.append(Change.from_dict(bot=bot, data=change)) + + if result[15] is not None: + metadata = json.loads(result[15]) + else: + metadata = {} + + case = { + "moderation_id": int(result[0]), + "guild_id": int(guild_id), + "timestamp": datetime.fromtimestamp(result[1]), + "moderation_type": str(result[2]), + "target_type": str(result[3]), + "target_id": int(result[4]), + "moderator_id": int(result[5]), + "role_id": int(result[6]) if result[6] is not None else None, + "duration": duration, + "end_timestamp": datetime.fromtimestamp(result[8]) if result[8] is not None else None, + "reason": result[9], + "resolved": bool(result[10]), + "resolved_by": result[11], + "resolve_reason": result[12], + "expired": bool(result[13]), + "changes": change_obj_list, + "metadata": metadata if metadata else {}, + } + return cls.from_dict(bot=bot, data=case) + + @classmethod + def execute(cls, bot: Red, guild_id: int, query: str, parameters: tuple | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: + from ..utilities.database import connect + if not parameters: + parameters = () + if not cursor: + no_cursor = True + database = connect() + cursor = database.cursor() + else: + no_cursor = False + + cursor.execute(query, parameters) + results = cursor.fetchall() + if no_cursor: + cursor.close() + database.close() + + if results: + cases = [] + for result in results: + case = cls.from_result(bot=bot, result=result, guild_id=guild_id) + if case.moderation_id != 0: + cases.append(case) + return tuple(cases) + return () + + @classmethod + def get_latest(cls, bot: Red, guild_id: int, limit: int | None = None, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: + params = [] + query = f"SELECT * FROM moderation_{guild_id} ORDER BY moderation_id DESC" + if limit: + query += " LIMIT ?" + params.append(limit) + if types: + query += f" WHERE moderation_type IN ({', '.join(['?' for _ in types])})" + params.extend(types) + query += ";" + return cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=tuple(params) if limit else (), cursor=cursor) + + @classmethod + def get_next_case_number(cls, bot: Red, guild_id: int, cursor: Cursor | None = None) -> int: + result = cls.get_latest(bot=bot, guild_id=guild_id, cursor=cursor) + return (result[0].moderation_id + 1) if result else 1 + + @classmethod + def find_by_id(cls, bot: Red, moderation_id: int, guild_id: int, cursor: Cursor | None = None) -> "Moderation": + query = f"SELECT * FROM moderation_{guild_id} WHERE moderation_id = ?;" + case = cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(moderation_id,), cursor=cursor) + if case: + return case[0] + raise ValueError(f"Case {moderation_id} not found in moderation_{guild_id}!") + + @classmethod + def find_by_target(cls, bot: Red, guild_id: int, target: int, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: + query = f"SELECT * FROM moderation_{guild_id} WHERE target_id = ?" + if types: + query += f" AND moderation_type IN ({', '.join(['?' for _ in types])})" + query += " ORDER BY moderation_id DESC;" + + return cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(target, *types) if types else (target,), cursor=cursor) + + @classmethod + def find_by_moderator(cls, bot: Red, guild_id: int, moderator: int, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: + query = f"SELECT * FROM moderation_{guild_id} WHERE moderator_id = ?" + if types: + query += f" AND moderation_type IN ({', '.join(['?' for _ in types])})" + query += " ORDER BY moderation_id DESC;" + + return cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(moderator, *types) if types else (moderator,), cursor=cursor) + + @classmethod + def log( + cls, + bot: Red, + guild_id: int, + moderator_id: int, + moderation_type: str, + target_type: str, + target_id: int, + role_id: int | None = None, + duration: timedelta | None = None, + reason: str | None = None, + database: sqlite3.Connection | None = None, + timestamp: datetime | None = None, + resolved: bool = False, + resolved_by: int | None = None, + resolved_reason: str | None = None, + expired: bool | None = None, + changes: list | None = None, + metadata: dict | None = None, + ) -> "Moderation": + from ..utilities.database import connect + from ..utilities.json import dumps + if not timestamp: + timestamp = datetime.fromtimestamp(time()) + elif not isinstance(timestamp, datetime): + timestamp = datetime.fromtimestamp(timestamp) + + if duration == "NULL": + duration = None + + if duration is not None: + end_timestamp = timestamp + duration + else: + duration = None + end_timestamp = None + + if not expired: + if end_timestamp: + expired = bool(timestamp > end_timestamp) + else: + expired = False + + if reason == "NULL": + reason = None + + if resolved_by == "NULL": + resolved_by = None + + if resolved_reason == "NULL": + resolved_reason = None + + if role_id == 0: + role_id = None + + if not database: + database = connect() + close_db = True + else: + close_db = False + cursor = database.cursor() + + moderation_id = cls.get_next_case_number(bot=bot, guild_id=guild_id, cursor=cursor) + + case = { + "moderation_id": moderation_id, + "timestamp": timestamp.timestamp(), + "moderation_type": moderation_type, + "target_type": target_type, + "target_id": target_id, + "moderator_id": moderator_id, + "role_id": role_id, + "duration": str(duration) if duration else None, + "end_timestamp": end_timestamp.timestamp() if end_timestamp else None, + "reason": reason, + "resolved": resolved, + "resolved_by": resolved_by, + "resolve_reason": resolved_reason, + "expired": expired, + "changes": dumps(changes), + "metadata": dumps(metadata) + } + + sql = f"INSERT INTO `moderation_{guild_id}` (moderation_id, timestamp, moderation_type, target_type, target_id, moderator_id, role_id, duration, end_timestamp, reason, resolved, resolved_by, resolve_reason, expired, changes, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + cursor.execute(sql, tuple(case.values())) + + cursor.close() + database.commit() + if close_db: + database.close() + + logger.debug( + "Row inserted into moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s", + guild_id, + case["moderation_id"], + case["timestamp"], + case["moderation_type"], + case["target_type"], + case["target_id"], + case["moderator_id"], + case["role_id"], + case["duration"], + case["end_timestamp"], + case["reason"], + case["resolved"], + case["resolved_by"], + case["resolve_reason"], + case["expired"], + case["changes"], + case["metadata"], + ) + + return cls.find_by_id(bot=bot, moderation_id=moderation_id, guild_id=guild_id) diff --git a/aurora/models/partials.py b/aurora/models/partials.py new file mode 100644 index 0000000..48f4030 --- /dev/null +++ b/aurora/models/partials.py @@ -0,0 +1,79 @@ +from discord import Forbidden, HTTPException, InvalidData, NotFound +from redbot.core.bot import Red + +from .base import AuroraBaseModel, AuroraGuildModel + + +class PartialUser(AuroraBaseModel): + id: int + username: str + discriminator: int + + @property + def name(self): + return f"{self.username}#{self.discriminator}" if self.discriminator != 0 else self.username + + def __str__(self): + return self.name + + @classmethod + async def from_id(cls, bot: Red, user_id: int) -> "PartialUser": + user = bot.get_user(user_id) + if not user: + try: + user = await bot.fetch_user(user_id) + return cls(bot=bot, id=user.id, username=user.name, discriminator=user.discriminator) + except NotFound: + return cls(bot=bot, id=user_id, username="Deleted User", discriminator=0) + return cls(bot=bot, id=user.id, username=user.name, discriminator=user.discriminator) + + +class PartialChannel(AuroraGuildModel): + id: int + name: str + + @property + def mention(self): + if self.name in ["Deleted Channel", "Forbidden Channel"]: + return self.name + return f"<#{self.id}>" + + def __str__(self): + return self.mention + + @classmethod + async def from_id(cls, bot: Red, channel_id: int) -> "PartialChannel": + channel = bot.get_channel(channel_id) + if not channel: + try: + channel = await bot.fetch_channel(channel_id) + return cls(bot=bot, guild_id=channel.guild.id, id=channel.id, name=channel.name) + except (NotFound, InvalidData, HTTPException, Forbidden) as e: + if e == Forbidden: + return cls(bot=bot, guild_id=0, id=channel_id, name="Forbidden Channel") + return cls(bot=bot, guild_id=0, id=channel_id, name="Deleted Channel") + return cls(bot=bot, guild_id=channel.guild.id, id=channel.id, name=channel.name) + +class PartialRole(AuroraGuildModel): + id: int + name: str + + @property + def mention(self): + if self.name in ["Deleted Role", "Forbidden Role"]: + return self.name + return f"<@&{self.id}>" + + def __str__(self): + return self.mention + + @classmethod + async def from_id(cls, bot: Red, guild_id: int, role_id: int) -> "PartialRole": + try: + guild = await bot.fetch_guild(guild_id, with_counts=False) + except (Forbidden, HTTPException): + return cls(bot=bot, guild_id=guild_id, id=role_id, name="Forbidden Role") + role = guild.get_role(role_id) + if not role: + return cls(bot=bot, guild_id=guild_id, id=role_id, name="Deleted Role") + return cls(bot=bot, guild_id=guild_id, id=role.id, name=role.name) diff --git a/aurora/utilities/database.py b/aurora/utilities/database.py index a2bcb92..588ad35 100644 --- a/aurora/utilities/database.py +++ b/aurora/utilities/database.py @@ -1,15 +1,11 @@ # pylint: disable=cyclic-import import json import sqlite3 -import time -from datetime import datetime, timedelta from discord import Guild from redbot.core import data_manager from .logger import logger -from .utils import (convert_timedelta_to_str, generate_dict, - get_next_case_number) def connect() -> sqlite3.Connection: @@ -42,9 +38,9 @@ async def create_guild_table(guild: Guild): timestamp INTEGER NOT NULL, moderation_type TEXT NOT NULL, target_type TEXT NOT NULL, - target_id TEXT NOT NULL, - moderator_id TEXT NOT NULL, - role_id TEXT, + target_id INTEGER NOT NULL, + moderator_id INTEGER NOT NULL, + role_id INTEGER, duration TEXT, end_timestamp INTEGER, reason TEXT, @@ -52,8 +48,8 @@ async def create_guild_table(guild: Guild): resolved_by TEXT, resolve_reason TEXT, expired INTEGER NOT NULL, - changes TEXT NOT NULL, - metadata TEXT NOT NULL + changes JSON NOT NULL, + metadata JSON NOT NULL ) """ cursor.execute(query) @@ -79,13 +75,13 @@ async def create_guild_table(guild: Guild): "NULL", 0, 0, + None, + None, + None, + None, 0, - "NULL", - 0, - "NULL", - 0, - "NULL", - "NULL", + None, + None, 0, json.dumps([]), json.dumps({}), @@ -102,118 +98,3 @@ async def create_guild_table(guild: Guild): ) database.close() - - -async def mysql_log( - guild_id: str, - author_id: str, - moderation_type: str, - target_type: str, - target_id: int, - role_id: int, - duration: timedelta, - reason: str, - database: sqlite3.Connection = None, - timestamp: int = None, - resolved: bool = False, - resolved_by: str = None, - resolved_reason: str = None, - expired: bool = None, - changes: list = None, - metadata: dict = None, -) -> int: - if not timestamp: - timestamp = int(time.time()) - - if duration != "NULL": - end_timedelta = datetime.fromtimestamp(timestamp) + duration - end_timestamp = int(end_timedelta.timestamp()) - - duration = convert_timedelta_to_str(duration) - else: - end_timestamp = 0 - - if not expired: - if int(time.time()) > end_timestamp: - expired = 1 - else: - expired = 0 - - if resolved_by is None: - resolved_by = "NULL" - - if resolved_reason is None: - resolved_reason = "NULL" - - if not database: - database = connect() - close_db = True - else: - close_db = False - cursor = database.cursor() - - moderation_id = await get_next_case_number(guild_id=guild_id, cursor=cursor) - - sql = f"INSERT INTO `moderation_{guild_id}` (moderation_id, timestamp, moderation_type, target_type, target_id, moderator_id, role_id, duration, end_timestamp, reason, resolved, resolved_by, resolve_reason, expired, changes, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - val = ( - moderation_id, - timestamp, - moderation_type, - target_type, - target_id, - author_id, - role_id, - duration, - end_timestamp, - reason, - int(resolved), - resolved_by, - resolved_reason, - expired, - json.dumps(changes if changes else []), - json.dumps(metadata if metadata else {}), - ) - cursor.execute(sql, val) - - cursor.close() - database.commit() - if close_db: - database.close() - - logger.debug( - "Row inserted into moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s", - guild_id, - moderation_id, - timestamp, - moderation_type, - target_type, - target_id, - author_id, - role_id, - duration, - end_timestamp, - reason, - int(resolved), - resolved_by, - resolved_reason, - expired, - changes, - metadata, - ) - - return moderation_id - - -async def fetch_case(moderation_id: int, guild_id: str) -> dict: - """This method fetches a case from the database and returns the case's dictionary.""" - database = connect() - cursor = database.cursor() - - query = f"SELECT * FROM moderation_{guild_id} WHERE moderation_id = ?;" - cursor.execute(query, (moderation_id,)) - result = cursor.fetchone() - - cursor.close() - database.close() - - return generate_dict(result) diff --git a/aurora/utilities/factory.py b/aurora/utilities/factory.py index d7f4eb7..c30ef1b 100644 --- a/aurora/utilities/factory.py +++ b/aurora/utilities/factory.py @@ -4,25 +4,30 @@ from typing import Union from discord import Color, Embed, Guild, Interaction, InteractionMessage, Member, Role, User from redbot.core import commands +from redbot.core.bot import Red from redbot.core.utils.chat_formatting import bold, box, error, humanize_timedelta, warning -from aurora.utilities.config import config -from aurora.utilities.utils import fetch_channel_dict, fetch_user_dict, get_bool_emoji, get_next_case_number, get_pagesize_str +from ..models.moderation import Moderation +from ..models.partials import PartialUser +from .config import config +from .utils import get_bool_emoji, get_pagesize_str async def message_factory( + bot: Red, color: Color, guild: Guild, reason: str, moderation_type: str, - moderator: Union[Member, User] = None, - duration: timedelta = None, - response: InteractionMessage = None, - role: Role = None, + moderator: Union[Member, User] | None = None, + duration: timedelta | None = None, + response: InteractionMessage | None = None, + role: Role | None = None, ) -> Embed: """This function creates a message from set parameters, meant for contacting the moderated user. Args: + bot (Red): The bot instance. color (Color): The color of the embed. guild (Guild): The guild the moderation occurred in. reason (str): The reason for the moderation. @@ -86,7 +91,7 @@ async def message_factory( embed.set_author(name=guild.name) embed.set_footer( - text=f"Case #{await get_next_case_number(guild.id):,}", + text=f"Case #{Moderation.get_next_case_number(bot=bot, guild_id=guild.id):,}", icon_url="attachment://arrow.png", ) @@ -94,256 +99,173 @@ async def message_factory( async def log_factory( - interaction: Interaction, case_dict: dict, resolved: bool = False + interaction: Interaction, moderation: Moderation, resolved: bool = False ) -> Embed: """This function creates a log embed from set parameters, meant for moderation logging. Args: - interaction (Interaction): The interaction object. - case_dict (dict): The case dictionary. + interaction (discord.Interaction): The interaction object. + moderation (aurora.models.Moderation): The moderation object. resolved (bool, optional): Whether the case is resolved or not. Defaults to False. """ + target = await moderation.get_target() + moderator = await moderation.get_moderator() if resolved: - if case_dict["target_type"] == "USER": - target_user = await fetch_user_dict(interaction.client, case_dict["target_id"]) - target_name = ( - f"`{target_user['name']}`" - if target_user["discriminator"] == "0" - else f"`{target_user['name']}#{target_user['discriminator']}`" - ) - elif case_dict["target_type"] == "CHANNEL": - target_user = await fetch_channel_dict(interaction.guild, case_dict["target_id"]) - if target_user["mention"]: - target_name = f"{target_user['mention']}" - else: - target_name = f"`{target_user['name']}`" - - moderator_user = await fetch_user_dict(interaction.client, case_dict["moderator_id"]) - moderator_name = ( - f"`{moderator_user['name']}`" - if moderator_user["discriminator"] == "0" - else f"`{moderator_user['name']}#{moderator_user['discriminator']}`" - ) - embed = Embed( - title=f"📕 Case #{case_dict['moderation_id']:,} Resolved", + title=f"📕 Case #{moderation.id:,} Resolved", color=await interaction.client.get_embed_color(interaction.channel), ) - embed.description = f"**Type:** {str.title(case_dict['moderation_type'])}\n**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})\n**Timestamp:** | " + resolved_by = await moderation.get_resolved_by() + embed.description = f"**Type:** {str.title(moderation.moderation_type)}\n**Target:** {target.name} ({target.id})\n**Moderator:** {moderator.name} ({moderator.id})\n**Timestamp:** | " - if case_dict["duration"] != "NULL": - td = timedelta( - **{ - unit: int(val) - for unit, val in zip( - ["hours", "minutes", "seconds"], - case_dict["duration"].split(":"), - ) - } - ) + if moderation.duration is not None: duration_embed = ( - f"{humanize_timedelta(timedelta=td)} | " - if case_dict["expired"] == "0" - else str(humanize_timedelta(timedelta=td)) + f"{humanize_timedelta(timedelta=moderation.duration)} | " + if not moderation.expired + else str(humanize_timedelta(timedelta=moderation.duration)) ) embed.description = ( embed.description - + f"\n**Duration:** {duration_embed}\n**Expired:** {bool(case_dict['expired'])}" + + f"\n**Duration:** {duration_embed}\n**Expired:** {moderation.expired}" ) - embed.add_field(name="Reason", value=box(case_dict["reason"]), inline=False) + if moderation.metadata.items(): + for key, value in moderation.metadata.items(): + embed.description += f"\n**{key.title()}:** {value}" + + embed.add_field(name="Reason", value=box(moderation.reason), inline=False) - resolved_user = await fetch_user_dict(interaction.client, case_dict["resolved_by"]) - resolved_name = ( - resolved_user["name"] - if resolved_user["discriminator"] == "0" - else f"{resolved_user['name']}#{resolved_user['discriminator']}" - ) embed.add_field( name="Resolve Reason", - value=f"Resolved by `{resolved_name}` ({resolved_user['id']}) for:\n" - + box(case_dict["resolve_reason"]), + value=f"Resolved by `{resolved_by.name}` ({resolved_by.id}) for:\n" + + box(moderation.resolve_reason), inline=False, ) else: - if case_dict["target_type"] == "USER": - target_user = await fetch_user_dict(interaction.client, case_dict["target_id"]) - target_name = ( - f"`{target_user['name']}`" - if target_user["discriminator"] == "0" - else f"`{target_user['name']}#{target_user['discriminator']}`" - ) - elif case_dict["target_type"] == "CHANNEL": - target_user = await fetch_channel_dict(interaction.guild, case_dict["target_id"]) - if target_user["mention"]: - target_name = target_user["mention"] - else: - target_name = f"`{target_user['name']}`" - - moderator_user = await fetch_user_dict(interaction.client, case_dict["moderator_id"]) - moderator_name = ( - f"`{moderator_user['name']}`" - if moderator_user["discriminator"] == "0" - else f"`{moderator_user['name']}#{moderator_user['discriminator']}`" - ) - embed = Embed( - title=f"📕 Case #{case_dict['moderation_id']:,}", + title=f"📕 Case #{moderation.id:,}", color=await interaction.client.get_embed_color(interaction.channel), ) - embed.description = f"**Type:** {str.title(case_dict['moderation_type'])}\n**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})\n**Timestamp:** | " + embed.description = f"**Type:** {str.title(moderation.type)}\n**Target:** {target.name} ({target.id})\n**Moderator:** {moderator.name} ({moderator.id})\n**Timestamp:** | " - if case_dict["duration"] != "NULL": - td = timedelta( - **{ - unit: int(val) - for unit, val in zip( - ["hours", "minutes", "seconds"], - case_dict["duration"].split(":"), - ) - } - ) + if moderation.duration: embed.description = ( embed.description - + f"\n**Duration:** {humanize_timedelta(timedelta=td)} | " + + f"\n**Duration:** {humanize_timedelta(timedelta=moderation.duration)} | " ) - embed.add_field(name="Reason", value=box(case_dict["reason"]), inline=False) + if moderation.metadata.items(): + for key, value in moderation.metadata.items(): + embed.description += f"\n**{key.title()}:** {value}" + + embed.add_field(name="Reason", value=box(moderation.reason), inline=False) return embed -async def case_factory(interaction: Interaction, case_dict: dict) -> Embed: +async def case_factory(interaction: Interaction, moderation: Moderation) -> Embed: """This function creates a case embed from set parameters. Args: - interaction (Interaction): The interaction object. - case_dict (dict): The case dictionary. + interaction (discord.Interaction): The interaction object. + moderation (aurora.models.Moderation): The moderation object. """ - if case_dict["target_type"] == "USER": - target_user = await fetch_user_dict(interaction.client, case_dict["target_id"]) - target_name = ( - f"`{target_user['name']}`" - if target_user["discriminator"] == "0" - else f"`{target_user['name']}#{target_user['discriminator']}`" - ) - elif case_dict["target_type"] == "CHANNEL": - target_user = await fetch_channel_dict(interaction.guild, case_dict["target_id"]) - if target_user["mention"]: - target_name = f"{target_user['mention']}" - else: - target_name = f"`{target_user['name']}`" - - moderator_user = await fetch_user_dict(interaction.client, case_dict["moderator_id"]) - moderator_name = ( - f"`{moderator_user['name']}`" - if moderator_user["discriminator"] == "0" - else f"`{moderator_user['name']}#{moderator_user['discriminator']}`" - ) + target = await moderation.get_target() + moderator = await moderation.get_moderator() embed = Embed( - title=f"📕 Case #{case_dict['moderation_id']:,}", + title=f"📕 Case #{moderation.id:,}", color=await interaction.client.get_embed_color(interaction.channel), ) - embed.description = f"**Type:** {str.title(case_dict['moderation_type'])}\n**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})\n**Resolved:** {bool(case_dict['resolved'])}\n**Timestamp:** | " + embed.description = f"**Type:** {str.title(moderation.type)}\n**Target:** `{target.name}` ({target.id})\n**Moderator:** `{moderator.name}` ({moderator.id})\n**Resolved:** {moderation.resolved}\n**Timestamp:** | " - if case_dict["duration"] != "NULL": - td = timedelta( - **{ - unit: int(val) - for unit, val in zip( - ["hours", "minutes", "seconds"], case_dict["duration"].split(":") - ) - } - ) + if moderation.duration: duration_embed = ( - f"{humanize_timedelta(timedelta=td)} | " - if bool(case_dict["expired"]) is False - else str(humanize_timedelta(timedelta=td)) + f"{humanize_timedelta(timedelta=moderation.duration)} | " + if moderation.expired is False + else str(humanize_timedelta(timedelta=moderation.duration)) ) - embed.description += f"\n**Duration:** {duration_embed}\n**Expired:** {bool(case_dict['expired'])}" + embed.description += f"\n**Duration:** {duration_embed}\n**Expired:** {moderation.expired}" embed.description += ( - f"\n**Changes:** {len(case_dict['changes']) - 1}" - if case_dict["changes"] + f"\n**Changes:** {len(moderation.changes) - 1}" + if moderation.changes else "\n**Changes:** 0" ) - if case_dict["role_id"]: - embed.description += f"\n**Role:** <@&{case_dict['role_id']}>" + if moderation.role_id: + role = await moderation.get_role() + embed.description += f"\n**Role:** {role.name}" - if case_dict["metadata"]: - if case_dict["metadata"]["imported_from"]: + if moderation.metadata: + if moderation.metadata.get("imported_from"): embed.description += ( - f"\n**Imported From:** {case_dict['metadata']['imported_from']}" + f"\n**Imported From:** {moderation.metadata['imported_from']}" ) + moderation.metadata.pop("imported_from") + if moderation.metadata.get("imported_timestamp"): + embed.description += ( + f"\n**Imported Timestamp:** | " + ) + moderation.metadata.pop("imported_timestamp") + if moderation.metadata.items(): + for key, value in moderation.metadata.items(): + embed.description += f"\n**{key.title()}:** {value}" - embed.add_field(name="Reason", value=box(case_dict["reason"]), inline=False) + embed.add_field(name="Reason", value=box(moderation.reason), inline=False) - if case_dict["resolved"] == 1: - resolved_user = await fetch_user_dict(interaction.client, case_dict["resolved_by"]) - resolved_name = ( - f"`{resolved_user['name']}`" - if resolved_user["discriminator"] == "0" - else f"`{resolved_user['name']}#{resolved_user['discriminator']}`" - ) + if moderation.resolved: + resolved_user = await moderation.get_resolved_by() embed.add_field( name="Resolve Reason", - value=f"Resolved by {resolved_name} ({resolved_user['id']}) for:\n{box(case_dict['resolve_reason'])}", + value=f"Resolved by `{resolved_user.name}` ({resolved_user.id}) for:\n{box(moderation.resolve_reason)}", inline=False, ) return embed -async def changes_factory(interaction: Interaction, case_dict: dict) -> Embed: +async def changes_factory(interaction: Interaction, moderation: Moderation) -> Embed: """This function creates a changes embed from set parameters. Args: - interaction (Interaction): The interaction object. - case_dict (dict): The case dictionary. + interaction (discord.Interaction): The interaction object. + moderation (aurora.models.Moderation): The moderation object. """ embed = Embed( - title=f"📕 Case #{case_dict['moderation_id']:,} Changes", + title=f"📕 Case #{moderation.id:,} Changes", color=await interaction.client.get_embed_color(interaction.channel), ) memory_dict = {} - if case_dict["changes"]: - for change in case_dict["changes"]: - if change["user_id"] not in memory_dict: - memory_dict[str(change["user_id"])] = await fetch_user_dict( - interaction.client, change["user_id"] - ) + if moderation.changes: + for change in moderation.changes: + if change.user_id not in memory_dict: + memory_dict[str(change.user_id)] = await change.get_user() - user = memory_dict[str(change["user_id"])] - name = ( - user["name"] - if user["discriminator"] == "0" - else f"{user['name']}#{user['discriminator']}" - ) + user: PartialUser = memory_dict[str(change.user_id)] - timestamp = f" | " + timestamp = f" | " - if change["type"] == "ORIGINAL": + if change.type == "ORIGINAL": embed.add_field( name="Original", - value=f"**User:** `{name}` ({user['id']})\n**Reason:** {change['reason']}\n**Timestamp:** {timestamp}", + value=f"**User:** `{user.name}` ({user.id})\n**Reason:** {change.reason}\n**Timestamp:** {timestamp}", inline=False, ) - elif change["type"] == "EDIT": + elif change.type == "EDIT": embed.add_field( name="Edit", - value=f"**User:** `{name}` ({user['id']})\n**Reason:** {change['reason']}\n**Timestamp:** {timestamp}", + value=f"**User:** `{user.name}` ({user.id})\n**Reason:** {change.reason}\n**Timestamp:** {timestamp}", inline=False, ) - elif change["type"] == "RESOLVE": + elif change.type == "RESOLVE": embed.add_field( name="Resolve", - value=f"**User:** `{name}` ({user['id']})\n**Reason:** {change['reason']}\n**Timestamp:** {timestamp}", + value=f"**User:** `{user.name}` ({user.id})\n**Reason:** {change.reason}\n**Timestamp:** {timestamp}", inline=False, ) @@ -353,40 +275,25 @@ async def changes_factory(interaction: Interaction, case_dict: dict) -> Embed: return embed -async def evidenceformat_factory(interaction: Interaction, case_dict: dict) -> str: +async def evidenceformat_factory(moderation: Moderation) -> str: """This function creates a codeblock in evidence format from set parameters. Args: - interaction (Interaction): The interaction object. - case_dict (dict): The case dictionary. + interaction (discord.Interaction): The interaction object. + moderation (aurora.models.Moderation): The moderation object. """ - if case_dict["target_type"] == "USER": - target_user = await fetch_user_dict(interaction.client, case_dict["target_id"]) - target_name = ( - target_user["name"] - if target_user["discriminator"] == "0" - else f"{target_user['name']}#{target_user['discriminator']}" - ) + target = await moderation.get_target() + moderator = await moderation.get_moderator() - elif case_dict["target_type"] == "CHANNEL": - target_user = await fetch_channel_dict(interaction.guild, case_dict["target_id"]) - target_name = target_user["name"] + content = f"Case: {moderation.id:,} ({str.title(moderation.type)})\nTarget: {target.name} ({target.id})\nModerator: {moderator.name} ({moderator.id})" - moderator_user = await fetch_user_dict(interaction.client, case_dict["moderator_id"]) - moderator_name = ( - moderator_user["name"] - if moderator_user["discriminator"] == "0" - else f"{moderator_user['name']}#{moderator_user['discriminator']}" - ) + if moderation.duration is not None: + content += f"\nDuration: {humanize_timedelta(timedelta=moderation.duration)}" - content = f"Case: {case_dict['moderation_id']:,} ({str.title(case_dict['moderation_type'])})\nTarget: {target_name} ({target_user['id']})\nModerator: {moderator_name} ({moderator_user['id']})" + content += f"\nReason: {moderation.reason}" - if case_dict["duration"] != "NULL": - hours, minutes, seconds = map(int, case_dict["duration"].split(":")) - td = timedelta(hours=hours, minutes=minutes, seconds=seconds) - content += f"\nDuration: {humanize_timedelta(timedelta=td)}" - - content += f"\nReason: {case_dict['reason']}" + for key, value in moderation.metadata.items(): + content += f"\n{key.title()}: {value}" return box(content, "prolog") diff --git a/aurora/utilities/json.py b/aurora/utilities/json.py new file mode 100644 index 0000000..80f1259 --- /dev/null +++ b/aurora/utilities/json.py @@ -0,0 +1,128 @@ +import json +from datetime import datetime, timedelta + +from redbot.core.bot import Red + +from ..models.base import AuroraBaseModel + + +class JSONEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, datetime): + return int(o.timestamp()) + if isinstance(o, timedelta): + return str(o) + if isinstance(o, AuroraBaseModel): + return o.dump() + if isinstance(o, Red): + return None + return super().default(o) + + +# This is a wrapper around the json module's dumps function that uses our custom JSONEncoder class +def dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, indent=None, separators=None, + default=None, sort_keys=False, **kw) -> str: + """Serialize ``obj`` to a JSON formatted ``str``. + + If ``skipkeys`` is true then ``dict`` keys that are not basic types + (``str``, ``int``, ``float``, ``bool``, ``None``) will be skipped + instead of raising a ``TypeError``. + + If ``ensure_ascii`` is false, then the return value can contain non-ASCII + characters if they appear in strings contained in ``obj``. Otherwise, all + such characters are escaped in JSON strings. + + If ``check_circular`` is false, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``RecursionError`` (or worse). + + If ``allow_nan`` is false, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in + strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If ``indent`` is a non-negative integer, then JSON array elements and + object members will be pretty-printed with that indent level. An indent + level of 0 will only insert newlines. ``None`` is the most compact + representation. + + If specified, ``separators`` should be an ``(item_separator, key_separator)`` + tuple. The default is ``(', ', ': ')`` if *indent* is ``None`` and + ``(',', ': ')`` otherwise. To get the most compact JSON representation, + you should specify ``(',', ':')`` to eliminate whitespace. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + If *sort_keys* is true (default: ``False``), then the output of + dictionaries will be sorted by key. + """ + return json.dumps( + obj, + cls=JSONEncoder, + skipkeys=skipkeys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + indent=indent, + separators=separators, + default=default, + sort_keys=sort_keys, + **kw + ) + +# This is a wrapper around the json module's dump function that uses our custom JSONEncoder class +def dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, indent=None, separators=None, + default=None, sort_keys=False, **kw) -> str: + """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a + ``.write()``-supporting file-like object). + + If ``skipkeys`` is true then ``dict`` keys that are not basic types + (``str``, ``int``, ``float``, ``bool``, ``None``) will be skipped + instead of raising a ``TypeError``. + + If ``ensure_ascii`` is false, then the strings written to ``fp`` can + contain non-ASCII characters if they appear in strings contained in + ``obj``. Otherwise, all such characters are escaped in JSON strings. + + If ``check_circular`` is false, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``RecursionError`` (or worse). + + If ``allow_nan`` is false, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) + in strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If ``indent`` is a non-negative integer, then JSON array elements and + object members will be pretty-printed with that indent level. An indent + level of 0 will only insert newlines. ``None`` is the most compact + representation. + + If specified, ``separators`` should be an ``(item_separator, key_separator)`` + tuple. The default is ``(', ', ': ')`` if *indent* is ``None`` and + ``(',', ': ')`` otherwise. To get the most compact JSON representation, + you should specify ``(',', ':')`` to eliminate whitespace. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + If *sort_keys* is true (default: ``False``), then the output of + dictionaries will be sorted by key. + """ + return json.dump( + obj, + fp, + cls=JSONEncoder, + skipkeys=skipkeys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + indent=indent, + separators=separators, + default=default, + sort_keys=sort_keys, + **kw + ) diff --git a/aurora/utilities/utils.py b/aurora/utilities/utils.py index 1ff21fa..f2adae0 100644 --- a/aurora/utilities/utils.py +++ b/aurora/utilities/utils.py @@ -1,23 +1,21 @@ # pylint: disable=cyclic-import -import json -from datetime import datetime -from datetime import timedelta as td -from typing import Optional, Union +from datetime import datetime, timedelta +from typing import Optional, Tuple, Union from dateutil.relativedelta import relativedelta as rd -from discord import File, Guild, Interaction, Member, SelectOption, User -from discord.errors import Forbidden, NotFound +from discord import File, Guild, Interaction, Member, SelectOption, TextChannel, User +from discord.errors import Forbidden from redbot.core import commands, data_manager from redbot.core.utils.chat_formatting import error -from .config import config +from ..utilities.config import config def check_permissions( user: User, - permissions: list, - ctx: Union[commands.Context, Interaction] = None, - guild: Guild = None, + permissions: Tuple[str], + ctx: Union[commands.Context, Interaction] | None = None, + guild: Guild | None = None, ) -> Union[bool, str]: """Checks if a user has a specific permission (or a list of permissions) in a channel.""" if ctx: @@ -42,9 +40,10 @@ def check_permissions( async def check_moddable( - target: Union[User, Member], interaction: Interaction, permissions: list + target: Union[User, Member, TextChannel], interaction: Interaction, permissions: Tuple[str] ) -> bool: """Checks if a moderator can moderate a target.""" + is_channel = isinstance(target, TextChannel) if check_permissions(interaction.client.user, permissions, guild=interaction.guild): await interaction.response.send_message( error( @@ -70,7 +69,7 @@ async def check_moddable( ) return False - if target.bot: + if not is_channel and target.bot: await interaction.response.send_message( content="You cannot moderate bots!", ephemeral=True ) @@ -111,122 +110,31 @@ async def check_moddable( return True -async def get_next_case_number(guild_id: str, cursor=None) -> int: - """This function returns the next case number from the MySQL table for a specific guild.""" - from .database import connect - - if not cursor: - database = connect() - cursor = database.cursor() - cursor.execute( - f"SELECT moderation_id FROM `moderation_{guild_id}` ORDER BY moderation_id DESC LIMIT 1" - ) - result = cursor.fetchone() - return (result[0] + 1) if result else 1 - - -def generate_dict(result) -> dict: - case = { - "moderation_id": result[0], - "timestamp": result[1], - "moderation_type": result[2], - "target_type": result[3], - "target_id": result[4], - "moderator_id": result[5], - "role_id": result[6], - "duration": result[7], - "end_timestamp": result[8], - "reason": result[9], - "resolved": result[10], - "resolved_by": result[11], - "resolve_reason": result[12], - "expired": result[13], - "changes": json.loads(result[14]), - "metadata": json.loads(result[15]), - } - return case - - -async def fetch_user_dict(client: commands.Bot, user_id: str) -> dict: - """This function returns a dictionary containing either user information or a standard deleted user template.""" - if user_id == "?": - user_dict = {"id": "?", "name": "Unknown User", "discriminator": "0"} - - else: - try: - user = client.get_user(int(user_id)) - if user is None: - user = await client.fetch_user(int(user_id)) - - user_dict = { - "id": user.id, - "name": user.name, - "discriminator": user.discriminator, - } - - except NotFound: - user_dict = { - "id": user_id, - "name": "Deleted User", - "discriminator": "0", - } - - - return user_dict - - -async def fetch_channel_dict(guild: Guild, channel_id: int) -> dict: - """This function returns a dictionary containing either channel information or a standard deleted channel template.""" - try: - channel = guild.get_channel(int(channel_id)) - if not channel: - channel = await guild.fetch_channel(channel_id) - - channel_dict = { - "id": channel.id, - "name": channel.name, - "mention": channel.mention, - } - - except NotFound: - channel_dict = {"id": channel_id, "name": "Deleted Channel", "mention": None} - - return channel_dict - - -async def fetch_role_dict(guild: Guild, role_id: int) -> dict: - """This function returns a dictionary containing either role information or a standard deleted role template.""" - role = guild.get_role(int(role_id)) - if not role: - role_dict = {"id": role_id, "name": "Deleted Role"} - - role_dict = {"id": role.id, "name": role.name} - - return role_dict - - async def log(interaction: Interaction, moderation_id: int, resolved: bool = False) -> None: """This function sends a message to the guild's configured logging channel when an infraction takes place.""" - from .database import fetch_case + from ..models.moderation import Moderation from .factory import log_factory logging_channel_id = await config.guild(interaction.guild).log_channel() if logging_channel_id != " ": logging_channel = interaction.guild.get_channel(logging_channel_id) - case = await fetch_case(moderation_id, interaction.guild.id) - if case: + try: + moderation = Moderation.find_by_id(interaction.client, moderation_id, interaction.guild_id) embed = await log_factory( - interaction=interaction, case_dict=case, resolved=resolved + interaction=interaction, moderation=moderation, resolved=resolved ) try: await logging_channel.send(embed=embed) except Forbidden: return + except ValueError: + return -async def send_evidenceformat(interaction: Interaction, case_dict: dict) -> None: +async def send_evidenceformat(interaction: Interaction, moderation_id: int) -> None: """This function sends an ephemeral message to the moderator who took the moderation action, with a pre-made codeblock for use in the mod-evidence channel.""" + from ..models.moderation import Moderation from .factory import evidenceformat_factory send_evidence_bool = ( @@ -237,26 +145,20 @@ async def send_evidenceformat(interaction: Interaction, case_dict: dict) -> None if send_evidence_bool is False: return - content = await evidenceformat_factory(interaction=interaction, case_dict=case_dict) + moderation = Moderation.find_by_id(interaction.client, moderation_id, interaction.guild.id) + content = await evidenceformat_factory(moderation=moderation) await interaction.followup.send(content=content, ephemeral=True) -def convert_timedelta_to_str(timedelta: td) -> str: - """This function converts a timedelta object to a string.""" - total_seconds = int(timedelta.total_seconds()) - hours = total_seconds // 3600 - minutes = (total_seconds % 3600) // 60 - seconds = total_seconds % 60 - return f"{hours}:{minutes}:{seconds}" - - def get_bool_emoji(value: Optional[bool]) -> str: """Returns a unicode emoji based on a boolean value.""" - if value is True: - return "\N{WHITE HEAVY CHECK MARK}" - if value is False: - return "\N{NO ENTRY SIGN}" - return "\N{BLACK QUESTION MARK ORNAMENT}\N{VARIATION SELECTOR-16}" + match value: + case True: + return "\N{WHITE HEAVY CHECK MARK}" + case False: + return "\N{NO ENTRY SIGN}" + case _: + return "\N{BLACK QUESTION MARK ORNAMENT}\N{VARIATION SELECTOR-16}" def get_pagesize_str(value: Union[int, None]) -> str: @@ -286,7 +188,7 @@ def create_pagesize_options() -> list[SelectOption]: ) return options -def timedelta_from_relativedelta(relativedelta: rd) -> td: +def timedelta_from_relativedelta(relativedelta: rd) -> timedelta: """Converts a relativedelta object to a timedelta object.""" now = datetime.now() then = now - relativedelta diff --git a/poetry.lock b/poetry.lock index 2767d72..6f6d265 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiohttp" @@ -123,6 +123,17 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + [[package]] name = "apsw" version = "3.45.2.0" @@ -184,6 +195,20 @@ files = [ {file = "apsw-3.45.2.0.tar.gz", hash = "sha256:4fe81f5e390969d08d3048f357a68b347316b8f09455ff4657d94c56acfa255c"}, ] +[[package]] +name = "argcomplete" +version = "3.3.0" +description = "Bash tab completion for argparse" +optional = false +python-versions = ">=3.8" +files = [ + {file = "argcomplete-3.3.0-py3-none-any.whl", hash = "sha256:c168c3723482c031df3c207d4ba8fa702717ccb9fc0bfe4117166c1f537b4a54"}, + {file = "argcomplete-3.3.0.tar.gz", hash = "sha256:fd03ff4a5b9e6580569d34b273f741e85cd9e072f3feeeee3eba4891c70eda62"}, +] + +[package.extras] +test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] + [[package]] name = "astroid" version = "3.1.0" @@ -322,13 +347,13 @@ files = [ [[package]] name = "cairocffi" -version = "1.6.1" +version = "1.7.0" description = "cffi-based cairo bindings for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "cairocffi-1.6.1-py3-none-any.whl", hash = "sha256:aa78ee52b9069d7475eeac457389b6275aa92111895d78fbaa2202a52dac112e"}, - {file = "cairocffi-1.6.1.tar.gz", hash = "sha256:78e6bbe47357640c453d0be929fa49cd05cce2e1286f3d2a1ca9cbda7efdb8b7"}, + {file = "cairocffi-1.7.0-py3-none-any.whl", hash = "sha256:1f29a8d41dbda4090c0aa33bcdea64f3b493e95f74a43ea107c4a8a7b7f632ef"}, + {file = "cairocffi-1.7.0.tar.gz", hash = "sha256:7761863603894305f3160eca68452f373433ca8745ab7dd445bd2c6ce50dcab7"}, ] [package.dependencies] @@ -336,7 +361,7 @@ cffi = ">=1.1.0" [package.extras] doc = ["sphinx", "sphinx_rtd_theme"] -test = ["flake8", "isort", "numpy", "pikepdf", "pytest"] +test = ["numpy", "pikepdf", "pytest", "ruff"] xcb = ["xcffib (>=1.4.0)"] [[package]] @@ -766,30 +791,31 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.42" +version = "3.1.43" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ - {file = "GitPython-3.1.42-py3-none-any.whl", hash = "sha256:1bf9cd7c9e7255f77778ea54359e54ac22a72a5b51288c457c881057b7bb9ecd"}, - {file = "GitPython-3.1.42.tar.gz", hash = "sha256:2d99869e0fef71a73cbd242528105af1d6c1b108c60dfabd994bf292f76c3ceb"}, + {file = "GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff"}, + {file = "GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] -test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar"] +doc = ["sphinx (==4.3.2)", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "sphinxcontrib-applehelp (>=1.0.2,<=1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (>=2.0.0,<=2.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)"] +test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] [[package]] name = "griffe" -version = "0.42.1" +version = "0.44.0" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.8" files = [ - {file = "griffe-0.42.1-py3-none-any.whl", hash = "sha256:7e805e35617601355edcac0d3511cedc1ed0cb1f7645e2d336ae4b05bbae7b3b"}, - {file = "griffe-0.42.1.tar.gz", hash = "sha256:57046131384043ed078692b85d86b76568a686266cc036b9b56b704466f803ce"}, + {file = "griffe-0.44.0-py3-none-any.whl", hash = "sha256:8a4471c469ba980b87c843f1168850ce39d0c1d0c7be140dca2480f76c8e5446"}, + {file = "griffe-0.44.0.tar.gz", hash = "sha256:34aee1571042f9bf00529bc715de4516fb6f482b164e90d030300601009e0223"}, ] [package.dependencies] @@ -1057,13 +1083,13 @@ pytz = "*" [[package]] name = "mkdocs-material" -version = "9.5.15" +version = "9.5.18" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.15-py3-none-any.whl", hash = "sha256:e5c96dec3d19491de49ca643fc1dbb92b278e43cdb816c775bc47db77d9b62fb"}, - {file = "mkdocs_material-9.5.15.tar.gz", hash = "sha256:39f03cca45e82bf54eb7456b5a18bd252eabfdd67f237a229471484a0a4d4635"}, + {file = "mkdocs_material-9.5.18-py3-none-any.whl", hash = "sha256:1e0e27fc9fe239f9064318acf548771a4629d5fd5dfd45444fd80a953fe21eb4"}, + {file = "mkdocs_material-9.5.18.tar.gz", hash = "sha256:a43f470947053fa2405c33995f282d24992c752a50114f23f30da9d8d0c57e62"}, ] [package.dependencies] @@ -1303,61 +1329,62 @@ files = [ [[package]] name = "orjson" -version = "3.9.15" +version = "3.10.0" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.9.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d61f7ce4727a9fa7680cd6f3986b0e2c732639f46a5e0156e550e35258aa313a"}, - {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4feeb41882e8aa17634b589533baafdceb387e01e117b1ec65534ec724023d04"}, - {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fbbeb3c9b2edb5fd044b2a070f127a0ac456ffd079cb82746fc84af01ef021a4"}, - {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b66bcc5670e8a6b78f0313bcb74774c8291f6f8aeef10fe70e910b8040f3ab75"}, - {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2973474811db7b35c30248d1129c64fd2bdf40d57d84beed2a9a379a6f57d0ab"}, - {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fe41b6f72f52d3da4db524c8653e46243c8c92df826ab5ffaece2dba9cccd58"}, - {file = "orjson-3.9.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4228aace81781cc9d05a3ec3a6d2673a1ad0d8725b4e915f1089803e9efd2b99"}, - {file = "orjson-3.9.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f7b65bfaf69493c73423ce9db66cfe9138b2f9ef62897486417a8fcb0a92bfe"}, - {file = "orjson-3.9.15-cp310-none-win32.whl", hash = "sha256:2d99e3c4c13a7b0fb3792cc04c2829c9db07838fb6973e578b85c1745e7d0ce7"}, - {file = "orjson-3.9.15-cp310-none-win_amd64.whl", hash = "sha256:b725da33e6e58e4a5d27958568484aa766e825e93aa20c26c91168be58e08cbb"}, - {file = "orjson-3.9.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c8e8fe01e435005d4421f183038fc70ca85d2c1e490f51fb972db92af6e047c2"}, - {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87f1097acb569dde17f246faa268759a71a2cb8c96dd392cd25c668b104cad2f"}, - {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff0f9913d82e1d1fadbd976424c316fbc4d9c525c81d047bbdd16bd27dd98cfc"}, - {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8055ec598605b0077e29652ccfe9372247474375e0e3f5775c91d9434e12d6b1"}, - {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6768a327ea1ba44c9114dba5fdda4a214bdb70129065cd0807eb5f010bfcbb5"}, - {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12365576039b1a5a47df01aadb353b68223da413e2e7f98c02403061aad34bde"}, - {file = "orjson-3.9.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:71c6b009d431b3839d7c14c3af86788b3cfac41e969e3e1c22f8a6ea13139404"}, - {file = "orjson-3.9.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e18668f1bd39e69b7fed19fa7cd1cd110a121ec25439328b5c89934e6d30d357"}, - {file = "orjson-3.9.15-cp311-none-win32.whl", hash = "sha256:62482873e0289cf7313461009bf62ac8b2e54bc6f00c6fabcde785709231a5d7"}, - {file = "orjson-3.9.15-cp311-none-win_amd64.whl", hash = "sha256:b3d336ed75d17c7b1af233a6561cf421dee41d9204aa3cfcc6c9c65cd5bb69a8"}, - {file = "orjson-3.9.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:82425dd5c7bd3adfe4e94c78e27e2fa02971750c2b7ffba648b0f5d5cc016a73"}, - {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c51378d4a8255b2e7c1e5cc430644f0939539deddfa77f6fac7b56a9784160a"}, - {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ae4e06be04dc00618247c4ae3f7c3e561d5bc19ab6941427f6d3722a0875ef7"}, - {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcef128f970bb63ecf9a65f7beafd9b55e3aaf0efc271a4154050fc15cdb386e"}, - {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b72758f3ffc36ca566ba98a8e7f4f373b6c17c646ff8ad9b21ad10c29186f00d"}, - {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c57bc7b946cf2efa67ac55766e41764b66d40cbd9489041e637c1304400494"}, - {file = "orjson-3.9.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:946c3a1ef25338e78107fba746f299f926db408d34553b4754e90a7de1d44068"}, - {file = "orjson-3.9.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2f256d03957075fcb5923410058982aea85455d035607486ccb847f095442bda"}, - {file = "orjson-3.9.15-cp312-none-win_amd64.whl", hash = "sha256:5bb399e1b49db120653a31463b4a7b27cf2fbfe60469546baf681d1b39f4edf2"}, - {file = "orjson-3.9.15-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b17f0f14a9c0ba55ff6279a922d1932e24b13fc218a3e968ecdbf791b3682b25"}, - {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f6cbd8e6e446fb7e4ed5bac4661a29e43f38aeecbf60c4b900b825a353276a1"}, - {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76bc6356d07c1d9f4b782813094d0caf1703b729d876ab6a676f3aaa9a47e37c"}, - {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fdfa97090e2d6f73dced247a2f2d8004ac6449df6568f30e7fa1a045767c69a6"}, - {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7413070a3e927e4207d00bd65f42d1b780fb0d32d7b1d951f6dc6ade318e1b5a"}, - {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cf1596680ac1f01839dba32d496136bdd5d8ffb858c280fa82bbfeb173bdd40"}, - {file = "orjson-3.9.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:809d653c155e2cc4fd39ad69c08fdff7f4016c355ae4b88905219d3579e31eb7"}, - {file = "orjson-3.9.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:920fa5a0c5175ab14b9c78f6f820b75804fb4984423ee4c4f1e6d748f8b22bc1"}, - {file = "orjson-3.9.15-cp38-none-win32.whl", hash = "sha256:2b5c0f532905e60cf22a511120e3719b85d9c25d0e1c2a8abb20c4dede3b05a5"}, - {file = "orjson-3.9.15-cp38-none-win_amd64.whl", hash = "sha256:67384f588f7f8daf040114337d34a5188346e3fae6c38b6a19a2fe8c663a2f9b"}, - {file = "orjson-3.9.15-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6fc2fe4647927070df3d93f561d7e588a38865ea0040027662e3e541d592811e"}, - {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34cbcd216e7af5270f2ffa63a963346845eb71e174ea530867b7443892d77180"}, - {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f541587f5c558abd93cb0de491ce99a9ef8d1ae29dd6ab4dbb5a13281ae04cbd"}, - {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92255879280ef9c3c0bcb327c5a1b8ed694c290d61a6a532458264f887f052cb"}, - {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05a1f57fb601c426635fcae9ddbe90dfc1ed42245eb4c75e4960440cac667262"}, - {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ede0bde16cc6e9b96633df1631fbcd66491d1063667f260a4f2386a098393790"}, - {file = "orjson-3.9.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e88b97ef13910e5f87bcbc4dd7979a7de9ba8702b54d3204ac587e83639c0c2b"}, - {file = "orjson-3.9.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57d5d8cf9c27f7ef6bc56a5925c7fbc76b61288ab674eb352c26ac780caa5b10"}, - {file = "orjson-3.9.15-cp39-none-win32.whl", hash = "sha256:001f4eb0ecd8e9ebd295722d0cbedf0748680fb9998d3993abaed2f40587257a"}, - {file = "orjson-3.9.15-cp39-none-win_amd64.whl", hash = "sha256:ea0b183a5fe6b2b45f3b854b0d19c4e932d6f5934ae1f723b07cf9560edd4ec7"}, - {file = "orjson-3.9.15.tar.gz", hash = "sha256:95cae920959d772f30ab36d3b25f83bb0f3be671e986c72ce22f8fa700dae061"}, + {file = "orjson-3.10.0-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47af5d4b850a2d1328660661f0881b67fdbe712aea905dadd413bdea6f792c33"}, + {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c90681333619d78360d13840c7235fdaf01b2b129cb3a4f1647783b1971542b6"}, + {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:400c5b7c4222cb27b5059adf1fb12302eebcabf1978f33d0824aa5277ca899bd"}, + {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5dcb32e949eae80fb335e63b90e5808b4b0f64e31476b3777707416b41682db5"}, + {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7d507c7493252c0a0264b5cc7e20fa2f8622b8a83b04d819b5ce32c97cf57b"}, + {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e286a51def6626f1e0cc134ba2067dcf14f7f4b9550f6dd4535fd9d79000040b"}, + {file = "orjson-3.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8acd4b82a5f3a3ec8b1dc83452941d22b4711964c34727eb1e65449eead353ca"}, + {file = "orjson-3.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:30707e646080dd3c791f22ce7e4a2fc2438765408547c10510f1f690bd336217"}, + {file = "orjson-3.10.0-cp310-none-win32.whl", hash = "sha256:115498c4ad34188dcb73464e8dc80e490a3e5e88a925907b6fedcf20e545001a"}, + {file = "orjson-3.10.0-cp310-none-win_amd64.whl", hash = "sha256:6735dd4a5a7b6df00a87d1d7a02b84b54d215fb7adac50dd24da5997ffb4798d"}, + {file = "orjson-3.10.0-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9587053e0cefc284e4d1cd113c34468b7d3f17666d22b185ea654f0775316a26"}, + {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bef1050b1bdc9ea6c0d08468e3e61c9386723633b397e50b82fda37b3563d72"}, + {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d16c6963ddf3b28c0d461641517cd312ad6b3cf303d8b87d5ef3fa59d6844337"}, + {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4251964db47ef090c462a2d909f16c7c7d5fe68e341dabce6702879ec26d1134"}, + {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73bbbdc43d520204d9ef0817ac03fa49c103c7f9ea94f410d2950755be2c349c"}, + {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:414e5293b82373606acf0d66313aecb52d9c8c2404b1900683eb32c3d042dbd7"}, + {file = "orjson-3.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:feaed5bb09877dc27ed0d37f037ddef6cb76d19aa34b108db270d27d3d2ef747"}, + {file = "orjson-3.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5127478260db640323cea131ee88541cb1a9fbce051f0b22fa2f0892f44da302"}, + {file = "orjson-3.10.0-cp311-none-win32.whl", hash = "sha256:b98345529bafe3c06c09996b303fc0a21961820d634409b8639bc16bd4f21b63"}, + {file = "orjson-3.10.0-cp311-none-win_amd64.whl", hash = "sha256:658ca5cee3379dd3d37dbacd43d42c1b4feee99a29d847ef27a1cb18abdfb23f"}, + {file = "orjson-3.10.0-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4329c1d24fd130ee377e32a72dc54a3c251e6706fccd9a2ecb91b3606fddd998"}, + {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef0f19fdfb6553342b1882f438afd53c7cb7aea57894c4490c43e4431739c700"}, + {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c4f60db24161534764277f798ef53b9d3063092f6d23f8f962b4a97edfa997a0"}, + {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1de3fd5c7b208d836f8ecb4526995f0d5877153a4f6f12f3e9bf11e49357de98"}, + {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f93e33f67729d460a177ba285002035d3f11425ed3cebac5f6ded4ef36b28344"}, + {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:237ba922aef472761acd697eef77fef4831ab769a42e83c04ac91e9f9e08fa0e"}, + {file = "orjson-3.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98c1bfc6a9bec52bc8f0ab9b86cc0874b0299fccef3562b793c1576cf3abb570"}, + {file = "orjson-3.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:30d795a24be16c03dca0c35ca8f9c8eaaa51e3342f2c162d327bd0225118794a"}, + {file = "orjson-3.10.0-cp312-none-win32.whl", hash = "sha256:6a3f53dc650bc860eb26ec293dfb489b2f6ae1cbfc409a127b01229980e372f7"}, + {file = "orjson-3.10.0-cp312-none-win_amd64.whl", hash = "sha256:983db1f87c371dc6ffc52931eb75f9fe17dc621273e43ce67bee407d3e5476e9"}, + {file = "orjson-3.10.0-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9a667769a96a72ca67237224a36faf57db0c82ab07d09c3aafc6f956196cfa1b"}, + {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade1e21dfde1d37feee8cf6464c20a2f41fa46c8bcd5251e761903e46102dc6b"}, + {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:23c12bb4ced1c3308eff7ba5c63ef8f0edb3e4c43c026440247dd6c1c61cea4b"}, + {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2d014cf8d4dc9f03fc9f870de191a49a03b1bcda51f2a957943fb9fafe55aac"}, + {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eadecaa16d9783affca33597781328e4981b048615c2ddc31c47a51b833d6319"}, + {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd583341218826f48bd7c6ebf3310b4126216920853cbc471e8dbeaf07b0b80e"}, + {file = "orjson-3.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:90bfc137c75c31d32308fd61951d424424426ddc39a40e367704661a9ee97095"}, + {file = "orjson-3.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13b5d3c795b09a466ec9fcf0bd3ad7b85467d91a60113885df7b8d639a9d374b"}, + {file = "orjson-3.10.0-cp38-none-win32.whl", hash = "sha256:5d42768db6f2ce0162544845facb7c081e9364a5eb6d2ef06cd17f6050b048d8"}, + {file = "orjson-3.10.0-cp38-none-win_amd64.whl", hash = "sha256:33e6655a2542195d6fd9f850b428926559dee382f7a862dae92ca97fea03a5ad"}, + {file = "orjson-3.10.0-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4050920e831a49d8782a1720d3ca2f1c49b150953667eed6e5d63a62e80f46a2"}, + {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1897aa25a944cec774ce4a0e1c8e98fb50523e97366c637b7d0cddabc42e6643"}, + {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9bf565a69e0082ea348c5657401acec3cbbb31564d89afebaee884614fba36b4"}, + {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6ebc17cfbbf741f5c1a888d1854354536f63d84bee537c9a7c0335791bb9009"}, + {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2817877d0b69f78f146ab305c5975d0618df41acf8811249ee64231f5953fee"}, + {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57d017863ec8aa4589be30a328dacd13c2dc49de1c170bc8d8c8a98ece0f2925"}, + {file = "orjson-3.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:22c2f7e377ac757bd3476ecb7480c8ed79d98ef89648f0176deb1da5cd014eb7"}, + {file = "orjson-3.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e62ba42bfe64c60c1bc84799944f80704e996592c6b9e14789c8e2a303279912"}, + {file = "orjson-3.10.0-cp39-none-win32.whl", hash = "sha256:60c0b1bdbccd959ebd1575bd0147bd5e10fc76f26216188be4a36b691c937077"}, + {file = "orjson-3.10.0-cp39-none-win_amd64.whl", hash = "sha256:175a41500ebb2fdf320bf78e8b9a75a1279525b62ba400b2b2444e274c2c8bee"}, + {file = "orjson-3.10.0.tar.gz", hash = "sha256:ba4d8cac5f2e2cff36bea6b6481cdb92b38c202bcec603d6f5ff91960595a1ed"}, ] [[package]] @@ -1478,6 +1505,24 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa typing = ["typing-extensions"] xmp = ["defusedxml"] +[[package]] +name = "pipx" +version = "1.5.0" +description = "Install and Run Python Applications in Isolated Environments" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pipx-1.5.0-py3-none-any.whl", hash = "sha256:801a55a9d58004bb18a464f668508e79fbffc22deb6f07982832d3ce3ff3756d"}, + {file = "pipx-1.5.0.tar.gz", hash = "sha256:2371af2b772954cdb5c1dbfa0170219e3d2c09d9ff9b18e975f65562eeb7ab0a"}, +] + +[package.dependencies] +argcomplete = ">=1.9.4" +colorama = {version = ">=0.4.4", markers = "sys_platform == \"win32\""} +packaging = ">=20" +platformdirs = ">=2.1" +userpath = ">=1.6,<1.9.0 || >1.9.0" + [[package]] name = "platformdirs" version = "4.2.0" @@ -1537,15 +1582,125 @@ requests = ">=2.21.0" [[package]] name = "pycparser" -version = "2.21" +version = "2.22" description = "C parser in Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] +[[package]] +name = "pydantic" +version = "2.7.1" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, + {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.18.2" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.18.2" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"}, + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"}, + {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"}, + {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"}, + {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"}, + {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"}, + {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"}, + {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"}, + {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"}, + {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"}, + {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"}, + {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"}, + {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"}, + {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"}, + {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + [[package]] name = "pygments" version = "2.17.2" @@ -1587,17 +1742,17 @@ testutils = ["gitpython (>3)"] [[package]] name = "pymdown-extensions" -version = "10.7.1" +version = "10.8.1" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.7.1-py3-none-any.whl", hash = "sha256:f5cc7000d7ff0d1ce9395d216017fa4df3dde800afb1fb72d1c7d3fd35e710f4"}, - {file = "pymdown_extensions-10.7.1.tar.gz", hash = "sha256:c70e146bdd83c744ffc766b4671999796aba18842b268510a329f7f64700d584"}, + {file = "pymdown_extensions-10.8.1-py3-none-any.whl", hash = "sha256:f938326115884f48c6059c67377c46cf631c733ef3629b6eed1349989d1b30cb"}, + {file = "pymdown_extensions-10.8.1.tar.gz", hash = "sha256:3ab1db5c9e21728dabf75192d71471f8e50f216627e9a1fa9535ecb0231b9940"}, ] [package.dependencies] -markdown = ">=3.5" +markdown = ">=3.6" pyyaml = "*" [package.extras] @@ -1704,101 +1859,101 @@ pyyaml = "*" [[package]] name = "rapidfuzz" -version = "3.6.2" +version = "3.7.0" description = "rapid fuzzy string matching" optional = false python-versions = ">=3.8" files = [ - {file = "rapidfuzz-3.6.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a5637e6bf11b15b5aff6ee818c76bdec99ad208511b78985e6209ba648a6e3ee"}, - {file = "rapidfuzz-3.6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:380586664f2f63807050ddb95e7702888b4f0b425abf17655940c411f39287ad"}, - {file = "rapidfuzz-3.6.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3168ff565d4b8c239cf11fb604dd2507d30e9bcaac76a4077c0ac23cf2c866ed"}, - {file = "rapidfuzz-3.6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be69f7fd46b5c6467fe5e2fd4cff3816b0c03048eed8a4becb9a73e6000960e7"}, - {file = "rapidfuzz-3.6.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cbd5894f23fdf5697499cf759523639838ac822bd1600e343fdce7313baa02ae"}, - {file = "rapidfuzz-3.6.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85a5b6e026393fe39fb61146b9c17c5af66fffbe1410e992c4bb06d9ec327bd3"}, - {file = "rapidfuzz-3.6.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab269adfc64480f209e99f253391a10735edd5c09046e04899adab5fb132f20e"}, - {file = "rapidfuzz-3.6.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35aeac852bca06023d6bbd50c1fc504ca5a9a3613d5e75a140f0be7601fa34ef"}, - {file = "rapidfuzz-3.6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e706f302c6a3ae0d74edd0d6ace46aee1ae07c563b436ccf5ff04db2b3571e60"}, - {file = "rapidfuzz-3.6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bec353f022011e6e5cd28ccb8700fbd2a33918197af0d4e0abb3c3f4845cc864"}, - {file = "rapidfuzz-3.6.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ef3925daaa93eed20401012e219f569ff0c039ed5bf4ce2d3737b4f75d441622"}, - {file = "rapidfuzz-3.6.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6ee98d88ae9ccc77ff61992ed33b2496478def5dc0da55c9a9aa06fcb725a352"}, - {file = "rapidfuzz-3.6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:423c7c588b09d618601097b7a0017dfcb91132a2076bef29023c5f3cd2dc3de1"}, - {file = "rapidfuzz-3.6.2-cp310-cp310-win32.whl", hash = "sha256:c17c5efee347a40a6f4c1eec59e3d7d1e22f7613a97f8b8a07733ef723483a04"}, - {file = "rapidfuzz-3.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:4209816626d8d6ff8ae7dc248061c6059e618b70c6e6f6e4d7444ae3740b2b85"}, - {file = "rapidfuzz-3.6.2-cp310-cp310-win_arm64.whl", hash = "sha256:1c54d3c85e522d3ac9ee39415f183c8fa184c4f87e7e5a37938f15a6d50e853a"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e06f6d270112f5db001f1cba5a97e1a48aee3d3dbdcbea3ec027c230462dbf9b"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:080cb71b50cb6aff11d1c6aeb157f273e2da0b2bdb3f9d7b01257e49e69a8576"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7895e04a22d6515bc91a850e0831f2405547605aa311d1ffec51e4818abc3c1"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd82f9838519136b7083dd1e3149ee80344521f3dc37f744f227505ff0883efb"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a945567c2b0b6e069454c9782d5234b0b6795718adf7a9f868bd3144afa6a023"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:673ba2c343644805acdae1cb949c6a4de71aa2f62a998978551ebea59603af3f"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d457c89bac1471442002e70551e8268e639b3870b4a4521eae363c07253be87"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:495c0d8e14e6f12520eb7fc71b9ba9fcaafb47fc23a654e6e89b6c7985ec0020"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6d67b649bf3e1b1722d04eca44d37919aef88305ce7ad05564502d013cf550fd"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e48dde8ca83d11daa00900cf6a5d281a1297aef9b7bfa73801af6e8822be5019"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:824cc381cf81cbf8d158f6935664ec2a69e6ac3b1d39fa201988bf81a257f775"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:1dfe4c24957474ce0ac75d886387e30e292b4be39228a6d71f76de414dc187db"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d57b98013b802621bbc8b12a46bfc9d36ac552ab51ca207f7ce167ad46adabeb"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-win32.whl", hash = "sha256:9a07dffac439223b4f1025dbfc68f4445a3460a859309c9858c2a3fa29617cdc"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:95a49c6b8bf1229743ae585dd5b7d57f0d15a7eb6e826866d5c9965ba958503c"}, - {file = "rapidfuzz-3.6.2-cp311-cp311-win_arm64.whl", hash = "sha256:af7c19ec86e11488539380d3db1755be5d561a3c0e7b04ff9d07abd7f9a8e9d8"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:de8adc12161bf282c60f12dc9233bb31632f71d446a010fe7469a69b8153427f"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:337e357f693130c4c6be740652542b260e36f622c59e01fa33d58f1d2750c930"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6468f8bc8c3c50604f43631550ef9cfec873515dba5023ca34d461be94669fc8"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74c6773b11445b5e5cf93ca383171cd0ac0cdeafea11a7b2a5688f8bf8d813e6"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1507fc5769aa109dda4de3a15f822a0f6a03e18d627bd0ba3ddbb253cf70e07"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:617949a70150e6fffdaed19253dd49f7a53528411dc8bf7663d499ba21e0f61e"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f8b77779174b1b40aa70827692571ab457061897846255ad7d5d559e2edb1932"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80e51b22a7da83f9c87a97e92df07ed0612c74c35496590255f4b5d5b4212dfe"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3ae7c86914cb6673e97e187ba431b9c4cf4177d9ae77f8a1e5b2ba9a5628839e"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ddc380ffaa90f204cc9ddcb779114b9ab6f015246d549de9d47871a97ef9f18a"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3c1dc078ef371fce09f9f3eec2ca4eaa2a8cd412ec53941015b4f39f14d34407"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:9a74102fc5a2534fe91f7507838623e1f3a149d8e05648389c42bb42e14b1c3f"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:48e1eaea8fcd522fca7f04f0480663f0f0cfb77957092cce60a93f4462864996"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-win32.whl", hash = "sha256:66b008bf2972740cd2dda5d382eb8bdb87265cd88198e71c7797bdc0d1f79d20"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:87ac3a87f2251ae2e95fc9478ca5c759de6d141d04c84d3fec9f9cdcfc167b33"}, - {file = "rapidfuzz-3.6.2-cp312-cp312-win_arm64.whl", hash = "sha256:b593cc51aed887e93b78c2f94dfae9008be2b23d17afd3b1f1d3eb3913b58f26"}, - {file = "rapidfuzz-3.6.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7d830bc7a9b586a374147ec60b08b1f9ae5996b43f75cc514f37faef3866b519"}, - {file = "rapidfuzz-3.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dbee7f5ff11872b76505cbd87c814abc823e8757f11c69062eb3b25130a283da"}, - {file = "rapidfuzz-3.6.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28c011fb31f2c3f82f503aedd6097d3d3854e574e327a119a3b7eb2cf90b79ca"}, - {file = "rapidfuzz-3.6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cda81d0e0ce0c13abfa46b24e10c1e85f9c6acb628f0a9a948f5779f9c2076a2"}, - {file = "rapidfuzz-3.6.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c279928651ce0e9e5220dcb25a00cc53b65e592a0861336a38299bcdca3a596"}, - {file = "rapidfuzz-3.6.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35bd4bc9c40e6994c5d6edea4b9319388b4d9711c13c66d543bb4c37624b4184"}, - {file = "rapidfuzz-3.6.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d07899506a5a8760448d9df036d528b55a554bf571714173635c79eef4a86e58"}, - {file = "rapidfuzz-3.6.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb2e51d01b9c6d6954a3e055c57a80d4685b4fc82719db5519fc153566bcd6bb"}, - {file = "rapidfuzz-3.6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:153d065e353371cc0aeff32b99999a5758266a64e958d1364189367c1c9f6814"}, - {file = "rapidfuzz-3.6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4edcceebb85ebfa49a3ddcde20ad891d36c08dc0fd592efdab0e7d313a4e36af"}, - {file = "rapidfuzz-3.6.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3549123fca5bb817341025f98e8e49ca99f84596c7c4f92b658f8e5836040d4a"}, - {file = "rapidfuzz-3.6.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:84c1032ae42628465b7a5cc35249906061e18a8193c9c27cbd2db54e9823a9a6"}, - {file = "rapidfuzz-3.6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9bcc91ebd8fc69a6bd3b5711c8250f5f4e70606b4da75ef415f57ad209978205"}, - {file = "rapidfuzz-3.6.2-cp38-cp38-win32.whl", hash = "sha256:f3a70f341c4c111bad910d2df69c78577a98af140319a996af24c9385939335d"}, - {file = "rapidfuzz-3.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:354ad5fe655beb7b279390cb58334903931c5452ecbad1b1666ffb06786498e2"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1b86b93d93020c2b3edc1665d75c8855784845fc0a739b312c26c3a4bf0c80d5"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:28243086ed0e50808bb56632e5442c457241646aeafafd501ac87901f40a3237"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed52461ae5a9ea4c400d38e2649c74a413f1a6d8fb8308b66f1fbd122514732f"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a46220f86a5f9cb016af31525e0d0865cad437d02239aa0d8aed2ab8bff1f1c"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81a630ed2fc3ec5fc7400eb66bab1f87e282b4d47f0abe3e48c6634dfa13b5e4"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8e5a437b9089df6242a718d9c31ab1742989e9400a0977af012ef483b63b4c2"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16270b5529de83b7bae7457e952e4d9cf3fbf029a837dd32d415bb9e0eb8e599"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5378c04102c7f084cde30a100154fa6d7e2baf0d51a6bdd2f912545559c1fb35"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7f18397c8d6a65fc0b288d2fc29bc7baeea6ba91eeb95163a3cd98f23cd3bc85"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2acd2514defce81e6ff4bbff50252d5e7df8e85a731442c4b83e44c86cf1c916"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:1df2faf80201952e252413b6fac6f3e146080dcebb87bb1bb722508e67558ed8"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6440ed0b3007c1c9286b0b88fe2ab2d9e83edd60cd62293b3dfabb732b4e8a30"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4fcfa23b5553b27f4016df77c53172ea743454cf12c28cfa7c35a309a2be93b3"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-win32.whl", hash = "sha256:2d580d937146e803c8e5e1b87916cab8d6f84013b6392713e201efcda335c7d8"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:fe2a68be734e8e88af23385c68d6467e15818b6b1df1cbfebf7bff577226c957"}, - {file = "rapidfuzz-3.6.2-cp39-cp39-win_arm64.whl", hash = "sha256:6478f7803efebf5f644d0b758439c5b25728550fdfbb19783d150004c46a75a9"}, - {file = "rapidfuzz-3.6.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:36ce7b68a7b90b787cdd73480a68d2f1ca63c31a3a9d5a79a8736f978e1e9344"}, - {file = "rapidfuzz-3.6.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53597fd72a9340bcdd80d3620f4957c2b92f9b569313b969a3abdaffd193aae6"}, - {file = "rapidfuzz-3.6.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4f6de745fe6ce46a422d353ee10599013631d7d714a36d025f164b2d4e8c000"}, - {file = "rapidfuzz-3.6.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62df2136068e2515ed8beb01756381ff62c29384d785e3bf46e3111d4ea3ba1e"}, - {file = "rapidfuzz-3.6.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7382c90170f60c846c81a07ddd80bb2e8c43c8383754486fa37f67391a571897"}, - {file = "rapidfuzz-3.6.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f31314fd2e2f3dc3e519e6f93669462ce7953df2def1c344aa8f5345976d0eb2"}, - {file = "rapidfuzz-3.6.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012221629d54d3bee954148247f711eb86d4d390b589ebfe03172ea0b37a7531"}, - {file = "rapidfuzz-3.6.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d41dd59a70decfce6595315367a2fea2af660d92a9d144acc6479030501014d7"}, - {file = "rapidfuzz-3.6.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f9fa14136a5b0cba1ec42531f7c3e0b0d3edb7fd6bc5e5ae7b498541f3855ab"}, - {file = "rapidfuzz-3.6.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:259364199cbfeca33b1af369fc7951f71717aa285184a3fa5a7b1772da1b89db"}, - {file = "rapidfuzz-3.6.2.tar.gz", hash = "sha256:cf911e792ab0c431694c9bf2648afabfd92099103f2e31492893e078ddca5e1a"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:860f438238f1807532aa5c5c25e74c284232ccc115fe84697b78e25d48f364f7"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bb9285abeb0477cdb2f8ea0cf7fd4b5f72ed5a9a7d3f0c0bb4a5239db2fc1ed"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:08671280e0c04d2bb3f39511f13cae5914e6690036fd1eefc3d47a47f9fae634"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04bae4d9c16ce1bab6447d196fb8258d98139ed8f9b288a38b84887985e4227b"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1efa2268b51b68156fb84d18ca1720311698a58051c4a19c40d670057ce60519"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:600b4d4315f33ec0356c0dab3991a5d5761102420bcff29e0773706aa48936e8"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18bc2f13c73d5d34499ff6ada55b052c445d3aa64d22c2639e5ab45472568046"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e11c5e6593be41a555475c9c20320342c1f5585d635a064924956944c465ad4"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d7878025248b99ccca3285891899373f98548f2ca13835d83619ffc42241c626"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b4a7e37fe136022d944374fcd8a2f72b8a19f7b648d2cdfb946667e9ede97f9f"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b5881856f830351aaabd869151124f64a80bf61560546d9588a630a4e933a5de"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:c788b11565cc176fab8fab6dfcd469031e906927db94bf7e422afd8ef8f88a5a"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9e17a3092e74025d896ef1d67ac236c83494da37a78ef84c712e4e2273c115f1"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-win32.whl", hash = "sha256:e499c823206c9ffd9d89aa11f813a4babdb9219417d4efe4c8a6f8272da00e98"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:91f798cc00cd94a0def43e9befc6e867c9bd8fa8f882d1eaa40042f528b7e2c7"}, + {file = "rapidfuzz-3.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:d5a3872f35bec89f07b993fa1c5401d11b9e68bcdc1b9737494e279308a38a5f"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ef6b6ab64c4c91c57a6b58e1d690b59453bfa1f1e9757a7e52e59b4079e36631"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f9070b42c0ba030b045bba16a35bdb498a0d6acb0bdb3ff4e325960e685e290"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:63044c63565f50818d885bfcd40ac369947da4197de56b4d6c26408989d48edf"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49b0c47860c733a3d73a4b70b97b35c8cbf24ef24f8743732f0d1c412a8c85de"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1b14489b038f007f425a06fcf28ac6313c02cb603b54e3a28d9cfae82198cc0"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be08f39e397a618aab907887465d7fabc2d1a4d15d1a67cb8b526a7fb5202a3e"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16895dc62a7b92028f9c8b6d22830f1cbc77306ee794f461afc6028e1a8d7539"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:579cce49dfa57ffd8c8227b3fb53cced54b4df70cec502e63e9799b4d1f44004"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:40998c8dc35fdd221790b8b5134a8d7499adbfab9a5dd9ec626c7e92e17a43ed"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:dc3fdb4738a6b83ae27f1d8923b00d3a9c2b5c50da75b9f8b81841839c6e3e1f"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:92b8146fbfb37ac358ef7e0f6b79619e4f793fbbe894b99ea87920f9c0a9d77d"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:1dfceaa7c2914585bb8a043265c39ec09078f13fbf53b5525722fc074306b6fa"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f332d61f51b0b9c8b55a0fb052b4764b6ad599ea8ce948ac47a4388e9083c35e"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-win32.whl", hash = "sha256:dfd1e4819f1f3c47141f86159b44b7360ecb19bf675080b3b40437bf97273ab9"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:594b9c33fc1a86784962043ee3fbaaed875fbaadff72e467c2f7a83cd6c5d69d"}, + {file = "rapidfuzz-3.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b13a6823a1b83ae43f8bf35955df35032bee7bec0daf9b5ab836e0286067434"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:075a419a0ec29be44b3d7f4bcfa5cb7e91e419379a85fc05eb33de68315bd96f"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:51a5b96d2081c3afbef1842a61d63e55d0a5a201473e6975a80190ff2d6f22ca"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9460d8fddac7ea46dff9298eee9aa950dbfe79f2eb509a9f18fbaefcd10894c"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f39eb1513ee139ba6b5c01fe47ddf2d87e9560dd7fdee1068f7f6efbae70de34"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eace9fdde58a425d4c9a93021b24a0cac830df167a5b2fc73299e2acf9f41493"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cc77237242303733de47829028a0a8b6ab9188b23ec9d9ff0a674fdcd3c8e7f"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74e692357dd324dff691d379ef2c094c9ec526c0ce83ed43a066e4e68fe70bf6"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f2075ac9ee5c15d33d24a1efc8368d095602b5fd9634c5b5f24d83e41903528"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5a8ba64d72329a940ff6c74b721268c2004eecc48558f648a38e96915b5d1c1b"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a1f268a2a37cd22573b4a06eccd481c04504b246d3cadc2d8e8dfa64b575636d"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:42c2e8a2341363c7caf276efdbe1a673fc5267a02568c47c8e980f12e9bc8727"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:a9acca34b34fb895ee6a84c436bb919f3b9cd8f43e7003d43e9573a1d990ff74"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9bad6a0fe3bc1753dacaa6229a8ba7d9844eb7ae24d44d17c5f4c51c91a8a95e"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-win32.whl", hash = "sha256:c86bc4b1d2380739e6485396195e30021df509b4923f3f757914e171587bce7c"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:d7361608c8e73a1dc0203a87d151cddebdade0098a047c46da43c469c07df964"}, + {file = "rapidfuzz-3.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:8fdc26e7863e0f63c2185d53bb61f5173ad4451c1c8287b535b30ea25a419a5a"}, + {file = "rapidfuzz-3.7.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9b6167468f76779a14b9af66210f68741af94d32d086f19118de4e919f00585c"}, + {file = "rapidfuzz-3.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bd394e28ff221557ea4d8152fcec3e66d9f620557feca5f2bedc4c21f8cf2f9"}, + {file = "rapidfuzz-3.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8e70f876ca89a6df344f8157ac60384e8c05a0dfb442da2490c3f1c45238ccf5"}, + {file = "rapidfuzz-3.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c837f89d86a5affe9ee6574dad6b195475676a6ab171a67920fc99966f2ab2c"}, + {file = "rapidfuzz-3.7.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cda4550a98658f9a8bcdc03d0498ed1565c1563880e3564603a9eaae28d51b2a"}, + {file = "rapidfuzz-3.7.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecd70212fd9f1f8b1d3bdd8bcb05acc143defebd41148bdab43e573b043bb241"}, + {file = "rapidfuzz-3.7.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:187db4cc8fb54f8c49c67b7f38ef3a122ce23be273032fa2ff34112a2694c3d8"}, + {file = "rapidfuzz-3.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4604dfc1098920c4eb6d0c6b5cc7bdd4bf95b48633e790c1d3f100a25870691d"}, + {file = "rapidfuzz-3.7.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01581b688c5f4f6665b779135e32db0edab1d78028abf914bb91469928efa383"}, + {file = "rapidfuzz-3.7.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0828b55ec8ad084febdf4ab0c942eb1f81c97c0935f1cb0be0b4ea84ce755988"}, + {file = "rapidfuzz-3.7.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:150c98b65faff17b917b9d36bff8a4d37b6173579c6bc2e38ff2044e209d37a4"}, + {file = "rapidfuzz-3.7.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7e4eea225d2bff1aff4c85fcc44716596d3699374d99eb5906b7a7560297460e"}, + {file = "rapidfuzz-3.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7bc944d7e830cfce0f8b4813875f05904207017b66e25ab7ee757507001310a9"}, + {file = "rapidfuzz-3.7.0-cp38-cp38-win32.whl", hash = "sha256:3e55f02105c451ab6ff0edaaba57cab1b6c0a0241cfb2b306d4e8e1503adba50"}, + {file = "rapidfuzz-3.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:41851620d2900791d66d9b6092fc163441d7dd91a460c73b07957ff1c517bc30"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e8041c6b2d339766efe6298fa272f79d6dd799965df364ef4e50f488c101c899"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4e09d81008e212fc824ea23603ff5270d75886e72372fa6c7c41c1880bcb57ed"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:419c8961e861fb5fc5590056c66a279623d1ea27809baea17e00cdc313f1217a"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1522eaab91b9400b3ef16eebe445940a19e70035b5bc5d98aef23d66e9ac1df0"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:611278ce3136f4544d596af18ab8849827d64372e1d8888d9a8d071bf4a3f44d"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4efa9bfc5b955b6474ee077eee154e240441842fa304f280b06e6b6aa58a1d1e"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0cc9d3c8261457af3f8756b1f71a9fdc4892978a9e8b967976d2803e08bf972"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce728e2b582fd396bc2559160ee2e391e6a4b5d2e455624044699d96abe8a396"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a6a36c9299e059e0bee3409218bc5235a46570c20fc980cdee5ed21ea6110ad"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9ea720db8def684c1eb71dadad1f61c9b52f4d979263eb5d443f2b22b0d5430a"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:358692f1df3f8aebcd48e69c77c948c9283b44c0efbaf1eeea01739efe3cd9a6"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:faded69ffe79adcefa8da08f414a0fd52375e2b47f57be79471691dad9656b5a"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7f9f3dc14fadbd553975f824ac48c381f42192cec9d7e5711b528357662a8d8e"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-win32.whl", hash = "sha256:7be5f460ff42d7d27729115bfe8a02e83fa0284536d8630ee900d17b75c29e65"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:dd5ad2c12dab2b98340c4b7b9592c8f349730bda9a2e49675ea592bbcbc1360b"}, + {file = "rapidfuzz-3.7.0-cp39-cp39-win_arm64.whl", hash = "sha256:aa163257a0ac4e70f9009d25e5030bdd83a8541dfa3ba78dc86b35c9e16a80b4"}, + {file = "rapidfuzz-3.7.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4e50840a8a8e0229563eeaf22e21a203359859557db8829f4d0285c17126c5fb"}, + {file = "rapidfuzz-3.7.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:632f09e19365ace5ff2670008adc8bf23d03d668b03a30230e5b60ff9317ee93"}, + {file = "rapidfuzz-3.7.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:209dda6ae66b702f74a78cef555397cdc2a83d7f48771774a20d2fc30808b28c"}, + {file = "rapidfuzz-3.7.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bc0b78572626af6ab134895e4dbfe4f4d615d18dcc43b8d902d8e45471aabba"}, + {file = "rapidfuzz-3.7.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ba14850cc8258b3764ea16b8a4409ac2ba16d229bde7a5f495dd479cd9ccd56"}, + {file = "rapidfuzz-3.7.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b917764fd2b267addc9d03a96d26f751f6117a95f617428c44a069057653b528"}, + {file = "rapidfuzz-3.7.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1252ca156e1b053e84e5ae1c8e9e062ee80468faf23aa5c543708212a42795fd"}, + {file = "rapidfuzz-3.7.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:86c7676a32d7524e40bc73546e511a408bc831ae5b163029d325ea3a2027d089"}, + {file = "rapidfuzz-3.7.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20e7d729af2e5abb29caa070ec048aba042f134091923d9ca2ac662b5604577e"}, + {file = "rapidfuzz-3.7.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86eea3e6c314a9238de568254a9c591ec73c2985f125675ed5f171d869c47773"}, + {file = "rapidfuzz-3.7.0.tar.gz", hash = "sha256:620df112c39c6d27316dc1e22046dc0382d6d91fd60d7c51bd41ca0333d867e9"}, ] [package.extras] @@ -1820,13 +1975,13 @@ dev = ["black (==22.1.0)", "flake8 (==4.0.1)", "isort (==5.10.1)"] [[package]] name = "red-discordbot" -version = "3.5.7" +version = "3.5.9" description = "A highly customisable Discord bot" optional = false python-versions = "<3.12,>=3.8.1" files = [ - {file = "Red-DiscordBot-3.5.7.tar.gz", hash = "sha256:7673ea794016b6d3d7b96eb07a46cd92c52974e15b3a6ad95608df69dc320838"}, - {file = "Red_DiscordBot-3.5.7-py3-none-any.whl", hash = "sha256:3b9df02cd8efd35170c9505e3f0a8c9b1bd415bcc2ca451a19733d28b1b3a5b4"}, + {file = "Red_DiscordBot-3.5.9-py3-none-any.whl", hash = "sha256:d392c4947f95151435792e99cc74afb7c440b11fb516fa59927f5e462c61385f"}, + {file = "red_discordbot-3.5.9.tar.gz", hash = "sha256:1f50e508b6923868ea4d7bb0eb80a2b1dbdeae248cf0a57717539ca6f5c5874f"}, ] [package.dependencies] @@ -1848,14 +2003,14 @@ markdown = "3.6" markdown-it-py = "3.0.0" mdurl = "0.1.2" multidict = "6.0.5" -orjson = "3.9.15" +orjson = "3.10.0" packaging = "24.0" platformdirs = "4.2.0" psutil = "5.9.8" pygments = "2.17.2" python-dateutil = "2.9.0.post0" pyyaml = "6.0.1" -rapidfuzz = "3.6.2" +rapidfuzz = "3.7.0" red-commons = "1.0.0" red-lavalink = "0.11.0" rich = "13.7.1" @@ -1867,11 +2022,11 @@ yarl = "1.9.4" [package.extras] all = ["async-timeout (==4.0.3)", "asyncpg (==0.29.0)"] -dev = ["alabaster (==0.7.13)", "astroid (==3.1.0)", "async-timeout (==4.0.3)", "asyncpg (==0.29.0)", "black (==23.12.1)", "certifi (==2024.2.2)", "charset-normalizer (==3.3.2)", "dill (==0.3.8)", "docutils (==0.20.1)", "exceptiongroup (==1.2.0)", "imagesize (==1.4.1)", "importlib-metadata (==7.1.0)", "iniconfig (==2.0.0)", "isort (==5.13.2)", "jinja2 (==3.1.3)", "markupsafe (==2.1.5)", "mccabe (==0.7.0)", "mypy-extensions (==1.0.0)", "pathspec (==0.12.1)", "pluggy (==1.4.0)", "pylint (==3.1.0)", "pytest (==7.4.4)", "pytest-asyncio (==0.21.1)", "pytest-mock (==3.12.0)", "pytz (==2024.1)", "requests (==2.31.0)", "snowballstemmer (==2.2.0)", "sphinx (==7.1.2)", "sphinx-prompt (==1.7.0)", "sphinx-rtd-theme (==2.0.0)", "sphinxcontrib-applehelp (==1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (==2.0.1)", "sphinxcontrib-jquery (==4.1)", "sphinxcontrib-jsmath (==1.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)", "sphinxcontrib-trio (==1.1.2)", "tomli (==2.0.1)", "tomli (==2.0.1)", "tomlkit (==0.12.4)", "urllib3 (==2.2.1)", "zipp (==3.18.1)"] +dev = ["alabaster (==0.7.13)", "astroid (==3.1.0)", "async-timeout (==4.0.3)", "asyncpg (==0.29.0)", "black (==23.12.1)", "certifi (==2024.2.2)", "charset-normalizer (==3.3.2)", "dill (==0.3.8)", "docutils (==0.20.1)", "exceptiongroup (==1.2.0)", "imagesize (==1.4.1)", "importlib-metadata (==7.1.0)", "iniconfig (==2.0.0)", "isort (==5.13.2)", "jinja2 (==3.1.3)", "markupsafe (==2.1.5)", "mccabe (==0.7.0)", "mypy-extensions (==1.0.0)", "pathspec (==0.12.1)", "pluggy (==1.4.0)", "pylint (==3.1.0)", "pytest (==7.4.4)", "pytest-asyncio (==0.21.1)", "pytest-mock (==3.14.0)", "pytz (==2024.1)", "requests (==2.31.0)", "snowballstemmer (==2.2.0)", "sphinx (==7.1.2)", "sphinx-prompt (==1.7.0)", "sphinx-rtd-theme (==2.0.0)", "sphinxcontrib-applehelp (==1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (==2.0.1)", "sphinxcontrib-jquery (==4.1)", "sphinxcontrib-jsmath (==1.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)", "sphinxcontrib-trio (==1.1.2)", "tomli (==2.0.1)", "tomli (==2.0.1)", "tomlkit (==0.12.4)", "urllib3 (==2.2.1)", "zipp (==3.18.1)"] doc = ["alabaster (==0.7.13)", "certifi (==2024.2.2)", "charset-normalizer (==3.3.2)", "docutils (==0.20.1)", "imagesize (==1.4.1)", "importlib-metadata (==7.1.0)", "jinja2 (==3.1.3)", "markupsafe (==2.1.5)", "pytz (==2024.1)", "requests (==2.31.0)", "snowballstemmer (==2.2.0)", "sphinx (==7.1.2)", "sphinx-prompt (==1.7.0)", "sphinx-rtd-theme (==2.0.0)", "sphinxcontrib-applehelp (==1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (==2.0.1)", "sphinxcontrib-jquery (==4.1)", "sphinxcontrib-jsmath (==1.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)", "sphinxcontrib-trio (==1.1.2)", "urllib3 (==2.2.1)", "zipp (==3.18.1)"] postgres = ["async-timeout (==4.0.3)", "asyncpg (==0.29.0)"] style = ["black (==23.12.1)", "mypy-extensions (==1.0.0)", "pathspec (==0.12.1)", "tomli (==2.0.1)"] -test = ["astroid (==3.1.0)", "dill (==0.3.8)", "exceptiongroup (==1.2.0)", "iniconfig (==2.0.0)", "isort (==5.13.2)", "mccabe (==0.7.0)", "pluggy (==1.4.0)", "pylint (==3.1.0)", "pytest (==7.4.4)", "pytest-asyncio (==0.21.1)", "pytest-mock (==3.12.0)", "tomli (==2.0.1)", "tomlkit (==0.12.4)"] +test = ["astroid (==3.1.0)", "dill (==0.3.8)", "exceptiongroup (==1.2.0)", "iniconfig (==2.0.0)", "isort (==5.13.2)", "mccabe (==0.7.0)", "pluggy (==1.4.0)", "pylint (==3.1.0)", "pytest (==7.4.4)", "pytest-asyncio (==0.21.1)", "pytest-mock (==3.14.0)", "tomli (==2.0.1)", "tomlkit (==0.12.4)"] [[package]] name = "red-lavalink" @@ -1895,104 +2050,90 @@ test = ["pytest (>=7)", "pytest-asyncio (>=0.19)"] [[package]] name = "regex" -version = "2023.12.25" +version = "2024.4.28" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, - {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, - {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, - {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, - {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, - {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, - {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, - {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, - {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, - {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"}, - {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"}, - {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"}, - {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"}, - {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"}, - {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"}, - {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"}, - {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, + {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd196d056b40af073d95a2879678585f0b74ad35190fac04ca67954c582c6b61"}, + {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8bb381f777351bd534462f63e1c6afb10a7caa9fa2a421ae22c26e796fe31b1f"}, + {file = "regex-2024.4.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:47af45b6153522733aa6e92543938e97a70ce0900649ba626cf5aad290b737b6"}, + {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99d6a550425cc51c656331af0e2b1651e90eaaa23fb4acde577cf15068e2e20f"}, + {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf29304a8011feb58913c382902fde3395957a47645bf848eea695839aa101b7"}, + {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:92da587eee39a52c91aebea8b850e4e4f095fe5928d415cb7ed656b3460ae79a"}, + {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6277d426e2f31bdbacb377d17a7475e32b2d7d1f02faaecc48d8e370c6a3ff31"}, + {file = "regex-2024.4.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28e1f28d07220c0f3da0e8fcd5a115bbb53f8b55cecf9bec0c946eb9a059a94c"}, + {file = "regex-2024.4.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aaa179975a64790c1f2701ac562b5eeb733946eeb036b5bcca05c8d928a62f10"}, + {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6f435946b7bf7a1b438b4e6b149b947c837cb23c704e780c19ba3e6855dbbdd3"}, + {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:19d6c11bf35a6ad077eb23852827f91c804eeb71ecb85db4ee1386825b9dc4db"}, + {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:fdae0120cddc839eb8e3c15faa8ad541cc6d906d3eb24d82fb041cfe2807bc1e"}, + {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e672cf9caaf669053121f1766d659a8813bd547edef6e009205378faf45c67b8"}, + {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f57515750d07e14743db55d59759893fdb21d2668f39e549a7d6cad5d70f9fea"}, + {file = "regex-2024.4.28-cp310-cp310-win32.whl", hash = "sha256:a1409c4eccb6981c7baabc8888d3550df518add6e06fe74fa1d9312c1838652d"}, + {file = "regex-2024.4.28-cp310-cp310-win_amd64.whl", hash = "sha256:1f687a28640f763f23f8a9801fe9e1b37338bb1ca5d564ddd41619458f1f22d1"}, + {file = "regex-2024.4.28-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84077821c85f222362b72fdc44f7a3a13587a013a45cf14534df1cbbdc9a6796"}, + {file = "regex-2024.4.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b45d4503de8f4f3dc02f1d28a9b039e5504a02cc18906cfe744c11def942e9eb"}, + {file = "regex-2024.4.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:457c2cd5a646dd4ed536c92b535d73548fb8e216ebee602aa9f48e068fc393f3"}, + {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b51739ddfd013c6f657b55a508de8b9ea78b56d22b236052c3a85a675102dc6"}, + {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:459226445c7d7454981c4c0ce0ad1a72e1e751c3e417f305722bbcee6697e06a"}, + {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:670fa596984b08a4a769491cbdf22350431970d0112e03d7e4eeaecaafcd0fec"}, + {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe00f4fe11c8a521b173e6324d862ee7ee3412bf7107570c9b564fe1119b56fb"}, + {file = "regex-2024.4.28-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36f392dc7763fe7924575475736bddf9ab9f7a66b920932d0ea50c2ded2f5636"}, + {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:23a412b7b1a7063f81a742463f38821097b6a37ce1e5b89dd8e871d14dbfd86b"}, + {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f1d6e4b7b2ae3a6a9df53efbf199e4bfcff0959dbdb5fd9ced34d4407348e39a"}, + {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:499334ad139557de97cbc4347ee921c0e2b5e9c0f009859e74f3f77918339257"}, + {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:0940038bec2fe9e26b203d636c44d31dd8766abc1fe66262da6484bd82461ccf"}, + {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:66372c2a01782c5fe8e04bff4a2a0121a9897e19223d9eab30c54c50b2ebeb7f"}, + {file = "regex-2024.4.28-cp311-cp311-win32.whl", hash = "sha256:c77d10ec3c1cf328b2f501ca32583625987ea0f23a0c2a49b37a39ee5c4c4630"}, + {file = "regex-2024.4.28-cp311-cp311-win_amd64.whl", hash = "sha256:fc0916c4295c64d6890a46e02d4482bb5ccf33bf1a824c0eaa9e83b148291f90"}, + {file = "regex-2024.4.28-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:08a1749f04fee2811c7617fdd46d2e46d09106fa8f475c884b65c01326eb15c5"}, + {file = "regex-2024.4.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b8eb28995771c087a73338f695a08c9abfdf723d185e57b97f6175c5051ff1ae"}, + {file = "regex-2024.4.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd7ef715ccb8040954d44cfeff17e6b8e9f79c8019daae2fd30a8806ef5435c0"}, + {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb0315a2b26fde4005a7c401707c5352df274460f2f85b209cf6024271373013"}, + {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2fc053228a6bd3a17a9b0a3f15c3ab3cf95727b00557e92e1cfe094b88cc662"}, + {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fe9739a686dc44733d52d6e4f7b9c77b285e49edf8570754b322bca6b85b4cc"}, + {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74fcf77d979364f9b69fcf8200849ca29a374973dc193a7317698aa37d8b01c"}, + {file = "regex-2024.4.28-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:965fd0cf4694d76f6564896b422724ec7b959ef927a7cb187fc6b3f4e4f59833"}, + {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2fef0b38c34ae675fcbb1b5db760d40c3fc3612cfa186e9e50df5782cac02bcd"}, + {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bc365ce25f6c7c5ed70e4bc674f9137f52b7dd6a125037f9132a7be52b8a252f"}, + {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ac69b394764bb857429b031d29d9604842bc4cbfd964d764b1af1868eeebc4f0"}, + {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:144a1fc54765f5c5c36d6d4b073299832aa1ec6a746a6452c3ee7b46b3d3b11d"}, + {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2630ca4e152c221072fd4a56d4622b5ada876f668ecd24d5ab62544ae6793ed6"}, + {file = "regex-2024.4.28-cp312-cp312-win32.whl", hash = "sha256:7f3502f03b4da52bbe8ba962621daa846f38489cae5c4a7b5d738f15f6443d17"}, + {file = "regex-2024.4.28-cp312-cp312-win_amd64.whl", hash = "sha256:0dd3f69098511e71880fb00f5815db9ed0ef62c05775395968299cb400aeab82"}, + {file = "regex-2024.4.28-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:374f690e1dd0dbdcddea4a5c9bdd97632cf656c69113f7cd6a361f2a67221cb6"}, + {file = "regex-2024.4.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f87ae6b96374db20f180eab083aafe419b194e96e4f282c40191e71980c666"}, + {file = "regex-2024.4.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5dbc1bcc7413eebe5f18196e22804a3be1bfdfc7e2afd415e12c068624d48247"}, + {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f85151ec5a232335f1be022b09fbbe459042ea1951d8a48fef251223fc67eee1"}, + {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57ba112e5530530fd175ed550373eb263db4ca98b5f00694d73b18b9a02e7185"}, + {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:224803b74aab56aa7be313f92a8d9911dcade37e5f167db62a738d0c85fdac4b"}, + {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a54a047b607fd2d2d52a05e6ad294602f1e0dec2291152b745870afc47c1397"}, + {file = "regex-2024.4.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a2a512d623f1f2d01d881513af9fc6a7c46e5cfffb7dc50c38ce959f9246c94"}, + {file = "regex-2024.4.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c06bf3f38f0707592898428636cbb75d0a846651b053a1cf748763e3063a6925"}, + {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1031a5e7b048ee371ab3653aad3030ecfad6ee9ecdc85f0242c57751a05b0ac4"}, + {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d7a353ebfa7154c871a35caca7bfd8f9e18666829a1dc187115b80e35a29393e"}, + {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7e76b9cfbf5ced1aca15a0e5b6f229344d9b3123439ffce552b11faab0114a02"}, + {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5ce479ecc068bc2a74cb98dd8dba99e070d1b2f4a8371a7dfe631f85db70fe6e"}, + {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7d77b6f63f806578c604dca209280e4c54f0fa9a8128bb8d2cc5fb6f99da4150"}, + {file = "regex-2024.4.28-cp38-cp38-win32.whl", hash = "sha256:d84308f097d7a513359757c69707ad339da799e53b7393819ec2ea36bc4beb58"}, + {file = "regex-2024.4.28-cp38-cp38-win_amd64.whl", hash = "sha256:2cc1b87bba1dd1a898e664a31012725e48af826bf3971e786c53e32e02adae6c"}, + {file = "regex-2024.4.28-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7413167c507a768eafb5424413c5b2f515c606be5bb4ef8c5dee43925aa5718b"}, + {file = "regex-2024.4.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:108e2dcf0b53a7c4ab8986842a8edcb8ab2e59919a74ff51c296772e8e74d0ae"}, + {file = "regex-2024.4.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f1c5742c31ba7d72f2dedf7968998730664b45e38827637e0f04a2ac7de2f5f1"}, + {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecc6148228c9ae25ce403eade13a0961de1cb016bdb35c6eafd8e7b87ad028b1"}, + {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7d893c8cf0e2429b823ef1a1d360a25950ed11f0e2a9df2b5198821832e1947"}, + {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4290035b169578ffbbfa50d904d26bec16a94526071ebec3dadbebf67a26b25e"}, + {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44a22ae1cfd82e4ffa2066eb3390777dc79468f866f0625261a93e44cdf6482b"}, + {file = "regex-2024.4.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd24fd140b69f0b0bcc9165c397e9b2e89ecbeda83303abf2a072609f60239e2"}, + {file = "regex-2024.4.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:39fb166d2196413bead229cd64a2ffd6ec78ebab83fff7d2701103cf9f4dfd26"}, + {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9301cc6db4d83d2c0719f7fcda37229691745168bf6ae849bea2e85fc769175d"}, + {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c3d389e8d76a49923683123730c33e9553063d9041658f23897f0b396b2386f"}, + {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:99ef6289b62042500d581170d06e17f5353b111a15aa6b25b05b91c6886df8fc"}, + {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b91d529b47798c016d4b4c1d06cc826ac40d196da54f0de3c519f5a297c5076a"}, + {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:43548ad74ea50456e1c68d3c67fff3de64c6edb85bcd511d1136f9b5376fc9d1"}, + {file = "regex-2024.4.28-cp39-cp39-win32.whl", hash = "sha256:05d9b6578a22db7dedb4df81451f360395828b04f4513980b6bd7a1412c679cc"}, + {file = "regex-2024.4.28-cp39-cp39-win_amd64.whl", hash = "sha256:3986217ec830c2109875be740531feb8ddafe0dfa49767cdcd072ed7e8927962"}, + {file = "regex-2024.4.28.tar.gz", hash = "sha256:83ab366777ea45d58f72593adf35d36ca911ea8bd838483c1823b883a121b0e4"}, ] [[package]] @@ -2036,28 +2177,28 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ruff" -version = "0.3.4" +version = "0.3.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:60c870a7d46efcbc8385d27ec07fe534ac32f3b251e4fc44b3cbfd9e09609ef4"}, - {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6fc14fa742e1d8f24910e1fff0bd5e26d395b0e0e04cc1b15c7c5e5fe5b4af91"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3ee7880f653cc03749a3bfea720cf2a192e4f884925b0cf7eecce82f0ce5854"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf133dd744f2470b347f602452a88e70dadfbe0fcfb5fd46e093d55da65f82f7"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f3860057590e810c7ffea75669bdc6927bfd91e29b4baa9258fd48b540a4365"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:986f2377f7cf12efac1f515fc1a5b753c000ed1e0a6de96747cdf2da20a1b369"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fd98e85869603e65f554fdc5cddf0712e352fe6e61d29d5a6fe087ec82b76c"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64abeed785dad51801b423fa51840b1764b35d6c461ea8caef9cf9e5e5ab34d9"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df52972138318bc7546d92348a1ee58449bc3f9eaf0db278906eb511889c4b50"}, - {file = "ruff-0.3.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:98e98300056445ba2cc27d0b325fd044dc17fcc38e4e4d2c7711585bd0a958ed"}, - {file = "ruff-0.3.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:519cf6a0ebed244dce1dc8aecd3dc99add7a2ee15bb68cf19588bb5bf58e0488"}, - {file = "ruff-0.3.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb0acfb921030d00070539c038cd24bb1df73a2981e9f55942514af8b17be94e"}, - {file = "ruff-0.3.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cf187a7e7098233d0d0c71175375c5162f880126c4c716fa28a8ac418dcf3378"}, - {file = "ruff-0.3.4-py3-none-win32.whl", hash = "sha256:af27ac187c0a331e8ef91d84bf1c3c6a5dea97e912a7560ac0cef25c526a4102"}, - {file = "ruff-0.3.4-py3-none-win_amd64.whl", hash = "sha256:de0d5069b165e5a32b3c6ffbb81c350b1e3d3483347196ffdf86dc0ef9e37dd6"}, - {file = "ruff-0.3.4-py3-none-win_arm64.whl", hash = "sha256:6810563cc08ad0096b57c717bd78aeac888a1bfd38654d9113cb3dc4d3f74232"}, - {file = "ruff-0.3.4.tar.gz", hash = "sha256:f0f4484c6541a99862b693e13a151435a279b271cff20e37101116a21e2a1ad1"}, + {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"}, + {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"}, + {file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"}, + {file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"}, + {file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"}, + {file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"}, ] [[package]] @@ -2098,13 +2239,13 @@ files = [ [[package]] name = "tinycss2" -version = "1.2.1" +version = "1.3.0" description = "A tiny CSS parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, - {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, + {file = "tinycss2-1.3.0-py3-none-any.whl", hash = "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7"}, + {file = "tinycss2-1.3.0.tar.gz", hash = "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d"}, ] [package.dependencies] @@ -2112,7 +2253,7 @@ webencodings = ">=0.4" [package.extras] doc = ["sphinx", "sphinx_rtd_theme"] -test = ["flake8", "isort", "pytest"] +test = ["pytest", "ruff"] [[package]] name = "tomlkit" @@ -2153,6 +2294,20 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "userpath" +version = "1.9.2" +description = "Cross-platform tool for adding locations to the user PATH" +optional = false +python-versions = ">=3.7" +files = [ + {file = "userpath-1.9.2-py3-none-any.whl", hash = "sha256:2cbf01a23d655a1ff8fc166dfb78da1b641d1ceabf0fe5f970767d380b14e89d"}, + {file = "userpath-1.9.2.tar.gz", hash = "sha256:6c52288dab069257cc831846d15d48133522455d4677ee69a9781f11dbefd815"}, +] + +[package.dependencies] +click = "*" + [[package]] name = "uvloop" version = "0.19.0" @@ -2436,4 +2591,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.11,<3.12" -content-hash = "42f6d2a053929b5a71b673e30f222c3e5914c723d074b453afd349e11118590f" +content-hash = "25dce93989e94b49932b4f5bb8489eb37b2e9aa5860679f7150e2beca8b0471c" diff --git a/pyproject.toml b/pyproject.toml index 93bdd53..507b821 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,11 +9,12 @@ package-mode = false [tool.poetry.dependencies] python = ">=3.11,<3.12" -Red-DiscordBot = "^3.5.5" +Red-DiscordBot = "^3.5.9" py-dactyl = "^2.0.4" websockets = "^12.0" pillow = "^10.3.0" numpy = "^1.26.4" +pydantic = "^2.7.1" [tool.poetry.group.dev] optional = true @@ -21,6 +22,7 @@ optional = true [tool.poetry.group.dev.dependencies] ruff = "^0.3.1" pylint = "^3.1.0" +pipx = "^1.5.0" [tool.poetry.group.docs] optional = true