From ec5a09e4df86ea848a41ed6f6d04e01a6337d889 Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Thu, 1 Aug 2024 21:22:48 +0000 Subject: [PATCH 01/40] add some stuff but it's fucked --- cogs/__init__.py | 3 ++ cogs/events.py | 110 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 cogs/events.py diff --git a/cogs/__init__.py b/cogs/__init__.py index bfd6accc..ab8d0f83 100644 --- a/cogs/__init__.py +++ b/cogs/__init__.py @@ -12,6 +12,7 @@ "GetTokenAuthorisationCommandCog", "CommandErrorCog", "DeleteAllCommandsCog", + "EventsManagementCommandsCog", "EditMessageCommandCog", "EnsureMembersInductedCommandCog", "MakeApplicantSlashCommandCog", @@ -53,6 +54,7 @@ from .command_error import CommandErrorCog from .delete_all import DeleteAllCommandsCog from .edit_message import EditMessageCommandCog +from .events import EventsManagementCommandsCog from .get_token_authorisation import GetTokenAuthorisationCommandCog from .induct import ( EnsureMembersInductedCommandCog, @@ -91,6 +93,7 @@ def setup(bot: TeXBot) -> None: CommitteeHandoverCommandCog, AnnualRolesResetCommandCog, InductSlashCommandCog, + EventsManagementCommandsCog, InductSendMessageCog, AnnualYearChannelsIncrementCommandCog, InductContextCommandsCog, diff --git a/cogs/events.py b/cogs/events.py new file mode 100644 index 00000000..7cd0b7df --- /dev/null +++ b/cogs/events.py @@ -0,0 +1,110 @@ +"""Contains cog classes for any events interactions.""" + +from collections.abc import Sequence + +__all__: Sequence[str] = ("EventsManagementCommandsCog",) + + +import logging +from collections.abc import Mapping +from logging import Logger +from typing import Final + +import aiohttp +import bs4 +import discord +from bs4 import BeautifulSoup + +from config import settings +from utils import CommandChecks, TeXBotApplicationContext, TeXBotBaseCog + +logger: Final[Logger] = logging.getLogger("TeX-Bot") + + +REQUEST_HEADERS: Final[Mapping[str, str]] = { + "Cache-Control": "no-cache", + "Pragma": "no-cache", + "Expires": "0", +} + +REQUEST_COOKIES: Final[Mapping[str, str]] = { + ".ASPXAUTH": settings["MEMBERS_LIST_AUTH_SESSION_COOKIE"], +} + +REQUEST_URL: Final[str] = "https://www.guildofstudents.com/events/edit/6531/" +FROM_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$datesFilter$txtFromDate" +TO_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$datesFilter$txtToDate" +BUTTON_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$fsSetDates$btnSubmit" +EVENT_TABLE_ID: Final[str] = "ctl00_ctl00_Main_AdminPageContent_gvEvents" + + +class EventsManagementCommandsCog(TeXBotBaseCog): + """Cog class to define event management commands.""" + + async def _get_all_guild_events(self, ctx: TeXBotApplicationContext) -> None: + """Fetch all events on the guild website.""" + form_data: dict[str, str] = { + FROM_DATE_KEY: "01/01/2024", + TO_DATE_KEY: "01/01/2025", + BUTTON_KEY: "Find Events", + "__EVENTTARGET": "", + "__EVENTARGUMENT": "", + } + http_session: aiohttp.ClientSession = aiohttp.ClientSession( + headers=REQUEST_HEADERS, + cookies=REQUEST_COOKIES, + ) + async with http_session, http_session.get(REQUEST_URL) as field_data: + data_response = BeautifulSoup(await field_data.text(), "html.parser") + view_state: str = data_response.find("input", {"name": "__VIEWSTATE"}).get("value") # type: ignore[union-attr, assignment] + event_validation: str = data_response.find("input", {"name": "__EVENTVALIDATION"}).get("value") # type: ignore[union-attr, assignment] # noqa: E501 + + form_data["__VIEWSTATE"] = view_state + form_data["__EVENTVALIDATION"] = event_validation + + session_v2: aiohttp.ClientSession = aiohttp.ClientSession( + headers=REQUEST_HEADERS, + cookies=REQUEST_COOKIES, + ) + async with session_v2, session_v2.post(REQUEST_URL, data=form_data) as http_response: + if http_response.headers.get("Content-Type") == "gzip": + response_html = await http_response.read() + else: + response_html = await http_response.text() + + parsed_html: bs4.Tag | bs4.NavigableString | None = BeautifulSoup( + response_html, + "html.parser", + ).find( + "table", + {"id": EVENT_TABLE_ID}, + ) + + if parsed_html is None or isinstance(parsed_html, bs4.NavigableString): + await self.command_send_error( + ctx, + message="Something went wrong and the event list could not be fetched.", + ) + logger.debug(http_response) + return + + if "There are no events" in str(parsed_html): + await ctx.respond("There are no events found for the date range selected.") + logger.debug(http_response.request_info) + return + + await ctx.respond(parsed_html) + + @discord.slash_command( # type: ignore[no-untyped-call, misc] + name="get-events", + description="Returns all events currently on the guild website.", + ) + @CommandChecks.check_interaction_user_has_committee_role + @CommandChecks.check_interaction_user_in_main_guild + async def get_events(self, ctx: TeXBotApplicationContext) -> None: + """Command to get the events on the guild website.""" + await self._get_all_guild_events(ctx) + + + + From 572e03824772b28b065a623b7b14fadc59f4f1eb Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Fri, 2 Aug 2024 22:43:28 +0000 Subject: [PATCH 02/40] pending --- cogs/events.py | 73 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index 7cd0b7df..a939594d 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -7,11 +7,13 @@ import logging from collections.abc import Mapping +from datetime import datetime from logging import Logger from typing import Final import aiohttp import bs4 +import dateutil import discord from bs4 import BeautifulSoup @@ -41,14 +43,15 @@ class EventsManagementCommandsCog(TeXBotBaseCog): """Cog class to define event management commands.""" - async def _get_all_guild_events(self, ctx: TeXBotApplicationContext) -> None: + async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: str, to_date: str) -> None: # noqa: E501 """Fetch all events on the guild website.""" form_data: dict[str, str] = { - FROM_DATE_KEY: "01/01/2024", - TO_DATE_KEY: "01/01/2025", + FROM_DATE_KEY: from_date, + TO_DATE_KEY: to_date, BUTTON_KEY: "Find Events", "__EVENTTARGET": "", "__EVENTARGUMENT": "", + "__VIEWSTATEENCRYPTED": "", } http_session: aiohttp.ClientSession = aiohttp.ClientSession( headers=REQUEST_HEADERS, @@ -58,19 +61,42 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext) -> None: data_response = BeautifulSoup(await field_data.text(), "html.parser") view_state: str = data_response.find("input", {"name": "__VIEWSTATE"}).get("value") # type: ignore[union-attr, assignment] event_validation: str = data_response.find("input", {"name": "__EVENTVALIDATION"}).get("value") # type: ignore[union-attr, assignment] # noqa: E501 + view_state_generator: str = data_response.find("input", {"name": "__VIEWSTATEGENERATOR"}).get("value") # type: ignore[union-attr, assignment] # noqa: E501 form_data["__VIEWSTATE"] = view_state form_data["__EVENTVALIDATION"] = event_validation + form_data["__VIEWSTATEGENERATOR"] = view_state_generator + + new_cookies: dict[str, str] = { + ".ASPXAUTH": settings["MEMBERS_LIST_AUTH_SESSION_COOKIE"], + } + + anti_xss_cookie = field_data.cookies.get("__AntiXsrfToken") + if anti_xss_cookie is not None: + new_cookies["__AntiXsrfToken"] = anti_xss_cookie.value + + asp_net_shared_cookie = field_data.cookies.get(".AspNet.SharedCookie") + if asp_net_shared_cookie is not None: + new_cookies[".AspNet.SharedCookie"] = asp_net_shared_cookie.value + + asp_session_id = field_data.cookies.get("ASP.NET_SessionId") + if asp_session_id is not None: + new_cookies["ASP.NET_SessionId"] = asp_session_id.value session_v2: aiohttp.ClientSession = aiohttp.ClientSession( headers=REQUEST_HEADERS, - cookies=REQUEST_COOKIES, + cookies=new_cookies, ) async with session_v2, session_v2.post(REQUEST_URL, data=form_data) as http_response: - if http_response.headers.get("Content-Type") == "gzip": - response_html = await http_response.read() - else: - response_html = await http_response.text() + if http_response.status != 200: + await self.command_send_error( + ctx, + message="Returned a non-200 status code!!!.", + ) + logger.debug(http_response) + return + + response_html: str = await http_response.text() parsed_html: bs4.Tag | bs4.NavigableString | None = BeautifulSoup( response_html, @@ -93,16 +119,43 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext) -> None: logger.debug(http_response.request_info) return - await ctx.respond(parsed_html) + event_list: list[bs4.Tag] = parsed_html.find_all("tr") + + event_list.pop(0) + + event_ids: dict[str, str] = { + event.find("a").get("href").split("/")[5]: event.find("a").text # type: ignore[union-attr] + for event in event_list + } + + await ctx.respond(event_ids) + @discord.slash_command( # type: ignore[no-untyped-call, misc] name="get-events", description="Returns all events currently on the guild website.", ) + @discord.option( # type: ignore[no-untyped-call, misc] + name="from-date", + description="The date to start searching from.", + required=False, + input_type=str, + parameter_name="str_from_date", + ) + @discord.option( # type: ignore[no-untyped-call, misc] + name="to-date", + description="The date to stop searching at.", + required=False, + input_type=str, + parameter_name="str_to_date", + ) @CommandChecks.check_interaction_user_has_committee_role @CommandChecks.check_interaction_user_in_main_guild - async def get_events(self, ctx: TeXBotApplicationContext) -> None: + async def get_events(self, ctx: TeXBotApplicationContext, *, str_from_date: str, str_to_date: str) -> None: # noqa: E501 """Command to get the events on the guild website.""" + from_date_dt = datetime.strptime(str_from_date) + + await self._get_all_guild_events(ctx) From 50f238f37ada5f2eb03669a8c8df7d797e2ccd20 Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Fri, 2 Aug 2024 22:49:22 +0000 Subject: [PATCH 03/40] add deteutil --- poetry.lock | 2 +- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index c7c783e1..76b217e1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1903,4 +1903,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "247f394ba9db556f1db3e5cb3051a617618f4d74736eba9cea280f8cc4df43f8" +content-hash = "b46c3bd323839945f0fc4e10f91481fdd6750d1194ed8f54ff793d03f61f4ce6" diff --git a/pyproject.toml b/pyproject.toml index c3906975..de80582a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ python-logging-discord-handler = "^0.1" classproperties = {git = "https://github.com/hottwaj/classproperties.git"} asyncstdlib = "~3.12" setuptools = "^70.3" +python-dateutil = "^2.9.0" [tool.poetry.group.dev.dependencies] pre-commit = "^3.8" From 096db415d613a7edd4a5cc4f6633e61e56daa331 Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Fri, 2 Aug 2024 22:52:47 +0000 Subject: [PATCH 04/40] fix some stuff --- cogs/events.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index a939594d..34c1ac4e 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -153,10 +153,21 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: @CommandChecks.check_interaction_user_in_main_guild async def get_events(self, ctx: TeXBotApplicationContext, *, str_from_date: str, str_to_date: str) -> None: # noqa: E501 """Command to get the events on the guild website.""" - from_date_dt = datetime.strptime(str_from_date) + from_date_dt = dateutil.parser.parse(str_from_date) + to_date_dt = dateutil.parser.parse(str_to_date) + + if from_date_dt > to_date_dt: + await ctx.respond( + content=( + f":warning: Start date ({from_date_dt}) is after end date ({to_date_dt})." + ), + ) + + formatted_from_date = from_date_dt.strftime("%d/%m/%Y") + formatted_to_date = to_date_dt.strftime("%d/%m/%Y") - await self._get_all_guild_events(ctx) + await self._get_all_guild_events(ctx, formatted_from_date, formatted_to_date) From 6d4aad49673a6ab3825026914f414a688143af5c Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Fri, 2 Aug 2024 23:15:29 +0000 Subject: [PATCH 05/40] fix?? --- cogs/events.py | 1 - poetry.lock | 13 ++++++++++++- pyproject.toml | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index 34c1ac4e..5c8d042c 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -7,7 +7,6 @@ import logging from collections.abc import Mapping -from datetime import datetime from logging import Logger from typing import Final diff --git a/poetry.lock b/poetry.lock index 49761a9c..097866e3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1702,6 +1702,17 @@ files = [ {file = "types_html5lib-1.1.11.20240228-py3-none-any.whl", hash = "sha256:af5de0125cb0fe5667543b158db83849b22e25c0e36c9149836b095548bf1020"}, ] +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20240316" +description = "Typing stubs for python-dateutil" +optional = false +python-versions = ">=3.8" +files = [ + {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]] name = "types-pyyaml" version = "6.0.12.20240724" @@ -1903,4 +1914,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "b46c3bd323839945f0fc4e10f91481fdd6750d1194ed8f54ff793d03f61f4ce6" +content-hash = "256ef5a5b8a204254a1416a2b986876841ee8b12d6bfdc3a45828add7c350c2b" diff --git a/pyproject.toml b/pyproject.toml index de80582a..4ccef4f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,7 @@ ruff = "^0.5" gitpython = "^3.1" pymarkdownlnt = "^0.9" ccft-pymarkdown = "^1.1" +types-python-dateutil = "^2.9.0.20240316" From 89e349fd00aaa55a3c2db36b69f8d6295e0b0282 Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Fri, 2 Aug 2024 23:22:28 +0000 Subject: [PATCH 06/40] fix imports --- cogs/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/events.py b/cogs/events.py index 5c8d042c..5bf09dee 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -12,7 +12,7 @@ import aiohttp import bs4 -import dateutil +import dateutil.parser import discord from bs4 import BeautifulSoup From a6b2d193808e3676f094fe18d5b542a87fc66296 Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Fri, 2 Aug 2024 23:40:51 +0000 Subject: [PATCH 07/40] fix some stuff --- cogs/events.py | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index 5bf09dee..ae007c71 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -13,6 +13,7 @@ import aiohttp import bs4 import dateutil.parser +from dateutil.parser import ParserError import discord from bs4 import BeautifulSoup @@ -110,12 +111,14 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: ctx, message="Something went wrong and the event list could not be fetched.", ) - logger.debug(http_response) return if "There are no events" in str(parsed_html): - await ctx.respond("There are no events found for the date range selected.") - logger.debug(http_response.request_info) + await ctx.respond( + content=( + f"There are no events found for the date range: {from_date} to {to_date}." + ), + ) return event_list: list[bs4.Tag] = parsed_html.find_all("tr") @@ -127,7 +130,15 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: for event in event_list } - await ctx.respond(event_ids) + response_message: str = ( + f"Events from {from_date} to {to_date}:\n" + + "\n".join( + f"{event_id}: {event_name}" + for event_id, event_name in event_ids.items() + ) + ) + + await ctx.respond(content=response_message) @discord.slash_command( # type: ignore[no-untyped-call, misc] @@ -152,8 +163,23 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: @CommandChecks.check_interaction_user_in_main_guild async def get_events(self, ctx: TeXBotApplicationContext, *, str_from_date: str, str_to_date: str) -> None: # noqa: E501 """Command to get the events on the guild website.""" - from_date_dt = dateutil.parser.parse(str_from_date) - to_date_dt = dateutil.parser.parse(str_to_date) + try: + if str_from_date: + from_date_dt = dateutil.parser.parse(str_from_date, dayfirst=True) + else: + from_date_dt = dateutil.parser.parse("01/08/2024", dayfirst=True) + + if str_to_date: + to_date_dt = dateutil.parser.parse(str_to_date, dayfirst=True) + else: + to_date_dt = dateutil.parser.parse("31/07/2025", dayfirst=True) + except ParserError: + await ctx.respond( + content=( + ":warning: Invalid date format. Please use the format `dd/mm/yyyy`." + ), + ) + return if from_date_dt > to_date_dt: await ctx.respond( From 529dad16427dbe0d280a77acdd66d808993a67a3 Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Fri, 2 Aug 2024 23:41:08 +0000 Subject: [PATCH 08/40] org import --- cogs/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/events.py b/cogs/events.py index ae007c71..4225a915 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -13,9 +13,9 @@ import aiohttp import bs4 import dateutil.parser -from dateutil.parser import ParserError import discord from bs4 import BeautifulSoup +from dateutil.parser import ParserError from config import settings from utils import CommandChecks, TeXBotApplicationContext, TeXBotBaseCog From 5acd77852e0c0903342b9c51e98c24f40740d691 Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 3 Aug 2024 15:32:42 +0000 Subject: [PATCH 09/40] more --- cogs/events.py | 114 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 106 insertions(+), 8 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index 4225a915..cb0def06 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -8,7 +8,7 @@ import logging from collections.abc import Mapping from logging import Logger -from typing import Final +from typing import TYPE_CHECKING, Final import aiohttp import bs4 @@ -20,6 +20,10 @@ from config import settings from utils import CommandChecks, TeXBotApplicationContext, TeXBotBaseCog +if TYPE_CHECKING: + import datetime + from http.cookies import Morsel + logger: Final[Logger] = logging.getLogger("TeX-Bot") @@ -43,6 +47,17 @@ class EventsManagementCommandsCog(TeXBotBaseCog): """Cog class to define event management commands.""" + async def _get_msl_context(self, url: str) -> tuple[dict[str, str], dict[str, str]]: + """Get the required context headers, data and cookies to make a request to MSL.""" + http_session: aiohttp.ClientSession = aiohttp.ClientSession( + headers=REQUEST_HEADERS, + cookies=REQUEST_COOKIES, + ) + async with http_session, http_session.get(url) as field_data: + data_response = BeautifulSoup(await field_data.text(), "html.parser") + + return {}, {} + async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: str, to_date: str) -> None: # noqa: E501 """Fetch all events on the guild website.""" form_data: dict[str, str] = { @@ -53,6 +68,7 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: "__EVENTARGUMENT": "", "__VIEWSTATEENCRYPTED": "", } + http_session: aiohttp.ClientSession = aiohttp.ClientSession( headers=REQUEST_HEADERS, cookies=REQUEST_COOKIES, @@ -71,15 +87,15 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: ".ASPXAUTH": settings["MEMBERS_LIST_AUTH_SESSION_COOKIE"], } - anti_xss_cookie = field_data.cookies.get("__AntiXsrfToken") + anti_xss_cookie: Morsel[str] | None = field_data.cookies.get("__AntiXsrfToken") if anti_xss_cookie is not None: new_cookies["__AntiXsrfToken"] = anti_xss_cookie.value - asp_net_shared_cookie = field_data.cookies.get(".AspNet.SharedCookie") + asp_net_shared_cookie: Morsel[str] | None = field_data.cookies.get(".AspNet.SharedCookie") # noqa: E501 if asp_net_shared_cookie is not None: new_cookies[".AspNet.SharedCookie"] = asp_net_shared_cookie.value - asp_session_id = field_data.cookies.get("ASP.NET_SessionId") + asp_session_id: Morsel[str] | None = field_data.cookies.get("ASP.NET_SessionId") if asp_session_id is not None: new_cookies["ASP.NET_SessionId"] = asp_session_id.value @@ -98,7 +114,7 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: response_html: str = await http_response.text() - parsed_html: bs4.Tag | bs4.NavigableString | None = BeautifulSoup( + event_table_html: bs4.Tag | bs4.NavigableString | None = BeautifulSoup( response_html, "html.parser", ).find( @@ -106,14 +122,14 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: {"id": EVENT_TABLE_ID}, ) - if parsed_html is None or isinstance(parsed_html, bs4.NavigableString): + if event_table_html is None or isinstance(event_table_html, bs4.NavigableString): await self.command_send_error( ctx, message="Something went wrong and the event list could not be fetched.", ) return - if "There are no events" in str(parsed_html): + if "There are no events" in str(event_table_html): await ctx.respond( content=( f"There are no events found for the date range: {from_date} to {to_date}." @@ -121,7 +137,7 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: ) return - event_list: list[bs4.Tag] = parsed_html.find_all("tr") + event_list: list[bs4.Tag] = event_table_html.find_all("tr") event_list.pop(0) @@ -194,6 +210,88 @@ async def get_events(self, ctx: TeXBotApplicationContext, *, str_from_date: str, await self._get_all_guild_events(ctx, formatted_from_date, formatted_to_date) + @discord.slash_command( # type: ignore[no-untyped-call, misc] + name="create-event", + description="Sets up an event on the Guild website, Discord and Google Calendar.", + ) + @discord.option( # type: ignore[no-untyped-call, misc] + name="event-title", + description="The title of the event.", + required=True, + input_type=str, + parameter_name="str_event_title", + ) + @discord.option( # type: ignore[no-untyped-call, misc] + name="start-date", + description="The date the event starts. Must be in the format `dd/mm/yyyy`.", + required=True, + input_type=str, + parameter_name="str_start_date", + ) + @discord.option( # type: ignore[no-untyped-call, misc] + name="start-time", + description="The time the event starts. Must be in the format `hh:mm`.", + required=True, + input_type=str, + parameter_name="str_start_time", + ) + @discord.option( # type: ignore[no-untyped-call, misc] + name="end-date", + description="The date the event ends. Must be in the format `dd/mm/yyyy`.", + required=True, + input_type=str, + parameter_name="str_end_date", + ) + @discord.option( # type: ignore[no-untyped-call, misc] + name="end-time", + description="The time the event ends. Must be in the format `hh:mm`.", + required=True, + input_type=str, + parameter_name="str_end_time", + ) + @discord.option( # type: ignore[no-untyped-call, misc] + name="description", + description="A long description of the event.", + required=False, + input_type=str, + parameter_name="str_description", + ) + @CommandChecks.check_interaction_user_has_committee_role + @CommandChecks.check_interaction_user_in_main_guild + async def setup_event(self, ctx: TeXBotApplicationContext, str_event_title: str, str_start_date: str, str_start_time: str, str_end_date: str, str_end_time: str, *, str_description: str) -> None: # noqa: E501 + """ + Definition & callback response of the "delete_all_reminders" command. + + Takes in the title of the event, the start and end dates and times of the event. + Optionally takes a long description for the event. + """ + try: + start_date_dt: datetime.datetime = dateutil.parser.parse( + f"{str_start_date}T{str_start_time}", dayfirst=True, + ) + end_date_dt: datetime.datetime = dateutil.parser.parse( + f"{str_end_date}T{str_end_time}", dayfirst=True, + ) + except ParserError: + await ctx.respond( + content=( + ":warning: Invalid date format. " + "Please use the format `dd/mm/yyyy` for dates and `hh:mm` for times." + ), + ) + return + + if start_date_dt > end_date_dt: + await ctx.respond( + content=( + f":warning: Start dt ({start_date_dt}) " + f"is after end dt ({end_date_dt})." + ), + ) + return + + await ctx.respond(content=f"Got DTs: {start_date_dt} and {end_date_dt}.") + From c426d9743ae8f002081210b858aac2ff69e22a2b Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 3 Aug 2024 17:25:11 +0000 Subject: [PATCH 10/40] work --- cogs/events.py | 27 ++++- poetry.lock | 262 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 3 + 3 files changed, 289 insertions(+), 3 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index cb0def06..5107f26f 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -256,15 +256,23 @@ async def get_events(self, ctx: TeXBotApplicationContext, *, str_from_date: str, input_type=str, parameter_name="str_description", ) + @discord.option( # type: ignore[no-untyped-call, misc] + name="location", + description="The location of the event.", + required=False, + input_type=str, + parameter_name="str_location", + ) @CommandChecks.check_interaction_user_has_committee_role @CommandChecks.check_interaction_user_in_main_guild - async def setup_event(self, ctx: TeXBotApplicationContext, str_event_title: str, str_start_date: str, str_start_time: str, str_end_date: str, str_end_time: str, *, str_description: str) -> None: # noqa: E501 + async def setup_event(self, ctx: TeXBotApplicationContext, str_event_title: str, str_start_date: str, str_start_time: str, str_end_date: str, str_end_time: str, *, str_description: str, str_location: str) -> None: # noqa: E501, PLR0913 """ Definition & callback response of the "delete_all_reminders" command. Takes in the title of the event, the start and end dates and times of the event. Optionally takes a long description for the event. """ + main_guild: discord.Guild = self.bot.main_guild try: start_date_dt: datetime.datetime = dateutil.parser.parse( f"{str_start_date}T{str_start_time}", dayfirst=True, @@ -290,7 +298,22 @@ async def setup_event(self, ctx: TeXBotApplicationContext, str_event_title: str, ) return - await ctx.respond(content=f"Got DTs: {start_date_dt} and {end_date_dt}.") + try: + new_discord_event: discord.ScheduledEvent | None = await main_guild.create_scheduled_event( + name=str_event_title, + start_time=start_date_dt, + end_time=end_date_dt, + description=str_description if str_description else "No description provided.", + location=str_location if str_location else "No location provided.", + ) + except discord.Forbidden: + await self.command_send_error( + ctx=ctx, + message="TeX-Bot does not have the required permissions to create an event.", + ) + return + + await ctx.respond(f"Event created successful!\n{new_discord_event}") diff --git a/poetry.lock b/poetry.lock index 097866e3..a5157407 100644 --- a/poetry.lock +++ b/poetry.lock @@ -207,6 +207,17 @@ charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] +[[package]] +name = "cachetools" +version = "5.4.0" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"}, + {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"}, +] + [[package]] name = "ccft-pymarkdown" version = "1.1.2" @@ -766,6 +777,134 @@ gitdb = ">=4.0.1,<5" doc = ["sphinx (==4.3.2)", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "sphinxcontrib-applehelp (>=1.0.2,<=1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (>=2.0.0,<=2.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)"] test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] +[[package]] +name = "google-api-core" +version = "2.19.1" +description = "Google API client core library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-api-core-2.19.1.tar.gz", hash = "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd"}, + {file = "google_api_core-2.19.1-py3-none-any.whl", hash = "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125"}, +] + +[package.dependencies] +google-auth = ">=2.14.1,<3.0.dev0" +googleapis-common-protos = ">=1.56.2,<2.0.dev0" +proto-plus = ">=1.22.3,<2.0.0dev" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" +requests = ">=2.18.0,<3.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] + +[[package]] +name = "google-api-python-client" +version = "2.139.0" +description = "Google API Client Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google_api_python_client-2.139.0-py2.py3-none-any.whl", hash = "sha256:1850a92505d91a82e2ca1635ab2b8dff179f4b67082c2651e1db332e8039840c"}, + {file = "google_api_python_client-2.139.0.tar.gz", hash = "sha256:ed4bc3abe2c060a87412465b4e8254620bbbc548eefc5388e2c5ff912d36a68b"}, +] + +[package.dependencies] +google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0.dev0" +google-auth = ">=1.32.0,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0.dev0" +google-auth-httplib2 = ">=0.2.0,<1.0.0" +httplib2 = ">=0.19.0,<1.dev0" +uritemplate = ">=3.0.1,<5" + +[[package]] +name = "google-auth" +version = "2.32.0" +description = "Google Authentication Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google_auth-2.32.0-py2.py3-none-any.whl", hash = "sha256:53326ea2ebec768070a94bee4e1b9194c9646ea0c2bd72422785bd0f9abfad7b"}, + {file = "google_auth-2.32.0.tar.gz", hash = "sha256:49315be72c55a6a37d62819e3573f6b416aca00721f7e3e31a008d928bf64022"}, +] + +[package.dependencies] +cachetools = ">=2.0.0,<6.0" +pyasn1-modules = ">=0.2.1" +rsa = ">=3.1.4,<5" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.20.0,<3.0.0.dev0)"] + +[[package]] +name = "google-auth-httplib2" +version = "0.2.0" +description = "Google Authentication Library: httplib2 transport" +optional = false +python-versions = "*" +files = [ + {file = "google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05"}, + {file = "google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d"}, +] + +[package.dependencies] +google-auth = "*" +httplib2 = ">=0.19.0" + +[[package]] +name = "google-auth-oauthlib" +version = "1.2.1" +description = "Google Authentication Library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "google_auth_oauthlib-1.2.1-py2.py3-none-any.whl", hash = "sha256:2d58a27262d55aa1b87678c3ba7142a080098cbc2024f903c62355deb235d91f"}, + {file = "google_auth_oauthlib-1.2.1.tar.gz", hash = "sha256:afd0cad092a2eaa53cd8e8298557d6de1034c6cb4a740500b5357b648af97263"}, +] + +[package.dependencies] +google-auth = ">=2.15.0" +requests-oauthlib = ">=0.7.0" + +[package.extras] +tool = ["click (>=6.0.0)"] + +[[package]] +name = "googleapis-common-protos" +version = "1.63.2" +description = "Common protobufs used in Google APIs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87"}, + {file = "googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945"}, +] + +[package.dependencies] +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] + +[[package]] +name = "httplib2" +version = "0.22.0" +description = "A comprehensive HTTP client library." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"}, + {file = "httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81"}, +] + +[package.dependencies] +pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""} + [[package]] name = "identify" version = "2.6.0" @@ -1202,6 +1341,22 @@ files = [ {file = "numpy-2.0.1.tar.gz", hash = "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3"}, ] +[[package]] +name = "oauthlib" +version = "3.2.2" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +optional = false +python-versions = ">=3.6" +files = [ + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, +] + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + [[package]] name = "packaging" version = "24.1" @@ -1370,6 +1525,43 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "proto-plus" +version = "1.24.0" +description = "Beautiful, Pythonic protocol buffers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "proto-plus-1.24.0.tar.gz", hash = "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445"}, + {file = "proto_plus-1.24.0-py3-none-any.whl", hash = "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12"}, +] + +[package.dependencies] +protobuf = ">=3.19.0,<6.0.0dev" + +[package.extras] +testing = ["google-api-core (>=1.31.5)"] + +[[package]] +name = "protobuf" +version = "5.27.3" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "protobuf-5.27.3-cp310-abi3-win32.whl", hash = "sha256:dcb307cd4ef8fec0cf52cb9105a03d06fbb5275ce6d84a6ae33bc6cf84e0a07b"}, + {file = "protobuf-5.27.3-cp310-abi3-win_amd64.whl", hash = "sha256:16ddf3f8c6c41e1e803da7abea17b1793a97ef079a912e42351eabb19b2cffe7"}, + {file = "protobuf-5.27.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:68248c60d53f6168f565a8c76dc58ba4fa2ade31c2d1ebdae6d80f969cdc2d4f"}, + {file = "protobuf-5.27.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:b8a994fb3d1c11156e7d1e427186662b64694a62b55936b2b9348f0a7c6625ce"}, + {file = "protobuf-5.27.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:a55c48f2a2092d8e213bd143474df33a6ae751b781dd1d1f4d953c128a415b25"}, + {file = "protobuf-5.27.3-cp38-cp38-win32.whl", hash = "sha256:043853dcb55cc262bf2e116215ad43fa0859caab79bb0b2d31b708f128ece035"}, + {file = "protobuf-5.27.3-cp38-cp38-win_amd64.whl", hash = "sha256:c2a105c24f08b1e53d6c7ffe69cb09d0031512f0b72f812dd4005b8112dbe91e"}, + {file = "protobuf-5.27.3-cp39-cp39-win32.whl", hash = "sha256:c84eee2c71ed83704f1afbf1a85c3171eab0fd1ade3b399b3fad0884cbcca8bf"}, + {file = "protobuf-5.27.3-cp39-cp39-win_amd64.whl", hash = "sha256:af7c0b7cfbbb649ad26132e53faa348580f844d9ca46fd3ec7ca48a1ea5db8a1"}, + {file = "protobuf-5.27.3-py3-none-any.whl", hash = "sha256:8572c6533e544ebf6899c360e91d6bcbbee2549251643d32c52cf8a5de295ba5"}, + {file = "protobuf-5.27.3.tar.gz", hash = "sha256:82460903e640f2b7e34ee81a947fdaad89de796d324bcbc38ff5430bcdead82c"}, +] + [[package]] name = "py-cord" version = "2.6.0" @@ -1389,6 +1581,31 @@ docs = ["furo (==2023.3.23)", "myst-parser (==1.0.0)", "sphinx (==5.3.0)", "sphi speed = ["aiohttp[speedups]", "msgspec (>=0.18.6,<0.19.0)"] voice = ["PyNaCl (>=1.3.0,<1.6)"] +[[package]] +name = "pyasn1" +version = "0.6.0" +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, + {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.0" +description = "A collection of ASN.1-based protocols modules" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"}, + {file = "pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6"}, +] + +[package.dependencies] +pyasn1 = ">=0.4.6,<0.7.0" + [[package]] name = "pymarkdownlnt" version = "0.9.21" @@ -1565,6 +1782,38 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +description = "OAuthlib authentication support for Requests." +optional = false +python-versions = ">=3.4" +files = [ + {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, + {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, +] + +[package.dependencies] +oauthlib = ">=3.0.0" +requests = ">=2.0.0" + +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + [[package]] name = "ruff" version = "0.5.6" @@ -1746,6 +1995,17 @@ files = [ {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, ] +[[package]] +name = "uritemplate" +version = "4.1.1" +description = "Implementation of RFC 6570 URI Templates" +optional = false +python-versions = ">=3.6" +files = [ + {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, + {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, +] + [[package]] name = "urllib3" version = "2.2.2" @@ -1914,4 +2174,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "256ef5a5b8a204254a1416a2b986876841ee8b12d6bfdc3a45828add7c350c2b" +content-hash = "4b309aeafdc50abb154335b7b77279eab569606317ca422b72b484e8abdf5ace" diff --git a/pyproject.toml b/pyproject.toml index 4ccef4f5..98e2d3db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,9 @@ classproperties = {git = "https://github.com/hottwaj/classproperties.git"} asyncstdlib = "~3.12" setuptools = "^70.3" python-dateutil = "^2.9.0" +google-api-python-client = "^2.139.0" +google-auth-httplib2 = "^0.2.0" +google-auth-oauthlib = "^1.2.1" [tool.poetry.group.dev.dependencies] pre-commit = "^3.8" From f3e4fb53717bfe5cf23d1f840afa9344bff92e3e Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 3 Aug 2024 17:26:07 +0000 Subject: [PATCH 11/40] update attrs --- poetry.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/poetry.lock b/poetry.lock index a5157407..5ad77a51 100644 --- a/poetry.lock +++ b/poetry.lock @@ -169,22 +169,22 @@ typetest = ["mypy", "pyright", "typing-extensions"] [[package]] name = "attrs" -version = "23.2.0" +version = "24.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, + {file = "attrs-24.1.0-py3-none-any.whl", hash = "sha256:377b47448cb61fea38533f671fba0d0f8a96fd58facd4dc518e3dac9dbea0905"}, + {file = "attrs-24.1.0.tar.gz", hash = "sha256:adbdec84af72d38be7628e353a09b6a6790d15cd71819f6e9d7b0faa8a125745"}, ] [package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -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-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "beautifulsoup4" From eb8bbe94817ca3f71dc4f0e42ada6903f9eae80c Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 3 Aug 2024 18:20:18 +0000 Subject: [PATCH 12/40] work on gcal --- cogs/events.py | 2 +- poetry.lock | 29 ++++++++++++++++++++++++++++- pyproject.toml | 2 ++ utils/gcal.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 utils/gcal.py diff --git a/cogs/events.py b/cogs/events.py index 5107f26f..b9d6ab88 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -299,7 +299,7 @@ async def setup_event(self, ctx: TeXBotApplicationContext, str_event_title: str, return try: - new_discord_event: discord.ScheduledEvent | None = await main_guild.create_scheduled_event( + new_discord_event: discord.ScheduledEvent | None = await main_guild.create_scheduled_event( # noqa: E501 name=str_event_title, start_time=start_date_dt, end_time=end_date_dt, diff --git a/poetry.lock b/poetry.lock index 5ad77a51..e988c72e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -818,6 +818,22 @@ google-auth-httplib2 = ">=0.2.0,<1.0.0" httplib2 = ">=0.19.0,<1.dev0" uritemplate = ">=3.0.1,<5" +[[package]] +name = "google-api-python-client-stubs" +version = "1.26.0" +description = "Type stubs for google-api-python-client" +optional = false +python-versions = "<4.0,>=3.7" +files = [ + {file = "google_api_python_client_stubs-1.26.0-py3-none-any.whl", hash = "sha256:0614b0cef5beac43e6ab02418f07e64ee66dc99ae4e377d54a155ac261533987"}, + {file = "google_api_python_client_stubs-1.26.0.tar.gz", hash = "sha256:f3b38b46f7b5cf4f6e7cc63ca554a2d23096d49c841f38b9ea553a5237074b56"}, +] + +[package.dependencies] +google-api-python-client = ">=2.130.0" +types-httplib2 = ">=0.22.0.2" +typing-extensions = ">=3.10.0" + [[package]] name = "google-auth" version = "2.32.0" @@ -1951,6 +1967,17 @@ files = [ {file = "types_html5lib-1.1.11.20240228-py3-none-any.whl", hash = "sha256:af5de0125cb0fe5667543b158db83849b22e25c0e36c9149836b095548bf1020"}, ] +[[package]] +name = "types-httplib2" +version = "0.22.0.20240310" +description = "Typing stubs for httplib2" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-httplib2-0.22.0.20240310.tar.gz", hash = "sha256:1eda99fea18ec8a1dc1a725ead35b889d0836fec1b11ae6f1fe05440724c1d15"}, + {file = "types_httplib2-0.22.0.20240310-py3-none-any.whl", hash = "sha256:8cd706fc81f0da32789a4373a28df6f39e9d5657d1281db4d2fd22ee29e83661"}, +] + [[package]] name = "types-python-dateutil" version = "2.9.0.20240316" @@ -2174,4 +2201,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "4b309aeafdc50abb154335b7b77279eab569606317ca422b72b484e8abdf5ace" +content-hash = "663a033ec3ad4f3925342efaeb4fae0e370e3af73c1cd807e1ec9b62f2237e1c" diff --git a/pyproject.toml b/pyproject.toml index 98e2d3db..f7da929b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,6 +58,7 @@ gitpython = "^3.1" pymarkdownlnt = "^0.9" ccft-pymarkdown = "^1.1" types-python-dateutil = "^2.9.0.20240316" +google-api-python-client-stubs = "^1.26.0" @@ -93,6 +94,7 @@ module = [ "discord_logging.handler", "parsedatetime", "validators", + "google_auth_oauthlib", ] ignore_missing_imports = true diff --git a/utils/gcal.py b/utils/gcal.py new file mode 100644 index 00000000..bb4ec391 --- /dev/null +++ b/utils/gcal.py @@ -0,0 +1,42 @@ +"""Functions to enable interaction with Google Calendar API.""" + +from collections.abc import Sequence + +__all__: Sequence[str] = ("",) + + +import datetime +import os.path +from typing import Final + +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials +from google_auth_oauthlib.flow import InstalledAppFlow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError + +SCOPES: Final[Sequence[str]] = ["https://www.googleapis.com/auth/calendar.readonly"] + + +class GoogleCalendar: + + async def fetch_credentials(self) -> Credentials: + """Fetch the credentials for the Google Calendar API.""" + credentials: Credentials | None = None + + if os.path.exists("token.json"): + credentials = Credentials.from_authorized_user_file("token.json", SCOPES) + + if not credentials or not credentials.valid or (credentials.expired and credentials.refresh_token): + if credentials.expired and credentials.refresh_token: + credentials.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES) + credentials = flow.run_local_server(port=0) + + with open("token.json", "w") as token: + token.write(credentials.to_json()) + + return credentials + + From ff99c5e9771a2a4c182729b8ec5f42776a8aa05b Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 3 Aug 2024 19:00:08 +0000 Subject: [PATCH 13/40] do some shit --- poetry.lock | 33 ++++++++++++++++++++++++++++++- pyproject.toml | 3 ++- utils/__init__.py | 2 ++ utils/gcal.py | 49 +++++++++++++++++++++++++++++++++++------------ 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/poetry.lock b/poetry.lock index e988c72e..791b8acb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -121,6 +121,26 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "anyio" +version = "4.4.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + [[package]] name = "application-properties" version = "0.8.2" @@ -1894,6 +1914,17 @@ files = [ {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, ] +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + [[package]] name = "soupsieve" version = "2.5" @@ -2201,4 +2232,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "663a033ec3ad4f3925342efaeb4fae0e370e3af73c1cd807e1ec9b62f2237e1c" +content-hash = "dc68165b16bcf0aed05cbd470a31951e197521f4034df4c0fb4507ed03a48ab9" diff --git a/pyproject.toml b/pyproject.toml index f7da929b..f6039a83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ python-dateutil = "^2.9.0" google-api-python-client = "^2.139.0" google-auth-httplib2 = "^0.2.0" google-auth-oauthlib = "^1.2.1" +anyio = "^4.4.0" [tool.poetry.group.dev.dependencies] pre-commit = "^3.8" @@ -94,7 +95,7 @@ module = [ "discord_logging.handler", "parsedatetime", "validators", - "google_auth_oauthlib", + "google_auth_oauthlib.flow", ] ignore_missing_imports = true diff --git a/utils/__init__.py b/utils/__init__.py index 4f536d2d..46b51d89 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -14,6 +14,7 @@ "generate_invite_url", "is_member_inducted", "is_running_in_async", + "GoogleCalendar", ) @@ -23,6 +24,7 @@ import discord from .command_checks import CommandChecks +from .gcal import GoogleCalendar from .message_sender_components import MessageSavingSenderComponent from .suppress_traceback import SuppressTraceback from .tex_bot import TeXBot diff --git a/utils/gcal.py b/utils/gcal.py index bb4ec391..f6ce8df6 100644 --- a/utils/gcal.py +++ b/utils/gcal.py @@ -2,13 +2,16 @@ from collections.abc import Sequence -__all__: Sequence[str] = ("",) +__all__: Sequence[str] = ("GoogleCalendar",) import datetime -import os.path +import logging +from logging import Logger +from pathlib import Path from typing import Final +import anyio from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials from google_auth_oauthlib.flow import InstalledAppFlow @@ -17,26 +20,48 @@ SCOPES: Final[Sequence[str]] = ["https://www.googleapis.com/auth/calendar.readonly"] +logger: Final[Logger] = logging.getLogger("TeX-Bot") class GoogleCalendar: + """Class to define the Google Calendar API.""" - async def fetch_credentials(self) -> Credentials: + async def fetch_credentials(self) -> Credentials | None: """Fetch the credentials for the Google Calendar API.""" credentials: Credentials | None = None - if os.path.exists("token.json"): - credentials = Credentials.from_authorized_user_file("token.json", SCOPES) - - if not credentials or not credentials.valid or (credentials.expired and credentials.refresh_token): - if credentials.expired and credentials.refresh_token: - credentials.refresh(Request()) + if Path("token.json").exists(): + credentials = Credentials.from_authorized_user_file("token.json", SCOPES) # type: ignore[no-untyped-call] + + if not credentials or not credentials.valid: + if credentials and credentials.expired and credentials.refresh_token: + credentials.refresh(Request()) # type: ignore[no-untyped-call] else: flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES) credentials = flow.run_local_server(port=0) - - with open("token.json", "w") as token: - token.write(credentials.to_json()) + + if not credentials: + return None + + async with await anyio.open_file("token.json") as token: + await token.write(credentials.to_json()) # type: ignore[no-untyped-call] return credentials + async def fetch_events(self) -> list[dict[str, str]]: + """Fetch the events from the Google Calendar API.""" + credentials: Credentials | None = await self.fetch_credentials() + if not credentials: + return [] + + try: + service: CalendarResource = build("calendar", "v3", credentials=credentials) + + now = datetime.datetime.utcnow().isoformat() + "Z" + + except HttpError as error: + return [] + + + return [] + From 75850600aaad9b8e6d17c4ce70877759f1a0e173 Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 3 Aug 2024 19:31:41 +0000 Subject: [PATCH 14/40] Update gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 0aa74ee9..c5e22d25 100644 --- a/.gitignore +++ b/.gitignore @@ -147,3 +147,5 @@ local/ *.sqlite3.bak *.db.bak local_stubs/ +credentials.json +token.json From 031c5bda7f29fb0d58689d6df5fc068cea073250 Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 3 Aug 2024 19:42:30 +0000 Subject: [PATCH 15/40] more --- cogs/events.py | 13 ++++++++----- utils/gcal.py | 36 ++++++++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index b9d6ab88..8aa6a4c5 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -18,7 +18,7 @@ from dateutil.parser import ParserError from config import settings -from utils import CommandChecks, TeXBotApplicationContext, TeXBotBaseCog +from utils import CommandChecks, TeXBotApplicationContext, TeXBotBaseCog, GoogleCalendar if TYPE_CHECKING: import datetime @@ -147,7 +147,7 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: } response_message: str = ( - f"Events from {from_date} to {to_date}:\n" + f"Guild events from {from_date} to {to_date}:\n" + "\n".join( f"{event_id}: {event_name}" for event_id, event_name in event_ids.items() @@ -204,12 +204,15 @@ async def get_events(self, ctx: TeXBotApplicationContext, *, str_from_date: str, ), ) - formatted_from_date = from_date_dt.strftime("%d/%m/%Y") - formatted_to_date = to_date_dt.strftime("%d/%m/%Y") - + formatted_from_date: str = from_date_dt.strftime("%d/%m/%Y") + formatted_to_date: str = to_date_dt.strftime("%d/%m/%Y") await self._get_all_guild_events(ctx, formatted_from_date, formatted_to_date) + events = await GoogleCalendar.fetch_events() + + await ctx.send(content=f"Found GCal events: {events}") + @discord.slash_command( # type: ignore[no-untyped-call, misc] name="create-event", description="Sets up an event on the Guild website, Discord and Google Calendar.", diff --git a/utils/gcal.py b/utils/gcal.py index f6ce8df6..80991ab0 100644 --- a/utils/gcal.py +++ b/utils/gcal.py @@ -25,7 +25,8 @@ class GoogleCalendar: """Class to define the Google Calendar API.""" - async def fetch_credentials(self) -> Credentials | None: + @staticmethod + async def fetch_credentials() -> Credentials | None: """Fetch the credentials for the Google Calendar API.""" credentials: Credentials | None = None @@ -47,21 +48,36 @@ async def fetch_credentials(self) -> Credentials | None: return credentials - async def fetch_events(self) -> list[dict[str, str]]: + @staticmethod + async def fetch_events() -> list[dict[str, str]]: """Fetch the events from the Google Calendar API.""" - credentials: Credentials | None = await self.fetch_credentials() + credentials: Credentials | None = await GoogleCalendar.fetch_credentials() if not credentials: - return [] + return None try: - service: CalendarResource = build("calendar", "v3", credentials=credentials) - - now = datetime.datetime.utcnow().isoformat() + "Z" + service = build(serviceName="calendar", version="v3", credentials=credentials) + + now: str = datetime.datetime.now().isoformat() + "Z" + + events = ( + service.events().list( + calendarId="primary", + timeMin=now, + maxResults=10, + singleEvents=True, + orderBy="startTime", + ) + .execute() + ) + + if not events: + return None + + return events except HttpError as error: - return [] - + return None - return [] From a3a693fea53a787b8768607241ddf819989cfc1e Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 3 Aug 2024 20:02:20 +0000 Subject: [PATCH 16/40] tmp --- utils/gcal.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/utils/gcal.py b/utils/gcal.py index 80991ab0..922f4aee 100644 --- a/utils/gcal.py +++ b/utils/gcal.py @@ -49,7 +49,7 @@ async def fetch_credentials() -> Credentials | None: return credentials @staticmethod - async def fetch_events() -> list[dict[str, str]]: + async def fetch_events() -> list[Event] | None: """Fetch the events from the Google Calendar API.""" credentials: Credentials | None = await GoogleCalendar.fetch_credentials() if not credentials: @@ -74,10 +74,11 @@ async def fetch_events() -> list[dict[str, str]]: if not events: return None - return events + event_details: list[dict[str, str]] + for event in events: + logger.debug(event) + + return events.get("items", []) except HttpError as error: return None - - - From 2b3ac6dd32e657473b53237d189fcffea6d9f211 Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 3 Aug 2024 20:07:10 +0000 Subject: [PATCH 17/40] fuck --- utils/gcal.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/utils/gcal.py b/utils/gcal.py index 922f4aee..19bd64af 100644 --- a/utils/gcal.py +++ b/utils/gcal.py @@ -9,7 +9,7 @@ import logging from logging import Logger from pathlib import Path -from typing import Final +from typing import Final, TYPE_CHECKING import anyio from google.auth.transport.requests import Request @@ -18,6 +18,10 @@ from googleapiclient.discovery import build from googleapiclient.errors import HttpError +if TYPE_CHECKING: + from googleapiclient._apis.calendar.v3.schemas import Events + + SCOPES: Final[Sequence[str]] = ["https://www.googleapis.com/auth/calendar.readonly"] logger: Final[Logger] = logging.getLogger("TeX-Bot") @@ -60,7 +64,7 @@ async def fetch_events() -> list[Event] | None: now: str = datetime.datetime.now().isoformat() + "Z" - events = ( + events: Events = ( service.events().list( calendarId="primary", timeMin=now, @@ -74,11 +78,11 @@ async def fetch_events() -> list[Event] | None: if not events: return None - event_details: list[dict[str, str]] for event in events: logger.debug(event) return events.get("items", []) except HttpError as error: + logger.error(error) return None From c059608c30ea400dc724e245ad7dba3e643a5493 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 3 Aug 2024 22:47:56 +0100 Subject: [PATCH 18/40] the most scuffed code in existance --- cogs/events.py | 2 +- utils/gcal.py | 45 +++++++++++++++++++++++++++++++++------------ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index 8aa6a4c5..e931158f 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -209,7 +209,7 @@ async def get_events(self, ctx: TeXBotApplicationContext, *, str_from_date: str, await self._get_all_guild_events(ctx, formatted_from_date, formatted_to_date) - events = await GoogleCalendar.fetch_events() + events: list[dict[str, str]] | None = await GoogleCalendar.fetch_events() await ctx.send(content=f"Found GCal events: {events}") diff --git a/utils/gcal.py b/utils/gcal.py index 19bd64af..278a84c2 100644 --- a/utils/gcal.py +++ b/utils/gcal.py @@ -5,6 +5,7 @@ __all__: Sequence[str] = ("GoogleCalendar",) +import dateutil.parser import datetime import logging from logging import Logger @@ -19,8 +20,8 @@ from googleapiclient.errors import HttpError if TYPE_CHECKING: - from googleapiclient._apis.calendar.v3.schemas import Events - + from googleapiclient._apis.calendar.v3.schemas import Events # type: ignore + from googleapiclient._apis.calendar.v3.resources import CalendarResource # type: ignore SCOPES: Final[Sequence[str]] = ["https://www.googleapis.com/auth/calendar.readonly"] @@ -36,37 +37,46 @@ async def fetch_credentials() -> Credentials | None: if Path("token.json").exists(): credentials = Credentials.from_authorized_user_file("token.json", SCOPES) # type: ignore[no-untyped-call] + logger.debug("Credentials loaded from token.json") - if not credentials or not credentials.valid: + if not credentials or not credentials.token_state.FRESH: if credentials and credentials.expired and credentials.refresh_token: credentials.refresh(Request()) # type: ignore[no-untyped-call] + logger.debug("Credentials refreshed") else: - flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES) + flow: InstalledAppFlow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES) credentials = flow.run_local_server(port=0) + logger.debug("Attempted to fetch credentials") if not credentials: return None - async with await anyio.open_file("token.json") as token: - await token.write(credentials.to_json()) # type: ignore[no-untyped-call] + try: + async with await anyio.open_file("token.json") as token: + await token.write(credentials.to_json()) # type: ignore[no-untyped-call] + except Exception as error: + logger.error("Failed to write credentials to token.json") + logger.debug(error.args) + logger.debug(error.with_traceback) + return None return credentials @staticmethod - async def fetch_events() -> list[Event] | None: + async def fetch_events() -> list[dict[str, str]] | None: """Fetch the events from the Google Calendar API.""" credentials: Credentials | None = await GoogleCalendar.fetch_credentials() if not credentials: return None try: - service = build(serviceName="calendar", version="v3", credentials=credentials) + service: CalendarResource = build(serviceName="calendar", version="v3", credentials=credentials) now: str = datetime.datetime.now().isoformat() + "Z" events: Events = ( service.events().list( - calendarId="primary", + calendarId="kg5v9k480jn2qahpmq33h8g7cs@group.calendar.google.com", timeMin=now, maxResults=10, singleEvents=True, @@ -78,10 +88,21 @@ async def fetch_events() -> list[Event] | None: if not events: return None - for event in events: - logger.debug(event) + events_list = events.get("items", []) + + if not events_list: + return None + + formatted_events: list[dict[str, str]] = [{ + "event_id": event["id"], + "event_title": event["summary"], + "start_dt": str(event["start"]), + "end_dt": str(event["end"]), + } + for event in events_list + ] - return events.get("items", []) + return formatted_events except HttpError as error: logger.error(error) From 5fa5e564eb037997c9b5acffa1daf971f695279b Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 3 Aug 2024 22:55:35 +0100 Subject: [PATCH 19/40] some fixes --- cogs/events.py | 2 +- utils/gcal.py | 30 ++++++++++++++++++------------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index e931158f..f976d530 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -18,7 +18,7 @@ from dateutil.parser import ParserError from config import settings -from utils import CommandChecks, TeXBotApplicationContext, TeXBotBaseCog, GoogleCalendar +from utils import CommandChecks, GoogleCalendar, TeXBotApplicationContext, TeXBotBaseCog if TYPE_CHECKING: import datetime diff --git a/utils/gcal.py b/utils/gcal.py index 278a84c2..55a46f51 100644 --- a/utils/gcal.py +++ b/utils/gcal.py @@ -5,12 +5,11 @@ __all__: Sequence[str] = ("GoogleCalendar",) -import dateutil.parser import datetime import logging from logging import Logger from pathlib import Path -from typing import Final, TYPE_CHECKING +from typing import TYPE_CHECKING, Final import anyio from google.auth.transport.requests import Request @@ -20,8 +19,8 @@ from googleapiclient.errors import HttpError if TYPE_CHECKING: - from googleapiclient._apis.calendar.v3.schemas import Events # type: ignore - from googleapiclient._apis.calendar.v3.resources import CalendarResource # type: ignore + from googleapiclient._apis.calendar.v3.resources import CalendarResource # type: ignore[reportMissingModuleSource] # noqa: I001 + from googleapiclient._apis.calendar.v3.schemas import Events # type: ignore[reportMissingModuleSource] SCOPES: Final[Sequence[str]] = ["https://www.googleapis.com/auth/calendar.readonly"] @@ -44,7 +43,9 @@ async def fetch_credentials() -> Credentials | None: credentials.refresh(Request()) # type: ignore[no-untyped-call] logger.debug("Credentials refreshed") else: - flow: InstalledAppFlow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES) + flow: InstalledAppFlow = InstalledAppFlow.from_client_secrets_file( + client_secrets_file="credentials.json", scopes=SCOPES, + ) credentials = flow.run_local_server(port=0) logger.debug("Attempted to fetch credentials") @@ -55,7 +56,7 @@ async def fetch_credentials() -> Credentials | None: async with await anyio.open_file("token.json") as token: await token.write(credentials.to_json()) # type: ignore[no-untyped-call] except Exception as error: - logger.error("Failed to write credentials to token.json") + logger.exception("Failed to write credentials to token.json") logger.debug(error.args) logger.debug(error.with_traceback) return None @@ -70,9 +71,13 @@ async def fetch_events() -> list[dict[str, str]] | None: return None try: - service: CalendarResource = build(serviceName="calendar", version="v3", credentials=credentials) + service: CalendarResource = build( + serviceName="calendar", + version="v3", + credentials=credentials, + ) - now: str = datetime.datetime.now().isoformat() + "Z" + now: str = datetime.datetime.now(tz=datetime.UTC).isoformat() + "Z" events: Events = ( service.events().list( @@ -87,7 +92,7 @@ async def fetch_events() -> list[dict[str, str]] | None: if not events: return None - + events_list = events.get("items", []) if not events_list: @@ -102,8 +107,9 @@ async def fetch_events() -> list[dict[str, str]] | None: for event in events_list ] - return formatted_events - except HttpError as error: - logger.error(error) + logger.debug(error) return None + + else: + return formatted_events From 65113f1dc81cd0542631d4c5b8b77a525ff1a19f Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 3 Aug 2024 22:59:26 +0100 Subject: [PATCH 20/40] test --- utils/gcal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/gcal.py b/utils/gcal.py index 55a46f51..a8a395cf 100644 --- a/utils/gcal.py +++ b/utils/gcal.py @@ -19,8 +19,8 @@ from googleapiclient.errors import HttpError if TYPE_CHECKING: - from googleapiclient._apis.calendar.v3.resources import CalendarResource # type: ignore[reportMissingModuleSource] # noqa: I001 - from googleapiclient._apis.calendar.v3.schemas import Events # type: ignore[reportMissingModuleSource] + from googleapiclient._apis.calendar.v3.resources import CalendarResource + from googleapiclient._apis.calendar.v3.schemas import Events SCOPES: Final[Sequence[str]] = ["https://www.googleapis.com/auth/calendar.readonly"] From d6ac0063258a158f124096dede73fcb5006255b2 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Sun, 4 Aug 2024 17:04:00 +0100 Subject: [PATCH 21/40] fixes --- cogs/events.py | 109 +++++++++++++++++++++++++++---------------------- utils/gcal.py | 1 + 2 files changed, 62 insertions(+), 48 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index f976d530..5aabcbd6 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -27,17 +27,17 @@ logger: Final[Logger] = logging.getLogger("TeX-Bot") -REQUEST_HEADERS: Final[Mapping[str, str]] = { +BASE_HEADERS: Final[Mapping[str, str]] = { "Cache-Control": "no-cache", "Pragma": "no-cache", "Expires": "0", } -REQUEST_COOKIES: Final[Mapping[str, str]] = { +BASE_COOKIES: Final[Mapping[str, str]] = { ".ASPXAUTH": settings["MEMBERS_LIST_AUTH_SESSION_COOKIE"], } -REQUEST_URL: Final[str] = "https://www.guildofstudents.com/events/edit/6531/" +EVENT_LIST_URL: Final[str] = "https://www.guildofstudents.com/events/edit/6531/" FROM_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$datesFilter$txtFromDate" TO_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$datesFilter$txtToDate" BUTTON_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$fsSetDates$btnSubmit" @@ -50,16 +50,33 @@ class EventsManagementCommandsCog(TeXBotBaseCog): async def _get_msl_context(self, url: str) -> tuple[dict[str, str], dict[str, str]]: """Get the required context headers, data and cookies to make a request to MSL.""" http_session: aiohttp.ClientSession = aiohttp.ClientSession( - headers=REQUEST_HEADERS, - cookies=REQUEST_COOKIES, + headers=BASE_HEADERS, + cookies=BASE_COOKIES, ) - async with http_session, http_session.get(url) as field_data: - data_response = BeautifulSoup(await field_data.text(), "html.parser") + data_fields: dict[str, str] = {} + cookies: dict[str ,str] = {} + async with http_session, http_session.get(url=url) as field_data: + data_response = BeautifulSoup( + markup=await field_data.text(), + features="html.parser", + ) + + for field in data_response.find_all(name="input"): + if field.get("name") and field.get("value"): + data_fields[field.get("name")] = field.get("value") + + for cookie in field_data.cookies: + cookie_morsel: Morsel[str] | None = field_data.cookies.get(cookie) + if cookie_morsel is not None: + cookies[cookie] = cookie_morsel.value + cookies[".ASPXAUTH"] = settings["MEMBERS_LIST_AUTH_SESSION_COOKIE"] - return {}, {} + return data_fields, cookies async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: str, to_date: str) -> None: # noqa: E501 """Fetch all events on the guild website.""" + data_fields, new_cookies = await self._get_msl_context(url=EVENT_LIST_URL) + form_data: dict[str, str] = { FROM_DATE_KEY: from_date, TO_DATE_KEY: to_date, @@ -69,44 +86,16 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: "__VIEWSTATEENCRYPTED": "", } - http_session: aiohttp.ClientSession = aiohttp.ClientSession( - headers=REQUEST_HEADERS, - cookies=REQUEST_COOKIES, - ) - async with http_session, http_session.get(REQUEST_URL) as field_data: - data_response = BeautifulSoup(await field_data.text(), "html.parser") - view_state: str = data_response.find("input", {"name": "__VIEWSTATE"}).get("value") # type: ignore[union-attr, assignment] - event_validation: str = data_response.find("input", {"name": "__EVENTVALIDATION"}).get("value") # type: ignore[union-attr, assignment] # noqa: E501 - view_state_generator: str = data_response.find("input", {"name": "__VIEWSTATEGENERATOR"}).get("value") # type: ignore[union-attr, assignment] # noqa: E501 - - form_data["__VIEWSTATE"] = view_state - form_data["__EVENTVALIDATION"] = event_validation - form_data["__VIEWSTATEGENERATOR"] = view_state_generator - - new_cookies: dict[str, str] = { - ".ASPXAUTH": settings["MEMBERS_LIST_AUTH_SESSION_COOKIE"], - } - - anti_xss_cookie: Morsel[str] | None = field_data.cookies.get("__AntiXsrfToken") - if anti_xss_cookie is not None: - new_cookies["__AntiXsrfToken"] = anti_xss_cookie.value - - asp_net_shared_cookie: Morsel[str] | None = field_data.cookies.get(".AspNet.SharedCookie") # noqa: E501 - if asp_net_shared_cookie is not None: - new_cookies[".AspNet.SharedCookie"] = asp_net_shared_cookie.value - - asp_session_id: Morsel[str] | None = field_data.cookies.get("ASP.NET_SessionId") - if asp_session_id is not None: - new_cookies["ASP.NET_SessionId"] = asp_session_id.value + data_fields.update(form_data) session_v2: aiohttp.ClientSession = aiohttp.ClientSession( - headers=REQUEST_HEADERS, + headers=BASE_HEADERS, cookies=new_cookies, ) - async with session_v2, session_v2.post(REQUEST_URL, data=form_data) as http_response: + async with session_v2, session_v2.post(url=EVENT_LIST_URL, data=data_fields) as http_response: if http_response.status != 200: await self.command_send_error( - ctx, + ctx=ctx, message="Returned a non-200 status code!!!.", ) logger.debug(http_response) @@ -115,16 +104,16 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: response_html: str = await http_response.text() event_table_html: bs4.Tag | bs4.NavigableString | None = BeautifulSoup( - response_html, - "html.parser", + markup=response_html, + features="html.parser", ).find( - "table", - {"id": EVENT_TABLE_ID}, + name="table", + attrs={"id": EVENT_TABLE_ID}, ) if event_table_html is None or isinstance(event_table_html, bs4.NavigableString): await self.command_send_error( - ctx, + ctx=ctx, message="Something went wrong and the event list could not be fetched.", ) return @@ -135,14 +124,15 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: f"There are no events found for the date range: {from_date} to {to_date}." ), ) + logger.debug(event_table_html) return - event_list: list[bs4.Tag] = event_table_html.find_all("tr") + event_list: list[bs4.Tag] = event_table_html.find_all(name="tr") event_list.pop(0) event_ids: dict[str, str] = { - event.find("a").get("href").split("/")[5]: event.find("a").text # type: ignore[union-attr] + event.find(name="a").get("href").split("/")[5]: event.find(name="a").text # type: ignore[union-attr] for event in event_list } @@ -319,5 +309,28 @@ async def setup_event(self, ctx: TeXBotApplicationContext, str_event_title: str, await ctx.respond(f"Event created successful!\n{new_discord_event}") - + @discord.slash_command( # type: ignore[no-untyped-call, misc] + name="get-msl-context", + description="debug command to check the msl context retrieved for a given url", + ) + @discord.option( # type: ignore[no-untyped-call, misc] + name="url", + description="The URL to get the MSL context for.", + required=True, + input_type=str, + parameter_name="str_url", + ) + @CommandChecks.check_interaction_user_has_committee_role + @CommandChecks.check_interaction_user_in_main_guild + async def get_msl_context(self, ctx: TeXBotApplicationContext, str_url: str) -> None: + """Command to get the MSL context for a given URL.""" + data_fields, cookies = await self._get_msl_context(str_url) + logger.debug(data_fields) + logger.debug(cookies) + await ctx.respond( + content=( + f"Context headers: {data_fields}\n" + f"Context data: {cookies}" + ), + ) diff --git a/utils/gcal.py b/utils/gcal.py index a8a395cf..9dfeedba 100644 --- a/utils/gcal.py +++ b/utils/gcal.py @@ -47,6 +47,7 @@ async def fetch_credentials() -> Credentials | None: client_secrets_file="credentials.json", scopes=SCOPES, ) credentials = flow.run_local_server(port=0) + await anyio.sleep(10) logger.debug("Attempted to fetch credentials") if not credentials: From b0357b9f7030b604eade15f37b556456ca9d4aac Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Sun, 4 Aug 2024 17:24:58 +0100 Subject: [PATCH 22/40] fixed bullshit gcal api error --- utils/gcal.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/utils/gcal.py b/utils/gcal.py index 9dfeedba..ea6689d9 100644 --- a/utils/gcal.py +++ b/utils/gcal.py @@ -47,14 +47,13 @@ async def fetch_credentials() -> Credentials | None: client_secrets_file="credentials.json", scopes=SCOPES, ) credentials = flow.run_local_server(port=0) - await anyio.sleep(10) logger.debug("Attempted to fetch credentials") if not credentials: return None try: - async with await anyio.open_file("token.json") as token: + async with await anyio.open_file(file="token.json") as token: await token.write(credentials.to_json()) # type: ignore[no-untyped-call] except Exception as error: logger.exception("Failed to write credentials to token.json") @@ -78,7 +77,7 @@ async def fetch_events() -> list[dict[str, str]] | None: credentials=credentials, ) - now: str = datetime.datetime.now(tz=datetime.UTC).isoformat() + "Z" + now: str = datetime.datetime.now(tz=datetime.UTC).isoformat() events: Events = ( service.events().list( From 43a1f7cb0573bca9daa7aa607a4fcb6921a641b6 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Sun, 4 Aug 2024 19:36:18 +0100 Subject: [PATCH 23/40] add db object --- cogs/events.py | 32 ++++++++++++++++-- db/core/models/__init__.py | 68 ++++++++++++++++++++++++++++++++++++++ django_migrations.py | 7 ++++ 3 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 django_migrations.py diff --git a/cogs/events.py b/cogs/events.py index 5aabcbd6..7f670965 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -92,7 +92,7 @@ async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: headers=BASE_HEADERS, cookies=new_cookies, ) - async with session_v2, session_v2.post(url=EVENT_LIST_URL, data=data_fields) as http_response: + async with session_v2, session_v2.post(url=EVENT_LIST_URL, data=data_fields) as http_response: # noqa: E501 if http_response.status != 200: await self.command_send_error( ctx=ctx, @@ -195,13 +195,39 @@ async def get_events(self, ctx: TeXBotApplicationContext, *, str_from_date: str, ) formatted_from_date: str = from_date_dt.strftime("%d/%m/%Y") - formatted_to_date: str = to_date_dt.strftime("%d/%m/%Y") + formatted_to_date: str = to_date_dt.strftime(format="%d/%m/%Y") await self._get_all_guild_events(ctx, formatted_from_date, formatted_to_date) events: list[dict[str, str]] | None = await GoogleCalendar.fetch_events() - await ctx.send(content=f"Found GCal events: {events}") + if events is None: + await ctx.send(content="No events found on the Google Calendar.") + return + + events_message: str = ( + f"Found {len(events)} events on the Google Calendar:\n" + + "\n".join( + f"{event['event_title']} - {event['start_dt']} to {event['end_dt']}" + for event in events + ) + ) + + await ctx.send(content=events_message) + + scheduled_events_list: list[discord.ScheduledEvent] = await ctx.guild.fetch_scheduled_events() # noqa: E501 + + scheduled_events_message: str = ( + f"Found {len(scheduled_events_list)} scheduled events on the Discord server:\n" + + "\n".join( + f"{event.id} - {event.name} - {event.start_time} to {event.end_time}" + for event in scheduled_events_list + ) + ) + + await ctx.send(content=scheduled_events_message) + + @discord.slash_command( # type: ignore[no-untyped-call, misc] name="create-event", diff --git a/db/core/models/__init__.py b/db/core/models/__init__.py index bf57415d..f83b8fb4 100644 --- a/db/core/models/__init__.py +++ b/db/core/models/__init__.py @@ -26,6 +26,74 @@ from .utils import AsyncBaseModel, BaseDiscordMemberWrapper, DiscordMember +class SocietyEvent(AsyncBaseModel): + """ + Model to represent a society event. + + A society event is an event that is hosted by a society. + Each SocietyEvent is associated with a discord + ScheduledEvent, Guild Event and Google Calendar Event. + """ + + INSTANCES_NAME_PLURAL: str = "Society Events" + + guild_event_id = models.IntegerField( + "Guild Event ID", + unique=True, + null=False, + blank=False, + ) + + gcal_event_id = models.TextField( + "Google Calendar Event ID", + unique=True, + null=False, + blank=False, + ) + + discord_event_id = models.BigIntegerField( + "Discord Event ID", + unique=True, + null=False, + blank=False, + ) + + event_name = models.TextField( + "Event Name", + null=False, + blank=False, + ) + + event_description = models.TextField( + "Event Description", + ) + + event_start_time = models.DateTimeField( + "Event Start Time", + null=False, + blank=False, + ) + + event_end_time = models.DateTimeField( + "Event End Time", + null=False, + blank=False, + ) + + event_location = models.TextField( + "Event Location", + ) + + class Meta: + verbose_name: str = "Society Event" + constraints: list[models.UniqueConstraint] = [ # noqa: RUF012 + models.UniqueConstraint( + fields=["guild_event_id", "gcal_event_id", "discord_event_id"], + name="unique_society_event", + ), + ] + + class IntroductionReminderOptOutMember(BaseDiscordMemberWrapper): """ Model to represent a Discord member that has opted out of introduction reminders. diff --git a/django_migrations.py b/django_migrations.py new file mode 100644 index 00000000..91133b43 --- /dev/null +++ b/django_migrations.py @@ -0,0 +1,7 @@ +import os + +os.environ["DJANGO_SETTINGS_MODULE"] = "db.settings" + +from django.core import management + +management.call_command("makemigrations") From c5099b571caaad812cf1c20f5820492b62f61777 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Sun, 4 Aug 2024 19:39:01 +0100 Subject: [PATCH 24/40] yeet --- django_migrations.py | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 django_migrations.py diff --git a/django_migrations.py b/django_migrations.py deleted file mode 100644 index 91133b43..00000000 --- a/django_migrations.py +++ /dev/null @@ -1,7 +0,0 @@ -import os - -os.environ["DJANGO_SETTINGS_MODULE"] = "db.settings" - -from django.core import management - -management.call_command("makemigrations") From 47a509e527f42b09e6104dfa870aa1470df89d4d Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Sun, 4 Aug 2024 18:39:42 +0000 Subject: [PATCH 25/40] make migration --- ...event_societyevent_unique_society_event.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 db/core/migrations/0010_societyevent_societyevent_unique_society_event.py diff --git a/db/core/migrations/0010_societyevent_societyevent_unique_society_event.py b/db/core/migrations/0010_societyevent_societyevent_unique_society_event.py new file mode 100644 index 00000000..39ca3e4f --- /dev/null +++ b/db/core/migrations/0010_societyevent_societyevent_unique_society_event.py @@ -0,0 +1,64 @@ +# Generated by Django 5.0.7 on 2024-08-04 18:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0009_auto_20240519_0020"), + ] + + operations = [ + migrations.CreateModel( + name="SocietyEvent", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "guild_event_id", + models.IntegerField(unique=True, verbose_name="Guild Event ID"), + ), + ( + "gcal_event_id", + models.TextField( + unique=True, verbose_name="Google Calendar Event ID" + ), + ), + ( + "discord_event_id", + models.BigIntegerField( + unique=True, verbose_name="Discord Event ID" + ), + ), + ("event_name", models.TextField(verbose_name="Event Name")), + ( + "event_description", + models.TextField(verbose_name="Event Description"), + ), + ( + "event_start_time", + models.DateTimeField(verbose_name="Event Start Time"), + ), + ("event_end_time", models.DateTimeField(verbose_name="Event End Time")), + ("event_location", models.TextField(verbose_name="Event Location")), + ], + options={ + "verbose_name": "Society Event", + }, + ), + migrations.AddConstraint( + model_name="societyevent", + constraint=models.UniqueConstraint( + fields=("guild_event_id", "gcal_event_id", "discord_event_id"), + name="unique_society_event", + ), + ), + ] From 8ea4fc8d6ebcb0eeb7d6ade2565a45d0924ae491 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Sun, 4 Aug 2024 20:46:12 +0100 Subject: [PATCH 26/40] do some stuf --- cogs/events.py | 14 ++++++++------ utils/gcal.py | 4 ++-- utils/msl.py | 21 +++++++++++++++++++++ 3 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 utils/msl.py diff --git a/cogs/events.py b/cogs/events.py index 7f670965..0b46adaf 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -294,10 +294,10 @@ async def setup_event(self, ctx: TeXBotApplicationContext, str_event_title: str, main_guild: discord.Guild = self.bot.main_guild try: start_date_dt: datetime.datetime = dateutil.parser.parse( - f"{str_start_date}T{str_start_time}", dayfirst=True, + timestr=f"{str_start_date}T{str_start_time}", dayfirst=True, ) end_date_dt: datetime.datetime = dateutil.parser.parse( - f"{str_end_date}T{str_end_time}", dayfirst=True, + timestr=f"{str_end_date}T{str_end_time}", dayfirst=True, ) except ParserError: await ctx.respond( @@ -317,20 +317,22 @@ async def setup_event(self, ctx: TeXBotApplicationContext, str_event_title: str, ) return + location: str = str_location if str_location else "No location provided." + description: str = str_description if str_description else "No description provided." + try: new_discord_event: discord.ScheduledEvent | None = await main_guild.create_scheduled_event( # noqa: E501 name=str_event_title, start_time=start_date_dt, end_time=end_date_dt, - description=str_description if str_description else "No description provided.", - location=str_location if str_location else "No location provided.", + description=description, + location=location, ) except discord.Forbidden: await self.command_send_error( ctx=ctx, - message="TeX-Bot does not have the required permissions to create an event.", + message="TeX-Bot does not have the required permissions to create a discord event.", # noqa: E501 ) - return await ctx.respond(f"Event created successful!\n{new_discord_event}") diff --git a/utils/gcal.py b/utils/gcal.py index ea6689d9..a3c76f28 100644 --- a/utils/gcal.py +++ b/utils/gcal.py @@ -22,7 +22,7 @@ from googleapiclient._apis.calendar.v3.resources import CalendarResource from googleapiclient._apis.calendar.v3.schemas import Events -SCOPES: Final[Sequence[str]] = ["https://www.googleapis.com/auth/calendar.readonly"] +SCOPES: Final[Sequence[str]] = ["https://www.googleapis.com/auth/calendar.events"] logger: Final[Logger] = logging.getLogger("TeX-Bot") @@ -35,7 +35,7 @@ async def fetch_credentials() -> Credentials | None: credentials: Credentials | None = None if Path("token.json").exists(): - credentials = Credentials.from_authorized_user_file("token.json", SCOPES) # type: ignore[no-untyped-call] + credentials = Credentials.from_authorized_user_file(filename="token.json", scopes=SCOPES) # type: ignore[no-untyped-call] logger.debug("Credentials loaded from token.json") if not credentials or not credentials.token_state.FRESH: diff --git a/utils/msl.py b/utils/msl.py new file mode 100644 index 00000000..3f54f9f2 --- /dev/null +++ b/utils/msl.py @@ -0,0 +1,21 @@ +"""Functions to enable interaction with MSL based SU websites.""" + +from collections.abc import Sequence + +__all__: Sequence[str] = ("",) + + +import logging +from logging import Logger +from typing import Final + +logger: Final[Logger] = logging.getLogger("TeX-Bot") + + +class MSL: + """Class to define the functions related to MSL based SU websites.""" + + async def _get_msl_context(self, url: str) -> tuple[dict[str, str], dict[str, str]]: + """Get the required context headers, data and cookies to make a request to MSL.""" + return {}, {} + From b9fda240505aa8b28733c730417242e83a6df28b Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Wed, 7 Aug 2024 19:24:00 +0100 Subject: [PATCH 27/40] chonky refactor --- cogs/events.py | 110 +----------------------------------------- utils/msl.py | 128 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 126 insertions(+), 112 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index 0b46adaf..f9b31d84 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -18,11 +18,10 @@ from dateutil.parser import ParserError from config import settings -from utils import CommandChecks, GoogleCalendar, TeXBotApplicationContext, TeXBotBaseCog +from utils import CommandChecks, GoogleCalendar, TeXBotApplicationContext, TeXBotBaseCog, msl if TYPE_CHECKING: import datetime - from http.cookies import Morsel logger: Final[Logger] = logging.getLogger("TeX-Bot") @@ -37,116 +36,9 @@ ".ASPXAUTH": settings["MEMBERS_LIST_AUTH_SESSION_COOKIE"], } -EVENT_LIST_URL: Final[str] = "https://www.guildofstudents.com/events/edit/6531/" -FROM_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$datesFilter$txtFromDate" -TO_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$datesFilter$txtToDate" -BUTTON_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$fsSetDates$btnSubmit" -EVENT_TABLE_ID: Final[str] = "ctl00_ctl00_Main_AdminPageContent_gvEvents" - - class EventsManagementCommandsCog(TeXBotBaseCog): """Cog class to define event management commands.""" - async def _get_msl_context(self, url: str) -> tuple[dict[str, str], dict[str, str]]: - """Get the required context headers, data and cookies to make a request to MSL.""" - http_session: aiohttp.ClientSession = aiohttp.ClientSession( - headers=BASE_HEADERS, - cookies=BASE_COOKIES, - ) - data_fields: dict[str, str] = {} - cookies: dict[str ,str] = {} - async with http_session, http_session.get(url=url) as field_data: - data_response = BeautifulSoup( - markup=await field_data.text(), - features="html.parser", - ) - - for field in data_response.find_all(name="input"): - if field.get("name") and field.get("value"): - data_fields[field.get("name")] = field.get("value") - - for cookie in field_data.cookies: - cookie_morsel: Morsel[str] | None = field_data.cookies.get(cookie) - if cookie_morsel is not None: - cookies[cookie] = cookie_morsel.value - cookies[".ASPXAUTH"] = settings["MEMBERS_LIST_AUTH_SESSION_COOKIE"] - - return data_fields, cookies - - async def _get_all_guild_events(self, ctx: TeXBotApplicationContext, from_date: str, to_date: str) -> None: # noqa: E501 - """Fetch all events on the guild website.""" - data_fields, new_cookies = await self._get_msl_context(url=EVENT_LIST_URL) - - form_data: dict[str, str] = { - FROM_DATE_KEY: from_date, - TO_DATE_KEY: to_date, - BUTTON_KEY: "Find Events", - "__EVENTTARGET": "", - "__EVENTARGUMENT": "", - "__VIEWSTATEENCRYPTED": "", - } - - data_fields.update(form_data) - - session_v2: aiohttp.ClientSession = aiohttp.ClientSession( - headers=BASE_HEADERS, - cookies=new_cookies, - ) - async with session_v2, session_v2.post(url=EVENT_LIST_URL, data=data_fields) as http_response: # noqa: E501 - if http_response.status != 200: - await self.command_send_error( - ctx=ctx, - message="Returned a non-200 status code!!!.", - ) - logger.debug(http_response) - return - - response_html: str = await http_response.text() - - event_table_html: bs4.Tag | bs4.NavigableString | None = BeautifulSoup( - markup=response_html, - features="html.parser", - ).find( - name="table", - attrs={"id": EVENT_TABLE_ID}, - ) - - if event_table_html is None or isinstance(event_table_html, bs4.NavigableString): - await self.command_send_error( - ctx=ctx, - message="Something went wrong and the event list could not be fetched.", - ) - return - - if "There are no events" in str(event_table_html): - await ctx.respond( - content=( - f"There are no events found for the date range: {from_date} to {to_date}." - ), - ) - logger.debug(event_table_html) - return - - event_list: list[bs4.Tag] = event_table_html.find_all(name="tr") - - event_list.pop(0) - - event_ids: dict[str, str] = { - event.find(name="a").get("href").split("/")[5]: event.find(name="a").text # type: ignore[union-attr] - for event in event_list - } - - response_message: str = ( - f"Guild events from {from_date} to {to_date}:\n" - + "\n".join( - f"{event_id}: {event_name}" - for event_id, event_name in event_ids.items() - ) - ) - - await ctx.respond(content=response_message) - - @discord.slash_command( # type: ignore[no-untyped-call, misc] name="get-events", description="Returns all events currently on the guild website.", diff --git a/utils/msl.py b/utils/msl.py index 3f54f9f2..e69556c3 100644 --- a/utils/msl.py +++ b/utils/msl.py @@ -2,20 +2,142 @@ from collections.abc import Sequence -__all__: Sequence[str] = ("",) +__all__: Sequence[str] = ("MSL",) import logging +from collections.abc import Mapping from logging import Logger -from typing import Final +from typing import TYPE_CHECKING, Final + +import aiohttp +import bs4 +from bs4 import BeautifulSoup + +from config import settings + +if TYPE_CHECKING: + from http.cookies import Morsel + logger: Final[Logger] = logging.getLogger("TeX-Bot") +MSL_URLS: Final[Mapping[str, str]] = { + "EVENT_LIST": "https://www.guildofstudents.com/events/edit/6531/", + "MEMBERS_LIST": settings["MEMBERS_LIST_URL"], +} + +BASE_HEADERS: Final[Mapping[str, str]] = { + "Cache-Control": "no-cache", + "Pragma": "no-cache", + "Expires": "0", +} + +BASE_COOKIES: Final[Mapping[str, str]] = { + ".ASPXAUTH": settings["MEMBERS_LIST_AUTH_SESSION_COOKIE"], +} + +FROM_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$datesFilter$txtFromDate" +TO_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$datesFilter$txtToDate" +BUTTON_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$fsSetDates$btnSubmit" +EVENT_TABLE_ID: Final[str] = "ctl00_ctl00_Main_AdminPageContent_gvEvents" + class MSL: """Class to define the functions related to MSL based SU websites.""" async def _get_msl_context(self, url: str) -> tuple[dict[str, str], dict[str, str]]: """Get the required context headers, data and cookies to make a request to MSL.""" - return {}, {} + http_session: aiohttp.ClientSession = aiohttp.ClientSession( + headers=BASE_HEADERS, + cookies=BASE_COOKIES, + ) + data_fields: dict[str, str] = {} + cookies: dict[str ,str] = {} + async with http_session, http_session.get(url=url) as field_data: + data_response = BeautifulSoup( + markup=await field_data.text(), + features="html.parser", + ) + + for field in data_response.find_all(name="input"): + if field.get("name") and field.get("value"): + data_fields[field.get("name")] = field.get("value") + + for cookie in field_data.cookies: + cookie_morsel: Morsel[str] | None = field_data.cookies.get(cookie) + if cookie_morsel is not None: + cookies[cookie] = cookie_morsel.value + cookies[".ASPXAUTH"] = settings["MEMBERS_LIST_AUTH_SESSION_COOKIE"] + + return data_fields, cookies + + class MSLEvents: + """Class to define Event specific MSL methods.""" + + async def _get_all_guild_events(self, from_date: str, to_date: str) -> dict[str, str]: + """Fetch all events on the guild website.""" + EVENT_LIST_URL: Final[str] = MSL_URLS["EVENT_LIST"] + + data_fields, cookies = await MSL._get_msl_context(self, url=EVENT_LIST_URL) + + form_data: dict[str, str] = { + FROM_DATE_KEY: from_date, + TO_DATE_KEY: to_date, + BUTTON_KEY: "Find Events", + "__EVENTTARGET": "", + "__EVENTARGUMENT": "", + "__VIEWSTATEENCRYPTED": "", + } + + data_fields.update(form_data) + + session_v2: aiohttp.ClientSession = aiohttp.ClientSession( + headers=BASE_HEADERS, + cookies=cookies, + ) + async with session_v2, session_v2.post(url=EVENT_LIST_URL, data=data_fields) as http_response: # noqa: E501 + if http_response.status != 200: + logger.debug("Returned a non 200 status code!!") + logger.debug(http_response) + return {} + + response_html: str = await http_response.text() + + event_table_html: bs4.Tag | bs4.NavigableString | None = BeautifulSoup( + markup=response_html, + features="html.parser", + ).find( + name="table", + attrs={"id": EVENT_TABLE_ID}, + ) + + if event_table_html is None or isinstance(event_table_html, bs4.NavigableString): + # TODO: something went wrong!! + logger.debug("Something went wrong!") + return {} + + if "There are no events" in str(event_table_html): + # TODO: No events!! + logger.debug("No events were found!") + return {} + + event_list: list[bs4.Tag] = event_table_html.find_all(name="tr") + + event_list.pop(0) + + event_ids: dict[str, str] = { + event.find(name="a").get("href").split("/")[5]: event.find(name="a").text # type: ignore[union-attr] + for event in event_list + } + + return event_ids + + + class MSLMemberships: + """Class to define Membership specific MSL methods.""" + + + class MSLSalesReports: + """Class to define Sales Reports specific MSL methods.""" From 8eb93a32090018e8350a9f461449a566abdb7317 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Wed, 7 Aug 2024 19:29:06 +0100 Subject: [PATCH 28/40] add urls --- cogs/events.py | 5 +--- poetry.lock | 80 +++++++++++++++++++++++++------------------------- utils/msl.py | 2 ++ 3 files changed, 43 insertions(+), 44 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index f9b31d84..be9b2115 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -10,15 +10,12 @@ from logging import Logger from typing import TYPE_CHECKING, Final -import aiohttp -import bs4 import dateutil.parser import discord -from bs4 import BeautifulSoup from dateutil.parser import ParserError from config import settings -from utils import CommandChecks, GoogleCalendar, TeXBotApplicationContext, TeXBotBaseCog, msl +from utils import CommandChecks, GoogleCalendar, TeXBotApplicationContext, TeXBotBaseCog if TYPE_CHECKING: import datetime diff --git a/poetry.lock b/poetry.lock index 2f0e5baa..56db53b8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "aiohappyeyeballs" -version = "2.3.4" +version = "2.3.5" description = "Happy Eyeballs for asyncio" optional = false -python-versions = "<4.0,>=3.8" +python-versions = ">=3.8" files = [ - {file = "aiohappyeyeballs-2.3.4-py3-none-any.whl", hash = "sha256:40a16ceffcf1fc9e142fd488123b2e218abc4188cf12ac20c67200e1579baa42"}, - {file = "aiohappyeyeballs-2.3.4.tar.gz", hash = "sha256:7e1ae8399c320a8adec76f6c919ed5ceae6edd4c3672f4d9eae2b27e37c80ff6"}, + {file = "aiohappyeyeballs-2.3.5-py3-none-any.whl", hash = "sha256:4d6dea59215537dbc746e93e779caea8178c866856a721c9c660d7a5a7b8be03"}, + {file = "aiohappyeyeballs-2.3.5.tar.gz", hash = "sha256:6fa48b9f1317254f122a07a131a86b71ca6946ca989ce6326fff54a99a920105"}, ] [[package]] @@ -822,13 +822,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.139.0" +version = "2.140.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "google_api_python_client-2.139.0-py2.py3-none-any.whl", hash = "sha256:1850a92505d91a82e2ca1635ab2b8dff179f4b67082c2651e1db332e8039840c"}, - {file = "google_api_python_client-2.139.0.tar.gz", hash = "sha256:ed4bc3abe2c060a87412465b4e8254620bbbc548eefc5388e2c5ff912d36a68b"}, + {file = "google_api_python_client-2.140.0-py2.py3-none-any.whl", hash = "sha256:aeb4bb99e9fdd241473da5ff35464a0658fea0db76fe89c0f8c77ecfc3813404"}, + {file = "google_api_python_client-2.140.0.tar.gz", hash = "sha256:0bb973adccbe66a3d0a70abe4e49b3f2f004d849416bfec38d22b75649d389d8"}, ] [package.dependencies] @@ -856,13 +856,13 @@ typing-extensions = ">=3.10.0" [[package]] name = "google-auth" -version = "2.32.0" +version = "2.33.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google_auth-2.32.0-py2.py3-none-any.whl", hash = "sha256:53326ea2ebec768070a94bee4e1b9194c9646ea0c2bd72422785bd0f9abfad7b"}, - {file = "google_auth-2.32.0.tar.gz", hash = "sha256:49315be72c55a6a37d62819e3573f6b416aca00721f7e3e31a008d928bf64022"}, + {file = "google_auth-2.33.0-py2.py3-none-any.whl", hash = "sha256:8eff47d0d4a34ab6265c50a106a3362de6a9975bb08998700e389f857e4d39df"}, + {file = "google_auth-2.33.0.tar.gz", hash = "sha256:d6a52342160d7290e334b4d47ba390767e4438ad0d45b7630774533e82655b95"}, ] [package.dependencies] @@ -1092,40 +1092,40 @@ files = [ [[package]] name = "matplotlib" -version = "3.9.0" +version = "3.9.1.post1" description = "Python plotting package" optional = false python-versions = ">=3.9" files = [ - {file = "matplotlib-3.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2bcee1dffaf60fe7656183ac2190bd630842ff87b3153afb3e384d966b57fe56"}, - {file = "matplotlib-3.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3f988bafb0fa39d1074ddd5bacd958c853e11def40800c5824556eb630f94d3b"}, - {file = "matplotlib-3.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe428e191ea016bb278758c8ee82a8129c51d81d8c4bc0846c09e7e8e9057241"}, - {file = "matplotlib-3.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaf3978060a106fab40c328778b148f590e27f6fa3cd15a19d6892575bce387d"}, - {file = "matplotlib-3.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2e7f03e5cbbfacdd48c8ea394d365d91ee8f3cae7e6ec611409927b5ed997ee4"}, - {file = "matplotlib-3.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:13beb4840317d45ffd4183a778685e215939be7b08616f431c7795276e067463"}, - {file = "matplotlib-3.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:063af8587fceeac13b0936c42a2b6c732c2ab1c98d38abc3337e430e1ff75e38"}, - {file = "matplotlib-3.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a2fa6d899e17ddca6d6526cf6e7ba677738bf2a6a9590d702c277204a7c6152"}, - {file = "matplotlib-3.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550cdda3adbd596078cca7d13ed50b77879104e2e46392dcd7c75259d8f00e85"}, - {file = "matplotlib-3.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cce0f31b351e3551d1f3779420cf8f6ec0d4a8cf9c0237a3b549fd28eb4abb"}, - {file = "matplotlib-3.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c53aeb514ccbbcbab55a27f912d79ea30ab21ee0531ee2c09f13800efb272674"}, - {file = "matplotlib-3.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5be985db2596d761cdf0c2eaf52396f26e6a64ab46bd8cd810c48972349d1be"}, - {file = "matplotlib-3.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c79f3a585f1368da6049318bdf1f85568d8d04b2e89fc24b7e02cc9b62017382"}, - {file = "matplotlib-3.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bdd1ecbe268eb3e7653e04f451635f0fb0f77f07fd070242b44c076c9106da84"}, - {file = "matplotlib-3.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e85a1a6d732f645f1403ce5e6727fd9418cd4574521d5803d3d94911038e5"}, - {file = "matplotlib-3.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a490715b3b9984fa609116481b22178348c1a220a4499cda79132000a79b4db"}, - {file = "matplotlib-3.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8146ce83cbc5dc71c223a74a1996d446cd35cfb6a04b683e1446b7e6c73603b7"}, - {file = "matplotlib-3.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:d91a4ffc587bacf5c4ce4ecfe4bcd23a4b675e76315f2866e588686cc97fccdf"}, - {file = "matplotlib-3.9.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:616fabf4981a3b3c5a15cd95eba359c8489c4e20e03717aea42866d8d0465956"}, - {file = "matplotlib-3.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cd53c79fd02f1c1808d2cfc87dd3cf4dbc63c5244a58ee7944497107469c8d8a"}, - {file = "matplotlib-3.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06a478f0d67636554fa78558cfbcd7b9dba85b51f5c3b5a0c9be49010cf5f321"}, - {file = "matplotlib-3.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81c40af649d19c85f8073e25e5806926986806fa6d54be506fbf02aef47d5a89"}, - {file = "matplotlib-3.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52146fc3bd7813cc784562cb93a15788be0b2875c4655e2cc6ea646bfa30344b"}, - {file = "matplotlib-3.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:0fc51eaa5262553868461c083d9adadb11a6017315f3a757fc45ec6ec5f02888"}, - {file = "matplotlib-3.9.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bd4f2831168afac55b881db82a7730992aa41c4f007f1913465fb182d6fb20c0"}, - {file = "matplotlib-3.9.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:290d304e59be2b33ef5c2d768d0237f5bd132986bdcc66f80bc9bcc300066a03"}, - {file = "matplotlib-3.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff2e239c26be4f24bfa45860c20ffccd118d270c5b5d081fa4ea409b5469fcd"}, - {file = "matplotlib-3.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:af4001b7cae70f7eaacfb063db605280058246de590fa7874f00f62259f2df7e"}, - {file = "matplotlib-3.9.0.tar.gz", hash = "sha256:e6d29ea6c19e34b30fb7d88b7081f869a03014f66fe06d62cc77d5a6ea88ed7a"}, + {file = "matplotlib-3.9.1.post1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3779ad3e8b72df22b8a622c5796bbcfabfa0069b835412e3c1dec8ee3de92d0c"}, + {file = "matplotlib-3.9.1.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec400340f8628e8e2260d679078d4e9b478699f386e5cc8094e80a1cb0039c7c"}, + {file = "matplotlib-3.9.1.post1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82c18791b8862ea095081f745b81f896b011c5a5091678fb33204fef641476af"}, + {file = "matplotlib-3.9.1.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:621a628389c09a6b9f609a238af8e66acecece1cfa12febc5fe4195114ba7446"}, + {file = "matplotlib-3.9.1.post1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9a54734ca761ebb27cd4f0b6c2ede696ab6861052d7d7e7b8f7a6782665115f5"}, + {file = "matplotlib-3.9.1.post1-cp310-cp310-win_amd64.whl", hash = "sha256:0721f93db92311bb514e446842e2b21c004541dcca0281afa495053e017c5458"}, + {file = "matplotlib-3.9.1.post1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b08b46058fe2a31ecb81ef6aa3611f41d871f6a8280e9057cb4016cb3d8e894a"}, + {file = "matplotlib-3.9.1.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:22b344e84fcc574f561b5731f89a7625db8ef80cdbb0026a8ea855a33e3429d1"}, + {file = "matplotlib-3.9.1.post1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b49fee26d64aefa9f061b575f0f7b5fc4663e51f87375c7239efa3d30d908fa"}, + {file = "matplotlib-3.9.1.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89eb7e89e2b57856533c5c98f018aa3254fa3789fcd86d5f80077b9034a54c9a"}, + {file = "matplotlib-3.9.1.post1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c06e742bade41fda6176d4c9c78c9ea016e176cd338e62a1686384cb1eb8de41"}, + {file = "matplotlib-3.9.1.post1-cp311-cp311-win_amd64.whl", hash = "sha256:c44edab5b849e0fc1f1c9d6e13eaa35ef65925f7be45be891d9784709ad95561"}, + {file = "matplotlib-3.9.1.post1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bf28b09986aee06393e808e661c3466be9c21eff443c9bc881bce04bfbb0c500"}, + {file = "matplotlib-3.9.1.post1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:92aeb8c439d4831510d8b9d5e39f31c16c7f37873879767c26b147cef61e54cd"}, + {file = "matplotlib-3.9.1.post1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f15798b0691b45c80d3320358a88ce5a9d6f518b28575b3ea3ed31b4bd95d009"}, + {file = "matplotlib-3.9.1.post1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d59fc6096da7b9c1df275f9afc3fef5cbf634c21df9e5f844cba3dd8deb1847d"}, + {file = "matplotlib-3.9.1.post1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ab986817a32a70ce22302438691e7df4c6ee4a844d47289db9d583d873491e0b"}, + {file = "matplotlib-3.9.1.post1-cp312-cp312-win_amd64.whl", hash = "sha256:0d78e7d2d86c4472da105d39aba9b754ed3dfeaeaa4ac7206b82706e0a5362fa"}, + {file = "matplotlib-3.9.1.post1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bd07eba6431b4dc9253cce6374a28c415e1d3a7dc9f8aba028ea7592f06fe172"}, + {file = "matplotlib-3.9.1.post1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ca230cc4482010d646827bd2c6d140c98c361e769ae7d954ebf6fff2a226f5b1"}, + {file = "matplotlib-3.9.1.post1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ace27c0fdeded399cbc43f22ffa76e0f0752358f5b33106ec7197534df08725a"}, + {file = "matplotlib-3.9.1.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a4f3aeb7ba14c497dc6f021a076c48c2e5fbdf3da1e7264a5d649683e284a2f"}, + {file = "matplotlib-3.9.1.post1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:23f96fbd4ff4cfa9b8a6b685a65e7eb3c2ced724a8d965995ec5c9c2b1f7daf5"}, + {file = "matplotlib-3.9.1.post1-cp39-cp39-win_amd64.whl", hash = "sha256:2808b95452b4ffa14bfb7c7edffc5350743c31bda495f0d63d10fdd9bc69e895"}, + {file = "matplotlib-3.9.1.post1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ffc91239f73b4179dec256b01299d46d0ffa9d27d98494bc1476a651b7821cbe"}, + {file = "matplotlib-3.9.1.post1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f965ebca9fd4feaaca45937c4849d92b70653057497181100fcd1e18161e5f29"}, + {file = "matplotlib-3.9.1.post1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801ee9323fd7b2da0d405aebbf98d1da77ea430bbbbbec6834c0b3af15e5db44"}, + {file = "matplotlib-3.9.1.post1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:50113e9b43ceb285739f35d43db36aa752fb8154325b35d134ff6e177452f9ec"}, + {file = "matplotlib-3.9.1.post1.tar.gz", hash = "sha256:c91e585c65092c975a44dc9d4239ba8c594ba3c193d7c478b6d178c4ef61f406"}, ] [package.dependencies] diff --git a/utils/msl.py b/utils/msl.py index e69556c3..752377b1 100644 --- a/utils/msl.py +++ b/utils/msl.py @@ -25,7 +25,9 @@ MSL_URLS: Final[Mapping[str, str]] = { "EVENT_LIST": "https://www.guildofstudents.com/events/edit/6531/", + "CREATE_EVENT": "https://www.guildofstudents.com/events/edit/event/6531/", "MEMBERS_LIST": settings["MEMBERS_LIST_URL"], + "SALES_REPORTS": "https://www.guildofstudents.com/organisation/salesreports/6531/", } BASE_HEADERS: Final[Mapping[str, str]] = { From eca766038dee03c7c983e6ca4f2e008001e9c57a Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Fri, 9 Aug 2024 18:23:30 +0100 Subject: [PATCH 29/40] stuff --- utils/msl.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/utils/msl.py b/utils/msl.py index 752377b1..70b5c3c6 100644 --- a/utils/msl.py +++ b/utils/msl.py @@ -48,7 +48,8 @@ class MSL: """Class to define the functions related to MSL based SU websites.""" - async def _get_msl_context(self, url: str) -> tuple[dict[str, str], dict[str, str]]: + @staticmethod + async def _get_msl_context(url: str) -> tuple[dict[str, str], dict[str, str]]: """Get the required context headers, data and cookies to make a request to MSL.""" http_session: aiohttp.ClientSession = aiohttp.ClientSession( headers=BASE_HEADERS, @@ -81,7 +82,7 @@ async def _get_all_guild_events(self, from_date: str, to_date: str) -> dict[str, """Fetch all events on the guild website.""" EVENT_LIST_URL: Final[str] = MSL_URLS["EVENT_LIST"] - data_fields, cookies = await MSL._get_msl_context(self, url=EVENT_LIST_URL) + data_fields, cookies = await MSL._get_msl_context(url=EVENT_LIST_URL) form_data: dict[str, str] = { FROM_DATE_KEY: from_date, @@ -139,6 +140,13 @@ async def _get_all_guild_events(self, from_date: str, to_date: str) -> dict[str, class MSLMemberships: """Class to define Membership specific MSL methods.""" + @staticmethod + async def get_full_membership_list() -> list[tuple[str, str]]: + """Get a list of tuples of student ID to names.""" + + + + return [] class MSLSalesReports: """Class to define Sales Reports specific MSL methods.""" From c77d12be4dc5f666125d49825b763ffde9371096 Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Fri, 9 Aug 2024 23:55:00 +0000 Subject: [PATCH 30/40] update deps --- poetry.lock | 162 ++++++++++++++++++++++++++-------------------------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/poetry.lock b/poetry.lock index f724d4f5..8771fa76 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,87 +13,87 @@ files = [ [[package]] name = "aiohttp" -version = "3.10.1" +version = "3.10.2" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:47b4c2412960e64d97258f40616efddaebcb34ff664c8a972119ed38fac2a62c"}, - {file = "aiohttp-3.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7dbf637f87dd315fa1f36aaed8afa929ee2c607454fb7791e74c88a0d94da59"}, - {file = "aiohttp-3.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c8fb76214b5b739ce59e2236a6489d9dc3483649cfd6f563dbf5d8e40dbdd57d"}, - {file = "aiohttp-3.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c577cdcf8f92862363b3d598d971c6a84ed8f0bf824d4cc1ce70c2fb02acb4a"}, - {file = "aiohttp-3.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:777e23609899cb230ad2642b4bdf1008890f84968be78de29099a8a86f10b261"}, - {file = "aiohttp-3.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b07286a1090483799599a2f72f76ac396993da31f6e08efedb59f40876c144fa"}, - {file = "aiohttp-3.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9db600a86414a9a653e3c1c7f6a2f6a1894ab8f83d11505247bd1b90ad57157"}, - {file = "aiohttp-3.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c3f1eb280008e51965a8d160a108c333136f4a39d46f516c64d2aa2e6a53f2"}, - {file = "aiohttp-3.10.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f5dd109a925fee4c9ac3f6a094900461a2712df41745f5d04782ebcbe6479ccb"}, - {file = "aiohttp-3.10.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8c81ff4afffef9b1186639506d70ea90888218f5ddfff03870e74ec80bb59970"}, - {file = "aiohttp-3.10.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:2a384dfbe8bfebd203b778a30a712886d147c61943675f4719b56725a8bbe803"}, - {file = "aiohttp-3.10.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:b9fb6508893dc31cfcbb8191ef35abd79751db1d6871b3e2caee83959b4d91eb"}, - {file = "aiohttp-3.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:88596384c3bec644a96ae46287bb646d6a23fa6014afe3799156aef42669c6bd"}, - {file = "aiohttp-3.10.1-cp310-cp310-win32.whl", hash = "sha256:68164d43c580c2e8bf8e0eb4960142919d304052ccab92be10250a3a33b53268"}, - {file = "aiohttp-3.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:d6bbe2c90c10382ca96df33b56e2060404a4f0f88673e1e84b44c8952517e5f3"}, - {file = "aiohttp-3.10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f6979b4f20d3e557a867da9d9227de4c156fcdcb348a5848e3e6190fd7feb972"}, - {file = "aiohttp-3.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03c0c380c83f8a8d4416224aafb88d378376d6f4cadebb56b060688251055cd4"}, - {file = "aiohttp-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c2b104e81b3c3deba7e6f5bc1a9a0e9161c380530479970766a6655b8b77c7c"}, - {file = "aiohttp-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b023b68c61ab0cd48bd38416b421464a62c381e32b9dc7b4bdfa2905807452a4"}, - {file = "aiohttp-3.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a07c76a82390506ca0eabf57c0540cf5a60c993c442928fe4928472c4c6e5e6"}, - {file = "aiohttp-3.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:41d8dab8c64ded1edf117d2a64f353efa096c52b853ef461aebd49abae979f16"}, - {file = "aiohttp-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:615348fab1a9ef7d0960a905e83ad39051ae9cb0d2837da739b5d3a7671e497a"}, - {file = "aiohttp-3.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:256ee6044214ee9d66d531bb374f065ee94e60667d6bbeaa25ca111fc3997158"}, - {file = "aiohttp-3.10.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7d5bb926805022508b7ddeaad957f1fce7a8d77532068d7bdb431056dc630cd"}, - {file = "aiohttp-3.10.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:028faf71b338f069077af6315ad54281612705d68889f5d914318cbc2aab0d50"}, - {file = "aiohttp-3.10.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5c12310d153b27aa630750be44e79313acc4e864c421eb7d2bc6fa3429c41bf8"}, - {file = "aiohttp-3.10.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:de1a91d5faded9054957ed0a9e01b9d632109341942fc123947ced358c5d9009"}, - {file = "aiohttp-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9c186b270979fb1dee3ababe2d12fb243ed7da08b30abc83ebac3a928a4ddb15"}, - {file = "aiohttp-3.10.1-cp311-cp311-win32.whl", hash = "sha256:4a9ce70f5e00380377aac0e568abd075266ff992be2e271765f7b35d228a990c"}, - {file = "aiohttp-3.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:a77c79bac8d908d839d32c212aef2354d2246eb9deb3e2cb01ffa83fb7a6ea5d"}, - {file = "aiohttp-3.10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2212296cdb63b092e295c3e4b4b442e7b7eb41e8a30d0f53c16d5962efed395d"}, - {file = "aiohttp-3.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4dcb127ca3eb0a61205818a606393cbb60d93b7afb9accd2fd1e9081cc533144"}, - {file = "aiohttp-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb8b79a65332e1a426ccb6290ce0409e1dc16b4daac1cc5761e059127fa3d134"}, - {file = "aiohttp-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68cc24f707ed9cb961f6ee04020ca01de2c89b2811f3cf3361dc7c96a14bfbcc"}, - {file = "aiohttp-3.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cb54f5725b4b37af12edf6c9e834df59258c82c15a244daa521a065fbb11717"}, - {file = "aiohttp-3.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:51d03e948e53b3639ce4d438f3d1d8202898ec6655cadcc09ec99229d4adc2a9"}, - {file = "aiohttp-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:786299d719eb5d868f161aeec56d589396b053925b7e0ce36e983d30d0a3e55c"}, - {file = "aiohttp-3.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abda4009a30d51d3f06f36bc7411a62b3e647fa6cc935ef667e3e3d3a7dd09b1"}, - {file = "aiohttp-3.10.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:67f7639424c313125213954e93a6229d3a1d386855d70c292a12628f600c7150"}, - {file = "aiohttp-3.10.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8e5a26d7aac4c0d8414a347da162696eea0629fdce939ada6aedf951abb1d745"}, - {file = "aiohttp-3.10.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:120548d89f14b76a041088b582454d89389370632ee12bf39d919cc5c561d1ca"}, - {file = "aiohttp-3.10.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f5293726943bdcea24715b121d8c4ae12581441d22623b0e6ab12d07ce85f9c4"}, - {file = "aiohttp-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1f8605e573ed6c44ec689d94544b2c4bb1390aaa723a8b5a2cc0a5a485987a68"}, - {file = "aiohttp-3.10.1-cp312-cp312-win32.whl", hash = "sha256:e7168782621be4448d90169a60c8b37e9b0926b3b79b6097bc180c0a8a119e73"}, - {file = "aiohttp-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fbf8c0ded367c5c8eaf585f85ca8dd85ff4d5b73fb8fe1e6ac9e1b5e62e11f7"}, - {file = "aiohttp-3.10.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:54b7f4a20d7cc6bfa4438abbde069d417bb7a119f870975f78a2b99890226d55"}, - {file = "aiohttp-3.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2fa643ca990323db68911b92f3f7a0ca9ae300ae340d0235de87c523601e58d9"}, - {file = "aiohttp-3.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d8311d0d690487359fe2247ec5d2cac9946e70d50dced8c01ce9e72341c21151"}, - {file = "aiohttp-3.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222821c60b8f6a64c5908cb43d69c0ee978a1188f6a8433d4757d39231b42cdb"}, - {file = "aiohttp-3.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7b55d9ede66af7feb6de87ff277e0ccf6d51c7db74cc39337fe3a0e31b5872d"}, - {file = "aiohttp-3.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a95151a5567b3b00368e99e9c5334a919514f60888a6b6d2054fea5e66e527e"}, - {file = "aiohttp-3.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e9e9171d2fe6bfd9d3838a6fe63b1e91b55e0bf726c16edf265536e4eafed19"}, - {file = "aiohttp-3.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a57e73f9523e980f6101dc9a83adcd7ac0006ea8bf7937ca3870391c7bb4f8ff"}, - {file = "aiohttp-3.10.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0df51a3d70a2bfbb9c921619f68d6d02591f24f10e9c76de6f3388c89ed01de6"}, - {file = "aiohttp-3.10.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:b0de63ff0307eac3961b4af74382d30220d4813f36b7aaaf57f063a1243b4214"}, - {file = "aiohttp-3.10.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:8db9b749f589b5af8e4993623dbda6716b2b7a5fcb0fa2277bf3ce4b278c7059"}, - {file = "aiohttp-3.10.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6b14c19172eb53b63931d3e62a9749d6519f7c121149493e6eefca055fcdb352"}, - {file = "aiohttp-3.10.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cd57ad998e3038aa87c38fe85c99ed728001bf5dde8eca121cadee06ee3f637"}, - {file = "aiohttp-3.10.1-cp38-cp38-win32.whl", hash = "sha256:df31641e3f02b77eb3c5fb63c0508bee0fc067cf153da0e002ebbb0db0b6d91a"}, - {file = "aiohttp-3.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:93094eba50bc2ad4c40ff4997ead1fdcd41536116f2e7d6cfec9596a8ecb3615"}, - {file = "aiohttp-3.10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:440954ddc6b77257e67170d57b1026aa9545275c33312357472504eef7b4cc0b"}, - {file = "aiohttp-3.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f9f8beed277488a52ee2b459b23c4135e54d6a819eaba2e120e57311015b58e9"}, - {file = "aiohttp-3.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d8a8221a63602008550022aa3a4152ca357e1dde7ab3dd1da7e1925050b56863"}, - {file = "aiohttp-3.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a702bd3663b5cbf3916e84bf332400d24cdb18399f0877ca6b313ce6c08bfb43"}, - {file = "aiohttp-3.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1988b370536eb14f0ce7f3a4a5b422ab64c4e255b3f5d7752c5f583dc8c967fc"}, - {file = "aiohttp-3.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7ccf1f0a304352c891d124ac1a9dea59b14b2abed1704aaa7689fc90ef9c5be1"}, - {file = "aiohttp-3.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc3ea6ef2a83edad84bbdb5d96e22f587b67c68922cd7b6f9d8f24865e655bcf"}, - {file = "aiohttp-3.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b47c125ab07f0831803b88aeb12b04c564d5f07a1c1a225d4eb4d2f26e8b5e"}, - {file = "aiohttp-3.10.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:21778552ef3d44aac3278cc6f6d13a6423504fa5f09f2df34bfe489ed9ded7f5"}, - {file = "aiohttp-3.10.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bde0693073fd5e542e46ea100aa6c1a5d36282dbdbad85b1c3365d5421490a92"}, - {file = "aiohttp-3.10.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:bf66149bb348d8e713f3a8e0b4f5b952094c2948c408e1cfef03b49e86745d60"}, - {file = "aiohttp-3.10.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:587237571a85716d6f71f60d103416c9df7d5acb55d96d3d3ced65f39bff9c0c"}, - {file = "aiohttp-3.10.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bfe33cba6e127d0b5b417623c9aa621f0a69f304742acdca929a9fdab4593693"}, - {file = "aiohttp-3.10.1-cp39-cp39-win32.whl", hash = "sha256:9fbff00646cf8211b330690eb2fd64b23e1ce5b63a342436c1d1d6951d53d8dd"}, - {file = "aiohttp-3.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:5951c328f9ac42d7bce7a6ded535879bc9ae13032818d036749631fa27777905"}, - {file = "aiohttp-3.10.1.tar.gz", hash = "sha256:8b0d058e4e425d3b45e8ec70d49b402f4d6b21041e674798b1f91ba027c73f28"}, + {file = "aiohttp-3.10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:95213b3d79c7e387144e9cb7b9d2809092d6ff2c044cb59033aedc612f38fb6d"}, + {file = "aiohttp-3.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1aa005f060aff7124cfadaa2493f00a4e28ed41b232add5869e129a2e395935a"}, + {file = "aiohttp-3.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eabe6bf4c199687592f5de4ccd383945f485779c7ffb62a9b9f1f8a3f9756df8"}, + {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e010736fc16d21125c7e2dc5c350cd43c528b85085c04bf73a77be328fe944"}, + {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99f81f9c1529fd8e03be4a7bd7df32d14b4f856e90ef6e9cbad3415dbfa9166c"}, + {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d611d1a01c25277bcdea06879afbc11472e33ce842322496b211319aa95441bb"}, + {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00191d38156e09e8c81ef3d75c0d70d4f209b8381e71622165f22ef7da6f101"}, + {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74c091a5ded6cb81785de2d7a8ab703731f26de910dbe0f3934eabef4ae417cc"}, + {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:18186a80ec5a701816adbf1d779926e1069392cf18504528d6e52e14b5920525"}, + {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5a7ceb2a0d2280f23a02c64cd0afdc922079bb950400c3dd13a1ab2988428aac"}, + {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8bd7be6ff6c162a60cb8fce65ee879a684fbb63d5466aba3fa5b9288eb04aefa"}, + {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fae962b62944eaebff4f4fddcf1a69de919e7b967136a318533d82d93c3c6bd1"}, + {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a0fde16d284efcacbe15fb0c1013f0967b6c3e379649239d783868230bf1db42"}, + {file = "aiohttp-3.10.2-cp310-cp310-win32.whl", hash = "sha256:f81cd85a0e76ec7b8e2b6636fe02952d35befda4196b8c88f3cec5b4fb512839"}, + {file = "aiohttp-3.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:54ba10eb5a3481c28282eb6afb5f709aedf53cf9c3a31875ffbdc9fc719ffd67"}, + {file = "aiohttp-3.10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:87fab7f948e407444c2f57088286e00e2ed0003ceaf3d8f8cc0f60544ba61d91"}, + {file = "aiohttp-3.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec6ad66ed660d46503243cbec7b2b3d8ddfa020f984209b3b8ef7d98ce69c3f2"}, + {file = "aiohttp-3.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a4be88807283bd96ae7b8e401abde4ca0bab597ba73b5e9a2d98f36d451e9aac"}, + {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01c98041f90927c2cbd72c22a164bb816fa3010a047d264969cf82e1d4bcf8d1"}, + {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54e36c67e1a9273ecafab18d6693da0fb5ac48fd48417e4548ac24a918c20998"}, + {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7de3ddb6f424af54535424082a1b5d1ae8caf8256ebd445be68c31c662354720"}, + {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dd9c7db94b4692b827ce51dcee597d61a0e4f4661162424faf65106775b40e7"}, + {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e57e21e1167705f8482ca29cc5d02702208d8bf4aff58f766d94bcd6ead838cd"}, + {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a1a50e59b720060c29e2951fd9f13c01e1ea9492e5a527b92cfe04dd64453c16"}, + {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:686c87782481fda5ee6ba572d912a5c26d9f98cc5c243ebd03f95222af3f1b0f"}, + {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:dafb4abb257c0ed56dc36f4e928a7341b34b1379bd87e5a15ce5d883c2c90574"}, + {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:494a6f77560e02bd7d1ab579fdf8192390567fc96a603f21370f6e63690b7f3d"}, + {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6fe8503b1b917508cc68bf44dae28823ac05e9f091021e0c41f806ebbb23f92f"}, + {file = "aiohttp-3.10.2-cp311-cp311-win32.whl", hash = "sha256:4ddb43d06ce786221c0dfd3c91b4892c318eaa36b903f7c4278e7e2fa0dd5102"}, + {file = "aiohttp-3.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:ca2f5abcb0a9a47e56bac173c01e9f6c6e7f27534d91451c5f22e6a35a5a2093"}, + {file = "aiohttp-3.10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:14eb6b17f6246959fb0b035d4f4ae52caa870c4edfb6170aad14c0de5bfbf478"}, + {file = "aiohttp-3.10.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:465e445ec348d4e4bd349edd8b22db75f025da9d7b6dc1369c48e7935b85581e"}, + {file = "aiohttp-3.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:341f8ece0276a828d95b70cd265d20e257f5132b46bf77d759d7f4e0443f2906"}, + {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c01fbb87b5426381cd9418b3ddcf4fc107e296fa2d3446c18ce6c76642f340a3"}, + {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c474af073e1a6763e1c5522bbb2d85ff8318197e4c6c919b8d7886e16213345"}, + {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d9076810a5621236e29b2204e67a68e1fe317c8727ee4c9abbfbb1083b442c38"}, + {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8f515d6859e673940e08de3922b9c4a2249653b0ac181169313bd6e4b1978ac"}, + {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:655e583afc639bef06f3b2446972c1726007a21003cd0ef57116a123e44601bc"}, + {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8da9449a575133828cc99985536552ea2dcd690e848f9d41b48d8853a149a959"}, + {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:19073d57d0feb1865d12361e2a1f5a49cb764bf81a4024a3b608ab521568093a"}, + {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8e98e1845805f184d91fda6f9ab93d7c7b0dddf1c07e0255924bfdb151a8d05"}, + {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:377220a5efde6f9497c5b74649b8c261d3cce8a84cb661be2ed8099a2196400a"}, + {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:92f7f4a4dc9cdb5980973a74d43cdbb16286dacf8d1896b6c3023b8ba8436f8e"}, + {file = "aiohttp-3.10.2-cp312-cp312-win32.whl", hash = "sha256:9bb2834a6f11d65374ce97d366d6311a9155ef92c4f0cee543b2155d06dc921f"}, + {file = "aiohttp-3.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:518dc3cb37365255708283d1c1c54485bbacccd84f0a0fb87ed8917ba45eda5b"}, + {file = "aiohttp-3.10.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7f98e70bbbf693086efe4b86d381efad8edac040b8ad02821453083d15ec315f"}, + {file = "aiohttp-3.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f6f0b252a009e98fe84028a4ec48396a948e7a65b8be06ccfc6ef68cf1f614d"}, + {file = "aiohttp-3.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9360e3ffc7b23565600e729e8c639c3c50d5520e05fdf94aa2bd859eef12c407"}, + {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3988044d1635c7821dd44f0edfbe47e9875427464e59d548aece447f8c22800a"}, + {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a9d59da1543a6f1478c3436fd49ec59be3868bca561a33778b4391005e499d"}, + {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9f49bdb94809ac56e09a310a62f33e5f22973d6fd351aac72a39cd551e98194"}, + {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddfd2dca3f11c365d6857a07e7d12985afc59798458a2fdb2ffa4a0332a3fd43"}, + {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c1508ec97b2cd3e120bfe309a4ff8e852e8a7460f1ef1de00c2c0ed01e33c"}, + {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:49904f38667c44c041a0b44c474b3ae36948d16a0398a8f8cd84e2bb3c42a069"}, + {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:352f3a4e5f11f3241a49b6a48bc5b935fabc35d1165fa0d87f3ca99c1fcca98b"}, + {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:fc61f39b534c5d5903490478a0dd349df397d2284a939aa3cbaa2fb7a19b8397"}, + {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:ad2274e707be37420d0b6c3d26a8115295fe9d8e6e530fa6a42487a8ca3ad052"}, + {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c836bf3c7512100219fe1123743fd8dd9a2b50dd7cfb0c3bb10d041309acab4b"}, + {file = "aiohttp-3.10.2-cp38-cp38-win32.whl", hash = "sha256:53e8898adda402be03ff164b0878abe2d884e3ea03a4701e6ad55399d84b92dc"}, + {file = "aiohttp-3.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:7cc8f65f5b22304693de05a245b6736b14cb5bc9c8a03da6e2ae9ef15f8b458f"}, + {file = "aiohttp-3.10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9dfc906d656e14004c5bc672399c1cccc10db38df2b62a13fb2b6e165a81c316"}, + {file = "aiohttp-3.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:91b10208b222ddf655c3a3d5b727879d7163db12b634492df41a9182a76edaae"}, + {file = "aiohttp-3.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9fd16b5e1a7bdd14668cd6bde60a2a29b49147a535c74f50d8177d11b38433a7"}, + {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2bfdda4971bd79201f59adbad24ec2728875237e1c83bba5221284dbbf57bda"}, + {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69d73f869cf29e8a373127fc378014e2b17bcfbe8d89134bc6fb06a2f67f3cb3"}, + {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df59f8486507c421c0620a2c3dce81fbf1d54018dc20ff4fecdb2c106d6e6abc"}, + {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df930015db36b460aa9badbf35eccbc383f00d52d4b6f3de2ccb57d064a6ade"}, + {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:562b1153ab7f766ee6b8b357ec777a302770ad017cf18505d34f1c088fccc448"}, + {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d984db6d855de58e0fde1ef908d48fe9a634cadb3cf715962722b4da1c40619d"}, + {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:14dc3fcb0d877911d775d511eb617a486a8c48afca0a887276e63db04d3ee920"}, + {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b52a27a5c97275e254704e1049f4b96a81e67d6205f52fa37a4777d55b0e98ef"}, + {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:cd33d9de8cfd006a0d0fe85f49b4183c57e91d18ffb7e9004ce855e81928f704"}, + {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1238fc979160bc03a92fff9ad021375ff1c8799c6aacb0d8ea1b357ea40932bb"}, + {file = "aiohttp-3.10.2-cp39-cp39-win32.whl", hash = "sha256:e2f43d238eae4f0b04f58d4c0df4615697d4ca3e9f9b1963d49555a94f0f5a04"}, + {file = "aiohttp-3.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:947847f07a8f81d7b39b2d0202fd73e61962ebe17ac2d8566f260679e467da7b"}, + {file = "aiohttp-3.10.2.tar.gz", hash = "sha256:4d1f694b5d6e459352e5e925a42e05bac66655bfde44d81c59992463d2897014"}, ] [package.dependencies] @@ -2024,13 +2024,13 @@ files = [ [[package]] name = "types-pyyaml" -version = "6.0.12.20240724" +version = "6.0.12.20240808" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.8" files = [ - {file = "types-PyYAML-6.0.12.20240724.tar.gz", hash = "sha256:cf7b31ae67e0c5b2919c703d2affc415485099d3fe6666a6912f040fd05cb67f"}, - {file = "types_PyYAML-6.0.12.20240724-py3-none-any.whl", hash = "sha256:e5becec598f3aa3a2ddf671de4a75fa1c6856fbf73b2840286c9d50fae2d5d48"}, + {file = "types-PyYAML-6.0.12.20240808.tar.gz", hash = "sha256:b8f76ddbd7f65440a8bda5526a9607e4c7a322dc2f8e1a8c405644f9a6f4b9af"}, + {file = "types_PyYAML-6.0.12.20240808-py3-none-any.whl", hash = "sha256:deda34c5c655265fc517b546c902aa6eed2ef8d3e921e4765fe606fe2afe8d35"}, ] [[package]] @@ -2234,4 +2234,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "f9e3b36233d6aa14d7b478cca8311df8fe44f1fccad51b12e76bb256cc4867db" +content-hash = "1ef889fffeeaa1d00dc8f59d6e4eca78b294c68160bac36eec356f0a43eb0c38" From 43bb5893718fdbfb81fcadc354b23c7456111800 Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 10 Aug 2024 00:15:36 +0000 Subject: [PATCH 31/40] work --- utils/msl.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/utils/msl.py b/utils/msl.py index 70b5c3c6..a5098fc3 100644 --- a/utils/msl.py +++ b/utils/msl.py @@ -45,6 +45,14 @@ BUTTON_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$fsSetDates$btnSubmit" EVENT_TABLE_ID: Final[str] = "ctl00_ctl00_Main_AdminPageContent_gvEvents" +MEMBER_HTML_TABLE_IDS: Final[frozenset[str]] = frozenset( + { + "ctl00_Main_rptGroups_ctl05_gvMemberships", + "ctl00_Main_rptGroups_ctl03_gvMemberships", + }, +) + + class MSL: """Class to define the functions related to MSL based SU websites.""" @@ -143,7 +151,12 @@ class MSLMemberships: @staticmethod async def get_full_membership_list() -> list[tuple[str, str]]: """Get a list of tuples of student ID to names.""" - + http_session: aiohttp.ClientSession = aiohttp.ClientSession( + headers=BASE_HEADERS, + cookies=BASE_COOKIES, + ) + async with http_session, http_session.get(url=MSL_URLS["MEMBERS_LIST"]) as http_response: + response_html: str = await http_response.text() return [] From 04928514514ae746a73371721a43d06324daa8ba Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 10 Aug 2024 15:57:44 +0100 Subject: [PATCH 32/40] add membership list handling --- cogs/events.py | 33 ++++++++++++++++++++++++++++++++- utils/msl.py | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index be9b2115..0dd7525a 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -12,6 +12,7 @@ import dateutil.parser import discord +from utils.msl import MSL from dateutil.parser import ParserError from config import settings @@ -22,7 +23,6 @@ logger: Final[Logger] = logging.getLogger("TeX-Bot") - BASE_HEADERS: Final[Mapping[str, str]] = { "Cache-Control": "no-cache", "Pragma": "no-cache", @@ -58,6 +58,7 @@ class EventsManagementCommandsCog(TeXBotBaseCog): @CommandChecks.check_interaction_user_in_main_guild async def get_events(self, ctx: TeXBotApplicationContext, *, str_from_date: str, str_to_date: str) -> None: # noqa: E501 """Command to get the events on the guild website.""" + return try: if str_from_date: from_date_dt = dateutil.parser.parse(str_from_date, dayfirst=True) @@ -241,6 +242,7 @@ async def setup_event(self, ctx: TeXBotApplicationContext, str_event_title: str, @CommandChecks.check_interaction_user_in_main_guild async def get_msl_context(self, ctx: TeXBotApplicationContext, str_url: str) -> None: """Command to get the MSL context for a given URL.""" + return data_fields, cookies = await self._get_msl_context(str_url) logger.debug(data_fields) logger.debug(cookies) @@ -251,3 +253,32 @@ async def get_msl_context(self, ctx: TeXBotApplicationContext, str_url: str) -> ), ) + + + @discord.slash_command( # type: ignore[no-untyped-call, misc] + name="get-member-list", + description="Returns the list of members on the guild website.", + ) + @CommandChecks.check_interaction_user_has_committee_role + @CommandChecks.check_interaction_user_in_main_guild + async def get_member_list(self, ctx: TeXBotApplicationContext) -> None: + """Command to get the member list on the guild website.""" + initial_response: discord.Interaction | discord.WebhookMessage = await ctx.respond( + content="Fetching member list...", + ) + + member_list: list[tuple[str, int]] = await MSL.MSLMemberships.get_full_membership_list() + + if not member_list: + await initial_response.edit(content="No members found on the guild website.") + return + + member_list_message: str = ( + f"Found {len(member_list)} members on the guild website:\n" + + "\n".join( + f"{member[0]} - {member[1]}" + for member in member_list + ) + ) + + await initial_response.edit(content=member_list_message) diff --git a/utils/msl.py b/utils/msl.py index a5098fc3..97bb644c 100644 --- a/utils/msl.py +++ b/utils/msl.py @@ -149,7 +149,7 @@ class MSLMemberships: """Class to define Membership specific MSL methods.""" @staticmethod - async def get_full_membership_list() -> list[tuple[str, str]]: + async def get_full_membership_list() -> list[tuple[str, int]]: """Get a list of tuples of student ID to names.""" http_session: aiohttp.ClientSession = aiohttp.ClientSession( headers=BASE_HEADERS, @@ -158,8 +158,42 @@ async def get_full_membership_list() -> list[tuple[str, str]]: async with http_session, http_session.get(url=MSL_URLS["MEMBERS_LIST"]) as http_response: response_html: str = await http_response.text() + standard_members_table: bs4.Tag | bs4.NavigableString | None = BeautifulSoup( + markup=response_html, + features="html.parser", + ).find( + name="table", + attrs={"id": "ctl00_Main_rptGroups_ctl03_gvMemberships"}, + ) + + all_members_table: bs4.Tag | bs4.NavigableString | None = BeautifulSoup( + markup=response_html, + features="html.parser", + ).find( + name="table", + attrs={"id": "ctl00_Main_rptGroups_ctl05_gvMemberships"}, + ) + + if standard_members_table is None or all_members_table is None: + return [] + + if isinstance(standard_members_table, bs4.NavigableString) or isinstance(all_members_table, bs4.NavigableString): # noqa: E501 + return [] + + standard_members: list[bs4.Tag] = standard_members_table.find_all(name="tr") + all_members: list[bs4.Tag] = all_members_table.find_all(name="tr") + + standard_members.pop(0) + all_members.pop(0) + + member_list: list[tuple[str, int]] = [( + member.find_all(name="td")[0].text.strip(), + member.find_all(name="td")[1].text.strip(), # NOTE: This will not properly handle external members who do not have an ID... + ) + for member in standard_members + all_members + ] - return [] + return member_list class MSLSalesReports: """Class to define Sales Reports specific MSL methods.""" From 31bdd1995152581ebb57f57d8e480ff867767ba6 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 10 Aug 2024 16:26:12 +0100 Subject: [PATCH 33/40] do stuff --- utils/msl.py | 56 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/utils/msl.py b/utils/msl.py index 97bb644c..b7f4f856 100644 --- a/utils/msl.py +++ b/utils/msl.py @@ -7,6 +7,7 @@ import logging from collections.abc import Mapping +from datetime import datetime from logging import Logger from typing import TYPE_CHECKING, Final @@ -40,11 +41,6 @@ ".ASPXAUTH": settings["MEMBERS_LIST_AUTH_SESSION_COOKIE"], } -FROM_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$datesFilter$txtFromDate" -TO_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$datesFilter$txtToDate" -BUTTON_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$fsSetDates$btnSubmit" -EVENT_TABLE_ID: Final[str] = "ctl00_ctl00_Main_AdminPageContent_gvEvents" - MEMBER_HTML_TABLE_IDS: Final[frozenset[str]] = frozenset( { "ctl00_Main_rptGroups_ctl05_gvMemberships", @@ -86,6 +82,11 @@ async def _get_msl_context(url: str) -> tuple[dict[str, str], dict[str, str]]: class MSLEvents: """Class to define Event specific MSL methods.""" + FROM_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$datesFilter$txtFromDate" + TO_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$datesFilter$txtToDate" + BUTTON_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$fsSetDates$btnSubmit" + EVENT_TABLE_ID: Final[str] = "ctl00_ctl00_Main_AdminPageContent_gvEvents" + async def _get_all_guild_events(self, from_date: str, to_date: str) -> dict[str, str]: """Fetch all events on the guild website.""" EVENT_LIST_URL: Final[str] = MSL_URLS["EVENT_LIST"] @@ -93,9 +94,9 @@ async def _get_all_guild_events(self, from_date: str, to_date: str) -> dict[str, data_fields, cookies = await MSL._get_msl_context(url=EVENT_LIST_URL) form_data: dict[str, str] = { - FROM_DATE_KEY: from_date, - TO_DATE_KEY: to_date, - BUTTON_KEY: "Find Events", + self.FROM_DATE_KEY: from_date, + self.TO_DATE_KEY: to_date, + self.BUTTON_KEY: "Find Events", "__EVENTTARGET": "", "__EVENTARGUMENT": "", "__VIEWSTATEENCRYPTED": "", @@ -120,7 +121,7 @@ async def _get_all_guild_events(self, from_date: str, to_date: str) -> dict[str, features="html.parser", ).find( name="table", - attrs={"id": EVENT_TABLE_ID}, + attrs={"id": self.EVENT_TABLE_ID}, ) if event_table_html is None or isinstance(event_table_html, bs4.NavigableString): @@ -198,3 +199,40 @@ async def get_full_membership_list() -> list[tuple[str, int]]: class MSLSalesReports: """Class to define Sales Reports specific MSL methods.""" + FROM_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$drDateRange$txtFromDate" + FROM_TIME_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$drDateRange$txtFromTime" + TO_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$drDateRange$txtToDate" + TO_TIME_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$drDateRange$txtToTime" + + async def get_all_sales_report(self, from_date: datetime, to_date: datetime) -> None: + """Get all sales reports from the guild website.""" + SALES_REPORT_URL: Final[str] = MSL_URLS["SALES_REPORTS"] + + data_fields, cookies = await MSL._get_msl_context(url=SALES_REPORT_URL) + + form_data: dict[str, str] = { + self.FROM_DATE_KEY: from_date.strftime("%d/%m/%Y"), + self.TO_TIME_KEY: from_date.strftime("%H:%M"), + self.TO_DATE_KEY: to_date.strftime("%d/%m/%Y"), + self.TO_TIME_KEY: to_date.strftime("%H:%M"), + "__EVENTTARGET": "ctl00$ctl00$Main$AdminPageContent$lbSales", + "__EVENTARGUMENT": "", + "__VIEWSTATEENCRYPTED": "", + } + + data_fields.update(form_data) + + session_v2: aiohttp.ClientSession = aiohttp.ClientSession( + headers=BASE_HEADERS, + cookies=cookies, + ) + async with session_v2, session_v2.post(url=SALES_REPORT_URL, data=data_fields) as http_response: + if http_response.status != 200: + logger.debug("Returned a non 200 status code!!") + logger.debug(http_response) + return + + response_html: str = await http_response.text() + + + From 738437babbc327e54d9ab6de46a94415225faf9a Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Sat, 10 Aug 2024 16:58:36 +0100 Subject: [PATCH 34/40] more --- cogs/events.py | 42 +++++++++++++++++++++++++++++++++++------- utils/msl.py | 14 +++++++++++++- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index 0dd7525a..41451a27 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -5,21 +5,19 @@ __all__: Sequence[str] = ("EventsManagementCommandsCog",) +import datetime import logging from collections.abc import Mapping from logging import Logger -from typing import TYPE_CHECKING, Final +from typing import Final import dateutil.parser import discord -from utils.msl import MSL from dateutil.parser import ParserError from config import settings from utils import CommandChecks, GoogleCalendar, TeXBotApplicationContext, TeXBotBaseCog - -if TYPE_CHECKING: - import datetime +from utils.msl import MSL logger: Final[Logger] = logging.getLogger("TeX-Bot") @@ -227,6 +225,15 @@ async def setup_event(self, ctx: TeXBotApplicationContext, str_event_title: str, await ctx.respond(f"Event created successful!\n{new_discord_event}") + + + + + + + + + # TODO: THESE COMMANDS ARE FOR TESTING PURPOSES ONLY AND MUST BE REMOVED @discord.slash_command( # type: ignore[no-untyped-call, misc] name="get-msl-context", description="debug command to check the msl context retrieved for a given url", @@ -242,8 +249,7 @@ async def setup_event(self, ctx: TeXBotApplicationContext, str_event_title: str, @CommandChecks.check_interaction_user_in_main_guild async def get_msl_context(self, ctx: TeXBotApplicationContext, str_url: str) -> None: """Command to get the MSL context for a given URL.""" - return - data_fields, cookies = await self._get_msl_context(str_url) + data_fields, cookies = await MSL._get_msl_context(str_url) # noqa: SLF001 logger.debug(data_fields) logger.debug(cookies) await ctx.respond( @@ -282,3 +288,25 @@ async def get_member_list(self, ctx: TeXBotApplicationContext) -> None: ) await initial_response.edit(content=member_list_message) + + + @discord.slash_command( # type: ignore[no-untyped-call, misc] + name="get-sales-reports", + description="Returns the sales reports on the guild website.", + ) + @CommandChecks.check_interaction_user_has_committee_role + @CommandChecks.check_interaction_user_in_main_guild + async def get_sales_reports(self, ctx: TeXBotApplicationContext) -> None: + """Command to get the sales reports on the guild website.""" + initial_response: discord.Interaction | discord.WebhookMessage = await ctx.respond( + content="Fetching sales reports...", + ) + + from_date: datetime.datetime = datetime.datetime(year=2024, month=7, day=1, tzinfo=datetime.UTC) + to_date: datetime.datetime = datetime.datetime(year=2024, month=8, day=9, tzinfo=datetime.UTC) + + sales_report_object: MSL.MSLSalesReports = MSL.MSLSalesReports() + + await MSL.MSLSalesReports.get_all_sales_report(self=sales_report_object, from_date=from_date, to_date=to_date) + + await initial_response.edit(content="Done!") diff --git a/utils/msl.py b/utils/msl.py index b7f4f856..0e0fdcc1 100644 --- a/utils/msl.py +++ b/utils/msl.py @@ -232,7 +232,19 @@ async def get_all_sales_report(self, from_date: datetime, to_date: datetime) -> logger.debug(http_response) return - response_html: str = await http_response.text() + response_html: str = await http_response.text() + + logger.debug(response_html) + + sales_report_table: bs4.Tag | bs4.NavigableString | None = BeautifulSoup( + markup=response_html, + features="html.parser", + ).find( + name="table", + attrs={"class": "A22a30fa654c04e588b579b10a158372a310"}, + ) + + logger.debug(sales_report_table) From 7ae83a2836b7c12c87a9b7d66fd18ca080dbbc3b Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Tue, 13 Aug 2024 18:53:35 +0100 Subject: [PATCH 35/40] implement csv download --- cogs/events.py | 12 +++++------- utils/msl.py | 35 +++++++++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index 41451a27..a56bf482 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -56,7 +56,6 @@ class EventsManagementCommandsCog(TeXBotBaseCog): @CommandChecks.check_interaction_user_in_main_guild async def get_events(self, ctx: TeXBotApplicationContext, *, str_from_date: str, str_to_date: str) -> None: # noqa: E501 """Command to get the events on the guild website.""" - return try: if str_from_date: from_date_dt = dateutil.parser.parse(str_from_date, dayfirst=True) @@ -85,7 +84,9 @@ async def get_events(self, ctx: TeXBotApplicationContext, *, str_from_date: str, formatted_from_date: str = from_date_dt.strftime("%d/%m/%Y") formatted_to_date: str = to_date_dt.strftime(format="%d/%m/%Y") - await self._get_all_guild_events(ctx, formatted_from_date, formatted_to_date) + events_object: MSL.MSLEvents = MSL.MSLEvents() + + await events_object._get_all_guild_events(formatted_from_date, formatted_to_date) events: list[dict[str, str]] | None = await GoogleCalendar.fetch_events() @@ -296,17 +297,14 @@ async def get_member_list(self, ctx: TeXBotApplicationContext) -> None: ) @CommandChecks.check_interaction_user_has_committee_role @CommandChecks.check_interaction_user_in_main_guild - async def get_sales_reports(self, ctx: TeXBotApplicationContext) -> None: + async def update_sales_report(self, ctx: TeXBotApplicationContext) -> None: """Command to get the sales reports on the guild website.""" initial_response: discord.Interaction | discord.WebhookMessage = await ctx.respond( content="Fetching sales reports...", ) - from_date: datetime.datetime = datetime.datetime(year=2024, month=7, day=1, tzinfo=datetime.UTC) - to_date: datetime.datetime = datetime.datetime(year=2024, month=8, day=9, tzinfo=datetime.UTC) - sales_report_object: MSL.MSLSalesReports = MSL.MSLSalesReports() - await MSL.MSLSalesReports.get_all_sales_report(self=sales_report_object, from_date=from_date, to_date=to_date) + await sales_report_object.update_current_year_sales_report() await initial_response.edit(content="Done!") diff --git a/utils/msl.py b/utils/msl.py index 0e0fdcc1..dc681b44 100644 --- a/utils/msl.py +++ b/utils/msl.py @@ -6,12 +6,14 @@ import logging +import re from collections.abc import Mapping from datetime import datetime from logging import Logger from typing import TYPE_CHECKING, Final import aiohttp +import anyio import bs4 from bs4 import BeautifulSoup @@ -204,12 +206,15 @@ class MSLSalesReports: TO_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$drDateRange$txtToDate" TO_TIME_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$drDateRange$txtToTime" - async def get_all_sales_report(self, from_date: datetime, to_date: datetime) -> None: + async def update_current_year_sales_report(self) -> None: """Get all sales reports from the guild website.""" SALES_REPORT_URL: Final[str] = MSL_URLS["SALES_REPORTS"] data_fields, cookies = await MSL._get_msl_context(url=SALES_REPORT_URL) + from_date: datetime = datetime(year=datetime.now().year, month=7, day=1) + to_date: datetime = datetime(year=datetime.now().year + 1, month=6, day=30) + form_data: dict[str, str] = { self.FROM_DATE_KEY: from_date.strftime("%d/%m/%Y"), self.TO_TIME_KEY: from_date.strftime("%H:%M"), @@ -220,6 +225,8 @@ async def get_all_sales_report(self, from_date: datetime, to_date: datetime) -> "__VIEWSTATEENCRYPTED": "", } + data_fields.pop("ctl00$ctl00$search$btnSubmit") + data_fields.update(form_data) session_v2: aiohttp.ClientSession = aiohttp.ClientSession( @@ -234,17 +241,33 @@ async def get_all_sales_report(self, from_date: datetime, to_date: datetime) -> response_html: str = await http_response.text() - logger.debug(response_html) - sales_report_table: bs4.Tag | bs4.NavigableString | None = BeautifulSoup( markup=response_html, features="html.parser", ).find( - name="table", - attrs={"class": "A22a30fa654c04e588b579b10a158372a310"}, + name="div", + attrs={"id": "ctl00_ctl00_Main_AdminPageContent_ReportViewer1_ctl13"}, ) - logger.debug(sales_report_table) + if not isinstance(sales_report_table, bs4.Tag): + logger.debug("Couldn't find the sales reports!!") + return + + match = re.search(r'ExportUrlBase":"(.*?)"', response_html) + if not match: + logger.debug("Couldn't find the export URL!!") + return + urlbase: str = match.group(1).replace(r"\u0026", "&").replace("\\/", "/") + report_url: str = f"https://guildofstudents.com/{urlbase}CSV" + + file_session: aiohttp.ClientSession = aiohttp.ClientSession( + headers=BASE_HEADERS, + cookies=cookies, + ) + async with file_session, file_session.get(url=report_url) as file_response: + if file_response.status == 200: + async with await anyio.open_file("CurrentYearSalesReport.csv", "wb") as report_file: + await report_file.write(await file_response.read()) From 6184dee792536460bf2e7703816362389798faf3 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Tue, 13 Aug 2024 20:48:30 +0100 Subject: [PATCH 36/40] clean csv --- utils/msl.py | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/utils/msl.py b/utils/msl.py index dc681b44..211ddac4 100644 --- a/utils/msl.py +++ b/utils/msl.py @@ -158,7 +158,7 @@ async def get_full_membership_list() -> list[tuple[str, int]]: headers=BASE_HEADERS, cookies=BASE_COOKIES, ) - async with http_session, http_session.get(url=MSL_URLS["MEMBERS_LIST"]) as http_response: + async with http_session, http_session.get(url=MSL_URLS["MEMBERS_LIST"]) as http_response: # noqa: E501 response_html: str = await http_response.text() standard_members_table: bs4.Tag | bs4.NavigableString | None = BeautifulSoup( @@ -233,7 +233,7 @@ async def update_current_year_sales_report(self) -> None: headers=BASE_HEADERS, cookies=cookies, ) - async with session_v2, session_v2.post(url=SALES_REPORT_URL, data=data_fields) as http_response: + async with session_v2, session_v2.post(url=SALES_REPORT_URL, data=data_fields) as http_response: # noqa: E501 if http_response.status != 200: logger.debug("Returned a non 200 status code!!") logger.debug(http_response) @@ -268,6 +268,36 @@ async def update_current_year_sales_report(self) -> None: ) async with file_session, file_session.get(url=report_url) as file_response: if file_response.status == 200: - async with await anyio.open_file("CurrentYearSalesReport.csv", "wb") as report_file: - await report_file.write(await file_response.read()) + async with await anyio.open_file("CurrentYearSalesReport.csv", "wb") as report_file: # noqa: E501 + # skip the first 6 lines + # the columns we want are: 1, 6, 7, 8, 9 + # these are: product, date, qty, unit price, total + await report_file.write( + b"product_id,product_name,date,quantity,unit_price,total\n", + ) + + for line in (await file_response.read()).split(b"\n")[7:]: + if line == b"\r" or not line: + break + + values: list[bytes] = line.split(b",") + + product_name_and_id: bytes = values[0] + product_id: bytes = product_name_and_id.split(b" ")[0].removeprefix(b"[").removesuffix(b"]") # noqa: E501 + product_name: bytes = b" ".join(product_name_and_id.split(b" ")[1:]) # noqa: E501 + + # get the date, quantity, unit price and total + date: bytes = values[5] + quantity: bytes = values[6] + unit_price: bytes = values[7] + total: bytes = values[8] + + await report_file.write( + product_id + b"," + product_name + b"," + date + b"," + quantity + b"," + unit_price + b"," + total + b"\n", # noqa: E501 + ) + + logger.debug("Sales report updated successfully!!") + + async def get_product_sales(self, product_id: str) -> dict[str, int]: + return {} From e36d8e4edd29ee33e051c9a6ef882b71e508149c Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Tue, 13 Aug 2024 22:51:08 +0100 Subject: [PATCH 37/40] more --- utils/msl.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/utils/msl.py b/utils/msl.py index 211ddac4..722fbb29 100644 --- a/utils/msl.py +++ b/utils/msl.py @@ -269,10 +269,6 @@ async def update_current_year_sales_report(self) -> None: async with file_session, file_session.get(url=report_url) as file_response: if file_response.status == 200: async with await anyio.open_file("CurrentYearSalesReport.csv", "wb") as report_file: # noqa: E501 - # skip the first 6 lines - # the columns we want are: 1, 6, 7, 8, 9 - # these are: product, date, qty, unit price, total - await report_file.write( b"product_id,product_name,date,quantity,unit_price,total\n", ) @@ -286,8 +282,6 @@ async def update_current_year_sales_report(self) -> None: product_name_and_id: bytes = values[0] product_id: bytes = product_name_and_id.split(b" ")[0].removeprefix(b"[").removesuffix(b"]") # noqa: E501 product_name: bytes = b" ".join(product_name_and_id.split(b" ")[1:]) # noqa: E501 - - # get the date, quantity, unit price and total date: bytes = values[5] quantity: bytes = values[6] unit_price: bytes = values[7] From fee28ea1ff21b7d0a04ca859478dd9e4fe372b8b Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Thu, 15 Aug 2024 19:10:13 +0100 Subject: [PATCH 38/40] implement some stuff --- cogs/sales_data.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 cogs/sales_data.py diff --git a/cogs/sales_data.py b/cogs/sales_data.py new file mode 100644 index 00000000..4db46fd9 --- /dev/null +++ b/cogs/sales_data.py @@ -0,0 +1,60 @@ +"""Contains cog classes for MSL Sales Report interactions.""" + +from collections.abc import Sequence + +__all__: Sequence[str] = ("SalesDataCommandsCog",) + + +import logging +from logging import Logger +from typing import Final + +import discord + +from utils import MSL, CommandChecks, TeXBotApplicationContext, TeXBotBaseCog + +logger: Final[Logger] = logging.getLogger("TeX-Bot") + + +class SalesDataCommandsCog(TeXBotBaseCog): + """Cog class for MSL Sales Report interactions.""" + + @discord.slash_command( # type: ignore[no-untyped-call, misc] + name="get-sales-reports", + description="Returns the sales reports on the guild website.", + ) + @discord.option( # type: ignore[no-untyped-call, misc] + name="product_id", + description="The product ID to get the sales report for.", + required=True, + input_type=str, + parameter_name="product_id", + ) + @CommandChecks.check_interaction_user_has_committee_role + @CommandChecks.check_interaction_user_in_main_guild + async def update_sales_report(self, ctx: TeXBotApplicationContext, product_id: str) -> None: # noqa: E501 + """Command to get the sales reports on the guild website.""" + initial_response: discord.Interaction | discord.WebhookMessage = await ctx.respond( + content="Fetching sales reports...", + ) + + sales_report_object: MSL.MSLSalesReports = MSL.MSLSalesReports() + + product_sales: dict[str, int] = await sales_report_object.get_product_sales("10000610") + + if not product_sales: + await initial_response.edit( + content=f"No sales data found for product ID: {product_id}.", + ) + return + + sales_report_message: str = ( + "Found sales data for product ID: 10000610:\n" + + "\n".join( + f"{date} - {quantity}" + for date, quantity in product_sales.items() + ) + ) + + await ctx.respond(content=sales_report_message) + From 55890de1cfb471b41923c69814dbdf7db8c88fcf Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Thu, 15 Aug 2024 19:10:42 +0100 Subject: [PATCH 39/40] more --- cogs/__init__.py | 3 ++ cogs/events.py | 94 +++-------------------------------------------- utils/__init__.py | 2 + utils/msl.py | 55 ++++++++++++++++++++++----- 4 files changed, 56 insertions(+), 98 deletions(-) diff --git a/cogs/__init__.py b/cogs/__init__.py index ab8d0f83..46de552c 100644 --- a/cogs/__init__.py +++ b/cogs/__init__.py @@ -23,6 +23,7 @@ "InductSendMessageCog", "InductContextCommandsCog", "KillCommandCog", + "SalesDataCommandsCog" "MakeMemberCommandCog", "AnnualYearChannelsIncrementCommandCog", "PingCommandCog", @@ -67,6 +68,7 @@ from .make_member import MakeMemberCommandCog from .ping import PingCommandCog from .remind_me import ClearRemindersBacklogTaskCog, RemindMeCommandCog +from .sales_data import SalesDataCommandsCog from .send_get_roles_reminders import SendGetRolesRemindersTaskCog from .send_introduction_reminders import SendIntroductionRemindersTaskCog from .source import SourceCommandCog @@ -100,6 +102,7 @@ def setup(bot: TeXBot) -> None: KillCommandCog, MakeApplicantSlashCommandCog, MakeApplicantContextCommandsCog, + SalesDataCommandsCog, MakeMemberCommandCog, PingCommandCog, ClearRemindersBacklogTaskCog, diff --git a/cogs/events.py b/cogs/events.py index a56bf482..3df30130 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -5,11 +5,10 @@ __all__: Sequence[str] = ("EventsManagementCommandsCog",) -import datetime import logging from collections.abc import Mapping from logging import Logger -from typing import Final +from typing import TYPE_CHECKING, Final import dateutil.parser import discord @@ -19,6 +18,9 @@ from utils import CommandChecks, GoogleCalendar, TeXBotApplicationContext, TeXBotBaseCog from utils.msl import MSL +if TYPE_CHECKING: + import datetime + logger: Final[Logger] = logging.getLogger("TeX-Bot") BASE_HEADERS: Final[Mapping[str, str]] = { @@ -86,7 +88,7 @@ async def get_events(self, ctx: TeXBotApplicationContext, *, str_from_date: str, events_object: MSL.MSLEvents = MSL.MSLEvents() - await events_object._get_all_guild_events(formatted_from_date, formatted_to_date) + await events_object._get_all_guild_events(formatted_from_date, formatted_to_date) # noqa: SLF001 events: list[dict[str, str]] | None = await GoogleCalendar.fetch_events() @@ -173,7 +175,7 @@ async def get_events(self, ctx: TeXBotApplicationContext, *, str_from_date: str, ) @CommandChecks.check_interaction_user_has_committee_role @CommandChecks.check_interaction_user_in_main_guild - async def setup_event(self, ctx: TeXBotApplicationContext, str_event_title: str, str_start_date: str, str_start_time: str, str_end_date: str, str_end_time: str, *, str_description: str, str_location: str) -> None: # noqa: E501, PLR0913 + async def setup_society_event(self, ctx: TeXBotApplicationContext, str_event_title: str, str_start_date: str, str_start_time: str, str_end_date: str, str_end_time: str, *, str_description: str, str_location: str) -> None: # noqa: E501, PLR0913 """ Definition & callback response of the "delete_all_reminders" command. @@ -224,87 +226,3 @@ async def setup_event(self, ctx: TeXBotApplicationContext, str_event_title: str, ) await ctx.respond(f"Event created successful!\n{new_discord_event}") - - - - - - - - - - - # TODO: THESE COMMANDS ARE FOR TESTING PURPOSES ONLY AND MUST BE REMOVED - @discord.slash_command( # type: ignore[no-untyped-call, misc] - name="get-msl-context", - description="debug command to check the msl context retrieved for a given url", - ) - @discord.option( # type: ignore[no-untyped-call, misc] - name="url", - description="The URL to get the MSL context for.", - required=True, - input_type=str, - parameter_name="str_url", - ) - @CommandChecks.check_interaction_user_has_committee_role - @CommandChecks.check_interaction_user_in_main_guild - async def get_msl_context(self, ctx: TeXBotApplicationContext, str_url: str) -> None: - """Command to get the MSL context for a given URL.""" - data_fields, cookies = await MSL._get_msl_context(str_url) # noqa: SLF001 - logger.debug(data_fields) - logger.debug(cookies) - await ctx.respond( - content=( - f"Context headers: {data_fields}\n" - f"Context data: {cookies}" - ), - ) - - - - @discord.slash_command( # type: ignore[no-untyped-call, misc] - name="get-member-list", - description="Returns the list of members on the guild website.", - ) - @CommandChecks.check_interaction_user_has_committee_role - @CommandChecks.check_interaction_user_in_main_guild - async def get_member_list(self, ctx: TeXBotApplicationContext) -> None: - """Command to get the member list on the guild website.""" - initial_response: discord.Interaction | discord.WebhookMessage = await ctx.respond( - content="Fetching member list...", - ) - - member_list: list[tuple[str, int]] = await MSL.MSLMemberships.get_full_membership_list() - - if not member_list: - await initial_response.edit(content="No members found on the guild website.") - return - - member_list_message: str = ( - f"Found {len(member_list)} members on the guild website:\n" - + "\n".join( - f"{member[0]} - {member[1]}" - for member in member_list - ) - ) - - await initial_response.edit(content=member_list_message) - - - @discord.slash_command( # type: ignore[no-untyped-call, misc] - name="get-sales-reports", - description="Returns the sales reports on the guild website.", - ) - @CommandChecks.check_interaction_user_has_committee_role - @CommandChecks.check_interaction_user_in_main_guild - async def update_sales_report(self, ctx: TeXBotApplicationContext) -> None: - """Command to get the sales reports on the guild website.""" - initial_response: discord.Interaction | discord.WebhookMessage = await ctx.respond( - content="Fetching sales reports...", - ) - - sales_report_object: MSL.MSLSalesReports = MSL.MSLSalesReports() - - await sales_report_object.update_current_year_sales_report() - - await initial_response.edit(content="Done!") diff --git a/utils/__init__.py b/utils/__init__.py index 46b51d89..d6e2fd68 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -15,6 +15,7 @@ "is_member_inducted", "is_running_in_async", "GoogleCalendar", + "MSL", ) @@ -26,6 +27,7 @@ from .command_checks import CommandChecks from .gcal import GoogleCalendar from .message_sender_components import MessageSavingSenderComponent +from .msl import MSL from .suppress_traceback import SuppressTraceback from .tex_bot import TeXBot from .tex_bot_base_cog import TeXBotBaseCog diff --git a/utils/msl.py b/utils/msl.py index 722fbb29..d9c335b6 100644 --- a/utils/msl.py +++ b/utils/msl.py @@ -8,7 +8,7 @@ import logging import re from collections.abc import Mapping -from datetime import datetime +from datetime import datetime, timezone from logging import Logger from typing import TYPE_CHECKING, Final @@ -127,12 +127,10 @@ async def _get_all_guild_events(self, from_date: str, to_date: str) -> dict[str, ) if event_table_html is None or isinstance(event_table_html, bs4.NavigableString): - # TODO: something went wrong!! logger.debug("Something went wrong!") return {} if "There are no events" in str(event_table_html): - # TODO: No events!! logger.debug("No events were found!") return {} @@ -198,6 +196,11 @@ async def get_full_membership_list() -> list[tuple[str, int]]: return member_list + @staticmethod + async def is_student_id_member() -> bool: + """Check if the student ID is a member of the society.""" + return False + class MSLSalesReports: """Class to define Sales Reports specific MSL methods.""" @@ -212,8 +215,8 @@ async def update_current_year_sales_report(self) -> None: data_fields, cookies = await MSL._get_msl_context(url=SALES_REPORT_URL) - from_date: datetime = datetime(year=datetime.now().year, month=7, day=1) - to_date: datetime = datetime(year=datetime.now().year + 1, month=6, day=30) + from_date: datetime = datetime(year=datetime.now(tz=timezone.utc).year, month=7, day=1, tzinfo=timezone.utc) # noqa: E501, UP017 + to_date: datetime = datetime(year=datetime.now(tz=timezone.utc).year + 1, month=6, day=30, tzinfo=timezone.utc) # noqa: E501, UP017 form_data: dict[str, str] = { self.FROM_DATE_KEY: from_date.strftime("%d/%m/%Y"), @@ -255,11 +258,20 @@ async def update_current_year_sales_report(self) -> None: match = re.search(r'ExportUrlBase":"(.*?)"', response_html) if not match: - logger.debug("Couldn't find the export URL!!") + logger.warning( + "Something went wrong when attempting to extract the export url " + "from the http response.", + ) + logger.debug(response_html) return urlbase: str = match.group(1).replace(r"\u0026", "&").replace("\\/", "/") + if not urlbase: + logger.debug("Couldn't find the export URL!!") + logger.debug(response_html) + return + report_url: str = f"https://guildofstudents.com/{urlbase}CSV" file_session: aiohttp.ClientSession = aiohttp.ClientSession( @@ -280,18 +292,41 @@ async def update_current_year_sales_report(self) -> None: values: list[bytes] = line.split(b",") product_name_and_id: bytes = values[0] - product_id: bytes = product_name_and_id.split(b" ")[0].removeprefix(b"[").removesuffix(b"]") # noqa: E501 - product_name: bytes = b" ".join(product_name_and_id.split(b" ")[1:]) # noqa: E501 + product_id: bytes = (( + product_name_and_id.split(b" ")[0].removeprefix(b"[") + ).removesuffix(b"]") + ) + product_name: bytes = b" ".join( + product_name_and_id.split(b" ")[1:], + ) date: bytes = values[5] quantity: bytes = values[6] unit_price: bytes = values[7] total: bytes = values[8] await report_file.write( - product_id + b"," + product_name + b"," + date + b"," + quantity + b"," + unit_price + b"," + total + b"\n", # noqa: E501 + product_id + b"," + + product_name + b"," + + date + b"," + + quantity + b"," + + unit_price + b"," + + total + b"\n", ) logger.debug("Sales report updated successfully!!") + return + + logger.debug("Couldn't get the sales report!!") + logger.debug(file_response) async def get_product_sales(self, product_id: str) -> dict[str, int]: - return {} + """Get the dates and quantities of sales for a given product ID.""" + product_sales_data: dict[str, int] = {} + async with await anyio.open_file("CurrentYearSalesReport.csv", "r") as report_file: + for line in (await report_file.readlines())[1:]: + values: list[str] = line.split(",") + + if values[0] == product_id: + product_sales_data[values[2]] = int(values[3]) + + return product_sales_data From 88ecd33e7aa8ffb07a91d2c2e577bc8947259bc1 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Mon, 26 Aug 2024 23:27:22 +0100 Subject: [PATCH 40/40] update poetry --- poetry.lock | 332 ++++++++++++++++++++++++++++------------------------ 1 file changed, 177 insertions(+), 155 deletions(-) diff --git a/poetry.lock b/poetry.lock index be634b86..bfc6b6b2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,98 +2,113 @@ [[package]] name = "aiohappyeyeballs" -version = "2.3.7" +version = "2.4.0" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "aiohappyeyeballs-2.3.7-py3-none-any.whl", hash = "sha256:337ce4dc0e99eb697c3c5a77d6cb3c52925824d9a67ac0dea7c55b8a2d60b222"}, - {file = "aiohappyeyeballs-2.3.7.tar.gz", hash = "sha256:e794cd29ba6a14078092984e43688212a19081de3a73b6796c2fdeb3706dd6ce"}, + {file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd"}, + {file = "aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2"}, ] [[package]] name = "aiohttp" -version = "3.10.4" +version = "3.10.5" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.10.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:81037ddda8cc0a95c6d8c1b9029d0b19a62db8770c0e239e3bea0109d294ab66"}, - {file = "aiohttp-3.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:71944d4f4090afc07ce96b7029d5a574240e2f39570450df4af0d5b93a5ee64a"}, - {file = "aiohttp-3.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c774f08afecc0a617966f45a9c378456e713a999ee60654d9727617def3e4ee4"}, - {file = "aiohttp-3.10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc990e73613c78ab2930b60266135066f37fdfce6b32dd604f42c5c377ee880a"}, - {file = "aiohttp-3.10.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6acd1a908740f708358d240f9a3243cec31a456e3ded65c2cb46f6043bc6735"}, - {file = "aiohttp-3.10.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6075e27e7e54fbcd1c129c5699b2d251c885c9892e26d59a0fb7705141c2d14b"}, - {file = "aiohttp-3.10.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc98d93d11d860ac823beb6131f292d82efb76f226b5e28a3eab1ec578dfd041"}, - {file = "aiohttp-3.10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:201ddf1471567568be381b6d4701e266a768f7eaa2f99ef753f2c9c5e1e3fb5c"}, - {file = "aiohttp-3.10.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7d202ec55e61f06b1a1eaf317fba7546855cbf803c13ce7625d462fb8c88e238"}, - {file = "aiohttp-3.10.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:96b2e7c110a941c8c1a692703b8ac1013e47f17ee03356c71d55c0a54de2ce38"}, - {file = "aiohttp-3.10.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8ba0fbc56c44883bd757ece433f9caadbca67f565934afe9bc53ba3bd99cc368"}, - {file = "aiohttp-3.10.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46cc9069da466652bb7b8b3fac1f8ce2e12a9dc0fb11551faa420c4cdbc60abf"}, - {file = "aiohttp-3.10.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:93a19cd1e9dc703257fda78b8e889c3a08eabaa09f6ff0d867850b03964f80d1"}, - {file = "aiohttp-3.10.4-cp310-cp310-win32.whl", hash = "sha256:8593040bcc8075fc0e817a602bc5d3d74c7bd717619ffc175a8ba0188edebadf"}, - {file = "aiohttp-3.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:326fb5228aadfc395981d9b336d56a698da335897c4143105c73b583d7500839"}, - {file = "aiohttp-3.10.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dfe48f477e02ef5ab247c6ac431a6109c69b5c24cb3ccbcd3e27c4fb39691fe4"}, - {file = "aiohttp-3.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f6fe78b51852e25d4e20be51ef88c2a0bf31432b9f2223bdbd61c01a0f9253a7"}, - {file = "aiohttp-3.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5cc75ff5efbd92301e63a157fddb18a6964a3f40e31c77d57e97dbb9bb3373b4"}, - {file = "aiohttp-3.10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dca39391f45fbb28daa6412f98c625265bf6b512cc41382df61672d1b242f8f4"}, - {file = "aiohttp-3.10.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8616dd5ed8b3b4029021b560305041c62e080bb28f238c27c2e150abe3539587"}, - {file = "aiohttp-3.10.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d7958ba22854b3f00a7bbb66cde1dc759760ce8a3e6dfe9ea53f06bccaa9aa2"}, - {file = "aiohttp-3.10.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a24ac7164a824ef2e8e4e9a9f6debb1f43c44ad7ad04efc6018a6610555666d"}, - {file = "aiohttp-3.10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:660ad010b8fd0b26e8edb8ae5c036db5b16baac4278198ad238b11956d920b3d"}, - {file = "aiohttp-3.10.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:93ee83008d3e505db9846a5a1f48a002676d8dcc90ee431a9462541c9b81393c"}, - {file = "aiohttp-3.10.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77071795efd6ba87f409001141fb05c94ee962b9fca6c8fa1f735c2718512de4"}, - {file = "aiohttp-3.10.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ff371ae72a1816c3eeba5c9cff42cb739aaa293fec7d78f180d1c7ee342285b6"}, - {file = "aiohttp-3.10.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c253e81f12da97f85d45441e8c6da0d9c12e07db4a7136b0a955df6fc5e4bf51"}, - {file = "aiohttp-3.10.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2ce101c447cf7ba4b6e5ab07bfa2c0da21cbab66922f78a601f0b84fd7710d72"}, - {file = "aiohttp-3.10.4-cp311-cp311-win32.whl", hash = "sha256:705c311ecf2d30fbcf3570d1a037c657be99095694223488140c47dee4ef2460"}, - {file = "aiohttp-3.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:ebddbfea8a8d6b97f717658fa85a96681a28990072710d3de3a4eba5d6804a37"}, - {file = "aiohttp-3.10.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fe4d63f42d9c604521b208b754abfafe01218af4a8f6332b43196ee8fe88bbd5"}, - {file = "aiohttp-3.10.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fef7b7bd3a6911b4d148332136d34d3c2aee3d54d354373b1da6d96bc08089a5"}, - {file = "aiohttp-3.10.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fff8606149098935188fe1e135f7e7991e6a36d6fe394fd15939fc57d0aff889"}, - {file = "aiohttp-3.10.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb3df1aa83602be9a5e572c834d74c3c8e382208b59a873aabfe4c493c45ed0"}, - {file = "aiohttp-3.10.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c4a71d4a5e0cbfd4bfadd13cb84fe2bc76c64d550dc4f22c22008c9354cffb3"}, - {file = "aiohttp-3.10.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf61884a604c399458c4a42c8caea000fbcc44255ed89577ff50cb688a0fe8e2"}, - {file = "aiohttp-3.10.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2015e4b40bd5dedc8155c2b2d24a2b07963ae02b5772373d0b599a68e38a316b"}, - {file = "aiohttp-3.10.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b06e1a66bf0a1a2d0f12aef25843dfd2093df080d6c1acbc43914bb9c8f36ed3"}, - {file = "aiohttp-3.10.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:eb898c9ad5a1228a669ebe2e2ba3d76aebe1f7c10b78f09a36000254f049fc2b"}, - {file = "aiohttp-3.10.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2d64a5a7539320c3cecb4bca093ea825fcc906f8461cf8b42a7bf3c706ce1932"}, - {file = "aiohttp-3.10.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:438c6e1492d060b21285f4b6675b941cf96dd9ef3dfdd59940561029b82e3e1f"}, - {file = "aiohttp-3.10.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e99bf118afb2584848dba169a685fe092b338a4fe52ae08c7243d7bc4cc204fe"}, - {file = "aiohttp-3.10.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9dc26781fb95225c6170619dece8b5c6ca7cfb1b0be97b7ee719915773d0c2a9"}, - {file = "aiohttp-3.10.4-cp312-cp312-win32.whl", hash = "sha256:45bb655cb8b3a61e19977183a4e0962051ae90f6d46588ed4addb8232128141c"}, - {file = "aiohttp-3.10.4-cp312-cp312-win_amd64.whl", hash = "sha256:347bbdc48411badc24fe3a13565820bc742db3aa2f9127cd5f48c256caf87e29"}, - {file = "aiohttp-3.10.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4ad284cee0fdcdc0216346b849fd53d201b510aff3c48aa3622daec9ada4bf80"}, - {file = "aiohttp-3.10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:58df59234be7d7e80548b9482ebfeafdda21948c25cb2873c7f23870c8053dfe"}, - {file = "aiohttp-3.10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5f52225af7f91f27b633f73473e9ef0aa8e2112d57b69eaf3aa4479e3ea3bc0e"}, - {file = "aiohttp-3.10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93f1a0e12c321d923c024b56d7dcd8012e60bf30a4b3fb69a88be15dcb9ab80b"}, - {file = "aiohttp-3.10.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9e9e9a51dd12f2f71fdbd7f7230dcb75ed8f77d8ac8e07c73b599b6d7027e5c"}, - {file = "aiohttp-3.10.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:38bb515f1affc36d3d97b02bf82099925a5785c4a96066ff4400a83ad09d3d5d"}, - {file = "aiohttp-3.10.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e685afb0e3b7b861d89cb3690d89eeda221b43095352efddaaa735c6baf87f3"}, - {file = "aiohttp-3.10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd5673e3391564871ba6753cf674dcf2051ef19dc508998fe0758a6c7b429a0"}, - {file = "aiohttp-3.10.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4b34e5086e1ead3baa740e32adf35cc5e42338e44c4b07f7b62b41ca6d6a5bfd"}, - {file = "aiohttp-3.10.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c3fd3b8f0164fb2866400cd6eb9e884ab0dc95f882cf8b25e560ace7350c552d"}, - {file = "aiohttp-3.10.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:b95e1694d234f27b4bbf5bdef56bb751974ac5dbe045b1e462bde1fe39421cbe"}, - {file = "aiohttp-3.10.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:c031de4dfabe7bb6565743745ab43d20588944ddfc7233360169cab4008eee2f"}, - {file = "aiohttp-3.10.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:03c5a3143d4a82c43a3d82ac77d9cdef527a72f1c04dcca7b14770879f33d196"}, - {file = "aiohttp-3.10.4-cp38-cp38-win32.whl", hash = "sha256:b71722b527445e02168e2d1cf435772731874671a647fa159ad000feea7933b6"}, - {file = "aiohttp-3.10.4-cp38-cp38-win_amd64.whl", hash = "sha256:0fd1f57aac7d01c9c768675d531976d20d5b79d9da67fac87e55d41b4ade05f9"}, - {file = "aiohttp-3.10.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:15b36a644d1f44ea3d94a0bbb71e75d5f394a3135dc388a209466e22b711ce64"}, - {file = "aiohttp-3.10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:394ddf9d216cf0bd429b223239a0ab628f01a7a1799c93ce4685eedcdd51b9bc"}, - {file = "aiohttp-3.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dd33f4d571b4143fc9318c3d9256423579c7d183635acc458a6db81919ae5204"}, - {file = "aiohttp-3.10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5991b80886655e6c785aadf3114d4f86e6bec2da436e2bb62892b9f048450a4"}, - {file = "aiohttp-3.10.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92021bf0a4b9ad16851a6c1ca3c86e5b09aecca4f7a2576430c6bbf3114922b1"}, - {file = "aiohttp-3.10.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:938e37fd337343c67471098736deb33066d72cec7d8927b9c1b6b4ea807ade9e"}, - {file = "aiohttp-3.10.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d697023b16c62f9aeb3ffdfb8ec4ac3afd477388993b9164b47dadbd60e7062"}, - {file = "aiohttp-3.10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2f9f07fe6d0d51bd2a788cbb339f1570fd691449c53b5dec83ff838f117703e"}, - {file = "aiohttp-3.10.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:50ac670f3fc13ce95e4d6d5a299db9288cc84c663aa630142444ef504756fcf7"}, - {file = "aiohttp-3.10.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9bcdd19398212785a9cb82a63a4b75a299998343f3f5732dfd37c1a4275463f9"}, - {file = "aiohttp-3.10.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:122c26f0976225aba46f381e3cabb5ef89a08af6503fc30493fb732e578cfa55"}, - {file = "aiohttp-3.10.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:d0665e2a346b6b66959f831ffffd8aa71dd07dd2300017d478f5b47573e66cfe"}, - {file = "aiohttp-3.10.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:625a4a9d4b9f80e7bbaaf2ace06341cf701b2fee54232843addf0bb7304597fb"}, - {file = "aiohttp-3.10.4-cp39-cp39-win32.whl", hash = "sha256:5115490112f39f16ae87c1b34dff3e2c95306cf456b1d2af5974c4ac7d2d1ec7"}, - {file = "aiohttp-3.10.4-cp39-cp39-win_amd64.whl", hash = "sha256:9b58b2ef7f28a2462ba86acbf3b20371bd80a1faa1cfd82f31968af4ac81ef25"}, - {file = "aiohttp-3.10.4.tar.gz", hash = "sha256:23a5f97e7dd22e181967fb6cb6c3b11653b0fdbbc4bb7739d9b6052890ccab96"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683"}, + {file = "aiohttp-3.10.5-cp310-cp310-win32.whl", hash = "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef"}, + {file = "aiohttp-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058"}, + {file = "aiohttp-3.10.5-cp311-cp311-win32.whl", hash = "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072"}, + {file = "aiohttp-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6"}, + {file = "aiohttp-3.10.5-cp312-cp312-win32.whl", hash = "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12"}, + {file = "aiohttp-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987"}, + {file = "aiohttp-3.10.5-cp313-cp313-win32.whl", hash = "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04"}, + {file = "aiohttp-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511"}, + {file = "aiohttp-3.10.5-cp38-cp38-win32.whl", hash = "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a"}, + {file = "aiohttp-3.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11"}, + {file = "aiohttp-3.10.5-cp39-cp39-win32.whl", hash = "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1"}, + {file = "aiohttp-3.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862"}, + {file = "aiohttp-3.10.5.tar.gz", hash = "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691"}, ] [package.dependencies] @@ -229,13 +244,13 @@ lxml = ["lxml"] [[package]] name = "cachetools" -version = "5.4.0" +version = "5.5.0" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" files = [ - {file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"}, - {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"}, + {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, + {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, ] [[package]] @@ -822,13 +837,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.140.0" +version = "2.142.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "google_api_python_client-2.140.0-py2.py3-none-any.whl", hash = "sha256:aeb4bb99e9fdd241473da5ff35464a0658fea0db76fe89c0f8c77ecfc3813404"}, - {file = "google_api_python_client-2.140.0.tar.gz", hash = "sha256:0bb973adccbe66a3d0a70abe4e49b3f2f004d849416bfec38d22b75649d389d8"}, + {file = "google_api_python_client-2.142.0-py2.py3-none-any.whl", hash = "sha256:266799082bb8301f423ec204dffbffb470b502abbf29efd1f83e644d36eb5a8f"}, + {file = "google_api_python_client-2.142.0.tar.gz", hash = "sha256:a1101ac9e24356557ca22f07ff48b7f61fa5d4b4e7feeef3bda16e5dcb86350e"}, ] [package.dependencies] @@ -840,29 +855,29 @@ uritemplate = ">=3.0.1,<5" [[package]] name = "google-api-python-client-stubs" -version = "1.26.0" +version = "1.27.0" description = "Type stubs for google-api-python-client" optional = false python-versions = "<4.0,>=3.7" files = [ - {file = "google_api_python_client_stubs-1.26.0-py3-none-any.whl", hash = "sha256:0614b0cef5beac43e6ab02418f07e64ee66dc99ae4e377d54a155ac261533987"}, - {file = "google_api_python_client_stubs-1.26.0.tar.gz", hash = "sha256:f3b38b46f7b5cf4f6e7cc63ca554a2d23096d49c841f38b9ea553a5237074b56"}, + {file = "google_api_python_client_stubs-1.27.0-py3-none-any.whl", hash = "sha256:3c1f9f2a7cac8d1e9a7e84ed24e6c29cf4c643b0f94e39ed09ac1b7e91ab239a"}, + {file = "google_api_python_client_stubs-1.27.0.tar.gz", hash = "sha256:148e16613e070969727f39691e23a73cdb87c65a4fc8133abd4c41d17b80b313"}, ] [package.dependencies] -google-api-python-client = ">=2.130.0" +google-api-python-client = ">=2.141.0" types-httplib2 = ">=0.22.0.2" typing-extensions = ">=3.10.0" [[package]] name = "google-auth" -version = "2.33.0" +version = "2.34.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google_auth-2.33.0-py2.py3-none-any.whl", hash = "sha256:8eff47d0d4a34ab6265c50a106a3362de6a9975bb08998700e389f857e4d39df"}, - {file = "google_auth-2.33.0.tar.gz", hash = "sha256:d6a52342160d7290e334b4d47ba390767e4438ad0d45b7630774533e82655b95"}, + {file = "google_auth-2.34.0-py2.py3-none-any.whl", hash = "sha256:72fd4733b80b6d777dcde515628a9eb4a577339437012874ea286bca7261ee65"}, + {file = "google_auth-2.34.0.tar.gz", hash = "sha256:8eb87396435c19b20d32abd2f984e31c191a15284af72eb922f10e5bde9c04cc"}, ] [package.dependencies] @@ -872,7 +887,7 @@ rsa = ">=3.1.4,<5" [package.extras] aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] -enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +enterprise-cert = ["cryptography", "pyopenssl"] pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] requests = ["requests (>=2.20.0,<3.0.0.dev0)"] @@ -912,13 +927,13 @@ tool = ["click (>=6.0.0)"] [[package]] name = "googleapis-common-protos" -version = "1.63.2" +version = "1.64.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87"}, - {file = "googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945"}, + {file = "googleapis_common_protos-1.64.0-py2.py3-none-any.whl", hash = "sha256:d1bfc569f70ed2e96ccf06ead265c2cf42b5abfc817cda392e3835f3b67b5c59"}, + {file = "googleapis_common_protos-1.64.0.tar.gz", hash = "sha256:7d77ca6b7c0c38eb6b1bab3b4c9973acf57ce4f2a6d3a4136acba10bcbfb3025"}, ] [package.dependencies] @@ -957,13 +972,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.7" +version = "3.8" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, + {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, ] [[package]] @@ -1336,56 +1351,63 @@ files = [ [[package]] name = "numpy" -version = "2.0.1" +version = "2.1.0" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "numpy-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134"}, - {file = "numpy-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42"}, - {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02"}, - {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101"}, - {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9"}, - {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015"}, - {file = "numpy-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87"}, - {file = "numpy-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82"}, - {file = "numpy-2.0.1-cp310-cp310-win32.whl", hash = "sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1"}, - {file = "numpy-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b"}, - {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe"}, - {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67"}, - {file = "numpy-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7"}, - {file = "numpy-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55"}, - {file = "numpy-2.0.1-cp311-cp311-win32.whl", hash = "sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4"}, - {file = "numpy-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587"}, - {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8"}, - {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a"}, - {file = "numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7"}, - {file = "numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b"}, - {file = "numpy-2.0.1-cp312-cp312-win32.whl", hash = "sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf"}, - {file = "numpy-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c"}, - {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59"}, - {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26"}, - {file = "numpy-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018"}, - {file = "numpy-2.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c"}, - {file = "numpy-2.0.1-cp39-cp39-win32.whl", hash = "sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4"}, - {file = "numpy-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f"}, - {file = "numpy-2.0.1.tar.gz", hash = "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3"}, + {file = "numpy-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6326ab99b52fafdcdeccf602d6286191a79fe2fda0ae90573c5814cd2b0bc1b8"}, + {file = "numpy-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0937e54c09f7a9a68da6889362ddd2ff584c02d015ec92672c099b61555f8911"}, + {file = "numpy-2.1.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:30014b234f07b5fec20f4146f69e13cfb1e33ee9a18a1879a0142fbb00d47673"}, + {file = "numpy-2.1.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:899da829b362ade41e1e7eccad2cf274035e1cb36ba73034946fccd4afd8606b"}, + {file = "numpy-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08801848a40aea24ce16c2ecde3b756f9ad756586fb2d13210939eb69b023f5b"}, + {file = "numpy-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:398049e237d1aae53d82a416dade04defed1a47f87d18d5bd615b6e7d7e41d1f"}, + {file = "numpy-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0abb3916a35d9090088a748636b2c06dc9a6542f99cd476979fb156a18192b84"}, + {file = "numpy-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10e2350aea18d04832319aac0f887d5fcec1b36abd485d14f173e3e900b83e33"}, + {file = "numpy-2.1.0-cp310-cp310-win32.whl", hash = "sha256:f6b26e6c3b98adb648243670fddc8cab6ae17473f9dc58c51574af3e64d61211"}, + {file = "numpy-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:f505264735ee074250a9c78247ee8618292091d9d1fcc023290e9ac67e8f1afa"}, + {file = "numpy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:76368c788ccb4f4782cf9c842b316140142b4cbf22ff8db82724e82fe1205dce"}, + {file = "numpy-2.1.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f8e93a01a35be08d31ae33021e5268f157a2d60ebd643cfc15de6ab8e4722eb1"}, + {file = "numpy-2.1.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9523f8b46485db6939bd069b28b642fec86c30909cea90ef550373787f79530e"}, + {file = "numpy-2.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54139e0eb219f52f60656d163cbe67c31ede51d13236c950145473504fa208cb"}, + {file = "numpy-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebbf9fbdabed208d4ecd2e1dfd2c0741af2f876e7ae522c2537d404ca895c3"}, + {file = "numpy-2.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:378cb4f24c7d93066ee4103204f73ed046eb88f9ad5bb2275bb9fa0f6a02bd36"}, + {file = "numpy-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8f699a709120b220dfe173f79c73cb2a2cab2c0b88dd59d7b49407d032b8ebd"}, + {file = "numpy-2.1.0-cp311-cp311-win32.whl", hash = "sha256:ffbd6faeb190aaf2b5e9024bac9622d2ee549b7ec89ef3a9373fa35313d44e0e"}, + {file = "numpy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0af3a5987f59d9c529c022c8c2a64805b339b7ef506509fba7d0556649b9714b"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fe76d75b345dc045acdbc006adcb197cc680754afd6c259de60d358d60c93736"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f358ea9e47eb3c2d6eba121ab512dfff38a88db719c38d1e67349af210bc7529"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:dd94ce596bda40a9618324547cfaaf6650b1a24f5390350142499aa4e34e53d1"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b47c551c6724960479cefd7353656498b86e7232429e3a41ab83be4da1b109e8"}, + {file = "numpy-2.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0756a179afa766ad7cb6f036de622e8a8f16ffdd55aa31f296c870b5679d745"}, + {file = "numpy-2.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24003ba8ff22ea29a8c306e61d316ac74111cebf942afbf692df65509a05f111"}, + {file = "numpy-2.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b34fa5e3b5d6dc7e0a4243fa0f81367027cb6f4a7215a17852979634b5544ee0"}, + {file = "numpy-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4f982715e65036c34897eb598d64aef15150c447be2cfc6643ec7a11af06574"}, + {file = "numpy-2.1.0-cp312-cp312-win32.whl", hash = "sha256:c4cd94dfefbefec3f8b544f61286584292d740e6e9d4677769bc76b8f41deb02"}, + {file = "numpy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0cdef204199278f5c461a0bed6ed2e052998276e6d8ab2963d5b5c39a0500bc"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8ab81ccd753859ab89e67199b9da62c543850f819993761c1e94a75a814ed667"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:442596f01913656d579309edcd179a2a2f9977d9a14ff41d042475280fc7f34e"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:848c6b5cad9898e4b9ef251b6f934fa34630371f2e916261070a4eb9092ffd33"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:54c6a63e9d81efe64bfb7bcb0ec64332a87d0b87575f6009c8ba67ea6374770b"}, + {file = "numpy-2.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:652e92fc409e278abdd61e9505649e3938f6d04ce7ef1953f2ec598a50e7c195"}, + {file = "numpy-2.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ab32eb9170bf8ffcbb14f11613f4a0b108d3ffee0832457c5d4808233ba8977"}, + {file = "numpy-2.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:8fb49a0ba4d8f41198ae2d52118b050fd34dace4b8f3fb0ee34e23eb4ae775b1"}, + {file = "numpy-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44e44973262dc3ae79e9063a1284a73e09d01b894b534a769732ccd46c28cc62"}, + {file = "numpy-2.1.0-cp313-cp313-win32.whl", hash = "sha256:ab83adc099ec62e044b1fbb3a05499fa1e99f6d53a1dde102b2d85eff66ed324"}, + {file = "numpy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:de844aaa4815b78f6023832590d77da0e3b6805c644c33ce94a1e449f16d6ab5"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:343e3e152bf5a087511cd325e3b7ecfd5b92d369e80e74c12cd87826e263ec06"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f07fa2f15dabe91259828ce7d71b5ca9e2eb7c8c26baa822c825ce43552f4883"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5474dad8c86ee9ba9bb776f4b99ef2d41b3b8f4e0d199d4f7304728ed34d0300"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1f817c71683fd1bb5cff1529a1d085a57f02ccd2ebc5cd2c566f9a01118e3b7d"}, + {file = "numpy-2.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a3336fbfa0d38d3deacd3fe7f3d07e13597f29c13abf4d15c3b6dc2291cbbdd"}, + {file = "numpy-2.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a894c51fd8c4e834f00ac742abad73fc485df1062f1b875661a3c1e1fb1c2f6"}, + {file = "numpy-2.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:9156ca1f79fc4acc226696e95bfcc2b486f165a6a59ebe22b2c1f82ab190384a"}, + {file = "numpy-2.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:624884b572dff8ca8f60fab591413f077471de64e376b17d291b19f56504b2bb"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15ef8b2177eeb7e37dd5ef4016f30b7659c57c2c0b57a779f1d537ff33a72c7b"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:e5f0642cdf4636198a4990de7a71b693d824c56a757862230454629cf62e323d"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15976718c004466406342789f31b6673776360f3b1e3c575f25302d7e789575"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6c1de77ded79fef664d5098a66810d4d27ca0224e9051906e634b3f7ead134c2"}, + {file = "numpy-2.1.0.tar.gz", hash = "sha256:7dc90da0081f7e1da49ec4e398ede6a8e9cc4f5ebe5f9e06b443ed889ee9aaa2"}, ] [[package]] @@ -1671,13 +1693,13 @@ typing-extensions = ">=4.7.0" [[package]] name = "pyparsing" -version = "3.1.2" +version = "3.1.4" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.6.8" files = [ - {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, - {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, + {file = "pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c"}, + {file = "pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032"}, ] [package.extras] @@ -2024,13 +2046,13 @@ files = [ [[package]] name = "types-python-dateutil" -version = "2.9.0.20240316" +version = "2.9.0.20240821" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.8" files = [ - {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"}, + {file = "types-python-dateutil-2.9.0.20240821.tar.gz", hash = "sha256:9649d1dcb6fef1046fb18bebe9ea2aa0028b160918518c34589a46045f6ebd98"}, + {file = "types_python_dateutil-2.9.0.20240821-py3-none-any.whl", hash = "sha256:f5889fcb4e63ed4aaa379b44f93c32593d50b9a94c9a60a0c854d8cc3511cd57"}, ] [[package]] @@ -2245,4 +2267,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "e1b616b24bfd0fa80d6d81c89a3ba502bc2446a9f096671dc2f465390c2a3f88" +content-hash = "4ba9b18f2e34b033bdfe8d4c9fa4cc0b0f994c6a3ad400832271edd58cc12fa0"