Compare commits

...

1 commit

Author SHA1 Message Date
144aafccdf
misc(moderation): working on sqlalchemy migration
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 1m46s
2023-11-02 20:59:45 -04:00
4 changed files with 365 additions and 211 deletions

View file

@ -5,7 +5,7 @@
"short" : "Custom cog intended for use on the Galaxy discord server.", "short" : "Custom cog intended for use on the Galaxy discord server.",
"description" : "Custom cog intended for use on the Galaxy discord server.", "description" : "Custom cog intended for use on the Galaxy discord server.",
"end_user_data_statement" : "This cog does not store any End User Data.", "end_user_data_statement" : "This cog does not store any End User Data.",
"requirements": ["mysql-connector-python", "humanize", "pytimeparse2"], "requirements": ["sqlalchemy", "humanize", "pytimeparse2"],
"hidden": false, "hidden": false,
"disabled": false "disabled": false
} }

View file

@ -4,7 +4,11 @@ from datetime import datetime, timedelta, timezone
from typing import Union from typing import Union
import discord import discord
import humanize import humanize
import mysql.connector from sqlalchemy import MetaData, Table, Column, Integer, String, Boolean, DateTime, Text, BigInteger, func, or_, and_
from sqlalchemy.exc import OperationalError, NoSuchTableError
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine
from discord.ext import tasks from discord.ext import tasks
from pytimeparse2 import disable_dateutil, parse from pytimeparse2 import disable_dateutil, parse
from redbot.core import app_commands, checks, Config, commands from redbot.core import app_commands, checks, Config, commands
@ -19,10 +23,8 @@ class Moderation(commands.Cog):
self.bot = bot self.bot = bot
self.config = Config.get_conf(self, identifier=481923957134912) self.config = Config.get_conf(self, identifier=481923957134912)
self.config.register_global( self.config.register_global(
mysql_address= " ", db_use_sqlite = True,
mysql_database = " ", db_connection_string = " "
mysql_username = " ",
mysql_password = " "
) )
self.config.register_guild( self.config.register_guild(
ignore_other_bots = True, ignore_other_bots = True,
@ -105,78 +107,69 @@ class Moderation(commands.Cog):
moderation_type = 'UNMUTE' moderation_type = 'UNMUTE'
else: else:
return return
await self.mysql_log(entry.guild.id, entry.user.id, moderation_type, entry.target.id, duration, reason) await self.sql_log(entry.guild.id, entry.user.id, moderation_type, entry.target.id, duration, reason)
async def connect(self): async def connect(self):
"""Connects to the MySQL database, and returns a connection object.""" """Connects to the MySQL database, and returns a connection object."""
conf = await self.check_conf([
'mysql_address',
'mysql_database',
'mysql_username',
'mysql_password'
])
if conf:
raise LookupError("MySQL connection details not set properly!")
try: try:
connection = mysql.connector.connect( if await self.config.db_use_sqlite() is False and await self.config.db_connection_string() != " ":
host=await self.config.mysql_address(), engine = create_async_engine(await self.config.db_connection_string())
user=await self.config.mysql_username(), else:
password=await self.config.mysql_password(), engine = create_async_engine('sqlite:///moderation.db')
database=await self.config.mysql_database() except OperationalError as e:
) self.logger.fatal("Unable to access the database!\nError:\n%s", e.msg)
return connection raise ConnectionRefusedError(f"Unable to access the Database!\n{e.msg}") from e
except mysql.connector.ProgrammingError as e: return engine
self.logger.fatal("Unable to access the MySQL database!\nError:\n%s", e.msg)
raise ConnectionRefusedError(f"Unable to access the MySQL Database!\n{e.msg}") from e async def fetch_table(self, engine, guild_id):
table = Table(f'moderation_{guild_id}', MetaData(), autoload_with=engine)
return table
async def create_guild_table(self, guild: discord.Guild): async def create_guild_table(self, guild: discord.Guild):
database = await self.connect() engine = await self.connect()
cursor = database.cursor() metadata = MetaData()
try: try:
cursor.execute(f"SELECT * FROM `moderation_{guild.id}`") await self.fetch_table(engine, guild.id)
self.logger.info("MySQL Table exists for server %s (%s)", guild.name, guild.id) self.logger.info("MySQL Table exists for server %s (%s)", guild.name, guild.id)
except mysql.connector.errors.ProgrammingError: except NoSuchTableError:
query = f""" moderation = Table(
CREATE TABLE `moderation_{guild.id}` ( f'moderation_{guild.id}', metadata,
moderation_id INT UNIQUE PRIMARY KEY NOT NULL, Column('moderationId', Integer, primary_key=True, autoincrement=True, index=True),
timestamp INT NOT NULL, Column('timestamp', Integer, nullable=False),
moderation_type LONGTEXT NOT NULL, Column('moderationType', String(20), nullable=False),
target_id LONGTEXT NOT NULL, Column('targetId', BigInteger, nullable=False, index=True),
moderator_id LONGTEXT NOT NULL, Column('moderatorId', BigInteger, nullable=False, index=True),
duration LONGTEXT, Column('roleId', BigInteger),
end_timestamp INT, Column('duration', String(40)),
reason LONGTEXT, Column('endTimestamp', Integer),
resolved BOOL NOT NULL, Column('reason', Text),
resolved_by LONGTEXT, Column('resolved', Boolean, nullable=False),
resolve_reason LONGTEXT, Column('resolvedBy', BigInteger),
expired BOOL NOT NULL Column('resolveReason', Text),
) Column('expired', Boolean, nullable=False)
""" )
cursor.execute(query) ins = moderation.insert()
index_query_1 = "CREATE INDEX idx_target_id ON moderation_%s(target_id(25));" ins = moderation.insert().values(
cursor.execute(index_query_1, (guild.id,)) moderationId=0,
index_query_2 = "CREATE INDEX idx_moderator_id ON moderation_%s(moderator_id(25));" timestamp=0,
cursor.execute(index_query_2, (guild.id,)) moderationType="NULL",
index_query_3 = "CREATE INDEX idx_moderation_id ON moderation_%s(moderation_id);" targetId=0,
cursor.execute(index_query_3, (guild.id,)) moderatorId=0,
insert_query = f""" roleId=0,
INSERT INTO `moderation_{guild.id}` duration="NULL",
(moderation_id, timestamp, moderation_type, target_id, moderator_id, duration, end_timestamp, reason, resolved, resolved_by, resolve_reason, expired) endTimestamp=0,
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) reason="NULL",
""" resolved=0,
insert_values = (0, 0, "NULL", 0, 0, "NULL", 0, "NULL", 0, "NULL", "NULL", 0) resolvedBy=0,
cursor.execute(insert_query, insert_values) resolveReason="NULL",
database.commit() expired=0
self.logger.info("MySQL Table (moderation_%s) created for %s (%s)", guild.id, guild.name, guild.id) )
database.close() async with engine.begin() as conn:
await conn.run_sync(metadata.create_all)
async def check_conf(self, config: list): await conn.execute(ins)
"""Checks if any required config options are not set.""" conn.commit()
not_found_list = [] conn.close()
for item in config: self.logger.info("Database Table (moderation_%s) created for %s (%s)", guild.id, guild.name, guild.id)
if await self.config.item() == " ":
not_found_list.append(item)
return not_found_list
def check_permissions(self, user: discord.User, permissions: list, ctx: Union[commands.Context, discord.Interaction] = None, guild: discord.Guild = None): def check_permissions(self, user: discord.User, permissions: list, ctx: Union[commands.Context, discord.Interaction] = None, guild: discord.Guild = None):
"""Checks if a user has a specific permission (or a list of permissions) in a channel.""" """Checks if a user has a specific permission (or a list of permissions) in a channel."""
@ -193,31 +186,45 @@ class Moderation(commands.Cog):
return permission return permission
return False return False
async def mysql_log(self, guild_id: str, author_id: str, moderation_type: str, target_id: int, duration, reason: str): async def sql_log(self, guild_id: str, author_id: str, moderation_type: str, target_id: int, duration, reason: str):
timestamp = int(time.time()) timestamp = int(time.time())
if duration != "NULL": if duration != "NULL":
end_timedelta = datetime.fromtimestamp(timestamp) + duration end_timedelta = datetime.fromtimestamp(timestamp) + duration
end_timestamp = int(end_timedelta.timestamp()) end_timestamp = int(end_timedelta.timestamp())
else: else:
end_timestamp = 0 end_timestamp = 0
database = await self.connect() engine = await self.connect()
cursor = database.cursor() table = await self.fetch_table(engine, guild_id)
moderation_id = await self.get_next_case_number(guild_id=guild_id, cursor=cursor) moderation_id = await self.get_next_case_number(guild_id=guild_id, engine=engine)
sql = f"INSERT INTO `moderation_{guild_id}` (moderation_id, timestamp, moderation_type, target_id, moderator_id, duration, end_timestamp, reason, resolved, resolved_by, resolve_reason, expired) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" sql = table.insert().values(
val = (moderation_id, timestamp, moderation_type, target_id, author_id, duration, end_timestamp, f"{reason}", 0, "NULL", "NULL", 0) moderation_id=moderation_id,
cursor.execute(sql, val) timestamp=timestamp,
database.commit() moderation_type=moderation_type,
database.close() target_id=target_id,
self.logger.debug("MySQL row inserted into moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, 0, NULL, NULL, 0", guild_id, moderation_id, timestamp, moderation_type, target_id, author_id, duration, end_timestamp, reason) moderator_id=author_id,
duration=duration,
end_timestamp=end_timestamp,
reason=reason,
resolved=0,
resolved_by="NULL",
resolve_reason="NULL",
expired=0
)
async with engine.connect() as conn:
conn.execute(sql)
conn.commit()
conn.close()
self.logger.debug("Row inserted into moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, 0, NULL, NULL, 0", guild_id, moderation_id, timestamp, moderation_type, target_id, author_id, duration, end_timestamp, reason)
return moderation_id return moderation_id
async def get_next_case_number(self, guild_id: str, cursor = None): async def get_next_case_number(self, guild_id: str, engine: AsyncEngine):
"""This method returns the next case number from the MySQL table for a specific guild.""" """This method returns the next case number from the database table for a specific guild."""
if not cursor: table = await self.fetch_table(engine, guild_id)
database = await self.connect() async with engine.connect() as conn:
cursor = database.cursor() result = await conn.execute(table.select().order_by(table.c.moderationId.desc()).limit(1))
cursor.execute(f"SELECT moderation_id FROM `moderation_{guild_id}` ORDER BY moderation_id DESC LIMIT 1") result = result.fetchone()
return cursor.fetchone()[0] + 1 conn.close()
return result[0] + 1 if result else 1
def generate_dict(self, result): def generate_dict(self, result):
case: dict = { case: dict = {
@ -226,13 +233,14 @@ class Moderation(commands.Cog):
"moderation_type": result[2], "moderation_type": result[2],
"target_id": result[3], "target_id": result[3],
"moderator_id": result[4], "moderator_id": result[4],
"duration": result[5], "role_id": result[5],
"end_timestamp": result[6], "duration": result[6],
"reason": result[7], "end_timestamp": result[7],
"resolved": result[8], "reason": result[8],
"resolved_by": result[9], "resolved": result[9],
"resolve_reason": result[10], "resolved_by": result[10],
"expired": result[11] "resolve_reason": result[11],
"expired": result[12]
} }
return case return case
@ -346,13 +354,12 @@ class Moderation(commands.Cog):
async def fetch_case(self, moderation_id: int, guild_id: str): async def fetch_case(self, moderation_id: int, guild_id: str):
"""This method fetches a case from the database and returns the case's dictionary.""" """This method fetches a case from the database and returns the case's dictionary."""
database = await self.connect() engine = await self.connect()
cursor = database.cursor() table = await self.fetch_table(engine, guild_id)
query = "SELECT * FROM moderation_%s WHERE moderation_id = %s;" async with engine.connect() as conn:
cursor.execute(query, (guild_id, moderation_id)) result = await conn.execute(table.select().where(table.c.moderation_id == moderation_id))
result = cursor.fetchone() result = result.fetchone()
cursor.close() conn.close()
database.close()
return self.generate_dict(result) return self.generate_dict(result)
async def log(self, interaction: discord.Interaction, moderation_id: int, resolved: bool = False): async def log(self, interaction: discord.Interaction, moderation_id: int, resolved: bool = False):
@ -397,7 +404,7 @@ class Moderation(commands.Cog):
await target.send(embed=embed) await target.send(embed=embed)
except discord.errors.HTTPException: except discord.errors.HTTPException:
pass pass
moderation_id = await self.mysql_log(interaction.guild.id, interaction.user.id, 'NOTE', target.id, 'NULL', reason) moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'NOTE', target.id, 'NULL', reason)
await self.log(interaction, moderation_id) await self.log(interaction, moderation_id)
@app_commands.command(name="warn") @app_commands.command(name="warn")
@ -429,7 +436,7 @@ class Moderation(commands.Cog):
await target.send(embed=embed) await target.send(embed=embed)
except discord.errors.HTTPException: except discord.errors.HTTPException:
pass pass
moderation_id = await self.mysql_log(interaction.guild.id, interaction.user.id, 'WARN', target.id, 'NULL', reason) moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'WARN', target.id, 'NULL', reason)
await self.log(interaction, moderation_id) await self.log(interaction, moderation_id)
@app_commands.command(name="mute") @app_commands.command(name="mute")
@ -479,7 +486,7 @@ class Moderation(commands.Cog):
await target.send(embed=embed) await target.send(embed=embed)
except discord.errors.HTTPException: except discord.errors.HTTPException:
pass pass
moderation_id = await self.mysql_log(interaction.guild.id, interaction.user.id, 'MUTE', target.id, parsed_time, reason) moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'MUTE', target.id, parsed_time, reason)
await self.log(interaction, moderation_id) await self.log(interaction, moderation_id)
@app_commands.command(name="unmute") @app_commands.command(name="unmute")
@ -523,7 +530,7 @@ class Moderation(commands.Cog):
await target.send(embed=embed) await target.send(embed=embed)
except discord.errors.HTTPException: except discord.errors.HTTPException:
pass pass
moderation_id = await self.mysql_log(interaction.guild.id, interaction.user.id, 'UNMUTE', target.id, 'NULL', reason) moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'UNMUTE', target.id, 'NULL', reason)
await self.log(interaction, moderation_id) await self.log(interaction, moderation_id)
@app_commands.command(name="kick") @app_commands.command(name="kick")
@ -560,7 +567,7 @@ class Moderation(commands.Cog):
except discord.errors.HTTPException: except discord.errors.HTTPException:
pass pass
await target.kick(f"Kicked by {interaction.user.id} for: {reason}") await target.kick(f"Kicked by {interaction.user.id} for: {reason}")
moderation_id = await self.mysql_log(interaction.guild.id, interaction.user.id, 'KICK', target.id, 'NULL', reason) moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'KICK', target.id, 'NULL', reason)
await self.log(interaction, moderation_id) await self.log(interaction, moderation_id)
@app_commands.command(name="ban") @app_commands.command(name="ban")
@ -618,7 +625,7 @@ class Moderation(commands.Cog):
except discord.errors.HTTPException: except discord.errors.HTTPException:
pass pass
await interaction.guild.ban(target, reason=f"Tempbanned by {interaction.user.id} for: {reason} (Duration: {parsed_time})", delete_message_seconds=delete_messages) await interaction.guild.ban(target, reason=f"Tempbanned by {interaction.user.id} for: {reason} (Duration: {parsed_time})", delete_message_seconds=delete_messages)
await self.mysql_log(interaction.guild.id, interaction.user.id, 'TEMPBAN', target.id, parsed_time, reason) await self.sql_log(interaction.guild.id, interaction.user.id, 'TEMPBAN', target.id, parsed_time, reason)
else: else:
await interaction.response.send_message(content=f"{target.mention} has been banned!\n**Reason** - `{reason}`") await interaction.response.send_message(content=f"{target.mention} has been banned!\n**Reason** - `{reason}`")
if silent is None: if silent is None:
@ -630,7 +637,7 @@ class Moderation(commands.Cog):
except discord.errors.HTTPException: except discord.errors.HTTPException:
pass pass
await interaction.guild.ban(target, reason=f"Banned by {interaction.user.id} for: {reason}", delete_message_seconds=delete_messages) await interaction.guild.ban(target, reason=f"Banned by {interaction.user.id} for: {reason}", delete_message_seconds=delete_messages)
moderation_id = await self.mysql_log(interaction.guild.id, interaction.user.id, 'BAN', target.id, 'NULL', reason) moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'BAN', target.id, 'NULL', reason)
await self.log(interaction, moderation_id) await self.log(interaction, moderation_id)
@app_commands.command(name="unban") @app_commands.command(name="unban")
@ -676,7 +683,7 @@ class Moderation(commands.Cog):
await target.send(embed=embed) await target.send(embed=embed)
except discord.errors.HTTPException: except discord.errors.HTTPException:
pass pass
moderation_id = await self.mysql_log(interaction.guild.id, interaction.user.id, 'UNBAN', target.id, 'NULL', reason) moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'UNBAN', target.id, 'NULL', reason)
await self.log(interaction, moderation_id) await self.log(interaction, moderation_id)
@app_commands.command(name="history") @app_commands.command(name="history")
@ -699,37 +706,26 @@ class Moderation(commands.Cog):
if permissions: if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True) await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return return
database = await self.connect() start_index = (page - 1) * pagesize
cursor = database.cursor() end_index = page * pagesize
if target: engine = await self.connect()
query = """SELECT * table = await self.fetch_table(engine, interaction.guild.id)
FROM moderation_%s async with engine.connect() as conn:
WHERE target_id = %s if target:
ORDER BY moderation_id DESC;""" results = conn.execute(table.select().where(table.c.targetId == target.id).order_by(table.c.moderationId.desc()).offset(start_index).limit(end_index - start_index + 1))
cursor.execute(query, (interaction.guild.id, target.id)) case_quantity = conn.execute(table.select(func.count()).where(table.c.targetId == target.id))
elif moderator: elif moderator:
query = """SELECT * results = conn.execute(table.select().where(table.c.moderatorId == moderator.id).order_by(table.c.moderationId.desc()).offset(start_index).limit(end_index - start_index + 1))
FROM moderation_%s case_quantity = conn.execute(table.select(func.count()).where(table.c.moderatorId == moderator.id))
WHERE moderator_id = %s else:
ORDER BY moderation_id DESC;""" results = conn.execute(table.select().order_by(table.c.moderationId.desc()).offset(start_index).limit(pagesize))
cursor.execute(query, (interaction.guild.id, moderator.id)) case_quantity = conn.execute(table.select(func.count())) - 1 # account for case 0 techincally existing
else: conn.close()
query = """SELECT *
FROM moderation_%s
ORDER BY moderation_id DESC;"""
cursor.execute(query, (interaction.guild.id,))
results = cursor.fetchall()
result_dict_list = [] result_dict_list = []
for result in results: for result in results:
case_dict = self.generate_dict(result) case_dict = self.generate_dict(result)
result_dict_list.append(case_dict) result_dict_list.append(case_dict)
if target or moderator:
case_quantity = len(result_dict_list)
else:
case_quantity = len(result_dict_list) - 1 # account for case 0 technically existing
page_quantity = round(case_quantity / pagesize) page_quantity = round(case_quantity / pagesize)
start_index = (page - 1) * pagesize
end_index = page * pagesize
embed = discord.Embed(color=await self.bot.get_embed_color(None)) embed = discord.Embed(color=await self.bot.get_embed_color(None))
embed.set_author(icon_url=interaction.guild.icon.url, name='Infraction History') embed.set_author(icon_url=interaction.guild.icon.url, name='Infraction History')
embed.set_footer(text=f"Page {page}/{page_quantity} | {case_quantity} Results") embed.set_footer(text=f"Page {page}/{page_quantity} | {case_quantity} Results")
@ -765,56 +761,49 @@ class Moderation(commands.Cog):
if permissions: if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True) await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return return
conf = await self.check_conf(['mysql_database']) engine = await self.connect()
if conf: table = await self.fetch_table(engine, interaction.guild.id)
raise(LookupError) async with engine.connect() as conn:
database = await self.connect() result_1 = conn.execute(table.select().where(table.c.moderationId == case_number))[0]
cursor = database.cursor() if result_1 is None or case_number == 0:
db = await self.config.mysql_database() await interaction.response.send_message(content=f"There is no moderation with a case number of {case_number}.", ephemeral=True)
query_1 = "SELECT * FROM moderation_%s WHERE moderation_id = %s;" conn.close()
cursor.execute(query_1, (interaction.guild.id, case_number)) return
result_1 = cursor.fetchone() result_2 = conn.execute(table.select().where(table.c.moderationId == case_number).where(table.c.resolved == 0))[0]
if result_1 is None or case_number == 0: if result_2 is None:
await interaction.response.send_message(content=f"There is no moderation with a case number of {case_number}.", ephemeral=True) await interaction.response.send_message(content=f"This moderation has already been resolved!\nUse `/case {case_number}` for more information.", ephemeral=True)
return conn.close()
query_2 = "SELECT * FROM moderation_%s WHERE moderation_id = %s AND resolved = 0;" return
cursor.execute(query_2, (interaction.guild.id, case_number)) case = self.generate_dict(result_2)
result_2 = cursor.fetchone() if reason is None:
if result_2 is None: reason = "No reason given."
await interaction.response.send_message(content=f"This moderation has already been resolved!\nUse `/case {case_number}` for more information.", ephemeral=True) if case['moderation_type'] in ['UNMUTE', 'UNBAN']:
return await interaction.response.send_message(content="You cannot resolve this type of moderation!", ephemeral=True)
case = self.generate_dict(result_2) conn.close()
if reason is None: return
reason = "No reason given." if case['moderation_type'] in ['MUTE', 'TEMPBAN', 'BAN']:
if case['moderation_type'] in ['UNMUTE', 'UNBAN']: if case['moderation_type'] == 'MUTE':
await interaction.response.send_message(content="You cannot resolve this type of moderation!", ephemeral=True) try:
if case['moderation_type'] in ['MUTE', 'TEMPBAN', 'BAN']: member = await interaction.guild.fetch_member(case['target_id'])
if case['moderation_type'] == 'MUTE': await member.timeout(None, reason=f"Case #{case_number} resolved by {interaction.user.id}")
try: except discord.NotFound:
member = await interaction.guild.fetch_member(case['target_id']) pass
await member.timeout(None, reason=f"Case #{case_number} resolved by {interaction.user.id}") if case['moderation_type'] in ['TEMPBAN', 'BAN']:
except discord.NotFound: try:
pass user = await interaction.client.fetch_user(case['target_id'])
if case['moderation_type'] in ['TEMPBAN', 'BAN']: await interaction.guild.unban(user, reason=f"Case #{case_number} resolved by {interaction.user.id}")
try: except discord.NotFound:
user = await interaction.client.fetch_user(case['target_id']) pass
await interaction.guild.unban(user, reason=f"Case #{case_number} resolved by {interaction.user.id}") conn.execute(table.update().where(table.c.moderationId == case_number).values(resolved=1, expired=1, resolvedBy=interaction.user.id, resolveReason=reason))
except discord.NotFound: else:
pass conn.execute(table.update().where(table.c.moderationId == case_number).values(resolved=1, resolvedBy=interaction.user.id, resolveReason=reason))
resolve_query = f"UPDATE `{db}`.`moderation_{interaction.guild.id}` SET resolved = 1, expired = 1, resolved_by = %s, resolve_reason = %s WHERE moderation_id = %s" conn.commit()
else: result = conn.execute(table.select().where(table.c.moderationId == case_number))[0]
resolve_query = f"UPDATE `{db}`.`moderation_{interaction.guild.id}` SET resolved = 1, resolved_by = %s, resolve_reason = %s WHERE moderation_id = %s" conn.close()
cursor.execute(resolve_query, (interaction.user.id, reason, case_number))
database.commit()
response_query = "SELECT * FROM moderation_%s WHERE moderation_id = %s;"
cursor.execute(response_query, (interaction.guild.id, case_number))
result = cursor.fetchone()
case_dict = self.generate_dict(result) case_dict = self.generate_dict(result)
embed = await self.embed_factory('case', interaction=interaction, case_dict=case_dict) embed = await self.embed_factory('case', interaction=interaction, case_dict=case_dict)
await interaction.response.send_message(content=f"✅ Moderation #{case_number} resolved!", embed=embed) await interaction.response.send_message(content=f"✅ Moderation #{case_number} resolved!", embed=embed)
await self.log(interaction, case_number, True) await self.log(interaction, case_number, True)
cursor.close()
database.close()
@app_commands.command(name="case") @app_commands.command(name="case")
async def case(self, interaction: discord.Interaction, case_number: int, ephemeral: bool = False): async def case(self, interaction: discord.Interaction, case_number: int, ephemeral: bool = False):
@ -840,36 +829,44 @@ class Moderation(commands.Cog):
@tasks.loop(minutes=1) @tasks.loop(minutes=1)
async def handle_expiry(self): async def handle_expiry(self):
conf = await self.check_conf(['mysql_database']) engine = await self.connect()
if conf:
raise(LookupError)
database = await self.connect()
cursor = database.cursor()
db = await self.config.mysql_database() db = await self.config.mysql_database()
guilds: list[discord.Guild] = self.bot.guilds guilds: list[discord.Guild] = self.bot.guilds
for guild in guilds: async with engine.connect() as conn:
if not await self.bot.cog_disabled_in_guild(self, guild): for guild in guilds:
tempban_query = f"SELECT target_id, moderation_id FROM moderation_{guild.id} WHERE end_timestamp != 0 AND end_timestamp <= %s AND moderation_type = 'TEMPBAN' AND expired = 0" if not await self.bot.cog_disabled_in_guild(self, guild):
try: table = await self.fetch_table(engine, guild.id)
cursor.execute(tempban_query, (time.time(),)) result = conn.execute(
result = cursor.fetchall() table.select().where(table.c.moderationType == 'TEMPBAN').where(table.c.expired == 0).where(table.c.endTimestamp != 0).where(table.c.endTimestamp <= time.time()))
except mysql.connector.errors.ProgrammingError: target_ids = [row[0] for row in result]
continue moderation_ids = [row[1] for row in result]
target_ids = [row[0] for row in result] for target_id, moderation_id in zip(target_ids, moderation_ids):
moderation_ids = [row[1] for row in result] user: discord.User = await self.bot.fetch_user(target_id)
for target_id, moderation_id in zip(target_ids, moderation_ids): await guild.unban(user, reason=f"Automatic unban from case #{moderation_id}")
user: discord.User = await self.bot.fetch_user(target_id) embed = await self.embed_factory('message', guild, f'Automatic unban from case #{moderation_id}', 'unbanned')
await guild.unban(user, reason=f"Automatic unban from case #{moderation_id}") try:
embed = await self.embed_factory('message', guild, f'Automatic unban from case #{moderation_id}', 'unbanned') await user.send(embed=embed)
try: except discord.errors.HTTPException:
await user.send(embed=embed) pass
except discord.errors.HTTPException: conn.execute(
pass table.update().
expiry_query = f"UPDATE `{db}`.`moderation_{guild.id}` SET expired = 1 WHERE (end_timestamp != 0 AND end_timestamp <= %s AND expired = 0) OR (expired = 0 AND resolved = 1)" where(
cursor.execute(expiry_query, (time.time(),)) or_(
database.commit() and_(
cursor.close() table.c.end_timestamp != 0,
database.close() table.c.end_timestamp <= time.time(),
table.c.expired == 0
),
and_(
table.c.expired == 0,
table.c.resolved == 1
)
)
).
values(expired=1)
)
conn.commit()
conn.close()
@commands.group(autohelp=True) @commands.group(autohelp=True)
@checks.admin() @checks.admin()

160
poetry.lock generated
View file

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. # This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
[[package]] [[package]]
name = "aiodns" name = "aiodns"
@ -730,6 +730,76 @@ files = [
{file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"},
] ]
[[package]]
name = "greenlet"
version = "3.0.1"
description = "Lightweight in-process concurrent programming"
optional = false
python-versions = ">=3.7"
files = [
{file = "greenlet-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f89e21afe925fcfa655965ca8ea10f24773a1791400989ff32f467badfe4a064"},
{file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28e89e232c7593d33cac35425b58950789962011cc274aa43ef8865f2e11f46d"},
{file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8ba29306c5de7717b5761b9ea74f9c72b9e2b834e24aa984da99cbfc70157fd"},
{file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19bbdf1cce0346ef7341705d71e2ecf6f41a35c311137f29b8a2dc2341374565"},
{file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599daf06ea59bfedbec564b1692b0166a0045f32b6f0933b0dd4df59a854caf2"},
{file = "greenlet-3.0.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b641161c302efbb860ae6b081f406839a8b7d5573f20a455539823802c655f63"},
{file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d57e20ba591727da0c230ab2c3f200ac9d6d333860d85348816e1dca4cc4792e"},
{file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5805e71e5b570d490938d55552f5a9e10f477c19400c38bf1d5190d760691846"},
{file = "greenlet-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:52e93b28db27ae7d208748f45d2db8a7b6a380e0d703f099c949d0f0d80b70e9"},
{file = "greenlet-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7bfb769f7efa0eefcd039dd19d843a4fbfbac52f1878b1da2ed5793ec9b1a65"},
{file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e6c7db42638dc45cf2e13c73be16bf83179f7859b07cfc139518941320be96"},
{file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1757936efea16e3f03db20efd0cd50a1c86b06734f9f7338a90c4ba85ec2ad5a"},
{file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19075157a10055759066854a973b3d1325d964d498a805bb68a1f9af4aaef8ec"},
{file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9d21aaa84557d64209af04ff48e0ad5e28c5cca67ce43444e939579d085da72"},
{file = "greenlet-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2847e5d7beedb8d614186962c3d774d40d3374d580d2cbdab7f184580a39d234"},
{file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:97e7ac860d64e2dcba5c5944cfc8fa9ea185cd84061c623536154d5a89237884"},
{file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b2c02d2ad98116e914d4f3155ffc905fd0c025d901ead3f6ed07385e19122c94"},
{file = "greenlet-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:22f79120a24aeeae2b4471c711dcf4f8c736a2bb2fabad2a67ac9a55ea72523c"},
{file = "greenlet-3.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:100f78a29707ca1525ea47388cec8a049405147719f47ebf3895e7509c6446aa"},
{file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60d5772e8195f4e9ebf74046a9121bbb90090f6550f81d8956a05387ba139353"},
{file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:daa7197b43c707462f06d2c693ffdbb5991cbb8b80b5b984007de431493a319c"},
{file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea6b8aa9e08eea388c5f7a276fabb1d4b6b9d6e4ceb12cc477c3d352001768a9"},
{file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d11ebbd679e927593978aa44c10fc2092bc454b7d13fdc958d3e9d508aba7d0"},
{file = "greenlet-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbd4c177afb8a8d9ba348d925b0b67246147af806f0b104af4d24f144d461cd5"},
{file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20107edf7c2c3644c67c12205dc60b1bb11d26b2610b276f97d666110d1b511d"},
{file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8bef097455dea90ffe855286926ae02d8faa335ed8e4067326257cb571fc1445"},
{file = "greenlet-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:b2d3337dcfaa99698aa2377c81c9ca72fcd89c07e7eb62ece3f23a3fe89b2ce4"},
{file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80ac992f25d10aaebe1ee15df45ca0d7571d0f70b645c08ec68733fb7a020206"},
{file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:337322096d92808f76ad26061a8f5fccb22b0809bea39212cd6c406f6a7060d2"},
{file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9934adbd0f6e476f0ecff3c94626529f344f57b38c9a541f87098710b18af0a"},
{file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc4d815b794fd8868c4d67602692c21bf5293a75e4b607bb92a11e821e2b859a"},
{file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41bdeeb552d814bcd7fb52172b304898a35818107cc8778b5101423c9017b3de"},
{file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e6061bf1e9565c29002e3c601cf68569c450be7fc3f7336671af7ddb4657166"},
{file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa24255ae3c0ab67e613556375a4341af04a084bd58764731972bcbc8baeba36"},
{file = "greenlet-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:b489c36d1327868d207002391f662a1d163bdc8daf10ab2e5f6e41b9b96de3b1"},
{file = "greenlet-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f33f3258aae89da191c6ebaa3bc517c6c4cbc9b9f689e5d8452f7aedbb913fa8"},
{file = "greenlet-3.0.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d2905ce1df400360463c772b55d8e2518d0e488a87cdea13dd2c71dcb2a1fa16"},
{file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a02d259510b3630f330c86557331a3b0e0c79dac3d166e449a39363beaae174"},
{file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55d62807f1c5a1682075c62436702aaba941daa316e9161e4b6ccebbbf38bda3"},
{file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fcc780ae8edbb1d050d920ab44790201f027d59fdbd21362340a85c79066a74"},
{file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eddd98afc726f8aee1948858aed9e6feeb1758889dfd869072d4465973f6bfd"},
{file = "greenlet-3.0.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eabe7090db68c981fca689299c2d116400b553f4b713266b130cfc9e2aa9c5a9"},
{file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f2f6d303f3dee132b322a14cd8765287b8f86cdc10d2cb6a6fae234ea488888e"},
{file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d923ff276f1c1f9680d32832f8d6c040fe9306cbfb5d161b0911e9634be9ef0a"},
{file = "greenlet-3.0.1-cp38-cp38-win32.whl", hash = "sha256:0b6f9f8ca7093fd4433472fd99b5650f8a26dcd8ba410e14094c1e44cd3ceddd"},
{file = "greenlet-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:990066bff27c4fcf3b69382b86f4c99b3652bab2a7e685d968cd4d0cfc6f67c6"},
{file = "greenlet-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ce85c43ae54845272f6f9cd8320d034d7a946e9773c693b27d620edec825e376"},
{file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ee2e967bd7ff85d84a2de09df10e021c9b38c7d91dead95b406ed6350c6997"},
{file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87c8ceb0cf8a5a51b8008b643844b7f4a8264a2c13fcbcd8a8316161725383fe"},
{file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6a8c9d4f8692917a3dc7eb25a6fb337bff86909febe2f793ec1928cd97bedfc"},
{file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fbc5b8f3dfe24784cee8ce0be3da2d8a79e46a276593db6868382d9c50d97b1"},
{file = "greenlet-3.0.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85d2b77e7c9382f004b41d9c72c85537fac834fb141b0296942d52bf03fe4a3d"},
{file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:696d8e7d82398e810f2b3622b24e87906763b6ebfd90e361e88eb85b0e554dc8"},
{file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:329c5a2e5a0ee942f2992c5e3ff40be03e75f745f48847f118a3cfece7a28546"},
{file = "greenlet-3.0.1-cp39-cp39-win32.whl", hash = "sha256:cf868e08690cb89360eebc73ba4be7fb461cfbc6168dd88e2fbbe6f31812cd57"},
{file = "greenlet-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ac4a39d1abae48184d420aa8e5e63efd1b75c8444dd95daa3e03f6c6310e9619"},
{file = "greenlet-3.0.1.tar.gz", hash = "sha256:816bd9488a94cba78d93e1abb58000e8266fa9cc2aa9ccdd6eb0696acb24005b"},
]
[package.extras]
docs = ["Sphinx"]
test = ["objgraph", "psutil"]
[[package]] [[package]]
name = "h11" name = "h11"
version = "0.14.0" version = "0.14.0"
@ -2015,6 +2085,92 @@ files = [
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
] ]
[[package]]
name = "sqlalchemy"
version = "2.0.22"
description = "Database Abstraction Library"
optional = false
python-versions = ">=3.7"
files = [
{file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f146c61ae128ab43ea3a0955de1af7e1633942c2b2b4985ac51cc292daf33222"},
{file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:875de9414393e778b655a3d97d60465eb3fae7c919e88b70cc10b40b9f56042d"},
{file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13790cb42f917c45c9c850b39b9941539ca8ee7917dacf099cc0b569f3d40da7"},
{file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04ab55cf49daf1aeb8c622c54d23fa4bec91cb051a43cc24351ba97e1dd09f5"},
{file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a42c9fa3abcda0dcfad053e49c4f752eef71ecd8c155221e18b99d4224621176"},
{file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14cd3bcbb853379fef2cd01e7c64a5d6f1d005406d877ed9509afb7a05ff40a5"},
{file = "SQLAlchemy-2.0.22-cp310-cp310-win32.whl", hash = "sha256:d143c5a9dada696bcfdb96ba2de4a47d5a89168e71d05a076e88a01386872f97"},
{file = "SQLAlchemy-2.0.22-cp310-cp310-win_amd64.whl", hash = "sha256:ccd87c25e4c8559e1b918d46b4fa90b37f459c9b4566f1dfbce0eb8122571547"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f6ff392b27a743c1ad346d215655503cec64405d3b694228b3454878bf21590"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f776c2c30f0e5f4db45c3ee11a5f2a8d9de68e81eb73ec4237de1e32e04ae81c"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8f1792d20d2f4e875ce7a113f43c3561ad12b34ff796b84002a256f37ce9437"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80eeb5189d7d4b1af519fc3f148fe7521b9dfce8f4d6a0820e8f5769b005051"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69fd9e41cf9368afa034e1c81f3570afb96f30fcd2eb1ef29cb4d9371c6eece2"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54bcceaf4eebef07dadfde424f5c26b491e4a64e61761dea9459103ecd6ccc95"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-win32.whl", hash = "sha256:7ee7ccf47aa503033b6afd57efbac6b9e05180f492aeed9fcf70752556f95624"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-win_amd64.whl", hash = "sha256:b560f075c151900587ade06706b0c51d04b3277c111151997ea0813455378ae0"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2c9bac865ee06d27a1533471405ad240a6f5d83195eca481f9fc4a71d8b87df8"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:625b72d77ac8ac23da3b1622e2da88c4aedaee14df47c8432bf8f6495e655de2"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b39a6e21110204a8c08d40ff56a73ba542ec60bab701c36ce721e7990df49fb9"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53a766cb0b468223cafdf63e2d37f14a4757476157927b09300c8c5832d88560"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0e1ce8ebd2e040357dde01a3fb7d30d9b5736b3e54a94002641dfd0aa12ae6ce"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:505f503763a767556fa4deae5194b2be056b64ecca72ac65224381a0acab7ebe"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-win32.whl", hash = "sha256:154a32f3c7b00de3d090bc60ec8006a78149e221f1182e3edcf0376016be9396"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-win_amd64.whl", hash = "sha256:129415f89744b05741c6f0b04a84525f37fbabe5dc3774f7edf100e7458c48cd"},
{file = "SQLAlchemy-2.0.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3940677d341f2b685a999bffe7078697b5848a40b5f6952794ffcf3af150c301"},
{file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55914d45a631b81a8a2cb1a54f03eea265cf1783241ac55396ec6d735be14883"},
{file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2096d6b018d242a2bcc9e451618166f860bb0304f590d205173d317b69986c95"},
{file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:19c6986cf2fb4bc8e0e846f97f4135a8e753b57d2aaaa87c50f9acbe606bd1db"},
{file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ac28bd6888fe3c81fbe97584eb0b96804bd7032d6100b9701255d9441373ec1"},
{file = "SQLAlchemy-2.0.22-cp37-cp37m-win32.whl", hash = "sha256:cb9a758ad973e795267da334a92dd82bb7555cb36a0960dcabcf724d26299db8"},
{file = "SQLAlchemy-2.0.22-cp37-cp37m-win_amd64.whl", hash = "sha256:40b1206a0d923e73aa54f0a6bd61419a96b914f1cd19900b6c8226899d9742ad"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3aa1472bf44f61dd27987cd051f1c893b7d3b17238bff8c23fceaef4f1133868"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:56a7e2bb639df9263bf6418231bc2a92a773f57886d371ddb7a869a24919face"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccca778c0737a773a1ad86b68bda52a71ad5950b25e120b6eb1330f0df54c3d0"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6c3e9350f9fb16de5b5e5fbf17b578811a52d71bb784cc5ff71acb7de2a7f9"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:564e9f9e4e6466273dbfab0e0a2e5fe819eec480c57b53a2cdee8e4fdae3ad5f"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af66001d7b76a3fab0d5e4c1ec9339ac45748bc4a399cbc2baa48c1980d3c1f4"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-win32.whl", hash = "sha256:9e55dff5ec115316dd7a083cdc1a52de63693695aecf72bc53a8e1468ce429e5"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-win_amd64.whl", hash = "sha256:4e869a8ff7ee7a833b74868a0887e8462445ec462432d8cbeff5e85f475186da"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9886a72c8e6371280cb247c5d32c9c8fa141dc560124348762db8a8b236f8692"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a571bc8ac092a3175a1d994794a8e7a1f2f651e7c744de24a19b4f740fe95034"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8db5ba8b7da759b727faebc4289a9e6a51edadc7fc32207a30f7c6203a181592"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b0b3f2686c3f162123adba3cb8b626ed7e9b8433ab528e36ed270b4f70d1cdb"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c1fea8c0abcb070ffe15311853abfda4e55bf7dc1d4889497b3403629f3bf00"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4bb062784f37b2d75fd9b074c8ec360ad5df71f933f927e9e95c50eb8e05323c"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-win32.whl", hash = "sha256:58a3aba1bfb32ae7af68da3f277ed91d9f57620cf7ce651db96636790a78b736"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-win_amd64.whl", hash = "sha256:92e512a6af769e4725fa5b25981ba790335d42c5977e94ded07db7d641490a85"},
{file = "SQLAlchemy-2.0.22-py3-none-any.whl", hash = "sha256:3076740335e4aaadd7deb3fe6dcb96b3015f1613bd190a4e1634e1b99b02ec86"},
{file = "SQLAlchemy-2.0.22.tar.gz", hash = "sha256:5434cc601aa17570d79e5377f5fd45ff92f9379e2abed0be5e8c2fba8d353d2b"},
]
[package.dependencies]
greenlet = {version = "!=0.4.17", optional = true, markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\" or extra == \"asyncio\""}
typing-extensions = ">=4.2.0"
[package.extras]
aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"]
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"]
asyncio = ["greenlet (!=0.4.17)"]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"]
mssql = ["pyodbc"]
mssql-pymssql = ["pymssql"]
mssql-pyodbc = ["pyodbc"]
mypy = ["mypy (>=0.910)"]
mysql = ["mysqlclient (>=1.4.0)"]
mysql-connector = ["mysql-connector-python"]
oracle = ["cx-oracle (>=7)"]
oracle-oracledb = ["oracledb (>=1.0.1)"]
postgresql = ["psycopg2 (>=2.7)"]
postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
postgresql-pg8000 = ["pg8000 (>=1.29.1)"]
postgresql-psycopg = ["psycopg (>=3.0.7)"]
postgresql-psycopg2binary = ["psycopg2-binary"]
postgresql-psycopg2cffi = ["psycopg2cffi"]
postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"]
pymysql = ["pymysql"]
sqlcipher = ["sqlcipher3-binary"]
[[package]] [[package]]
name = "tomli" name = "tomli"
version = "2.0.1" version = "2.0.1"
@ -2281,4 +2437,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.9,<3.12" python-versions = ">=3.9,<3.12"
content-hash = "84ec60771c14ef544d86eb389954a47c2767491c95ee63a0632aa5be68205468" content-hash = "353a8b2f960633927176885381eefa3eee9441798d53026556152a635f75cd10"

View file

@ -14,6 +14,7 @@ prisma = "^0.10.0"
mysql-connector-python = "^8.1.0" mysql-connector-python = "^8.1.0"
humanize = "^4.8.0" humanize = "^4.8.0"
pytube = "^15.0.0" pytube = "^15.0.0"
sqlalchemy = {extras = ["asyncio"], version = "^2.0.22"}
[tool.poetry.group.dev] [tool.poetry.group.dev]
optional = true optional = true