From 0d049e90b876f9e505d523990c2c1cbf6d425713 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Sat, 1 Jul 2023 10:50:24 +1000 Subject: [PATCH 01/48] Add Pyright instructions to the readme (#132) * Add Pyright instructions to the readme * added note about libraries with no stubs --------- Co-authored-by: andrewj-brown <92134285+andrewj-brown@users.noreply.github.com> --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 2a85e315..1785a2c9 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,26 @@ We use an automated code formatter called [Black](https://black.readthedocs.io/) poetry run black uqcsbot ``` +Individual files can also be styled with: + +```bash +poetry run pyright uqcsbot/file.py +``` + +## Static Type Checks + +We use [Pyright](https://github.com/microsoft/pyright) to perform static type checks; which all commits should pass. The exception list within `pyproject.toml` is only to be used for legacy code or libraries with no available typing stubs. We hope that all new cogs can be made to pass - if you're having trouble, ping us on discord and we'll give you a hand. To run Pyright, run from the root of the repo: + +```bash +poetry run pyright uqcsbot +``` + +Individual files can also be typechecked with: + +```bash +poetry run pyright uqcsbot/file.py +``` + ## Development Resources If this is your first time working on an open source project, we're here to walk you through every step of the way. From f8f4a271aea613d869e852f9cb173393b866eea8 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Mon, 3 Jul 2023 07:42:38 +1000 Subject: [PATCH 02/48] Added dominos coupons command (#128) Added the previously unimplemented dominoscoupons command. Returns a list of dominos coupons from couponese. --- uqcsbot/__main__.py | 1 + uqcsbot/dominos_coupons.py | 162 +++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 uqcsbot/dominos_coupons.py diff --git a/uqcsbot/__main__.py b/uqcsbot/__main__.py index 92342009..36c46757 100644 --- a/uqcsbot/__main__.py +++ b/uqcsbot/__main__.py @@ -43,6 +43,7 @@ async def main(): "basic", "cat", "cowsay", + "dominos_coupons", "error_handler", "events", "gaming", diff --git a/uqcsbot/dominos_coupons.py b/uqcsbot/dominos_coupons.py new file mode 100644 index 00000000..0553d94b --- /dev/null +++ b/uqcsbot/dominos_coupons.py @@ -0,0 +1,162 @@ +from datetime import datetime +from typing import List +import logging +import requests +from requests.exceptions import RequestException +from bs4 import BeautifulSoup + +import discord +from discord import app_commands +from discord.ext import commands + +from uqcsbot.bot import UQCSBot + + +MAX_COUPONS = 10 # Prevents abuse +COUPONESE_DOMINOS_URL = "https://www.couponese.com/store/dominos.com.au/" + + +class HTTPResponseException(Exception): + """ + An exception for when a HTTP response is not requests.codes.ok + """ + + def __init__(self, http_code: int, *args: object) -> None: + super().__init__(*args) + self.http_code = http_code + + +class DominosCoupons(commands.Cog): + def __init__(self, bot: UQCSBot): + self.bot = bot + + @app_commands.command() + @app_commands.describe( + number_of_coupons="The number of coupons to return. Defaults to 5 with max 10.", + ignore_expiry="Indicates to include coupons that have expired. Defaults to True.", + keywords="Words to search for within the coupon. All coupons descriptions will mention at least one keyword.", + ) + async def dominoscoupons( + self, + interaction: discord.Interaction, + number_of_coupons: int = 5, + ignore_expiry: bool = True, + keywords: str = "", + ): + """ + Returns a list of dominos coupons + """ + await interaction.response.defer(thinking=True) + + if number_of_coupons < 1 or number_of_coupons > MAX_COUPONS: + await interaction.edit_original_response( + content=f"You can't have that many coupons. Try a number between 1 and {MAX_COUPONS}." + ) + return + try: + coupons = _get_coupons(number_of_coupons, ignore_expiry, keywords.split()) + except RequestException as error: + logging.warning( + f"Could not connect to dominos coupon site ({COUPONESE_DOMINOS_URL}): {error.response.content}" + ) + await interaction.edit_original_response( + content=f"Sadly could not reach the coupon website (<{COUPONESE_DOMINOS_URL}>)..." + ) + return + except HTTPResponseException as error: + logging.warning( + f"Received a HTTP response code {error.http_code}. Error information: {error}" + ) + await interaction.edit_original_response( + content=f"Could not find the coupons on the coupon website (<{COUPONESE_DOMINOS_URL}>)..." + ) + return + + if not coupons: + await interaction.edit_original_response( + content=f"Could not find any coupons matching the given arguments from the coupon website (<{COUPONESE_DOMINOS_URL}>)." + ) + return + + embed = discord.Embed( + title="Domino's Coupons", + url=COUPONESE_DOMINOS_URL, + description=f"Keywords: {keywords}", + timestamp=datetime.now(), + ) + for coupon in coupons: + embed.add_field( + name=coupon.code, + value=f"{coupon.description} *[Expires: {coupon.expiry_date}]*", + inline=False, + ) + await interaction.edit_original_response(embed=embed) + + +class Coupon: + def __init__(self, code: str, expiry_date: str, description: str) -> None: + self.code = code + self.expiry_date = expiry_date + self.description = description + + def is_valid(self) -> bool: + try: + expiry_date = datetime.strptime(self.expiry_date, "%Y-%m-%d") + now = datetime.now() + return all( + [ + expiry_date.year >= now.year, + expiry_date.month >= now.month, + expiry_date.day >= now.day, + ] + ) + except ValueError: + return True + + def keyword_matches(self, keyword: str) -> bool: + return keyword.lower() in self.description.lower() + + +def _get_coupons(n: int, ignore_expiry: bool, keywords: List[str]) -> List[Coupon]: + """ + Returns a list of n Coupons + """ + + coupons = _get_coupons_from_page() + + if not ignore_expiry: + coupons = [coupon for coupon in coupons if coupon.is_valid()] + + if keywords: + coupons = [ + coupon + for coupon in coupons + if any(coupon.keyword_matches(keyword) for keyword in keywords) + ] + return coupons[:n] + + +def _get_coupons_from_page() -> List[Coupon]: + """ + Strips results from html page and returns a list of Coupon(s) + """ + http_response = requests.get(COUPONESE_DOMINOS_URL) + if http_response.status_code != requests.codes.ok: + raise HTTPResponseException(http_response.status_code) + soup = BeautifulSoup(http_response.content, "html.parser") + soup_coupons = soup.find_all(class_="ov-coupon") + + coupons: List[Coupon] = [] + + for soup_coupon in soup_coupons: + expiry_date_str = soup_coupon.find(class_="ov-expiry").get_text(strip=True) + description = soup_coupon.find(class_="ov-desc").get_text(strip=True) + code = soup_coupon.find(class_="ov-code").get_text(strip=True) + coupon = Coupon(code, expiry_date_str, description) + coupons.append(coupon) + + return coupons + + +async def setup(bot: UQCSBot): + await bot.add_cog(DominosCoupons(bot)) From 88bb705bc02bdec129059958ca2f996e8f4c3d54 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Thu, 13 Jul 2023 12:00:34 +1000 Subject: [PATCH 03/48] Dominos: Neatened embed (#136) * Dominos: Neatened embed * Dominos: Made error message ephemeral --- uqcsbot/dominos_coupons.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/uqcsbot/dominos_coupons.py b/uqcsbot/dominos_coupons.py index 0553d94b..8caad098 100644 --- a/uqcsbot/dominos_coupons.py +++ b/uqcsbot/dominos_coupons.py @@ -46,13 +46,14 @@ async def dominoscoupons( """ Returns a list of dominos coupons """ - await interaction.response.defer(thinking=True) - if number_of_coupons < 1 or number_of_coupons > MAX_COUPONS: - await interaction.edit_original_response( - content=f"You can't have that many coupons. Try a number between 1 and {MAX_COUPONS}." + await interaction.response.send_message( + content=f"You can't have that many coupons. Try a number between 1 and {MAX_COUPONS}.", + ephemeral=True, ) return + await interaction.response.defer(thinking=True) + try: coupons = _get_coupons(number_of_coupons, ignore_expiry, keywords.split()) except RequestException as error: @@ -81,7 +82,7 @@ async def dominoscoupons( embed = discord.Embed( title="Domino's Coupons", url=COUPONESE_DOMINOS_URL, - description=f"Keywords: {keywords}", + description=f"Keywords: *{keywords}*" if keywords else None, timestamp=datetime.now(), ) for coupon in coupons: From 852dda9766c47604f4e8b70cf234d17cd9e68b3a Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Thu, 13 Jul 2023 12:12:26 +1000 Subject: [PATCH 04/48] Added managecogs command (#131) * Added managecogs command * Formatted woth black * Added permission check for managecogs --------- Co-authored-by: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> --- uqcsbot/__main__.py | 1 + uqcsbot/manage_cogs.py | 50 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 uqcsbot/manage_cogs.py diff --git a/uqcsbot/__main__.py b/uqcsbot/__main__.py index 36c46757..13cc0b48 100644 --- a/uqcsbot/__main__.py +++ b/uqcsbot/__main__.py @@ -53,6 +53,7 @@ async def main(): "intros", "jobs_bulletin", "latex", + "manage_cogs", "member_counter", "minecraft", "phonetics", diff --git a/uqcsbot/manage_cogs.py b/uqcsbot/manage_cogs.py new file mode 100644 index 00000000..d702c7b1 --- /dev/null +++ b/uqcsbot/manage_cogs.py @@ -0,0 +1,50 @@ +from typing import Literal + +import discord +from discord import app_commands +from discord.ext import commands + + +class ManageCogs(commands.Cog): + """ + Note that most of these commands can make the bot load files to execute. Care should be made to ensure only entrusted users have access. + """ + + def __init__(self, bot: commands.Bot): + self.bot = bot + + @app_commands.command(name="managecogs") + @app_commands.default_permissions(manage_guild=True) + @app_commands.describe( + cog='The cog (i.e. python file) to try to unload. Use python package notation, so no suffix of ".py" and "." between folders: e.g. "manage_cogs".', + ) + async def manage_cogs( + self, + interaction: discord.Interaction, + action: Literal["load", "unload", "reload"], + cog: str, + ): + """ + Trys to unload a cog (i.e. python file). + Note that most of these commands can make the bot load files to execute. Care should be made to ensure only entrusted users have access. + """ + try: + match action: + case "load": + await self.bot.load_extension(f"uqcsbot.{cog}") + case "unload": + await self.bot.unload_extension(f"uqcsbot.{cog}") + case "reload": + await self.bot.reload_extension(f"uqcsbot.{cog}") + except Exception as error: + # Many errors can be caught during loading/unloading/reloading the bot, so it would be painful to separate by exception type + await interaction.response.send_message( + f"Error occured {action}ing {cog}: {error}" + ) + return + await interaction.response.send_message(f"Successfully {action}ed {cog}") + await self.bot.tree.sync() + + +async def setup(bot: commands.Bot): + await bot.add_cog(ManageCogs(bot)) From 689af81d407827026b1372cba443b4feec108e43 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Thu, 13 Jul 2023 12:16:19 +1000 Subject: [PATCH 05/48] Added /pastexams (#129) * Added past exams command * Past Exams: Removed debug logging and ran black * Past exams: Fixed typing * Past Exams: Added an embed * Pastexams: Added footer about UQ SSO * Pastexams: Fixed formatting * Past Exams: Neatened up embed with markdown links --------- Co-authored-by: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> --- uqcsbot/__main__.py | 1 + uqcsbot/past_exams.py | 109 +++++++++++++++++++++++++++++++ uqcsbot/utils/uq_course_utils.py | 49 +++++++++++++- 3 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 uqcsbot/past_exams.py diff --git a/uqcsbot/__main__.py b/uqcsbot/__main__.py index 13cc0b48..b91d4f1d 100644 --- a/uqcsbot/__main__.py +++ b/uqcsbot/__main__.py @@ -56,6 +56,7 @@ async def main(): "manage_cogs", "member_counter", "minecraft", + "past_exams", "phonetics", "remindme", "snailrace", diff --git a/uqcsbot/past_exams.py b/uqcsbot/past_exams.py new file mode 100644 index 00000000..b51f032d --- /dev/null +++ b/uqcsbot/past_exams.py @@ -0,0 +1,109 @@ +from typing import Optional, Literal +import logging +from random import choice + +import discord +from discord import app_commands +from discord.ext import commands + +from uqcsbot.utils.uq_course_utils import ( + get_past_exams, + get_past_exams_page_url, + HttpException, +) + +SemesterType = Optional[Literal["Sem 1", "Sem 2", "Summer"]] + + +class PastExams(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + + @app_commands.command() + @app_commands.describe( + course_code="The course to find a past exam for.", + year="The year to find exams for. Leave blank for all years.", + semester="The semester to find exams for. Leave blank for all semesters.", + random_exam="Whether to select a single random exam.", + ) + async def pastexams( + self, + interaction: discord.Interaction, + course_code: str, + year: Optional[int] = None, + semester: SemesterType = None, + random_exam: bool = False, + ): + """ + Returns a list of past exams, or, if specified, a past exam for a specific year. + """ + await interaction.response.defer(thinking=True) + + try: + past_exams = get_past_exams(course_code) + except HttpException as exception: + logging.warning( + f"Received a HTTP response code {exception.status_code}. Error information: {exception.message}" + ) + await interaction.edit_original_response( + content=f"Could not successfully contact UQ for past exams." + ) + return + if not past_exams: + await interaction.edit_original_response( + content=f"No past exams could be found for {course_code}." + ) + return + + if semester: + past_exams = list( + filter(lambda exam: exam.semester == semester, past_exams) + ) + if year: + past_exams = list(filter(lambda exam: exam.year == year, past_exams)) + + if not past_exams: + await interaction.edit_original_response( + content=f"No past exams could be found for {course_code} matching your specifications." + ) + return + + if random_exam: + past_exams = [choice(past_exams)] + + if len(past_exams) == 1: + exam = past_exams[0] + embed = discord.Embed( + title=f"Past exam for {course_code.upper()}", + description=f"[{exam.year} {exam.semester}]({exam.link})", + ) + embed.set_footer( + text="The above link will require a UQ SSO login to access." + ) + await interaction.edit_original_response(embed=embed) + return + + description = "" + if year is not None or semester is not None: + description = "Only showing exams for " + " ".join( + str(restriction) + for restriction in [year, semester] + if restriction is not None + ) + embed = discord.Embed( + title=f"Past exams for {course_code.upper()}", + url=get_past_exams_page_url(course_code), + description=description, + ) + for exam in past_exams: + embed.add_field( + name="", + value=f"[{exam.year} {exam.semester}]({exam.link})", + inline=True, + ) + embed.set_footer(text="The above links will require a UQ SSO login to access.") + await interaction.edit_original_response(embed=embed) + + +async def setup(bot: commands.Bot): + await bot.add_cog(PastExams(bot)) diff --git a/uqcsbot/utils/uq_course_utils.py b/uqcsbot/utils/uq_course_utils.py index b5f79927..cd06a944 100644 --- a/uqcsbot/utils/uq_course_utils.py +++ b/uqcsbot/utils/uq_course_utils.py @@ -5,7 +5,8 @@ from bs4 import BeautifulSoup from functools import partial from binascii import hexlify -from typing import List, Dict, Optional +from typing import List, Dict, Optional, Iterable +import json BASE_COURSE_URL = "https://my.uq.edu.au/programs-courses/course.html?course_code=" BASE_ASSESSMENT_URL = ( @@ -14,6 +15,7 @@ ) BASE_CALENDAR_URL = "http://www.uq.edu.au/events/calendar_view.php?category_id=16&year=" OFFERING_PARAMETER = "offer" +BASE_PAST_EXAMS_URL = "https://api.library.uq.edu.au/v1/exams/search/" class DateSyntaxException(Exception): @@ -255,3 +257,48 @@ def get_parsed_assessment_item(assessment_item): # Thus, this bit of code will keep only the weight portion of the field. weight = get_element_inner_html(weight).strip().split("
")[0] return (course_name, task, due_date, weight) + + +class Exam: + """ + Stores the information of a past exam, including its year, semester and link. + """ + + def __init__(self, year: int, semester: str, link: str) -> None: + self.year = year + self.semester = semester + self.link = link + + +def get_past_exams_page_url(course_code: str) -> str: + """ + Returns the URL of the UQ library past exam page + """ + return BASE_PAST_EXAMS_URL + course_code + + +def get_past_exams(course_code: str) -> List[Exam]: + """ + Takes the course code and generates each result in the format: + ('year Sem X:', link) + """ + url = get_past_exams_page_url(course_code) + http_response = requests.get(url) + if http_response.status_code != requests.codes.ok: + raise HttpException(url, http_response.status_code) + # The UQ library API has some funky nested lists within the output, so there will be a a few "[0]" lying about + exam_list_json = json.loads(http_response.content)["papers"] + + # Check if the course code exists + if not exam_list_json: + return [] + exam_list_json = exam_list_json[0] + + exam_list = [] + for exam_json in exam_list_json: + year = int(exam_json[0]["examYear"]) + # Semesters are given as "Sem.1", so we will change this to "Sem 1" + semester = exam_json[0]["examPeriod"].replace(".", " ") + link = exam_json[0]["paperUrl"] + exam_list.append(Exam(year, semester, link)) + return exam_list From 77904eb539f6f12cd1459a3cb501ff4ef29721ca Mon Sep 17 00:00:00 2001 From: Iain Jensen Date: Thu, 13 Jul 2023 12:27:22 +1000 Subject: [PATCH 06/48] Add morse command (#117) * Adds .env file with bot token * Adds basic functionality of converting letters to morse code * adds vscode settings.json to gitignore * Touch ups to provide proper spacing. * putting .env.example back * Adds formatting changes * Fixes Andrew's review. gitignore ignores the whole vscode folder, removes merge artifacts, deletes from unimplemented, changes name to UpperCamel, removes all back ticks instead of replacing 3 with 3 apostrophes * Morse Removed unnecessary imports. * Adds types for check adn encrypt. Changes class name to Morse and changes relevant references too. * Remove redundant SB_RATELIMIT in env ex. fixes order of morse in cogs. * Add return type to check and encrypt. Changes fallback return to "" to reflect this. Check on check return is also hence changed from != 0 to != "" * makes the regex a regex --------- Co-authored-by: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> --- .gitignore | 1 + uqcsbot/__main__.py | 1 + uqcsbot/morse.py | 159 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 uqcsbot/morse.py diff --git a/.gitignore b/.gitignore index 17163edb..a1eff683 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,4 @@ dmypy.json .vim *.db **/.DS_STORE +.vscode/* diff --git a/uqcsbot/__main__.py b/uqcsbot/__main__.py index b91d4f1d..f2c303b0 100644 --- a/uqcsbot/__main__.py +++ b/uqcsbot/__main__.py @@ -56,6 +56,7 @@ async def main(): "manage_cogs", "member_counter", "minecraft", + "morse", "past_exams", "phonetics", "remindme", diff --git a/uqcsbot/morse.py b/uqcsbot/morse.py new file mode 100644 index 00000000..22022cca --- /dev/null +++ b/uqcsbot/morse.py @@ -0,0 +1,159 @@ +from typing import List + +import re + +import discord +from discord import app_commands +from discord.ext import commands + +from uqcsbot.bot import UQCSBot + +# value of all valid ascii values in morse code +MorseCodeDict = { + "A": ". - ", + "B": "- . . . ", + "C": "- . - . ", + "D": "- . . ", + "E": ". ", + "F": ". . - . ", + "G": "- - . ", + "H": ". . . . ", + "I": ". . ", + "J": ". - - - ", + "K": "- . - ", + "L": ". - . . ", + "M": "- - ", + "N": "- . ", + "O": "- - - ", + "P": ". - - . ", + "Q": "- - . - ", + "R": ". - . ", + "S": ". . . ", + "T": "- ", + "U": ". . - ", + "V": ". . . - ", + "W": ". - - ", + "X": "- . . - ", + "Y": "- . - - ", + "Z": "- - . . ", + "1": ". - - - - ", + "2": ". . - - - ", + "3": ". . . - - ", + "4": ". . . . - ", + "5": ". . . . . ", + "6": "- . . . . ", + "7": "- - . . . ", + "8": "- - - . . ", + "9": "- - - - . ", + "0": "- - - - - ", + ",": "- - . . - - ", + ".": ". - . - . - ", + "?": ". . - - . . ", + "/": "- . . - . ", + "-": "- . . . . - ", + "(": "- . - - . ", + ")": "- . - - . - ", + " ": " ", + ";": "_ . _ . _ . ", + ":": "_ _ _ . . .", + "'": ". _ _ _ _ . ", + '"': ". _ . . _ . ", + "_": ". . _ _ . _ ", + "=": "_ . . . _ ", + "+": ". _ . _ . ", +} + + +class Morse(commands.Cog): + def __init__(self, bot: UQCSBot): + self.bot = bot + + @app_commands.command(name="morse") + @app_commands.describe(message="The message to be converted to morse code") + async def morse_command( + self, interaction: discord.Interaction, message: str + ) -> None: + """ + Returns a string containing the argument converted to dots and dashes. + """ + + # Sanitise invalid characters from the message + message = Morse.sanitise_illegals(message) + + # Sanitise message for discord emotes + message = Morse.sanitise_emotes(message) + + # Convert all chars to lower case + message = message.upper() + + # Then check they are valid morse code ascii + invalid = Morse.check(message) + if invalid != "": + await interaction.response.send_message( + f"Invalid morse code character/s in string: {invalid}" + ) + return + + # encrypt message + response = Morse.encrypt_to_morse(message) + + # Check if response is too long + if len(response) > 2000: + await interaction.response.send_message("Resulting message too long!") + return + + await interaction.response.send_message(response) + + @staticmethod + def sanitise_illegals(message: str) -> str: + """ + Replaces all illegal characters in the message with their sanitised + equivalent. + """ + + # Strip whitespace from either side of the message + message = message.strip() + + # replace code blocks with their sanitised equivalent + message = message.replace("`", "") + + return message + + @staticmethod + def sanitise_emotes(message: str) -> str: + """ + Replaces all emotes in the message with their emote name instead of + their id. + """ + + # Regex to match emotes. + emotes: List[str] = re.findall(r"", message) + + # Replace each emote with its name. + for emote in emotes: + emote_name: str = emote.split(":")[1].strip() + message = message.replace(emote, f":{emote_name}:") + + return message + + @staticmethod + def check(message: str) -> str: + ret = "" + for letter in message: + if letter not in MorseCodeDict: + ret += letter + if len(ret) != 0: + return ret + return "" + + @staticmethod + def encrypt_to_morse(message: str) -> str: + cipher = "" + for letter in message: + cipher += MorseCodeDict[letter] + " " + return cipher + + +async def setup(bot: UQCSBot): + cog = Morse(bot) + await bot.add_cog(cog) From 8a044d8df617412c5c27aa55f20c7dc52373c827 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Thu, 13 Jul 2023 17:16:27 +1000 Subject: [PATCH 07/48] Added semester, campus and mode options to /whatsdue (#125) * Fixed bug where workingon calls same person twice * Fixed blank output for empty whatsdue * Revert "Fixed bug where workingon calls same person twice" This reverts commit 7060da26baae79aad5981afc4c7ed768c1a1e159. * Whatsdue: Updated formatting for black * Added semester, mode and campus options to whatsdue * Added more campuses and modes to whatsdue * Fixed formatting * whatsdue: changed to embed * Whatsdue: Fixed typing * Whatsdue: added intensive mode Also replaced binascii andmodified the embed to emphasise that the list of assessment may not be correct. * Fixed typing --- pyproject.toml | 2 - uqcsbot/utils/uq_course_utils.py | 224 ++++++++++++++++++++++++------- uqcsbot/whatsdue.py | 60 ++++++--- 3 files changed, 215 insertions(+), 71 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a815ef9a..2ecc96c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,9 +51,7 @@ exclude = [ "**/snailrace.py", "**/starboard.py", "**/uptime.py", - "**/whatsdue.py", "**/working_on.py", "**/utils/command_utils.py", "**/utils/snailrace_utils.py", - "**/utils/uq_course_utils.py" ] \ No newline at end of file diff --git a/uqcsbot/utils/uq_course_utils.py b/uqcsbot/utils/uq_course_utils.py index cd06a944..a4c2d9db 100644 --- a/uqcsbot/utils/uq_course_utils.py +++ b/uqcsbot/utils/uq_course_utils.py @@ -2,10 +2,9 @@ from requests.exceptions import RequestException from datetime import datetime from dateutil import parser -from bs4 import BeautifulSoup +from bs4 import BeautifulSoup, element from functools import partial -from binascii import hexlify -from typing import List, Dict, Optional, Iterable +from typing import List, Dict, Optional, Literal, Tuple import json BASE_COURSE_URL = "https://my.uq.edu.au/programs-courses/course.html?course_code=" @@ -18,12 +17,100 @@ BASE_PAST_EXAMS_URL = "https://api.library.uq.edu.au/v1/exams/search/" +class Offering: + """ + A semester, campus and mode (e.g. Internal) that many courses occur within + """ + + CampusType = Literal["St Lucia", "Gatton", "Herston"] + # The codes used internally within UQ systems + campus_codes: Dict[CampusType, str] = { + "St Lucia": "STLUC", + "Gatton": "GATTN", + "Herston": "HERST", + } + + ModeType = Literal["Internal", "External", "Flexible Delivery", "Intensive"] + # The codes used internally within UQ systems + mode_codes: Dict[ModeType, str] = { + "Internal": "IN", + "External": "EX", + "Flexible Delivery": "FD", + "Intensive": "IT", + } + + SemesterType = Literal["1", "2", "Summer"] + semester_codes: Dict[SemesterType, int] = {"1": 1, "2": 2, "Summer": 3} + + semester: SemesterType + campus: CampusType + mode: ModeType + + def __init__( + self, + semester: Optional[SemesterType], + campus: CampusType = "St Lucia", + mode: ModeType = "Internal", + ): + """ + semester defaults to the current semester if None + """ + if semester is not None: + self.semester = semester + else: + self.semester = self._estimate_current_semester() + self.semester + self.campus = campus + self.mode = mode + + def get_semester_code(self) -> int: + """ + Returns the code used interally within UQ for the semester of the offering. + """ + return self.semester_codes[self.semester] + + def get_campus_code(self) -> str: + """ + Returns the code used interally within UQ for the campus of the offering. + """ + self.campus + return self.campus_codes[self.campus] + + def get_mode_code(self) -> str: + """ + Returns the code used interally within UQ for the mode of the offering. + """ + return self.mode_codes[self.mode] + + def get_offering_code(self) -> str: + """ + Returns the hex encoded offering string (containing all offering information) for the offering. + """ + offering_code_text = ( + f"{self.get_campus_code()}{self.get_semester_code()}{self.get_mode_code()}" + ) + return offering_code_text.encode("utf-8").hex() + + @staticmethod + def _estimate_current_semester() -> SemesterType: + """ + Returns an estimate of the current semester (represented by an integer) based on the current month. 3 represents summer semester. + """ + current_month = datetime.today().month + if 2 <= current_month <= 6: + return "1" + elif 7 <= current_month <= 11: + return "2" + else: + return "Summer" + + class DateSyntaxException(Exception): """ Raised when an unparsable date syntax is encountered. """ - def __init__(self, date, course_name): + def __init__(self, date: str, course_name: str): self.message = f"Could not parse date '{date}' for course '{course_name}'." self.date = date self.course_name = course_name @@ -35,7 +122,7 @@ class CourseNotFoundException(Exception): Raised when a given course cannot be found for UQ. """ - def __init__(self, course_name): + def __init__(self, course_name: str): self.message = f"Could not find course '{course_name}'." self.course_name = course_name super().__init__(self.message, self.course_name) @@ -46,10 +133,30 @@ class ProfileNotFoundException(Exception): Raised when a profile cannot be found for a given course. """ - def __init__(self, course_name): - self.message = f"Could not find profile for course '{course_name}'." + def __init__(self, course_name: str, offering: Optional[Offering] = None): + if offering is None: + self.message = f"Could not find profile for course '{course_name}'. This profile may not be out yet." + else: + self.message = f"Could not find profile for course '{course_name}' during semester {offering.semester} at {offering.campus} done in mode '{offering.mode}'. This profile may not be out yet." self.course_name = course_name - super().__init__(self.message, self.course_name) + self.offering = offering + super().__init__(self.message, self.course_name, self.offering) + + +class AssessmentNotFoundException(Exception): + """ + Raised when the assessment table cannot be found for assess page. + """ + + def __init__(self, course_names: List[str], offering: Optional[Offering] = None): + if offering is None: + self.message = ( + f"Could not find the assessment table for '{', '.join(course_names)}'." + ) + else: + self.message = f"Could not find the assessment table for '{', '.join(course_names)}' during semester {offering.semester} at {offering.campus} done in mode '{offering.mode}'." + self.course_names = course_names + super().__init__(self.message, self.course_names) class HttpException(Exception): @@ -58,29 +165,13 @@ class HttpException(Exception): unsuccessful (i.e. not 200 OK) status code. """ - def __init__(self, url, status_code): + def __init__(self, url: str, status_code: int): self.message = f"Received status code {status_code} from '{url}'." self.url = url self.status_code = status_code super().__init__(self.message, self.url, self.status_code) -def get_offering_code(semester=None, campus="STLUC", is_internal=True): - """ - Returns the hex encoded offering string for the given semester and campus. - - Keyword Arguments: - semester {str} -- Semester code (3 is summer) (default: current semester) - campus {str} -- Campus code string (one of STLUC, etc.) - is_internal {bool} -- True for internal, false for external. - """ - # TODO: Codes for other campuses. - if semester is None: - semester = 1 if datetime.today().month <= 6 else 2 - location = "IN" if is_internal else "EX" - return hexlify(f"{campus}{semester}{location}".encode("utf-8")).decode("utf-8") - - def get_uq_request( url: str, params: Optional[Dict[str, str]] = None ) -> requests.Response: @@ -100,30 +191,54 @@ def get_uq_request( raise HttpException(message, 500) -def get_course_profile_url(course_name): +def get_course_profile_url( + course_name: str, offering: Optional[Offering] = None +) -> str: """ - Returns the URL to the latest course profile for the given course. + Returns the URL to the course profile for the given course for a given offering. + If no offering is give, will return the first course profile on the course page. """ - course_url = BASE_COURSE_URL + course_name - http_response = get_uq_request( - course_url, params={OFFERING_PARAMETER: get_offering_code()} - ) + if offering is None: + course_url = BASE_COURSE_URL + course_name + else: + course_url = ( + BASE_COURSE_URL + + course_name + + "&" + + OFFERING_PARAMETER + + "=" + + offering.get_offering_code() + ) + + http_response = get_uq_request(course_url) if http_response.status_code != requests.codes.ok: raise HttpException(course_url, http_response.status_code) html = BeautifulSoup(http_response.content, "html.parser") if html.find(id="course-notfound"): raise CourseNotFoundException(course_name) - profile = html.find("a", class_="profile-available") - if profile is None: - raise ProfileNotFoundException(course_name) - return profile.get("href") + if offering is None: + profile = html.find("a", class_="profile-available") + else: + # The profile row on the course page that corresponds to the given offering + table_row = html.find("tr", class_="current") + if not isinstance(table_row, element.Tag): + raise ProfileNotFoundException(course_name, offering) + profile = table_row.find("a", class_="profile-available") + + if not isinstance(profile, element.Tag): + raise ProfileNotFoundException(course_name, offering) + url = profile.get("href") + if not isinstance(url, str): + raise ProfileNotFoundException(course_name, offering) + return url -def get_course_profile_id(course_name): + +def get_course_profile_id(course_name: str, offering: Optional[Offering]): """ Returns the ID to the latest course profile for the given course. """ - profile_url = get_course_profile_url(course_name) + profile_url = get_course_profile_url(course_name, offering=offering) # The profile url looks like this # https://course-profiles.uq.edu.au/student_section_loader/section_1/100728 return profile_url[profile_url.rindex("/") + 1 :] @@ -155,7 +270,7 @@ def get_current_exam_period(): return start_datetime, end_datetime -def get_parsed_assessment_due_date(assessment_item): +def get_parsed_assessment_due_date(assessment_item: Tuple[str, str, str, str]): """ Returns the parsed due date for the given assessment item as a datetime object. If the date cannot be parsed, a DateSyntaxException is raised. @@ -178,13 +293,13 @@ def get_parsed_assessment_due_date(assessment_item): raise DateSyntaxException(due_date, course_name) -def is_assessment_after_cutoff(assessment, cutoff): +def is_assessment_after_cutoff(assessment: Tuple[str, str, str, str], cutoff: datetime): """ Returns whether the assessment occurs after the given cutoff. """ try: start_datetime, end_datetime = get_parsed_assessment_due_date(assessment) - except DateSyntaxException as e: + except DateSyntaxException: # TODO bot.logger.error(e.message) # If we can't parse a date, we're better off keeping it just in case. # TODO(mitch): Keep track of these instances to attempt to accurately @@ -193,29 +308,40 @@ def is_assessment_after_cutoff(assessment, cutoff): return end_datetime >= cutoff if end_datetime else start_datetime >= cutoff -def get_course_assessment_page(course_names: List[str]) -> str: +def get_course_assessment_page( + course_names: List[str], offering: Optional[Offering] +) -> str: """ Determines the course ids from the course names and returns the url to the assessment table for the provided courses """ - profile_ids = map(get_course_profile_id, course_names) + profile_ids = map( + lambda course: get_course_profile_id(course, offering=offering), course_names + ) return BASE_ASSESSMENT_URL + ",".join(profile_ids) -def get_course_assessment(course_names, cutoff=None, assessment_url=None): +def get_course_assessment( + course_names: List[str], + cutoff: Optional[datetime] = None, + assessment_url: Optional[str] = None, + offering: Optional[Offering] = None, +) -> List[Tuple[str, str, str, str]]: """ Returns all the course assessment for the given courses that occur after the given cutoff. """ if assessment_url is None: - joined_assessment_url = get_course_assessment_page(course_names) + joined_assessment_url = get_course_assessment_page(course_names, offering) else: joined_assessment_url = assessment_url - http_response = get_uq_request(joined_assessment_url) + http_response = get_uq_request(joined_assessment_url) if http_response.status_code != requests.codes.ok: raise HttpException(joined_assessment_url, http_response.status_code) html = BeautifulSoup(http_response.content, "html.parser") assessment_table = html.find("table", class_="tblborder") + if not isinstance(assessment_table, element.Tag): + raise AssessmentNotFoundException(course_names, offering) # Start from 1st index to skip over the row containing column names. assessment = assessment_table.findAll("tr")[1:] parsed_assessment = map(get_parsed_assessment_item, assessment) @@ -226,14 +352,16 @@ def get_course_assessment(course_names, cutoff=None, assessment_url=None): return list(filtered_assessment) -def get_element_inner_html(dom_element): +def get_element_inner_html(dom_element: element.Tag): """ Returns the inner html for the given element. """ return dom_element.decode_contents(formatter="html") -def get_parsed_assessment_item(assessment_item): +def get_parsed_assessment_item( + assessment_item: element.Tag, +) -> Tuple[str, str, str, str]: """ Returns the parsed assessment details for the given assessment item table row element. @@ -294,7 +422,7 @@ def get_past_exams(course_code: str) -> List[Exam]: return [] exam_list_json = exam_list_json[0] - exam_list = [] + exam_list: List[Exam] = [] for exam_json in exam_list_json: year = int(exam_json[0]["examYear"]) # Semesters are given as "Sem.1", so we will change this to "Sem 1" diff --git a/uqcsbot/whatsdue.py b/uqcsbot/whatsdue.py index 8c418289..79b1b790 100644 --- a/uqcsbot/whatsdue.py +++ b/uqcsbot/whatsdue.py @@ -7,6 +7,7 @@ from discord.ext import commands from uqcsbot.utils.uq_course_utils import ( + Offering, CourseNotFoundException, HttpException, ProfileNotFoundException, @@ -19,18 +20,13 @@ class WhatsDue(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot - def get_formatted_assessment_item(self, assessment_item): - """ - Returns the given assessment item in a pretty - message format to display to a user. - """ - course, task, due, weight = assessment_item - return f"**{course}**: `{weight}` *{task}* **({due})**" - @app_commands.command() @app_commands.describe( fulloutput="Display the full list of assessment. Defaults to False, which only " - + "shows assessment due from today onwards", + + "shows assessment due from today onwards.", + semester="The semester to get assessment for. Defaults to what UQCSbot believes is the current semester.", + campus="The campus the course is held at. Defaults to St Lucia. Note that many external courses are 'hosted' at St Lucia.", + mode="The mode of the course. Defaults to Internal.", course1="Course code", course2="Course code", course3="Course code", @@ -47,7 +43,10 @@ async def whatsdue( course4: Optional[str], course5: Optional[str], course6: Optional[str], - fulloutput: Optional[bool] = False, + fulloutput: bool = False, + semester: Optional[Offering.SemesterType] = None, + campus: Offering.CampusType = "St Lucia", + mode: Offering.ModeType = "Internal", ): """ Returns all the assessment for a given list of course codes that are scheduled to occur. @@ -57,12 +56,13 @@ async def whatsdue( await interaction.response.defer(thinking=True) possible_courses = [course1, course2, course3, course4, course5, course6] - course_names = [c for c in possible_courses if c != None] + course_names = [c.upper() for c in possible_courses if c != None] + offering = Offering(semester=semester, campus=campus, mode=mode) # If full output is not specified, set the cutoff to today's date. cutoff = None if fulloutput else datetime.today() try: - asses_page = get_course_assessment_page(course_names) + asses_page = get_course_assessment_page(course_names, offering) assessment = get_course_assessment(course_names, cutoff, asses_page) except HttpException as e: logging.error(e.message) @@ -74,18 +74,36 @@ async def whatsdue( await interaction.edit_original_response(content=e.message) return - message = ( - "_*WARNING:* Assessment information may vary/change/be entirely" - + " different! Use at your own discretion_\n> " + embed = discord.Embed( + title=f"What's Due: {', '.join(course_names)}", + url=asses_page, + description="*WARNING: Assessment information may vary/change/be entirely different! Use at your own discretion. Check your ECP for a true list of assessment.*", ) - message += "\n> ".join(map(self.get_formatted_assessment_item, assessment)) + if assessment: + for assessment_item in assessment: + course, task, due, weight = assessment_item + embed.add_field( + name=course, + value=f"`{weight}` {task} **({due})**", + inline=False, + ) + elif fulloutput: + embed.add_field( + name="", + value=f"No assessment items could be found", + ) + else: + embed.add_field( + name="", + value=f"Nothing seems to be due soon", + ) + if not fulloutput: - message += ( - "\n_Note: This may not be the full assessment list. Set fulloutput" - + "to True for the full list._" + embed.set_footer( + text="Note: This may not be the full assessment list. Set fulloutput to True to see a potentially more complete list, or check your ECP for a true list of assessment." ) - message += f"\nLink to assessment page <{asses_page}|here>" - await interaction.edit_original_response(content=message) + + await interaction.edit_original_response(embed=embed) async def setup(bot: commands.Bot): From 69f514d0b75878854374f5d8dc6b1d0b34b0cfce Mon Sep 17 00:00:00 2001 From: Limao Chang <80520563+LimaoC@users.noreply.github.com> Date: Fri, 14 Jul 2023 12:12:52 +1000 Subject: [PATCH 08/48] Fix admin/committee ping permissions, fix timezone issues (#119) * Fix admin/committee ping permissions, fix timezone issues * Make black happy * Change role-based permissions to guild-based permissions for remindme * Require manage_events permission to bypass reminder limit --------- Co-authored-by: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> --- uqcsbot/remindme.py | 93 +++++++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/uqcsbot/remindme.py b/uqcsbot/remindme.py index a93d5cdc..3b545792 100644 --- a/uqcsbot/remindme.py +++ b/uqcsbot/remindme.py @@ -5,6 +5,7 @@ from functools import partial import logging from typing import List, NamedTuple, Optional, Union +from zoneinfo import ZoneInfo from uqcsbot.bot import UQCSBot from uqcsbot.models import Reminders @@ -226,10 +227,16 @@ def _reached_reminder_limit( ) -> bool: """Returns whether the given user has reached the maximum reminder limit.""" # a slight caveat is that if a user sends a command to the bot in DMs, it won't be able to - # check for admin perms, so the reminder limit will be enforced - if isinstance(user, discord.Member) and user.guild_permissions.administrator: - return False - return len(self._get_user_reminders(user.id)) >= USER_REMINDER_LIMIT + # check for the correct perms, so the reminder limit will be enforced + if ( + self.bot.uqcs_server != None + and (member := self.bot.uqcs_server.get_member(user.id)) != None + ): + # manage_event perms: for committee use. + if member.guild_permissions.manage_events: + return False + return len(self._get_user_reminders(user.id)) >= USER_REMINDER_LIMIT + return True def _schedule_reminder(self, reminder: Reminder): """Schedules the reminder to be sent at its specified time (or its next recurring time)""" @@ -314,44 +321,42 @@ async def _process_reminder(self, reminder: Reminder): Sends the given reminder, and schedules any future reminders if it is recurring and hasn't ended yet. Otherwise, removes the reminder from the database. """ - if (user := self.bot.get_user(reminder.user_id)) != None: - ctx = None - if reminder.channel_id == None: # send in DMs - ctx = user - elif isinstance( - channel := self.bot.get_channel(reminder.channel_id), - discord.TextChannel, - ): - # send in server channel, if it is a text channel - ctx = channel - - if ctx != None: - if ( - isinstance(user, discord.Member) - and user.guild_permissions.administrator + if self.bot.uqcs_server != None: + if (member := self.bot.uqcs_server.get_member(reminder.user_id)) != None: + ctx = None + if reminder.channel_id == None: # send in DMs + ctx = member + elif isinstance( + channel := self.bot.get_channel(reminder.channel_id), + discord.TextChannel, ): - allowed_mentions = discord.AllowedMentions.all() + # send in server channel, if it is a text channel + ctx = channel + + if ctx != None: + if member.guild_permissions.mention_everyone: + allowed_mentions = discord.AllowedMentions.all() + else: + allowed_mentions = discord.AllowedMentions(users=[member]) + await ctx.send( + REMINDER_MESSAGE.format(reminder.user_id, reminder.message), + allowed_mentions=allowed_mentions, + ) else: - allowed_mentions = discord.AllowedMentions(users=[user]) - await ctx.send( - REMINDER_MESSAGE.format(reminder.user_id, reminder.message), - allowed_mentions=allowed_mentions, - ) - else: - logging.warning( - f"Reminder couldn't be sent to channel with id {reminder.channel_id}; not a text channel" - ) - else: - logging.warning(f"User with id {reminder.user_id} couldn't be found") - - if reminder.week_frequency == None: # one-time reminder, remove from db - self._remove_reminder_from_db(reminder.id) - else: # recurring reminder - # check if we need to schedule reminder again - if reminder.end_date == None or reminder.end_date > dt.date.today(): - self._schedule_reminder(reminder) + logging.warning( + f"Reminder couldn't be sent to channel with id {reminder.channel_id}; not a text channel" + ) else: + logging.warning(f"User with id {reminder.user_id} couldn't be found") + + if reminder.week_frequency == None: # one-time reminder, remove from db self._remove_reminder_from_db(reminder.id) + else: # recurring reminder + # check if we need to schedule reminder again + if reminder.end_date == None or reminder.end_date > dt.date.today(): + self._schedule_reminder(reminder) + else: + self._remove_reminder_from_db(reminder.id) @remindme_group.command(name="add") @app_commands.describe( @@ -374,13 +379,19 @@ async def add_reminder( # check datetime is valid try: check_time = dt.time.fromisoformat(time) - check_date = dt.date.fromisoformat(date) if date else dt.date.today() - check_datetime = dt.datetime.combine(check_date, check_time) + check_date = ( + dt.date.fromisoformat(date) + if date + else dt.datetime.today().replace(tzinfo=ZoneInfo("Australia/Brisbane")) + ) + check_datetime = dt.datetime.combine( + check_date, check_time, tzinfo=ZoneInfo("Australia/Brisbane") + ) except ValueError: embed = _error_embed(DATETIME_VALID_FORMAT_ERR) return await interaction.response.send_message(embed=embed) - if check_datetime < dt.datetime.now(): + if check_datetime < dt.datetime.now(tz=ZoneInfo("Australia/Brisbane")): embed = _error_embed(DATETIME_IN_PAST_ERR) return await interaction.response.send_message(embed=embed) From c5ddc0d3e9d71258429efd30c4ad8da3723b4215 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Sat, 22 Jul 2023 20:34:12 +1000 Subject: [PATCH 09/48] Made haiku detection non-deterministic (#127) * Haiku: changes to "qual" suffixes * Haiku: Added "BSOD" as an exception * Haiku: Actually add the change to "haiku.py" as well * Haiku: rearranged logic to remove if hell * Haiku: Added citations for test cases * Haiku: Added miri to haiku test case * Haiku: No more code haikus * Haiku: More compound word exceptions * Haiku: Made accent reading consistent No longer reliant on a dictionary * Haiku: Fixed "Poetry" * Haiku: Fixed "es" and "'s" bugs * Haiku: Fixed removed suffixes not counted E.g. "less" is now 1 syllable, not 2 * Haiku: Fixed "tongue" and "something" * Haiku: Introduced a new general "re" prefix case Added a lot more exception cases and test cases * Added more test cases and fixed up some exceptions Mainly, this had to do with three letter words getting their own treatment. This is no longer required due to the more detailed system we currently have. The only exception that needed to be adjusted due to this was eye. * Formatted with black * Haiku: Moved syllable rules to a YAML file * Haiku: Made haiku detection probabilistic * Haiku: Added some documentation about haiku probability * Haiku: Added FatalErrorWithLog if the syllable rules file cannot be read * Haiku: Fixed most typing issues * Haiku: Fixed poetry lock * Haiku: Fixed formatting * Updated error detection for syllable rules file --- poetry.lock | 74 ++++- pyproject.toml | 2 +- tests/test_haiku.py | 65 +++- uqcsbot/haiku.py | 476 +++++++++-------------------- uqcsbot/static/syllable_rules.yaml | 210 +++++++++++++ 5 files changed, 471 insertions(+), 356 deletions(-) create mode 100644 uqcsbot/static/syllable_rules.yaml diff --git a/poetry.lock b/poetry.lock index 72560c43..f3713986 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "aio-mc-rcon" @@ -391,14 +391,14 @@ files = [ [[package]] name = "discord-py" -version = "2.3.0" +version = "2.3.1" description = "A Python wrapper for the Discord API" category = "main" optional = false python-versions = ">=3.8.0" files = [ - {file = "discord.py-2.3.0-py3-none-any.whl", hash = "sha256:3e9498967822ad4499f8f72deb9173f942d9827d92b6e4e4e7732d24f78f300c"}, - {file = "discord.py-2.3.0.tar.gz", hash = "sha256:c71066a30f037d069218e59092505c3e8945fd175e396a80748056d989756806"}, + {file = "discord.py-2.3.1-py3-none-any.whl", hash = "sha256:149652f24da299706270bf8c03c2fcf80cf1caf3a480744c61d5b001688b380d"}, + {file = "discord.py-2.3.1.tar.gz", hash = "sha256:8eb4fe66b5d503da6de3a8425e23012711dc2fbcd7a782107a92beac15ee3459"}, ] [package.dependencies] @@ -585,14 +585,14 @@ test = ["objgraph", "psutil"] [[package]] name = "humanize" -version = "4.6.0" +version = "4.7.0" description = "Python humanize utilities" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "humanize-4.6.0-py3-none-any.whl", hash = "sha256:401201aca462749773f02920139f302450cb548b70489b9b4b92be39fe3c3c50"}, - {file = "humanize-4.6.0.tar.gz", hash = "sha256:5f1f22bc65911eb1a6ffe7659bd6598e33dcfeeb904eb16ee1e705a09bf75916"}, + {file = "humanize-4.7.0-py3-none-any.whl", hash = "sha256:df7c429c2d27372b249d3f26eb53b07b166b661326e0325793e0a988082e3889"}, + {file = "humanize-4.7.0.tar.gz", hash = "sha256:7ca0e43e870981fa684acb5b062deb307218193bca1a01f2b2676479df849b3a"}, ] [package.extras] @@ -976,6 +976,56 @@ files = [ {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, ] +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] + [[package]] name = "requests" version = "2.31.0" @@ -1211,14 +1261,14 @@ files = [ [[package]] name = "typing-extensions" -version = "4.6.3" +version = "4.7.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, - {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, + {file = "typing_extensions-4.7.0-py3-none-any.whl", hash = "sha256:5d8c9dac95c27d20df12fb1d97b9793ab8b2af8a3a525e68c80e21060c161771"}, + {file = "typing_extensions-4.7.0.tar.gz", hash = "sha256:935ccf31549830cda708b42289d44b6f74084d616a00be651601a4f968e77c82"}, ] [[package]] @@ -1360,4 +1410,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "279be46d6277bf0ca0b5d9e0934296b6008a3dc8c3ed43e00bbcde989094ee3c" +content-hash = "c83c2c52dd6a94fb55fb0e23afc7d8e3b9d0b761e7b3d77add8a2ad70bda4379" diff --git a/pyproject.toml b/pyproject.toml index 2ecc96c4..ab0935d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ SQLAlchemy = {version = "^2.0.12", extras = ["postgresql_psycopg2binary"]} humanize = "^4.3" aiohttp = "^3.8" aio-mc-rcon = "^3.2.0" +PyYAML = "^6.0" [tool.poetry.scripts] botdev = "dev.cli:main" @@ -45,7 +46,6 @@ exclude = [ "**/error_handler.py", "**/events.py", "**/gaming.py", - "**/haiku.py", "**/member_counter.py", "**/remindme.py", "**/snailrace.py", diff --git a/tests/test_haiku.py b/tests/test_haiku.py index df8d97f8..b07894fb 100644 --- a/tests/test_haiku.py +++ b/tests/test_haiku.py @@ -1,4 +1,3 @@ -import pytest from uqcsbot.haiku import _number_of_syllables_in_word, _find_haiku @@ -251,6 +250,58 @@ def test_number_of_syllables_in_word(): "imbued": 2, "silhouette": 3, "epitome": 4, + "forte": 2, + "daybed": 2, + "jaded": 2, + "naked": 2, + "resume": 2, + "biped": 2, + "anyone": 3, + "schism": 2, + "less": 1, + "mist": 1, + "fish": 1, + "Wostershire": 3, + "Frappe": 2, + "livestream": 2, + "pikelet": 2, + "meringue": 2, + "vague": 1, + "any": 2, + "video": 3, + "area": 3, + "media": 3, + "previous": 3, + "experience": 4, + "audio": 3, + "areas": 3, + "radio": 3, + "safety": 2, + "association": 5, + "period": 3, + "style": 1, + "centre": 2, + "via": 2, + "videos": 3, + "player": 2, + "india": 3, + "created": 3, + "abusing": 3, + "acne": 2, + "acquire": 2, + "accompaniment": 5, + "amusing": 3, + "rhythm": 2, + "mario": 3, + "punctuation": 4, + "variation": 4, + "ivy": 2, + "era": 2, + "metre": 2, + "slayer": 2, + "create": 2, + "algorithm": 4, + "the": 1, } for word, expected_syllable_count in test_cases.items(): assert _number_of_syllables_in_word(word) == expected_syllable_count @@ -279,7 +330,7 @@ def test_find_haiku(): "wow I can't believe that it's haiku poetry day already guys", # enchi#8880 "Rhyme's overrated Haikus\n let you have some fun\n Plus they have good tune", # NotRealAqua#6969 "I could tell you more\n But with less words or lots more\n And you would feel them", # Anti-Matter#1740 - "Random syllables?\n Perhaps we need more Lovecraft\n Really random tongue", # lsenjov#4288 + "Random syllables?\n Perhaps we need more Lovecraft\n Really random tongue", # lsenjov#4288 "something blah blah blah\n insert random words right here\n blah blah blah deez nuts", # numberri#4096 ] false_cases = [ @@ -287,12 +338,14 @@ def test_find_haiku(): "neither is this", # indium#6908 "this is far too long to be a haiku, you should not accept this", # indium#6908 "when a haiku; kinda fits but has a word; at the end too longer", # indium#6908 - 'ive tried these as emergency "feed me now" meals and theyre so bland', # villuna#6251 + "ive tried these as emergency \"feed me now\" meals and theyre so bland", # villuna#6251 "Lovecraft my dear\n The bot is well confused\n Stop confusing it", # lsenjov#4288 "someone's getting it sooner and someone's getting it later :^)", # Madeline#8084 - "socially inept people? in MY computer science discord server", # miri#2222 + "socially inept people? in MY computer science discord server", # miri#2222 ] for haiku in true_cases: - assert _find_haiku(haiku) + is_haiku, _ = _find_haiku(haiku) + assert is_haiku for text in false_cases: - assert not _find_haiku(text) + is_haiku, _ = _find_haiku(text) + assert not is_haiku diff --git a/uqcsbot/haiku.py b/uqcsbot/haiku.py index 2f367623..bfe1fa06 100644 --- a/uqcsbot/haiku.py +++ b/uqcsbot/haiku.py @@ -1,40 +1,91 @@ import re +from typing import Final, Dict, List, Tuple +from yaml import load, Loader +import random +import logging + import discord from discord import app_commands from discord.ext import commands from uqcsbot.bot import UQCSBot +SYLLABLE_RULES_PATH: Final[str] = "uqcsbot/static/syllable_rules.yaml" +ALLOWED_CHANNEL_NAMES: Final[List[str]] = [ + "banter", + "bot-testing", + "dating", + "food", + "general", + "memes", + "yelling", +] +YELLING_CHANNEL_NAME: Final[str] = "yelling" +HAIKU_BASE_PROBABILITY: float = 0.4 +# How much "more likely" (as determined by _increase_probability) a haiku is if it has punctuation at the end of a line. +HAIKU_PUNCTUATION_PROBABILITY_INCREASE: float = 1.6 +# Words that increase the probability of a haiku, and the amount they increase the probability by (as determined by _increase_probability) +HAIKU_FAVOURITE_WORD_LIST: Dict[str, float] = { + "haiku": 6, + "haikus": 6, + "syllable": 4, + "word": 1.6, + "words": 1.6, + "poem": 2, + "poems": 2, +} + +# The following should be treated like constants after they are loaded in +# Affixes should contain all prefix, suffix and infix lists as tuples, as it is easier to work with beginswith and endswith +affixes: Dict[str, Tuple[str, ...]] = {} +# Words that are excluded from the syllable counting process and instead have a custom syllable count (like acronyms) +syllable_exceptions: Dict[str, int] = {} +# Accents and "equivalent" characters that they should be replaced with for syllable counting purposes +accent_replacements: Dict[str, str] = {} + +try: + with open(SYLLABLE_RULES_PATH, "r", encoding="utf-8") as syllable_rules_file: + syllable_rules = load(syllable_rules_file, Loader=Loader) + # beginswith and endswith both require tuples, so turn all lists into tuples + for rule_name, rule_specification in syllable_rules.items(): + if isinstance(rule_specification, list): + affixes[rule_name] = tuple(rule_specification) # type: ignore + elif isinstance(rule_specification, dict): + match rule_name: + case "exceptions": + syllable_exceptions = rule_specification + case "accents": + accent_replacements = rule_specification + case _: + # We will catch this on __init__ of the cog. We cannot deal with this error now via FatalErrorWithLog, as the bot may not have loaded enough + pass +except: + # We will catch this on __init__ of the cog. We cannot deal with this error now via FatalErrorWithLog, as the bot may not have loaded enough + pass + class Haiku(commands.Cog): """ Trys to find Haiku messages in certain channels, and respond "Nice haiku" if it finds one """ - ALLOWED_CHANNEL_NAMES = [ - "banter", - "bot-testing", - "dating", - "food", - "general", - "memes", - "yelling", - ] - YELLING_CHANNEL_NAME = "yelling" - def __init__(self, bot: UQCSBot): self.bot = bot + if affixes == {} or syllable_exceptions == {} or accent_replacements == {}: + raise RuntimeError( + f"The syllable rules (used for haiku detection) could not be found in {SYLLABLE_RULES_PATH} or did not follow the required format. Haiku detection will not work." + ) @commands.Cog.listener() async def on_ready(self): # As channels aren't ready when __init__() is called self.allowed_channels = [ discord.utils.get(self.bot.uqcs_server.channels, name=channel_name) - for channel_name in self.ALLOWED_CHANNEL_NAMES + for channel_name in ALLOWED_CHANNEL_NAMES ] @commands.Cog.listener() - async def on_message(self, message): + async def on_message(self, message: discord.Message): if ( message.channel not in self.allowed_channels or message.author.bot @@ -42,14 +93,14 @@ async def on_message(self, message): ): return - haiku_lines = _find_haiku(message.content) - if not haiku_lines: + haiku_lines, probability_of_showing_haiku = _find_haiku(message.content) + if not haiku_lines or random.random() > probability_of_showing_haiku: return haiku_lines = ["> " + line for line in haiku_lines] haiku = "\n".join(haiku_lines) if message.channel == discord.utils.get( - self.bot.uqcs_server.channels, name=self.YELLING_CHANNEL_NAME + self.bot.uqcs_server.channels, name=YELLING_CHANNEL_NAME ): await message.reply(f"Nice haiku:\n{haiku}".upper()) else: @@ -74,350 +125,99 @@ async def syllables(self, interaction: discord.Interaction, word: str): def _find_haiku(text: str): - syllable_count = 0 - lines = [] - current_line = [] + """ + Finds a haiku and a related "probability" that something is a haiku. + The "probability" is a rough estimate based on the amount of punctuation and words contained. + Note that this function gives probabilities of 0 for many messages that might be a haiku (if the bot miscounts the syllables). + """ + probability: float = HAIKU_BASE_PROBABILITY + syllable_count: int = 0 + lines: List[str] = [] + current_line: List[str] = [] + punctuation_at_end_of_previous_line = True # Initially true so that any punctuation at the beginning does not increase the probability. haiku_syllable_count = [5, 7, 5] + + def _increased_probability(probability: float, index: float): + """ + Calculates the probability of at least 1 success in index Bernoulli trials with given probability. + """ + return 1 - (1 - probability) ** index + for word in text.split(): + number_of_syllables = _number_of_syllables_in_word(word) + # Remove all space-separated punctuation and emotes - if _number_of_syllables_in_word(word) == 0: + if number_of_syllables == 0: + # If the last or first "word" is actually punctuation, keep it and increase the chance of it being a haiku + if not current_line: + if not punctuation_at_end_of_previous_line: + lines[-1] = lines[-1] + " " + word + punctuation_at_end_of_previous_line = True + probability = _increased_probability( + probability, HAIKU_PUNCTUATION_PROBABILITY_INCREASE + ) continue if len(lines) == 3: - return False + return False, 0 current_line.append(word) - syllable_count += _number_of_syllables_in_word(word) + if word.lower() in HAIKU_FAVOURITE_WORD_LIST: + probability = _increased_probability( + probability, HAIKU_FAVOURITE_WORD_LIST[word.lower()] + ) + + syllable_count += number_of_syllables if syllable_count > haiku_syllable_count[len(lines)]: - return False + return False, 0 if syllable_count == haiku_syllable_count[len(lines)]: + # If the last character is punctuation, increase the chance of it being a haiku + if not word[-1].isalnum(): + probability = _increased_probability( + probability, HAIKU_PUNCTUATION_PROBABILITY_INCREASE + ) + lines.append(" ".join(current_line)) current_line = [] syllable_count = 0 + punctuation_at_end_of_previous_line = False if len(lines) != 3: - return False - return lines + return False, 0 + return lines, probability def _number_of_vowel_groups(word: str): """ - Find the number of vowel groups within a word. A vowel group string of consecutive vowels. Each vowel can only be part of one vowel group and distinct vowel groups must be separated by a non-vowel character. The letter "y" is included as a vowel. + Find the number of vowel groups within a word. A vowel group string of consecutive vowels. + Each vowel can only be part of one vowel group and distinct vowel groups must be separated by a non-vowel character. + The letter "y" is included as a vowel. """ return len(re.findall("[aeiouy]+", word)) -def _number_of_syllables_in_word(word: str): +def _number_of_syllables_in_word(word: str) -> int: """ Estimate the number of syllables in a word. Inspired off the algorithm from this website: https://eayd.in/?p=232 Also the tool https://www.dcode.fr/word-search-regexp is useful at finding words and counterexamples """ - # Try to keep these to a minimum by writing new rules, especially the dictionary exceptions. - exceptions = { - # Abbreviations - "ok": 2, - "bbq": 3, - "bsod": 4, - "uq": 2, - "uqcs": 4, - } - - # PREFIXES - prefixes_needing_extra_syllable = ( - # As "mc" is pronounced as its own syllable - "mc", - # Account for the prefixes tri and bi, which for separate syllables from the following vowel. For example, "triangle" and "biology". - "tria", - "trie", - "trii", - "trio", - "triu", - "bia", - "bie", - "bii", - "bio", - "biu", - # The prefix "co-" often forms a separate syllable to the following vowel, as in "coincidence". The longer prefixes are to ensure it is a prefix, not just a word starting with "co" such as "cooking" or "coup". - "coapt", - "coed", - "coinci", - "coop", - # The prefix "pre" often forms a separate syllable to the following vowel, as in "preamble" or "preempt") - "prea", - "pree", - "prei", - "preo", - "preu", - # The prefix "sci" often forms a separate syllable to the following vowel, as in "science" or "sciatic" - "scia", - "scie", - "scii", - "scio", - "sciu", - # WORD-LIKE ENTRIES - # These are exceptions to the usual rules. Treat as prefixes variations of the words such as "cereal-box" for "cereal". - # Words ending in "Xial" where "X" is not "b", "d", "m", "n", "r", "v" or "x", but "Xial" consists of 2 syllables - "celestial", - # Words ending in "eal" where "eal" consists of 2 syllables - "boreal", - "cereal", - "corneal", - "ethereal", - "montreal", - # Words ending in "nt" due to contraction (user forgetting punctuation) - "didnt", - "doesnt", - "isnt", - "shouldnt", - "couldnt", - "wouldnt", - # Words ending in "e" that is considered silent, when it is not. - "maybe", - "cafe", - "naive", - "recipe", - "abalone", - "marscapone", - "epitome", - # Words starting with "real", "read", "reap", "rear", "reed", "reel", "reign" (see prefixes_needing_one_less_syllable) that use "re" as a prefix - # Note that "realit" covers all words with root "reality" - "realign", - "realit", - "reallocat", - "readdres", - "readjust", - "reapp", - "rearm", - "rearrang", - "rearrest", - "reeducat", - "reelect", - "reignit", - # Words that have "ee" pronounced as two syllables - "career", - # Words that have "ie" pronounced as two syllables - "audience", - "plier", - "societ", - "quiet", - # Words that have "ia" pronounced as two syllables - "pliant", - # Words that have "oe" pronounced as two syllables - "poet", - # Words that have "oi" pronounced as two syllables - "heroic", - # Words that have "oo" pronounced as two syllables - "zoology", - # Words that have "ue" pronounced as two syllables - "silhouett", - # Words that have "yo" pronounced as two syllables - "everyone", - # Words ending in "ed" that use "ed" as a syllable - "biped", - "daybed", - "naked", - "parallelepiped", - "wretched", - ) - # These are prefixes that contain "illegal" characters what are replaced (such as "é") - prefixes_needing_extra_syllable_before_illegal_replacement = ( - # Words ending in "n't" due to contraction - "didn't", - "doesn't", - "isn't", - "shouldn't", - "couldn't", - "wouldn't", - # Words with accents making a usually silent vowel spoken - "pâté", - "résumé", - ) - - prefixes_needing_one_less_syllable = ( - # WORD-LIKE ENTRIES - # These are exceptions to the usual rules. Treat as prefixes variations of the words such as "preacher" for "preach". - # Compound words with a silent "e" in the middle. - # Note that "something" with the suffix "ing" removed - "facebook", - "forefather", - "lovecraft", - "someth", - "therefore", - "whitespace", - "timezone", - # Words starting with "triX" where "X" is a vowel that aren't using "tri" as a prefix - # Note that "s" is removed for "tries, becoming "trie" - "tried", - "trie", - # Words starting with "preX" where "X" is a vowel that aren't using "pre" as a prefix - "preach", - # Words that have been shortened in speech - "every", - # Words that start with "reX" where "X" is a vowel that aren't using "re" as a prefix - "reach", - "read", - "reagan", - "real", - "realm", - "ream", - "reap", - "rear", - "reason", - "reebok", - "reed", - "reef", - "reek", - "reel", - "reich", - "reign", - "reindeer", - "reovirus", - "reuben", - "reuter", - # Words ending in "Xing" where "X" is a vowel that use "Xing" as a single syllable - "boing", - ) - - # SUFFIXES - suffixes_needing_one_more_syllable = ( - # Words ending in "le" such as "apple" often have a "le" syllable. But if we have a vowel then "le", "e" is often silent, such as "whale". - "le", - # If not part of the "cian" or "tian" suffixes, "ian" often is pronounced as 2 syllables. For example, "Australian" (compared to "politician"). - "ian", - # Usually, the suffix "ious" is one syllable, but if it is preceeded by "b", "n", "p" or "r" it is two syllables. For example, "anxious" has 2 syllables, but "amphibious" has 4 syllables. Likewise, consider "harmonious", "copious" and "glorious". Note: "s" has already been removed. - "biou", - "niou", - "piou", - "riou", - # Usually, the suffix "ial" is one syllable, but if it is preceeded by "b", "d", "l", "m", "n", "r", "v" or "x" it is two syllables. For example, "initial" has 3 syllables, but "microbial" has 4 syllables. Likewise, consider "radial", "familial", "polynomial", "millennial", "aerial", "trivial" and "axial". - "bial", - "dial", - "lial", - "mial", - "nial", - "rial", - "vial", - "xial", - # Words ending in "Xate" where X is a vowel, such as "graduate", often have "ate" as a separate syllable. The only exception is words ending in "quate" such as "adequate". - "aate", - "eate", - "iate", - "oate", - "uate", - # The suffix "ual" consists of two syllables such as "contextual". (Enter debate about "actual", "casual" and "usual". We will assume all of these have 3 syllables. Note that "actually" also has 3 syllables by this classification (which matches google's recommended pronunciation). We also use the British pronunciation of "dual", which has 2 syllables.) We exclude "qual" for words such as "equal". - "ual", - # The suffix "rior" contains two syllables in most words. For example "posterior" and "superior". - "rior", - # The suffix "phe" is pronounced as a syllable, for example "apostrophe". - "phe", - ) - - suffixes_needing_one_less_syllable = ( - # Usually words ending in "le" have "le" as a syllable, but this does not occur if a vowel is before the "e", as the "e" acts to change the other vowels sound. For example, consider "whale", "clientele", "pile", "hole" and "capsule" - "ale", - "ele", - "ile", - "ole", - "ule", - # The "cian" or "tian" suffixes have "ian" pronounced as 1 syllables. For example, "politician" (compared to "Australian"). - "cian", - "tian", - # Words ending in "Xate" where X is a vowel where "Xate" is a single syllable, for example "adequate". - "quate", - # Words ending in "Xual" where "Xual" is 1 syllable, such as "equal" - "qual", - # Words ending in "Xle" where "X" is a constant but with a silent "e" at the end - "ville", - # WORD-LIKE ENTRIES - # These are exceptions to the usual rules. - # Words ending in "Xle" where "X" is a constant but with a silent "e" at the end - "aisle", - "isle", - # Words containing "ue" at the end acting as a silent "e" - "tongue", - ) - - # REMOVED SUFFIXES - # These are suffixes that may hide a root word and can be removed without changing the number of syllables in the root word - suffixes_to_remove = ( - "ful", - "fully", - "ness", - "ment", - "ship", - "ist", - "ish", - "less", - "ly", - "ing", - "ising", - "isation", - "izing", - "ization", - "istic", - "istically", - "able", - "ably", - "ible", - "ibly", - ) - suffixes_to_remove_with_one_less_syllable = ( - "ise", - "ize", - "ised", - "ized", - ) - suffixes_to_remove_with_extra_syllable = ("ism",) - - # SYLLABLE COUNTING PROCESS - number_of_syllables = 0 word = word.lower() # Get rid of emotes. Stolen from https://www.freecodecamp.org/news/how-to-use-regex-to-match-emoji-including-discord-emotes/ word = re.sub("", " ", word) - if word.startswith(prefixes_needing_extra_syllable_before_illegal_replacement): + if word.startswith( + affixes["prefixes_needing_extra_syllable_before_illegal_replacement"] + ): number_of_syllables += 1 # Replace "illegals" (non-alphabetic characters) - accents = { - "à": "a", - "á": "a", - "â": "a", - "ã": "a", - "ä": "a", - "å": "a", - "æ": "ae", - "ç": "c", - "è": "e", - "é": "e", - "ê": "e", - "ë": "e", - "ì": "i", - "í": "i", - "î": "i", - "ï": "i", - "ñ": "n", - "ò": "o", - "ó": "o", - "ô": "o", - "õ": "o", - "ö": "o", - "ø": "o", - "œ": "oe", - "ù": "u", - "ú": "u", - "û": "u", - "ü": "u", - "ý": "y", - "ÿ": "y", - } for i, letter in enumerate(word): - if letter in accents: - unaccented_letter = accents[letter] + if letter in accent_replacements: + unaccented_letter = accent_replacements[letter] # Note that unaccented letter may be more than one character (eg "æ" goes to "ae") word = word[:i] + unaccented_letter + word[i + len(unaccented_letter) :] # Words ending in "'s" are similar to pluralising a word. If the word ends in "ch", "s" or "sh" then we add "es", otherwise we just add "s" @@ -432,15 +232,15 @@ def _number_of_syllables_in_word(word: str): if word == "": return 0 - if word in exceptions.keys(): - return exceptions[word] + if word in syllable_exceptions: + return syllable_exceptions[word] # Deals with abbreviations with no vowels if _number_of_vowel_groups(word) == 0: return len(word.replace(" ", "")) # Remove suffixes so we can focus on the syllables of the root word, but only if it is a true suffix (checked by testing if there is another vowel without the suffix) - for suffix in suffixes_to_remove: + for suffix in affixes["suffixes_to_remove"]: if ( word.endswith((suffix, suffix + "s")) and _number_of_vowel_groups( @@ -450,7 +250,7 @@ def _number_of_syllables_in_word(word: str): ): word = word.removesuffix(suffix).removesuffix(suffix + "s") number_of_syllables += _number_of_vowel_groups(suffix) - for suffix in suffixes_to_remove_with_one_less_syllable: + for suffix in affixes["suffixes_to_remove_with_one_less_syllable"]: if ( word.endswith((suffix, suffix + "s")) and _number_of_vowel_groups( @@ -461,7 +261,7 @@ def _number_of_syllables_in_word(word: str): word = word.removesuffix(suffix).removesuffix(suffix + "s") number_of_syllables += _number_of_vowel_groups(suffix) number_of_syllables -= 1 - for suffix in suffixes_to_remove_with_extra_syllable: + for suffix in affixes["suffixes_to_remove_with_extra_syllable"]: if ( word.endswith((suffix, suffix + "s")) and _number_of_vowel_groups( @@ -481,11 +281,6 @@ def _number_of_syllables_in_word(word: str): number_of_syllables += 1 word = word.removesuffix("s") - # Any exceptions to this need to be put in the exceptions dictionary - if len(word) <= 3: - # Root words of 3 letters or less tend to have only 1 syllable. Any extra vowel groups within the root word need to be disregarded. For example "ageless" turns into "age" which only has 1 syllable, so 3 - 2 + 1 = 2 syllables in total. Similarly "eyes" turns into "eye" has 2 - 2 + 1 = 1 syllables in total, and "manly" has 2 - 1 + 1 = 2 syllables in total. - return number_of_syllables - _number_of_vowel_groups(word) + 1 - # GENERAL SUFFIX RULES # Do not move these to the suffixes tuples, as they often are contained within larger suffixes (contained within the suffix tuple; at most one suffix tuple rule applies, so we should avoid overlap) # Words like "flipped" and "asked" don't have a syllable for "ed" @@ -497,7 +292,11 @@ def _number_of_syllables_in_word(word: str): ): number_of_syllables -= 1 # Accounts for silent "e" at the ends of words - if word.endswith("e") and not word.endswith(("ae", "ee", "ie", "oe", "ue")): + if ( + word.endswith("e") + and not word.endswith(("ae", "ee", "ie", "oe", "ue")) + and _number_of_vowel_groups(word.removesuffix("e")) > 0 + ): number_of_syllables -= 1 # GENERAL PREFIX RULES @@ -509,17 +308,20 @@ def _number_of_syllables_in_word(word: str): number_of_syllables += 1 # Deal with exceptions from the given prefix and suffix lists - if word.startswith(prefixes_needing_extra_syllable): + if word.startswith(affixes["prefixes_needing_extra_syllable"]): number_of_syllables += 1 - if word.startswith(prefixes_needing_one_less_syllable): + if word.startswith(affixes["prefixes_needing_one_less_syllable"]): number_of_syllables -= 1 - if word.endswith(suffixes_needing_one_more_syllable): + if word.endswith(affixes["suffixes_needing_one_more_syllable"]): number_of_syllables += 1 - if word.endswith(suffixes_needing_one_less_syllable): + if word.endswith(affixes["suffixes_needing_one_less_syllable"]): number_of_syllables -= 1 return number_of_syllables async def setup(bot: UQCSBot): - await bot.add_cog(Haiku(bot)) + try: + await bot.add_cog(Haiku(bot)) + except RuntimeError as e: + logging.error(e) diff --git a/uqcsbot/static/syllable_rules.yaml b/uqcsbot/static/syllable_rules.yaml new file mode 100644 index 00000000..b3e69c2a --- /dev/null +++ b/uqcsbot/static/syllable_rules.yaml @@ -0,0 +1,210 @@ +# Try to keep these to a minimum by writing new rules, especially explicit exceptions. +# A YAML file was chosen over JSON in order to have comments, as many of these require some explanation + +exceptions: { + # Abbreviations + "ok": 2, + "bbq": 3, + "bsod": 4, + "uq": 2, + "uqcs": 4, +} + +# Letters to be replaced +accents: { + "à": "a", + "á": "a", + "â": "a", + "ã": "a", + "ä": "a", + "å": "a", + "æ": "ae", + "ç": "c", + "è": "e", + "é": "e", + "ê": "e", + "ë": "e", + "ì": "i", + "í": "i", + "î": "i", + "ï": "i", + "ñ": "n", + "ò": "o", + "ó": "o", + "ô": "o", + "õ": "o", + "ö": "o", + "ø": "o", + "œ": "oe", + "ù": "u", + "ú": "u", + "û": "u", + "ü": "u", + "ý": "y", + "ÿ": "y", +} + +# PREFIXES +prefixes_needing_extra_syllable: [ + # As "mc" is pronounced as its own syllable + "mc", + # Account for the prefixes tri and bi, which for separate syllables from the following vowel. For example, "triangle" and "biology". + "tria", "trie", "trii", "trio", "triu", + "bia", "bie", "bii", "bio", "biu", + # The prefix "co-" often forms a separate syllable to the following vowel, as in "coincidence". + # The longer prefixes are to ensure it is a prefix, not just a word starting with "co" such as "cooking" or "coup". + "coapt", "coed", "coinci", "coop", + # The prefix "pre" often forms a separate syllable to the following vowel, as in "preamble" or "preempt") + "prea", "pree", "prei", "preo", "preu", + # The prefix "sci" often forms a separate syllable to the following vowel, as in "science" or "sciatic" + "scia", "scie", "scii", "scio", "sciu", + + # WORD-LIKE ENTRIES + # These are exceptions to the usual rules. Treat as prefixes variations of the words such as "cereal-box" for "cereal". + + # Words ending in "Xial" where "X" is not "b", "d", "m", "n", "r", "v" or "x", but "Xial" consists of 2 syllables + "celestial", + # Words ending in "eal" where "eal" consists of 2 syllables + "boreal", "cereal", "corneal", "ethereal", "montreal", + # Words ending in "nt" due to contraction (user forgetting punctuation) + "didnt", + "doesnt", + "isnt", + "shouldnt", + "couldnt", + "wouldnt", + # Words ending in "e" that is considered silent, when it is not. + "maybe", "cafe", "naive", "recipe", "abalone", "marscapone", "epitome", + "forte", "frappe", "eye", "acne", + # Words starting with "real", "read", "reap", "rear", "reed", "reel", "reign" (see prefixes_needing_one_less_syllable) that use "re" as a prefix + # Note that "realit" covers all words with root "reality" + "realign", "realit", "reallocat", "readdres", "readjust", "reapp", "rearm", + "rearrang", "rearrest", "reeducat", "reelect", "reignit", + # Words that have "ea" pronounced as two syllables + "area", + # Words that have "ee" pronounced as two syllables + "career", + # Words that have "eo" pronounced as two syllables + "video", + # Words that have "ia" pronounced as two syllables + "pliant", "media", "association", "via", "india", "variat", + # Words that have "ie" pronounced as two syllables + "audience", "plier", "societ", "quiet", "experience", + # Words that have "io" pronounced as two syllables + "audio", "radio", "period", "mario", + # Words that have "oe" pronounced as two syllables + "poet", + # Words that have "oi" pronounced as two syllables + "heroic", + # Words that have "oo" pronounced as two syllables + "zoology", + # Words that have "ue" pronounced as two syllables + "silhouett", + # Words that have "ua" pronounced as two syllables + "punctuat", + # Words that have "yo" pronounced as two syllables + "everyone", "anyone", + # Words ending in "ed" that use "ed" as a syllable + "biped", "daybed", "naked", "parallelepiped", "wretched", + # Words that have "ism" as a suffix despite having no other vowels + "schism", +] + +# These are prefixes that contain "illegal" characters what are replaced (such as "é") +prefixes_needing_extra_syllable_before_illegal_replacement: [ + # Words ending in "n't" due to contraction + "didn't", "doesn't", "isn't", "shouldn't", "couldn't", "wouldn't", + # Words with accents making a usually silent vowel spoken + "pâté", "résumé", +] + +prefixes_needing_one_less_syllable: [ + # WORD-LIKE ENTRIES + # These are exceptions to the usual rules. Treat as prefixes variations of the words such as "preacher" for "preach". + + # Compound words with a silent "e" in the middle. + # Note that "something" with the suffix "ing" removed + "facebook", "forefather", "lovecraft", "someth", "therefore", "whitespace", "timezone", + "livestream", "pikelet", "safety", + # Words starting with "triX" where "X" is a vowel that aren't using "tri" as a prefix + # Note that "s" is removed for "tries, becoming "trie" + "tried", "trie", + # Words starting with "preX" where "X" is a vowel that aren't using "pre" as a prefix + "preach", + # Words that have been shortened in speech + "every", + # Words that start with "reX" where "X" is a vowel that aren't using "re" as a prefix + "reach", "read", "reagan", "real", "realm", "ream", "reap", + "rear", "reason", "reebok", "reed", "reef", "reek", "reel", + "reich", "reign", "reindeer", "reovirus", "reuben", "reuter", + # Words ending in "Xing" where "X" is a vowel that use "Xing" as a single syllable + "boing", +] + +# SUFFIXES +suffixes_needing_one_more_syllable: [ + # Words ending in "le" such as "apple" often have a "le" syllable. But if we have a vowel then "le", "e" is often silent, such as "whale". + "le", + # If not part of the "cian" or "tian" suffixes, "ian" often is pronounced as 2 syllables. For example, "Australian" (compared to "politician"). + "ian", + # Usually, the suffix "ious" is one syllable, but if it is preceeded by "b", "n", "p", "r", "v" it is two syllables. + # For example, "anxious" has 2 syllables, but "amphibious" has 4 syllables. + # Likewise, consider "harmonious", "copious" and "glorious". Note: "s" has already been removed. + "biou", "niou", "piou", "riou", "viou", + # Usually, the suffix "ial" is one syllable, but if it is preceeded by "b", "d", "l", "m", "n", "r", "v" or "x" it is two syllables. + # For example, "initial" has 3 syllables, but "microbial" has 4 syllables. Likewise, consider "radial", "familial", "polynomial", "millennial", "aerial", "trivial" and "axial". + "bial", "dial", "lial", "mial", "nial", "rial", "vial", "xial", + # Words ending in "Xate" where X is a vowel, such as "graduate", often have "ate" as a separate syllable. The only exception is words ending in "quate" such as "adequate". + "aate", "eate", "iate", "oate", "uate", "aated", "eated", + "iated", "oated", "uated", + # The suffix "ual" consists of two syllables such as "contextual". (Enter debate about "actual", "casual" and "usual". + # We will assume all of these have 3 syllables. Note that "actually" also has 3 syllables by this classification (which matches google's recommended pronunciation). + # We also use the British pronunciation of "dual", which has 2 syllables.) We exclude "qual" for words such as "equal". + "ual", + # The suffix "rior" contains two syllables in most words. For example "posterior" and "superior". + "rior", + # The suffix "phe" is pronounced as a syllable, for example "apostrophe". + "phe", + # Words ending in "tre" have it pronounced as "ter", for example "metre" and "centre" + "tre", + # Words ending in "ayer" have it pronounced as two syllables, for example "player" and "slayer" + "ayer", + # Words ending in "thm" have it pronounced as a syllable, for example "rhythm" and "algorithm" + "thm", +] + +suffixes_needing_one_less_syllable: [ + # Usually words ending in "le" have "le" as a syllable, but this does not occur if a vowel is before the "e", as the "e" acts to change the other vowels sound. + # For example, consider "whale", "clientele", "pile", "hole" and "capsule" + "ale", "ele", "ile", "ole", "ule", "yle", + # The "cian" or "tian" suffixes have "ian" pronounced as 1 syllables. For example, "politician" (compared to "Australian"). + "cian", "tian", + # Words ending in "Xate" where X is a vowel where "Xate" is a single syllable, for example "adequate". + "quate", + # Words ending in "Xual" where "Xual" is 1 syllable, such as "equal" + "qual", + # Words ending in "Xle" where "X" is a constant but with a silent "e" at the end + "ville", + + # WORD-LIKE ENTRIES + # These are exceptions to the usual rules. + + # Words ending in "Xle" where "X" is a constant but with a silent "e" at the end + "aisle", "isle", + # Words containing "ue" at the end acting as a silent "e" + "tongue", "meringue", "merengue", "vague", +] + +# REMOVED SUFFIXES +# These are suffixes that may hide a root word and can be removed without changing the number of syllables in the root word +suffixes_to_remove: [ + "ful", "fully", "ness", "ment", "ship", "ist", "ish", + "less", "ly", "ing", "ising", "isation", "izing", "ization", + "istic", "istically", "able", "ably", "ible", "ibly", +] +suffixes_to_remove_with_one_less_syllable: [ + "ise", "ize", "ised", "ized", +] +suffixes_to_remove_with_extra_syllable: [ + "ism", +] \ No newline at end of file From 8bbcaf357d16fb4c7ff435af5e47fe383e327c74 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Sat, 22 Jul 2023 21:29:20 +1000 Subject: [PATCH 10/48] Haiku: Fixed syllable count for "poem" (#140) --- tests/test_haiku.py | 1 + uqcsbot/static/syllable_rules.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_haiku.py b/tests/test_haiku.py index b07894fb..eaa740ba 100644 --- a/tests/test_haiku.py +++ b/tests/test_haiku.py @@ -302,6 +302,7 @@ def test_number_of_syllables_in_word(): "create": 2, "algorithm": 4, "the": 1, + "poem": 2, } for word, expected_syllable_count in test_cases.items(): assert _number_of_syllables_in_word(word) == expected_syllable_count diff --git a/uqcsbot/static/syllable_rules.yaml b/uqcsbot/static/syllable_rules.yaml index b3e69c2a..efa79503 100644 --- a/uqcsbot/static/syllable_rules.yaml +++ b/uqcsbot/static/syllable_rules.yaml @@ -93,7 +93,7 @@ prefixes_needing_extra_syllable: [ # Words that have "io" pronounced as two syllables "audio", "radio", "period", "mario", # Words that have "oe" pronounced as two syllables - "poet", + "poet", "poem", # Words that have "oi" pronounced as two syllables "heroic", # Words that have "oo" pronounced as two syllables From 41b1bd40650b1a96670d9f4b5bea2bc8a2b7eaee Mon Sep 17 00:00:00 2001 From: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> Date: Sun, 23 Jul 2023 12:42:31 +1000 Subject: [PATCH 11/48] use cache for both poetry workflow runs (#133) * use cache for both poetry workflow runs * cache poetry itself as well as deps * fix typo & change dependency key * hoist out python setup to separate job, cache everything * move checkout to test jobs * i can read * fix composite job syntax * istg there's zero documentation for local composite actions * add shell * kill me * attempt to fix caching * use ** instead of ~ * remove debugging ls --- .../workflows/{black.yml => run-black.yml} | 5 +- .github/workflows/run-tests.yml | 47 +++++++++++++ .github/workflows/setup-python/action.yml | 69 +++++++++++++++++++ .github/workflows/tests.yml | 37 ---------- .github/workflows/typecheck.yml | 33 --------- 5 files changed, 119 insertions(+), 72 deletions(-) rename .github/workflows/{black.yml => run-black.yml} (90%) create mode 100644 .github/workflows/run-tests.yml create mode 100644 .github/workflows/setup-python/action.yml delete mode 100644 .github/workflows/tests.yml delete mode 100644 .github/workflows/typecheck.yml diff --git a/.github/workflows/black.yml b/.github/workflows/run-black.yml similarity index 90% rename from .github/workflows/black.yml rename to .github/workflows/run-black.yml index e0a5c8e9..f33b5ca8 100644 --- a/.github/workflows/black.yml +++ b/.github/workflows/run-black.yml @@ -1,12 +1,13 @@ name: Code Style Check -on: [pull_request] +on: [ pull_request ] jobs: black: runs-on: ubuntu-latest + steps: - - name: Checkout repo + - name: Checkout repository uses: actions/checkout@v3 - name: Run black diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 00000000..f0bed37d --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,47 @@ +name: Static Code Tests + +on: + push: + branches: [ main ] + pull_request: [] + +env: + PYTHON_VERSION: '3.10' + POETRY_VERSION: '1.4.2' + +jobs: + tests: + name: Run tests + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Local action that tries to cache as much of python & poetry as possible + - name: Setup environment + uses: ./.github/workflows/setup-python + with: + python-version: ${{ env.PYTHON_VERSION }} + poetry-version: ${{ env.POETRY_VERSION }} + + - name: Check with pytest + run: poetry run pytest + + types: + name: Run typechecking + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + # Local action that tries to cache as much of python & poetry as possible + - name: Setup environment + uses: ./.github/workflows/setup-python + with: + python-version: ${{ env.PYTHON_VERSION }} + poetry-version: ${{ env.POETRY_VERSION }} + + - name: Check with pyright + run: poetry run pyright uqcsbot \ No newline at end of file diff --git a/.github/workflows/setup-python/action.yml b/.github/workflows/setup-python/action.yml new file mode 100644 index 00000000..bc152a41 --- /dev/null +++ b/.github/workflows/setup-python/action.yml @@ -0,0 +1,69 @@ +name: Setup Env +description: Setup python & poetry for running tests & typechecking + +inputs: + python-version: + description: Version of python to use + required: true + poetry-version: + description: Version of poetry to use + required: true + +runs: + using: "composite" + steps: + # ------ + # Get python + # ------ + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: ${{ inputs.python-version }} + + # ------ + # Get poetry (hopefully from cache) + # ------ + - name: Check for cached poetry binary + id: cached-poetry-binary + uses: actions/cache@v3 + with: + path: ~/.local + # poetry depends on OS, python version, and poetry version + key: poetry-${{ runner.os }}-${{ inputs.python-version }}-${{ inputs.poetry-version }} + + - name: Install poetry on cache miss + # we don't need an `if:` here because poetry checks if it's already installed + uses: snok/install-poetry@v1 + with: + version: ${{ inputs.poetry-version }} + virtualenvs-create: true + virtualenvs-in-project: true + virtualenvs-path: '**/.venv' + installer-parallel: true + + - name: Ensure poetry is on PATH + run: echo "$HOME/.poetry/bin" >> $GITHUB_PATH + shell: bash + + # ------ + # Get library dependencies (hopefully from cache) + # ------ + - name: Check for cached dependencies + id: cached-poetry-dependencies + uses: actions/cache@v3 + with: + path: '**/.venv' + # poetry dependencies depend on OS, python version, poetry version, and repository lockfile + key: poetry-deps-${{ runner.os }}-${{ inputs.python-version }}-${{ inputs.poetry-version }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install dependencies on cache miss + if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + run: poetry install --no-interaction --no-root + shell: bash + + # ------ + # Finalise install + # ------ + - name: Install main project + run: poetry install --no-interaction + shell: bash diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index aac3d4d9..00000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Run Tests - -on: - push: - branches: [ main ] - pull_request: [] - -env: - PYTHON_VERSION: '3.10' - POETRY_VERSION: '1.4.2' - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Install Poetry - uses: snok/install-poetry@v1 - with: - version: ${{ env.POETRY_VERSION }} - virtualenvs-create: true - virtualenvs-in-project: true - installer-parallel: true - - - name: Install dependencies - run: poetry install --no-interaction - - - name: Test with pytest - run: poetry run pytest diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml deleted file mode 100644 index 51689034..00000000 --- a/.github/workflows/typecheck.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Static Type Check - -on: [pull_request] - -env: - PYTHON_VERSION: '3.10' - POETRY_VERSION: '1.4.2' - -jobs: - types: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v3 - - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Install Poetry - uses: snok/install-poetry@v1 - with: - version: ${{ env.POETRY_VERSION }} - virtualenvs-create: true - virtualenvs-in-project: true - installer-parallel: true - - - name: Install dependencies - run: poetry install --no-interaction - - - name: Type with pyright - run: poetry run pyright uqcsbot From 42ac75a239cc48493091cce595e0b84871005c97 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Wed, 26 Jul 2023 23:37:53 +1000 Subject: [PATCH 12/48] LaTeX: Added error checking for the equation (#141) * LaTeX: Added error checking for the equation * LaTeX: Fix formatting * Fixed LaTeX embed title being too long * Fixed formatting --------- Co-authored-by: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> --- uqcsbot/latex.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/uqcsbot/latex.py b/uqcsbot/latex.py index aebb4921..8c79f58f 100644 --- a/uqcsbot/latex.py +++ b/uqcsbot/latex.py @@ -1,4 +1,5 @@ from urllib.parse import quote +import requests import discord from discord import app_commands @@ -20,9 +21,29 @@ async def latex(self, interaction: discord.Interaction, input: str): "%5Cdpi%7B200%7D%5Cbg%7B36393f%7D%5Cfg%7Bwhite%7D" f"{quote(input)}" ) + + # Check that the image can be found, otherwise it is likely that the equation is invalid + status_code = requests.get(url).status_code + if status_code == requests.codes.bad_request: + await interaction.edit_original_response( + content=f"Invalid equation: {input}" + ) + return + elif status_code != requests.codes.ok: + await interaction.edit_original_response( + content=f"Could not reach CodeCogs to render LaTeX" + ) + return + + # Will error if embed title is greater than 256 characters + if len(input) >= 256 - len('LaTeX render for ""...'): + title = f'LaTeX render for "{input[:220]}..."' + else: + title = f'LaTeX render for "{input}"' + embed = discord.Embed( colour=discord.Colour.blue(), - title=f'Latex render for "{input}"', + title=title, ).set_image(url=f"{url}") await interaction.edit_original_response(embed=embed) From 9f02d0044fd1ab6dde0834114067dd52296cfee9 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Fri, 11 Aug 2023 15:29:39 +1000 Subject: [PATCH 13/48] Haiku: Fixed small bugs with miscounting and allowed channels (#147) * Haiku: Fixed bug from allowed channels not existing * Haiku: Fixed miscount for 'Bayes' and '-ium' suffix --- tests/test_haiku.py | 9 ++++++++- uqcsbot/haiku.py | 5 ++++- uqcsbot/static/syllable_rules.yaml | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/test_haiku.py b/tests/test_haiku.py index eaa740ba..97229fd3 100644 --- a/tests/test_haiku.py +++ b/tests/test_haiku.py @@ -1,4 +1,5 @@ -from uqcsbot.haiku import _number_of_syllables_in_word, _find_haiku +# For testing private methods, we need to tell pyright to be quiet +from uqcsbot.haiku import _number_of_syllables_in_word, _find_haiku # pyright: ignore [reportPrivateUsage] def test_number_of_syllables_in_word(): @@ -303,6 +304,12 @@ def test_number_of_syllables_in_word(): "algorithm": 4, "the": 1, "poem": 2, + "Chromium": 3, + "YT": 2, + "Bayes": 1, + "yes": 1, + "theorem": 2, + "stadium": 3, } for word, expected_syllable_count in test_cases.items(): assert _number_of_syllables_in_word(word) == expected_syllable_count diff --git a/uqcsbot/haiku.py b/uqcsbot/haiku.py index bfe1fa06..7e8b7db7 100644 --- a/uqcsbot/haiku.py +++ b/uqcsbot/haiku.py @@ -75,6 +75,8 @@ def __init__(self, bot: UQCSBot): raise RuntimeError( f"The syllable rules (used for haiku detection) could not be found in {SYLLABLE_RULES_PATH} or did not follow the required format. Haiku detection will not work." ) + # Initially set allowed_channels to be empty incase a message is recived before on_ready has completed + self.allowed_channels = [] @commands.Cog.listener() async def on_ready(self): @@ -294,7 +296,7 @@ def _number_of_syllables_in_word(word: str) -> int: # Accounts for silent "e" at the ends of words if ( word.endswith("e") - and not word.endswith(("ae", "ee", "ie", "oe", "ue")) + and not word.endswith(("ae", "ee", "ie", "oe", "ue", "ye")) and _number_of_vowel_groups(word.removesuffix("e")) > 0 ): number_of_syllables -= 1 @@ -310,6 +312,7 @@ def _number_of_syllables_in_word(word: str) -> int: # Deal with exceptions from the given prefix and suffix lists if word.startswith(affixes["prefixes_needing_extra_syllable"]): number_of_syllables += 1 + print(f"{word} {number_of_syllables}") if word.startswith(affixes["prefixes_needing_one_less_syllable"]): number_of_syllables -= 1 if word.endswith(affixes["suffixes_needing_one_more_syllable"]): diff --git a/uqcsbot/static/syllable_rules.yaml b/uqcsbot/static/syllable_rules.yaml index efa79503..63d1adde 100644 --- a/uqcsbot/static/syllable_rules.yaml +++ b/uqcsbot/static/syllable_rules.yaml @@ -8,6 +8,7 @@ exceptions: { "bsod": 4, "uq": 2, "uqcs": 4, + "yt": 2, } # Letters to be replaced @@ -75,7 +76,7 @@ prefixes_needing_extra_syllable: [ "wouldnt", # Words ending in "e" that is considered silent, when it is not. "maybe", "cafe", "naive", "recipe", "abalone", "marscapone", "epitome", - "forte", "frappe", "eye", "acne", + "forte", "frappe", "acne", # Words starting with "real", "read", "reap", "rear", "reed", "reel", "reign" (see prefixes_needing_one_less_syllable) that use "re" as a prefix # Note that "realit" covers all words with root "reality" "realign", "realit", "reallocat", "readdres", "readjust", "reapp", "rearm", @@ -163,6 +164,8 @@ suffixes_needing_one_more_syllable: [ "ual", # The suffix "rior" contains two syllables in most words. For example "posterior" and "superior". "rior", + # The suffix "ium" usually contains two syllables. For example "chromium", "gymnasium" and "aquarium" + "ium", # The suffix "phe" is pronounced as a syllable, for example "apostrophe". "phe", # Words ending in "tre" have it pronounced as "ter", for example "metre" and "centre" @@ -193,6 +196,8 @@ suffixes_needing_one_less_syllable: [ "aisle", "isle", # Words containing "ue" at the end acting as a silent "e" "tongue", "meringue", "merengue", "vague", + # Words containing "ium" at the end acting as a single syllable + "belgium", ] # REMOVED SUFFIXES From 5f58ebd08be52626072c7ac95394d10ab7f9a766 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Fri, 11 Aug 2023 16:54:35 +1000 Subject: [PATCH 14/48] Fixed README typo (#143) An example command said `pyright` instead of `black`. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1785a2c9..9751ad47 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ More information for currently implemented environment variables can be found on Once you have a .env file, you can run the following command to start the bot: -```sh +```bash poetry run botdev ``` @@ -75,7 +75,7 @@ poetry run black uqcsbot Individual files can also be styled with: ```bash -poetry run pyright uqcsbot/file.py +poetry run black uqcsbot/file.py ``` ## Static Type Checks From 4143d90d1c38cbebf282c68dc52fc145ebb4fa16 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Sat, 19 Aug 2023 21:27:18 +1000 Subject: [PATCH 15/48] Removed erronious print statement (#149) --- uqcsbot/haiku.py | 1 - 1 file changed, 1 deletion(-) diff --git a/uqcsbot/haiku.py b/uqcsbot/haiku.py index 7e8b7db7..37bc7bf5 100644 --- a/uqcsbot/haiku.py +++ b/uqcsbot/haiku.py @@ -312,7 +312,6 @@ def _number_of_syllables_in_word(word: str) -> int: # Deal with exceptions from the given prefix and suffix lists if word.startswith(affixes["prefixes_needing_extra_syllable"]): number_of_syllables += 1 - print(f"{word} {number_of_syllables}") if word.startswith(affixes["prefixes_needing_one_less_syllable"]): number_of_syllables -= 1 if word.endswith(affixes["suffixes_needing_one_more_syllable"]): From 5d82891722b90c4fbdf794695e1f1e178b2e7694 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Sun, 20 Aug 2023 21:42:09 +1000 Subject: [PATCH 16/48] Added a hackathon countdown (#150) --- .env.example | 2 + uqcsbot/__main__.py | 1 + uqcsbot/hackathon.py | 108 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 uqcsbot/hackathon.py diff --git a/.env.example b/.env.example index fb0c38b5..c19c1d24 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,8 @@ SERVER_ID= # Variables for various command cogs. AOC_SESSION_ID= +HACKATHON_START_TIME= +HACKATHON_END_TIME= MC_RCON_ADDRESS= MC_RCON_PORT= MC_RCON_PASSWORD= diff --git a/uqcsbot/__main__.py b/uqcsbot/__main__.py index f2c303b0..93e7591e 100644 --- a/uqcsbot/__main__.py +++ b/uqcsbot/__main__.py @@ -47,6 +47,7 @@ async def main(): "error_handler", "events", "gaming", + "hackathon", "haiku", "holidays", "hoogle", diff --git a/uqcsbot/hackathon.py b/uqcsbot/hackathon.py new file mode 100644 index 00000000..21df5e03 --- /dev/null +++ b/uqcsbot/hackathon.py @@ -0,0 +1,108 @@ +import os +from datetime import datetime, timedelta +from zoneinfo import ZoneInfo +import logging + +import discord +from discord import app_commands +from discord.ext import commands + +from uqcsbot.bot import UQCSBot + +HACKATHON_START_TIME_STR = os.environ.get("HACKATHON_START_TIME") +HACKATHON_END_TIME_STR = os.environ.get("HACKATHON_END_TIME") + +TIME_SUFFIXES = ["day", "hour", "minute", "second"] + + +class Hackathon(commands.Cog): + def __init__(self, bot: UQCSBot): + self.bot = bot + try: + self.start_time = datetime.strptime( + HACKATHON_START_TIME_STR or "", "%Y-%m-%d %H:%M" + ).replace(tzinfo=ZoneInfo("Australia/Brisbane")) + self.end_time = datetime.strptime( + HACKATHON_END_TIME_STR or "", "%Y-%m-%d %H:%M" + ).replace(tzinfo=ZoneInfo("Australia/Brisbane")) + except ValueError: + logging.error("Unable to parse environment variable dates for hackathon") + self.start_time = None + self.end_time = None + + @app_commands.command(name="hackathon") + async def countdown(self, interaction: discord.Interaction): + """ + Provides the time until the start or end of the next hackathon. + """ + now = datetime.now(tz=ZoneInfo("Australia/Brisbane")) + if self.start_time == None or self.end_time == None: + await interaction.response.send_message( + "Could not process or find the time of the next/current hackathon." + ) + return + + elif self.end_time + timedelta(days=1) < now: + await interaction.response.send_message( + "No exact date is given for the next hackathon just yet. Stay tuned for details to come." + ) + return + + elif self.end_time < now: + await interaction.response.send_message( + f"Tools down for hackathon occurred {countdown_string(now - self.end_time)} ago." + ) + return + + elif self.start_time < now <= self.end_time: + await interaction.response.send_message( + f"Tools down for hackathon in {countdown_string(self.end_time - now)}!" + ) + return + + elif now <= self.start_time: + await interaction.response.send_message( + f"Hackathon starts in {countdown_string(self.start_time - now)}!" + ) + return + + await interaction.response.send_message( + "Time is just a construct of human perception." + ) + + +def countdown_string(time: timedelta): + """ + Provides the time in a countdown format + """ + days = time.days + hours = time.seconds // 36000 + minutes = (time.seconds // 60) % 60 + seconds = time.seconds % 60 + + time_values = [days, hours, minutes, seconds] + time_suffixes_pluralised = [ + suffix + "s" if value != 1 else suffix + for value, suffix in zip(time_values, TIME_SUFFIXES) + ] + time_strings = [ + str(value) + " " + suffix + for value, suffix in zip(time_values, time_suffixes_pluralised) + if value > 0 + ] + + if len(time_strings) == 0: + return "0 seconds" + elif len(time_strings) == 1: + time_string = time_strings[0] + else: + time_string = ", ".join(time_strings[:-1]) + " and " + time_strings[-1] + + output = "only " if days == 0 else "" + output += time_string + + return output + + +async def setup(bot: UQCSBot): + await bot.add_cog(Hackathon(bot)) From abeae5a502368aefc5d35924ea0ca3927063ce74 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Sun, 20 Aug 2023 21:56:40 +1000 Subject: [PATCH 17/48] Fixed hackathon countdown (#151) * Fixed hackathon countdown * Removed debug print --- uqcsbot/hackathon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uqcsbot/hackathon.py b/uqcsbot/hackathon.py index 21df5e03..3147ad0c 100644 --- a/uqcsbot/hackathon.py +++ b/uqcsbot/hackathon.py @@ -76,7 +76,7 @@ def countdown_string(time: timedelta): Provides the time in a countdown format """ days = time.days - hours = time.seconds // 36000 + hours = time.seconds // 3600 minutes = (time.seconds // 60) % 60 seconds = time.seconds % 60 From 0493b7d00aa298dcfbbde76a26d4f9c65bce3676 Mon Sep 17 00:00:00 2001 From: bradleysigma <42644678+bradleysigma@users.noreply.github.com> Date: Fri, 25 Aug 2023 23:45:39 +1000 Subject: [PATCH 18/48] Timeout Members Who Don't Yell In #yelling (#152) * timeout members who don't yell in #yelling * Formatting Etc. * Update yelling.py --- uqcsbot/models.py | 9 +++++++++ uqcsbot/yelling.py | 42 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/uqcsbot/models.py b/uqcsbot/models.py index 42bae757..44a5dd2a 100644 --- a/uqcsbot/models.py +++ b/uqcsbot/models.py @@ -70,3 +70,12 @@ class Starboard(Base): sent: Mapped[Optional[int]] = mapped_column( "sent", BigInteger, primary_key=True, nullable=True, unique=True ) + + +class YellingBans(Base): + __tablename__ = "yellingbans" + + user_id: Mapped[int] = mapped_column( + "user_id", BigInteger, primary_key=True, nullable=False + ) + value: Mapped[int] = mapped_column("value", BigInteger, nullable=False) diff --git a/uqcsbot/yelling.py b/uqcsbot/yelling.py index 035df448..b8b31438 100644 --- a/uqcsbot/yelling.py +++ b/uqcsbot/yelling.py @@ -3,12 +3,19 @@ from random import choice, random import re +from uqcsbot.bot import UQCSBot +from uqcsbot.models import YellingBans +from datetime import timedelta + class Yelling(commands.Cog): CHANNEL_NAME = "yelling" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: UQCSBot): self.bot = bot + self.bot.schedule_task( + self.clear_bans, trigger="cron", hour=17, timezone="Australia/Brisbane" + ) @commands.Cog.listener() async def on_message_edit(self, old: discord.Message, new: discord.Message): @@ -43,6 +50,37 @@ async def on_message(self, msg: discord.Message): # check if minuscule in message, and if so, post response if self.contains_lowercase(text): await msg.reply(self.generate_response(text)) + if isinstance(msg.author, discord.Member): + await self.handle_bans(msg.author) + + async def handle_bans(self, author: discord.Member): + db_session = self.bot.create_db_session() + yellingbans_query = ( + db_session.query(YellingBans) + .filter(YellingBans.user_id == author.id) + .one_or_none() + ) + if yellingbans_query is None: + value = 0 + db_session.add(YellingBans(user_id=author.id, value=1)) + else: + value = yellingbans_query.value + yellingbans_query.value += 1 + db_session.commit() + db_session.close() + + await author.timeout(timedelta(seconds=(15 * 2**value)), reason="#yelling") + + async def clear_bans(self): + db_session = self.bot.create_db_session() + yellingbans_query = db_session.query(YellingBans) + for i in yellingbans_query: + if i.value <= 1: + db_session.delete(i) + else: + i.value -= 1 + db_session.commit() + db_session.close() def clean_text(self, message: str) -> str: """Cleans text of links, emoji, and any character escaping.""" @@ -127,5 +165,5 @@ def random_minuscule(self, message: str) -> str: return choice(possible) if possible else "" -async def setup(bot: commands.Bot): +async def setup(bot: UQCSBot): await bot.add_cog(Yelling(bot)) From 3f6915c000cdadc0423cb6d366cdb87ebfa33044 Mon Sep 17 00:00:00 2001 From: bradleysigma <42644678+bradleysigma@users.noreply.github.com> Date: Sat, 26 Aug 2023 14:29:32 +1000 Subject: [PATCH 19/48] Timeout on edits in #yelling too. (#153) * timeout members who don't yell in #yelling * Formatting Etc. * Update yelling.py * Update yelling.py * Update yelling.py --------- Co-authored-by: Isaac Beh --- uqcsbot/yelling.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/uqcsbot/yelling.py b/uqcsbot/yelling.py index b8b31438..66a134f7 100644 --- a/uqcsbot/yelling.py +++ b/uqcsbot/yelling.py @@ -33,6 +33,8 @@ async def on_message_edit(self, old: discord.Message, new: discord.Message): if self.contains_lowercase(text): await new.reply(self.generate_response(text)) + if isinstance(new.author, discord.Member): + await self.handle_bans(new.author) @commands.Cog.listener() async def on_message(self, msg: discord.Message): From b3c4f36f4baefa96c12eba128e267e7bf5e04cea Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Sat, 26 Aug 2023 15:34:24 +1000 Subject: [PATCH 20/48] Added voteyrachels, voteytoms, voteyjimmys (#154) --- uqcsbot/voteythumbs.py | 126 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 118 insertions(+), 8 deletions(-) diff --git a/uqcsbot/voteythumbs.py b/uqcsbot/voteythumbs.py index d0d68949..3a68da7a 100644 --- a/uqcsbot/voteythumbs.py +++ b/uqcsbot/voteythumbs.py @@ -3,6 +3,12 @@ from discord.ext import commands +class EmoteNotFoundError(Exception): + def __init__(self, emote: str, *args: object) -> None: + self.emote = emote + super().__init__(*args) + + class VoteyThumbs(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot @@ -17,14 +23,36 @@ async def cog_unload(self) -> None: self.voteythumbs_menu.name, type=self.voteythumbs_menu.type ) - async def common_react(self, message: discord.Message): + async def common_react( + self, + message: discord.Message, + up_reaction: str = "👍", + down_reaction: str = "👎", + middle_reaction: str = "thumbsright", + ): """Reactions for voteythumbs""" - await message.add_reaction("👍") - await message.add_reaction("👎") - # thumbsright is a custom server emoji, get the Discord emoji string for it. - thumbsright = discord.utils.get(self.bot.emojis, name="thumbsright") - await message.add_reaction(str(thumbsright)) + if len(up_reaction) > 1: + up_emoji = discord.utils.get(self.bot.emojis, name=up_reaction) + if not up_emoji: + raise EmoteNotFoundError(up_reaction) + up_reaction = str(up_emoji) + + if len(down_reaction) > 1: + down_emoji = discord.utils.get(self.bot.emojis, name=down_reaction) + if not down_emoji: + raise EmoteNotFoundError(down_reaction) + down_reaction = str(down_emoji) + + if len(middle_reaction) > 1: + middle_emoji = discord.utils.get(self.bot.emojis, name=middle_reaction) + if not middle_emoji: + raise EmoteNotFoundError(middle_reaction) + middle_reaction = str(middle_emoji) + + await message.add_reaction(up_reaction) + await message.add_reaction(down_reaction) + await message.add_reaction(middle_reaction) async def voteythumbs_context( self, interaction: discord.Interaction, message: discord.Message @@ -32,7 +60,13 @@ async def voteythumbs_context( """Starts a 👍 👎 vote.""" await interaction.response.defer(ephemeral=True) - await self.common_react(message) + try: + await self.common_react(message) + except EmoteNotFoundError: + await interaction.edit_original_response( + content="The emotes for this vote don't seem to be available." + ) + return await interaction.edit_original_response(content="Vote away!") @app_commands.command(name="voteythumbs") @@ -44,7 +78,83 @@ async def voteythumbs_command( await interaction.response.defer() message = await interaction.original_response() - await self.common_react(message) + try: + await self.common_react(message) + except EmoteNotFoundError: + await interaction.edit_original_response( + content="The emotes for this vote don't seem to be available." + ) + return + + await interaction.edit_original_response(content=question) + + @app_commands.command(name="voteyrachels") + @app_commands.describe(question="The question that shall be voted upon") + async def voteyrachels_command( + self, interaction: discord.Interaction, question: str + ): + """Starts a vote with Rachel faces.""" + await interaction.response.defer() + message = await interaction.original_response() + + try: + await self.common_react( + message, + up_reaction="presidentialpoggers", + down_reaction="no", + middle_reaction="lmao", + ) + except EmoteNotFoundError: + await interaction.edit_original_response( + content="The emotes for this vote don't seem to be available." + ) + return + + await interaction.edit_original_response(content=question) + + @app_commands.command(name="voteytoms") + @app_commands.describe(question="The question that shall be voted upon") + async def voteytoms_command(self, interaction: discord.Interaction, question: str): + """Starts a vote with Tom faces.""" + await interaction.response.defer() + message = await interaction.original_response() + + try: + await self.common_react( + message, + up_reaction="expresident2", + down_reaction="expresident", + middle_reaction="big_if_true", + ) + except EmoteNotFoundError: + await interaction.edit_original_response( + content="The emotes for this vote don't seem to be available." + ) + return + + await interaction.edit_original_response(content=question) + + @app_commands.command(name="voteyjimmys") + @app_commands.describe(question="The question that shall be voted upon") + async def voteyjimmys_command( + self, interaction: discord.Interaction, question: str + ): + """Starts a vote with Jimmy faces.""" + await interaction.response.defer() + message = await interaction.original_response() + + try: + await self.common_react( + message, + up_reaction="jimmytree", + down_reaction="no", + middle_reaction="ghostJimmy", + ) + except EmoteNotFoundError: + await interaction.edit_original_response( + content="The emotes for this vote don't seem to be available." + ) + return await interaction.edit_original_response(content=question) From 00b28aa96cb895fecc1ff97c4ad6c01c24505a97 Mon Sep 17 00:00:00 2001 From: bradleysigma <42644678+bradleysigma@users.noreply.github.com> Date: Sun, 27 Aug 2023 07:31:06 +1000 Subject: [PATCH 21/48] Create Decorator to Prevent Use of Commands With Minuscule Argument To Bot Commands In #yelling (#155) * timeout members who don't yell in #yelling * Formatting Etc. * Update yelling.py * Update yelling.py * Update yelling.py * Create Decorator to Prevent Use of Commands With Minuscule Argument To Bot Commands In #yelling --- uqcsbot/cowsay.py | 3 ++ uqcsbot/dominos_coupons.py | 2 ++ uqcsbot/gaming.py | 3 ++ uqcsbot/haiku.py | 2 ++ uqcsbot/hoogle.py | 3 ++ uqcsbot/latex.py | 3 ++ uqcsbot/manage_cogs.py | 3 ++ uqcsbot/minecraft.py | 2 ++ uqcsbot/morse.py | 2 ++ uqcsbot/past_exams.py | 2 ++ uqcsbot/phonetics.py | 3 ++ uqcsbot/text.py | 8 +++++ uqcsbot/voteythumbs.py | 3 ++ uqcsbot/whatsdue.py | 5 +++ uqcsbot/whatweekisit.py | 3 ++ uqcsbot/yelling.py | 72 ++++++++++++++++++++++++++++++++++++-- 16 files changed, 117 insertions(+), 2 deletions(-) diff --git a/uqcsbot/cowsay.py b/uqcsbot/cowsay.py index 58a91aba..63480e3a 100644 --- a/uqcsbot/cowsay.py +++ b/uqcsbot/cowsay.py @@ -7,6 +7,7 @@ from discord.ext import commands from uqcsbot.bot import UQCSBot +from uqcsbot.yelling import yelling_exemptor # Max length of the message to be displayed per line in the bubble. CowsayWrapLength = 40 @@ -49,6 +50,7 @@ def __init__(self, bot: UQCSBot): tongue="Whether the cow should show its tongue (optional, default to False)", tux="Display the Linux Tux instead of the cow. Tux doesn't show tongue. (optional, default to False)", ) + @yelling_exemptor(input_args=["message"]) async def cowsay_command( self, interaction: discord.Interaction, @@ -91,6 +93,7 @@ async def cowsay_command( tongue="Whether the cow should show its tongue (optional, default to False)", tux="Display the Linux Tux instead of the cow. Tux doesn't show tongue. (optional, default to False)", ) + @yelling_exemptor(input_args=["message"]) async def cowthink_command( self, interaction: discord.Interaction, diff --git a/uqcsbot/dominos_coupons.py b/uqcsbot/dominos_coupons.py index 8caad098..b7eb7b5c 100644 --- a/uqcsbot/dominos_coupons.py +++ b/uqcsbot/dominos_coupons.py @@ -10,6 +10,7 @@ from discord.ext import commands from uqcsbot.bot import UQCSBot +from uqcsbot.yelling import yelling_exemptor MAX_COUPONS = 10 # Prevents abuse @@ -36,6 +37,7 @@ def __init__(self, bot: UQCSBot): ignore_expiry="Indicates to include coupons that have expired. Defaults to True.", keywords="Words to search for within the coupon. All coupons descriptions will mention at least one keyword.", ) + @yelling_exemptor(input_args=["keywords"]) async def dominoscoupons( self, interaction: discord.Interaction, diff --git a/uqcsbot/gaming.py b/uqcsbot/gaming.py index 9793f6e1..a7c3ac9b 100644 --- a/uqcsbot/gaming.py +++ b/uqcsbot/gaming.py @@ -11,6 +11,7 @@ from requests import get from uqcsbot.bot import UQCSBot +from uqcsbot.yelling import yelling_exemptor class Gaming(commands.Cog): @@ -217,6 +218,7 @@ def format_board_game_parameters(self, parameters: Dict[str, str]) -> discord.Em @app_commands.command() @app_commands.describe(board_game="Board game to search for") + @yelling_exemptor(input_args=["board_game"]) async def bgg(self, interaction: discord.Interaction, board_game: str): """ Gets the details of the provided board game from Board Game Geek @@ -242,6 +244,7 @@ async def bgg(self, interaction: discord.Interaction, board_game: str): @app_commands.command() @app_commands.describe(card="Card to search for") + @yelling_exemptor(input_args=["card"]) async def scry(self, interaction: discord.Interaction, card: Optional[str]): """ Returns the Magic: the Gathering card that matches (partially or diff --git a/uqcsbot/haiku.py b/uqcsbot/haiku.py index 37bc7bf5..231e47ec 100644 --- a/uqcsbot/haiku.py +++ b/uqcsbot/haiku.py @@ -9,6 +9,7 @@ from discord.ext import commands from uqcsbot.bot import UQCSBot +from uqcsbot.yelling import yelling_exemptor SYLLABLE_RULES_PATH: Final[str] = "uqcsbot/static/syllable_rules.yaml" ALLOWED_CHANNEL_NAMES: Final[List[str]] = [ @@ -110,6 +111,7 @@ async def on_message(self, message: discord.Message): @app_commands.command() @app_commands.describe(word="Word to syllable check") + @yelling_exemptor(input_args=["word"]) async def syllables(self, interaction: discord.Interaction, word: str): """Checks the number of syllables in a given word.""" if " " not in word: diff --git a/uqcsbot/hoogle.py b/uqcsbot/hoogle.py index 04cb054e..28c5ec17 100644 --- a/uqcsbot/hoogle.py +++ b/uqcsbot/hoogle.py @@ -10,6 +10,8 @@ import re import urllib.parse +from uqcsbot.yelling import yelling_exemptor + class Hoogle(commands.Cog): def __init__(self, bot: commands.Bot): @@ -39,6 +41,7 @@ def pretty_hoogle_result(self, result: Dict[str, str]) -> str: @app_commands.command() @app_commands.describe(search="Function name or type signature to search for") + @yelling_exemptor(input_args=["search"]) async def hoogle(self, interaction: discord.Interaction, search: str): """ Queries the Hoogle Haskell API search engine, searching Haskell libraries by either function name diff --git a/uqcsbot/latex.py b/uqcsbot/latex.py index 8c79f58f..8fc344bb 100644 --- a/uqcsbot/latex.py +++ b/uqcsbot/latex.py @@ -5,6 +5,8 @@ from discord import app_commands from discord.ext import commands +from uqcsbot.yelling import yelling_exemptor + class Latex(commands.Cog): def __init__(self, bot: commands.Bot): @@ -12,6 +14,7 @@ def __init__(self, bot: commands.Bot): @app_commands.command(description="Renders the given LaTeX") @app_commands.describe(input="LaTeX to render") + @yelling_exemptor(input_args=["input"]) async def latex(self, interaction: discord.Interaction, input: str): # since bot prohibits empty prompts, checking len==0 seems redundant await interaction.response.defer(thinking=True) diff --git a/uqcsbot/manage_cogs.py b/uqcsbot/manage_cogs.py index d702c7b1..116742ab 100644 --- a/uqcsbot/manage_cogs.py +++ b/uqcsbot/manage_cogs.py @@ -4,6 +4,8 @@ from discord import app_commands from discord.ext import commands +from uqcsbot.yelling import yelling_exemptor + class ManageCogs(commands.Cog): """ @@ -18,6 +20,7 @@ def __init__(self, bot: commands.Bot): @app_commands.describe( cog='The cog (i.e. python file) to try to unload. Use python package notation, so no suffix of ".py" and "." between folders: e.g. "manage_cogs".', ) + @yelling_exemptor(input_args=["cog"]) async def manage_cogs( self, interaction: discord.Interaction, diff --git a/uqcsbot/minecraft.py b/uqcsbot/minecraft.py index 93f2a83c..76c8a649 100644 --- a/uqcsbot/minecraft.py +++ b/uqcsbot/minecraft.py @@ -10,6 +10,7 @@ from uqcsbot.bot import UQCSBot from uqcsbot.models import MCWhitelist from uqcsbot.utils.err_log_utils import FatalErrorWithLog +from uqcsbot.yelling import yelling_exemptor RCON_ADDRESS = os.environ.get("MC_RCON_ADDRESS") RCON_PORT = os.environ.get("MC_RCON_PORT") @@ -49,6 +50,7 @@ async def send_rcon_command(self, command: str): @app_commands.command() @app_commands.describe(username="Minecraft username to whitelist.") + @yelling_exemptor(input_args=["username"]) async def mcwhitelist(self, interaction: discord.Interaction, username: str): """Adds a username to the whitelist for the UQCS server.""" db_session = self.bot.create_db_session() diff --git a/uqcsbot/morse.py b/uqcsbot/morse.py index 22022cca..a3358dbf 100644 --- a/uqcsbot/morse.py +++ b/uqcsbot/morse.py @@ -7,6 +7,7 @@ from discord.ext import commands from uqcsbot.bot import UQCSBot +from uqcsbot.yelling import yelling_exemptor # value of all valid ascii values in morse code MorseCodeDict = { @@ -70,6 +71,7 @@ def __init__(self, bot: UQCSBot): @app_commands.command(name="morse") @app_commands.describe(message="The message to be converted to morse code") + @yelling_exemptor(input_args=["message"]) async def morse_command( self, interaction: discord.Interaction, message: str ) -> None: diff --git a/uqcsbot/past_exams.py b/uqcsbot/past_exams.py index b51f032d..9be92d15 100644 --- a/uqcsbot/past_exams.py +++ b/uqcsbot/past_exams.py @@ -11,6 +11,7 @@ get_past_exams_page_url, HttpException, ) +from uqcsbot.yelling import yelling_exemptor SemesterType = Optional[Literal["Sem 1", "Sem 2", "Summer"]] @@ -26,6 +27,7 @@ def __init__(self, bot: commands.Bot): semester="The semester to find exams for. Leave blank for all semesters.", random_exam="Whether to select a single random exam.", ) + @yelling_exemptor(input_args=["course_code"]) async def pastexams( self, interaction: discord.Interaction, diff --git a/uqcsbot/phonetics.py b/uqcsbot/phonetics.py index 6cbfa280..c79afc11 100644 --- a/uqcsbot/phonetics.py +++ b/uqcsbot/phonetics.py @@ -2,6 +2,8 @@ from discord import app_commands from discord.ext import commands +from uqcsbot.yelling import yelling_exemptor + # X-SAMPA is basically a giant lookup table of symbols. It is split up into tables of # length 4, 3, 2, and 1 for ease of decoding. This is not the most efficient way to # convert X-SAMPA to Unicode, but it is definitely the simplest + easiest to understand. @@ -169,6 +171,7 @@ def __init__(self, bot: commands.Bot): @app_commands.command() @app_commands.describe(input="X-SAMPA to convert") + @yelling_exemptor(input_args=["input"]) async def xsampa(self, interaction: discord.Interaction, input: str): """ Converts X-SAMPA to IPA diff --git a/uqcsbot/text.py b/uqcsbot/text.py index c8f4171e..c62a82cc 100644 --- a/uqcsbot/text.py +++ b/uqcsbot/text.py @@ -6,6 +6,8 @@ from discord import app_commands from discord.ext import commands +from uqcsbot.yelling import yelling_exemptor + async def encoding_autocomplete( interaction: discord.Interaction, current: str @@ -70,6 +72,7 @@ def __init__(self, bot: commands.Bot): message="Input string", encoding="Character encoding to use, defaults to UTF-8" ) @app_commands.autocomplete(encoding=encoding_autocomplete) + @yelling_exemptor(input_args=["message"]) async def binify( self, interaction: discord.Interaction, @@ -112,6 +115,7 @@ async def binify( @app_commands.describe( message="Text to shift", distance="Distance to shift, defaults to 13" ) + @yelling_exemptor(input_args=["message"]) async def caesar( self, interaction: discord.Interaction, @@ -140,6 +144,7 @@ async def caesar( message="Input string", encoding="Character encoding to use, defaults to UTF-8" ) @app_commands.autocomplete(encoding=encoding_autocomplete) + @yelling_exemptor(input_args=["message"]) async def hexify( self, interaction: discord.Interaction, @@ -213,6 +218,7 @@ async def mock_context( @app_commands.command(name="mock") @app_commands.describe(text="Text to mock") + @yelling_exemptor() async def mock_command(self, interaction: discord.Interaction, text: str): """mOckS ThE pRovIdEd teXT.""" @@ -231,6 +237,7 @@ async def scare_context( @app_commands.command(name="scare") @app_commands.describe(text='Text to "scare"') + @yelling_exemptor() async def scare_command(self, interaction: discord.Interaction, text: str): """ "adds" "scary" "quotes" "around" "the" "provided" "text" @@ -258,6 +265,7 @@ async def zalgo_context( @app_commands.command(name="zalgo") @app_commands.describe(text="Input text") + @yelling_exemptor() async def zalgo_command(self, interaction: discord.Interaction, text: str): """ Ȃd͍̋͗̃d͒̈́s̒͢ ̅̂̚͏̞̩ͅZͩ̆a̦̐ͭ́l̠̫̈́̐g̡͗ͯo̝̱̽ ̮̰͊c̢̞ͬh̩ͤ̑a̡̫̟͐̽̌r̪̭͇̓a̘͕̣c͓̐́t̠̂̈̓e̳̣̣͂̉r͓͗s͉̞͝ t̙͓̊ͨoͭ ̋̽͊t̛̖̮̊͋hͤ̂͏̯̺͚e̷͖̩̙̿ ͇̩̕ğ̵̟̘̼i̢͙̜v̲ͫ͘e͐͐͆̕n͟ ̭͋͢ͅt͐͆̀e̝̱͑͛x̝̲t͇͕ diff --git a/uqcsbot/voteythumbs.py b/uqcsbot/voteythumbs.py index 3a68da7a..d35994aa 100644 --- a/uqcsbot/voteythumbs.py +++ b/uqcsbot/voteythumbs.py @@ -2,6 +2,8 @@ from discord import app_commands from discord.ext import commands +from uqcsbot.yelling import yelling_exemptor + class EmoteNotFoundError(Exception): def __init__(self, emote: str, *args: object) -> None: @@ -71,6 +73,7 @@ async def voteythumbs_context( @app_commands.command(name="voteythumbs") @app_commands.describe(question="The question that shall be voted upon") + @yelling_exemptor(input_args=["question"]) async def voteythumbs_command( self, interaction: discord.Interaction, question: str ): diff --git a/uqcsbot/whatsdue.py b/uqcsbot/whatsdue.py index 79b1b790..9dd61c68 100644 --- a/uqcsbot/whatsdue.py +++ b/uqcsbot/whatsdue.py @@ -6,6 +6,8 @@ from discord import app_commands from discord.ext import commands +from uqcsbot.yelling import yelling_exemptor + from uqcsbot.utils.uq_course_utils import ( Offering, CourseNotFoundException, @@ -34,6 +36,9 @@ def __init__(self, bot: commands.Bot): course5="Course code", course6="Course code", ) + @yelling_exemptor( + input_args=["course1", "course2", "course3", "course4", "course5", "course6"] + ) async def whatsdue( self, interaction: discord.Interaction, diff --git a/uqcsbot/whatweekisit.py b/uqcsbot/whatweekisit.py index a72e1836..6b996bae 100644 --- a/uqcsbot/whatweekisit.py +++ b/uqcsbot/whatweekisit.py @@ -9,6 +9,8 @@ from bs4 import BeautifulSoup from discord.ext import commands +from uqcsbot.yelling import yelling_exemptor + # Endpoint that contains a table of semester dates MARKUP_CALENDAR_URL: str = "https://systems-training.its.uq.edu.au/systems/student-systems/electronic-course-profile-system/design-or-edit-course-profile/academic-calendar-teaching-week" DATE_FORMAT = "%d/%m/%Y" @@ -121,6 +123,7 @@ def __init__(self, bot: commands.Bot): @app_commands.describe( date="Date to lookup in the format of %d/%m/%Y (defaults to today)" ) + @yelling_exemptor(input_args=["date"]) async def whatweekisit(self, interaction: discord.Interaction, date: Optional[str]): """ Sends information about which semester, week and weekday it is. diff --git a/uqcsbot/yelling.py b/uqcsbot/yelling.py index 66a134f7..a2cb7f16 100644 --- a/uqcsbot/yelling.py +++ b/uqcsbot/yelling.py @@ -1,11 +1,57 @@ import discord +from typing import List, Dict, Callable, Any from discord.ext import commands from random import choice, random import re from uqcsbot.bot import UQCSBot from uqcsbot.models import YellingBans + from datetime import timedelta +from functools import wraps + + +def yelling_exemptor(input_args: List[str] = ["text"]) -> Callable[..., Any]: + def handler(func: Callable[..., Any]): + @wraps(func) + async def wrapper( + cogself: commands.Cog, *args: List[Any], **kwargs: Dict[str, Any] + ): + bot = cogself.bot # type: ignore + interaction = None + text = "".join([str(kwargs.get(i, "") or "") for i in input_args]) + if text == "": + await func(bot, *args, **kwargs) + return + for a in args: + if isinstance(a, discord.interactions.Interaction): + interaction = a + break + if interaction is None: + await func(bot, *args, **kwargs) + return + if not hasattr(interaction, "channel"): + await func(bot, *args, **kwargs) + return + if interaction.channel is None: + await func(bot, *args, **kwargs) + return + if interaction.channel.type != discord.ChannelType.text: + await func(bot, *args, **kwargs) + return + if interaction.channel.name != "yelling": + await func(bot, *args, **kwargs) + return + if not Yelling.contains_lowercase(text): + await func(bot, *args, **kwargs) + return + await interaction.response.send_message(str(discord.utils.get(bot.emojis, name="disapproval") or "")) # type: ignore + if isinstance(interaction.user, discord.Member): + await Yelling.external_handle_bans(bot, interaction.user) # type: ignore + + return wrapper + + return handler class Yelling(commands.Cog): @@ -55,6 +101,25 @@ async def on_message(self, msg: discord.Message): if isinstance(msg.author, discord.Member): await self.handle_bans(msg.author) + @staticmethod + async def external_handle_bans(bot: UQCSBot, author: discord.Member): + db_session = bot.create_db_session() + yellingbans_query = ( + db_session.query(YellingBans) + .filter(YellingBans.user_id == author.id) + .one_or_none() + ) + if yellingbans_query is None: + value = 0 + db_session.add(YellingBans(user_id=author.id, value=1)) + else: + value = yellingbans_query.value + yellingbans_query.value += 1 + db_session.commit() + db_session.close() + + await author.timeout(timedelta(seconds=(15 * 2**value)), reason="#yelling") + async def handle_bans(self, author: discord.Member): db_session = self.bot.create_db_session() yellingbans_query = ( @@ -104,7 +169,8 @@ def clean_text(self, message: str) -> str: return text - def contains_lowercase(self, message: str) -> bool: + @staticmethod + def contains_lowercase(message: str) -> bool: """Checks if message contains any lowercase characters""" return any(char.islower() for char in message) @@ -125,7 +191,9 @@ def generate_response(self, text: str) -> str: "IT’S ON THE LEFT OF THE “A” KEY!", "FORMER PRESIDENT THEODORE ROOSEVELT’S FOREIGN POLICY IS A SHAM!", "#YELLING IS FOR EXTERNAL SCREAMING!", - f"DID YOU SAY \n>>>{self.mutate_minuscule(text)}".upper(), + f"DID YOU SAY \n{self.mutate_minuscule(text)}".upper().replace( + "\n", "\n> " + ), f"WHAT IS THE MEANING OF THIS ARCANE SYMBOL “{self.random_minuscule(text)}”‽" + " I RECOGNISE IT NOT!", ] From 8fb2a5bb57d5f7aa9f972d36ee84e0d711d7d732 Mon Sep 17 00:00:00 2001 From: bradleysigma <42644678+bradleysigma@users.noreply.github.com> Date: Sun, 27 Aug 2023 11:52:09 +1000 Subject: [PATCH 22/48] Trigger Yelling Filter On Bad URLs (#156) * timeout members who don't yell in #yelling * Formatting Etc. * Update yelling.py * Update yelling.py * Update yelling.py * Create Decorator to Prevent Use of Commands With Minuscule Argument To Bot Commands In #yelling * Update yelling.py --- uqcsbot/yelling.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/uqcsbot/yelling.py b/uqcsbot/yelling.py index a2cb7f16..cb994e13 100644 --- a/uqcsbot/yelling.py +++ b/uqcsbot/yelling.py @@ -3,6 +3,8 @@ from discord.ext import commands from random import choice, random import re +from urllib.request import urlopen +from urllib.error import URLError from uqcsbot.bot import UQCSBot from uqcsbot.models import YellingBans @@ -161,9 +163,14 @@ def clean_text(self, message: str) -> str: ) # slightly more permissive version of discord's url regex, matches absolutely anything between http(s):// and whitespace - text = re.sub( - r"https?:\/\/[^\s]+", lambda m: m.group(0).upper(), text, flags=re.UNICODE - ) + for url in re.findall(r"https?:\/\/[^\s]+", text, flags=re.UNICODE): + try: + resp = urlopen(url) + except (ValueError, URLError): + continue + if 400 <= resp.code <= 499: + continue + text = text.replace(url, url.upper()) text = text.replace(">", ">").replace("<", "<").replace("&", "&") From 7a58fd0a64642c8b0e1ad49977a25f3b01ba5fff Mon Sep 17 00:00:00 2001 From: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> Date: Sun, 27 Aug 2023 13:37:55 +1000 Subject: [PATCH 23/48] update deps and gitignore (#159) --- .gitignore | 3 +++ poetry.lock | 64 +++++------------------------------------------------ 2 files changed, 9 insertions(+), 58 deletions(-) diff --git a/.gitignore b/.gitignore index a1eff683..7dc06e1e 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,6 @@ dmypy.json *.db **/.DS_STORE .vscode/* + +# (per-user) configs for pyright +pyrightconfig.json diff --git a/poetry.lock b/poetry.lock index f3713986..212ff96d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "aio-mc-rcon" version = "3.2.0" description = "An async library for utilizing remote console on Minecraft Java Edition servers" -category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -16,7 +15,6 @@ files = [ name = "aiohttp" version = "3.8.4" description = "Async http client/server framework (asyncio)" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -125,7 +123,6 @@ speedups = ["Brotli", "aiodns", "cchardet"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -140,7 +137,6 @@ frozenlist = ">=1.1.0" name = "apscheduler" version = "3.10.1" description = "In-process task scheduler with Cron-like capabilities" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -152,7 +148,7 @@ files = [ pytz = "*" setuptools = ">=0.7" six = ">=1.4.0" -tzlocal = ">=2.0,<3.0.0 || >=4.0.0" +tzlocal = ">=2.0,<3.dev0 || >=4.dev0" [package.extras] doc = ["sphinx", "sphinx-rtd-theme"] @@ -170,7 +166,6 @@ zookeeper = ["kazoo"] name = "async-timeout" version = "4.0.2" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -182,7 +177,6 @@ files = [ name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -201,7 +195,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "beautifulsoup4" version = "4.12.2" description = "Screen-scraping library" -category = "main" optional = false python-versions = ">=3.6.0" files = [ @@ -220,7 +213,6 @@ lxml = ["lxml"] name = "black" version = "23.3.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -267,21 +259,19 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2023.5.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, - {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] name = "charset-normalizer" version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -366,7 +356,6 @@ files = [ name = "click" version = "8.1.3" description = "Composable command line interface toolkit" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -381,7 +370,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -393,7 +381,6 @@ files = [ name = "discord-py" version = "2.3.1" description = "A Python wrapper for the Discord API" -category = "main" optional = false python-versions = ">=3.8.0" files = [ @@ -414,7 +401,6 @@ voice = ["PyNaCl (>=1.3.0,<1.6)"] name = "exceptiongroup" version = "1.1.1" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -429,7 +415,6 @@ test = ["pytest (>=6)"] name = "frozenlist" version = "1.3.3" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -513,7 +498,6 @@ files = [ name = "greenlet" version = "2.0.2" description = "Lightweight in-process concurrent programming" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ @@ -587,7 +571,6 @@ test = ["objgraph", "psutil"] name = "humanize" version = "4.7.0" description = "Python humanize utilities" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -602,7 +585,6 @@ tests = ["freezegun", "pytest", "pytest-cov"] name = "icalendar" version = "5.0.7" description = "iCalendar parser/generator" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -618,7 +600,6 @@ pytz = "*" name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -630,7 +611,6 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -642,7 +622,6 @@ files = [ name = "multidict" version = "6.0.4" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -726,7 +705,6 @@ files = [ name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -738,7 +716,6 @@ files = [ name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -753,7 +730,6 @@ setuptools = "*" name = "packaging" version = "23.1" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -765,7 +741,6 @@ files = [ name = "pathspec" version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -777,7 +752,6 @@ files = [ name = "platformdirs" version = "3.8.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -793,7 +767,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest- name = "pluggy" version = "1.2.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -809,7 +782,6 @@ testing = ["pytest", "pytest-benchmark"] name = "psycopg2-binary" version = "2.9.6" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -881,7 +853,6 @@ files = [ name = "pyright" version = "1.1.316" description = "Command line wrapper for pyright" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -900,7 +871,6 @@ dev = ["twine (>=3.4.1)"] name = "pytest" version = "7.4.0" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -923,7 +893,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-datafiles" version = "3.0.0" description = "py.test plugin to create a 'tmp_path' containing predefined files/directories." -category = "dev" optional = false python-versions = "*" files = [ @@ -938,7 +907,6 @@ pytest = ">=3.6" name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -953,7 +921,6 @@ six = ">=1.5" name = "python-dotenv" version = "1.0.0" description = "Read key-value pairs from a .env file and set them as environment variables" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -968,7 +935,6 @@ cli = ["click (>=5.0)"] name = "pytz" version = "2023.3" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -980,7 +946,6 @@ files = [ name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1030,7 +995,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1052,7 +1016,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "setuptools" version = "68.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1069,7 +1032,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1081,7 +1043,6 @@ files = [ name = "soupsieve" version = "2.4.1" description = "A modern CSS selector implementation for Beautiful Soup." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1093,7 +1054,6 @@ files = [ name = "sqlalchemy" version = "2.0.17" description = "Database Abstraction Library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1141,7 +1101,7 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", 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\""} +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} psycopg2-binary = {version = "*", optional = true, markers = "extra == \"postgresql_psycopg2binary\""} typing-extensions = ">=4.2.0" @@ -1173,7 +1133,6 @@ sqlcipher = ["sqlcipher3-binary"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1185,7 +1144,6 @@ files = [ name = "types-beautifulsoup4" version = "4.12.0.5" description = "Typing stubs for beautifulsoup4" -category = "dev" optional = false python-versions = "*" files = [ @@ -1200,7 +1158,6 @@ types-html5lib = "*" name = "types-html5lib" version = "1.1.11.14" description = "Typing stubs for html5lib" -category = "dev" optional = false python-versions = "*" files = [ @@ -1212,7 +1169,6 @@ files = [ name = "types-python-dateutil" version = "2.8.19.13" description = "Typing stubs for python-dateutil" -category = "dev" optional = false python-versions = "*" files = [ @@ -1224,7 +1180,6 @@ files = [ name = "types-pytz" version = "2023.3.0.0" description = "Typing stubs for pytz" -category = "dev" optional = false python-versions = "*" files = [ @@ -1236,7 +1191,6 @@ files = [ name = "types-requests" version = "2.31.0.1" description = "Typing stubs for requests" -category = "dev" optional = false python-versions = "*" files = [ @@ -1251,7 +1205,6 @@ types-urllib3 = "*" name = "types-urllib3" version = "1.26.25.13" description = "Typing stubs for urllib3" -category = "dev" optional = false python-versions = "*" files = [ @@ -1263,7 +1216,6 @@ files = [ name = "typing-extensions" version = "4.7.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1275,7 +1227,6 @@ files = [ name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ @@ -1287,7 +1238,6 @@ files = [ name = "tzlocal" version = "5.0.1" description = "tzinfo object for the local timezone" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1305,7 +1255,6 @@ devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pyte name = "urllib3" version = "2.0.3" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1323,7 +1272,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "yarl" version = "1.9.2" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ From f62c6c050a93caba43d60a8287f48b493eedb331 Mon Sep 17 00:00:00 2001 From: bradleysigma <42644678+bradleysigma@users.noreply.github.com> Date: Thu, 31 Aug 2023 10:20:29 +1000 Subject: [PATCH 24/48] Better Detection of Emoji in #yelling (#157) * timeout members who don't yell in #yelling * Formatting Etc. * Update yelling.py * Update yelling.py * Update yelling.py * Create Decorator to Prevent Use of Commands With Minuscule Argument To Bot Commands In #yelling * Update yelling.py * Update yelling.py * Update yelling.py --------- Co-authored-by: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> --- uqcsbot/yelling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uqcsbot/yelling.py b/uqcsbot/yelling.py index cb994e13..740efee7 100644 --- a/uqcsbot/yelling.py +++ b/uqcsbot/yelling.py @@ -156,7 +156,7 @@ def clean_text(self, message: str) -> str: # ignore emoji and links text = re.sub( - r":[\w\-\+\~]+:", + r"<(?Pa?):(?P\w{2,32}):(?P\d{18,22})>", lambda m: m.group(0).upper(), message, flags=re.UNICODE, From 8de4344f412cc279bf5ec16db1107a0cd829a36b Mon Sep 17 00:00:00 2001 From: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> Date: Mon, 4 Sep 2023 18:40:31 +1000 Subject: [PATCH 25/48] fix broken yelling exemptor (#162) * fix broken yelling exemptor * black --- pyproject.toml | 3 ++- uqcsbot/cog.py | 8 ++++++++ uqcsbot/yelling.py | 25 ++++++++++++++----------- 3 files changed, 24 insertions(+), 12 deletions(-) create mode 100644 uqcsbot/cog.py diff --git a/pyproject.toml b/pyproject.toml index ab0935d9..18bc2814 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,4 +54,5 @@ exclude = [ "**/working_on.py", "**/utils/command_utils.py", "**/utils/snailrace_utils.py", -] \ No newline at end of file +] + diff --git a/uqcsbot/cog.py b/uqcsbot/cog.py new file mode 100644 index 00000000..9339008b --- /dev/null +++ b/uqcsbot/cog.py @@ -0,0 +1,8 @@ +from discord.ext import commands + +from uqcsbot.bot import UQCSBot + + +class UQCSBotCog(commands.Cog): + def __init__(self, bot: UQCSBot): + self.bot = bot diff --git a/uqcsbot/yelling.py b/uqcsbot/yelling.py index 740efee7..abbe005a 100644 --- a/uqcsbot/yelling.py +++ b/uqcsbot/yelling.py @@ -7,6 +7,7 @@ from urllib.error import URLError from uqcsbot.bot import UQCSBot +from uqcsbot.cog import UQCSBotCog from uqcsbot.models import YellingBans from datetime import timedelta @@ -17,39 +18,41 @@ def yelling_exemptor(input_args: List[str] = ["text"]) -> Callable[..., Any]: def handler(func: Callable[..., Any]): @wraps(func) async def wrapper( - cogself: commands.Cog, *args: List[Any], **kwargs: Dict[str, Any] + cogself: UQCSBotCog, *args: List[Any], **kwargs: Dict[str, Any] ): - bot = cogself.bot # type: ignore + bot = cogself.bot interaction = None text = "".join([str(kwargs.get(i, "") or "") for i in input_args]) if text == "": - await func(bot, *args, **kwargs) + await func(cogself, *args, **kwargs) return for a in args: if isinstance(a, discord.interactions.Interaction): interaction = a break if interaction is None: - await func(bot, *args, **kwargs) + await func(cogself, *args, **kwargs) return if not hasattr(interaction, "channel"): - await func(bot, *args, **kwargs) + await func(cogself, *args, **kwargs) return if interaction.channel is None: - await func(bot, *args, **kwargs) + await func(cogself, *args, **kwargs) return if interaction.channel.type != discord.ChannelType.text: - await func(bot, *args, **kwargs) + await func(cogself, *args, **kwargs) return if interaction.channel.name != "yelling": - await func(bot, *args, **kwargs) + await func(cogself, *args, **kwargs) return if not Yelling.contains_lowercase(text): - await func(bot, *args, **kwargs) + await func(cogself, *args, **kwargs) return - await interaction.response.send_message(str(discord.utils.get(bot.emojis, name="disapproval") or "")) # type: ignore + await interaction.response.send_message( + str(discord.utils.get(bot.emojis, name="disapproval") or "") + ) if isinstance(interaction.user, discord.Member): - await Yelling.external_handle_bans(bot, interaction.user) # type: ignore + await Yelling.external_handle_bans(bot, interaction.user) return wrapper From cd5b338582a6f0cb4107b8bffe0aa24c2303604c Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Tue, 26 Sep 2023 20:11:24 +1000 Subject: [PATCH 26/48] Added Whatweekisit Flavour Text (#163) * Added random whatweekisit flavour text * Styled to make black happy * Removed some repitition --- uqcsbot/whatweekisit.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/uqcsbot/whatweekisit.py b/uqcsbot/whatweekisit.py index 6b996bae..f655b3e9 100644 --- a/uqcsbot/whatweekisit.py +++ b/uqcsbot/whatweekisit.py @@ -5,6 +5,7 @@ from zoneinfo import ZoneInfo from datetime import datetime, timedelta from math import ceil +from random import choice from bs4 import BeautifulSoup from discord.ext import commands @@ -158,11 +159,29 @@ async def whatweekisit(self, interaction: discord.Interaction, date: Optional[st else: semester_name, week_name, weekday = semester_tuple - message = ( - "The week we're in is:" - if date == None - else f"The week of {date} is in:" - ) + if date != None: + message = f"The week of {date} is in:" + else: + message = choice( + [ + "The week we're in is:", + "The current week is:", + "Currently, the week is:", + "Hey, look at the time:", + f"Can you believe that it's already {week_name}:", + "Time flies when you're having fun:", + "Maybe time's just a construct of human perception:", + "Time waits for noone:", + "This week is:", + "It is currently:", + "The week is", + "The week we're currently in is:", + f"Right now we are in:", + "Good heavens, would you look at the time:", + "What's the time, mister wolf? It's:", + ] + ) + message += f"\n> {weekday}, {week_name} of {semester_name}" await interaction.edit_original_response(content=message) From c61622528754fd383e618716f3a5247111fb5555 Mon Sep 17 00:00:00 2001 From: James Dearlove <39483549+JamesDearlove@users.noreply.github.com> Date: Tue, 17 Oct 2023 21:03:00 +1000 Subject: [PATCH 27/48] Added logging for holidays cog (#169) * Added logging for holidays cog * Extra log was not necessary * Caught out by the style guide once more --- uqcsbot/holidays.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/uqcsbot/holidays.py b/uqcsbot/holidays.py index d4eaea07..e8a2f957 100644 --- a/uqcsbot/holidays.py +++ b/uqcsbot/holidays.py @@ -113,8 +113,11 @@ async def holiday(self): Posts a random celebratory day on #general from https://www.timeanddate.com/holidays/fun/ """ + logging.info("Running daily holiday task") + holiday = get_holiday() if holiday is None: + logging.info("No holiday was found for today") return general_channel = discord.utils.get( From 05c1e052d0ed9ed0cfed2e522b255407bfc4c01d Mon Sep 17 00:00:00 2001 From: bradleysigma <42644678+bradleysigma@users.noreply.github.com> Date: Sat, 28 Oct 2023 09:28:08 +1000 Subject: [PATCH 28/48] Remove Ununimplemented (#168) * Remove Ununimplemented * Delete cookbook.py --------- Co-authored-by: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> --- unimplemented/calendar.py | 74 --------------- unimplemented/channel_log.py | 13 --- unimplemented/coin.py | 20 ---- unimplemented/cookbook.py | 10 -- unimplemented/dominos.py | 116 ----------------------- unimplemented/emoji_log.py | 37 -------- unimplemented/emojify.py | 173 ----------------------------------- unimplemented/history.py | 56 ------------ unimplemented/id.py | 9 -- unimplemented/link.py | 163 --------------------------------- unimplemented/pastexams.py | 67 -------------- unimplemented/wavie.py | 22 ----- unimplemented/whoami.py | 18 ---- unimplemented/xkcd.py | 125 ------------------------- 14 files changed, 903 deletions(-) delete mode 100644 unimplemented/calendar.py delete mode 100644 unimplemented/channel_log.py delete mode 100644 unimplemented/coin.py delete mode 100644 unimplemented/cookbook.py delete mode 100644 unimplemented/dominos.py delete mode 100644 unimplemented/emoji_log.py delete mode 100644 unimplemented/emojify.py delete mode 100644 unimplemented/history.py delete mode 100644 unimplemented/id.py delete mode 100644 unimplemented/link.py delete mode 100644 unimplemented/pastexams.py delete mode 100644 unimplemented/wavie.py delete mode 100644 unimplemented/whoami.py delete mode 100644 unimplemented/xkcd.py diff --git a/unimplemented/calendar.py b/unimplemented/calendar.py deleted file mode 100644 index a14806b0..00000000 --- a/unimplemented/calendar.py +++ /dev/null @@ -1,74 +0,0 @@ -from datetime import datetime -from icalendar import Calendar, Event -from uuid import uuid4 as uuid -from uqcsbot import bot, Command -from uqcsbot.utils.command_utils import loading_status, success_status -from uqcsbot.utils.uq_course_utils import (get_course_assessment, - get_parsed_assessment_due_date, - HttpException, - CourseNotFoundException, - ProfileNotFoundException, - DateSyntaxException) - -# Maximum number of courses supported by !calendar to reduce call abuse. -COURSE_LIMIT = 6 - - -def get_calendar(assessment): - """ - Returns a compiled calendar containing the given assessment. - """ - calendar = Calendar() - for assessment_item in assessment: - course, task, due_date, weight = assessment_item - event = Event() - event['uid'] = str(uuid()) - event['summary'] = f'{course} ({weight}): {task}' - try: - start_datetime, end_datetime = get_parsed_assessment_due_date(assessment_item) - except DateSyntaxException as e: - bot.logger.error(e.message) - # If we can't parse a date, set its due date to today - # and let the user know through its summary. - # TODO(mitch): Keep track of these instances to attempt to accurately - # parse them in future. Will require manual detection + parsing. - start_datetime = end_datetime = datetime.today() - event['summary'] = ("WARNING: DATE PARSING FAILED\n" - "Please manually set date for event!\n" - "The provided due date from UQ was" - + f" '{due_date}\'. {event['summary']}") - event.add('dtstart', start_datetime) - event.add('dtend', end_datetime) - calendar.add_component(event) - return calendar.to_ical() - - -@bot.on_command('calendar') -@success_status -@loading_status -def handle_calendar(command: Command): - """ - `!calendar [COURSE CODE 2] ...` - Returns a compiled - calendar containing all the assessment for a given list of course codes. - """ - channel = bot.channels.get(command.channel_id) - course_names = command.arg.split() if command.has_arg() else [channel.name] - - if len(course_names) > COURSE_LIMIT: - bot.post_message(channel, f'Cannot process more than {COURSE_LIMIT} courses.') - return - - try: - assessment = get_course_assessment(course_names) - except HttpException as e: - bot.logger.error(e.message) - bot.post_message(channel, f'An error occurred, please try again.') - return - except (CourseNotFoundException, ProfileNotFoundException) as e: - bot.post_message(channel, e.message) - return - - user_direct_channel = bot.channels.get(command.user_id) - bot.api.files.upload(title='Importable calendar containing your assessment!', - channels=user_direct_channel.id, filetype='text/calendar', - filename='assessment.ics', file=get_calendar(assessment)) diff --git a/unimplemented/channel_log.py b/unimplemented/channel_log.py deleted file mode 100644 index d1c5c175..00000000 --- a/unimplemented/channel_log.py +++ /dev/null @@ -1,13 +0,0 @@ -from uqcsbot import bot - - -@bot.on("channel_created") -def channel_log(evt: dict): - """ - Notes when channels are created in #uqcs-meta - - @no_help - """ - bot.post_message(bot.channels.get("uqcs-meta"), - 'New Channel Created: ' - + f'<#{evt.get("channel").get("id")}|{evt.get("channel").get("name")}>') diff --git a/unimplemented/coin.py b/unimplemented/coin.py deleted file mode 100644 index ca5d2ae4..00000000 --- a/unimplemented/coin.py +++ /dev/null @@ -1,20 +0,0 @@ -from random import choice -from uqcsbot import bot, Command - - -@bot.on_command("coin") -def handle_coin(command: Command): - """ - `!coin [number]` - Flips 1 or more coins. - """ - if command.has_arg() and command.arg.isnumeric(): - flips = min(max(int(command.arg), 1), 500) - else: - flips = 1 - - response = [] - emoji = (':heads:', ':tails:') - for i in range(flips): - response.append(choice(emoji)) - - bot.post_message(command.channel_id, "".join(response)) diff --git a/unimplemented/cookbook.py b/unimplemented/cookbook.py deleted file mode 100644 index 34d5948a..00000000 --- a/unimplemented/cookbook.py +++ /dev/null @@ -1,10 +0,0 @@ -from uqcsbot import bot, Command - - -@bot.on_command("cookbook") -def handle_cookbook(command: Command): - """ - `!cookbook` - Returns the URL of the UQCS student-compiled cookbook (pdf). - """ - bot.post_message(command.channel_id, "It's A Cookbook!\n" - "https://github.com/UQComputingSociety/cookbook") diff --git a/unimplemented/dominos.py b/unimplemented/dominos.py deleted file mode 100644 index a75318be..00000000 --- a/unimplemented/dominos.py +++ /dev/null @@ -1,116 +0,0 @@ -import argparse -from uqcsbot import bot, Command -from bs4 import BeautifulSoup -from datetime import datetime -from requests.exceptions import RequestException -from typing import List -from uqcsbot.utils.command_utils import loading_status, UsageSyntaxException -import requests - -MAX_COUPONS = 10 # Prevents abuse -COUPONESE_DOMINOS_URL = 'https://www.couponese.com/store/dominos.com.au/' - - -class Coupon: - def __init__(self, code: str, expiry_date: str, description: str) -> None: - self.code = code - self.expiry_date = expiry_date - self.description = description - - def is_valid(self) -> bool: - try: - expiry_date = datetime.strptime(self.expiry_date, '%Y-%m-%d') - now = datetime.now() - return all([expiry_date.year >= now.year, expiry_date.month >= now.month, - expiry_date.day >= now.day]) - except ValueError: - return True - - def keyword_matches(self, keyword: str) -> bool: - return keyword.lower() in self.description.lower() - - -@bot.on_command("dominos") -@loading_status -def handle_dominos(command: Command): - """ - `!dominos [--num] N [--expiry] ` - Returns a list of dominos coupons (default: 5 | max: 10) - """ - command_args = command.arg.split() if command.has_arg() else [] - - parser = argparse.ArgumentParser() - - def usage_error(*args, **kwargs): - raise UsageSyntaxException() - parser.error = usage_error # type: ignore - parser.add_argument('-n', '--num', default=5, type=int) - parser.add_argument('-e', '--expiry', action='store_true') - parser.add_argument('keywords', nargs='*') - - args = parser.parse_args(command_args) - coupons_amount = min(args.num, MAX_COUPONS) - coupons = get_coupons(coupons_amount, args.expiry, args.keywords) - - message = "" - for coupon in coupons: - message += f"Code: *{coupon.code}* - {coupon.description}\n" - bot.post_message(command.channel_id, message) - - -def filter_coupons(coupons: List[Coupon], keywords: List[str]) -> List[Coupon]: - """ - Filters coupons iff a keyword is found in the description. - """ - return [coupon for coupon in coupons if - any(coupon.keyword_matches(keyword) for keyword in keywords)] - - -def get_coupons(n: int, ignore_expiry: bool, keywords: List[str]) -> List[Coupon]: - """ - Returns a list of n Coupons - """ - - coupon_page = get_coupon_page() - if coupon_page is None: - return None - - coupons = get_coupons_from_page(coupon_page) - - if not ignore_expiry: - coupons = [coupon for coupon in coupons if coupon.is_valid()] - - if keywords: - coupons = filter_coupons(coupons, keywords) - return coupons[:n] - - -def get_coupons_from_page(coupon_page: bytes) -> List[Coupon]: - """ - Strips results from html page and returns a list of Coupon(s) - """ - soup = BeautifulSoup(coupon_page, 'html.parser') - soup_coupons = soup.find_all(class_="ov-coupon") - - coupons = [] - - for soup_coupon in soup_coupons: - expiry_date_str = soup_coupon.find(class_='ov-expiry').get_text(strip=True) - description = soup_coupon.find(class_='ov-desc').get_text(strip=True) - code = soup_coupon.find(class_='ov-code').get_text(strip=True) - coupon = Coupon(code, expiry_date_str, description) - coupons.append(coupon) - - return coupons - - -def get_coupon_page() -> bytes: - """ - Gets the coupon page HTML - """ - try: - response = requests.get(COUPONESE_DOMINOS_URL) - return response.content - except RequestException as e: - bot.logger.error(e.response.content) - return None diff --git a/unimplemented/emoji_log.py b/unimplemented/emoji_log.py deleted file mode 100644 index aedb8d12..00000000 --- a/unimplemented/emoji_log.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -Logs emoji addition/removal to emoji-request for audit purposes -""" -from uqcsbot import bot - - -@bot.on("emoji_changed") -def emoji_log(evt: dict): - """ - Notes when emojis are added or deleted. - - @no_help - """ - emoji_request = bot.channels.get("emoji-request") - subtype = evt.get("subtype") - - if subtype == 'add': - name = evt["name"] - value = evt["value"] - - if value.startswith('alias:'): - _, alias = value.split('alias:') - - bot.post_message(emoji_request, - f'Emoji alias added: `:{name}:` :arrow_right: `:{alias}:` (:{name}:)') - - else: - message = bot.post_message(emoji_request, f'Emoji added: :{name}: (`:{name}:`)') - bot.api.reactions.add(channel=message["channel"], - timestamp=message["ts"], name=name) - - elif subtype == 'remove': - names = evt.get("names") - removed = ', '.join(f'`:{name}:`' for name in names) - plural = 's' if len(names) > 1 else '' - - bot.post_message(emoji_request, f'Emoji{plural} removed: {removed}') diff --git a/unimplemented/emojify.py b/unimplemented/emojify.py deleted file mode 100644 index af131ec3..00000000 --- a/unimplemented/emojify.py +++ /dev/null @@ -1,173 +0,0 @@ -from uqcsbot import bot, Command -from uqcsbot.utils.command_utils import loading_status -from typing import Dict, List - -from collections import defaultdict -from random import shuffle, choice - - -@bot.on_command("emojify") -@loading_status -def handle_emojify(command: Command): - ''' - `!emojify text` - converts text to emoji. - ''' - master: Dict[str, List[str]] = defaultdict(lambda: [":grey_question:"]) - - # letters - master['A'] = [":adobe:", ":airbnb:", ":amazon:", ":anarchism:", - ":arch:", ":atlassian:", ":office_access:", ":capital_a_agile:", - choice([":card-ace-clubs:", ":card-ace-diamonds:", - ":card-ace-hearts:", ":card-ace-spades:"])] - master['B'] = [":bhinking:", ":bitcoin:", ":blutes:"] - master['C'] = [":c:", ":clang:", ":cplusplus:", ":copyright:", ":clipchamp:", - ":clipchamp_old:"] - master['D'] = [":d:", ":disney:", ":deloitte:"] - master['E'] = [":ecorp:", ":emacs:", ":erlang:", ":ie10:", ":thonk_slow:", ":edge:", - ":expedia_group:"] - master['F'] = [":f:", ":facebook:", ":flutter:", ":figma:"] - master['G'] = [":g+:", ":google:", ":nintendo_gamecube:", ":gatsbyjs:", ":gmod:"] - master['H'] = [":hackerrank:", ":homejoy:"] - master['I'] = [":information_source:", ":indoorooshs:"] - master['J'] = [choice([":card-jack-clubs:", ":card-jack-diamonds:", - ":card-jack-hearts:", ":card-jack-spades:"])] - master['K'] = [":kickstarter:", ":kotlin:", - choice([":card-king-clubs:", ":card-king-diamonds:", - ":card-king-hearts:", ":card-king-spades:"])] - master['L'] = [":l:", ":lime:", ":l_plate:", ":ti_nekro:"] - master['M'] = [":gmail:", ":maccas:", ":mcgrathnicol:", ":melange_mining:", ":mtg:", ":mxnet:", - ":jmod:"] - master['N'] = [":nano:", ":neovim:", ":netscape_navigator:", ":notion:", - ":nginx:", ":nintendo_64:", ":office_onenote:", ":netflix-n:"] - master['O'] = [":office_outlook:", ":oracle:", ":o_:", ":tetris_o:", ":ubuntu:", - choice([":portal_blue:", ":portal_orange:"])] - master['P'] = [":auspost:", ":office_powerpoint:", ":office_publisher:", - ":pinterest:", ":paypal:", ":producthunt:", ":uqpain:"] - master['Q'] = [":quora:", ":quantium:", choice([":card-queen-clubs:", ":card-queen-diamonds:", - ":card-queen-hearts:", ":card-queen-spades:"])] - master['R'] = [":r-project:", ":rust:", ":redroom:", ":registered:"] - master['S'] = [":s:", ":skedulo:", ":stanford:", ":stripe_s:", ":sublime:", ":tetris_s:"] - master['T'] = [":tanda:", choice([":telstra:", ":telstra-pink:"]), - ":tesla:", ":tetris_t:", ":torchwood:", ":tumblr:", ":nyt:"] - master['U'] = [":uber:", ":uqu:", ":the_horns:", ":proctoru:", ":ubiquiti:"] - master['V'] = [":vim:", ":vue:", ":vuetify:", ":v:"] - master['W'] = [":office_word:", ":washio:", ":wesfarmers:", ":westpac:", - ":weyland_consortium:", ":wikipedia_w:", ":woolworths:"] - master['X'] = [":atlassian_old:", ":aginicx:", ":sonarr:", ":x-files:", ":xbox:", - ":x:", ":flag-scotland:", ":office_excel:"] - master['Y'] = [":hackernews:"] - master['Z'] = [":tetris_z:"] - - # numbers - master['0'] = [":chrome:", ":suncorp:", ":disney_zero:", ":firefox:", - ":mars:", ":0_:", choice([":dvd:", ":cd:"])] - master['1'] = [":techone:", ":testtube:", ":thonk_ping:", ":first_place_medal:", - ":critical_fail:", ":slack_unread_1:"] - master['2'] = [":second_place_medal:", choice([":card-2-clubs:", ":card-2-diamonds:", - ":card-2-hearts:", ":card-2-spades:"])] - master['3'] = [":css:", ":third_place_medal:", choice([":card-3-clubs:", ":card-3-diamonds:", - ":card-3-hearts:", ":card-3-spades:"])] - master['4'] = [choice([":card-4-clubs:", ":card-4-diamonds:", - ":card-4-hearts:"]), ":card-4-spades:"] - master['5'] = [":html:", choice([":card-5-clubs:", ":card-5-diamonds:", - ":card-5-hearts:", ":card-5-spades:"])] - master['6'] = [choice([":card-6-clubs:", ":card-6-diamonds:", - ":card-6-hearts:", ":card-6-spades:"])] - master['7'] = [choice([":card-7-clubs:", ":card-7-diamonds:", - ":card-7-hearts:", ":card-7-spades:"])] - master['8'] = [":8ball:", choice([":card-8-clubs:", ":card-8-diamonds:", - ":card-8-hearts:", ":card-8-spades:"])] - master['9'] = [choice([":card-9-clubs:", ":card-9-diamonds:", - ":card-9-hearts:", ":card-9-spades:"])] - - # whitespace - master[' '] = [":whitespace:"] - master['\n'] = ["\n"] - - # other ascii characters (sorted by ascii value) - master['!'] = [":exclamation:"] - master['"'] = [choice([":ldquo:", ":rdquo:"]), ":pig_nose:"] - master['\''] = [":apostrophe:"] - master['#'] = [":slack_old:", ":csharp:"] - master['$'] = [":thonk_money:", ":moneybag:"] - # '&' converts to '&' - master['&'] = [":ampersand:", ":dnd:"] - master['('] = [":lparen:"] - master[')'] = [":rparen:"] - master['*'] = [":day:", ":nab:", ":youtried:", ":msn_star:", ":rune_prayer:", ":wolfram:", - ":shuriken:", ":mtg_s:", ":aoc:", ":jetstar:"] - master['+'] = [":tf2_medic:", ":flag-ch:", ":flag-england:"] - master['-'] = [":no_entry:"] - master['.'] = [":full_stop_big:"] - master[','] = [":comma:"] - master['/'] = [":slash:"] - master[';'] = [":semi-colon:"] - # '>' converts to '>' - master['>'] = [":accenture:", ":implying:", ":plex:", ":powershell:"] - master['?'] = [":question:"] - master['@'] = [":whip:"] - master['^'] = [":this:", ":typographical_carrot:", ":arrow_up:", - ":this_but_it's_an_actual_caret:"] - master['~'] = [":wavy_dash:"] - - # slack/uqcsbot convert the following to other symbols - - # greek letters - # 'Α' converts to 'A' - master['Α'] = [":alpha:"] - # 'Β' converts to 'B' - master['Β'] = [":beta:"] - # 'Δ' converts to 'D' - master['Δ'] = [":optiver:"] - # 'Λ' converts to 'L' - master['Λ'] = [":halflife:", ":haskell:", ":lambda:", ":racket:", - choice([":uqcs:", ":scrollinguqcs:", ":scrollinguqcs_alt:", ":uqcs_mono:"])] - # 'Π' converts to 'P' - master['Π'] = [":pi:"] - # 'Φ' converts to 'PH' - master['Φ'] = [":phyrexia_blue:"] - # 'Σ' converts to 'S' - master['Σ'] = [":polymathian:", ":sigma:"] - - # other symbols (sorted by unicode value) - # '…' converts to '...' - master['…'] = [":lastpass:"] - # '€' converts to 'EUR' - master['€'] = [":martian_euro:"] - # '√' converts to '[?]' - master['√'] = [":sqrt:"] - # '∞' converts to '[?]' - master['∞'] = [":arduino:", ":visualstudio:", ":infinitely:"] - # '∴' converts to '[?]' - master['∴'] = [":julia:"] - - master['人'] = [":人:"] - - master[chr(127)] = [":delet_this:"] - - text = "" - if command.has_arg(): - text = command.arg.upper() - # revert HTML conversions - text = text.replace(">", ">") - text = text.replace("<", "<") - text = text.replace("&", "&") - - lexicon = {} - for character in set(text+'…'): - full, part = divmod((text+'…').count(character), len(master[character])) - shuffle(master[character]) - lexicon[character] = full * master[character] + master[character][:part] - shuffle(lexicon[character]) - - ellipsis = lexicon['…'].pop() - - response = "" - for character in text: - emoji = lexicon[character].pop() - if len(response + emoji + ellipsis) > 4000: - response += ellipsis - break - response += emoji - - bot.post_message(command.channel_id, response) diff --git a/unimplemented/history.py b/unimplemented/history.py deleted file mode 100644 index 2dd62122..00000000 --- a/unimplemented/history.py +++ /dev/null @@ -1,56 +0,0 @@ -from uqcsbot import bot -from datetime import datetime -from pytz import timezone, utc -from random import choice - - -class Pin: - """ - Class for pins, with channel, age in years, user and pin text - """ - def __init__(self, channel: str, years: int, user: str, text: str): - self.channel = channel - self.years = years - self.user = user - self.text = text - - def message(self) -> str: - return (f"On this day, {self.years} years ago, <@{self.user}> said" - f"\n>>>{self.text}") - - def origin(self): - return bot.channels.get(self.channel) - - -@bot.on_schedule('cron', hour=12, minute=0, timezone='Australia/Brisbane') -def daily_history() -> None: - """ - Selets a random pin that was posted on this date some years ago, - and reposts it in the same channel - """ - anniversary = [] - today = datetime.now(utc).astimezone(timezone('Australia/Brisbane')).date() - - # for every channel - for channel in bot.api.conversations.list(types="public_channel")['channels']: - # skip archived channels - if channel['is_archived']: - continue - - for pin in bot.api.pins.list(channel=channel['id'])['items']: - # messily get the date the pin was originally posted - pin_date = (datetime.fromtimestamp(int(float(pin['message']['ts'])), tz=utc) - .astimezone(timezone('Australia/Brisbane')).date()) - # if same date as today - if pin_date.month == today.month and pin_date.day == today.day: - # add pin to possibilities - anniversary.append(Pin(channel=channel['name'], years=today.year-pin_date.year, - user=pin['message']['user'], text=pin['message']['text'])) - - # if no pins were posted on this date, do nothing - if not anniversary: - return - - # randomly select a pin, and post it in the original channel - selected = choice(anniversary) - bot.post_message(selected.origin(), selected.message()) diff --git a/unimplemented/id.py b/unimplemented/id.py deleted file mode 100644 index 8d7ff00a..00000000 --- a/unimplemented/id.py +++ /dev/null @@ -1,9 +0,0 @@ -from uqcsbot import bot, Command - - -@bot.on_command("id") -def handle_id(command: Command): - """ - `!id` - Returns the calling user's Slack ID. - """ - bot.post_message(command.channel_id, f'You are Number `{command.user_id}`') diff --git a/unimplemented/link.py b/unimplemented/link.py deleted file mode 100644 index 83674dc0..00000000 --- a/unimplemented/link.py +++ /dev/null @@ -1,163 +0,0 @@ -from argparse import ArgumentParser -from enum import Enum -from typing import Optional, Tuple - -from slackblocks import Attachment, Color, SectionBlock -from sqlalchemy.exc import NoResultFound - -from uqcsbot import bot, Command -from uqcsbot.models import Link -from uqcsbot.utils.command_utils import loading_status - - -class LinkScope(Enum): - """ - Possible requested scopes for setting or retrieving a link. - """ - CHANNEL = "channel" - GLOBAL = "global" - - -class SetResult(Enum): - """ - Possible outcomes of the set link operation. - """ - NEEDS_OVERRIDE = "Link already exists, use `-f` to override" - OVERRIDE_SUCCESS = "Successfully overrode link" - NEW_LINK_SUCCESS = "Successfully added link" - - -def set_link_value(key: str, value: str, channel: str, - override: bool, link_scope: Optional[LinkScope] = None) -> Tuple[SetResult, str]: - """ - Sets a corresponding value for a particular key. Keys are set to global by default but this can - be overridden by passing the channel flag. Existing links can only be overridden if the - override flag is passed. - :param key: the lookup key for users to search the value by - :param value: the value to associate with the key - :param channel: the name of the channel the set operation was initiated in - :param link_scope: defines the scope to set the link in, defaults to global if not provided - :param override: required to be True if an association already exists and needs to be updated - :return: a SetResult status and the value associated with the given key/channel combination - """ - link_channel = channel if link_scope == LinkScope.CHANNEL else None - session = bot.create_db_session() - - try: - exists = session.query(Link).filter(Link.key == key, - Link.channel == link_channel).one() - if exists and not override: - return SetResult.NEEDS_OVERRIDE, exists.value - session.delete(exists) - result = SetResult.OVERRIDE_SUCCESS - except NoResultFound: - result = SetResult.NEW_LINK_SUCCESS - session.add(Link(key=key, channel=link_channel, value=value)) - session.commit() - session.close() - return result, value - - -def get_link_value(key: str, - channel: str, - link_scope: Optional[LinkScope] = None) -> Tuple[Optional[str], Optional[str]]: - """ - Gets the value associated with a given key (and optionally channel). If a channel association - exists, this is returned, otherwise a global association is returned. If no association exists - then None is returned. The default behaviour can be overridden by passing the global flag to - force retrieval of a global association when a channel association exists. - :param key: the key to look up - :param channel: the name of the channel the lookup request was made from - :param link_scope: the requested scope to retrieve the link from (if supplied) - :return: the associated value if an association exists, else None, and the source - (global/channel) if any else None - """ - session = bot.create_db_session() - channel_match = session.query(Link).filter(Link.key == key, - Link.channel == channel).one_or_none() - global_match = session.query(Link).filter(Link.key == key, - Link.channel == None).one_or_none() # noqa: E711 - session.close() - - if link_scope == LinkScope.GLOBAL: - return (global_match.value, "global") if global_match else (None, None) - - if link_scope == LinkScope.CHANNEL: - return (channel_match.value, "channel") if channel_match else (None, None) - - if channel_match: - return channel_match.value, "channel" - - if global_match: - return global_match.value, "global" - - return None, None - - -@bot.on_command('link') -@loading_status -def handle_link(command: Command) -> None: - """ - `!link [-c | -g] [-f] key [value [value ...]]` - Set and retrieve information in a key value - store. Links can be set to be channel specific or global. Links are set as global by default, - and channel specific links are retrieved by default unless overridden with the respective flag. - """ - parser = ArgumentParser("!link", add_help=False) - parser.add_argument("key", type=str, help="Lookup key") - parser.add_argument("value", type=str, help="Value to associate with key", nargs="*") - flag_group = parser.add_mutually_exclusive_group() - flag_group.add_argument("-c", "--channel", action="store_true", dest="channel_flag", - help="Ensure a channel link is retrieved, or none is") - flag_group.add_argument("-g", "--global", action="store_true", dest="global_flag", - help="Ignore channel link and force retrieval of global") - parser.add_argument("-f", "--force-override", action="store_true", dest="override", - help="Must be passed if overriding a link") - - try: - args = parser.parse_args(command.arg.split() if command.has_arg() else []) - except SystemExit: - # Incorrect Usage - return bot.post_message(command.channel_id, "", - attachments=[Attachment(SectionBlock(str(parser.format_help())), - color=Color.YELLOW)._resolve()]) - - channel = bot.channels.get(command.channel_id) - if not channel: - return bot.post_message(command.channel_id, "", attachments=[ - Attachment(SectionBlock("Cannot find channel name, please try again."), - color=Color.YELLOW)._resolve() - ]) - - channel_name = channel.name - - link_scope = LinkScope.CHANNEL if args.channel_flag else \ - LinkScope.GLOBAL if args.global_flag else None - - # Retrieve a link - if not args.value: - link_value, source = get_link_value(key=args.key, - channel=channel_name, - link_scope=link_scope) - channel_text = f" in channel `{channel_name}`" if args.channel_flag else "" - if link_value: - source_text = source if source == 'global' else channel_name - response = f"{args.key} ({source_text}): {link_value}" - else: - response = f"No link found for key: `{args.key}`" + channel_text - color = Color.GREEN if link_value else Color.RED - return bot.post_message(command.channel_id, "", attachments=[ - Attachment(SectionBlock(response), color=color)._resolve() - ]) - - # Set a link - if args.key and args.value: - result, current_value = set_link_value(key=args.key, - channel=channel_name, - value=" ".join(args.value), - override=args.override, - link_scope=link_scope) - color = Color.YELLOW if result == SetResult.NEEDS_OVERRIDE else Color.GREEN - scope = channel_name if args.channel_flag else 'global' - response = f"{args.key} ({scope}): {current_value}" - attachment = Attachment(SectionBlock(response), color=color)._resolve() - bot.post_message(command.channel_id, f"{result.value}:", attachments=[attachment]) diff --git a/unimplemented/pastexams.py b/unimplemented/pastexams.py deleted file mode 100644 index 21cfa759..00000000 --- a/unimplemented/pastexams.py +++ /dev/null @@ -1,67 +0,0 @@ -from uqcsbot import bot, Command -from bs4 import BeautifulSoup -from typing import Iterable, Tuple -import requests -from uqcsbot.utils.command_utils import loading_status - - -@bot.on_command('pastexams') -@loading_status -def handle_pastexams(command: Command): - """ - `!pastexams [COURSE CODE]` - Retrieves past exams for a given course code. - If unspecified, will attempt to find the ECP - for the channel the command was called from. - """ - channel = bot.channels.get(command.channel_id) - course_code = command.arg if command.has_arg() else channel.name - bot.post_message(channel, get_past_exams(course_code)) - - -def get_exam_data(soup: BeautifulSoup) -> Iterable[Tuple[str, str]]: - """ - Takes the soup object of the page and generates each result in the format: - ('year Sem X:', link) - """ - - # The exams are stored in a table with the structure: - # Row 1: A bunch of informational text - # Row 2: Semester information - # Row 3: Links to Exams - # Rows two and three are what we care about. Of those the first column is just a row title so - # we ignore that as well - - exam_table_rows = soup.find('table', class_='maintable').contents - semesters = exam_table_rows[1].find_all('td')[1:] # All columns in row 2 excluding the first - # Gets the content from each td. Text is separated by a
thus result is in the format - # (year,
, 'Sem.x' - semesters = [semester.contents for semester in semesters] - - # Same thing but for links - links = exam_table_rows[2].find_all('td')[1:] - links = [link.find('a')['href'] for link in links] - - for (year, _, semester_id), link in zip(semesters, links): - semester_str = semester_id.replace('.', ' ') - yield f'{year} {semester_str}', link - - -def get_past_exams(course_code: str) -> str: - """ - Gets the past exams for the course with the specified course code. - Returns intuitive error messages if this fails. - """ - url = 'https://www.library.uq.edu.au/exams/papers.php?' - http_response = requests.get(url, params={'stub': course_code}) - - if http_response.status_code != requests.codes.ok: - return "There was a problem getting a response" - - # Check if the course code exists - soup = BeautifulSoup(http_response.content, 'html.parser') - no_course = soup.find('div', class_='page').find('div').contents[0] - if "Sorry. We have not found any past exams for this course" in no_course: - return f"The course code {course_code} did not return any results" - - return '>>>' + '\n'.join((f'*{semester}*: <{link}|PDF>' - for semester, link in get_exam_data(soup))) diff --git a/unimplemented/wavie.py b/unimplemented/wavie.py deleted file mode 100644 index be81b47f..00000000 --- a/unimplemented/wavie.py +++ /dev/null @@ -1,22 +0,0 @@ -from uqcsbot import bot -import logging - - -logger = logging.getLogger(__name__) - - -@bot.on('message') -def wave(evt): - """ - :wave: reacts to "person joined/left this channel" - - @no_help - """ - if evt.get('subtype') not in ['channel_join', 'channel_leave']: - return - chan = bot.channels.get(evt['channel']) - if chan is not None and chan.name == 'announcements': - return - result = bot.api.reactions.add(name='wave', channel=chan.id, timestamp=evt['ts']) - if not result.get('ok'): - logger.error(f"Error adding reaction: {result}") diff --git a/unimplemented/whoami.py b/unimplemented/whoami.py deleted file mode 100644 index 0d1983b7..00000000 --- a/unimplemented/whoami.py +++ /dev/null @@ -1,18 +0,0 @@ -from uqcsbot import bot, Command -from uqcsbot.utils.command_utils import success_status - - -@bot.on_command("whoami") -@success_status -def handle_whoami(command: Command): - """ - `!whoami` - Returns the Slack information for the calling user. - """ - response = bot.api.users.info(user=command.user_id) - if not response['ok']: - message = 'An error occurred, please try again.' - else: - user_info = response['user'] - message = f'Your vital statistics: \n```{user_info}```' - user_direct_channel = bot.channels.get(command.user_id) - bot.post_message(user_direct_channel, message) diff --git a/unimplemented/xkcd.py b/unimplemented/xkcd.py deleted file mode 100644 index eaf491a6..00000000 --- a/unimplemented/xkcd.py +++ /dev/null @@ -1,125 +0,0 @@ -import datetime -import requests -import feedparser -import re -from urllib.parse import quote -from uqcsbot import bot, Command -from uqcsbot.utils.command_utils import loading_status - - -# HTTP Endpoints -XKCD_BASE_URL = "https://xkcd.com/" -XKCD_RSS_URL = "https://xkcd.com/rss.xml" -RELEVANT_XKCD_URL = 'https://relevantxkcd.appspot.com/process' - - -def get_by_id(comic_number: int) -> str: - """ - Gets an xkcd comic based on its unique ID/sequence number. - :param comic_number: the ID number of the xkcd comic to retrieve. - :return: a response containing either a comic URL or an error message. - """ - if comic_number <= 0: - return "Invalid xkcd ID, it must be a positive integer." - url = f"{XKCD_BASE_URL}{str(comic_number)}" - response = requests.get(url) - if response.status_code != 200: - return "Could not retrieve an xkcd with that ID (are there even that many?)" - return url - - -def get_by_search_phrase(search_phrase: str) -> str: - """ - Uses the site relevantxkcd.appspot.com to identify the - most appropriate xkcd comic based on the phrase provided. - :param search_phrase: the phrase to find an xkcd comic related to. - :return: the URL of the most relevant comic for that search phrase. - """ - params = {"action": "xkcd", "query": quote(search_phrase)} - response = requests.get(RELEVANT_XKCD_URL, params=params) - # Response consists of a newline delimited list, with two irrelevant first parameters - relevant_comics = response.content.decode().split("\n")[2:] - # Each line consists of "comic_id image_url" - best_response = relevant_comics[0].split(" ") - comic_number = int(best_response[0]) - return get_by_id(comic_number) - - -def get_latest() -> str: - """ - Gets the most recently published xkcd comic by examining the RSS feed. - :return: the URL to the latest xkcd comic. - """ - rss = feedparser.parse(XKCD_RSS_URL) - entries = rss['entries'] - if len(entries) > 0: - i = 0 - latest = entries[i]['guid'] - if not re.match(r"https://xkcd\.com/\d+/", latest): - i += 1 - latest = entries[i]['guid'] - else: - latest = 'https://xkcd.com/2200/' - return latest - - -def is_id(argument: str) -> bool: - """ - Determines whether the given argument is a valid id (i.e. an integer). - :param argument: the string argument to evaluate - :return: true if the argument can be evaluated as an interger, false otherwise - """ - try: - int(argument) - except ValueError: - return False - else: - return True - - -@bot.on_command('xkcd') -@loading_status -def handle_xkcd(command: Command) -> None: - """ - `!xkcd [COMIC_ID|SEARCH_PHRASE]` - Returns the xkcd comic associated - with the given COMIC_ID (an integer) or matching the SEARCH_PHRASE. - Providing no arguments will return the most recent comic. - """ - if command.has_arg(): - argument = command.arg - if is_id(argument): - comic_number = int(argument) - response = get_by_id(comic_number) - else: - response = get_by_search_phrase(command.arg) - else: - response = get_latest() - - bot.post_message(command.channel_id, response, unfurl_links=True, unfurl_media=True) - - -@bot.on_schedule('cron', hour=14, minute=1, day_of_week='mon,wed,fri', - timezone='Australia/Brisbane') -def new_xkcd() -> None: - """ - Posts new xkcd comic when they are released every Monday, - Wednesday & Friday at 4AM UTC or 2PM Brisbane time. - - @no_help - """ - link = get_latest() - - day = datetime.datetime.today().weekday() - if (day == 0): # Monday - message = "It's Monday, 4 days till Friday; here's the" - elif (day == 2): # Wednesday - message = "Half way through the week, time for the" - elif (day == 4): # Friday - message = (":musical_note: It's Friday, Friday\nGotta get down on Friday\n" - "Everybody's lookin' forward to the") - else: - message = "@pah It is day " + str(day) + ", please fix me... Here's the" - message += " latest xkcd comic " - - general = bot.channels.get("general") - bot.post_message(general.id, message + link, unfurl_links=True, unfurl_media=True) From eaadaf8c00184759fc2621f9df1e83503a8a89e5 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Sat, 28 Oct 2023 09:30:18 +1000 Subject: [PATCH 29/48] Whatsdue: Added Sorting (#142) * Whatsdue: Added sorting by type. * Whatsdue: Added course ECP links as an option * Whatsdue: Fixed grammar for ECP link(s) * Whatsdue: Added weeks_to_show to reduce output * Removed surplus old TODOs and added typing to get_weight_as_int() * Changed get_weight_as_int() to return 0 if no weight can be parsed * Revert "Changed get_weight_as_int() to return 0 if no weight can be parsed" This reverts commit 709071dd3e20271bdd48bdaf036a865d3e4aa8cd. --------- Co-authored-by: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> --- uqcsbot/utils/uq_course_utils.py | 132 +++++++++++++++++++------------ uqcsbot/whatsdue.py | 85 ++++++++++++++------ 2 files changed, 143 insertions(+), 74 deletions(-) diff --git a/uqcsbot/utils/uq_course_utils.py b/uqcsbot/utils/uq_course_utils.py index a4c2d9db..93432227 100644 --- a/uqcsbot/utils/uq_course_utils.py +++ b/uqcsbot/utils/uq_course_utils.py @@ -3,9 +3,10 @@ from datetime import datetime from dateutil import parser from bs4 import BeautifulSoup, element -from functools import partial from typing import List, Dict, Optional, Literal, Tuple +from dataclasses import dataclass import json +import re BASE_COURSE_URL = "https://my.uq.edu.au/programs-courses/course.html?course_code=" BASE_ASSESSMENT_URL = ( @@ -105,6 +106,69 @@ def _estimate_current_semester() -> SemesterType: return "Summer" +@dataclass +class AssessmentItem: + course_name: str + task: str + due_date: str + weight: str + + def get_parsed_due_date(self): + """ + Returns the parsed due date for the given assessment item as a datetime + object. If the date cannot be parsed, a DateSyntaxException is raised. + """ + if self.due_date == "Examination Period": + return get_current_exam_period() + parser_info = parser.parserinfo(dayfirst=True) + try: + # If a date range is detected, attempt to split into start and end + # dates. Else, attempt to just parse the whole thing. + if " - " in self.due_date: + start_date, end_date = self.due_date.split(" - ", 1) + start_datetime = parser.parse(start_date, parser_info) + end_datetime = parser.parse(end_date, parser_info) + return start_datetime, end_datetime + due_datetime = parser.parse(self.due_date, parser_info) + return due_datetime, due_datetime + except Exception: + raise DateSyntaxException(self.due_date, self.course_name) + + def is_after(self, cutoff: datetime): + """ + Returns whether the assessment occurs after the given cutoff. + """ + try: + start_datetime, end_datetime = self.get_parsed_due_date() + except DateSyntaxException: + # If we can't parse a date, we're better off keeping it just in case. + return True + return end_datetime >= cutoff if end_datetime else start_datetime >= cutoff + + def is_before(self, cutoff: datetime): + """ + Returns whether the assessment occurs before the given cutoff. + """ + try: + start_datetime, _ = self.get_parsed_due_date() + except DateSyntaxException: + # TODO bot.logger.error(e.message) + # If we can't parse a date, we're better off keeping it just in case. + # TODO(mitch): Keep track of these instances to attempt to accurately + # parse them in future. Will require manual detection + parsing. + return True + return start_datetime <= cutoff + + def get_weight_as_int(self) -> Optional[int]: + """ + Trys to get the weight percentage of an assessment as a percentage. Will return None + if a percentage can not be obtained. + """ + if match := re.match(r"\d+", self.weight): + return int(match.group(0)) + return None + + class DateSyntaxException(Exception): """ Raised when an unparsable date syntax is encountered. @@ -234,14 +298,14 @@ def get_course_profile_url( return url -def get_course_profile_id(course_name: str, offering: Optional[Offering]): +def get_course_profile_id(course_name: str, offering: Optional[Offering] = None) -> int: """ Returns the ID to the latest course profile for the given course. """ profile_url = get_course_profile_url(course_name, offering=offering) # The profile url looks like this # https://course-profiles.uq.edu.au/student_section_loader/section_1/100728 - return profile_url[profile_url.rindex("/") + 1 :] + return int(profile_url[profile_url.rindex("/") + 1 :]) def get_current_exam_period(): @@ -270,44 +334,6 @@ def get_current_exam_period(): return start_datetime, end_datetime -def get_parsed_assessment_due_date(assessment_item: Tuple[str, str, str, str]): - """ - Returns the parsed due date for the given assessment item as a datetime - object. If the date cannot be parsed, a DateSyntaxException is raised. - """ - course_name, _, due_date, _ = assessment_item - if due_date == "Examination Period": - return get_current_exam_period() - parser_info = parser.parserinfo(dayfirst=True) - try: - # If a date range is detected, attempt to split into start and end - # dates. Else, attempt to just parse the whole thing. - if " - " in due_date: - start_date, end_date = due_date.split(" - ", 1) - start_datetime = parser.parse(start_date, parser_info) - end_datetime = parser.parse(end_date, parser_info) - return start_datetime, end_datetime - due_datetime = parser.parse(due_date, parser_info) - return due_datetime, due_datetime - except Exception: - raise DateSyntaxException(due_date, course_name) - - -def is_assessment_after_cutoff(assessment: Tuple[str, str, str, str], cutoff: datetime): - """ - Returns whether the assessment occurs after the given cutoff. - """ - try: - start_datetime, end_datetime = get_parsed_assessment_due_date(assessment) - except DateSyntaxException: - # TODO bot.logger.error(e.message) - # If we can't parse a date, we're better off keeping it just in case. - # TODO(mitch): Keep track of these instances to attempt to accurately - # parse them in future. Will require manual detection + parsing. - return True - return end_datetime >= cutoff if end_datetime else start_datetime >= cutoff - - def get_course_assessment_page( course_names: List[str], offering: Optional[Offering] ) -> str: @@ -316,17 +342,18 @@ def get_course_assessment_page( url to the assessment table for the provided courses """ profile_ids = map( - lambda course: get_course_profile_id(course, offering=offering), course_names + lambda course: str(get_course_profile_id(course, offering=offering)), + course_names, ) return BASE_ASSESSMENT_URL + ",".join(profile_ids) def get_course_assessment( course_names: List[str], - cutoff: Optional[datetime] = None, + cutoff: Tuple[Optional[datetime], Optional[datetime]] = (None, None), assessment_url: Optional[str] = None, offering: Optional[Offering] = None, -) -> List[Tuple[str, str, str, str]]: +) -> List[AssessmentItem]: """ Returns all the course assessment for the given courses that occur after the given cutoff. @@ -346,9 +373,12 @@ def get_course_assessment( assessment = assessment_table.findAll("tr")[1:] parsed_assessment = map(get_parsed_assessment_item, assessment) # If no cutoff is specified, set cutoff to UNIX epoch (i.e. filter nothing). - cutoff = cutoff or datetime.min - assessment_filter = partial(is_assessment_after_cutoff, cutoff=cutoff) - filtered_assessment = filter(assessment_filter, parsed_assessment) + cutoff_min = cutoff[0] or datetime.min + cutoff_max = cutoff[1] or datetime.max + filtered_assessment = filter( + lambda item: item.is_after(cutoff_min) and item.is_before(cutoff_max), + parsed_assessment, + ) return list(filtered_assessment) @@ -360,8 +390,8 @@ def get_element_inner_html(dom_element: element.Tag): def get_parsed_assessment_item( - assessment_item: element.Tag, -) -> Tuple[str, str, str, str]: + assessment_item_tag: element.Tag, +) -> AssessmentItem: """ Returns the parsed assessment details for the given assessment item table row element. @@ -371,7 +401,7 @@ def get_parsed_assessment_item( This is likely insufficient to handle every course's structure, and thus is subject to change. """ - course_name, task, due_date, weight = assessment_item.findAll("div") + course_name, task, due_date, weight = assessment_item_tag.findAll("div") # Handles courses of the form 'CSSE1001 - Sem 1 2018 - St Lucia - Internal'. # Thus, this bit of code will extract the course. course_name = course_name.text.strip().split(" - ")[0] @@ -384,7 +414,7 @@ def get_parsed_assessment_item( # Handles weights of the form '30%
Alternative to oral presentation'. # Thus, this bit of code will keep only the weight portion of the field. weight = get_element_inner_html(weight).strip().split("
")[0] - return (course_name, task, due_date, weight) + return AssessmentItem(course_name, task, due_date, weight) class Exam: diff --git a/uqcsbot/whatsdue.py b/uqcsbot/whatsdue.py index 9dd61c68..7b3a51cb 100644 --- a/uqcsbot/whatsdue.py +++ b/uqcsbot/whatsdue.py @@ -1,6 +1,6 @@ -from datetime import datetime +from datetime import datetime, timedelta import logging -from typing import Optional +from typing import Optional, Callable, Literal, Dict import discord from discord import app_commands @@ -9,14 +9,39 @@ from uqcsbot.yelling import yelling_exemptor from uqcsbot.utils.uq_course_utils import ( + DateSyntaxException, Offering, CourseNotFoundException, HttpException, ProfileNotFoundException, + AssessmentItem, get_course_assessment, get_course_assessment_page, + get_course_profile_id, ) +AssessmentSortType = Literal["Date", "Course Name", "Weight"] +ECP_ASSESSMENT_URL = ( + "https://course-profiles.uq.edu.au/student_section_loader/section_5/" +) + + +def sort_by_date(item: AssessmentItem): + """Provides a key to sort assessment dates by. If the date cannot be parsed, will put it with items occuring during exam block.""" + try: + return item.get_parsed_due_date()[0] + except DateSyntaxException: + return datetime.max + + +SORT_METHODS: Dict[ + AssessmentSortType, Callable[[AssessmentItem], int | str | datetime] +] = { + "Date": sort_by_date, + "Course Name": (lambda item: item.course_name), + "Weight": (lambda item: item.get_weight_as_int() or 0), +} + class WhatsDue(commands.Cog): def __init__(self, bot: commands.Bot): @@ -26,15 +51,14 @@ def __init__(self, bot: commands.Bot): @app_commands.describe( fulloutput="Display the full list of assessment. Defaults to False, which only " + "shows assessment due from today onwards.", + weeks_to_show="Only show assessment due within this number of weeks. If 0 (default), show all assessment.", semester="The semester to get assessment for. Defaults to what UQCSbot believes is the current semester.", campus="The campus the course is held at. Defaults to St Lucia. Note that many external courses are 'hosted' at St Lucia.", mode="The mode of the course. Defaults to Internal.", - course1="Course code", - course2="Course code", - course3="Course code", - course4="Course code", - course5="Course code", - course6="Course code", + courses="Course codes seperated by spaces", + sort_order="The order to sort courses by. Defualts to Date.", + reverse_sort="Whether to reverse the sort order. Defaults to false.", + show_ecp_links="Show the first ECP link for each course page. Defaults to false.", ) @yelling_exemptor( input_args=["course1", "course2", "course3", "course4", "course5", "course6"] @@ -42,16 +66,15 @@ def __init__(self, bot: commands.Bot): async def whatsdue( self, interaction: discord.Interaction, - course1: str, - course2: Optional[str], - course3: Optional[str], - course4: Optional[str], - course5: Optional[str], - course6: Optional[str], + courses: str, fulloutput: bool = False, + weeks_to_show: int = 0, semester: Optional[Offering.SemesterType] = None, campus: Offering.CampusType = "St Lucia", mode: Offering.ModeType = "Internal", + sort_order: AssessmentSortType = "Date", + reverse_sort: bool = False, + show_ecp_links: bool = False, ): """ Returns all the assessment for a given list of course codes that are scheduled to occur. @@ -60,15 +83,19 @@ async def whatsdue( await interaction.response.defer(thinking=True) - possible_courses = [course1, course2, course3, course4, course5, course6] - course_names = [c.upper() for c in possible_courses if c != None] + course_names = [c.upper() for c in courses.split()] offering = Offering(semester=semester, campus=campus, mode=mode) # If full output is not specified, set the cutoff to today's date. - cutoff = None if fulloutput else datetime.today() + cutoff = ( + None if fulloutput else datetime.today(), + datetime.today() + timedelta(weeks=weeks_to_show) + if weeks_to_show > 0 + else None, + ) try: - asses_page = get_course_assessment_page(course_names, offering) - assessment = get_course_assessment(course_names, cutoff, asses_page) + assessment_page = get_course_assessment_page(course_names, offering) + assessment = get_course_assessment(course_names, cutoff, assessment_page) except HttpException as e: logging.error(e.message) await interaction.edit_original_response( @@ -81,15 +108,15 @@ async def whatsdue( embed = discord.Embed( title=f"What's Due: {', '.join(course_names)}", - url=asses_page, + url=assessment_page, description="*WARNING: Assessment information may vary/change/be entirely different! Use at your own discretion. Check your ECP for a true list of assessment.*", ) if assessment: + assessment.sort(key=SORT_METHODS[sort_order], reverse=reverse_sort) for assessment_item in assessment: - course, task, due, weight = assessment_item embed.add_field( - name=course, - value=f"`{weight}` {task} **({due})**", + name=assessment_item.course_name, + value=f"`{assessment_item.weight}` {assessment_item.task} **({assessment_item.due_date})**", inline=False, ) elif fulloutput: @@ -103,6 +130,18 @@ async def whatsdue( value=f"Nothing seems to be due soon", ) + if show_ecp_links: + ecp_links = [ + f"[{course_name}]({ECP_ASSESSMENT_URL + str(get_course_profile_id(course_name))})" + for course_name in course_names + ] + embed.add_field( + name=f"Potential ECP {'Link' if len(course_names) == 1 else 'Links'}", + value=" ".join(ecp_links) + + "\nNote that these may not be the correct ECPs. Check the year and offering type.", + inline=False, + ) + if not fulloutput: embed.set_footer( text="Note: This may not be the full assessment list. Set fulloutput to True to see a potentially more complete list, or check your ECP for a true list of assessment." From 86b238b697c7ed19bf7288f8ad80900ddf4caa30 Mon Sep 17 00:00:00 2001 From: "Eli (they/them)" Date: Thu, 2 Nov 2023 16:50:06 +1000 Subject: [PATCH 30/48] Course ECP Command (#171) * Spoilering description text of xkcd Made xkcd_desc an f string, added spoiler `||` on either side so when it's posted to discord it spoilers the text. * Course ECP Command Attempting to create a command that takes in a course code, selected year, semester, mode, and campus and then provides the link to the course profile. Currently it defaults to the current semester and I'm yet to work out how to make it not default. * Remove unused imports * Added year parameter to get_course_profile_url * Course ECP now considers the year, semester, campus and mode input Need to format the embedded and allow it to take in multiple courses * Lists multiple course ecps It assume when requesting you want the same semester, year, mode and campus for all courses. * Adding suggestions - Fixed typo - Clarified error message - Changes wording of the else statement that in theory should never happen because of the exceptions. * Made `estimate_current_semester()` in `Offering` a public method. Unsure if this will cause issues, doesn't look like it and improve any future use of checking what the estimated current semester is. * Black formatting --------- Co-authored-by: Isaac Beh --- uqcsbot/__main__.py | 1 + uqcsbot/course_ecp.py | 110 +++++++++++++++++++++++++++++++ uqcsbot/utils/uq_course_utils.py | 32 +++++---- 3 files changed, 126 insertions(+), 17 deletions(-) create mode 100644 uqcsbot/course_ecp.py diff --git a/uqcsbot/__main__.py b/uqcsbot/__main__.py index 93e7591e..ee848766 100644 --- a/uqcsbot/__main__.py +++ b/uqcsbot/__main__.py @@ -43,6 +43,7 @@ async def main(): "basic", "cat", "cowsay", + "course_ecp", "dominos_coupons", "error_handler", "events", diff --git a/uqcsbot/course_ecp.py b/uqcsbot/course_ecp.py new file mode 100644 index 00000000..e00643b5 --- /dev/null +++ b/uqcsbot/course_ecp.py @@ -0,0 +1,110 @@ +from typing import Optional +import logging +from datetime import datetime +import discord +from discord import app_commands +from discord.ext import commands + +from uqcsbot.utils.uq_course_utils import ( + Offering, + HttpException, + CourseNotFoundException, + ProfileNotFoundException, + get_course_profile_url, +) +from uqcsbot.yelling import yelling_exemptor + + +class CourseECP(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + + @app_commands.command() + @app_commands.describe( + course1="The course to find an ECP for.", + course2="The second course to find an ECP for.", + course3="The third course to find an ECP for.", + course4="The fourth course to find an ECP for.", + year="The year to find the course ECP for. Defaults to what UQCSbot believes is the current year.", + semester="The semester to find the course ECP for. Defaults to what UQCSbot believes is the current semester.", + campus="The campus the course is held at. Defaults to St Lucia. Defaults to St Lucia. Note that many external courses are 'hosted' at St Lucia.", + mode="The mode of the course. Defaults to Internal.", + ) + @yelling_exemptor(input_args=["course1, course2, course3, course4"]) + async def courseecp( + self, + interaction: discord.Interaction, + course1: str, + course2: Optional[str], + course3: Optional[str], + course4: Optional[str], + year: Optional[int] = None, + semester: Optional[Offering.SemesterType] = None, + campus: Offering.CampusType = "St Lucia", + mode: Offering.ModeType = "Internal", + ): + """ + Returns the URL of the ECPs for course codes given. Assumes the same semester and year for the course codes given. + + """ + await interaction.response.defer(thinking=True) + + possible_courses = [course1, course2, course3, course4] + course_names = [c.upper() for c in possible_courses if c != None] + course_name_urls: dict[str, str] = {} + offering = Offering(semester=semester, campus=campus, mode=mode) + + try: + for course in course_names: + course_name_urls.update( + {course: get_course_profile_url(course, offering, year)} + ) + except HttpException as exception: + logging.warning( + f"Received a HTTP response code {exception.status_code} when trying find the course url using get_course_profile_url in course_ecp.py . Error information: {exception.message}" + ) + await interaction.edit_original_response( + content=f"Could not contact UQ, please try again." + ) + return + except (CourseNotFoundException, ProfileNotFoundException) as exception: + await interaction.edit_original_response(content=exception.message) + return + + # If year is none assign it the current year + if not year: + year = datetime.today().year + + # If semester is none assign it the current estimated semester + if not semester: + semester = Offering.estimate_current_semester() + + # Create the embedded message with the course names and details + embed = discord.Embed( + title=f"Course ECP: {', '.join(course_names)}", + description=f"For Semester {semester} {year}, {mode}, {campus}", + ) + + # Add the ECP urls to the embedded message + if course_name_urls: + for course in course_name_urls: + embed.add_field( + name=f"", + value=f"[{course}]({course_name_urls.get(course)}) ", + inline=False, + ) + else: + await interaction.edit_original_response( + content=f"No ECP could be found for the courses: {course_names}. The ECP(s) might not be available." + ) + return + + embed.set_footer( + text="The course ECP might be out of date, be sure to check the course on BlackBoard." + ) + await interaction.edit_original_response(embed=embed) + return + + +async def setup(bot: commands.Bot): + await bot.add_cog(CourseECP(bot)) diff --git a/uqcsbot/utils/uq_course_utils.py b/uqcsbot/utils/uq_course_utils.py index 93432227..f879d790 100644 --- a/uqcsbot/utils/uq_course_utils.py +++ b/uqcsbot/utils/uq_course_utils.py @@ -14,8 +14,10 @@ "student_section_report.php?report=assessment&profileIds=" ) BASE_CALENDAR_URL = "http://www.uq.edu.au/events/calendar_view.php?category_id=16&year=" -OFFERING_PARAMETER = "offer" BASE_PAST_EXAMS_URL = "https://api.library.uq.edu.au/v1/exams/search/" +# Parameters for the course page +OFFERING_PARAMETER = "offer" +YEAR_PARAMETER = "year" class Offering: @@ -59,7 +61,7 @@ def __init__( if semester is not None: self.semester = semester else: - self.semester = self._estimate_current_semester() + self.semester = self.estimate_current_semester() self.semester self.campus = campus self.mode = mode @@ -93,7 +95,7 @@ def get_offering_code(self) -> str: return offering_code_text.encode("utf-8").hex() @staticmethod - def _estimate_current_semester() -> SemesterType: + def estimate_current_semester() -> SemesterType: """ Returns an estimate of the current semester (represented by an integer) based on the current month. 3 represents summer semester. """ @@ -256,23 +258,19 @@ def get_uq_request( def get_course_profile_url( - course_name: str, offering: Optional[Offering] = None + course_name: str, + offering: Optional[Offering] = None, + year: Optional[int] = None, ) -> str: """ - Returns the URL to the course profile for the given course for a given offering. - If no offering is give, will return the first course profile on the course page. + Returns the URL to the course profile (ECP) for the given course for a given offering. + If no offering or year are given, the first course profile on the course page will be returned. """ - if offering is None: - course_url = BASE_COURSE_URL + course_name - else: - course_url = ( - BASE_COURSE_URL - + course_name - + "&" - + OFFERING_PARAMETER - + "=" - + offering.get_offering_code() - ) + course_url = BASE_COURSE_URL + course_name + if offering: + course_url += "&" + OFFERING_PARAMETER + "=" + offering.get_offering_code() + if year: + course_url += "&" + YEAR_PARAMETER + "=" + str(year) http_response = get_uq_request(course_url) if http_response.status_code != requests.codes.ok: From c3042687c534e7266961779e37899803b3929f99 Mon Sep 17 00:00:00 2001 From: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> Date: Wed, 8 Nov 2023 16:27:54 +1000 Subject: [PATCH 31/48] bruh (#176) --- poetry.lock | 6 +++--- uqcsbot/manage_cogs.py | 7 ++++--- uqcsbot/starboard.py | 7 +++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/poetry.lock b/poetry.lock index 212ff96d..ed25228b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -851,13 +851,13 @@ files = [ [[package]] name = "pyright" -version = "1.1.316" +version = "1.1.334" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.316-py3-none-any.whl", hash = "sha256:7259d73287c882f933d8cd88c238ef02336e172171ae95117a963a962a1fed4a"}, - {file = "pyright-1.1.316.tar.gz", hash = "sha256:bac1baf8567b90f2082ec95b61fc1cb50a68917119212c5608a72210870c6a9a"}, + {file = "pyright-1.1.334-py3-none-any.whl", hash = "sha256:dcb13e8358e021189672c4d6ebcad192ab061e4c7225036973ec493183c6da68"}, + {file = "pyright-1.1.334.tar.gz", hash = "sha256:3adaf10f1f4209575dc022f9c897f7ef024639b7ea5b3cbe49302147e6949cd4"}, ] [package.dependencies] diff --git a/uqcsbot/manage_cogs.py b/uqcsbot/manage_cogs.py index 116742ab..12bcef46 100644 --- a/uqcsbot/manage_cogs.py +++ b/uqcsbot/manage_cogs.py @@ -5,6 +5,7 @@ from discord.ext import commands from uqcsbot.yelling import yelling_exemptor +from uqcsbot.bot import UQCSBot class ManageCogs(commands.Cog): @@ -12,11 +13,11 @@ class ManageCogs(commands.Cog): Note that most of these commands can make the bot load files to execute. Care should be made to ensure only entrusted users have access. """ - def __init__(self, bot: commands.Bot): + def __init__(self, bot: UQCSBot): self.bot = bot @app_commands.command(name="managecogs") - @app_commands.default_permissions(manage_guild=True) + @app_commands.checks.has_permissions(manage_guild=True) @app_commands.describe( cog='The cog (i.e. python file) to try to unload. Use python package notation, so no suffix of ".py" and "." between folders: e.g. "manage_cogs".', ) @@ -49,5 +50,5 @@ async def manage_cogs( await self.bot.tree.sync() -async def setup(bot: commands.Bot): +async def setup(bot: UQCSBot): await bot.add_cog(ManageCogs(bot)) diff --git a/uqcsbot/starboard.py b/uqcsbot/starboard.py index ed727d18..7614bd74 100644 --- a/uqcsbot/starboard.py +++ b/uqcsbot/starboard.py @@ -62,7 +62,7 @@ async def on_ready(self): ) @app_commands.command() - @app_commands.default_permissions(manage_guild=True) + @app_commands.checks.has_permissions(manage_guild=True) async def cleanup_starboard(self, interaction: discord.Interaction): """Cleans up the last 100 messages from the starboard. Removes any uqcsbot message that doesn't have a corresponding message id in the db, regardless of recv. @@ -70,7 +70,6 @@ async def cleanup_starboard(self, interaction: discord.Interaction): manage_guild perms: for committee and infra use. """ - if interaction.channel == self.starboard_channel: # because if you do it from in the starboard, it deletes its own interaction response # and i cba making it not do that, so i'll just forbid doing it in starboard. @@ -146,7 +145,7 @@ async def _blacklist_log( ) ) - @app_commands.default_permissions(manage_messages=True) + @app_commands.checks.has_permissions(manage_messages=True) async def context_blacklist_sb_message( self, interaction: discord.Interaction, message: discord.Message ): @@ -194,7 +193,7 @@ async def context_blacklist_sb_message( f"Blacklisted message {message.id}.", ephemeral=True ) - @app_commands.default_permissions(manage_messages=True) + @app_commands.checks.has_permissions(manage_messages=True) async def context_unblacklist_sb_message( self, interaction: discord.Interaction, message: discord.Message ): From 5f630cd13294cfcb74f760791f2ecf8a1e88cdb4 Mon Sep 17 00:00:00 2001 From: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> Date: Wed, 8 Nov 2023 18:32:53 +1000 Subject: [PATCH 32/48] fix yelling types and remove arbitrary web request (#177) * fix yelling types and remove arbitrary web request * style. also take 2 * of all the days for my typechecker to stop fucking working * i'm capitulating * fixed capitulation --- uqcsbot/yelling.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/uqcsbot/yelling.py b/uqcsbot/yelling.py index abbe005a..5a6ee04c 100644 --- a/uqcsbot/yelling.py +++ b/uqcsbot/yelling.py @@ -3,8 +3,6 @@ from discord.ext import commands from random import choice, random import re -from urllib.request import urlopen -from urllib.error import URLError from uqcsbot.bot import UQCSBot from uqcsbot.cog import UQCSBotCog @@ -48,7 +46,8 @@ async def wrapper( if not Yelling.contains_lowercase(text): await func(cogself, *args, **kwargs) return - await interaction.response.send_message( + + await interaction.response.send_message( # type: ignore str(discord.utils.get(bot.emojis, name="disapproval") or "") ) if isinstance(interaction.user, discord.Member): @@ -167,12 +166,6 @@ def clean_text(self, message: str) -> str: # slightly more permissive version of discord's url regex, matches absolutely anything between http(s):// and whitespace for url in re.findall(r"https?:\/\/[^\s]+", text, flags=re.UNICODE): - try: - resp = urlopen(url) - except (ValueError, URLError): - continue - if 400 <= resp.code <= 499: - continue text = text.replace(url, url.upper()) text = text.replace(">", ">").replace("<", "<").replace("&", "&") From fa9fc2928b21a8324a7e7567b48362779a31378e Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Thu, 30 Nov 2023 21:51:55 +1000 Subject: [PATCH 33/48] Reworked advent of code. (#183) * Reworked advent of code. Refreshed all of advent of code to work with the new slash commands. This includes: - Adding a database to record AOC accounts and discord users - Adding a database to record previous winners - Reworking the command to select winners - Reworking the leaderboard display system * Added detail to advent help menu * Added weights to advent help menu * Used the proper discord mention property and refactored a little * Minor style changes * Refactored AOC into a utils file * Small style changes for black * Fixed autoincrement for the SQL database * Removed ids from registration db & removed advent from pyright exception list * Removed type-checking machinery --- pyproject.toml | 1 - uqcsbot/advent.py | 1285 ++++++++++++++++++++++----------- uqcsbot/bot.py | 2 + uqcsbot/models.py | 19 +- uqcsbot/utils/advent_utils.py | 386 ++++++++++ 5 files changed, 1268 insertions(+), 425 deletions(-) create mode 100644 uqcsbot/utils/advent_utils.py diff --git a/pyproject.toml b/pyproject.toml index 18bc2814..2989fa34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,6 @@ build-backend = "poetry.core.masonry.api" [tool.pyright] strict = ["**"] exclude = [ - "**/advent.py", "**/bot.py", "**/error_handler.py", "**/events.py", diff --git a/uqcsbot/advent.py b/uqcsbot/advent.py index d37f6563..92cdbdbc 100644 --- a/uqcsbot/advent.py +++ b/uqcsbot/advent.py @@ -1,21 +1,29 @@ import io -import logging import os -from argparse import ArgumentParser, Namespace -from datetime import datetime, timedelta, timezone -from enum import Enum +from datetime import datetime from random import choices -from typing import Any, Callable, Dict, List, Optional, Tuple +from typing import Callable, Dict, Iterable, List, Optional, Literal +import requests +from requests.exceptions import RequestException +from sqlalchemy.sql.expression import and_ import discord -import requests +from discord import app_commands from discord.ext import commands -from requests.exceptions import RequestException from uqcsbot.bot import UQCSBot -from uqcsbot.models import AOCWinner -from uqcsbot.utils.command_utils import loading_status +from uqcsbot.models import AOCRegistrations, AOCWinners from uqcsbot.utils.err_log_utils import FatalErrorWithLog +from uqcsbot.utils.advent_utils import ( + Member, + Day, + Json, + InvalidHTTPSCode, + ADVENT_DAYS, + CACHE_TIME, + parse_leaderboard_column_string, + print_leaderboard, +) # Leaderboard API URL with placeholders for year and code. LEADERBOARD_URL = "https://adventofcode.com/{year}/leaderboard/private/view/{code}.json" @@ -23,145 +31,141 @@ # UQCS leaderboard ID. UQCS_LEADERBOARD = 989288 -# Days in Advent of Code. List of numbers 1 to 25. -ADVENT_DAYS = list(range(1, 25 + 1)) - -# Puzzles are unlocked at midnight EST. -EST_TIMEZONE = timezone(timedelta(hours=-5)) - - -class SortMode(Enum): - """Options for sorting the leaderboard.""" - - PART_1 = "p1" - PART_2 = "p2" - DELTA = "delta" - LOCAL = "local" # SortMode.LOCAL is not shown to users - GLOBAL = "global" # SortMode.GLOBAL is not shown to users +# The maximum time in seconds that a person can complete a challenge in. Used as a maximum value to help with sorting when someone whas not attempted a day. +MAXIMUM_TIME_FOR_STAR = 365 * 24 * 60 * 60 + +# --- Sorting Methods & Related Leaderboards --- + +# Star 1 Time: Time for just getting star 1. For the monthly leaderboard, this will be the total time spent on star 1 across all problems. +# Star 2 Time: Time for just getting star 2. Does not include the time to get star 1. For the monthly leaderboard, this will be the total time spent on star 2 across all problems. +# Star 1 & 2 Time: Time for getting both stars 1 and 2. +# Total Time: The total time spent on problems over the entire month. For the monthly leaderboard, this is the same as Star 1 & 2 Time. +# Total Stars: The total number of stars over the entire month. +# Global Rank: The users global rank over the month. This is not reasonable to be daily, as very few get a global ranking each day. +SortingMethod = Literal[ + "Star 1 Time", + "Star 2 Time", + "Star 1 & 2 Time", + "Total Time", + "Total Stars", + "Global Rank", +] + +# Note that a tuple is used so that there can be multiple sorting criterial +sorting_functions_for_day: Dict[ + SortingMethod, Callable[[Member, Day], tuple[int, ...]] +] = { + "Star 1 Time": lambda member, day: ( + member.times[day].get(1, MAXIMUM_TIME_FOR_STAR), + member.times[day].get(2, MAXIMUM_TIME_FOR_STAR), + ), + "Star 2 Time": lambda member, day: ( + member.times[day][2] - member.times[day][1] + if 2 in member.times[day] + else MAXIMUM_TIME_FOR_STAR, + member.times[day].get(1, MAXIMUM_TIME_FOR_STAR), + ), + "Star 1 & 2 Time": lambda member, day: ( + member.times[day].get(2, MAXIMUM_TIME_FOR_STAR), + member.times[day].get(1, MAXIMUM_TIME_FOR_STAR), + ), + "Total Time": lambda member, dat: ( + member.get_total_time(default=MAXIMUM_TIME_FOR_STAR), + -member.star_total, + ), + "Total Stars": lambda member, day: ( + -member.star_total, + member.get_total_time(default=MAXIMUM_TIME_FOR_STAR), + ), + "Global Rank": lambda member, day: ( + -member.global_, + member.get_total_time(default=MAXIMUM_TIME_FOR_STAR), + ), +} - def __str__(self): - return self.value # needed so --help prints string values +# Each sorting method has its own leaderboard to show the most relevant details +leaderboards_for_day: Dict[SortingMethod, str] = { + "Star 1 Time": "# 1 2 3 ! @ T", + "Star 2 Time": "# 1 2 3 ! @ T", + "Star 1 & 2 Time": "# 1 2 3 ! @ T L", + "Total Time": "# T ! @ 1 2 3", + "Total Stars": "# * L 1 2 3", + "Global Rank": "# G L * 1 2 3", +} +# These are used for the monthly leaderboard +sorting_functions_for_month: Dict[ + SortingMethod, Callable[[Member], tuple[int, ...]] +] = { + "Star 1 Time": lambda member: ( + member.get_total_star1_time(default=MAXIMUM_TIME_FOR_STAR), + member.get_total_star2_time(default=MAXIMUM_TIME_FOR_STAR), + ), + "Star 2 Time": lambda member: ( + member.get_total_star2_time(default=MAXIMUM_TIME_FOR_STAR), + member.get_total_star1_time(default=MAXIMUM_TIME_FOR_STAR), + ), + "Star 1 & 2 Time": lambda member: ( + member.get_total_time(default=MAXIMUM_TIME_FOR_STAR), + -member.star_total, + member.get_total_star1_time(default=MAXIMUM_TIME_FOR_STAR), + ), + "Total Time": lambda member: ( + member.get_total_time(default=MAXIMUM_TIME_FOR_STAR), + -member.star_total, + ), + "Total Stars": lambda member: ( + -member.star_total, + member.get_total_time(default=MAXIMUM_TIME_FOR_STAR), + ), + "Global Rank": lambda member: ( + -member.global_, + member.get_total_time(default=MAXIMUM_TIME_FOR_STAR), + ), +} -# Map of sorting options to friendly name. -SORT_LABELS = { - SortMode.PART_1: "part 1 completion", - SortMode.PART_2: "part 2 completion", - SortMode.DELTA: "time delta", +# Each sorting method has its own leaderboard to show the most relevant details +leaderboards_for_month: Dict[SortingMethod, str] = { + "Star 1 Time": "# ! @ T * L", + "Star 2 Time": "# ! @ T * L", + "Star 1 & 2 Time": "# L * T", + "Total Time": "# L * T ! @", + "Total Stars": "# L T B", + "Global Rank": "# G L * T", } -def sort_none_last(key): +class Advent(commands.Cog): """ - Given sort key function, returns new key function which can handle None. - - None values are sorted after non-None values. + All of the commands related to Advent of Code (AOC). + Commands: + /advent help - Display help menu + /advent leaderboard - Display a leaderboard. Many sorting options and different leaderboard styles + /advent register - Register an AOC id to the current discord username. Used for registrating for prizes + /advent register-force - Force a registration between an AOC id and a discord user. Used for moderation and admin reasons + /advent unregister - Unregister an AOC id to the current discord username. + /advent unregister-force - Force-remove a registration between an AOC id and a discord user. Used for moderation and admin reasons + /advent previous-winners - Show the previous winners from a year + /advent new-winner - Add a discord user as a winner (chosen directly or by random selection) for prizes + /advent remove-winner - Remove a winner for the database """ - return lambda x: (key(x) is None, key(x)) - - -# type aliases for documentation purposes. -Day = int # from 1 to 25 -Star = int # 1 or 2 -Seconds = int -Times = Dict[Star, Seconds] -Delta = Optional[Seconds] -# TODO: make these types more specific with TypedDict and Literal when possible. - - -class Member: - def __init__( - self, id: int, name: str, local: int, stars: int, global_: int - ) -> None: - self.id = id - self.name = name - self.local = local - self.stars = stars - self.global_ = global_ - - self.all_times: Dict[Day, Times] = {d: {} for d in ADVENT_DAYS} - self.all_deltas: Dict[Day, Delta] = {d: None for d in ADVENT_DAYS} - - self.day: Optional[Day] = None - self.day_times: Times = {} - self.day_delta: Delta = None - - @classmethod - def from_member_data( - cls, data: Dict, year: int, day: Optional[int] = None - ) -> "Member": - """ - Constructs a Member from the API response. - - Times and delta are calculated for the given year and day. - """ - - member = cls( - data["id"], - data["name"], - data["local_score"], - data["stars"], - data["global_score"], - ) - - for d, day_data in data["completion_day_level"].items(): - d = int(d) - times = member.all_times[d] - - # timestamp of puzzle unlock, rounded to whole seconds - DAY_START = int(datetime(year, 12, d, tzinfo=EST_TIMEZONE).timestamp()) - - for star, star_data in day_data.items(): - star = int(star) - times[star] = int(star_data["get_star_ts"]) - DAY_START - assert times[star] >= 0 - - if len(times) == 2: - part_1, part_2 = sorted(times.values()) - member.all_deltas[d] = part_2 - part_1 - - # if day is specified, save that day's information into the day_ fields. - if day: - member.day = day - member.day_times = member.all_times[day] - member.day_delta = member.all_deltas[day] - - return member - - @staticmethod - def sort_key(sort: SortMode) -> Callable[["Member"], Any]: - """ - Given sort mode, returns a key function which sorts members - by that option using the stored times and delta. - """ - - if sort == SortMode.LOCAL: - # sorts by local score, then stars, descending. - return lambda m: (-m.local, -m.stars) - if sort == SortMode.GLOBAL: - # sorts by global score, then local score, then stars, descending. - return lambda m: (-m.global_, -m.local, -m.stars) - - # these key functions sort in ascending order of the specified value. - # E731 advises using function definitions over lambdas which is unreasonable here - if sort == SortMode.PART_1: - key = lambda m: m.day_times.get(1) # noqa: E731 - elif sort == SortMode.PART_2: - key = lambda m: m.day_times.get(2) # noqa: E731 - elif sort == SortMode.DELTA: - key = lambda m: m.day_delta # noqa: E731 - else: - assert False - - return sort_none_last(key) - -class Advent(commands.Cog): - CHANNEL_NAME = "contests" - - # Session cookie (will expire in approx 30 days). - # See: https://github.com/UQComputingSociety/uqcsbot-discord/wiki/Tokens-and-Environment-Variables#aoc_session_id - SESSION_ID: str = "" + advent_command_group = app_commands.Group( + name="advent", description="Commands for Advent of Code" + ) + + Command = Literal[ + "help", + "leaderboard", + "register", + "register-force", + "unregister", + "unregister-force", + "previous-winners", + "new-winner", + "remove-winner", + "leaderboard_style", + ] def __init__(self, bot: UQCSBot): self.bot = bot @@ -183,372 +187,811 @@ def __init__(self, bot: UQCSBot): month=12, ) - if os.environ.get("AOC_SESSION_ID") is not None: - SESSION_ID = os.environ.get("AOC_SESSION_ID") + # A dictionary from a year to the list of members + self.members_cache: Dict[int, List[Member]] = {} + self.last_reload_time = datetime.now() + + if isinstance((session_id := os.environ.get("AOC_SESSION_ID")), str): + # Session cookie (will expire in approx 30 days). + # See: https://github.com/UQComputingSociety/uqcsbot-discord/wiki/Tokens-and-Environment-Variables#aoc_session_id + self.session_id: str = session_id else: raise FatalErrorWithLog( bot, "Unable to find AoC session ID. Not loading advent cog." ) - def star_char(self, num_stars: int): + @commands.Cog.listener() + async def on_ready(self): + channel = discord.utils.get( + self.bot.uqcs_server.channels, name=self.bot.AOC_CNAME + ) + if isinstance(channel, discord.TextChannel): + self.channel = channel + else: + raise FatalErrorWithLog( + self.bot, + f"Could not find channel #{self.bot.AOC_CNAME} for advent of code cog.", + ) + role = discord.utils.get(self.bot.uqcs_server.roles, name=self.bot.AOC_ROLE) + if isinstance(role, discord.Role): + self.role = role + else: + raise FatalErrorWithLog( + self.bot, + f"Could not find role @{self.bot.AOC_ROLE} for advent of code cog", + ) + + def _get_leaderboard_json(self, year: int, code: int) -> Json: """ - Given a number of stars (0, 1, or 2), returns its leaderboard - representation. + Returns a json dump of the leaderboard """ - return " .*"[num_stars] + try: + response = requests.get( + LEADERBOARD_URL.format(year=year, code=code), + cookies={"session": self.session_id}, + ) + except RequestException as exception: + raise FatalErrorWithLog( + self.bot, + f"Could not get the leaderboard from Advent of Code. For more information {exception}", + ) + if response.status_code != 200: + raise InvalidHTTPSCode( + "Expected a HTTPS status code of 200.", response.status_code + ) + try: + return response.json() + except ValueError as exception: # json.JSONDecodeError + raise FatalErrorWithLog( + self.bot, + f"Could not interpret the JSON from Advent of Code (AOC). This suggests that AOC no longer provides JSON or something went very wrong. For more information: {exception}", + ) - def format_full_leaderboard(self, members: List[Member]) -> str: + def _get_members( + self, year: int, code: int = UQCS_LEADERBOARD, force_refresh: bool = False + ): """ - Returns a string representing the full leaderboard of the given list. - - Full leaderboard includes rank, points, stars (per day), and username. + Returns the list of members in the leaderboard for the given year and leaderboard code. + It will attempt to retrieve from a cache if 15 minutes has not passed. + This can be overriden by setting force refresh. """ + if ( + force_refresh + or (datetime.now() - self.last_reload_time >= CACHE_TIME) + or year not in self.members_cache + ): + leaderboard = self._get_leaderboard_json(year, code) + self.members_cache[year] = [ + Member.from_member_data(data, year) + for data in leaderboard["members"].values() + ] + return self.members_cache[year] - # 3 4 25 - # |-| |--| |-----------------------| - # 1) 751 **************** Name - def format_member(i: int, m: Member): - stars = "".join(self.star_char(len(m.all_times[d])) for d in ADVENT_DAYS) - return f"{i:>3}) {m.local:>4} {stars} {m.name}" - - left = " " * (3 + 2 + 4 + 1) # chars before stars start - header = ( - f"{left} 1111111111222222\n" f"{left}1234567890123456789012345\n" + def _get_registrations(self, year: int) -> Iterable[AOCRegistrations]: + """ + Get all registrations linking an AOC id to a discord account. + """ + db_session = self.bot.create_db_session() + registrations = db_session.query(AOCRegistrations).filter( + AOCRegistrations.year == year ) + db_session.commit() + db_session.close() + return registrations - return header + "\n".join(format_member(i, m) for i, m in enumerate(members, 1)) - - def format_global_leaderboard(self, members: List[Member]) -> str: + async def reminder_fifteen_minutes(self): + """ + The function used within the AOC reminder 15 minutes before each challenge starts. """ - Returns a string representing the global leaderboard of the given list. + await self.channel.send( + f"{self.role.mention}Today's Advent of Code puzzle is released in 15 minutes." + ) - Full leaderboard includes rank, global points, and username. + async def reminder_released(self): """ + The function used within the AOC reminder when each challenge starts. + """ + await self.channel.send( + f"{self.role.mention}Today's Advent of Code puzzle has been released. Good luck!" + ) - # 3 4 - # |-| |--| - # 1) 751 Name - def format_member(i: int, m: Member): - return f"{i:>3}) {m.global_:>4} {m.name}" + def _get_previous_winner_aoc_ids(self, year: int) -> List[int]: + """ + Returns a list of all winner aoc ids for a year + """ + db_session = self.bot.create_db_session() + prev_winners = db_session.query(AOCWinners).filter(AOCWinners.year == year) + db_session.commit() + db_session.close() - return "\n".join(format_member(i, m) for i, m in enumerate(members, 1)) + return [winner.aoc_userid for winner in prev_winners] - def format_day_leaderboard(self, members: List[Member]) -> str: + def _add_winners(self, winners: List[Member], year: int, prize: str): """ - Returns a string representing the leaderboard of the given members on - the given day. - - Full leaderboard includes rank, points, stars (per day), and username. + Add all members within the list to the database """ + for winner in winners: + db_session = self.bot.create_db_session() + db_session.add(AOCWinners(aoc_userid=winner.id, year=year, prize=prize)) + db_session.commit() + db_session.close() + + def _random_choices_without_repition( + self, population: List[Member], weights: List[int], k: int + ) -> List[Member]: + """ + Selects k people from a list of members, weighted by weights. + The weight of a person is like how many tickets they have for the lottery. + """ + result: List[Member] = [] + for _ in range(k): + if sum(weights) == 0: + return [] - def format_seconds(seconds: Optional[int]) -> str: - if seconds is None: - return "" - delta = timedelta(seconds=seconds) - if delta > timedelta(hours=24): - return ">24h" - return str(delta) - - # 3 8 8 8 - # |-| |------| |------| |------| - # Part 1 Part 2 Delta - # 1) 0:00:00 0:00:00 0:00:00 Name 1 - # 2) >24h >24h >24h Name 2 - def format_member(i: int, m: Member) -> str: - assert m.day is not None - part_1 = format_seconds(m.day_times.get(1)) - part_2 = format_seconds(m.day_times.get(2)) - delta = format_seconds(m.day_delta) - return f"{i:>3}) {part_1:>8} {part_2:>8} {delta:>8} {m.name}" + result.append(choices(population, weights)[0]) + index = population.index(result[-1]) + population.pop(index) + weights.pop(index) - header = " Part 1 Part 2 Delta\n" - return header + "\n".join(format_member(i, m) for i, m in enumerate(members, 1)) + return result - def format_advent_leaderboard( - self, members: List[Member], is_day: bool, is_global: bool, sort: SortMode - ) -> str: + @advent_command_group.command(name="help") + @app_commands.describe(command="The command you want to view help about.") + async def help_command( + self, interaction: discord.Interaction, command: Optional[Command] = None + ): """ - Returns a leaderboard for the given members with the given options. - - If full is True, leaderboard will show progress for all days, otherwise one - specific day is shown. + Print a help message about advent of code. """ + match command: + case None: + await interaction.response.send_message( + """ +[Advent of Code](https://adventofcode.com/) is a yearly coding competition that occurs during the first 25 days of december. Coding puzzles are released at 3pm AEST each day, with two stars available for each puzzle. You can spend as long as you like on each puzzle, but UQCS also has a private leaderboard with prizes on offer. + +To join, go to and sign in. The UQCS private leaderboard join code is `989288-0ff5a98d`. To be eligible for prizes, you will also have to link your discord account. This can be done by using the `/advent register` command. Reach out to committee if you are having any issues. + +For more help, you can use `/advent help ` to get information about a specific command. + """ + ) + case "help": + await interaction.response.send_message( + """ +`/advent help` is a help menu for all the Advent of Code commands. If you use `/advent help ` you can see details of a particular command. Not much else to say here, try another command. + """ + ) + case "leaderboard": + await interaction.response.send_message( + """ +`/advent leaderboard` displays a leaderboard for the Advent of Code challenges. There are two types of leaderboard: for a single day, and for the entire month. These are selected by either providing the `day` option or not. You can also display the leaderboard for a past year or another leaderboard (say another private leaderboard that you have). + +There are 6 different sorting options, which do slightly different things depending on whether the leaderboard is for a single day or an entire month. The default sorting method changes on which type of leaderboard you want. + `Star 1 Time ` - For single-day leaderboards, this sorts by the shortest time to get star 1 for the given problem. For monthly leaderboards, this sorts by the shortest total star 1 time for all problems. + `Star 2 Time ` - For single-day leaderboards, this sorts by the shortest time to get just star 2 for the given problem. For monthly leaderboards, this sorts by the shortest total star 2 time for all problems. + `Star 1 & 2 Time` - For single-day leaderboards, this sorts by the shortest time to get both stars 1 and 2 for the given problem. For monthly leaderboards, this sorts by the shortest total time working on all the problems. + `Total Time ` - This sorts by the sortest total time working on all the problems. For monthly leaderboards, this is the same as `Star 1 & 2 Time`. + `Total Stars ` - This sorts by the largest number of total stars collected over the month. + `Global ` - This sorts by users global score. Note that this will only show users with global score. + +You can also style the leaderboard (i.e. change the columns). The default style will change depending on whether the leaderboard is for a single-day or the entire month, and depending on the sorting method. Styles consist of a string, with each character representing a column. Use `/advent help leaderboard-style` to see the possoble characters. + """ + ) + case "leaderboard_style": + await interaction.response.send_message( + """ +Not a command, but an option given to the command `/advent leaderboard` controling the columns in the leaderboard. Each character in the given string represents a certain column. The possible characters are: +The characters in the string can be: + `# ` - Provides a column of the form "XXX)" telling the order for the given leaderboard + `1 ` - The time for star 1 for the specific day (daily leaderboards only) + `2 ` - The time for star 2 for the specific day (daily leaderboards only) + `3 ` - The time for both stars for the specific day (daily leaderboards only) + `! ` - The total time spent on first stars for the whole competition + `@ ` - The total time spent on second stars for the whole competition + `T ` - The total time spent overall for the whole competition + `* ` - The total number of stars for the whole competition + `L ` - The local ranking someone has within the UQCS leaderboard + `G ` - The global score someone has + `B ` - A progress bar of the stars each person has + `space` - A padding column of a single character +All other characters will be ignored. + """ + ) + case "register": + await interaction.response.send_message( + """ +`/advent register` links an Advent of Code account and a discord user so that you are eligble for prizes. Each Advent of Code account and discord account can only be linked to one other account each year. Note that registrations last for only the current year. If you are having any issues with this, message committee to help. + """ + ) + case "register-force": + await interaction.response.send_message( + """ +`/advent register-force` is an admin-only command to force a registration (i.e. create a registration between any Advent of Code account and Discord user). This can be used for moderation, if someone is having difficulties registering or if you want to register someone for a previous year. This command can break things (such as creating duplicate registrations), so be careful. Exactly one of `aoc_name` or `aoc_id` should be given. Also note that you need to use the Discord ID, not the discord username. If you have developer options enables on your account, this can be found by right clicking on the user and selecting `Copy User ID`. + """ + ) + case "unregister": + await interaction.response.send_message( + """ +`/advent unregister` unlinks your discord account from the currently linked Advent of Code account. Message committee if you need any help. + """ + ) + case "unregister-force": + await interaction.response.send_message( + """ +`/advent unregister-force` is an admin-only command that removes a registration from the database. This can be used as a moderation tool, to remove someone who has registered to an Advent of Code account that isn't there. Note that you need to use the Discord ID, not the discord username. If you have developer options enables on your account, this can be found by right clicking on the user and selecting `Copy User ID`. + """ + ) + case "previous-winners": + await interaction.response.send_message( + """ +`/advent previous-winners` displays the previous winners for a particular year. Note that the records for year prior to 2022 may not be accurate, as the current system was not in use then. + """ + ) + case "new-winner": + await interaction.response.send_message( + """ +`/advent add-winner` is an admin-only command that allows you to either manually or randomly select winners. Participants will only be eligible to win if they have completed at least one star within the given times. For manual selection, provide an Advent of Code user ID (note that this is not the same as their Advent of Code name), otherwise a random winner will be drawn. + +The arguments for the command have a bit of nuance. They are as follow: + `prize ` - A description of the prize to be given. This will be displayed when the winner is selected and if `/advent previous-winners` is used. + `start` & `end ` - The initial and final dates (inclusive) of the time range of the prize. To be eligible to win, participants need to get a star from ode of these days. The weights of the selected winner are determined from this range as well. + `weights ` - How the winners are selected. For "Equal", each eligible participant has an equal probability of winning. For "Stars", it is as if each user gets a "raffle ticket" for each star they completed within the timeframe, meaning more stars provides a greater chance of winning. + `number_of_winners ` - The number of winners to randomly select. + `allow_repeat_winners ` - This allows participants to win multiple times from the same selection if `number_of_winners` is greater than 1. Note that regardless of this option, someone can win multiple times in a year, just not in a single selection. + `allow_unregistered_users` - This allows Advent of Code accounts that do not have a linked discord account to win. Note that it can be difficult to give out prizes to users that do not have a linked discord. + `year ` - The year the prize is for. + """ + ) + case "remove-winner": + await interaction.response.send_message( + """ +`/advent remove-winner` is an admin-only command that removes a winner from the database. It uses the database ID (which is distinct from the Advent of code user ID and the Discord user ID). You can find these ids by running `/advent previous-winners show_ids:True`. + """ + ) + + @advent_command_group.command(name="leaderboard") + @app_commands.describe( + day="Day of the leaderboard [1-25]. If not given (default), the entire month leaderboard is given.", + year="Year of the leaderboard. Defaults to this year.", + code="The leaderboard code. Defaults to the UQCS leaderboard.", + sortby="The method to sort the leaderboard.", + leaderboard_style="The display format of the leaderboard. See the help menu for more information.", + ) + async def leaderboard_command( + self, + interaction: discord.Interaction, + day: Optional[Day] = None, + year: Optional[int] = None, + code: int = UQCS_LEADERBOARD, + sortby: Optional[SortingMethod] = None, + leaderboard_style: Optional[str] = None, + ): + """ + Display an advent of code leaderboard. + """ + if (not day is None) and (day not in ADVENT_DAYS): + await interaction.response.send_message( + "The day given is not a valid advent of code day." + ) + return - if is_day: - # filter to users who have at least one star on this day. - members = [m for m in members if m.day_times] - members.sort(key=Member.sort_key(sort)) - return self.format_day_leaderboard(members) + await interaction.response.defer(thinking=True) - if is_global: - # filter to users who have global points. - members = [m for m in members if m.global_] - members.sort(key=Member.sort_key(SortMode.GLOBAL)) - return self.format_global_leaderboard(members) + if year is None: + year = datetime.now().year + if sortby is None: + sortby = "Star 1 & 2 Time" if day else "Total Stars" + if leaderboard_style is None: + leaderboard_style = ( + leaderboards_for_day[sortby] if day else leaderboards_for_month[sortby] + ) - members.sort(key=Member.sort_key(SortMode.LOCAL)) - return self.format_full_leaderboard(members) + try: + members = self._get_members(year, code) + except InvalidHTTPSCode: + await interaction.edit_original_response( + content="Error fetching leaderboard data. Check the leaderboard code and year." + ) + return + except AssertionError: + await interaction.edit_original_response( + content="Error parsing leaderboard data." + ) + return - def parse_arguments(self, argv: List[str]) -> Namespace: - """ - Parses !advent arguments from the given list. + if code == UQCS_LEADERBOARD: + message = ":star: *Advent of Code UQCS Leaderboard* :trophy:" + else: + message = f":star: *Advent of Code Leaderboard {code}* :trophy:" - Returns namespace with argument values or throws UsageSyntaxException. - If an exception is thrown, its message should be shown to the user and - execution should NOT continue. - """ - parser = ArgumentParser("!advent", add_help=False) + if day: + message += f"\n:calendar: *Day {day}* (Sorted By {sortby})" + members = [member for member in members if member.attempted_day(day)] + members.sort(key=lambda m: sorting_functions_for_day[sortby](m, day)) + else: + members = [ + member + for member in members + if any(member.attempted_day(day) for day in ADVENT_DAYS) + ] + members.sort(key=sorting_functions_for_month[sortby]) - parser.add_argument( - "day", - type=int, - default=0, - nargs="?", - help="Show leaderboard for specific day" + " (default: all days)", - ) - parser.add_argument( - "-g", - "--global", - action="store_true", - dest="global_", - help="Show global points", - ) - parser.add_argument( - "-y", - "--year", - type=int, - default=datetime.now().year, - help="Year of leaderboard (default: current year)", - ) - parser.add_argument( - "-c", - "--code", - type=int, - default=UQCS_LEADERBOARD, - help="Leaderboard code (default: UQCS leaderboard)", - ) - parser.add_argument( - "-s", - "--sort", - default=SortMode.PART_2, - type=SortMode, - choices=(SortMode.PART_1, SortMode.PART_2, SortMode.DELTA), - help="Sorting method when displaying one day" - + " (default: part 2 completion time)", + if not members: + await interaction.edit_original_response( + content="This leaderboard contains no people." + ) + return + + scoreboard_file = io.BytesIO( + bytes( + print_leaderboard( + parse_leaderboard_column_string(leaderboard_style, self.bot), + members, + day, + ), + "utf-8", + ) ) - parser.add_argument( - "-h", "--help", action="store_true", help="Prints this help message" + await interaction.edit_original_response( + content=message, + attachments=[ + discord.File( + scoreboard_file, + filename=f"advent_{code}_{year}_{day}.txt", + ) + ], ) - # used to propagate usage errors out. - # somewhat hacky. typically, this should be done by subclassing ArgumentParser - def usage_error(message, *args, **kwargs): - raise ValueError(message) + @advent_command_group.command(name="register") + @app_commands.describe( + aoc_name="Your name shown on Advent of Code.", + ) + async def register_command(self, interaction: discord.Interaction, aoc_name: str): + """ + Register for prizes by linking your discord to an Advent of Code name. + """ + # TODO: Check UQCS membership + await interaction.response.defer(thinking=True) + + db_session = self.bot.create_db_session() + year = datetime.now().year - parser.error = usage_error + members = self._get_members(year) + if aoc_name not in [member.name for member in members]: + await interaction.edit_original_response( + content=f"Could not find the Advent of Code name `{aoc_name}` within the UQCS leaderboard." + ) + return + member = [member for member in members if member.name == aoc_name] + if len(member) != 1: + await interaction.edit_original_response( + content=f"Could not find a unique Advent of Code name `{aoc_name}` within the UQCS leaderboard." + ) + member = member[0] + AOC_id = member.id + + query = ( + db_session.query(AOCRegistrations) + .filter( + and_( + AOCRegistrations.year == year, AOCRegistrations.aoc_userid == AOC_id + ) + ) + .one_or_none() + ) + if query is not None: + discord_user = self.bot.uqcs_server.get_member(query.discord_userid) + if discord_user: + discord_ping = discord_user.mention + else: + discord_ping = f"someone who doesn't seem to be in the server (discord id = {query.discord_userid})" + await interaction.edit_original_response( + content=f"Advent of Code name `{aoc_name}` is already registered to {discord_ping}. Please contact committee if this is your Advent of Code name." + ) + return - args = parser.parse_args(argv) + discord_id = interaction.user.id + query = ( + db_session.query(AOCRegistrations) + .filter( + and_( + AOCRegistrations.year == year, + AOCRegistrations.discord_userid == discord_id, + ) + ) + .one_or_none() + ) + if query is not None: + await interaction.edit_original_response( + content=f"Your discord account ({interaction.user.mention}) is already registered to the Advent of Code name `{query.aoc_userid}`. You'll need to unregister to change name." + ) + return - if args.help: - raise ValueError("```\n" + parser.format_help() + "\n```") + db_session.add( + AOCRegistrations(aoc_userid=AOC_id, year=year, discord_userid=discord_id) + ) + db_session.commit() + db_session.close() - return args + await interaction.edit_original_response( + content=f"Advent of Code name `{aoc_name}` is now registered to {interaction.user.mention}." + ) - def get_leaderboard(self, year: int, code: int) -> Optional[Dict]: + @app_commands.checks.has_permissions(manage_guild=True) + @advent_command_group.command(name="register-force") + @app_commands.describe( + year="The year of Advent of Code this registration is for.", + discord_id_str="The discord ID number of the user. Note that this is not their username.", + aoc_name="The name shown on Advent of Code.", + aoc_id="The AOC id of the user.", + ) + async def register_admin_command( + self, + interaction: discord.Interaction, + year: int, + discord_id_str: str, # str as discord can't handle integers this big + aoc_name: Optional[str] = None, + aoc_id: Optional[int] = None, + ): """ - Returns a json dump of the leaderboard + Forces a registration entry to be created. For admin use only. Either aoc_name or aoc_id should be given. """ - try: - response = requests.get( - LEADERBOARD_URL.format(year=year, code=code), - cookies={"session": self.SESSION_ID}, + discord_id = int(discord_id_str) + if (aoc_name is None and aoc_id is None) or ( + aoc_name is not None and aoc_id is not None + ): + await interaction.response.send_message( + "Exactly one of `aoc_name` and `aoc_id` must be given.", ephemeral=True ) - return response.json() - except ValueError as exception: # json.JSONDecodeError - # TODO: Handle the case when the response is ok but the contents - # are invalid (cannot be parsed as json) - raise exception - except RequestException as exception: - logging.error(exception.response.content) - pass - return None - - @commands.command() - @loading_status - async def advent(self, ctx: commands.Context, *args): - """ - Prints the Advent of Code private leaderboard for UQCS. + return - !advent --help for additional help. - """ + await interaction.response.defer(thinking=True) - try: - args = self.parse_arguments(args) - except ValueError as error: - await ctx.send(str(error)) - return + db_session = self.bot.create_db_session() - try: - leaderboard = self.get_leaderboard(args.year, args.code) - except ValueError: - await ctx.send( - "Error fetching leaderboard data. Check the leaderboard code, year, and day." + if aoc_name: + members = self._get_members(year, force_refresh=True) + if aoc_name not in [member.name for member in members]: + await interaction.edit_original_response( + content=f"Could not find the Advent of Code name `{aoc_name}` within the UQCS leaderboard." + ) + return + member = [member for member in members if member.name == aoc_name] + if len(member) != 1: + await interaction.edit_original_response( + content=f"Could not find a unique Advent of Code name `{aoc_name}` within the UQCS leaderboard." + ) + member = member[0] + aoc_id = member.id + + query = ( + db_session.query(AOCRegistrations) + .filter( + and_( + AOCRegistrations.year == year, AOCRegistrations.aoc_userid == aoc_id + ) ) - raise - - try: - members = [ - Member.from_member_data(data, args.year, args.day) - for data in leaderboard["members"].values() - ] - except Exception: - await ctx.send("Error parsing leaderboard data.") - raise - - # whether to show only one day - is_day = bool(args.day) - # whether to use global points - is_global = args.global_ - - # header message - message = f":star: *Advent of Code Leaderboard {args.code}* :trophy:" - if is_day: - message += ( - f"\n:calendar: *Day {args.day}* (sorted by {SORT_LABELS[args.sort]})" - ) - elif is_global: - message += "\n:earth_asia: *Global Leaderboard Points*" - - scoreboardFile = io.StringIO( - self.format_advent_leaderboard(members, is_day, is_global, args.sort) + .one_or_none() ) - await ctx.send( - file=discord.File( - scoreboardFile, - filename=f"advent_{args.code}_{args.year}_{args.day}.txt", + if query is not None: + discord_user = self.bot.uqcs_server.get_member(query.discord_userid) + if discord_user: + discord_ping = discord_user.mention + else: + discord_ping = f"someone who doesn't seem to be in the server (discord id = {query.discord_userid})" + await interaction.edit_original_response( + content=f"Advent of Code name `{aoc_name}` is already registered to {discord_ping}." ) - ) + return - async def reminder_fifteen_minutes(self): - channel = discord.utils.get( - self.bot.uqcs_server.channels, name=self.CHANNEL_NAME + db_session.add( + AOCRegistrations(aoc_userid=aoc_id, year=year, discord_userid=discord_id) ) - if channel is not None: - await channel.send( - "Today's Advent of Code puzzle is released in 15 minutes." - ) + db_session.commit() + db_session.close() + + discord_user = self.bot.uqcs_server.get_member(discord_id) + if discord_user: + discord_ping = discord_user.mention else: - logging.warning(f"Could not find required channel #{self.CHANNEL_NAME}") + discord_ping = f"someone who doesn't seem to be in the server (discord id = {discord_id})" + await interaction.edit_original_response( + content=f"Advent of Code name `{aoc_name}` is now registered to {discord_ping} (for {year})." + ) - async def reminder_released(self): - channel = discord.utils.get( - self.bot.uqcs_server.channels, name=self.CHANNEL_NAME + @advent_command_group.command(name="unregister") + async def unregister_command(self, interaction: discord.Interaction): + """ + Remove your registration for Advent of code prizes. + """ + await interaction.response.defer(thinking=True) + + db_session = self.bot.create_db_session() + year = datetime.now().year + + discord_id = interaction.user.id + query = db_session.query(AOCRegistrations).filter( + and_( + AOCRegistrations.year == year, + AOCRegistrations.discord_userid == discord_id, + ) ) - if channel is not None: - await channel.send( - "Today's Advent of Code puzzle has been released. Good luck!" + if (query.one_or_none()) is None: + await interaction.edit_original_response( + content=f"Your discord account ({interaction.user.mention}) is already unregistered for this year." ) - else: - logging.warning(f"Could not find required channel #{self.CHANNEL_NAME}") + return - def _get_previous_winners(self, year: int): - db_session = self.bot.create_db_session() - prev_winners = db_session.query(AOCWinner).filter(AOCWinner.year == year) + query.delete(synchronize_session=False) + db_session.commit() db_session.close() - return [winner.aoc_userid for winner in prev_winners] + await interaction.edit_original_response( + content=f"{interaction.user.mention} is no longer registered to win Advent of Code prizes." + ) - def _add_winners(self, winners: List[Member], year: int): - db_session = self.bot.create_db_session() + @app_commands.checks.has_permissions(manage_guild=True) + @advent_command_group.command(name="unregister-force") + @app_commands.describe( + year="Year that the registration is for", + discord_id_str="The discord id to remove. Note that this is not the username.", + ) + async def unregister_admin_command( + self, interaction: discord.Interaction, year: int, discord_id_str: str + ): + """ + Forces a registration entry to be removed. + For admin use only; assumes you know what you are doing. + """ + discord_id = int(discord_id_str) + await interaction.response.defer(thinking=True) + discord_user = self.bot.uqcs_server.get_member(discord_id) - for winner in winners: - winner = AOCWinner(aoc_userid=winner.id, year=year) - db_session.add(winner) + db_session = self.bot.create_db_session() + query = db_session.query(AOCRegistrations).filter( + and_( + AOCRegistrations.year == year, + AOCRegistrations.discord_userid == discord_id, + ) + ) + if (query.one_or_none()) is None: + if discord_user: + discord_ping = discord_user.mention + else: + discord_ping = ( + f"who does not seem to be in the server; id = {discord_id}" + ) + await interaction.edit_original_response( + content=f"This discord account ({discord_ping}) is already unregistered for this year. Ensure that you enter the users discord id, not discord name or nickname." + ) + return + query.delete(synchronize_session=False) db_session.commit() db_session.close() - def random_choices_without_repition(self, population, weights, k): - result = [] - for _ in range(k): - if sum(weights) == 0: - return None + if discord_user: + discord_ping = discord_user.mention + else: + discord_ping = ( + f"A user who does not seem to be in the server (id = {discord_id})" + ) + await interaction.edit_original_response( + content=f"{discord_ping} is no longer registered to win Advent of Code prizes for {year}." + ) - result.append(choices(population, weights)[0]) - index = population.index(result[-1]) - population.pop(index) - weights.pop(index) + @advent_command_group.command(name="previous-winners") + @app_commands.describe( + year="Year to find the previous listed winners for. Defaults to the current year.", + show_ids="Whether to show the database ids. Mainly for debugging purposes. Defaults to false.", + ) + async def previous_winners_command( + self, + interaction: discord.Interaction, + year: Optional[int] = None, + show_ids: bool = False, + ): + """ + List the previous winners of Advent of Code. + """ + await interaction.response.defer(thinking=True) + if year is None: + year = datetime.now().year - return result + db_session = self.bot.create_db_session() + prev_winners = list( + db_session.query(AOCWinners).filter(AOCWinners.year == year) + ) + + if not prev_winners: + await interaction.edit_original_response( + content=f"No Advent of Code winners are on record for {year}." + ) + return + + registrations = self._get_registrations(year) + registered_AOC_ids = [member.aoc_userid for member in registrations] + + # TODO would an embed be appropriate? + message = f"UQCS Advent of Code winners for {year}:" + for winner in prev_winners: + message += f"\n{winner.id} " if show_ids else "\n" + + name = [ + member.name + for member in self._get_members(year) + if member.id == winner.aoc_userid + ] + # There are three types of user: + # 1) Those who are not on the downloaded members list from AOC (error case) + # 2) Those who have not linked a discord account + # 3) Those who have linked a discord account + if len(name) != 1: + message += f"Unknown User (AOC id {winner.aoc_userid}) - {winner.prize}" + elif winner.aoc_userid not in registered_AOC_ids: + message += f"{name[0]} (unregisted discord) - {winner.prize}" + else: + discord_user = self.bot.uqcs_server.get_member( + [user.discord_userid for user in registrations][0] + ) + discord_ping = f" ({discord_user.display_name})" if discord_user else "" + # Don't actually ping as this may be called many times + message += f"{name[0]}{discord_ping} - {winner.prize}" + db_session.commit() + db_session.close() - @commands.command() - @loading_status - async def advent_winners( - self, ctx: commands.Context, start: int, end: int, numberOfWinners: int, *args + await interaction.edit_original_response(content=message) + + @app_commands.checks.has_permissions(manage_guild=True) + @advent_command_group.command(name="add-winners") + @app_commands.describe( + prize="A description of the prize that is being awarded.", + start="The initial date (inclusive) to base the weights on. Defaults to 1.", + end="The final date (includive) to base the weights on. Defaults to 25.", + number_of_winners="The number of winners to select. Defaults to 1.", + weights='How to bias the winner selection. Defaults to "Equal"', + allow_repeat_winners="Allow for winners to be selected multiple times. Defaults to False", + allow_unregistered_users="Allow winners to be selected from unregistered users. Defaults to False.", + year="The year the prize is for. Defaults to the current year.", + aoc_id="The AOC id of the winner to add, if selecting a winner. Use only if manually selecting a winner.", + ) + async def add_winners_command( + self, + interaction: discord.Interaction, + prize: str, + start: int = 1, + end: int = 25, + number_of_winners: int = 1, + weights: Literal["Stars", "Equal"] = "Equal", + allow_repeat_winners: bool = False, + allow_unregistered_users: bool = False, + year: Optional[int] = None, + aoc_id: Optional[int] = None, ): """ - Determines winners for the AOC competition. Winners must be drawn by a member of the committee. - - !advent --help for additional help. + Randomly choose (or select) winners from those who have completed challenges. """ - if len([role for role in ctx.author.roles if role.name == "Committee"]) == 0: - await ctx.send("Only committee can select the winners") - return - try: - args = self.parse_arguments(args) - except ValueError as error: - await ctx.send(str(error)) - return + await interaction.response.defer(thinking=True) + if year is None: + year = datetime.now().year - try: - leaderboard = self.get_leaderboard(args.year, args.code) - except ValueError: - await ctx.send( - "Error fetching leaderboard data. Check the leaderboard code, year, and day." + if aoc_id: + self._add_winners( + [member for member in self._get_members(year) if member.id == aoc_id], + year, + prize, + ) + # Note that this message is a bit more dull, as it should only be used for admin reasons. + await interaction.edit_original_response( + content=f"The user with AOC id {aoc_id} has been recorded as winning a prize: {prize}" ) - raise + return - try: - members = [ - Member.from_member_data(data, args.year, args.day) - for data in leaderboard["members"].values() - ] - except Exception: - await ctx.send("Error parsing leaderboard data.") - raise + registrations = self._get_registrations(year) + registered_AOC_ids = [member.aoc_userid for member in registrations] - previous_winners = self._get_previous_winners(args.year) potential_winners = [ - member for member in members if int(member.id) not in previous_winners - ] - weights = [ - sum([1 for d in range(start, end + 1) if len(member.all_times[d]) > 0]) - for member in potential_winners + member + for member in self._get_members(year) + if any(member.attempted_day(day) for day in range(start, end + 1)) ] + if not allow_unregistered_users: + potential_winners = [ + member + for member in potential_winners + if member.id in registered_AOC_ids + ] - winners = self.random_choices_without_repition( - potential_winners, weights, numberOfWinners - ) + if allow_repeat_winners: + required_number_of_potential_winners = 1 + else: + required_number_of_potential_winners = number_of_winners - if winners == None: - await ctx.send( - f"Insufficient participants to be able to draw {numberOfWinners} winners." + if len(potential_winners) < required_number_of_potential_winners: + await interaction.edit_original_response( + content=f"There were not enough eligible users to select winners (at least {required_number_of_potential_winners} needed; only {len(potential_winners)} found)." ) return + number_of_potential_winners = len( + potential_winners + ) # potential winners will be changed ahead, so we store this value for the award message + + match weights: + case "Stars": + weight_values = [ + sum(len(member.times[day]) for day in range(start, end + 1)) + for member in potential_winners + ] + case "Equal": + weight_values = [1 for _ in potential_winners] + + if allow_repeat_winners: + winners = choices(potential_winners, weight_values, k=number_of_winners) + else: + winners = self._random_choices_without_repition( + potential_winners, weight_values, number_of_winners + ) - self._add_winners(winners, args.year) + if not winners: + await interaction.edit_original_response( + content="There was some problem choosing the winners." + ) + return - await ctx.send( - "And the winners are:\n" - + "\n".join( - [ - winner.name - if (winner.name != None) - else "anonymous user #" + str(winner.id) - for winner in winners - ] + self._add_winners(winners, year, prize) + + distinct_winners = set(winners) + + winners_message = "" + for i, winner in enumerate(distinct_winners): + discord_id = winner.get_discord_userid(self.bot) + discord_user = ( + self.bot.uqcs_server.get_member(discord_id) if discord_id else None + ) + discord_ping = f" ({discord_user.mention})" if discord_user else "" + number_of_prizes = len( + [member for member in winners if member.id == winner.id] + ) + prize_multiplier = f" (x{number_of_prizes})" if number_of_prizes > 1 else "" + winners_message += f"{winner.name}{discord_ping}{prize_multiplier}" + if len(distinct_winners) == 1: + pass + elif i < len(distinct_winners) - 1: + winners_message += ", " + else: + winners_message += " and " + + await interaction.edit_original_response( + content=f"The results are in! Out of {number_of_potential_winners} potential participants, {winners_message} have recieved a prize from participating in Advent of Code: {prize}" + ) + + @app_commands.checks.has_permissions(manage_guild=True) + @advent_command_group.command(name="remove-winner") + @app_commands.describe( + id="The database entry id for the winners database that should be deleted." + ) + async def remove_winner_command(self, interaction: discord.Interaction, id: int): + """ + Remove an AOC winner from the database. + The show_ids option for previous-winners can get the id. + """ + await interaction.response.defer(thinking=True) + + db_session = self.bot.create_db_session() + + query = db_session.query(AOCWinners).filter(AOCWinners.id == id) + if query.one_or_none() is None: + await interaction.response.send_message( + f"No Advent of Code winners could be found with a database id of {id}." ) + return + + query.delete(synchronize_session=False) + db_session.commit() + db_session.close() + + await interaction.edit_original_response( + content=f"Removed the winners entry with id {id}." ) diff --git a/uqcsbot/bot.py b/uqcsbot/bot.py index e2b045cf..77887a92 100644 --- a/uqcsbot/bot.py +++ b/uqcsbot/bot.py @@ -29,6 +29,8 @@ def __init__(self, *args: Any, **kwargs: Any): # Important channel names & constants go here self.ADMIN_ALERTS_CNAME = "admin-alerts" self.GENERAL_CNAME = "general" + self.AOC_CNAME = "contests" + self.AOC_ROLE = "CPG" self.BOT_TIMEZONE = timezone("Australia/Brisbane") self.uqcs_server: discord.Guild diff --git a/uqcsbot/models.py b/uqcsbot/models.py index 44a5dd2a..87744b74 100644 --- a/uqcsbot/models.py +++ b/uqcsbot/models.py @@ -16,10 +16,23 @@ class Base(DeclarativeBase): pass -class AOCWinner(Base): - __tablename__ = "aoc_winner" +class AOCWinners(Base): + __tablename__ = "aoc_winners" - id: Mapped[int] = mapped_column("id", BigInteger, primary_key=True, nullable=False) + id: Mapped[int] = mapped_column( + "id", Integer, primary_key=True, nullable=False, autoincrement=True + ) + aoc_userid: Mapped[int] = mapped_column("aoc_userid", Integer, nullable=False) + year: Mapped[int] = mapped_column("year", Integer, nullable=False) + prize: Mapped[str] = mapped_column("prize", String, nullable=True) + + +class AOCRegistrations(Base): + __tablename__ = "aoc_registrations" + + discord_userid: Mapped[int] = mapped_column( + "discord_userid", BigInteger, primary_key=True, nullable=False + ) aoc_userid: Mapped[int] = mapped_column("aoc_userid", Integer, nullable=False) year: Mapped[int] = mapped_column("year", Integer, nullable=False) diff --git a/uqcsbot/utils/advent_utils.py b/uqcsbot/utils/advent_utils.py new file mode 100644 index 00000000..b527ceff --- /dev/null +++ b/uqcsbot/utils/advent_utils.py @@ -0,0 +1,386 @@ +from typing import Any, List, Literal, Dict, Optional, Callable +from datetime import datetime, timedelta +from pytz import timezone + +from uqcsbot.bot import UQCSBot +from uqcsbot.models import AOCRegistrations + +# Days in Advent of Code. List of numbers 1 to 25. +ADVENT_DAYS = list(range(1, 25 + 1)) + +# type aliases for documentation purposes. +Day = int # from 1 to 25 +Star = Literal[1, 2] +Seconds = int +Times = Dict[Star, Seconds] +Delta = Optional[Seconds] +Json = Dict[str, Any] + +# Puzzles are unlocked at midnight EST. +EST_TIMEZONE = timezone("US/Eastern") + +# The time to cache results to limit requests to adventofcode.com. Note that 15 minutes is the recomended minimum time. +CACHE_TIME = timedelta(minutes=15) + + +class InvalidHTTPSCode(Exception): + def __init__(self, message: str, request_code: int): + super().__init__(message) + self.request_code = request_code + + +class Member: + def __init__(self, id: int, name: str, local: int, star_total: int, global_: int): + # The advent of code id + self.id = id + # The advent of code name + self.name = name + # The score of the user on the local leaderboard + self.local = local + # The total number of stars the user has collected + self.star_total = star_total + # The score of the user on the global leaderboard + self.global_ = global_ + + # All of the Times. If no stars are collected, the Times dictionary is empty. + self.times: Dict[Day, Times] = {d: {} for d in ADVENT_DAYS} + + @classmethod + def from_member_data(cls, data: Json, year: int) -> "Member": + """ + Constructs a Member from the API response. + + Times and delta are calculated for the given year and day. + """ + + member = cls( + data["id"], + data["name"], + data["local_score"], + data["stars"], + data["global_score"], + ) + + for d, day_data in data["completion_day_level"].items(): + day = int(d) + times = member.times[day] + + # timestamp of puzzle unlock, rounded to whole seconds + DAY_START = int(datetime(year, 12, day, tzinfo=EST_TIMEZONE).timestamp()) + + for s, star_data in day_data.items(): + star = int(s) + # assert is for type checking + assert star == 1 or star == 2 + times[star] = int(star_data["get_star_ts"]) - DAY_START + assert times[star] >= 0 + + return member + + def get_time_delta(self, day: Day) -> Optional[Seconds]: + """ + Returns the number of seconds between the completion of the second star from the first, or None if the second star have not been completed. + """ + if len(self.times[day]) == 2: + return self.times[day][2] - self.times[day][1] + return None + + def attempted_day(self, day: Day) -> bool: + """ + Returns if a member completed at least the first star in the day + """ + return len(self.times[day]) >= 1 + + def get_total_star1_time(self, default: int = 0) -> int: + """ + Returns the total time working on just star 1 for all challenges in a year. + The argument default determines the returned value if the total is 0. + """ + total = sum(self.times[day].get(1, 0) for day in ADVENT_DAYS) + return total if total != 0 else default + + def get_total_star2_time(self, default: int = 0) -> int: + """ + Returns the total time working on just star 2 for all challenges in a year. + The argument default determines the returned value if the total is 0. + """ + total = sum(self.times[day].get(2, 0) for day in ADVENT_DAYS) + return total if total != 0 else default + + def get_total_time(self, default: int = 0) -> int: + """ + Returns the total time working on stars 1 and 2 for all challenges in a year. + The argument default determines the returned value if the total is 0. + """ + total = self.get_total_star1_time() + self.get_total_star2_time() + return total if total != 0 else default + + def get_discord_userid(self, bot: UQCSBot) -> Optional[int]: + """ + Return the discord userid of this AOC member if one is registered in the database. + """ + db_session = bot.create_db_session() + registration = ( + db_session.query(AOCRegistrations) + .filter(AOCRegistrations.aoc_userid == self.id) + .one_or_none() + ) + db_session.close() + if registration: + return registration.discord_userid + return None + + +def _star_char(num_stars: int): + """ + Given a number of stars (0, 1, or 2), returns its leaderboard + representation. + """ + return " .*"[num_stars] + + +def _format_seconds(seconds: Optional[int]): + """ + Format seconds into the format "hh:mm:ss" or ">24h". + """ + if seconds is None or seconds == 0: + return "" + delta = timedelta(seconds=seconds) + if delta > timedelta(hours=24): + return ">24h" + return str(delta) + + +def _format_seconds_long(seconds: Optional[int]): + """ + Format seconds into the format "hhhh:mm:ss" or ">30 days". + """ + if seconds is None or seconds == 0: + return "-" + hours, remainder = divmod(seconds, 3600) + minutes, seconds = divmod(remainder, 60) + if hours >= 30 * 24: + return ">30 days" + return f"{hours}:{minutes:02}:{seconds:02}" + + +def _get_member_star_progress_bar(member: Member): + return "".join(_star_char(len(member.times[day])) for day in ADVENT_DAYS) + + +class LeaderboardColumn: + """ + A column in a leaderboard. The title is the name of the column as 2 lines and the calculation is a function that determines what is printed for a given member, index and day. The title and calculation should have the same constant width. + """ + + def __init__( + self, + title: tuple[str, str], + calculation: Callable[[Member, int, Optional[Day]], str], + ): + self.title = title + self.calculation = calculation + + @staticmethod + def ordering_column(): + """ + A column used at the right of leaderboards to indicate the overall order. Of the format "XXX)" where XXX is a left padded number of 3 characters. + """ + return LeaderboardColumn( + title=(" " * 4, " " * 4), # Empty spaces, as this does not need a heading + calculation=lambda _, index, __: f"{index:>3})", + ) + + @staticmethod + def star1_column(): + """ + A column indicating the time taken to achieve the first star. Of the format "hh:mm:ss" or ">24h". Only applicable for particular days. + """ + return LeaderboardColumn( + title=(" " * 8, " Star 1 "), + calculation=lambda member, _, day: f"{_format_seconds(member.times[day].get(1, 0)) if day else '':>8}", + ) + + @staticmethod + def star2_column(): + """ + A column indicating the time taken to achieve only the second star. Of the format "hh:mm:ss" or ">24h". Only applicable for particular days. + """ + return LeaderboardColumn( + title=(" " * 8, " Star 2 "), + calculation=lambda member, _, day: f"{_format_seconds(member.get_time_delta(day)) if day else '':>8}", + ) + + @staticmethod + def star1_and_2_column(): + """ + A column indicating the time taken to achieve both stars. Of the format "hh:mm:ss" or ">24h". Only applicable for particular days. + """ + return LeaderboardColumn( + title=(" " * 10, "Both Stars"), + calculation=lambda member, _, day: f"{_format_seconds(member.times[day].get(2, 0)) if day else '':>10}", + ) + + @staticmethod + def total_time_column(): + """ + A column indicating the total time the user has spent on all stars. Of the format "hhhh:mm:ss" or ">30 days". + """ + return LeaderboardColumn( + title=(" " * 10, "Total Time"), + calculation=lambda member, _, __: f"{_format_seconds_long(member.get_total_time()):>10}", + ) + + @staticmethod + def total_star1_time_column(): + """ + A column indicating the total time the user has spent on first stars. Of the format "hhhh:mm:ss" or ">30 days". + """ + return LeaderboardColumn( + title=("Total Star", " 1 Time "), + calculation=lambda member, _, __: f"{_format_seconds_long(member.get_total_star1_time()):>10}", + ) + + @staticmethod + def total_star2_time_column(): + """ + A column indicating the total time the user has spent on second stars. Of the format "hhhh:mm:ss" or ">30 days". + """ + return LeaderboardColumn( + title=("Total Star", " 2 Time "), + calculation=lambda member, _, __: f"{_format_seconds_long(member.get_total_star2_time()):>10}", + ) + + @staticmethod + def stars_column(): + """ + A column indicating the total number of stars a user has. Of the format of a 5 character right-padded number. + """ + return LeaderboardColumn( + title=("Total", "Stars"), + calculation=lambda member, _, __: f"{member.star_total if member.star_total else '':>5}", + ) + + @staticmethod + def local_rank_column(): + """ + A column indicating the members local rank (of the UQCS leaderboard). Of the format of a 5 character right-padded number. + """ + return LeaderboardColumn( + title=("Local", "Order"), + calculation=lambda member, _, __: f"{member.local if member.local else '':>5}", + ) + + @staticmethod + def global_score_column(): + """ + A column indicating the members global score. Of the format of a 5 character right-padded number. + """ + return LeaderboardColumn( + title=("Global", "Score "), + calculation=lambda member, _, __: f"{member.global_ if member.global_ else '':>6}", + ) + + @staticmethod + def star_bar_column(): + """ + A column with a progressbar of the stars that each person has. + """ + return LeaderboardColumn( + title=(" " * 9 + "1" * 10 + "2" * 6, "1234567890123456789012345"), + calculation=lambda member, _, __: _get_member_star_progress_bar(member), + ) + + @staticmethod + def name_column(bot: UQCSBot): + """ + A column listing each name. + """ + + def format_name(member: Member, _: int, __: Optional[int]) -> str: + if not (discord_userid := member.get_discord_userid(bot)): + return member.name + if not (discord_user := bot.uqcs_server.get_member(discord_userid)): + return member.name + # Don't actually ping as leaderboard is called many times + return f"{member.name} (@{discord_user.display_name})" + + return LeaderboardColumn(title=("", ""), calculation=format_name) + + @staticmethod + def padding_column(): + """ + A column that is of a single space character. + """ + return LeaderboardColumn(title=(" ", " "), calculation=lambda _, __, ___: " ") + + +def parse_leaderboard_column_string(s: str, bot: UQCSBot) -> List[LeaderboardColumn]: + """ + Create a list of columns corresponding to the given string. The characters in the string can be: + # - Provides a column of the form "XXX)" telling the order for the given leaderboard + 1 - The time for star 1 for the specific day (daily leaderboards only) + 2 - The time for star 2 for the specific day (daily leaderboards only) + 3 - The time for both stars for the specific day (dayly leaderboards only) + ! - The total time spent on first stars for the whole competition + @ - The total time spent on second stars for the whole competition + T - The total time spent overall for the whole competition + * - The total number of stars for the whole competition + L - The local ranking someone has within the UQCS leaderboard + G - The global score someone has + B - A progress bar of the stars each person has + space - A padding column of a single character + All other characters will be ignored + """ + columns: List[LeaderboardColumn] = [] + for c in s: + match c: + case "#": + columns.append(LeaderboardColumn.ordering_column()) + case "1": + columns.append(LeaderboardColumn.star1_column()) + case "2": + columns.append(LeaderboardColumn.star2_column()) + case "3": + columns.append(LeaderboardColumn.star1_and_2_column()) + case "!": + columns.append(LeaderboardColumn.total_star1_time_column()) + case "@": + columns.append(LeaderboardColumn.total_star2_time_column()) + case "T": + columns.append(LeaderboardColumn.total_time_column()) + case "*": + columns.append(LeaderboardColumn.stars_column()) + case "L": + columns.append(LeaderboardColumn.local_rank_column()) + case "G": + columns.append(LeaderboardColumn.global_score_column()) + case "B": + columns.append(LeaderboardColumn.star_bar_column()) + case " ": + columns.append(LeaderboardColumn.padding_column()) + case _: + pass + columns.append(LeaderboardColumn.padding_column()) + columns.append(LeaderboardColumn.name_column(bot)) + return columns + + +def print_leaderboard( + columns: List[LeaderboardColumn], members: List[Member], day: Optional[Day] +): + """ + Returns a string of the leaderboard of the given format. + """ + leaderboard = "".join(column.title[0] for column in columns) + leaderboard += "\n" + leaderboard += "".join(column.title[1] for column in columns) + + # Note that leaderboards start at 1, not 0 + for id, member in enumerate(members, start=1): + leaderboard += "\n" + leaderboard += "".join( + column.calculation(member, id, day) for column in columns + ) + + return leaderboard From f85153c2de51363423780cabb36b802e4ecec834 Mon Sep 17 00:00:00 2001 From: andrewj-brown <92134285+andrewj-brown@users.noreply.github.com> Date: Thu, 30 Nov 2023 22:03:45 +1000 Subject: [PATCH 34/48] cname changes --- uqcsbot/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uqcsbot/bot.py b/uqcsbot/bot.py index 77887a92..2c21174a 100644 --- a/uqcsbot/bot.py +++ b/uqcsbot/bot.py @@ -29,7 +29,7 @@ def __init__(self, *args: Any, **kwargs: Any): # Important channel names & constants go here self.ADMIN_ALERTS_CNAME = "admin-alerts" self.GENERAL_CNAME = "general" - self.AOC_CNAME = "contests" + self.AOC_CNAME = "cpg" self.AOC_ROLE = "CPG" self.BOT_TIMEZONE = timezone("Australia/Brisbane") From eef73e8a2e89c1e047e70aaba59b970f73e0cb77 Mon Sep 17 00:00:00 2001 From: andrewj-brown <92134285+andrewj-brown@users.noreply.github.com> Date: Thu, 30 Nov 2023 22:06:28 +1000 Subject: [PATCH 35/48] i hoped role names weren't case sensitive --- uqcsbot/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uqcsbot/bot.py b/uqcsbot/bot.py index 2c21174a..bc01bd3d 100644 --- a/uqcsbot/bot.py +++ b/uqcsbot/bot.py @@ -30,7 +30,7 @@ def __init__(self, *args: Any, **kwargs: Any): self.ADMIN_ALERTS_CNAME = "admin-alerts" self.GENERAL_CNAME = "general" self.AOC_CNAME = "cpg" - self.AOC_ROLE = "CPG" + self.AOC_ROLE = "cpg" self.BOT_TIMEZONE = timezone("Australia/Brisbane") self.uqcs_server: discord.Guild From 5efdfa09d98cf79a28d3255cf76905584ae7c7d3 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Thu, 30 Nov 2023 23:15:06 +1000 Subject: [PATCH 36/48] Fixed the bug crashing leaderboards from anonymous users (#185) --- uqcsbot/utils/advent_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uqcsbot/utils/advent_utils.py b/uqcsbot/utils/advent_utils.py index b527ceff..7d3ba168 100644 --- a/uqcsbot/utils/advent_utils.py +++ b/uqcsbot/utils/advent_utils.py @@ -34,7 +34,7 @@ def __init__(self, id: int, name: str, local: int, star_total: int, global_: int # The advent of code id self.id = id # The advent of code name - self.name = name + self.name = name if name else "Anon" # The score of the user on the local leaderboard self.local = local # The total number of stars the user has collected From 3cc856bc7e9fb1e67a459f8639dcbc7b70b8df18 Mon Sep 17 00:00:00 2001 From: Jackie <108224888+fattyhope@users.noreply.github.com> Date: Fri, 15 Dec 2023 18:55:23 +1000 Subject: [PATCH 37/48] Unwhitelist command (#188) * New command which unwhitelists user from a server, kicking them off instantly * Refactored code so it pases code style check * Fixed dodgy check, removed yelling and changd response format * Removed unused query variable and refactored its related code * Refactored code running the command: poetry run black uqcsbot * Removed comment previously used for debugging --- uqcsbot/minecraft.py | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/uqcsbot/minecraft.py b/uqcsbot/minecraft.py index 76c8a649..da5d1212 100644 --- a/uqcsbot/minecraft.py +++ b/uqcsbot/minecraft.py @@ -93,6 +93,51 @@ async def mcwhitelist(self, interaction: discord.Interaction, username: str): db_session.close() + @app_commands.command() + @app_commands.describe(username="Minecraft username to unwhitelist.") + async def mcunwhitelist(self, interaction: discord.Interaction, username: str): + """Removes a username from the whitelist for the UQCS server.""" + db_session = self.bot.create_db_session() + is_user_admin = ( + isinstance(interaction.user, Member) + and interaction.user.guild_permissions.manage_guild + ) + + # If the user has already whitelisted someone, and they aren't an admin deny it. + if not is_user_admin: + await interaction.response.send_message( + "You've already whitelisted an account." + ) + else: + # Send the RCON command to remove the user from the whitelist + response_remove = await self.send_rcon_command( + f"whitelist remove {username}" + ) + logging.info(f"[MINECRAFT] whitelist remove {username}: {response_remove}") + + # Send the RCON command to kick the player from the server + response_kick = await self.send_rcon_command(f"kick {username}") + logging.info(f"[MINECRAFT] kick {username}: {response_kick}") + + # If the responses indicate successful removal, remove from the database item + if "Removed" in response_remove[0]: + db_session.query(MCWhitelist).filter( + MCWhitelist.mc_username == username + ).delete() + db_session.commit() + + await self.bot.admin_alert( + title="Minecraft Server Unwhitelist", + description=response_remove[0], + footer=f"Action performed by {interaction.user}", + colour=Colour.red(), + ) + + # Display the response to the user in Discord + await interaction.response.send_message(response_remove[0]) + + db_session.close() + mcadmin_group = app_commands.Group( name="mcadmin", description="Commands for managing the UQCS Minecraft server" ) From d96efb13f5820c6f0a583a617da7ac77ac384bff Mon Sep 17 00:00:00 2001 From: James Dearlove <39483549+JamesDearlove@users.noreply.github.com> Date: Sat, 23 Dec 2023 22:50:42 +1000 Subject: [PATCH 38/48] Add allowed_mentions to ping messages (#187) Co-authored-by: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> --- uqcsbot/advent.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/uqcsbot/advent.py b/uqcsbot/advent.py index 92cdbdbc..5f53d60f 100644 --- a/uqcsbot/advent.py +++ b/uqcsbot/advent.py @@ -284,7 +284,10 @@ async def reminder_fifteen_minutes(self): The function used within the AOC reminder 15 minutes before each challenge starts. """ await self.channel.send( - f"{self.role.mention}Today's Advent of Code puzzle is released in 15 minutes." + f"{self.role.mention} Today's Advent of Code puzzle is released in 15 minutes.", + allowed_mentions=discord.AllowedMentions( + everyone=False, users=False, roles=True + ), ) async def reminder_released(self): @@ -292,7 +295,10 @@ async def reminder_released(self): The function used within the AOC reminder when each challenge starts. """ await self.channel.send( - f"{self.role.mention}Today's Advent of Code puzzle has been released. Good luck!" + f"{self.role.mention} Today's Advent of Code puzzle has been released. Good luck!", + allowed_mentions=discord.AllowedMentions( + everyone=False, users=False, roles=True + ), ) def _get_previous_winner_aoc_ids(self, year: int) -> List[int]: @@ -962,7 +968,10 @@ async def add_winners_command( winners_message += " and " await interaction.edit_original_response( - content=f"The results are in! Out of {number_of_potential_winners} potential participants, {winners_message} have recieved a prize from participating in Advent of Code: {prize}" + content=f"The results are in! Out of {number_of_potential_winners} potential participants, {winners_message} have recieved a prize from participating in Advent of Code: {prize}", + allowed_mentions=discord.AllowedMentions( + everyone=False, users=True, roles=False + ), ) @app_commands.checks.has_permissions(manage_guild=True) From a5dd7988cfa1684589bf6707033dd0825095e5a2 Mon Sep 17 00:00:00 2001 From: Anti Date: Mon, 25 Dec 2023 10:40:03 +1000 Subject: [PATCH 39/48] Added command to get current MC server player count (#190) * Added command to get current MC server player count * Added env variables for mc public ip and port * Added public ip and port to env.example (oops) * Ran black to fit code styling --- .env.example | 2 + poetry.lock | 115 +++++++++++++++++++++++++++++++++++++++++-- pyproject.toml | 1 + uqcsbot/minecraft.py | 25 ++++++++++ 4 files changed, 139 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index c19c1d24..c0b27a92 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,8 @@ HACKATHON_END_TIME= MC_RCON_ADDRESS= MC_RCON_PORT= MC_RCON_PASSWORD= +MC_PUBLIC_IP= +MC_PUBLIC_PORT= SB_BASE_THRESHOLD=8 SB_BIG_THRESHOLD=24 SB_RATELIMIT=30 diff --git a/poetry.lock b/poetry.lock index ed25228b..6152c59b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] name = "aio-mc-rcon" version = "3.2.0" description = "An async library for utilizing remote console on Minecraft Java Edition servers" +category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -15,6 +16,7 @@ files = [ name = "aiohttp" version = "3.8.4" description = "Async http client/server framework (asyncio)" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -123,6 +125,7 @@ speedups = ["Brotli", "aiodns", "cchardet"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -137,6 +140,7 @@ frozenlist = ">=1.1.0" name = "apscheduler" version = "3.10.1" description = "In-process task scheduler with Cron-like capabilities" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -148,7 +152,7 @@ files = [ pytz = "*" setuptools = ">=0.7" six = ">=1.4.0" -tzlocal = ">=2.0,<3.dev0 || >=4.dev0" +tzlocal = ">=2.0,<3.0.0 || >=4.0.0" [package.extras] doc = ["sphinx", "sphinx-rtd-theme"] @@ -166,6 +170,7 @@ zookeeper = ["kazoo"] name = "async-timeout" version = "4.0.2" description = "Timeout context manager for asyncio programs" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -173,10 +178,26 @@ files = [ {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, ] +[[package]] +name = "asyncio-dgram" +version = "2.1.2" +description = "Higher level Datagram support for Asyncio" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ + {file = "asyncio-dgram-2.1.2.tar.gz", hash = "sha256:bc28a90bc0523009fb0da16ca983c1400ff403a315754d86e037910563697f91"}, + {file = "asyncio_dgram-2.1.2-py3-none-any.whl", hash = "sha256:9ef55fc760f93c8212709329a1e28a1cf1c1f0fc8222f1be0227c2b7606a10a2"}, +] + +[package.extras] +test = ["black (>=20.8b1)", "flake8 (>=3.8.3)", "mypy (>=0.812)", "mypy-extensions (>=0.4.3)", "pytest (>=5.4.3)", "pytest-asyncio (>=0.14.0)", "typed-ast (>=1.4.3)", "typing-extensions (>=3.10.0.0)"] + [[package]] name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -195,6 +216,7 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "beautifulsoup4" version = "4.12.2" description = "Screen-scraping library" +category = "main" optional = false python-versions = ">=3.6.0" files = [ @@ -213,6 +235,7 @@ lxml = ["lxml"] name = "black" version = "23.3.0" description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -261,6 +284,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -272,6 +296,7 @@ files = [ name = "charset-normalizer" version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -356,6 +381,7 @@ files = [ name = "click" version = "8.1.3" description = "Composable command line interface toolkit" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -370,6 +396,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -381,6 +408,7 @@ files = [ name = "discord-py" version = "2.3.1" description = "A Python wrapper for the Discord API" +category = "main" optional = false python-versions = ">=3.8.0" files = [ @@ -397,10 +425,31 @@ speed = ["Brotli", "aiodns (>=1.1)", "cchardet (==2.1.7)", "orjson (>=3.5.4)"] test = ["coverage[toml]", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mock", "typing-extensions (>=4.3,<5)"] voice = ["PyNaCl (>=1.3.0,<1.6)"] +[[package]] +name = "dnspython" +version = "2.4.2" +description = "DNS toolkit" +category = "main" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"}, + {file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"}, +] + +[package.extras] +dnssec = ["cryptography (>=2.6,<42.0)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"] +doq = ["aioquic (>=0.9.20)"] +idna = ["idna (>=2.1,<4.0)"] +trio = ["trio (>=0.14,<0.23)"] +wmi = ["wmi (>=1.5.1,<2.0.0)"] + [[package]] name = "exceptiongroup" version = "1.1.1" description = "Backport of PEP 654 (exception groups)" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -415,6 +464,7 @@ test = ["pytest (>=6)"] name = "frozenlist" version = "1.3.3" description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -498,6 +548,7 @@ files = [ name = "greenlet" version = "2.0.2" description = "Lightweight in-process concurrent programming" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ @@ -506,6 +557,7 @@ files = [ {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d967650d3f56af314b72df7089d96cda1083a7fc2da05b375d2bc48c82ab3f3c"}, {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, @@ -514,6 +566,7 @@ files = [ {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d4606a527e30548153be1a9f155f4e283d109ffba663a15856089fb55f933e47"}, {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, @@ -543,6 +596,7 @@ files = [ {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1087300cf9700bbf455b1b97e24db18f2f77b55302a68272c56209d5587c12d1"}, {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, @@ -551,6 +605,7 @@ files = [ {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8512a0c38cfd4e66a858ddd1b17705587900dd760c6003998e9472b77b56d417"}, {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, @@ -571,6 +626,7 @@ test = ["objgraph", "psutil"] name = "humanize" version = "4.7.0" description = "Python humanize utilities" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -585,6 +641,7 @@ tests = ["freezegun", "pytest", "pytest-cov"] name = "icalendar" version = "5.0.7" description = "iCalendar parser/generator" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -600,6 +657,7 @@ pytz = "*" name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -611,6 +669,7 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -618,10 +677,27 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "mcstatus" +version = "11.1.0" +description = "A library to query Minecraft Servers for their status and capabilities." +category = "main" +optional = false +python-versions = ">=3.8.1,<4" +files = [ + {file = "mcstatus-11.1.0-py3-none-any.whl", hash = "sha256:15b81f777ac1413f5a450d221eac913dc1581169b633bd7d2417c96eb49ee965"}, + {file = "mcstatus-11.1.0.tar.gz", hash = "sha256:d14bf46db8ae5b98a68aebb7fce91a169282e9ac09eb7f6a10a20d9e7d18ca80"}, +] + +[package.dependencies] +asyncio-dgram = ">=2.1.2,<3.0.0" +dnspython = ">=2.4.2,<3.0.0" + [[package]] name = "multidict" version = "6.0.4" description = "multidict implementation" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -705,6 +781,7 @@ files = [ name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -716,6 +793,7 @@ files = [ name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" +category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -730,6 +808,7 @@ setuptools = "*" name = "packaging" version = "23.1" description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -741,6 +820,7 @@ files = [ name = "pathspec" version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -752,6 +832,7 @@ files = [ name = "platformdirs" version = "3.8.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -767,6 +848,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest- name = "pluggy" version = "1.2.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -782,6 +864,7 @@ testing = ["pytest", "pytest-benchmark"] name = "psycopg2-binary" version = "2.9.6" description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -853,6 +936,7 @@ files = [ name = "pyright" version = "1.1.334" description = "Command line wrapper for pyright" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -871,6 +955,7 @@ dev = ["twine (>=3.4.1)"] name = "pytest" version = "7.4.0" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -893,6 +978,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-datafiles" version = "3.0.0" description = "py.test plugin to create a 'tmp_path' containing predefined files/directories." +category = "dev" optional = false python-versions = "*" files = [ @@ -907,6 +993,7 @@ pytest = ">=3.6" name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -921,6 +1008,7 @@ six = ">=1.5" name = "python-dotenv" version = "1.0.0" description = "Read key-value pairs from a .env file and set them as environment variables" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -935,6 +1023,7 @@ cli = ["click (>=5.0)"] name = "pytz" version = "2023.3" description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" files = [ @@ -946,6 +1035,7 @@ files = [ name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -995,6 +1085,7 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1016,6 +1107,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "setuptools" version = "68.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1032,6 +1124,7 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1043,6 +1136,7 @@ files = [ name = "soupsieve" version = "2.4.1" description = "A modern CSS selector implementation for Beautiful Soup." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1054,6 +1148,7 @@ files = [ name = "sqlalchemy" version = "2.0.17" description = "Database Abstraction Library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1101,7 +1196,7 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} +greenlet = {version = "!=0.4.17", 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\""} psycopg2-binary = {version = "*", optional = true, markers = "extra == \"postgresql_psycopg2binary\""} typing-extensions = ">=4.2.0" @@ -1133,6 +1228,7 @@ sqlcipher = ["sqlcipher3-binary"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1144,6 +1240,7 @@ files = [ name = "types-beautifulsoup4" version = "4.12.0.5" description = "Typing stubs for beautifulsoup4" +category = "dev" optional = false python-versions = "*" files = [ @@ -1158,6 +1255,7 @@ types-html5lib = "*" name = "types-html5lib" version = "1.1.11.14" description = "Typing stubs for html5lib" +category = "dev" optional = false python-versions = "*" files = [ @@ -1169,6 +1267,7 @@ files = [ name = "types-python-dateutil" version = "2.8.19.13" description = "Typing stubs for python-dateutil" +category = "dev" optional = false python-versions = "*" files = [ @@ -1180,6 +1279,7 @@ files = [ name = "types-pytz" version = "2023.3.0.0" description = "Typing stubs for pytz" +category = "dev" optional = false python-versions = "*" files = [ @@ -1191,6 +1291,7 @@ files = [ name = "types-requests" version = "2.31.0.1" description = "Typing stubs for requests" +category = "dev" optional = false python-versions = "*" files = [ @@ -1205,6 +1306,7 @@ types-urllib3 = "*" name = "types-urllib3" version = "1.26.25.13" description = "Typing stubs for urllib3" +category = "dev" optional = false python-versions = "*" files = [ @@ -1216,6 +1318,7 @@ files = [ name = "typing-extensions" version = "4.7.0" description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1227,6 +1330,7 @@ files = [ name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" +category = "main" optional = false python-versions = ">=2" files = [ @@ -1238,6 +1342,7 @@ files = [ name = "tzlocal" version = "5.0.1" description = "tzinfo object for the local timezone" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1255,6 +1360,7 @@ devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pyte name = "urllib3" version = "2.0.3" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1272,6 +1378,7 @@ zstd = ["zstandard (>=0.18.0)"] name = "yarl" version = "1.9.2" description = "Yet another URL library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1358,4 +1465,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "c83c2c52dd6a94fb55fb0e23afc7d8e3b9d0b761e7b3d77add8a2ad70bda4379" +content-hash = "8332e7448e7a8f79b27e4e3f2597e5cfb8b312368ce0bf95c5cb13c32dc90da9" diff --git a/pyproject.toml b/pyproject.toml index 2989fa34..2f225a04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ humanize = "^4.3" aiohttp = "^3.8" aio-mc-rcon = "^3.2.0" PyYAML = "^6.0" +mcstatus = "^11.1.0" [tool.poetry.scripts] botdev = "dev.cli:main" diff --git a/uqcsbot/minecraft.py b/uqcsbot/minecraft.py index da5d1212..534ea8b3 100644 --- a/uqcsbot/minecraft.py +++ b/uqcsbot/minecraft.py @@ -4,6 +4,7 @@ import discord from aiomcrcon import Client, IncorrectPasswordError, RCONConnectionError # type: ignore +from mcstatus import JavaServer from discord import Member, app_commands, Colour from discord.ext import commands @@ -16,6 +17,9 @@ RCON_PORT = os.environ.get("MC_RCON_PORT") RCON_PASSWORD = os.environ.get("MC_RCON_PASSWORD") +MC_PUBLIC_IP = os.environ.get("MC_PUBLIC_IP") +MC_PUBLIC_PORT = os.environ.get("MC_PUBLIC_PORT") + class Minecraft(commands.Cog): def __init__(self, bot: UQCSBot): @@ -48,6 +52,27 @@ async def send_rcon_command(self, command: str): return response + @app_commands.command() + async def mcplayers(self, interaction: discord.Interaction): + """Returns the number and list of people currently playing on the Minecraft server.""" + server = JavaServer.lookup( + f"{MC_PUBLIC_IP}:{MC_PUBLIC_PORT}" + ) # Does this need to be hard coded?? Is RCON addr/IP the same? + status = server.status() # type: ignore + + # Check if there are players online + # Not none so pyright shuts up + if status.players.online > 0 and status.players.sample is not None: + # Extract player names + player_names = [player.name for player in status.players.sample] + # Format the list of players + players_list = "\n".join(player_names) + response_message = f"The server has {status.players.online} player(s) online:\n```\n{players_list}\n```" + else: + response_message = f"The server has {status.players.online} players online." + + await interaction.response.send_message(response_message) + @app_commands.command() @app_commands.describe(username="Minecraft username to whitelist.") @yelling_exemptor(input_args=["username"]) From ca15f7480d60ef026921ed2949b7f4ee1d6cd24d Mon Sep 17 00:00:00 2001 From: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:19:42 +1000 Subject: [PATCH 40/48] remove yelling exemptor from mcwhitelist (#191) this causes a timeout (?) when whitelisting, sometimes. idk why. It's pointless to have the exemptor on this command anyway, because capitalising a minecraft username will probably cause it to stop working. --- uqcsbot/minecraft.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/uqcsbot/minecraft.py b/uqcsbot/minecraft.py index 534ea8b3..5c863dcc 100644 --- a/uqcsbot/minecraft.py +++ b/uqcsbot/minecraft.py @@ -11,7 +11,6 @@ from uqcsbot.bot import UQCSBot from uqcsbot.models import MCWhitelist from uqcsbot.utils.err_log_utils import FatalErrorWithLog -from uqcsbot.yelling import yelling_exemptor RCON_ADDRESS = os.environ.get("MC_RCON_ADDRESS") RCON_PORT = os.environ.get("MC_RCON_PORT") @@ -75,7 +74,6 @@ async def mcplayers(self, interaction: discord.Interaction): @app_commands.command() @app_commands.describe(username="Minecraft username to whitelist.") - @yelling_exemptor(input_args=["username"]) async def mcwhitelist(self, interaction: discord.Interaction, username: str): """Adds a username to the whitelist for the UQCS server.""" db_session = self.bot.create_db_session() From 411757e011d55ab082ea2ad8a641d9cfee11e8a6 Mon Sep 17 00:00:00 2001 From: bradleysigma <42644678+bradleysigma@users.noreply.github.com> Date: Tue, 16 Jan 2024 19:04:27 +1000 Subject: [PATCH 41/48] Create ROT13 Command (#170) * Update text.py * Update text.py --------- Co-authored-by: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> --- uqcsbot/text.py | 69 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/uqcsbot/text.py b/uqcsbot/text.py index c62a82cc..49b2deb8 100644 --- a/uqcsbot/text.py +++ b/uqcsbot/text.py @@ -33,17 +33,30 @@ async def encoding_autocomplete( class Text(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot - self.zalgo_menu = app_commands.ContextMenu( - name="Zalgo", - callback=self.zalgo_context, - ) - self.bot.tree.add_command(self.zalgo_menu) - self.mock_menu = app_commands.ContextMenu( - name="Mock", - callback=self.mock_context, + ## self.zalgo_menu = app_commands.ContextMenu( + ## name="Zalgo", + ## callback=self.zalgo_context, + ## ) + ## self.bot.tree.add_command(self.zalgo_menu) + ## + ## self.mock_menu = app_commands.ContextMenu( + ## name="Mock", + ## callback=self.mock_context, + ## ) + ## self.bot.tree.add_command(self.mock_menu) + ## + ## self.rot_13_menu = app_commands.ContextMenu( + ## name="ROT13", + ## callback=self.rot_13_context, + ## ) + ## self.bot.tree.add_command(self.rot_13_menu) + + self.rot_13_secret_menu = app_commands.ContextMenu( + name="ROT13 (Secret)", + callback=self.rot_13_secret_context, ) - self.bot.tree.add_command(self.mock_menu) + self.bot.tree.add_command(self.rot_13_secret_menu) # casualty of the starboard's blacklist/whitelist commands, kept for posterity # self.scare_menu = app_commands.ContextMenu( @@ -273,6 +286,44 @@ async def zalgo_command(self, interaction: discord.Interaction, text: str): await interaction.response.send_message(self.zalgo_common(text)) + def rot_13_cipher(self, text: str) -> str: + result = "" + for c in text: + if "a" <= c <= "m" or "A" <= c <= "M": + result += chr(ord(c) + 13) + elif "n" <= c <= "z" or "N" <= c <= "Z": + result += chr(ord(c) - 13) + else: + result += c + return result + + @app_commands.command(name="rot_13") + @app_commands.describe(text="Input text") + @yelling_exemptor() + async def rot_13_command(self, interaction: discord.Interaction, text: str): + """ + Encodes the given text with the cunning ROT13 Cipher + """ + await interaction.response.send_message(self.rot_13_cipher(text)) + + ## async def rot_13_context( + ## self, interaction: discord.Interaction, message: discord.Message + ## ): + ## """ + ## Encodes this message with the cunning ROT13 Cipher + ## """ + ## await interaction.response.send_message(self.rot_13_cipher(message.content)) + + async def rot_13_secret_context( + self, interaction: discord.Interaction, message: discord.Message + ): + """ + Encodes this message with the cunning ROT13 Cipher, and shows it secretly to the caller + """ + await interaction.response.send_message( + self.rot_13_cipher(message.content), ephemeral=True + ) + async def setup(bot: commands.Bot): await bot.add_cog(Text(bot)) From ce8fd887b36a6acae3b87f152fd8ca3bdf3ab37a Mon Sep 17 00:00:00 2001 From: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> Date: Wed, 24 Jan 2024 20:31:54 +1000 Subject: [PATCH 42/48] security updates for update aiohttp and urllib3 (#192) --- poetry.lock | 246 +++++++++++++++++++--------------------------------- 1 file changed, 87 insertions(+), 159 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6152c59b..9266f4fc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "aio-mc-rcon" version = "3.2.0" description = "An async library for utilizing remote console on Minecraft Java Edition servers" -category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -14,118 +13,104 @@ files = [ [[package]] name = "aiohttp" -version = "3.8.4" +version = "3.9.1" description = "Async http client/server framework (asyncio)" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5ce45967538fb747370308d3145aa68a074bdecb4f3a300869590f725ced69c1"}, - {file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b744c33b6f14ca26b7544e8d8aadff6b765a80ad6164fb1a430bbadd593dfb1a"}, - {file = "aiohttp-3.8.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a45865451439eb320784918617ba54b7a377e3501fb70402ab84d38c2cd891b"}, - {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86d42d7cba1cec432d47ab13b6637bee393a10f664c425ea7b305d1301ca1a3"}, - {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee3c36df21b5714d49fc4580247947aa64bcbe2939d1b77b4c8dcb8f6c9faecc"}, - {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:176a64b24c0935869d5bbc4c96e82f89f643bcdf08ec947701b9dbb3c956b7dd"}, - {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c844fd628851c0bc309f3c801b3a3d58ce430b2ce5b359cd918a5a76d0b20cb5"}, - {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5393fb786a9e23e4799fec788e7e735de18052f83682ce2dfcabaf1c00c2c08e"}, - {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e4b09863aae0dc965c3ef36500d891a3ff495a2ea9ae9171e4519963c12ceefd"}, - {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:adfbc22e87365a6e564c804c58fc44ff7727deea782d175c33602737b7feadb6"}, - {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:147ae376f14b55f4f3c2b118b95be50a369b89b38a971e80a17c3fd623f280c9"}, - {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:eafb3e874816ebe2a92f5e155f17260034c8c341dad1df25672fb710627c6949"}, - {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6cc15d58053c76eacac5fa9152d7d84b8d67b3fde92709195cb984cfb3475ea"}, - {file = "aiohttp-3.8.4-cp310-cp310-win32.whl", hash = "sha256:59f029a5f6e2d679296db7bee982bb3d20c088e52a2977e3175faf31d6fb75d1"}, - {file = "aiohttp-3.8.4-cp310-cp310-win_amd64.whl", hash = "sha256:fe7ba4a51f33ab275515f66b0a236bcde4fb5561498fe8f898d4e549b2e4509f"}, - {file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d8ef1a630519a26d6760bc695842579cb09e373c5f227a21b67dc3eb16cfea4"}, - {file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b3f2e06a512e94722886c0827bee9807c86a9f698fac6b3aee841fab49bbfb4"}, - {file = "aiohttp-3.8.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a80464982d41b1fbfe3154e440ba4904b71c1a53e9cd584098cd41efdb188ef"}, - {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b631e26df63e52f7cce0cce6507b7a7f1bc9b0c501fcde69742130b32e8782f"}, - {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f43255086fe25e36fd5ed8f2ee47477408a73ef00e804cb2b5cba4bf2ac7f5e"}, - {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d347a172f866cd1d93126d9b239fcbe682acb39b48ee0873c73c933dd23bd0f"}, - {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3fec6a4cb5551721cdd70473eb009d90935b4063acc5f40905d40ecfea23e05"}, - {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80a37fe8f7c1e6ce8f2d9c411676e4bc633a8462844e38f46156d07a7d401654"}, - {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d1e6a862b76f34395a985b3cd39a0d949ca80a70b6ebdea37d3ab39ceea6698a"}, - {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cd468460eefef601ece4428d3cf4562459157c0f6523db89365202c31b6daebb"}, - {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:618c901dd3aad4ace71dfa0f5e82e88b46ef57e3239fc7027773cb6d4ed53531"}, - {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:652b1bff4f15f6287550b4670546a2947f2a4575b6c6dff7760eafb22eacbf0b"}, - {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80575ba9377c5171407a06d0196b2310b679dc752d02a1fcaa2bc20b235dbf24"}, - {file = "aiohttp-3.8.4-cp311-cp311-win32.whl", hash = "sha256:bbcf1a76cf6f6dacf2c7f4d2ebd411438c275faa1dc0c68e46eb84eebd05dd7d"}, - {file = "aiohttp-3.8.4-cp311-cp311-win_amd64.whl", hash = "sha256:6e74dd54f7239fcffe07913ff8b964e28b712f09846e20de78676ce2a3dc0bfc"}, - {file = "aiohttp-3.8.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:880e15bb6dad90549b43f796b391cfffd7af373f4646784795e20d92606b7a51"}, - {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb96fa6b56bb536c42d6a4a87dfca570ff8e52de2d63cabebfd6fb67049c34b6"}, - {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a6cadebe132e90cefa77e45f2d2f1a4b2ce5c6b1bfc1656c1ddafcfe4ba8131"}, - {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f352b62b45dff37b55ddd7b9c0c8672c4dd2eb9c0f9c11d395075a84e2c40f75"}, - {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ab43061a0c81198d88f39aaf90dae9a7744620978f7ef3e3708339b8ed2ef01"}, - {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9cb1565a7ad52e096a6988e2ee0397f72fe056dadf75d17fa6b5aebaea05622"}, - {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:1b3ea7edd2d24538959c1c1abf97c744d879d4e541d38305f9bd7d9b10c9ec41"}, - {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:7c7837fe8037e96b6dd5cfcf47263c1620a9d332a87ec06a6ca4564e56bd0f36"}, - {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3b90467ebc3d9fa5b0f9b6489dfb2c304a1db7b9946fa92aa76a831b9d587e99"}, - {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:cab9401de3ea52b4b4c6971db5fb5c999bd4260898af972bf23de1c6b5dd9d71"}, - {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d1f9282c5f2b5e241034a009779e7b2a1aa045f667ff521e7948ea9b56e0c5ff"}, - {file = "aiohttp-3.8.4-cp36-cp36m-win32.whl", hash = "sha256:5e14f25765a578a0a634d5f0cd1e2c3f53964553a00347998dfdf96b8137f777"}, - {file = "aiohttp-3.8.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4c745b109057e7e5f1848c689ee4fb3a016c8d4d92da52b312f8a509f83aa05e"}, - {file = "aiohttp-3.8.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:aede4df4eeb926c8fa70de46c340a1bc2c6079e1c40ccf7b0eae1313ffd33519"}, - {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddaae3f3d32fc2cb4c53fab020b69a05c8ab1f02e0e59665c6f7a0d3a5be54f"}, - {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4eb3b82ca349cf6fadcdc7abcc8b3a50ab74a62e9113ab7a8ebc268aad35bb9"}, - {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bcb89336efa095ea21b30f9e686763f2be4478f1b0a616969551982c4ee4c3b"}, - {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c08e8ed6fa3d477e501ec9db169bfac8140e830aa372d77e4a43084d8dd91ab"}, - {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c6cd05ea06daca6ad6a4ca3ba7fe7dc5b5de063ff4daec6170ec0f9979f6c332"}, - {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7a00a9ed8d6e725b55ef98b1b35c88013245f35f68b1b12c5cd4100dddac333"}, - {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:de04b491d0e5007ee1b63a309956eaed959a49f5bb4e84b26c8f5d49de140fa9"}, - {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:40653609b3bf50611356e6b6554e3a331f6879fa7116f3959b20e3528783e699"}, - {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dbf3a08a06b3f433013c143ebd72c15cac33d2914b8ea4bea7ac2c23578815d6"}, - {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854f422ac44af92bfe172d8e73229c270dc09b96535e8a548f99c84f82dde241"}, - {file = "aiohttp-3.8.4-cp37-cp37m-win32.whl", hash = "sha256:aeb29c84bb53a84b1a81c6c09d24cf33bb8432cc5c39979021cc0f98c1292a1a"}, - {file = "aiohttp-3.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:db3fc6120bce9f446d13b1b834ea5b15341ca9ff3f335e4a951a6ead31105480"}, - {file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fabb87dd8850ef0f7fe2b366d44b77d7e6fa2ea87861ab3844da99291e81e60f"}, - {file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91f6d540163f90bbaef9387e65f18f73ffd7c79f5225ac3d3f61df7b0d01ad15"}, - {file = "aiohttp-3.8.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d265f09a75a79a788237d7f9054f929ced2e69eb0bb79de3798c468d8a90f945"}, - {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d89efa095ca7d442a6d0cbc755f9e08190ba40069b235c9886a8763b03785da"}, - {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dac314662f4e2aa5009977b652d9b8db7121b46c38f2073bfeed9f4049732cd"}, - {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe11310ae1e4cd560035598c3f29d86cef39a83d244c7466f95c27ae04850f10"}, - {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ddb2a2026c3f6a68c3998a6c47ab6795e4127315d2e35a09997da21865757f8"}, - {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e75b89ac3bd27d2d043b234aa7b734c38ba1b0e43f07787130a0ecac1e12228a"}, - {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6e601588f2b502c93c30cd5a45bfc665faaf37bbe835b7cfd461753068232074"}, - {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a5d794d1ae64e7753e405ba58e08fcfa73e3fad93ef9b7e31112ef3c9a0efb52"}, - {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a1f4689c9a1462f3df0a1f7e797791cd6b124ddbee2b570d34e7f38ade0e2c71"}, - {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3032dcb1c35bc330134a5b8a5d4f68c1a87252dfc6e1262c65a7e30e62298275"}, - {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8189c56eb0ddbb95bfadb8f60ea1b22fcfa659396ea36f6adcc521213cd7b44d"}, - {file = "aiohttp-3.8.4-cp38-cp38-win32.whl", hash = "sha256:33587f26dcee66efb2fff3c177547bd0449ab7edf1b73a7f5dea1e38609a0c54"}, - {file = "aiohttp-3.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:e595432ac259af2d4630008bf638873d69346372d38255774c0e286951e8b79f"}, - {file = "aiohttp-3.8.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5a7bdf9e57126dc345b683c3632e8ba317c31d2a41acd5800c10640387d193ed"}, - {file = "aiohttp-3.8.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:22f6eab15b6db242499a16de87939a342f5a950ad0abaf1532038e2ce7d31567"}, - {file = "aiohttp-3.8.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7235604476a76ef249bd64cb8274ed24ccf6995c4a8b51a237005ee7a57e8643"}, - {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea9eb976ffdd79d0e893869cfe179a8f60f152d42cb64622fca418cd9b18dc2a"}, - {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92c0cea74a2a81c4c76b62ea1cac163ecb20fb3ba3a75c909b9fa71b4ad493cf"}, - {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:493f5bc2f8307286b7799c6d899d388bbaa7dfa6c4caf4f97ef7521b9cb13719"}, - {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a63f03189a6fa7c900226e3ef5ba4d3bd047e18f445e69adbd65af433add5a2"}, - {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10c8cefcff98fd9168cdd86c4da8b84baaa90bf2da2269c6161984e6737bf23e"}, - {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bca5f24726e2919de94f047739d0a4fc01372801a3672708260546aa2601bf57"}, - {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:03baa76b730e4e15a45f81dfe29a8d910314143414e528737f8589ec60cf7391"}, - {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8c29c77cc57e40f84acef9bfb904373a4e89a4e8b74e71aa8075c021ec9078c2"}, - {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:03543dcf98a6619254b409be2d22b51f21ec66272be4ebda7b04e6412e4b2e14"}, - {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17b79c2963db82086229012cff93ea55196ed31f6493bb1ccd2c62f1724324e4"}, - {file = "aiohttp-3.8.4-cp39-cp39-win32.whl", hash = "sha256:34ce9f93a4a68d1272d26030655dd1b58ff727b3ed2a33d80ec433561b03d67a"}, - {file = "aiohttp-3.8.4-cp39-cp39-win_amd64.whl", hash = "sha256:41a86a69bb63bb2fc3dc9ad5ea9f10f1c9c8e282b471931be0268ddd09430b04"}, - {file = "aiohttp-3.8.4.tar.gz", hash = "sha256:bf2e1a9162c1e441bf805a1fd166e249d574ca04e03b34f97e2928769e91ab5c"}, + {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590"}, + {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0"}, + {file = "aiohttp-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501"}, + {file = "aiohttp-3.9.1-cp310-cp310-win32.whl", hash = "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489"}, + {file = "aiohttp-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23"}, + {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d"}, + {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e"}, + {file = "aiohttp-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a"}, + {file = "aiohttp-3.9.1-cp311-cp311-win32.whl", hash = "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544"}, + {file = "aiohttp-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587"}, + {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065"}, + {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821"}, + {file = "aiohttp-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f"}, + {file = "aiohttp-3.9.1-cp312-cp312-win32.whl", hash = "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed"}, + {file = "aiohttp-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213"}, + {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70"}, + {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672"}, + {file = "aiohttp-3.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8"}, + {file = "aiohttp-3.9.1-cp38-cp38-win32.whl", hash = "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4"}, + {file = "aiohttp-3.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7"}, + {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766"}, + {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0"}, + {file = "aiohttp-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c"}, + {file = "aiohttp-3.9.1-cp39-cp39-win32.whl", hash = "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7"}, + {file = "aiohttp-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf"}, + {file = "aiohttp-3.9.1.tar.gz", hash = "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d"}, ] [package.dependencies] aiosignal = ">=1.1.2" -async-timeout = ">=4.0.0a3,<5.0" +async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" -charset-normalizer = ">=2.0,<4.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns", "cchardet"] +speedups = ["Brotli", "aiodns", "brotlicffi"] [[package]] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -140,7 +125,6 @@ frozenlist = ">=1.1.0" name = "apscheduler" version = "3.10.1" description = "In-process task scheduler with Cron-like capabilities" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -152,7 +136,7 @@ files = [ pytz = "*" setuptools = ">=0.7" six = ">=1.4.0" -tzlocal = ">=2.0,<3.0.0 || >=4.0.0" +tzlocal = ">=2.0,<3.dev0 || >=4.dev0" [package.extras] doc = ["sphinx", "sphinx-rtd-theme"] @@ -170,7 +154,6 @@ zookeeper = ["kazoo"] name = "async-timeout" version = "4.0.2" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -182,7 +165,6 @@ files = [ name = "asyncio-dgram" version = "2.1.2" description = "Higher level Datagram support for Asyncio" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -197,7 +179,6 @@ test = ["black (>=20.8b1)", "flake8 (>=3.8.3)", "mypy (>=0.812)", "mypy-extensio name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -216,7 +197,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "beautifulsoup4" version = "4.12.2" description = "Screen-scraping library" -category = "main" optional = false python-versions = ">=3.6.0" files = [ @@ -235,7 +215,6 @@ lxml = ["lxml"] name = "black" version = "23.3.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -284,7 +263,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -296,7 +274,6 @@ files = [ name = "charset-normalizer" version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -381,7 +358,6 @@ files = [ name = "click" version = "8.1.3" description = "Composable command line interface toolkit" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -396,7 +372,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -408,7 +383,6 @@ files = [ name = "discord-py" version = "2.3.1" description = "A Python wrapper for the Discord API" -category = "main" optional = false python-versions = ">=3.8.0" files = [ @@ -429,7 +403,6 @@ voice = ["PyNaCl (>=1.3.0,<1.6)"] name = "dnspython" version = "2.4.2" description = "DNS toolkit" -category = "main" optional = false python-versions = ">=3.8,<4.0" files = [ @@ -449,7 +422,6 @@ wmi = ["wmi (>=1.5.1,<2.0.0)"] name = "exceptiongroup" version = "1.1.1" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -464,7 +436,6 @@ test = ["pytest (>=6)"] name = "frozenlist" version = "1.3.3" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -548,7 +519,6 @@ files = [ name = "greenlet" version = "2.0.2" description = "Lightweight in-process concurrent programming" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ @@ -557,7 +527,6 @@ files = [ {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, - {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d967650d3f56af314b72df7089d96cda1083a7fc2da05b375d2bc48c82ab3f3c"}, {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, @@ -566,7 +535,6 @@ files = [ {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, - {file = "greenlet-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d4606a527e30548153be1a9f155f4e283d109ffba663a15856089fb55f933e47"}, {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, @@ -596,7 +564,6 @@ files = [ {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, - {file = "greenlet-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1087300cf9700bbf455b1b97e24db18f2f77b55302a68272c56209d5587c12d1"}, {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, @@ -605,7 +572,6 @@ files = [ {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, - {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8512a0c38cfd4e66a858ddd1b17705587900dd760c6003998e9472b77b56d417"}, {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, @@ -626,7 +592,6 @@ test = ["objgraph", "psutil"] name = "humanize" version = "4.7.0" description = "Python humanize utilities" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -641,7 +606,6 @@ tests = ["freezegun", "pytest", "pytest-cov"] name = "icalendar" version = "5.0.7" description = "iCalendar parser/generator" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -657,7 +621,6 @@ pytz = "*" name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -669,7 +632,6 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -681,7 +643,6 @@ files = [ name = "mcstatus" version = "11.1.0" description = "A library to query Minecraft Servers for their status and capabilities." -category = "main" optional = false python-versions = ">=3.8.1,<4" files = [ @@ -697,7 +658,6 @@ dnspython = ">=2.4.2,<3.0.0" name = "multidict" version = "6.0.4" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -781,7 +741,6 @@ files = [ name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -793,7 +752,6 @@ files = [ name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -808,7 +766,6 @@ setuptools = "*" name = "packaging" version = "23.1" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -820,7 +777,6 @@ files = [ name = "pathspec" version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -832,7 +788,6 @@ files = [ name = "platformdirs" version = "3.8.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -848,7 +803,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest- name = "pluggy" version = "1.2.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -864,7 +818,6 @@ testing = ["pytest", "pytest-benchmark"] name = "psycopg2-binary" version = "2.9.6" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -936,7 +889,6 @@ files = [ name = "pyright" version = "1.1.334" description = "Command line wrapper for pyright" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -955,7 +907,6 @@ dev = ["twine (>=3.4.1)"] name = "pytest" version = "7.4.0" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -978,7 +929,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-datafiles" version = "3.0.0" description = "py.test plugin to create a 'tmp_path' containing predefined files/directories." -category = "dev" optional = false python-versions = "*" files = [ @@ -993,7 +943,6 @@ pytest = ">=3.6" name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1008,7 +957,6 @@ six = ">=1.5" name = "python-dotenv" version = "1.0.0" description = "Read key-value pairs from a .env file and set them as environment variables" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1023,7 +971,6 @@ cli = ["click (>=5.0)"] name = "pytz" version = "2023.3" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -1035,7 +982,6 @@ files = [ name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1085,7 +1031,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1107,7 +1052,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "setuptools" version = "68.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1124,7 +1068,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1136,7 +1079,6 @@ files = [ name = "soupsieve" version = "2.4.1" description = "A modern CSS selector implementation for Beautiful Soup." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1148,7 +1090,6 @@ files = [ name = "sqlalchemy" version = "2.0.17" description = "Database Abstraction Library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1196,7 +1137,7 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", 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\""} +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} psycopg2-binary = {version = "*", optional = true, markers = "extra == \"postgresql_psycopg2binary\""} typing-extensions = ">=4.2.0" @@ -1228,7 +1169,6 @@ sqlcipher = ["sqlcipher3-binary"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1240,7 +1180,6 @@ files = [ name = "types-beautifulsoup4" version = "4.12.0.5" description = "Typing stubs for beautifulsoup4" -category = "dev" optional = false python-versions = "*" files = [ @@ -1255,7 +1194,6 @@ types-html5lib = "*" name = "types-html5lib" version = "1.1.11.14" description = "Typing stubs for html5lib" -category = "dev" optional = false python-versions = "*" files = [ @@ -1267,7 +1205,6 @@ files = [ name = "types-python-dateutil" version = "2.8.19.13" description = "Typing stubs for python-dateutil" -category = "dev" optional = false python-versions = "*" files = [ @@ -1279,7 +1216,6 @@ files = [ name = "types-pytz" version = "2023.3.0.0" description = "Typing stubs for pytz" -category = "dev" optional = false python-versions = "*" files = [ @@ -1291,7 +1227,6 @@ files = [ name = "types-requests" version = "2.31.0.1" description = "Typing stubs for requests" -category = "dev" optional = false python-versions = "*" files = [ @@ -1306,7 +1241,6 @@ types-urllib3 = "*" name = "types-urllib3" version = "1.26.25.13" description = "Typing stubs for urllib3" -category = "dev" optional = false python-versions = "*" files = [ @@ -1318,7 +1252,6 @@ files = [ name = "typing-extensions" version = "4.7.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1330,7 +1263,6 @@ files = [ name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ @@ -1342,7 +1274,6 @@ files = [ name = "tzlocal" version = "5.0.1" description = "tzinfo object for the local timezone" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1358,19 +1289,17 @@ devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pyte [[package]] name = "urllib3" -version = "2.0.3" +version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, - {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, + {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, + {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -1378,7 +1307,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "yarl" version = "1.9.2" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ From 0c38624ecad5029028745aba4a258864c121ba2f Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Thu, 7 Mar 2024 10:11:45 +1000 Subject: [PATCH 43/48] Added typing to command_utils.py (#194) * Added typing to command_utils.py * command_utils no longer needed due to interactions * Moved react emojis into holidays.py * Formatting --- pyproject.toml | 1 - uqcsbot/holidays.py | 11 ++++++++++- uqcsbot/utils/command_utils.py | 31 ------------------------------- 3 files changed, 10 insertions(+), 33 deletions(-) delete mode 100644 uqcsbot/utils/command_utils.py diff --git a/pyproject.toml b/pyproject.toml index 2f225a04..b6c2175a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,6 @@ exclude = [ "**/starboard.py", "**/uptime.py", "**/working_on.py", - "**/utils/command_utils.py", "**/utils/snailrace_utils.py", ] diff --git a/uqcsbot/holidays.py b/uqcsbot/holidays.py index e8a2f957..16adb0d2 100644 --- a/uqcsbot/holidays.py +++ b/uqcsbot/holidays.py @@ -11,12 +11,21 @@ from zoneinfo import ZoneInfo from uqcsbot.bot import UQCSBot -from uqcsbot.utils.command_utils import HYPE_REACTS HOLIDAY_URL = "https://www.timeanddate.com/holidays/fun/" HOLIDAY_CSV_PATH = "uqcsbot/static/geek_holidays.csv" HOLIDAY_MESSAGE = "Today is {}!" GENERAL_CHANNEL = "general" +HYPE_REACTS = [ + "blahaj", + "blobhajHeart", + "realheart", + "blobhajInnocent", + "keen", + "bigsippin", + "pog_of_greed", + "blobhajHearts", +] class Holiday: diff --git a/uqcsbot/utils/command_utils.py b/uqcsbot/utils/command_utils.py deleted file mode 100644 index b81bd74e..00000000 --- a/uqcsbot/utils/command_utils.py +++ /dev/null @@ -1,31 +0,0 @@ -from random import choice -from functools import wraps -from typing import Callable -from discord.ext import commands - -LOADING_REACTS = ["⏰", "🕰️", "⏲️", "🕖", "🕔", "🕥"] -HYPE_REACTS = [ - "blahaj", - "blobhajHeart", - "realheart", - "blobhajInnocent", - "keen", - "bigsippin", - "pog_of_greed", - "blobhajHearts", -] - - -def loading_status(command_fn: Callable): - @wraps(command_fn) # Important to preserve name because `command` uses it - async def wrapper(self, ctx: commands.Context, *args): - if ctx.message is None or ctx.bot is None: - return - - react = choice(LOADING_REACTS) - await ctx.message.add_reaction(react) - res = await command_fn(self, ctx, *args) - await ctx.message.remove_reaction(react, ctx.bot.user) - return res - - return wrapper From 547c804b72fdb50ae9080b078660130f5fac712b Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Sat, 9 Mar 2024 12:00:13 +1000 Subject: [PATCH 44/48] Fixed various typing issues (#195) * Fixed various typing issues * Fixed stub issue for error_handler * Added typing for member counter --- pyproject.toml | 4 ---- uqcsbot/error_handler.py | 7 ++++--- uqcsbot/member_counter.py | 26 ++++++++++++++++++++++---- uqcsbot/uptime.py | 2 +- uqcsbot/working_on.py | 4 ++-- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b6c2175a..91963233 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,15 +43,11 @@ build-backend = "poetry.core.masonry.api" strict = ["**"] exclude = [ "**/bot.py", - "**/error_handler.py", "**/events.py", "**/gaming.py", - "**/member_counter.py", "**/remindme.py", "**/snailrace.py", "**/starboard.py", - "**/uptime.py", - "**/working_on.py", "**/utils/snailrace_utils.py", ] diff --git a/uqcsbot/error_handler.py b/uqcsbot/error_handler.py index 98a55bc2..de605876 100644 --- a/uqcsbot/error_handler.py +++ b/uqcsbot/error_handler.py @@ -1,6 +1,7 @@ from discord.ext import commands -from discord.ext.commands.errors import MissingRequiredArgument import logging +from typing import Any +from uqcsbot.bot import UQCSBot """ TODO: this is bundled with advent.py and should be removed. @@ -9,8 +10,8 @@ class ErrorHandler(commands.Cog): @commands.Cog.listener() - async def on_command_error(self, ctx: commands.Context, err): - if isinstance(err, MissingRequiredArgument): + async def on_command_error(self, ctx: commands.Context[UQCSBot], err: Any): + if isinstance(err, commands.errors.MissingRequiredArgument): await ctx.send( "Missing required argument. For further information refer to `!help`" ) diff --git a/uqcsbot/member_counter.py b/uqcsbot/member_counter.py index a6cda54d..2e6939f2 100644 --- a/uqcsbot/member_counter.py +++ b/uqcsbot/member_counter.py @@ -7,13 +7,15 @@ from discord import app_commands from discord.ext import commands +from uqcsbot.bot import UQCSBot + class MemberCounter(commands.Cog): MEMBER_COUNT_PREFIX = "Member Count: " RATE_LIMIT = timedelta(minutes=5) NEW_MEMBER_TIME = timedelta(days=7) - def __init__(self, bot: commands.Bot): + def __init__(self, bot: UQCSBot): self.bot = bot self.last_rename_time = datetime.now() self.waiting_for_rename = False @@ -43,7 +45,13 @@ async def on_ready(self): ) return + if self.bot.user is None: + logging.warning("Could not find the bot's own id.") + return bot_member = self.bot.uqcs_server.get_member(self.bot.user.id) + if bot_member is None: + logging.warning("Could not find the bot's user.") + return permissions = self.member_count_channel.permissions_for(bot_member) if not permissions.manage_channels: logging.warning( @@ -55,17 +63,27 @@ async def on_ready(self): @app_commands.command(name="membercount") async def member_count(self, interaction: discord.Interaction, force: bool = False): """Display the number of members""" + if interaction.guild is None: + logging.warning( + "Could not update member count as could not access members." + ) + return new_members = [ member for member in interaction.guild.members - if member.joined_at + if member.joined_at is not None + and member.joined_at > datetime.now(tz=ZoneInfo("Australia/Brisbane")) - self.NEW_MEMBER_TIME ] await interaction.response.send_message( f"There are currently {interaction.guild.member_count} members in the UQ Computing Society discord server, with {len(new_members)} joining in the last 7 days." ) - if interaction.user.guild_permissions.manage_guild and force: + if ( + isinstance(interaction.user, discord.Member) + and interaction.user.guild_permissions.manage_guild + and force + ): # this is dodgy, but the alternative is to restart the bot # if it gets caught in a loop of waiting for a broken rename self.waiting_for_rename = False @@ -107,5 +125,5 @@ async def _update_member_count_channel_name(self): self.waiting_for_rename = False -async def setup(bot: commands.Bot): +async def setup(bot: UQCSBot): await bot.add_cog(MemberCounter(bot)) diff --git a/uqcsbot/uptime.py b/uqcsbot/uptime.py index ad38d7c5..f1caaa2a 100644 --- a/uqcsbot/uptime.py +++ b/uqcsbot/uptime.py @@ -22,7 +22,7 @@ async def on_ready(self): self.bot.uqcs_server.channels, name=self.CHANNEL_NAME ) - if channel is not None: + if isinstance(channel, discord.TextChannel): if random.randint(1, 100) == 1: await channel.send("Oopsie, I webooted uwu >_<") else: diff --git a/uqcsbot/working_on.py b/uqcsbot/working_on.py index e3af632c..8ebfea12 100644 --- a/uqcsbot/working_on.py +++ b/uqcsbot/working_on.py @@ -19,7 +19,7 @@ def __init__(self, bot: UQCSBot): async def workingon(self): """5pm ping for 2 lucky server members to share what they have been working on.""" members = list(self.bot.get_all_members()) - message = [] + message: list[str] = [] while len(message) < 2: chosen = choice(members) @@ -33,7 +33,7 @@ async def workingon(self): self.bot.uqcs_server.channels, name=GENERAL_CHANNEL ) - if general_channel is not None: + if isinstance(general_channel, discord.TextChannel): await general_channel.send( "\n".join(message), allowed_mentions=discord.AllowedMentions( From b7ebf6d7202a57866fbae3752a7a0e4efc48cfef Mon Sep 17 00:00:00 2001 From: James Dearlove <39483549+JamesDearlove@users.noreply.github.com> Date: Sun, 10 Mar 2024 06:55:07 +1000 Subject: [PATCH 45/48] Update actions to Node 20 & Deps update (#197) * Cause chaos, update actions to Node 20 * Updating deps, update actions with matching dep versions * Fix up some new typing issues * Remember to check style challenge (very hard) --- .github/workflows/build-docker.yml | 6 +- .github/workflows/run-black.yml | 6 +- .github/workflows/run-tests.yml | 6 +- .github/workflows/setup-python/action.yml | 6 +- Dockerfile | 2 +- poetry.lock | 1611 +++++++++++---------- pyproject.toml | 16 +- uqcsbot/dominos_coupons.py | 5 +- uqcsbot/holidays.py | 5 +- 9 files changed, 869 insertions(+), 794 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 5bfd87b1..86ede138 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -18,12 +18,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Login against the Docker registry # https://github.com/docker/login-action - name: Log into registry ${{ env.REGISTRY }} - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -32,7 +32,7 @@ jobs: # Build and push Docker image with Buildx # https://github.com/docker/build-push-action - name: Build and push Docker image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . push: true diff --git a/.github/workflows/run-black.yml b/.github/workflows/run-black.yml index f33b5ca8..df40d1d0 100644 --- a/.github/workflows/run-black.yml +++ b/.github/workflows/run-black.yml @@ -8,7 +8,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run black id: runBlack @@ -16,10 +16,10 @@ jobs: with: options: "--check --verbose" src: "./uqcsbot" - version: "23.3.0" + version: "23.12.1" - name: Convert logs to artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() && (steps.runBlack.outcome == 'failure') with: name: "black-logs" diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index f0bed37d..80d61312 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -7,7 +7,7 @@ on: env: PYTHON_VERSION: '3.10' - POETRY_VERSION: '1.4.2' + POETRY_VERSION: '1.7.1' jobs: tests: @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Local action that tries to cache as much of python & poetry as possible - name: Setup environment @@ -34,7 +34,7 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Local action that tries to cache as much of python & poetry as possible - name: Setup environment diff --git a/.github/workflows/setup-python/action.yml b/.github/workflows/setup-python/action.yml index bc152a41..218d4b0a 100644 --- a/.github/workflows/setup-python/action.yml +++ b/.github/workflows/setup-python/action.yml @@ -16,7 +16,7 @@ runs: # Get python # ------ - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ inputs.python-version }} @@ -25,7 +25,7 @@ runs: # ------ - name: Check for cached poetry binary id: cached-poetry-binary - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.local # poetry depends on OS, python version, and poetry version @@ -50,7 +50,7 @@ runs: # ------ - name: Check for cached dependencies id: cached-poetry-dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: '**/.venv' # poetry dependencies depend on OS, python version, poetry version, and repository lockfile diff --git a/Dockerfile b/Dockerfile index 705f5bcb..967e1409 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ ENV PYTHONUNBUFFERED=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 \ PIP_DEFAULT_TIMEOUT=100 \ POETRY_NO_INTERACTION=1 \ - POETRY_VERSION=1.4.2 \ + POETRY_VERSION=1.7.1 \ POETRY_VIRTUALENVS_IN_PROJECT=1 \ POETRY_CACHE_DIR='/var/cache/pypoetry' diff --git a/poetry.lock b/poetry.lock index 9266f4fc..1561ff2f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,99 +1,99 @@ -# 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.7.1 and should not be changed by hand. [[package]] name = "aio-mc-rcon" -version = "3.2.0" +version = "3.2.2" description = "An async library for utilizing remote console on Minecraft Java Edition servers" optional = false -python-versions = ">=3.7,<4.0" +python-versions = ">=3.8,<4.0" files = [ - {file = "aio-mc-rcon-3.2.0.tar.gz", hash = "sha256:fb2f8e3d7f893c913cbb1e3076a83e163d2f500dcbe37cda26689be2b078f446"}, - {file = "aio_mc_rcon-3.2.0-py3-none-any.whl", hash = "sha256:aed2dbc4be1c38150ecc6167d86dfa63a53c04c090bc6dfb94e0f7776979c420"}, + {file = "aio_mc_rcon-3.2.2-py3-none-any.whl", hash = "sha256:3eab224a323e2499a4bc996593691356ac7eb458aed020fbe826d98a22b2452a"}, + {file = "aio_mc_rcon-3.2.2.tar.gz", hash = "sha256:59439a0cafd2a51ad579bc91271b8dc8ff02417d31dd9cc35e56ba68fe52e1ed"}, ] [[package]] name = "aiohttp" -version = "3.9.1" +version = "3.9.3" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501"}, - {file = "aiohttp-3.9.1-cp310-cp310-win32.whl", hash = "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489"}, - {file = "aiohttp-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a"}, - {file = "aiohttp-3.9.1-cp311-cp311-win32.whl", hash = "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544"}, - {file = "aiohttp-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f"}, - {file = "aiohttp-3.9.1-cp312-cp312-win32.whl", hash = "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed"}, - {file = "aiohttp-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8"}, - {file = "aiohttp-3.9.1-cp38-cp38-win32.whl", hash = "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4"}, - {file = "aiohttp-3.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c"}, - {file = "aiohttp-3.9.1-cp39-cp39-win32.whl", hash = "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7"}, - {file = "aiohttp-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf"}, - {file = "aiohttp-3.9.1.tar.gz", hash = "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, + {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, + {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, + {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, + {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, + {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, + {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, + {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, + {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, + {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, + {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, + {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, ] [package.dependencies] @@ -123,18 +123,17 @@ frozenlist = ">=1.1.0" [[package]] name = "apscheduler" -version = "3.10.1" +version = "3.10.4" description = "In-process task scheduler with Cron-like capabilities" optional = false python-versions = ">=3.6" files = [ - {file = "APScheduler-3.10.1-py3-none-any.whl", hash = "sha256:e813ad5ada7aff36fb08cdda746b520531eaac7757832abc204868ba78e0c8f6"}, - {file = "APScheduler-3.10.1.tar.gz", hash = "sha256:0293937d8f6051a0f493359440c1a1b93e882c57daf0197afeff0e727777b96e"}, + {file = "APScheduler-3.10.4-py3-none-any.whl", hash = "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661"}, + {file = "APScheduler-3.10.4.tar.gz", hash = "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a"}, ] [package.dependencies] pytz = "*" -setuptools = ">=0.7" six = ">=1.4.0" tzlocal = ">=2.0,<3.dev0 || >=4.dev0" @@ -152,13 +151,13 @@ zookeeper = ["kazoo"] [[package]] name = "async-timeout" -version = "4.0.2" +version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, - {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] [[package]] @@ -177,72 +176,73 @@ test = ["black (>=20.8b1)", "flake8 (>=3.8.3)", "mypy (>=0.812)", "mypy-extensio [[package]] name = "attrs" -version = "23.1.0" +version = "23.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]", "pre-commit"] +dev = ["attrs[tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "beautifulsoup4" -version = "4.12.2" +version = "4.12.3" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" files = [ - {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, - {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, ] [package.dependencies] soupsieve = ">1.2" [package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] [[package]] name = "black" -version = "23.3.0" +version = "23.12.1" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, - {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, - {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, - {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, - {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, - {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, - {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, - {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, - {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, - {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, - {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, - {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, - {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, - {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, + {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, + {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, + {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, + {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, + {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, + {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, + {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, + {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, + {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, + {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, + {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, + {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, + {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, + {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, + {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, + {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, + {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, + {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, + {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, + {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, + {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, + {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, ] [package.dependencies] @@ -252,117 +252,133 @@ packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2023.7.22" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] name = "charset-normalizer" -version = "3.1.0" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, - {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] name = "click" -version = "8.1.3" +version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -381,13 +397,13 @@ files = [ [[package]] name = "discord-py" -version = "2.3.1" +version = "2.3.2" description = "A Python wrapper for the Discord API" optional = false python-versions = ">=3.8.0" files = [ - {file = "discord.py-2.3.1-py3-none-any.whl", hash = "sha256:149652f24da299706270bf8c03c2fcf80cf1caf3a480744c61d5b001688b380d"}, - {file = "discord.py-2.3.1.tar.gz", hash = "sha256:8eb4fe66b5d503da6de3a8425e23012711dc2fbcd7a782107a92beac15ee3459"}, + {file = "discord.py-2.3.2-py3-none-any.whl", hash = "sha256:9da4679fc3cb10c64b388284700dc998663e0e57328283bbfcfc2525ec5960a6"}, + {file = "discord.py-2.3.2.tar.gz", hash = "sha256:4560f70f2eddba7e83370ecebd237ac09fbb4980dc66507482b0c0e5b8f76b9c"}, ] [package.dependencies] @@ -401,32 +417,33 @@ voice = ["PyNaCl (>=1.3.0,<1.6)"] [[package]] name = "dnspython" -version = "2.4.2" +version = "2.6.1" description = "DNS toolkit" optional = false -python-versions = ">=3.8,<4.0" +python-versions = ">=3.8" files = [ - {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"}, - {file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"}, + {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, + {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, ] [package.extras] -dnssec = ["cryptography (>=2.6,<42.0)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"] -doq = ["aioquic (>=0.9.20)"] -idna = ["idna (>=2.1,<4.0)"] -trio = ["trio (>=0.14,<0.23)"] -wmi = ["wmi (>=1.5.1,<2.0.0)"] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=0.9.25)"] +idna = ["idna (>=3.6)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] [[package]] name = "exceptiongroup" -version = "1.1.1" +version = "1.2.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, - {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] [package.extras] @@ -434,169 +451,170 @@ test = ["pytest (>=6)"] [[package]] name = "frozenlist" -version = "1.3.3" +version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, - {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, - {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420"}, - {file = "frozenlist-1.3.3-cp310-cp310-win32.whl", hash = "sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642"}, - {file = "frozenlist-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1"}, - {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7"}, - {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678"}, - {file = "frozenlist-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81"}, - {file = "frozenlist-1.3.3-cp311-cp311-win32.whl", hash = "sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8"}, - {file = "frozenlist-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32"}, - {file = "frozenlist-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401"}, - {file = "frozenlist-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a"}, - {file = "frozenlist-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411"}, - {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a"}, - {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5"}, - {file = "frozenlist-1.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3"}, - {file = "frozenlist-1.3.3-cp38-cp38-win32.whl", hash = "sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b"}, - {file = "frozenlist-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef"}, - {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf"}, - {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1"}, - {file = "frozenlist-1.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1"}, - {file = "frozenlist-1.3.3-cp39-cp39-win32.whl", hash = "sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38"}, - {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"}, - {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] [[package]] name = "greenlet" -version = "2.0.2" +version = "3.0.3" description = "Lightweight in-process concurrent programming" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +python-versions = ">=3.7" files = [ - {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, - {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, - {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, - {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, - {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, - {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, - {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, - {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, - {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, - {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, - {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, - {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, - {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, - {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, - {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, - {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, - {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, - {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, - {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, - {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, - {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, - {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, - {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, - {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, - {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, - {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, ] [package.extras] -docs = ["Sphinx", "docutils (<0.18)"] +docs = ["Sphinx", "furo"] test = ["objgraph", "psutil"] [[package]] name = "humanize" -version = "4.7.0" +version = "4.9.0" description = "Python humanize utilities" optional = false python-versions = ">=3.8" files = [ - {file = "humanize-4.7.0-py3-none-any.whl", hash = "sha256:df7c429c2d27372b249d3f26eb53b07b166b661326e0325793e0a988082e3889"}, - {file = "humanize-4.7.0.tar.gz", hash = "sha256:7ca0e43e870981fa684acb5b062deb307218193bca1a01f2b2676479df849b3a"}, + {file = "humanize-4.9.0-py3-none-any.whl", hash = "sha256:ce284a76d5b1377fd8836733b983bfb0b76f1aa1c090de2566fcf008d7f6ab16"}, + {file = "humanize-4.9.0.tar.gz", hash = "sha256:582a265c931c683a7e9b8ed9559089dea7edcf6cc95be39a3cbc2c5d5ac2bcfa"}, ] [package.extras] @@ -604,13 +622,13 @@ tests = ["freezegun", "pytest", "pytest-cov"] [[package]] name = "icalendar" -version = "5.0.7" +version = "5.0.11" description = "iCalendar parser/generator" optional = false python-versions = ">=3.7" files = [ - {file = "icalendar-5.0.7-py3-none-any.whl", hash = "sha256:18ad51f9d1741d33795ddaf5c886c59f6575f287d30e5a145c2d42ef72910c7f"}, - {file = "icalendar-5.0.7.tar.gz", hash = "sha256:e306014a64dc4dcf638da0acb2487ee4ada57b871b03a62ed7b513dfc135655c"}, + {file = "icalendar-5.0.11-py3-none-any.whl", hash = "sha256:81864971ac43a1b7d0a555dc1b667836ce59fc719a7f845a96f2f03205fb83b9"}, + {file = "icalendar-5.0.11.tar.gz", hash = "sha256:7a298bb864526589d0de81f4b736eeb6ff9e539fefb405f7977aa5c1e201ca00"}, ] [package.dependencies] @@ -619,13 +637,13 @@ pytz = "*" [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] [[package]] @@ -641,13 +659,13 @@ files = [ [[package]] name = "mcstatus" -version = "11.1.0" +version = "11.1.1" description = "A library to query Minecraft Servers for their status and capabilities." optional = false python-versions = ">=3.8.1,<4" files = [ - {file = "mcstatus-11.1.0-py3-none-any.whl", hash = "sha256:15b81f777ac1413f5a450d221eac913dc1581169b633bd7d2417c96eb49ee965"}, - {file = "mcstatus-11.1.0.tar.gz", hash = "sha256:d14bf46db8ae5b98a68aebb7fce91a169282e9ac09eb7f6a10a20d9e7d18ca80"}, + {file = "mcstatus-11.1.1-py3-none-any.whl", hash = "sha256:7f5f7f44fa1d17c4e05c0ae94ecc114d9d146ef572e7dd9f2d9da72771ce135c"}, + {file = "mcstatus-11.1.1.tar.gz", hash = "sha256:b2856ee4032faf810b0c6af10a141381e6115caf14a7348c47edc8e3da84f5d4"}, ] [package.dependencies] @@ -656,85 +674,101 @@ dnspython = ">=2.4.2,<3.0.0" [[package]] name = "multidict" -version = "6.0.4" +version = "6.0.5" description = "multidict implementation" optional = false python-versions = ">=3.7" files = [ - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, - {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, - {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, - {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, - {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, - {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, - {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, - {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, - {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, - {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, - {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, - {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, - {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, + {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, + {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, + {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, + {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, + {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, + {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, + {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, + {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, + {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, + {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, + {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, + {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, + {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, + {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, + {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] [[package]] @@ -764,50 +798,50 @@ setuptools = "*" [[package]] name = "packaging" -version = "23.1" +version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] name = "pathspec" -version = "0.11.1" +version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, - {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] name = "platformdirs" -version = "3.8.0" +version = "4.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-3.8.0-py3-none-any.whl", hash = "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"}, - {file = "platformdirs-3.8.0.tar.gz", hash = "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc"}, + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] [[package]] name = "pluggy" -version = "1.2.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -816,84 +850,94 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "psycopg2-binary" -version = "2.9.6" +version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "psycopg2-binary-2.9.6.tar.gz", hash = "sha256:1f64dcfb8f6e0c014c7f55e51c9759f024f70ea572fbdef123f85318c297947c"}, - {file = "psycopg2_binary-2.9.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d26e0342183c762de3276cca7a530d574d4e25121ca7d6e4a98e4f05cb8e4df7"}, - {file = "psycopg2_binary-2.9.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c48d8f2db17f27d41fb0e2ecd703ea41984ee19362cbce52c097963b3a1b4365"}, - {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe9dc0a884a8848075e576c1de0290d85a533a9f6e9c4e564f19adf8f6e54a7"}, - {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a76e027f87753f9bd1ab5f7c9cb8c7628d1077ef927f5e2446477153a602f2c"}, - {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6460c7a99fc939b849431f1e73e013d54aa54293f30f1109019c56a0b2b2ec2f"}, - {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae102a98c547ee2288637af07393dd33f440c25e5cd79556b04e3fca13325e5f"}, - {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9972aad21f965599ed0106f65334230ce826e5ae69fda7cbd688d24fa922415e"}, - {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7a40c00dbe17c0af5bdd55aafd6ff6679f94a9be9513a4c7e071baf3d7d22a70"}, - {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:cacbdc5839bdff804dfebc058fe25684cae322987f7a38b0168bc1b2df703fb1"}, - {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7f0438fa20fb6c7e202863e0d5ab02c246d35efb1d164e052f2f3bfe2b152bd0"}, - {file = "psycopg2_binary-2.9.6-cp310-cp310-win32.whl", hash = "sha256:b6c8288bb8a84b47e07013bb4850f50538aa913d487579e1921724631d02ea1b"}, - {file = "psycopg2_binary-2.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:61b047a0537bbc3afae10f134dc6393823882eb263088c271331602b672e52e9"}, - {file = "psycopg2_binary-2.9.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:964b4dfb7c1c1965ac4c1978b0f755cc4bd698e8aa2b7667c575fb5f04ebe06b"}, - {file = "psycopg2_binary-2.9.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afe64e9b8ea66866a771996f6ff14447e8082ea26e675a295ad3bdbffdd72afb"}, - {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15e2ee79e7cf29582ef770de7dab3d286431b01c3bb598f8e05e09601b890081"}, - {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfa74c903a3c1f0d9b1c7e7b53ed2d929a4910e272add6700c38f365a6002820"}, - {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b83456c2d4979e08ff56180a76429263ea254c3f6552cd14ada95cff1dec9bb8"}, - {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0645376d399bfd64da57148694d78e1f431b1e1ee1054872a5713125681cf1be"}, - {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e99e34c82309dd78959ba3c1590975b5d3c862d6f279f843d47d26ff89d7d7e1"}, - {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4ea29fc3ad9d91162c52b578f211ff1c931d8a38e1f58e684c45aa470adf19e2"}, - {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4ac30da8b4f57187dbf449294d23b808f8f53cad6b1fc3623fa8a6c11d176dd0"}, - {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e78e6e2a00c223e164c417628572a90093c031ed724492c763721c2e0bc2a8df"}, - {file = "psycopg2_binary-2.9.6-cp311-cp311-win32.whl", hash = "sha256:1876843d8e31c89c399e31b97d4b9725a3575bb9c2af92038464231ec40f9edb"}, - {file = "psycopg2_binary-2.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:b4b24f75d16a89cc6b4cdff0eb6a910a966ecd476d1e73f7ce5985ff1328e9a6"}, - {file = "psycopg2_binary-2.9.6-cp36-cp36m-win32.whl", hash = "sha256:498807b927ca2510baea1b05cc91d7da4718a0f53cb766c154c417a39f1820a0"}, - {file = "psycopg2_binary-2.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0d236c2825fa656a2d98bbb0e52370a2e852e5a0ec45fc4f402977313329174d"}, - {file = "psycopg2_binary-2.9.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:34b9ccdf210cbbb1303c7c4db2905fa0319391bd5904d32689e6dd5c963d2ea8"}, - {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84d2222e61f313c4848ff05353653bf5f5cf6ce34df540e4274516880d9c3763"}, - {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30637a20623e2a2eacc420059be11527f4458ef54352d870b8181a4c3020ae6b"}, - {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8122cfc7cae0da9a3077216528b8bb3629c43b25053284cc868744bfe71eb141"}, - {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38601cbbfe600362c43714482f43b7c110b20cb0f8172422c616b09b85a750c5"}, - {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c7e62ab8b332147a7593a385d4f368874d5fe4ad4e341770d4983442d89603e3"}, - {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2ab652e729ff4ad76d400df2624d223d6e265ef81bb8aa17fbd63607878ecbee"}, - {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c83a74b68270028dc8ee74d38ecfaf9c90eed23c8959fca95bd703d25b82c88e"}, - {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d4e6036decf4b72d6425d5b29bbd3e8f0ff1059cda7ac7b96d6ac5ed34ffbacd"}, - {file = "psycopg2_binary-2.9.6-cp37-cp37m-win32.whl", hash = "sha256:a8c28fd40a4226b4a84bdf2d2b5b37d2c7bd49486b5adcc200e8c7ec991dfa7e"}, - {file = "psycopg2_binary-2.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:51537e3d299be0db9137b321dfb6a5022caaab275775680e0c3d281feefaca6b"}, - {file = "psycopg2_binary-2.9.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf4499e0a83b7b7edcb8dabecbd8501d0d3a5ef66457200f77bde3d210d5debb"}, - {file = "psycopg2_binary-2.9.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7e13a5a2c01151f1208d5207e42f33ba86d561b7a89fca67c700b9486a06d0e2"}, - {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e0f754d27fddcfd74006455b6e04e6705d6c31a612ec69ddc040a5468e44b4e"}, - {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d57c3fd55d9058645d26ae37d76e61156a27722097229d32a9e73ed54819982a"}, - {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71f14375d6f73b62800530b581aed3ada394039877818b2d5f7fc77e3bb6894d"}, - {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:441cc2f8869a4f0f4bb408475e5ae0ee1f3b55b33f350406150277f7f35384fc"}, - {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:65bee1e49fa6f9cf327ce0e01c4c10f39165ee76d35c846ade7cb0ec6683e303"}, - {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:af335bac6b666cc6aea16f11d486c3b794029d9df029967f9938a4bed59b6a19"}, - {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cfec476887aa231b8548ece2e06d28edc87c1397ebd83922299af2e051cf2827"}, - {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:65c07febd1936d63bfde78948b76cd4c2a411572a44ac50719ead41947d0f26b"}, - {file = "psycopg2_binary-2.9.6-cp38-cp38-win32.whl", hash = "sha256:4dfb4be774c4436a4526d0c554af0cc2e02082c38303852a36f6456ece7b3503"}, - {file = "psycopg2_binary-2.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:02c6e3cf3439e213e4ee930308dc122d6fb4d4bea9aef4a12535fbd605d1a2fe"}, - {file = "psycopg2_binary-2.9.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e9182eb20f41417ea1dd8e8f7888c4d7c6e805f8a7c98c1081778a3da2bee3e4"}, - {file = "psycopg2_binary-2.9.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8a6979cf527e2603d349a91060f428bcb135aea2be3201dff794813256c274f1"}, - {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8338a271cb71d8da40b023a35d9c1e919eba6cbd8fa20a54b748a332c355d896"}, - {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3ed340d2b858d6e6fb5083f87c09996506af483227735de6964a6100b4e6a54"}, - {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f81e65376e52f03422e1fb475c9514185669943798ed019ac50410fb4c4df232"}, - {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfb13af3c5dd3a9588000910178de17010ebcccd37b4f9794b00595e3a8ddad3"}, - {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4c727b597c6444a16e9119386b59388f8a424223302d0c06c676ec8b4bc1f963"}, - {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4d67fbdaf177da06374473ef6f7ed8cc0a9dc640b01abfe9e8a2ccb1b1402c1f"}, - {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0892ef645c2fabb0c75ec32d79f4252542d0caec1d5d949630e7d242ca4681a3"}, - {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:02c0f3757a4300cf379eb49f543fb7ac527fb00144d39246ee40e1df684ab514"}, - {file = "psycopg2_binary-2.9.6-cp39-cp39-win32.whl", hash = "sha256:c3dba7dab16709a33a847e5cd756767271697041fbe3fe97c215b1fc1f5c9848"}, - {file = "psycopg2_binary-2.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:f6a88f384335bb27812293fdb11ac6aee2ca3f51d3c7820fe03de0a304ab6249"}, + {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, ] [[package]] name = "pyright" -version = "1.1.334" +version = "1.1.353" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.334-py3-none-any.whl", hash = "sha256:dcb13e8358e021189672c4d6ebcad192ab061e4c7225036973ec493183c6da68"}, - {file = "pyright-1.1.334.tar.gz", hash = "sha256:3adaf10f1f4209575dc022f9c897f7ef024639b7ea5b3cbe49302147e6949cd4"}, + {file = "pyright-1.1.353-py3-none-any.whl", hash = "sha256:8d7e6719d0be4fd9f4a37f010237c6a74d91ec1e7c81de634c2f3f9965f8ab43"}, + {file = "pyright-1.1.353.tar.gz", hash = "sha256:24343bbc2a4f997563f966b6244a2e863473f1d85af6d24abcb366fcbb4abca9"}, ] [package.dependencies] @@ -905,13 +949,13 @@ dev = ["twine (>=3.4.1)"] [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -941,13 +985,13 @@ pytest = ">=3.6" [[package]] name = "python-dateutil" -version = "2.8.2" +version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [package.dependencies] @@ -955,13 +999,13 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "1.0.0" +version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" files = [ - {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, - {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, ] [package.extras] @@ -969,62 +1013,72 @@ cli = ["click (>=5.0)"] [[package]] name = "pytz" -version = "2023.3" +version = "2023.4" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, - {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, + {file = "pytz-2023.4-py2.py3-none-any.whl", hash = "sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a"}, + {file = "pytz-2023.4.tar.gz", hash = "sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40"}, ] [[package]] name = "pyyaml" -version = "6.0" +version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [[package]] @@ -1050,19 +1104,19 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "setuptools" -version = "68.0.0" +version = "69.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, - {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, + {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, + {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -1077,73 +1131,82 @@ files = [ [[package]] name = "soupsieve" -version = "2.4.1" +version = "2.5" description = "A modern CSS selector implementation for Beautiful Soup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, - {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, ] [[package]] name = "sqlalchemy" -version = "2.0.17" +version = "2.0.28" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.17-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:04383f1e3452f6739084184e427e9d5cb4e68ddc765d52157bf5ef30d5eca14f"}, - {file = "SQLAlchemy-2.0.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:724355973297bbe547f3eb98b46ade65a67a3d5a6303f17ab59a2dc6fb938943"}, - {file = "SQLAlchemy-2.0.17-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf07ff9920cb3ca9d73525dfd4f36ddf9e1a83734ea8b4f724edfd9a2c6e82d9"}, - {file = "SQLAlchemy-2.0.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f389f77c68dc22cb51f026619291c4a38aeb4b7ecb5f998fd145b2d81ca513"}, - {file = "SQLAlchemy-2.0.17-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba03518e64d86f000dc24ab3d3a1aa876bcbaa8aa15662ac2df5e81537fa3394"}, - {file = "SQLAlchemy-2.0.17-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:218fb20c01e95004f50a3062bf4c447dcb360cab8274232f31947e254f118298"}, - {file = "SQLAlchemy-2.0.17-cp310-cp310-win32.whl", hash = "sha256:b47be4c6281a86670ea5cfbbbe6c3a65366a8742f5bc8b986f790533c60b5ddb"}, - {file = "SQLAlchemy-2.0.17-cp310-cp310-win_amd64.whl", hash = "sha256:74ddcafb6488f382854a7da851c404c394be3729bb3d91b02ad86c5458140eff"}, - {file = "SQLAlchemy-2.0.17-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:51736cfb607cf4e8fafb693906f9bc4e5ee55be0b096d44bd7f20cd8489b8571"}, - {file = "SQLAlchemy-2.0.17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8741d3d401383e54b2aada37cbd10f55c5d444b360eae3a82f74a2be568a7710"}, - {file = "SQLAlchemy-2.0.17-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ead58cae2a089eee1b0569060999cb5f2b2462109498a0937cc230a7556945a1"}, - {file = "SQLAlchemy-2.0.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f40e3a7d0a464f1c8593f2991e5520b2f5b26da24e88000bbd4423f86103d4f"}, - {file = "SQLAlchemy-2.0.17-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:21583808d37f126a647652c90332ac1d3a102edf3c94bcc3319edcc0ea2300cc"}, - {file = "SQLAlchemy-2.0.17-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f593170fc09c5abb1205a738290b39532f7380094dc151805009a07ae0e85330"}, - {file = "SQLAlchemy-2.0.17-cp311-cp311-win32.whl", hash = "sha256:b0eaf82cc844f6b46defe15ad243ea00d1e39ed3859df61130c263dc7204da6e"}, - {file = "SQLAlchemy-2.0.17-cp311-cp311-win_amd64.whl", hash = "sha256:1822620c89779b85f7c23d535c8e04b79c517739ae07aaed48c81e591ed5498e"}, - {file = "SQLAlchemy-2.0.17-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2269b1f9b8be47e52b70936069a25a3771eff53367aa5cc59bb94f28a6412e13"}, - {file = "SQLAlchemy-2.0.17-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48111d56afea5699bab72c38ec95561796b81befff9e13d1dd5ce251ab25f51d"}, - {file = "SQLAlchemy-2.0.17-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28da17059ecde53e2d10ba813d38db942b9f6344360b2958b25872d5cb729d35"}, - {file = "SQLAlchemy-2.0.17-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:48b40dc2895841ea89d89df9eb3ac69e2950a659db20a369acf4259f68e6dc1f"}, - {file = "SQLAlchemy-2.0.17-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7f31d4e7ca1dd8ca5a27fd5eaa0f9e2732fe769ff7dd35bf7bba179597e4df07"}, - {file = "SQLAlchemy-2.0.17-cp37-cp37m-win32.whl", hash = "sha256:7830e01b02d440c27f2a5be68296e74ccb55e6a5b5962ffafd360b98930b2e5e"}, - {file = "SQLAlchemy-2.0.17-cp37-cp37m-win_amd64.whl", hash = "sha256:234678ed6576531b8e4be255b980f20368bf07241a2e67b84e6b0fe679edb9c4"}, - {file = "SQLAlchemy-2.0.17-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c6ff5767d954f6091113fedcaaf49cdec2197ae4c5301fe83d5ae4393c82f33"}, - {file = "SQLAlchemy-2.0.17-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:aa995b21f853864996e4056d9fde479bcecf8b7bff4beb3555eebbbba815f35d"}, - {file = "SQLAlchemy-2.0.17-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:125f9f7e62ddf8b590c069729080ffe18b68a20d9882eb0947f72e06274601d7"}, - {file = "SQLAlchemy-2.0.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b114a16bc03dfe20b625062e456affd7b9938286e05a3f904a025b9aacc29dd4"}, - {file = "SQLAlchemy-2.0.17-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf175d26f6787cce30fe6c04303ca0aeeb0ad40eeb22e3391f24b32ec432a1e1"}, - {file = "SQLAlchemy-2.0.17-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e2d5c3596254cf1a96474b98e7ce20041c74c008b0f101c1cb4f8261cb77c6d3"}, - {file = "SQLAlchemy-2.0.17-cp38-cp38-win32.whl", hash = "sha256:513411d73503a6fc5804f01fae3b3d44f267c1b3a06cfeac02e9286a7330e857"}, - {file = "SQLAlchemy-2.0.17-cp38-cp38-win_amd64.whl", hash = "sha256:40a3dc52b2b16f08b5c16b9ee7646329e4b3411e9280e5e8d57b19eaa51cbef4"}, - {file = "SQLAlchemy-2.0.17-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e3189432db2f5753b4fde1aa90a61c69976f4e7e31d1cf4611bfe3514ed07478"}, - {file = "SQLAlchemy-2.0.17-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6150560fcffc6aee5ec9a97419ac768c7a9f56baf7a7eb59cb4b1b6a4d463ad9"}, - {file = "SQLAlchemy-2.0.17-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:910d45bf3673f0e4ef13858674bd23cfdafdc8368b45b948bf511797dbbb401d"}, - {file = "SQLAlchemy-2.0.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0aeb3afaa19f187a70fa592fbe3c20a056b57662691fd3abf60f016aa5c1848"}, - {file = "SQLAlchemy-2.0.17-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:36a87e26fe8fa8c466fae461a8fcb780d0a1cbf8206900759fc6fe874475a3ce"}, - {file = "SQLAlchemy-2.0.17-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e3a6b2788f193756076061626679c5c5a6d600ddf8324f986bc72004c3e9d92e"}, - {file = "SQLAlchemy-2.0.17-cp39-cp39-win32.whl", hash = "sha256:af7e2ba75bf84b64adb331918188dda634689a2abb151bc1a583e488363fd2f8"}, - {file = "SQLAlchemy-2.0.17-cp39-cp39-win_amd64.whl", hash = "sha256:394ac3adf3676fad76d4b8fcecddf747627f17f0738dc94bac15f303d05b03d4"}, - {file = "SQLAlchemy-2.0.17-py3-none-any.whl", hash = "sha256:cc9c2630c423ac4973492821b2969f5fe99d9736f3025da670095668fbfcd4d5"}, - {file = "SQLAlchemy-2.0.17.tar.gz", hash = "sha256:e186e9e95fb5d993b075c33fe4f38a22105f7ce11cecb5c17b5618181e356702"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0b148ab0438f72ad21cb004ce3bdaafd28465c4276af66df3b9ecd2037bf252"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbda76961eb8f27e6ad3c84d1dc56d5bc61ba8f02bd20fcf3450bd421c2fcc9c"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feea693c452d85ea0015ebe3bb9cd15b6f49acc1a31c28b3c50f4db0f8fb1e71"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da98815f82dce0cb31fd1e873a0cb30934971d15b74e0d78cf21f9e1b05953f"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5adf383c73f2d49ad15ff363a8748319ff84c371eed59ffd0127355d6ea1da"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56856b871146bfead25fbcaed098269d90b744eea5cb32a952df00d542cdd368"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-win32.whl", hash = "sha256:943aa74a11f5806ab68278284a4ddd282d3fb348a0e96db9b42cb81bf731acdc"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-win_amd64.whl", hash = "sha256:c6c4da4843e0dabde41b8f2e8147438330924114f541949e6318358a56d1875a"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46a3d4e7a472bfff2d28db838669fc437964e8af8df8ee1e4548e92710929adc"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3dd67b5d69794cfe82862c002512683b3db038b99002171f624712fa71aeaa"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61e2e41656a673b777e2f0cbbe545323dbe0d32312f590b1bc09da1de6c2a02"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0315d9125a38026227f559488fe7f7cee1bd2fbc19f9fd637739dc50bb6380b2"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af8ce2d31679006e7b747d30a89cd3ac1ec304c3d4c20973f0f4ad58e2d1c4c9"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:81ba314a08c7ab701e621b7ad079c0c933c58cdef88593c59b90b996e8b58fa5"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-win32.whl", hash = "sha256:1ee8bd6d68578e517943f5ebff3afbd93fc65f7ef8f23becab9fa8fb315afb1d"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-win_amd64.whl", hash = "sha256:ad7acbe95bac70e4e687a4dc9ae3f7a2f467aa6597049eeb6d4a662ecd990bb6"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d3499008ddec83127ab286c6f6ec82a34f39c9817f020f75eca96155f9765097"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b66fcd38659cab5d29e8de5409cdf91e9986817703e1078b2fdaad731ea66f5"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea30da1e76cb1acc5b72e204a920a3a7678d9d52f688f087dc08e54e2754c67"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:124202b4e0edea7f08a4db8c81cc7859012f90a0d14ba2bf07c099aff6e96462"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e23b88c69497a6322b5796c0781400692eca1ae5532821b39ce81a48c395aae9"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b6303bfd78fb3221847723104d152e5972c22367ff66edf09120fcde5ddc2e2"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-win32.whl", hash = "sha256:a921002be69ac3ab2cf0c3017c4e6a3377f800f1fca7f254c13b5f1a2f10022c"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-win_amd64.whl", hash = "sha256:b4a2cf92995635b64876dc141af0ef089c6eea7e05898d8d8865e71a326c0385"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e91b5e341f8c7f1e5020db8e5602f3ed045a29f8e27f7f565e0bdee3338f2c7"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c7b78dfc7278329f27be02c44abc0d69fe235495bb8e16ec7ef1b1a17952db"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eba73ef2c30695cb7eabcdb33bb3d0b878595737479e152468f3ba97a9c22a4"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5df5d1dafb8eee89384fb7a1f79128118bc0ba50ce0db27a40750f6f91aa99d5"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2858bbab1681ee5406650202950dc8f00e83b06a198741b7c656e63818633526"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-win32.whl", hash = "sha256:9461802f2e965de5cff80c5a13bc945abea7edaa1d29360b485c3d2b56cdb075"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-win_amd64.whl", hash = "sha256:a6bec1c010a6d65b3ed88c863d56b9ea5eeefdf62b5e39cafd08c65f5ce5198b"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:843a882cadebecc655a68bd9a5b8aa39b3c52f4a9a5572a3036fb1bb2ccdc197"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dbb990612c36163c6072723523d2be7c3eb1517bbdd63fe50449f56afafd1133"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7e4baf9161d076b9a7e432fce06217b9bd90cfb8f1d543d6e8c4595627edb9"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0a5354cb4de9b64bccb6ea33162cb83e03dbefa0d892db88a672f5aad638a75"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fffcc8edc508801ed2e6a4e7b0d150a62196fd28b4e16ab9f65192e8186102b6"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aca7b6d99a4541b2ebab4494f6c8c2f947e0df4ac859ced575238e1d6ca5716b"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-win32.whl", hash = "sha256:8c7f10720fc34d14abad5b647bc8202202f4948498927d9f1b4df0fb1cf391b7"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-win_amd64.whl", hash = "sha256:243feb6882b06a2af68ecf4bec8813d99452a1b62ba2be917ce6283852cf701b"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc4974d3684f28b61b9a90fcb4c41fb340fd4b6a50c04365704a4da5a9603b05"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87724e7ed2a936fdda2c05dbd99d395c91ea3c96f029a033a4a20e008dd876bf"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68722e6a550f5de2e3cfe9da6afb9a7dd15ef7032afa5651b0f0c6b3adb8815d"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328529f7c7f90adcd65aed06a161851f83f475c2f664a898af574893f55d9e53"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:df40c16a7e8be7413b885c9bf900d402918cc848be08a59b022478804ea076b8"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:426f2fa71331a64f5132369ede5171c52fd1df1bd9727ce621f38b5b24f48750"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-win32.whl", hash = "sha256:33157920b233bc542ce497a81a2e1452e685a11834c5763933b440fedd1d8e2d"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-win_amd64.whl", hash = "sha256:2f60843068e432311c886c5f03c4664acaef507cf716f6c60d5fde7265be9d7b"}, + {file = "SQLAlchemy-2.0.28-py3-none-any.whl", hash = "sha256:78bb7e8da0183a8301352d569900d9d3594c48ac21dc1c2ec6b3121ed8b6c986"}, + {file = "SQLAlchemy-2.0.28.tar.gz", hash = "sha256:dd53b6c4e6d960600fd6532b79ee28e2da489322fcf6648738134587faf767b6"}, ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} -psycopg2-binary = {version = "*", optional = true, markers = "extra == \"postgresql_psycopg2binary\""} -typing-extensions = ">=4.2.0" +greenlet = {version = "!=0.4.17", 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\""} +psycopg2-binary = {version = "*", optional = true, markers = "extra == \"postgresql-psycopg2binary\""} +typing-extensions = ">=4.6.0" [package.extras] -aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "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)"] @@ -1153,7 +1216,7 @@ mssql-pyodbc = ["pyodbc"] mypy = ["mypy (>=0.910)"] mysql = ["mysqlclient (>=1.4.0)"] mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)"] +oracle = ["cx_oracle (>=8)"] oracle-oracledb = ["oracledb (>=1.0.1)"] postgresql = ["psycopg2 (>=2.7)"] postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] @@ -1163,7 +1226,7 @@ postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3-binary"] +sqlcipher = ["sqlcipher3_binary"] [[package]] name = "tomli" @@ -1178,13 +1241,13 @@ files = [ [[package]] name = "types-beautifulsoup4" -version = "4.12.0.5" +version = "4.12.0.20240229" description = "Typing stubs for beautifulsoup4" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-beautifulsoup4-4.12.0.5.tar.gz", hash = "sha256:d9be456416a62a5b9740559592e1063a69d4b0a1b83911d878558c8ae8e07074"}, - {file = "types_beautifulsoup4-4.12.0.5-py3-none-any.whl", hash = "sha256:718364c8e6787884501c700b1d22b6c0a8711bf9d6c9bf96e1fba81495bc46a8"}, + {file = "types-beautifulsoup4-4.12.0.20240229.tar.gz", hash = "sha256:e37e4cfa11b03b01775732e56d2c010cb24ee107786277bae6bc0fa3e305b686"}, + {file = "types_beautifulsoup4-4.12.0.20240229-py3-none-any.whl", hash = "sha256:000cdddb8aee4effb45a04be95654de8629fb8594a4f2f1231cff81108977324"}, ] [package.dependencies] @@ -1192,198 +1255,204 @@ types-html5lib = "*" [[package]] name = "types-html5lib" -version = "1.1.11.14" +version = "1.1.11.20240228" description = "Typing stubs for html5lib" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-html5lib-1.1.11.14.tar.gz", hash = "sha256:091e9e74e0ee37c93fd789a164e99b2af80ecf5a314280450c6a763d027ea209"}, - {file = "types_html5lib-1.1.11.14-py3-none-any.whl", hash = "sha256:758c1a27f3b63363a346f3646be9f8b1f25df4fc1f96f88af6d1d831f24ad675"}, + {file = "types-html5lib-1.1.11.20240228.tar.gz", hash = "sha256:22736b7299e605ec4ba539d48691e905fd0c61c3ea610acc59922232dc84cede"}, + {file = "types_html5lib-1.1.11.20240228-py3-none-any.whl", hash = "sha256:af5de0125cb0fe5667543b158db83849b22e25c0e36c9149836b095548bf1020"}, ] [[package]] name = "types-python-dateutil" -version = "2.8.19.13" +version = "2.8.19.20240106" description = "Typing stubs for python-dateutil" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-python-dateutil-2.8.19.13.tar.gz", hash = "sha256:09a0275f95ee31ce68196710ed2c3d1b9dc42e0b61cc43acc369a42cb939134f"}, - {file = "types_python_dateutil-2.8.19.13-py3-none-any.whl", hash = "sha256:0b0e7c68e7043b0354b26a1e0225cb1baea7abb1b324d02b50e2d08f1221043f"}, + {file = "types-python-dateutil-2.8.19.20240106.tar.gz", hash = "sha256:1f8db221c3b98e6ca02ea83a58371b22c374f42ae5bbdf186db9c9a76581459f"}, + {file = "types_python_dateutil-2.8.19.20240106-py3-none-any.whl", hash = "sha256:efbbdc54590d0f16152fa103c9879c7d4a00e82078f6e2cf01769042165acaa2"}, ] [[package]] name = "types-pytz" -version = "2023.3.0.0" +version = "2023.4.0.20240130" description = "Typing stubs for pytz" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-pytz-2023.3.0.0.tar.gz", hash = "sha256:ecdc70d543aaf3616a7e48631543a884f74205f284cefd6649ddf44c6a820aac"}, - {file = "types_pytz-2023.3.0.0-py3-none-any.whl", hash = "sha256:4fc2a7fbbc315f0b6630e0b899fd6c743705abe1094d007b0e612d10da15e0f3"}, + {file = "types-pytz-2023.4.0.20240130.tar.gz", hash = "sha256:33676a90bf04b19f92c33eec8581136bea2f35ddd12759e579a624a006fd387a"}, + {file = "types_pytz-2023.4.0.20240130-py3-none-any.whl", hash = "sha256:6ce76a9f8fd22bd39b01a59c35bfa2db39b60d11a2f77145e97b730de7e64fe0"}, ] [[package]] name = "types-requests" -version = "2.31.0.1" +version = "2.31.0.20240218" description = "Typing stubs for requests" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-requests-2.31.0.1.tar.gz", hash = "sha256:3de667cffa123ce698591de0ad7db034a5317457a596eb0b4944e5a9d9e8d1ac"}, - {file = "types_requests-2.31.0.1-py3-none-any.whl", hash = "sha256:afb06ef8f25ba83d59a1d424bd7a5a939082f94b94e90ab5e6116bd2559deaa3"}, + {file = "types-requests-2.31.0.20240218.tar.gz", hash = "sha256:f1721dba8385958f504a5386240b92de4734e047a08a40751c1654d1ac3349c5"}, + {file = "types_requests-2.31.0.20240218-py3-none-any.whl", hash = "sha256:a82807ec6ddce8f00fe0e949da6d6bc1fbf1715420218a9640d695f70a9e5a9b"}, ] [package.dependencies] -types-urllib3 = "*" - -[[package]] -name = "types-urllib3" -version = "1.26.25.13" -description = "Typing stubs for urllib3" -optional = false -python-versions = "*" -files = [ - {file = "types-urllib3-1.26.25.13.tar.gz", hash = "sha256:3300538c9dc11dad32eae4827ac313f5d986b8b21494801f1bf97a1ac6c03ae5"}, - {file = "types_urllib3-1.26.25.13-py3-none-any.whl", hash = "sha256:5dbd1d2bef14efee43f5318b5d36d805a489f6600252bb53626d4bfafd95e27c"}, -] +urllib3 = ">=2" [[package]] name = "typing-extensions" -version = "4.7.0" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.10.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.0-py3-none-any.whl", hash = "sha256:5d8c9dac95c27d20df12fb1d97b9793ab8b2af8a3a525e68c80e21060c161771"}, - {file = "typing_extensions-4.7.0.tar.gz", hash = "sha256:935ccf31549830cda708b42289d44b6f74084d616a00be651601a4f968e77c82"}, + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] [[package]] name = "tzdata" -version = "2023.3" +version = "2024.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, - {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, ] [[package]] name = "tzlocal" -version = "5.0.1" +version = "5.2" description = "tzinfo object for the local timezone" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tzlocal-5.0.1-py3-none-any.whl", hash = "sha256:f3596e180296aaf2dbd97d124fe76ae3a0e3d32b258447de7b939b3fd4be992f"}, - {file = "tzlocal-5.0.1.tar.gz", hash = "sha256:46eb99ad4bdb71f3f72b7d24f4267753e240944ecfc16f25d2719ba89827a803"}, + {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, + {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, ] [package.dependencies] tzdata = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] -devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] +devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] [[package]] name = "urllib3" -version = "2.1.0" +version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, - {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "yarl" -version = "1.9.2" +version = "1.9.4" description = "Yet another URL library" optional = false python-versions = ">=3.7" files = [ - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, - {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, - {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, - {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, - {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, - {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, - {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, - {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, - {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, - {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, - {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, - {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, - {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, ] [package.dependencies] @@ -1393,4 +1462,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "8332e7448e7a8f79b27e4e3f2597e5cfb8b312368ce0bf95c5cb13c32dc90da9" +content-hash = "cf0759e0860a4edb222e420d688b3857f1decc8fbde7425597428697500e460a" diff --git a/pyproject.toml b/pyproject.toml index 91963233..16053455 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,16 +7,16 @@ license = "MIT" [tool.poetry.dependencies] python = "^3.10" -"discord.py" = "^2.2.3" +"discord.py" = "^2.3.2" icalendar = "^5.0" -"python-dateutil" = "^2.8" +"python-dateutil" = "^2.9" pytz = "^2023.3" -requests = "^2.28" +requests = "^2.31" beautifulsoup4 = "^4.12" APScheduler = "^3.10" -SQLAlchemy = {version = "^2.0.12", extras = ["postgresql_psycopg2binary"]} -humanize = "^4.3" -aiohttp = "^3.8" +SQLAlchemy = {version = "^2.0.28", extras = ["postgresql_psycopg2binary"]} +humanize = "^4.9" +aiohttp = "^3.9" aio-mc-rcon = "^3.2.0" PyYAML = "^6.0" mcstatus = "^11.1.0" @@ -25,10 +25,10 @@ mcstatus = "^11.1.0" botdev = "dev.cli:main" [tool.poetry.group.dev.dependencies] -pytest = "^7.3.1" +pytest = "^7.4.4" pytest-datafiles = "^3.0.0" python-dotenv = "^1.0.0" -black = "^23.3.0" +black = "^23.12.0" pyright = "^1.1.316" types-requests = "^2.30.0.0" types-beautifulsoup4 = "^4.12.0.4" diff --git a/uqcsbot/dominos_coupons.py b/uqcsbot/dominos_coupons.py index b7eb7b5c..66ac7588 100644 --- a/uqcsbot/dominos_coupons.py +++ b/uqcsbot/dominos_coupons.py @@ -59,8 +59,11 @@ async def dominoscoupons( try: coupons = _get_coupons(number_of_coupons, ignore_expiry, keywords.split()) except RequestException as error: + resp_content = ( + error.response.content if error.response else "No response error given." + ) logging.warning( - f"Could not connect to dominos coupon site ({COUPONESE_DOMINOS_URL}): {error.response.content}" + f"Could not connect to dominos coupon site ({COUPONESE_DOMINOS_URL}): {resp_content}" ) await interaction.edit_original_response( content=f"Sadly could not reach the coupon website (<{COUPONESE_DOMINOS_URL}>)..." diff --git a/uqcsbot/holidays.py b/uqcsbot/holidays.py index 16adb0d2..a31618c5 100644 --- a/uqcsbot/holidays.py +++ b/uqcsbot/holidays.py @@ -103,7 +103,10 @@ def get_holiday_page() -> bytes | None: response = requests.get(HOLIDAY_URL) return response.content except RequestException as e: - logging.warning(e.response.content) + resp_content = e.response.content if e.response else "No response error given." + logging.warning( + f"(RequestException) Could not fetch {HOLIDAY_URL}: {resp_content}" + ) class Holidays(commands.Cog): From 0df12b943e28a67141b8378f848f62b798ff0543 Mon Sep 17 00:00:00 2001 From: Jake Moss Date: Sun, 10 Mar 2024 06:59:12 +1000 Subject: [PATCH 46/48] Add lichen cat (#196) * Add lichen cat * Add code fence and use join * Ugh, missing comma * Sometimes it's useful to run the code before you commit * Didn't realise this was part of the command description, fancy stuff --- uqcsbot/cat.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/uqcsbot/cat.py b/uqcsbot/cat.py index 7a3d7a06..86ccd55b 100644 --- a/uqcsbot/cat.py +++ b/uqcsbot/cat.py @@ -16,6 +16,21 @@ async def cat(self, interaction: discord.Interaction): Displays the moss cat. Brings torture to CSSE2310 students. """ + if randrange(0, 100) == 0: + cat = "\n".join( + ( + "```", + r""" |\ _,,,---,,_ """, + r""" /,`.-'`' -. ;-;;,_" """, + r""" |,4- ) )-,_. ,\ ( `'-'" """, + r""" '---''(_/--' `-'\_) """, + "```", + ) + ) + + await interaction.response.send_message(cat) + return + # ansi colour codes pink = "\u001b[0;35m" red = "\u001b[0;31m" From 460d6db91d4e397760e60add72d76f2d5a82406c Mon Sep 17 00:00:00 2001 From: Peter Gow <59110523+Ninjaman10p@users.noreply.github.com> Date: Wed, 27 Mar 2024 09:19:06 +1000 Subject: [PATCH 47/48] Removed potentially inappropriate #yelling response (#199) --- uqcsbot/yelling.py | 1 - 1 file changed, 1 deletion(-) diff --git a/uqcsbot/yelling.py b/uqcsbot/yelling.py index 5a6ee04c..90660be0 100644 --- a/uqcsbot/yelling.py +++ b/uqcsbot/yelling.py @@ -189,7 +189,6 @@ def generate_response(self, text: str) -> str: "I CAN’T UNDERSTAND YOU WHEN YOU MUMBLE!", "YOU’RE GONNA NEED TO BE LOUDER!", "WHY ARE YOU SO QUIET‽", - "QUIET PEOPLE SHOULD BE DRAGGED OUT INTO THE STREET AND SHOT!", "PLEASE USE YOUR OUTSIDE VOICE!", "IT’S ON THE LEFT OF THE “A” KEY!", "FORMER PRESIDENT THEODORE ROOSEVELT’S FOREIGN POLICY IS A SHAM!", From e3cc014c9794829ba587ca0ae66c5cb1a8fbb4a2 Mon Sep 17 00:00:00 2001 From: Andrew Brown <92134285+andrewj-brown@users.noreply.github.com> Date: Tue, 14 May 2024 10:50:47 +1000 Subject: [PATCH 48/48] please shut up dependabot (#200) --- poetry.lock | 349 ++++++++++++++++++++++++++-------------------------- 1 file changed, 177 insertions(+), 172 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1561ff2f..19b7fcd9 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.5.1 and should not be changed by hand. [[package]] name = "aio-mc-rcon" @@ -13,87 +13,87 @@ files = [ [[package]] name = "aiohttp" -version = "3.9.3" +version = "3.9.5" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, - {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, - {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, - {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, - {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, - {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, - {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, - {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, - {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, - {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, - {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, - {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, - {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, - {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, + {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, + {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, + {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, + {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, + {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, + {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, + {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, + {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, + {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, ] [package.dependencies] @@ -162,15 +162,18 @@ files = [ [[package]] name = "asyncio-dgram" -version = "2.1.2" +version = "2.2.0" description = "Higher level Datagram support for Asyncio" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "asyncio-dgram-2.1.2.tar.gz", hash = "sha256:bc28a90bc0523009fb0da16ca983c1400ff403a315754d86e037910563697f91"}, - {file = "asyncio_dgram-2.1.2-py3-none-any.whl", hash = "sha256:9ef55fc760f93c8212709329a1e28a1cf1c1f0fc8222f1be0227c2b7606a10a2"}, + {file = "asyncio-dgram-2.2.0.tar.gz", hash = "sha256:73362b491786153d8b888936c5780548b40b4e6f5e0d62bfef956cb7b6ed9684"}, + {file = "asyncio_dgram-2.2.0-py3-none-any.whl", hash = "sha256:7afe5a587d1d57908c7a02fe84c785f075d3fb59b555039a6ff8aead28622743"}, ] +[package.dependencies] +setuptools = "*" + [package.extras] test = ["black (>=20.8b1)", "flake8 (>=3.8.3)", "mypy (>=0.812)", "mypy-extensions (>=0.4.3)", "pytest (>=5.4.3)", "pytest-asyncio (>=0.14.0)", "typed-ast (>=1.4.3)", "typing-extensions (>=3.10.0.0)"] @@ -437,13 +440,13 @@ wmi = ["wmi (>=1.5.1)"] [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -622,13 +625,13 @@ tests = ["freezegun", "pytest", "pytest-cov"] [[package]] name = "icalendar" -version = "5.0.11" +version = "5.0.12" description = "iCalendar parser/generator" optional = false python-versions = ">=3.7" files = [ - {file = "icalendar-5.0.11-py3-none-any.whl", hash = "sha256:81864971ac43a1b7d0a555dc1b667836ce59fc719a7f845a96f2f03205fb83b9"}, - {file = "icalendar-5.0.11.tar.gz", hash = "sha256:7a298bb864526589d0de81f4b736eeb6ff9e539fefb405f7977aa5c1e201ca00"}, + {file = "icalendar-5.0.12-py3-none-any.whl", hash = "sha256:d873bb859df9c6d0e597b16d247436e0f83f7ac1b90a06429b8393fe8afeba40"}, + {file = "icalendar-5.0.12.tar.gz", hash = "sha256:73f9be68477722c98320621400943705dcfdbbc6c2b565253f72d3f87e514db8"}, ] [package.dependencies] @@ -637,13 +640,13 @@ pytz = "*" [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -798,13 +801,13 @@ setuptools = "*" [[package]] name = "packaging" -version = "23.2" +version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] @@ -820,28 +823,29 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, + {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -931,13 +935,13 @@ files = [ [[package]] name = "pyright" -version = "1.1.353" +version = "1.1.362" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.353-py3-none-any.whl", hash = "sha256:8d7e6719d0be4fd9f4a37f010237c6a74d91ec1e7c81de634c2f3f9965f8ab43"}, - {file = "pyright-1.1.353.tar.gz", hash = "sha256:24343bbc2a4f997563f966b6244a2e863473f1d85af6d24abcb366fcbb4abca9"}, + {file = "pyright-1.1.362-py3-none-any.whl", hash = "sha256:969957cff45154d8a45a4ab1dae5bdc8223d8bd3c64654fa608ab3194dfff319"}, + {file = "pyright-1.1.362.tar.gz", hash = "sha256:6a477e448d4a07a6a0eab58b2a15a1bbed031eb3169fa809edee79cca168d83a"}, ] [package.dependencies] @@ -1047,6 +1051,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -1104,18 +1109,18 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "setuptools" -version = "69.1.1" +version = "69.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, - {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] @@ -1142,64 +1147,64 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.28" +version = "2.0.30" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0b148ab0438f72ad21cb004ce3bdaafd28465c4276af66df3b9ecd2037bf252"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbda76961eb8f27e6ad3c84d1dc56d5bc61ba8f02bd20fcf3450bd421c2fcc9c"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feea693c452d85ea0015ebe3bb9cd15b6f49acc1a31c28b3c50f4db0f8fb1e71"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da98815f82dce0cb31fd1e873a0cb30934971d15b74e0d78cf21f9e1b05953f"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5adf383c73f2d49ad15ff363a8748319ff84c371eed59ffd0127355d6ea1da"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56856b871146bfead25fbcaed098269d90b744eea5cb32a952df00d542cdd368"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-win32.whl", hash = "sha256:943aa74a11f5806ab68278284a4ddd282d3fb348a0e96db9b42cb81bf731acdc"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-win_amd64.whl", hash = "sha256:c6c4da4843e0dabde41b8f2e8147438330924114f541949e6318358a56d1875a"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46a3d4e7a472bfff2d28db838669fc437964e8af8df8ee1e4548e92710929adc"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3dd67b5d69794cfe82862c002512683b3db038b99002171f624712fa71aeaa"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61e2e41656a673b777e2f0cbbe545323dbe0d32312f590b1bc09da1de6c2a02"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0315d9125a38026227f559488fe7f7cee1bd2fbc19f9fd637739dc50bb6380b2"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af8ce2d31679006e7b747d30a89cd3ac1ec304c3d4c20973f0f4ad58e2d1c4c9"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:81ba314a08c7ab701e621b7ad079c0c933c58cdef88593c59b90b996e8b58fa5"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-win32.whl", hash = "sha256:1ee8bd6d68578e517943f5ebff3afbd93fc65f7ef8f23becab9fa8fb315afb1d"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-win_amd64.whl", hash = "sha256:ad7acbe95bac70e4e687a4dc9ae3f7a2f467aa6597049eeb6d4a662ecd990bb6"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d3499008ddec83127ab286c6f6ec82a34f39c9817f020f75eca96155f9765097"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b66fcd38659cab5d29e8de5409cdf91e9986817703e1078b2fdaad731ea66f5"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea30da1e76cb1acc5b72e204a920a3a7678d9d52f688f087dc08e54e2754c67"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:124202b4e0edea7f08a4db8c81cc7859012f90a0d14ba2bf07c099aff6e96462"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e23b88c69497a6322b5796c0781400692eca1ae5532821b39ce81a48c395aae9"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b6303bfd78fb3221847723104d152e5972c22367ff66edf09120fcde5ddc2e2"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-win32.whl", hash = "sha256:a921002be69ac3ab2cf0c3017c4e6a3377f800f1fca7f254c13b5f1a2f10022c"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-win_amd64.whl", hash = "sha256:b4a2cf92995635b64876dc141af0ef089c6eea7e05898d8d8865e71a326c0385"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e91b5e341f8c7f1e5020db8e5602f3ed045a29f8e27f7f565e0bdee3338f2c7"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c7b78dfc7278329f27be02c44abc0d69fe235495bb8e16ec7ef1b1a17952db"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eba73ef2c30695cb7eabcdb33bb3d0b878595737479e152468f3ba97a9c22a4"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5df5d1dafb8eee89384fb7a1f79128118bc0ba50ce0db27a40750f6f91aa99d5"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2858bbab1681ee5406650202950dc8f00e83b06a198741b7c656e63818633526"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-win32.whl", hash = "sha256:9461802f2e965de5cff80c5a13bc945abea7edaa1d29360b485c3d2b56cdb075"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-win_amd64.whl", hash = "sha256:a6bec1c010a6d65b3ed88c863d56b9ea5eeefdf62b5e39cafd08c65f5ce5198b"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:843a882cadebecc655a68bd9a5b8aa39b3c52f4a9a5572a3036fb1bb2ccdc197"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dbb990612c36163c6072723523d2be7c3eb1517bbdd63fe50449f56afafd1133"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7e4baf9161d076b9a7e432fce06217b9bd90cfb8f1d543d6e8c4595627edb9"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0a5354cb4de9b64bccb6ea33162cb83e03dbefa0d892db88a672f5aad638a75"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fffcc8edc508801ed2e6a4e7b0d150a62196fd28b4e16ab9f65192e8186102b6"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aca7b6d99a4541b2ebab4494f6c8c2f947e0df4ac859ced575238e1d6ca5716b"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-win32.whl", hash = "sha256:8c7f10720fc34d14abad5b647bc8202202f4948498927d9f1b4df0fb1cf391b7"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-win_amd64.whl", hash = "sha256:243feb6882b06a2af68ecf4bec8813d99452a1b62ba2be917ce6283852cf701b"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc4974d3684f28b61b9a90fcb4c41fb340fd4b6a50c04365704a4da5a9603b05"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87724e7ed2a936fdda2c05dbd99d395c91ea3c96f029a033a4a20e008dd876bf"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68722e6a550f5de2e3cfe9da6afb9a7dd15ef7032afa5651b0f0c6b3adb8815d"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328529f7c7f90adcd65aed06a161851f83f475c2f664a898af574893f55d9e53"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:df40c16a7e8be7413b885c9bf900d402918cc848be08a59b022478804ea076b8"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:426f2fa71331a64f5132369ede5171c52fd1df1bd9727ce621f38b5b24f48750"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-win32.whl", hash = "sha256:33157920b233bc542ce497a81a2e1452e685a11834c5763933b440fedd1d8e2d"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-win_amd64.whl", hash = "sha256:2f60843068e432311c886c5f03c4664acaef507cf716f6c60d5fde7265be9d7b"}, - {file = "SQLAlchemy-2.0.28-py3-none-any.whl", hash = "sha256:78bb7e8da0183a8301352d569900d9d3594c48ac21dc1c2ec6b3121ed8b6c986"}, - {file = "SQLAlchemy-2.0.28.tar.gz", hash = "sha256:dd53b6c4e6d960600fd6532b79ee28e2da489322fcf6648738134587faf767b6"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b48154678e76445c7ded1896715ce05319f74b1e73cf82d4f8b59b46e9c0ddc"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2753743c2afd061bb95a61a51bbb6a1a11ac1c44292fad898f10c9839a7f75b2"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7bfc726d167f425d4c16269a9a10fe8630ff6d14b683d588044dcef2d0f6be7"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f61ada6979223013d9ab83a3ed003ded6959eae37d0d685db2c147e9143797"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a365eda439b7a00732638f11072907c1bc8e351c7665e7e5da91b169af794af"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bba002a9447b291548e8d66fd8c96a6a7ed4f2def0bb155f4f0a1309fd2735d5"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-win32.whl", hash = "sha256:0138c5c16be3600923fa2169532205d18891b28afa817cb49b50e08f62198bb8"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-win_amd64.whl", hash = "sha256:99650e9f4cf3ad0d409fed3eec4f071fadd032e9a5edc7270cd646a26446feeb"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:955991a09f0992c68a499791a753523f50f71a6885531568404fa0f231832aa0"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f69e4c756ee2686767eb80f94c0125c8b0a0b87ede03eacc5c8ae3b54b99dc46"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69c9db1ce00e59e8dd09d7bae852a9add716efdc070a3e2068377e6ff0d6fdaa"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1429a4b0f709f19ff3b0cf13675b2b9bfa8a7e79990003207a011c0db880a13"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:efedba7e13aa9a6c8407c48facfdfa108a5a4128e35f4c68f20c3407e4376aa9"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:16863e2b132b761891d6c49f0a0f70030e0bcac4fd208117f6b7e053e68668d0"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-win32.whl", hash = "sha256:2ecabd9ccaa6e914e3dbb2aa46b76dede7eadc8cbf1b8083c94d936bcd5ffb49"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-win_amd64.whl", hash = "sha256:0b3f4c438e37d22b83e640f825ef0f37b95db9aa2d68203f2c9549375d0b2260"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5a79d65395ac5e6b0c2890935bad892eabb911c4aa8e8015067ddb37eea3d56c"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a5baf9267b752390252889f0c802ea13b52dfee5e369527da229189b8bd592e"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cb5a646930c5123f8461f6468901573f334c2c63c795b9af350063a736d0134"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:296230899df0b77dec4eb799bcea6fbe39a43707ce7bb166519c97b583cfcab3"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c62d401223f468eb4da32627bffc0c78ed516b03bb8a34a58be54d618b74d472"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3b69e934f0f2b677ec111b4d83f92dc1a3210a779f69bf905273192cf4ed433e"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-win32.whl", hash = "sha256:77d2edb1f54aff37e3318f611637171e8ec71472f1fdc7348b41dcb226f93d90"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-win_amd64.whl", hash = "sha256:b6c7ec2b1f4969fc19b65b7059ed00497e25f54069407a8701091beb69e591a5"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a8e3b0a7e09e94be7510d1661339d6b52daf202ed2f5b1f9f48ea34ee6f2d57"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b60203c63e8f984df92035610c5fb76d941254cf5d19751faab7d33b21e5ddc0"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1dc3eabd8c0232ee8387fbe03e0a62220a6f089e278b1f0aaf5e2d6210741ad"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:40ad017c672c00b9b663fcfcd5f0864a0a97828e2ee7ab0c140dc84058d194cf"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e42203d8d20dc704604862977b1470a122e4892791fe3ed165f041e4bf447a1b"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-win32.whl", hash = "sha256:2a4f4da89c74435f2bc61878cd08f3646b699e7d2eba97144030d1be44e27584"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-win_amd64.whl", hash = "sha256:b6bf767d14b77f6a18b6982cbbf29d71bede087edae495d11ab358280f304d8e"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc0c53579650a891f9b83fa3cecd4e00218e071d0ba00c4890f5be0c34887ed3"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:311710f9a2ee235f1403537b10c7687214bb1f2b9ebb52702c5aa4a77f0b3af7"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:408f8b0e2c04677e9c93f40eef3ab22f550fecb3011b187f66a096395ff3d9fd"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37a4b4fb0dd4d2669070fb05b8b8824afd0af57587393015baee1cf9890242d9"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a943d297126c9230719c27fcbbeab57ecd5d15b0bd6bfd26e91bfcfe64220621"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a089e218654e740a41388893e090d2e2c22c29028c9d1353feb38638820bbeb"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-win32.whl", hash = "sha256:fa561138a64f949f3e889eb9ab8c58e1504ab351d6cf55259dc4c248eaa19da6"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-win_amd64.whl", hash = "sha256:7d74336c65705b986d12a7e337ba27ab2b9d819993851b140efdf029248e818e"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae8c62fe2480dd61c532ccafdbce9b29dacc126fe8be0d9a927ca3e699b9491a"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2383146973a15435e4717f94c7509982770e3e54974c71f76500a0136f22810b"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8409de825f2c3b62ab15788635ccaec0c881c3f12a8af2b12ae4910a0a9aeef6"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0094c5dc698a5f78d3d1539853e8ecec02516b62b8223c970c86d44e7a80f6c7"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:edc16a50f5e1b7a06a2dcc1f2205b0b961074c123ed17ebda726f376a5ab0953"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f7703c2010355dd28f53deb644a05fc30f796bd8598b43f0ba678878780b6e4c"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-win32.whl", hash = "sha256:1f9a727312ff6ad5248a4367358e2cf7e625e98b1028b1d7ab7b806b7d757513"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-win_amd64.whl", hash = "sha256:a0ef36b28534f2a5771191be6edb44cc2673c7b2edf6deac6562400288664221"}, + {file = "SQLAlchemy-2.0.30-py3-none-any.whl", hash = "sha256:7108d569d3990c71e26a42f60474b4c02c8586c4681af5fd67e51a044fdea86a"}, + {file = "SQLAlchemy-2.0.30.tar.gz", hash = "sha256:2b1708916730f4830bc69d6f49d37f7698b5bd7530aca7f04f785f8849e95255"}, ] [package.dependencies] -greenlet = {version = "!=0.4.17", 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\""} +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} psycopg2-binary = {version = "*", optional = true, markers = "extra == \"postgresql-psycopg2binary\""} typing-extensions = ">=4.6.0" @@ -1241,13 +1246,13 @@ files = [ [[package]] name = "types-beautifulsoup4" -version = "4.12.0.20240229" +version = "4.12.0.20240511" description = "Typing stubs for beautifulsoup4" optional = false python-versions = ">=3.8" files = [ - {file = "types-beautifulsoup4-4.12.0.20240229.tar.gz", hash = "sha256:e37e4cfa11b03b01775732e56d2c010cb24ee107786277bae6bc0fa3e305b686"}, - {file = "types_beautifulsoup4-4.12.0.20240229-py3-none-any.whl", hash = "sha256:000cdddb8aee4effb45a04be95654de8629fb8594a4f2f1231cff81108977324"}, + {file = "types-beautifulsoup4-4.12.0.20240511.tar.gz", hash = "sha256:004f6096fdd83b19cdbf6cb10e4eae57b10205eccc365d0a69d77da836012e28"}, + {file = "types_beautifulsoup4-4.12.0.20240511-py3-none-any.whl", hash = "sha256:7ceda66a93ba28d759d5046d7fec9f4cad2f563a77b3a789efc90bcadafeefd1"}, ] [package.dependencies] @@ -1266,13 +1271,13 @@ files = [ [[package]] name = "types-python-dateutil" -version = "2.8.19.20240106" +version = "2.9.0.20240316" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.8" files = [ - {file = "types-python-dateutil-2.8.19.20240106.tar.gz", hash = "sha256:1f8db221c3b98e6ca02ea83a58371b22c374f42ae5bbdf186db9c9a76581459f"}, - {file = "types_python_dateutil-2.8.19.20240106-py3-none-any.whl", hash = "sha256:efbbdc54590d0f16152fa103c9879c7d4a00e82078f6e2cf01769042165acaa2"}, + {file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"}, + {file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"}, ] [[package]] @@ -1288,13 +1293,13 @@ files = [ [[package]] name = "types-requests" -version = "2.31.0.20240218" +version = "2.31.0.20240406" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" files = [ - {file = "types-requests-2.31.0.20240218.tar.gz", hash = "sha256:f1721dba8385958f504a5386240b92de4734e047a08a40751c1654d1ac3349c5"}, - {file = "types_requests-2.31.0.20240218-py3-none-any.whl", hash = "sha256:a82807ec6ddce8f00fe0e949da6d6bc1fbf1715420218a9640d695f70a9e5a9b"}, + {file = "types-requests-2.31.0.20240406.tar.gz", hash = "sha256:4428df33c5503945c74b3f42e82b181e86ec7b724620419a2966e2de604ce1a1"}, + {file = "types_requests-2.31.0.20240406-py3-none-any.whl", hash = "sha256:6216cdac377c6b9a040ac1c0404f7284bd13199c0e1bb235f4324627e8898cf5"}, ] [package.dependencies] @@ -1302,13 +1307,13 @@ urllib3 = ">=2" [[package]] name = "typing-extensions" -version = "4.10.0" +version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, - {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] [[package]]