Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
ec5a09e
add some stuff but it's fucked
MattyTheHacker Aug 1, 2024
572e038
pending
MattyTheHacker Aug 2, 2024
50f238f
add deteutil
MattyTheHacker Aug 2, 2024
096db41
fix some stuff
MattyTheHacker Aug 2, 2024
e42c78c
Merge branch 'main' into events
MattyTheHacker Aug 2, 2024
6d4aad4
fix??
MattyTheHacker Aug 2, 2024
89e349f
fix imports
MattyTheHacker Aug 2, 2024
a6b2d19
fix some stuff
MattyTheHacker Aug 2, 2024
529dad1
org import
MattyTheHacker Aug 2, 2024
5acd778
more
MattyTheHacker Aug 3, 2024
c426d97
work
MattyTheHacker Aug 3, 2024
f3e4fb5
update attrs
MattyTheHacker Aug 3, 2024
eb8bbe9
work on gcal
MattyTheHacker Aug 3, 2024
ff99c5e
do some shit
MattyTheHacker Aug 3, 2024
7585060
Update gitignore
MattyTheHacker Aug 3, 2024
031c5bd
more
MattyTheHacker Aug 3, 2024
a3a693f
tmp
MattyTheHacker Aug 3, 2024
2b3ac6d
fuck
MattyTheHacker Aug 3, 2024
c059608
the most scuffed code in existance
MattyTheHacker Aug 3, 2024
5fa5e56
some fixes
MattyTheHacker Aug 3, 2024
65113f1
test
MattyTheHacker Aug 3, 2024
d6ac006
fixes
MattyTheHacker Aug 4, 2024
b0357b9
fixed bullshit gcal api error
MattyTheHacker Aug 4, 2024
43a1f7c
add db object
MattyTheHacker Aug 4, 2024
c5099b5
yeet
MattyTheHacker Aug 4, 2024
47a509e
make migration
MattyTheHacker Aug 4, 2024
8ea4fc8
do some stuf
MattyTheHacker Aug 4, 2024
b9fda24
chonky refactor
MattyTheHacker Aug 7, 2024
5225928
Merge branch 'main' into events
MattyTheHacker Aug 7, 2024
8eb93a3
add urls
MattyTheHacker Aug 7, 2024
eca7660
stuff
MattyTheHacker Aug 9, 2024
438130b
Merge branch 'main' into events
MattyTheHacker Aug 9, 2024
c77d12b
update deps
MattyTheHacker Aug 9, 2024
43bb589
work
MattyTheHacker Aug 10, 2024
073ab57
Merge branch 'main' into events
MattyTheHacker Aug 10, 2024
0492851
add membership list handling
MattyTheHacker Aug 10, 2024
31bdd19
do stuff
MattyTheHacker Aug 10, 2024
738437b
more
MattyTheHacker Aug 10, 2024
7ae83a2
implement csv download
MattyTheHacker Aug 13, 2024
6184dee
clean csv
MattyTheHacker Aug 13, 2024
e36d8e4
more
MattyTheHacker Aug 13, 2024
fee28ea
implement some stuff
MattyTheHacker Aug 15, 2024
55890de
more
MattyTheHacker Aug 15, 2024
b54cebd
Merge branch 'main' into events
MattyTheHacker Aug 26, 2024
88ecd33
update poetry
MattyTheHacker Aug 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,5 @@ local/
*.sqlite3.bak
*.db.bak
local_stubs/
credentials.json
token.json
3 changes: 3 additions & 0 deletions cogs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"CommandErrorCog",
"CommitteeHandoverCommandCog",
"DeleteAllCommandsCog",
"EventsManagementCommandsCog",
"EditMessageCommandCog",
"EnsureMembersInductedCommandCog",
"GetTokenAuthorisationCommandCog",
Expand Down Expand Up @@ -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,
Expand All @@ -65,6 +67,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
Expand Down
228 changes: 228 additions & 0 deletions cogs/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
"""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 TYPE_CHECKING, Final

import dateutil.parser
import discord
from dateutil.parser import ParserError

from config import settings
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]] = {
"Cache-Control": "no-cache",
"Pragma": "no-cache",
"Expires": "0",
}

BASE_COOKIES: Final[Mapping[str, str]] = {
".ASPXAUTH": settings["MEMBERS_LIST_AUTH_SESSION_COOKIE"],
}

class EventsManagementCommandsCog(TeXBotBaseCog):
"""Cog class to define event management commands."""

@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, *, str_from_date: str, str_to_date: str) -> None: # noqa: E501
"""Command to get the events on the guild website."""
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(
content=(
f":warning: Start date ({from_date_dt}) is after end date ({to_date_dt})."
),
)

formatted_from_date: str = from_date_dt.strftime("%d/%m/%Y")
formatted_to_date: str = to_date_dt.strftime(format="%d/%m/%Y")

events_object: MSL.MSLEvents = MSL.MSLEvents()

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()

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",
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",
)
@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_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.

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(
timestr=f"{str_start_date}T{str_start_time}", dayfirst=True,
)
end_date_dt: datetime.datetime = dateutil.parser.parse(
timestr=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

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=description,
location=location,
)
except discord.Forbidden:
await self.command_send_error(
ctx=ctx,
message="TeX-Bot does not have the required permissions to create a discord event.", # noqa: E501
)

await ctx.respond(f"Event created successful!\n{new_discord_event}")
60 changes: 60 additions & 0 deletions cogs/sales_data.py
Original file line number Diff line number Diff line change
@@ -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)

Original file line number Diff line number Diff line change
@@ -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",
),
),
]
Loading