From 1df345838d7d0b88c46d5396698403ad24ec71f0 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Sun, 25 Sep 2022 15:25:29 +0200 Subject: [PATCH 1/9] Remove unnecessary setting, add check around schedule parsing --- didier/data/embeds/schedules.py | 8 +++++--- settings.py | 2 -- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/didier/data/embeds/schedules.py b/didier/data/embeds/schedules.py index 98a7a51..9923a71 100644 --- a/didier/data/embeds/schedules.py +++ b/didier/data/embeds/schedules.py @@ -131,9 +131,11 @@ class ScheduleSlot: def __post_init__(self): """Fix some properties to display more nicely""" # Re-format the location data - room, building, campus = re.search(r"(.*)\. (?:Gebouw )?(.*)\. (?:Campus )?(.*)\. ", self.location).groups() - room = room.replace("PC / laptoplokaal ", "PC-lokaal") - self.location = f"{campus} {building} {room}" + match = re.search(r"(.*)\. (?:Gebouw )?(.*)\. (?:Campus )?(.*)\. ", self.location) + if match is not None: + room, building, campus = match.groups() + room = room.replace("PC / laptoplokaal ", "PC-lokaal") + self.location = f"{campus} {building} {room}" # The same course can only start once at the same moment, # so this is guaranteed to be unique diff --git a/settings.py b/settings.py index 9e36f1f..aa0f6fe 100644 --- a/settings.py +++ b/settings.py @@ -29,7 +29,6 @@ __all__ = [ "DISCORD_CUSTOM_COMMAND_PREFIX", "UFORA_ANNOUNCEMENTS_CHANNEL", "UFORA_RSS_TOKEN", - "URBAN_DICTIONARY_TOKEN", "IMGFLIP_NAME", "IMGFLIP_PASSWORD", "ScheduleType", @@ -77,7 +76,6 @@ MA_CS_ENG_2_ROLE: Optional[int] = env.int("MA_CS_ENG_2_ROLE", 102330043480016491 """API Keys""" UFORA_RSS_TOKEN: Optional[str] = env.str("UFORA_RSS_TOKEN", None) -URBAN_DICTIONARY_TOKEN: Optional[str] = env.str("URBAN_DICTIONARY_TOKEN", None) IMGFLIP_NAME: Optional[str] = env.str("IMGFLIP_NAME", None) IMGFLIP_PASSWORD: Optional[str] = env.str("IMGFLIP_PASSWORD", None) From 3a44a1b679477672b220867e40e98f83b6bbf185 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Sun, 25 Sep 2022 15:57:02 +0200 Subject: [PATCH 2/9] Fix duplicate schedule slots --- didier/data/embeds/schedules.py | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/didier/data/embeds/schedules.py b/didier/data/embeds/schedules.py index 9923a71..16095d2 100644 --- a/didier/data/embeds/schedules.py +++ b/didier/data/embeds/schedules.py @@ -52,7 +52,7 @@ class Schedule(EmbedBaseModel): if not self.slots: return Schedule() - personal_slots = set() + personal_slots = [] for slot in self.slots: alt_id = slot.alternative_overarching_role_id @@ -65,24 +65,9 @@ class Schedule(EmbedBaseModel): alt_id is not None and alt_id in roles ) if role_found or overarching_role_found: - personal_slots.add(slot) + personal_slots.append(slot) - return Schedule(personal_slots) - - def simplify(self): - """Merge sequential slots in the same location into one - - Note: this is done in-place instead of returning a new schedule! - (The operation is O(n^2)) - - Example: - 13:00 - 14:30: AD3 in S9 - 14:30 - 1600: AD3 in S9 - """ - for first in self.slots: - for second in self.slots: - if first == second: - continue + return Schedule(set(personal_slots)) @overrides def to_embed(self, **kwargs) -> discord.Embed: @@ -179,7 +164,8 @@ class ScheduleSlot: return False if self.start_time == other.end_time: - other.end_time = self.end_time + self.start_time = other.start_time + self._hash = hash(f"{self.course.course_id} {str(self.start_time)}") return True elif self.end_time == other.start_time: self.end_time = other.end_time From ce7ba4285a9bbceef857702ed64ce07f625b0569 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Sun, 25 Sep 2022 15:58:36 +0200 Subject: [PATCH 3/9] Put slots in set at the end --- didier/data/embeds/schedules.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/didier/data/embeds/schedules.py b/didier/data/embeds/schedules.py index 16095d2..ef829cf 100644 --- a/didier/data/embeds/schedules.py +++ b/didier/data/embeds/schedules.py @@ -222,7 +222,7 @@ async def parse_schedule_from_content(content: str, *, database_session: AsyncSe calendar = Calendar(content) events = list(calendar.events) course_codes: dict[str, UforaCourse] = {} - slots: set[ScheduleSlot] = set() + slots: list[ScheduleSlot] = [] for event in events: code = parse_course_code(event.name) @@ -248,9 +248,10 @@ async def parse_schedule_from_content(content: str, *, database_session: AsyncSe if any(s.could_merge_with(slot) for s in slots): continue - slots.add(slot) + slots.append(slot) - return Schedule(slots=slots) + # Cast to set at the END because the __hash__ can change while merging with others + return Schedule(slots=set(slots)) async def parse_schedule(name: ScheduleType, *, database_session: AsyncSession) -> Optional[Schedule]: From 89b345c61b7d2d227b42def2a4ad6ca33b407ec8 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Sun, 25 Sep 2022 17:22:02 +0200 Subject: [PATCH 4/9] Improve error message handling --- didier/data/embeds/error_embed.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/didier/data/embeds/error_embed.py b/didier/data/embeds/error_embed.py index 70bc2d3..ea03bfe 100644 --- a/didier/data/embeds/error_embed.py +++ b/didier/data/embeds/error_embed.py @@ -20,7 +20,7 @@ def _get_traceback(exception: Exception) -> str: break # Escape Discord Markdown formatting - error_string += line.replace(r"*", r"\*").replace(r"_", r"\_") + error_string += line if line.strip(): error_string += "\n" @@ -45,7 +45,12 @@ def create_error_embed(ctx: Optional[commands.Context], exception: Exception) -> invocation = f"{ctx.author.display_name} in {origin}" - embed.add_field(name="Command", value=f"{ctx.message.content}", inline=True) + if ctx.interaction is not None and ctx.interaction.command is not None: + command = ctx.interaction.command.name + else: + command = ctx.message.content or "N/A" + + embed.add_field(name="Command", value=command, inline=True) embed.add_field(name="Context", value=invocation, inline=True) if message: From 5f7270171439b189ef88436d0b8562cdb6164f43 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Sun, 25 Sep 2022 17:41:22 +0200 Subject: [PATCH 5/9] Fix page splitting for menus --- didier/menus/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/didier/menus/common.py b/didier/menus/common.py index 45b0d95..3b893da 100644 --- a/didier/menus/common.py +++ b/didier/menus/common.py @@ -169,7 +169,7 @@ class PageSource(ABC, Generic[T]): def get_page_data(self, page: int) -> list[T]: """Get the chunk of the dataset for page [page]""" - return self.dataset[page : page + self.per_page] + return self.dataset[page * self.per_page : (page + 1) * self.per_page] async def start(self, *, ephemeral: bool = False, timeout: Optional[int] = None) -> Menu: """Shortcut to creating (and starting) a Menu with this source From fa129efd0cb7ab4273c975c5ed5b560213f432f6 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Sun, 25 Sep 2022 17:59:41 +0200 Subject: [PATCH 6/9] Reduce db load for custom commands --- didier/didier.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/didier/didier.py b/didier/didier.py index 0d1671c..06db727 100644 --- a/didier/didier.py +++ b/didier/didier.py @@ -215,9 +215,14 @@ class Didier(commands.Bot): if not message.content.startswith(settings.DISCORD_CUSTOM_COMMAND_PREFIX): return False + # Remove the prefix + content = message.content[len(settings.DISCORD_CUSTOM_COMMAND_PREFIX) :].strip() + + # Message was just "?" (or whatever the prefix was configured to) + if not content: + return False + async with self.postgres_session as session: - # Remove the prefix - content = message.content[len(settings.DISCORD_CUSTOM_COMMAND_PREFIX) :] command = await custom_commands.get_command(session, content) # Command found From 60181aadea5eef94b7da8c3e9f7dd15426f2f2e5 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Sun, 25 Sep 2022 18:21:53 +0200 Subject: [PATCH 7/9] Allow admins to set user's birthdays --- didier/cogs/discord.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/didier/cogs/discord.py b/didier/cogs/discord.py index 7418994..df91673 100644 --- a/didier/cogs/discord.py +++ b/didier/cogs/discord.py @@ -63,12 +63,20 @@ class Discord(commands.Cog): return await ctx.reply(f"{name or 'Your'} birthday is set to **{day}/{month}**.", mention_author=False) @birthday.command(name="set", aliases=["config"]) - async def birthday_set(self, ctx: commands.Context, day: str): + async def birthday_set(self, ctx: commands.Context, day: str, user: Optional[discord.User] = None): """Set your birthday to `day`. Parsing of the `day`-argument happens in the following order: `DD/MM/YYYY`, `DD/MM/YY`, `DD/MM`. Other formats will not be accepted. """ + # Let owners set other people's birthdays + if user is not None and not await self.client.is_owner(ctx.author): + return await ctx.reply("You don't have permission to set other people's birthdays.", mention_author=False) + + # For regular users: default to your own birthday + if user is None: + user = ctx.author + try: default_year = 2001 date = str_to_date(day, formats=["%d/%m/%Y", "%d/%m/%y", "%d/%m"]) @@ -81,7 +89,7 @@ class Discord(commands.Cog): return await ctx.reply(f"`{day}` is not a valid date.", mention_author=False) async with self.client.postgres_session as session: - await birthdays.add_birthday(session, ctx.author.id, date) + await birthdays.add_birthday(session, user.id, date) await self.client.confirm_message(ctx.message) @commands.group(name="bookmark", aliases=["bm", "bookmarks"], case_insensitive=True, invoke_without_command=True) From 5528ce7c2e552443c52bb9ee5aa42394057716e0 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Sun, 25 Sep 2022 18:40:26 +0200 Subject: [PATCH 8/9] Make memegen preview ephemeral --- didier/cogs/fun.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/didier/cogs/fun.py b/didier/cogs/fun.py index 98a86d6..c013a0b 100644 --- a/didier/cogs/fun.py +++ b/didier/cogs/fun.py @@ -119,7 +119,7 @@ class Fun(commands.Cog): @app_commands.describe(template="The meme template to use in the preview.") async def memegen_preview_slash(self, interaction: discord.Interaction, template: str): """Generate a preview for a meme, to see how the fields are structured.""" - await interaction.response.defer() + await interaction.response.defer(ephemeral=True) fields = [f"Field #{i + 1}" for i in range(20)] meme_url = await self._do_generate_meme(template, fields) From 105cee7e6e7c71bafd02adfc5c8c1830dabab860 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Sun, 25 Sep 2022 18:54:18 +0200 Subject: [PATCH 9/9] Fix memegen preview --- didier/cogs/fun.py | 6 ++---- didier/data/apis/imgflip.py | 4 ++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/didier/cogs/fun.py b/didier/cogs/fun.py index c013a0b..6a89833 100644 --- a/didier/cogs/fun.py +++ b/didier/cogs/fun.py @@ -102,8 +102,7 @@ class Fun(commands.Cog): async def memegen_preview_msg(self, ctx: commands.Context, template: str): """Generate a preview for the meme template `template`, to see how the fields are structured.""" async with ctx.typing(): - fields = [f"Field #{i + 1}" for i in range(20)] - meme = await self._do_generate_meme(template, fields) + meme = await self._do_generate_meme(template, []) return await ctx.reply(meme, mention_author=False) @memes_slash.command(name="generate") @@ -121,8 +120,7 @@ class Fun(commands.Cog): """Generate a preview for a meme, to see how the fields are structured.""" await interaction.response.defer(ephemeral=True) - fields = [f"Field #{i + 1}" for i in range(20)] - meme_url = await self._do_generate_meme(template, fields) + meme_url = await self._do_generate_meme(template, []) await interaction.followup.send(meme_url, ephemeral=True) diff --git a/didier/data/apis/imgflip.py b/didier/data/apis/imgflip.py index c44eea6..416e22c 100644 --- a/didier/data/apis/imgflip.py +++ b/didier/data/apis/imgflip.py @@ -10,6 +10,10 @@ __all__ = ["generate_meme"] def generate_boxes(meme: MemeTemplate, fields: list[str]) -> list[str]: """Generate the template boxes for Imgflip""" + # If no fields were passed, generate a template instead + if not fields: + fields = [f"Field #{i + 1}" for i in range(meme.field_count)] + # If a meme only has 1 field, join all the arguments together into one string if meme.field_count == 1: fields = [" ".join(fields)]