From c32b915dc4808a530bd4fd56d110ab643f9922c2 Mon Sep 17 00:00:00 2001 From: MaxKosi Date: Wed, 26 May 2021 16:52:34 +0200 Subject: [PATCH 01/16] added multiple embeds to function createEmbed --- event.py | 57 +++++++++++++++++++++++++++++++++++++++++---- messageFunctions.py | 4 ++-- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/event.py b/event.py index dd190d7..18bd9cf 100644 --- a/event.py +++ b/event.py @@ -219,8 +219,8 @@ def reorder(self): self.roleGroups = newGroups return warnings - # Return an embed for the event - def createEmbed(self) -> Embed: + # Return a list of embeds for the event + def createEmbed(self) -> List[Embed]: date = self.date.strftime(f"%a %Y-%m-%d - %H:%M {cfg.TIME_ZONE}") title = f"{self.title} ({date})" local_time = f"" @@ -245,17 +245,64 @@ def createEmbed(self) -> Embed: # Add field to embed for every rolegroup for group in self.roleGroups.values(): - if len(group.roles) > 0: + if len(group.roles) > 0 and group.name != "Additional": + # excluding the group Additional eventEmbed.add_field(name=group.name, value=str(group), inline=group.isInline) elif group.name == "Dummy": eventEmbed.add_field(name="\N{ZERO WIDTH SPACE}", value="\N{ZERO WIDTH SPACE}", inline=group.isInline) - eventEmbed.set_footer(text="Event ID: " + str(self.id)) - return eventEmbed + eventEmbedList = [eventEmbed] + additionalEmbedList = self.createAdditionalEmbed(date, description) + for embed in additionalEmbedList: + eventEmbedList.append(embed) + return eventEmbedList + + # creates additional embeds. Amount depends on the Additional roles group + def createAdditionalEmbed(self, date, description) -> List[Embed]: + title = f"Additional Roles ({date})" + eventEmbedList = [] + + for group in self.roleGroups.values(): + if group.name == "Additional": + roleList = "" + min_ = 0 + max_ = 0 + counter = int(len(group.roles)/20) + odd = False + if len(group.roles) % 20 != 0: + odd = True + + if counter > 0: + max_ = 20 + for i in range(counter): + + for _ in range(min_, max_): + roleList += str(group.roles[i]) + min_ = max_ + max_ += 20 + eventEmbed = Embed(title=title, + description=description, + colour=self.color) + eventEmbed.add_field(name=group.name, value=roleList, + inline=group.isInline) + eventEmbed.set_footer(text="Event ID: " + str(self.id)) + eventEmbedList.append(eventEmbed) + roleList = "" + if odd: + max_ = min_ + (len(group.roles) % 20) + for i in range(min_, max_): + roleList += str(group.roles[i]) + eventEmbed = Embed(title=title, description=description, + colour=self.color) + eventEmbed.add_field(name=group.name, value=roleList, + inline=group.isInline) + eventEmbed.set_footer(text="Event ID: " + str(self.id)) + eventEmbedList.append(eventEmbed) + return eventEmbedList # Add default role groups def _add_default_role_groups(self): diff --git a/messageFunctions.py b/messageFunctions.py index a0df3be..0283b82 100644 --- a/messageFunctions.py +++ b/messageFunctions.py @@ -46,7 +46,7 @@ async def createEventMessage(event: Event, channel: TextChannel, update_id=True) -> Message: """Create a new event message.""" # Create embed and message - embed = event.createEmbed() + embed = event.createEmbed()[0] message = await channel.send(embed=embed) if update_id: event.messageID = message.id @@ -58,7 +58,7 @@ async def createEventMessage(event: Event, channel: TextChannel, async def updateMessageEmbed(eventMessage: Message, updatedEvent: Event) \ -> None: """Update the embed and footer of a message.""" - newEventEmbed = updatedEvent.createEmbed() + newEventEmbed = updatedEvent.createEmbed()[0] await eventMessage.edit(embed=newEventEmbed) From af9ff69ba2bed82a2533305215687df3ae441fc7 Mon Sep 17 00:00:00 2001 From: Sami Laine Date: Wed, 26 May 2021 20:42:12 +0200 Subject: [PATCH 02/16] Add partial multi-embed functionality The feature is still very much in progress. This commit isn't meant for merging as-is, see various TODO markers. Doesn't reorder or synchronise messages at all yet, just reposts the event message every time --- config.py | 16 +++++++++ event.py | 87 ++++++++++++++++++++++----------------------- messageFunctions.py | 24 +++++++++---- 3 files changed, 76 insertions(+), 51 deletions(-) diff --git a/config.py b/config.py index a900458..962f3a7 100644 --- a/config.py +++ b/config.py @@ -47,6 +47,22 @@ "\N{REGIONAL INDICATOR SYMBOL LETTER H}", "\N{REGIONAL INDICATOR SYMBOL LETTER I}", "\N{REGIONAL INDICATOR SYMBOL LETTER J}", + "\N{REGIONAL INDICATOR SYMBOL LETTER K}", + "\N{REGIONAL INDICATOR SYMBOL LETTER L}", + "\N{REGIONAL INDICATOR SYMBOL LETTER M}", + "\N{REGIONAL INDICATOR SYMBOL LETTER N}", + "\N{REGIONAL INDICATOR SYMBOL LETTER O}", + "\N{REGIONAL INDICATOR SYMBOL LETTER P}", + "\N{REGIONAL INDICATOR SYMBOL LETTER Q}", + "\N{REGIONAL INDICATOR SYMBOL LETTER R}", + "\N{REGIONAL INDICATOR SYMBOL LETTER S}", + "\N{REGIONAL INDICATOR SYMBOL LETTER T}", + "\N{REGIONAL INDICATOR SYMBOL LETTER U}", + "\N{REGIONAL INDICATOR SYMBOL LETTER V}", + "\N{REGIONAL INDICATOR SYMBOL LETTER W}", + "\N{REGIONAL INDICATOR SYMBOL LETTER X}", + "\N{REGIONAL INDICATOR SYMBOL LETTER Y}", + "\N{REGIONAL INDICATOR SYMBOL LETTER Z}", ] ADDITIONAL_ROLE_NAMES = [ diff --git a/event.py b/event.py index 18bd9cf..d7874de 100644 --- a/event.py +++ b/event.py @@ -20,8 +20,11 @@ COLOR = 0xFF4500 SIDEOP_COLOR = 0x0045FF WW2_SIDEOP_COLOR = 0x808080 +# TODO: Change to some reasonable number or remove completely +MAX_REACTIONS = 200 # Discord API limitation -MAX_REACTIONS = 20 +# TODO: Change back to 20, the lower number is just for easier testing +REACTIONS_PER_MESSAGE = 5 class User: @@ -46,6 +49,7 @@ def __init__(self, date: datetime.datetime, guildEmojis: Tuple[Emoji, ...], self.mods = MODS self.color = COLOR if not sideop else SIDEOP_COLOR self.roleGroups: Dict[str, RoleGroup] = {} + # TODO: Handle multiple message IDs self.messageID = 0 self.id = eventID self.sideop = sideop @@ -246,7 +250,7 @@ def createEmbed(self) -> List[Embed]: # Add field to embed for every rolegroup for group in self.roleGroups.values(): if len(group.roles) > 0 and group.name != "Additional": - # excluding the group Additional + # The Additional group is handled separately eventEmbed.add_field(name=group.name, value=str(group), inline=group.isInline) elif group.name == "Dummy": @@ -256,53 +260,46 @@ def createEmbed(self) -> List[Embed]: eventEmbed.set_footer(text="Event ID: " + str(self.id)) eventEmbedList = [eventEmbed] - additionalEmbedList = self.createAdditionalEmbed(date, description) - for embed in additionalEmbedList: - eventEmbedList.append(embed) + if len(self.roleGroups["Additional"].roles) > 0: + eventEmbedList += self.createAdditionalEmbed(date, description) return eventEmbedList - # creates additional embeds. Amount depends on the Additional roles group def createAdditionalEmbed(self, date, description) -> List[Embed]: - title = f"Additional Roles ({date})" - eventEmbedList = [] + """Creates additional embeds. - for group in self.roleGroups.values(): - if group.name == "Additional": - roleList = "" - min_ = 0 - max_ = 0 - counter = int(len(group.roles)/20) - odd = False - if len(group.roles) % 20 != 0: - odd = True - - if counter > 0: - max_ = 20 - for i in range(counter): - - for _ in range(min_, max_): - roleList += str(group.roles[i]) - min_ = max_ - max_ += 20 - eventEmbed = Embed(title=title, - description=description, - colour=self.color) - eventEmbed.add_field(name=group.name, value=roleList, - inline=group.isInline) - eventEmbed.set_footer(text="Event ID: " + str(self.id)) - eventEmbedList.append(eventEmbed) - roleList = "" - if odd: - max_ = min_ + (len(group.roles) % 20) - for i in range(min_, max_): - roleList += str(group.roles[i]) - eventEmbed = Embed(title=title, description=description, - colour=self.color) - eventEmbed.add_field(name=group.name, value=roleList, - inline=group.isInline) - eventEmbed.set_footer(text="Event ID: " + str(self.id)) - eventEmbedList.append(eventEmbed) - return eventEmbedList + The number of embeds depend on the Additional roles group""" + title = f"Additional Roles ({date})" + embeds = [] + + group = self.roleGroups["Additional"] + + # Substract 1 because REACTIONS_PER_MESSAGE roles still fit in a single + # message, otherwise we'd get an empty extra embed on the threshold + embed_count = ((len(group.roles) - 1) // REACTIONS_PER_MESSAGE) + 1 + + for embed_number in range(embed_count): + role_list = "" + first = embed_number * REACTIONS_PER_MESSAGE + last = (embed_number + 1) * REACTIONS_PER_MESSAGE + for role in group.roles[first:last]: + role_list += str(role) + if role_list == "": + # Didn't add any roles -> skipping this embed. Discord doesn't + # like embeds with empty fields. This should only happen if + # this function was called when Additional group is empty + continue + eventEmbed = Embed(title=title, description=description, + colour=self.color) + # TODO: Display the counter in name only if > 1 embeds. + # Alternatively display the counter in title instead of name + eventEmbed.add_field(name=f"{group.name} " + f"({embed_number + 1}/{embed_count})", + value=role_list, + inline=False) + eventEmbed.set_footer(text="Event ID: " + str(self.id)) + embeds.append(eventEmbed) + + return embeds # Add default role groups def _add_default_role_groups(self): diff --git a/messageFunctions.py b/messageFunctions.py index 0283b82..a274180 100644 --- a/messageFunctions.py +++ b/messageFunctions.py @@ -29,6 +29,8 @@ async def sortEventMessages(bot: OperationBot): """Sort events in event database. Raises MessageNotFound if messages are missing.""" + # TODO: Handle multiple message IDs + return EventDatabase.sortEvents() event: Event @@ -46,10 +48,14 @@ async def createEventMessage(event: Event, channel: TextChannel, update_id=True) -> Message: """Create a new event message.""" # Create embed and message - embed = event.createEmbed()[0] - message = await channel.send(embed=embed) + # TODO: Handle multiple message IDs + embeds = event.createEmbed() + for embed in embeds: + print(embed.fields[0].name) + message = await channel.send(embed=embed) if update_id: - event.messageID = message.id + # event.messageID = message.id + pass return message @@ -58,8 +64,10 @@ async def createEventMessage(event: Event, channel: TextChannel, async def updateMessageEmbed(eventMessage: Message, updatedEvent: Event) \ -> None: """Update the embed and footer of a message.""" - newEventEmbed = updatedEvent.createEmbed()[0] - await eventMessage.edit(embed=newEventEmbed) + # TODO: Handle multiple message IDs + # newEventEmbed = updatedEvent.createEmbed()[0] + # await eventMessage.edit(embed=newEventEmbed) + pass # from EventDatabase @@ -114,7 +122,9 @@ async def updateReactions(event: Event, message: Message = None, bot=None, # Add missing emojis for emoji in reactionEmojisToAdd: try: - await message.add_reaction(emoji) + # TODO: Handle multiple message IDs + # await message.add_reaction(emoji) + pass except Forbidden as e: if e.code == 30010: raise RoleError("Too many reactions, not adding role " @@ -147,6 +157,8 @@ def messageEventId(message: Message) -> int: async def syncMessages(events: Dict[int, Event], bot: OperationBot): + # TODO: Handle multiple message IDs + return sorted_events = sorted(list(events.values()), key=lambda event: event.date) for event in sorted_events: try: From 9c86511749e905f1c030532f2afc42d766031933 Mon Sep 17 00:00:00 2001 From: MaxKosi Date: Thu, 27 May 2021 20:56:11 +0200 Subject: [PATCH 03/16] added display counter for >1 embeds --- event.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/event.py b/event.py index d7874de..ed853ed 100644 --- a/event.py +++ b/event.py @@ -276,6 +276,7 @@ def createAdditionalEmbed(self, date, description) -> List[Embed]: # Substract 1 because REACTIONS_PER_MESSAGE roles still fit in a single # message, otherwise we'd get an empty extra embed on the threshold embed_count = ((len(group.roles) - 1) // REACTIONS_PER_MESSAGE) + 1 + embed_count_view = "" for embed_number in range(embed_count): role_list = "" @@ -292,8 +293,9 @@ def createAdditionalEmbed(self, date, description) -> List[Embed]: colour=self.color) # TODO: Display the counter in name only if > 1 embeds. # Alternatively display the counter in title instead of name - eventEmbed.add_field(name=f"{group.name} " - f"({embed_number + 1}/{embed_count})", + if embed_count > 1: + embed_count_view = f"({embed_number + 1}/{embed_count})" + eventEmbed.add_field(name=f"{group.name} {embed_count_view}", value=role_list, inline=False) eventEmbed.set_footer(text="Event ID: " + str(self.id)) From 57c082c3a418bcf587124a6df933da060b9cffcf Mon Sep 17 00:00:00 2001 From: MaxKosi Date: Thu, 27 May 2021 21:14:03 +0200 Subject: [PATCH 04/16] added multiple message IDs --- event.py | 5 ++--- messageFunctions.py | 8 +++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/event.py b/event.py index ed853ed..198e09a 100644 --- a/event.py +++ b/event.py @@ -49,8 +49,9 @@ def __init__(self, date: datetime.datetime, guildEmojis: Tuple[Emoji, ...], self.mods = MODS self.color = COLOR if not sideop else SIDEOP_COLOR self.roleGroups: Dict[str, RoleGroup] = {} - # TODO: Handle multiple message IDs + # TODO: Remove when everything is fixed self.messageID = 0 + self.messageIDList: List[int] = [] self.id = eventID self.sideop = sideop if platoon_size is None: @@ -291,8 +292,6 @@ def createAdditionalEmbed(self, date, description) -> List[Embed]: continue eventEmbed = Embed(title=title, description=description, colour=self.color) - # TODO: Display the counter in name only if > 1 embeds. - # Alternatively display the counter in title instead of name if embed_count > 1: embed_count_view = f"({embed_number + 1}/{embed_count})" eventEmbed.add_field(name=f"{group.name} {embed_count_view}", diff --git a/messageFunctions.py b/messageFunctions.py index a274180..167344a 100644 --- a/messageFunctions.py +++ b/messageFunctions.py @@ -47,15 +47,13 @@ async def sortEventMessages(bot: OperationBot): async def createEventMessage(event: Event, channel: TextChannel, update_id=True) -> Message: """Create a new event message.""" - # Create embed and message - # TODO: Handle multiple message IDs + # Create embeds and messages embeds = event.createEmbed() + event.messageIDList.clear() for embed in embeds: print(embed.fields[0].name) message = await channel.send(embed=embed) - if update_id: - # event.messageID = message.id - pass + event.messageIDList.append(message.id) return message From 4f2e9b596ef1a04c9a4c18320e6c8b102276fad4 Mon Sep 17 00:00:00 2001 From: MaxKosi Date: Mon, 7 Jun 2021 11:30:38 +0200 Subject: [PATCH 05/16] added reactions and handle updates --- commandListener.py | 26 ++++++---- converters.py | 4 +- event.py | 7 ++- eventDatabase.py | 8 +-- eventListener.py | 3 +- messageFunctions.py | 123 +++++++++++++++++++------------------------- 6 files changed, 82 insertions(+), 89 deletions(-) diff --git a/commandListener.py b/commandListener.py index 502ecd4..97fd8eb 100644 --- a/commandListener.py +++ b/commandListener.py @@ -164,7 +164,7 @@ async def _create_event(self, ctx: Context, _date: datetime, @command(aliases=["cat"]) async def show(self, ctx: Context, event: ArgEvent): message = await msgFnc.getEventMessage(event, self.bot) - await ctx.send(message.jump_url) + await ctx.send(message[0].jump_url) await msgFnc.createEventMessage(event, cast(TextChannel, ctx.channel), update_id=False) @@ -546,7 +546,8 @@ async def removegroup(self, ctx: Context, eventMessage: ArgMessage, *, for reaction in event.getReactionsOfGroup(groupName): await eventMessage.remove_reaction(reaction, self.bot.user) event.removeRoleGroup(groupName) - await msgFnc.updateMessageEmbed(eventMessage, event) + await msgFnc.updateMessageEmbed([eventMessage], event, + self.bot.eventchannel) EventDatabase.toJson() # Update JSON file await ctx.send(f"Group {groupName} removed from {event}") @@ -821,7 +822,7 @@ async def archive(self, ctx: Context, event: ArgEvent): await ctx.send(f"Internal error: event {event} without " "a message found") else: - await eventMessage.delete() + await eventMessage[0].delete() # Create new message await msgFnc.createEventMessage(event, self.bot.eventarchivechannel) @@ -832,13 +833,14 @@ async def _delete(self, event: Event, archived=False): # TODO: Move to a more appropriate location EventDatabase.removeEvent(event.id, archived=archived) try: - eventMessage = await msgFnc.getEventMessage( + eventMessageList = await msgFnc.getEventMessage( event, self.bot, archived=archived) except MessageNotFound: # Message already deleted, nothing to be done pass else: - await eventMessage.delete() + for eventMessage in eventMessageList: + await eventMessage.delete() EventDatabase.toJson(archive=archived) # Delete event command @@ -982,12 +984,16 @@ async def _update_event(self, event: Event, import_db=False, try: message = await msgFnc.getEventMessage(event, self.bot) except MessageNotFound: - message = await msgFnc.createEventMessage(event, - self.bot.eventchannel) + message = [await msgFnc.createEventMessage(event, + self.bot.eventchannel)] - await msgFnc.updateMessageEmbed(eventMessage=message, - updatedEvent=event) - await msgFnc.updateReactions(event=event, message=message, + await msgFnc.updateMessageEmbed(eventMessageList=message, + updatedEvent=event, + channel=self.bot.eventchannel) + + message = await msgFnc.getEventMessage(event, self.bot) + + await msgFnc.updateReactions(event=event, messageList=message, reorder=reorder) if export: EventDatabase.toJson() diff --git a/converters.py b/converters.py index 89c9dbc..0b668d7 100644 --- a/converters.py +++ b/converters.py @@ -1,6 +1,6 @@ import re from datetime import date, datetime, time -from typing import cast +from typing import List, cast from discord import Message from discord.ext.commands.context import Context @@ -184,7 +184,7 @@ async def convert(cls, _: Context, arg: str) -> time: class ArgMessage(Message): @classmethod - async def convert(cls, ctx: Context, arg: str) -> Message: + async def convert(cls, ctx: Context, arg: str) -> List[Message]: try: event_id = int(arg) except ValueError as e: diff --git a/event.py b/event.py index 198e09a..82090cb 100644 --- a/event.py +++ b/event.py @@ -468,6 +468,9 @@ def hasRoleGroup(self, groupName: str) -> bool: def get_additional_role(self, role_name: str) -> Role: return self.roleGroups["Additional"][role_name] + def getReactionsPerMessage(self) -> int: + return REACTIONS_PER_MESSAGE + def signup(self, roleToSet: Role, user: discord.abc.User, replace=False) \ -> Tuple[Optional[Role], User]: """Add username to role. @@ -536,7 +539,7 @@ def toJson(self, brief_output=False) -> Dict[str, Any]: data["mods"] = self.mods if not brief_output: data["color"] = self.color - data["messageID"] = self.messageID + data["messageIDList"] = self.messageIDList data["platoon_size"] = self.platoon_size data["sideop"] = self.sideop data["roleGroups"] = roleGroupsData @@ -554,7 +557,7 @@ def fromJson(self, eventID, data: dict, emojis, manual_load=False): self.mods = str(data.get("mods", MODS)) if not manual_load: self.color = int(data.get("color", COLOR)) - self.messageID = int(data.get("messageID", 0)) + self.messageIDList = list(data.get("messageIDList", [0])) self.platoon_size = str(data.get("platoon_size", PLATOON_SIZE)) self.sideop = bool(data.get("sideop", False)) # TODO: Handle missing roleGroups diff --git a/eventDatabase.py b/eventDatabase.py index 7ef9a4c..65c337b 100644 --- a/eventDatabase.py +++ b/eventDatabase.py @@ -127,22 +127,22 @@ def getArchivedEventByID(cls, eventID: int): @classmethod def sortEvents(cls): sortedEvents = [] - messageIDs = [] + messageIDLists = [] # Store existing events for event in cls.events.values(): sortedEvents.append(event) - messageIDs.append(event.messageID) + messageIDLists.append(event.messageIDList) # Sort events based on date and time sortedEvents.sort(key=lambda event: event.date, reverse=True) - messageIDs.sort(reverse=True) + messageIDLists.sort(reverse=True) # Fill events again cls.events = {} for event in sortedEvents: # event = sortedEvents[index] - event.messageID = messageIDs.pop() + event.messageIDList = messageIDLists.pop() cls.events[event.id] = event @classmethod diff --git a/eventListener.py b/eventListener.py index a082f1b..a66edcb 100644 --- a/eventListener.py +++ b/eventListener.py @@ -135,7 +135,8 @@ async def _handle_signup(self, event: Event, partial_emoji: PartialEmoji, old_role = f"{removed_role.display_name} -> " # Update discord embed - await msgFnc.updateMessageEmbed(message, event) + await msgFnc.updateMessageEmbed([message], event, + self.bot.eventchannel) EventDatabase.toJson() delta_message = "" diff --git a/messageFunctions.py b/messageFunctions.py index 167344a..507c581 100644 --- a/messageFunctions.py +++ b/messageFunctions.py @@ -2,16 +2,15 @@ from discord import Emoji, Message, NotFound, TextChannel from discord.embeds import Embed -from discord.errors import Forbidden -from errors import MessageNotFound, RoleError +from errors import MessageNotFound from event import Event from eventDatabase import EventDatabase from operationbot import OperationBot async def getEventMessage(event: Event, bot: OperationBot, archived=False) \ - -> Message: + -> List[Message]: """Get a message related to an event.""" if archived: channel = bot.eventarchivechannel @@ -19,7 +18,10 @@ async def getEventMessage(event: Event, bot: OperationBot, archived=False) \ channel = bot.eventchannel try: - return await channel.fetch_message(event.messageID) + messageIDList = [] + for messageID in event.messageIDList: + messageIDList.append(await channel.fetch_message(messageID)) + return messageIDList except NotFound as e: raise MessageNotFound("No event message found with " f"message ID {event.messageID}") from e @@ -29,18 +31,16 @@ async def sortEventMessages(bot: OperationBot): """Sort events in event database. Raises MessageNotFound if messages are missing.""" - # TODO: Handle multiple message IDs - return EventDatabase.sortEvents() event: Event for event in EventDatabase.events.values(): try: - message = await getEventMessage(event, bot) + messageList = await getEventMessage(event, bot) except MessageNotFound as e: raise MessageNotFound(f"sortEventMessages: {e}") from e - await updateMessageEmbed(message, event) - await updateReactions(event, message=message) + await updateMessageEmbed(messageList, event, bot.eventchannel) + await updateReactions(event, messageList=messageList) # from EventDatabase @@ -49,28 +49,34 @@ async def createEventMessage(event: Event, channel: TextChannel, """Create a new event message.""" # Create embeds and messages embeds = event.createEmbed() - event.messageIDList.clear() - for embed in embeds: - print(embed.fields[0].name) - message = await channel.send(embed=embed) - event.messageIDList.append(message.id) + if update_id: + event.messageIDList.clear() + for embed in embeds: + print(embed.fields[0].name) + message = await channel.send(embed=embed) + event.messageIDList.append(message.id) return message # was: EventDatabase.updateEvent -async def updateMessageEmbed(eventMessage: Message, updatedEvent: Event) \ +async def updateMessageEmbed(eventMessageList: List[Message], + updatedEvent: Event, channel: TextChannel) \ -> None: """Update the embed and footer of a message.""" - # TODO: Handle multiple message IDs - # newEventEmbed = updatedEvent.createEmbed()[0] - # await eventMessage.edit(embed=newEventEmbed) - pass + newEventEmbedList = updatedEvent.createEmbed() + if len(newEventEmbedList) == len(eventMessageList): + for i in range(len(newEventEmbedList)): + await eventMessageList[i].edit(embed=newEventEmbedList[i]) + else: + for eventMessage in eventMessageList: + await eventMessage.delete() + await createEventMessage(updatedEvent, channel) # from EventDatabase -async def updateReactions(event: Event, message: Message = None, bot=None, - reorder=False): +async def updateReactions(event: Event, messageList: List[Message] = None, + bot=None, reorder=False): """ Update reactions of an event message. @@ -78,17 +84,18 @@ async def updateReactions(event: Event, message: Message = None, bot=None, the function with reorder = True causes all reactions to be removed and reinserted in the correct order. """ - if message is None: + # TODO make efficient again + if not messageList: if bot is None: - raise ValueError("Requires either the `message` or `bot` argument" - " to be provided") - message = await getEventMessage(event, bot) + raise ValueError("Requires either the `messageList` or `bot` " + "argument to be provided") + messageList = await getEventMessage(event, bot) reactions: List[Union[Emoji, str]] = event.getReactions() - reactionsCurrent = message.reactions + reactionsCurrent = [] reactionEmojisCurrent = {} - reactionsToRemove = [] - reactionEmojisToAdd = [] + for message in messageList: + reactionsCurrent.extend(message.reactions) # Find current reaction emojis for reaction in reactionsCurrent: @@ -98,49 +105,25 @@ async def updateReactions(event: Event, message: Message = None, bot=None, # Emojis are already correct, no need for further edits return - if reorder: - # Re-adding all reactions in order to put them in the correct order + for message in messageList: await message.clear_reactions() - reactionEmojisToAdd = reactions - else: - # Find emojis to remove - for emoji, reaction in reactionEmojisCurrent.items(): - if emoji not in reactions: - reactionsToRemove.append(reaction) - - # Find emojis to add - for emoji in reactions: - if emoji not in reactionEmojisCurrent.keys(): - reactionEmojisToAdd.append(emoji) - - # Remove existing unintended reactions - for reaction in reactionsToRemove: - await message.clear_reaction(reaction) - - # Add missing emojis - for emoji in reactionEmojisToAdd: - try: - # TODO: Handle multiple message IDs - # await message.add_reaction(emoji) - pass - except Forbidden as e: - if e.code == 30010: - raise RoleError("Too many reactions, not adding role " - f"{emoji}. This should not happen.") from e - -# async def createMessages(events: Dict[int, Event], bot): -# # Update event message contents and add reactions - -# # Clear events channel -# if cfg.PURGE_ON_CONNECT: -# await bot.eventchannel.purge(limit=100) - -# for event in events.values(): -# await createEventMessage(event, bot.eventchannel) -# for event in events.values(): -# message = await getEventMessage(event, bot) -# await updateMessageEmbed(message, event) -# await updateReactions(event, bot=bot) + + # Add Emojis for the first embed without additional Roles + for i in range((len(reactions) - event.additionalRoleCount)): + emoji = reactions.pop(0) + await messageList[0].add_reaction(emoji) + + # Add Emojis to following embeds + counter = 0 + messageNumber = 1 + for i in range(len(reactions)): + emoji = reactions.pop(0) + await messageList[messageNumber].add_reaction(emoji) + + counter += 1 + if counter == event.getReactionsPerMessage(): + messageNumber += 1 + counter = 0 def messageEventId(message: Message) -> int: From 0491d310d36548067b34cb6250efff9bb9ff2bc2 Mon Sep 17 00:00:00 2001 From: MaxKosi Date: Tue, 8 Jun 2021 10:27:50 +0200 Subject: [PATCH 06/16] improved embeds and reactions --- commandListener.py | 5 +++-- event.py | 25 ++++++++++++++++--------- eventDatabase.py | 2 +- messageFunctions.py | 41 +++++++++++++++++++++++------------------ 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/commandListener.py b/commandListener.py index 97fd8eb..c6bc937 100644 --- a/commandListener.py +++ b/commandListener.py @@ -447,7 +447,8 @@ async def _add_role(self, event: Event, rolename: str, batch=False): # Adding the latest role failed, saving previously added roles await self._update_event(event, reorder=False) raise e - await self._update_event(event, reorder=False, export=(not batch)) + if not batch: + await self._update_event(event, reorder=False, export=(not batch)) @command(aliases=['ar']) async def addrole(self, ctx: Context, event: ArgEvent, *, @@ -979,7 +980,7 @@ async def _update_event(self, event: Event, import_db=False, if import_db: await self.bot.import_database() # Event instance might have changed because of DB import, get again - event = EventDatabase.getEventByMessage(event.messageID) + event = EventDatabase.getEventByMessage(event.messageIDList[0]) try: message = await msgFnc.getEventMessage(event, self.bot) diff --git a/event.py b/event.py index 82090cb..5d875f8 100644 --- a/event.py +++ b/event.py @@ -21,10 +21,11 @@ SIDEOP_COLOR = 0x0045FF WW2_SIDEOP_COLOR = 0x808080 # TODO: Change to some reasonable number or remove completely -MAX_REACTIONS = 200 +# 35 additional Emotes # first embed - better: len(cfg.ADDITIONAL_ROLE_EMOJIS) +# + something +MAX_REACTIONS = 55 # Discord API limitation -# TODO: Change back to 20, the lower number is just for easier testing -REACTIONS_PER_MESSAGE = 5 +REACTIONS_PER_MESSAGE = 20 class User: @@ -49,9 +50,7 @@ def __init__(self, date: datetime.datetime, guildEmojis: Tuple[Emoji, ...], self.mods = MODS self.color = COLOR if not sideop else SIDEOP_COLOR self.roleGroups: Dict[str, RoleGroup] = {} - # TODO: Remove when everything is fixed - self.messageID = 0 - self.messageIDList: List[int] = [] + self.messageIDList = [0] self.id = eventID self.sideop = sideop if platoon_size is None: @@ -260,9 +259,17 @@ def createEmbed(self) -> List[Embed]: inline=group.isInline) eventEmbed.set_footer(text="Event ID: " + str(self.id)) - eventEmbedList = [eventEmbed] - if len(self.roleGroups["Additional"].roles) > 0: - eventEmbedList += self.createAdditionalEmbed(date, description) + if len(self.getReactions()) <= REACTIONS_PER_MESSAGE: + for group in self.roleGroups.values(): + if group.name == "Additional" and \ + len(self.roleGroups["Additional"].roles) > 0: + eventEmbed.add_field(name=group.name, value=str(group), + inline=group.isInline) + eventEmbedList = [eventEmbed] + else: + eventEmbedList = [eventEmbed] + if len(self.roleGroups["Additional"].roles) > 0: + eventEmbedList += self.createAdditionalEmbed(date, description) return eventEmbedList def createAdditionalEmbed(self, date, description) -> List[Embed]: diff --git a/eventDatabase.py b/eventDatabase.py index 65c337b..eaf93a4 100644 --- a/eventDatabase.py +++ b/eventDatabase.py @@ -88,7 +88,7 @@ def getEventByMessage(cls, messageID: int, archived=False) -> Event: collection = cls.events for event in collection.values(): - if event.messageID == messageID: + if event.messageIDList[0] == messageID: return event raise EventNotFound(f"No event found with message ID {messageID}") diff --git a/messageFunctions.py b/messageFunctions.py index 507c581..4c3da1a 100644 --- a/messageFunctions.py +++ b/messageFunctions.py @@ -24,7 +24,7 @@ async def getEventMessage(event: Event, bot: OperationBot, archived=False) \ return messageIDList except NotFound as e: raise MessageNotFound("No event message found with " - f"message ID {event.messageID}") from e + f"message ID {event.messageIDList}") from e async def sortEventMessages(bot: OperationBot): @@ -84,7 +84,7 @@ async def updateReactions(event: Event, messageList: List[Message] = None, the function with reorder = True causes all reactions to be removed and reinserted in the correct order. """ - # TODO make efficient again + # TODO make efficient again (if necessary, works quiet well rn) if not messageList: if bot is None: raise ValueError("Requires either the `messageList` or `bot` " @@ -108,22 +108,27 @@ async def updateReactions(event: Event, messageList: List[Message] = None, for message in messageList: await message.clear_reactions() - # Add Emojis for the first embed without additional Roles - for i in range((len(reactions) - event.additionalRoleCount)): - emoji = reactions.pop(0) - await messageList[0].add_reaction(emoji) - - # Add Emojis to following embeds - counter = 0 - messageNumber = 1 - for i in range(len(reactions)): - emoji = reactions.pop(0) - await messageList[messageNumber].add_reaction(emoji) - - counter += 1 - if counter == event.getReactionsPerMessage(): - messageNumber += 1 - counter = 0 + if len(reactions) <= event.getReactionsPerMessage(): + # Add Emojis for the first embed with additional Roles + for emoji in reactions: + await messageList[0].add_reaction(emoji) + else: + # Add Emojis for the first embed without additional Roles + for i in range((len(reactions) - event.additional_role_count)): + emoji = reactions.pop(0) + await messageList[0].add_reaction(emoji) + + # Add Emojis to following embeds + counter = 0 + messageNumber = 1 + for i in range(len(reactions)): + emoji = reactions.pop(0) + await messageList[messageNumber].add_reaction(emoji) + + counter += 1 + if counter == event.getReactionsPerMessage(): + messageNumber += 1 + counter = 0 def messageEventId(message: Message) -> int: From 135c29954502d0a8061e9a85aa7cf908c4a0b31b Mon Sep 17 00:00:00 2001 From: MaxKosi Date: Tue, 8 Jun 2021 11:18:04 +0200 Subject: [PATCH 07/16] added on_raw_reaction_add function --- event.py | 4 ++-- eventDatabase.py | 5 +++-- eventListener.py | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/event.py b/event.py index 5d875f8..c3610e1 100644 --- a/event.py +++ b/event.py @@ -21,9 +21,9 @@ SIDEOP_COLOR = 0x0045FF WW2_SIDEOP_COLOR = 0x808080 # TODO: Change to some reasonable number or remove completely -# 35 additional Emotes # first embed - better: len(cfg.ADDITIONAL_ROLE_EMOJIS) +# 36 additional Emotes # first embed - better: len(cfg.ADDITIONAL_ROLE_EMOJIS) # + something -MAX_REACTIONS = 55 +MAX_REACTIONS = 56 # Discord API limitation REACTIONS_PER_MESSAGE = 20 diff --git a/eventDatabase.py b/eventDatabase.py index eaf93a4..4058d2f 100644 --- a/eventDatabase.py +++ b/eventDatabase.py @@ -88,8 +88,9 @@ def getEventByMessage(cls, messageID: int, archived=False) -> Event: collection = cls.events for event in collection.values(): - if event.messageIDList[0] == messageID: - return event + for eventMessageID in event.messageIDList: + if eventMessageID == messageID: + return event raise EventNotFound(f"No event found with message ID {messageID}") @classmethod diff --git a/eventListener.py b/eventListener.py index a66edcb..f05e99b 100644 --- a/eventListener.py +++ b/eventListener.py @@ -135,7 +135,8 @@ async def _handle_signup(self, event: Event, partial_emoji: PartialEmoji, old_role = f"{removed_role.display_name} -> " # Update discord embed - await msgFnc.updateMessageEmbed([message], event, + eventMessages = await msgFnc.getEventMessage(event, self.bot) + await msgFnc.updateMessageEmbed(eventMessages, event, self.bot.eventchannel) EventDatabase.toJson() From e8569bb4510a179e02a89c84b5e4f29e7148e33d Mon Sep 17 00:00:00 2001 From: MaxKosi Date: Tue, 13 Jul 2021 16:17:30 +0200 Subject: [PATCH 08/16] fixed remaining functions (hopefully) --- commandListener.py | 12 ++++++------ messageFunctions.py | 14 ++++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/commandListener.py b/commandListener.py index c6bc937..7c2ec18 100644 --- a/commandListener.py +++ b/commandListener.py @@ -511,6 +511,7 @@ async def renamerole(self, ctx: Context, event: ArgEvent, await ctx.send(f"Role renamed. Old name: {old_name}, " f"new name: {role} @ {event}") + # TODO this doesnt work @command(aliases=['rra']) async def removereaction(self, ctx: Context, event: ArgEvent, reaction: str): @@ -537,15 +538,13 @@ async def removegroup(self, ctx: Context, eventMessage: ArgMessage, *, Example: removegroup 1 Bravo """ - event = EventDatabase.getEventByMessage(eventMessage.id) + event = EventDatabase.getEventByMessage(eventMessage[0].id) if not event.hasRoleGroup(groupName): await ctx.send(f"No role group found with name {groupName}") return # Remove reactions, remove role, update event, add reactions, export - for reaction in event.getReactionsOfGroup(groupName): - await eventMessage.remove_reaction(reaction, self.bot.user) event.removeRoleGroup(groupName) await msgFnc.updateMessageEmbed([eventMessage], event, self.bot.eventchannel) @@ -611,7 +610,7 @@ async def setterrain(self, ctx: Context, event: ArgEvent, *, """ Set event terrain. - Example: settime 1 Takistan + Example: setterrain 1 Takistan """ # Change terrain, update event, export event.setTerrain(terrain) @@ -818,12 +817,13 @@ async def archive(self, ctx: Context, event: ArgEvent): # Archive event and export EventDatabase.archiveEvent(event) try: - eventMessage = await msgFnc.getEventMessage(event, self.bot) + eventMessageList = await msgFnc.getEventMessage(event, self.bot) except MessageNotFound: await ctx.send(f"Internal error: event {event} without " "a message found") else: - await eventMessage[0].delete() + for eventMessage in eventMessageList: + await eventMessage.delete() # Create new message await msgFnc.createEventMessage(event, self.bot.eventarchivechannel) diff --git a/messageFunctions.py b/messageFunctions.py index 4c3da1a..23e5a3b 100644 --- a/messageFunctions.py +++ b/messageFunctions.py @@ -52,9 +52,11 @@ async def createEventMessage(event: Event, channel: TextChannel, if update_id: event.messageIDList.clear() for embed in embeds: - print(embed.fields[0].name) message = await channel.send(embed=embed) event.messageIDList.append(message.id) + else: + for embed in embeds: + message = await channel.send(embed=embed) return message @@ -144,24 +146,24 @@ def messageEventId(message: Message) -> int: async def syncMessages(events: Dict[int, Event], bot: OperationBot): # TODO: Handle multiple message IDs - return sorted_events = sorted(list(events.values()), key=lambda event: event.date) for event in sorted_events: try: - message = await getEventMessage(event, bot) + messageList = await getEventMessage(event, bot) except MessageNotFound: print(f"Missing a message for event {event}, creating") await createEventMessage(event, bot.eventchannel) else: - if messageEventId(message) == event.id: - print(f"Found message {message.id} for event {event}") + if messageEventId(messageList[0]) == event.id: + print(f"Found message {messageList[0].id} for event {event}") else: print(f"Found incorrect message for event {event}, deleting " f"and creating") # Technically multiple events might have the same saved # messageID but it's simpler to just recreate messages here if # the event ID doesn't match - await message.delete() + for message in messageList: + await message.delete() await createEventMessage(event, bot.eventchannel) await sortEventMessages(bot) From b941325181ae8a3f9d757c39895e4bf08b4ab320 Mon Sep 17 00:00:00 2001 From: Sami Laine Date: Tue, 21 Sep 2021 21:34:23 +0200 Subject: [PATCH 09/16] Simplify getEmbed, rename to getEmbeds --- commandListener.py | 2 +- converters.py | 2 +- event.py | 58 ++++++++++++++++++++++----------------------- messageFunctions.py | 4 ++-- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/commandListener.py b/commandListener.py index 7c2ec18..c599905 100644 --- a/commandListener.py +++ b/commandListener.py @@ -546,7 +546,7 @@ async def removegroup(self, ctx: Context, eventMessage: ArgMessage, *, # Remove reactions, remove role, update event, add reactions, export event.removeRoleGroup(groupName) - await msgFnc.updateMessageEmbed([eventMessage], event, + await msgFnc.updateMessageEmbed(eventMessage, event, self.bot.eventchannel) EventDatabase.toJson() # Update JSON file await ctx.send(f"Group {groupName} removed from {event}") diff --git a/converters.py b/converters.py index 0b668d7..1175f77 100644 --- a/converters.py +++ b/converters.py @@ -182,7 +182,7 @@ async def convert(cls, _: Context, arg: str) -> time: "Has to be HH:MM or HHMM") -class ArgMessage(Message): +class ArgMessage(List[Message]): @classmethod async def convert(cls, ctx: Context, arg: str) -> List[Message]: try: diff --git a/event.py b/event.py index c3610e1..0427362 100644 --- a/event.py +++ b/event.py @@ -223,10 +223,9 @@ def reorder(self): self.roleGroups = newGroups return warnings - # Return a list of embeds for the event - def createEmbed(self) -> List[Embed]: + def _create_embed(self, title: str) -> Embed: date = self.date.strftime(f"%a %Y-%m-%d - %H:%M {cfg.TIME_ZONE}") - title = f"{self.title} ({date})" + title = f"{title} ({date})" local_time = f"" server_port = (f"\nServer port: **{self.port}**" if self.port != cfg.PORT_DEFAULT else "") @@ -244,12 +243,15 @@ def createEmbed(self) -> List[Embed]: f"{server_port}" f"{event_description}" f"{mods}") - eventEmbed = Embed(title=title, description=description, - colour=self.color) + return Embed(title=title, description=description, colour=self.color) + + def createEmbeds(self) -> List[Embed]: + """Return a list of embeds for the event""" + eventEmbed = self._create_embed(self.title) # Add field to embed for every rolegroup for group in self.roleGroups.values(): - if len(group.roles) > 0 and group.name != "Additional": + if len(group) > 0 and group.name != "Additional": # The Additional group is handled separately eventEmbed.add_field(name=group.name, value=str(group), inline=group.isInline) @@ -259,51 +261,49 @@ def createEmbed(self) -> List[Embed]: inline=group.isInline) eventEmbed.set_footer(text="Event ID: " + str(self.id)) + if len(self.roleGroups["Additional"]) == 0: + # There are no additional roles, the embed is ready + return [eventEmbed] + + # Handle additional roles if len(self.getReactions()) <= REACTIONS_PER_MESSAGE: - for group in self.roleGroups.values(): - if group.name == "Additional" and \ - len(self.roleGroups["Additional"].roles) > 0: - eventEmbed.add_field(name=group.name, value=str(group), - inline=group.isInline) - eventEmbedList = [eventEmbed] - else: - eventEmbedList = [eventEmbed] - if len(self.roleGroups["Additional"].roles) > 0: - eventEmbedList += self.createAdditionalEmbed(date, description) - return eventEmbedList + # All roles fit in a single message + group = self.roleGroups["Additional"] + eventEmbed.add_field(name=group.name, value=str(group), + inline=group.isInline) + return [eventEmbed] - def createAdditionalEmbed(self, date, description) -> List[Embed]: + return [eventEmbed] + self.createAdditionalEmbeds() + + def createAdditionalEmbeds(self) -> List[Embed]: """Creates additional embeds. The number of embeds depend on the Additional roles group""" - title = f"Additional Roles ({date})" embeds = [] - group = self.roleGroups["Additional"] # Substract 1 because REACTIONS_PER_MESSAGE roles still fit in a single # message, otherwise we'd get an empty extra embed on the threshold - embed_count = ((len(group.roles) - 1) // REACTIONS_PER_MESSAGE) + 1 - embed_count_view = "" + embed_count = ((len(group) - 1) // REACTIONS_PER_MESSAGE) + 1 for embed_number in range(embed_count): role_list = "" first = embed_number * REACTIONS_PER_MESSAGE last = (embed_number + 1) * REACTIONS_PER_MESSAGE for role in group.roles[first:last]: - role_list += str(role) + role_list += f'{str(role)}\n' if role_list == "": # Didn't add any roles -> skipping this embed. Discord doesn't # like embeds with empty fields. This should only happen if # this function was called when Additional group is empty continue - eventEmbed = Embed(title=title, description=description, - colour=self.color) + eventEmbed = self._create_embed("Additional Roles") if embed_count > 1: - embed_count_view = f"({embed_number + 1}/{embed_count})" - eventEmbed.add_field(name=f"{group.name} {embed_count_view}", - value=role_list, - inline=False) + embed_counter = f" ({embed_number + 1}/{embed_count})" + else: + embed_counter = "" + eventEmbed.add_field(name=f"{group.name}{embed_counter}", + value=role_list, inline=False) eventEmbed.set_footer(text="Event ID: " + str(self.id)) embeds.append(eventEmbed) diff --git a/messageFunctions.py b/messageFunctions.py index 23e5a3b..4ca0754 100644 --- a/messageFunctions.py +++ b/messageFunctions.py @@ -48,7 +48,7 @@ async def createEventMessage(event: Event, channel: TextChannel, update_id=True) -> Message: """Create a new event message.""" # Create embeds and messages - embeds = event.createEmbed() + embeds = event.createEmbeds() if update_id: event.messageIDList.clear() for embed in embeds: @@ -66,7 +66,7 @@ async def updateMessageEmbed(eventMessageList: List[Message], updatedEvent: Event, channel: TextChannel) \ -> None: """Update the embed and footer of a message.""" - newEventEmbedList = updatedEvent.createEmbed() + newEventEmbedList = updatedEvent.createEmbeds() if len(newEventEmbedList) == len(eventMessageList): for i in range(len(newEventEmbedList)): await eventMessageList[i].edit(embed=newEventEmbedList[i]) From fa35b02189a2c6a0f772fc65e53b166997b38967 Mon Sep 17 00:00:00 2001 From: Sami Laine Date: Tue, 21 Sep 2021 22:32:10 +0200 Subject: [PATCH 10/16] Rename message functions to plural All functions that handle multiple messages have been renamed to use plural in the function name. Additionally, there is a singular getEventMessage that's used by some functions, including the plural getEventMessages --- commandListener.py | 44 ++++++++--------- converters.py | 14 ++++-- eventListener.py | 14 +++--- messageFunctions.py | 113 ++++++++++++++++++++------------------------ 4 files changed, 90 insertions(+), 95 deletions(-) diff --git a/commandListener.py b/commandListener.py index c599905..2458870 100644 --- a/commandListener.py +++ b/commandListener.py @@ -17,7 +17,7 @@ import config as cfg import messageFunctions as msgFnc from converters import (ArgArchivedEvent, ArgDate, ArgDateTime, ArgEvent, - ArgMessage, ArgRole, ArgTime, UnquotedStr) + ArgMessages, ArgRole, ArgTime, UnquotedStr) from errors import MessageNotFound, RoleError, UnexpectedRole from event import Event from eventDatabase import EventDatabase @@ -153,7 +153,7 @@ async def _create_event(self, ctx: Context, _date: datetime, # Create event and sort events, export event: Event = EventDatabase.createEvent(_date, sideop=sideop, platoon_size=platoon_size) - await msgFnc.createEventMessage(event, self.bot.eventchannel) + await msgFnc.createEventMessages(event, self.bot.eventchannel) if not batch: await msgFnc.sortEventMessages(self.bot) EventDatabase.toJson() # Update JSON file @@ -164,9 +164,9 @@ async def _create_event(self, ctx: Context, _date: datetime, @command(aliases=["cat"]) async def show(self, ctx: Context, event: ArgEvent): message = await msgFnc.getEventMessage(event, self.bot) - await ctx.send(message[0].jump_url) - await msgFnc.createEventMessage(event, cast(TextChannel, ctx.channel), - update_id=False) + await ctx.send(message.jump_url) + await msgFnc.createEventMessages(event, cast(TextChannel, ctx.channel), + update_id=False) # Create event command @command(aliases=['c']) @@ -531,14 +531,14 @@ def _find_remove_reaction(self, reaction: str, event: Event): raise BadArgument("No reaction found") @command(aliases=['rg']) - async def removegroup(self, ctx: Context, eventMessage: ArgMessage, *, + async def removegroup(self, ctx: Context, eventMessages: ArgMessages, *, groupName: UnquotedStr): """ Remove a role group from the event. Example: removegroup 1 Bravo """ - event = EventDatabase.getEventByMessage(eventMessage[0].id) + event = EventDatabase.getEventByMessage(eventMessages[0].id) if not event.hasRoleGroup(groupName): await ctx.send(f"No role group found with name {groupName}") @@ -546,8 +546,8 @@ async def removegroup(self, ctx: Context, eventMessage: ArgMessage, *, # Remove reactions, remove role, update event, add reactions, export event.removeRoleGroup(groupName) - await msgFnc.updateMessageEmbed(eventMessage, event, - self.bot.eventchannel) + await msgFnc.updateMessageEmbeds(eventMessages, event, + self.bot.eventchannel) EventDatabase.toJson() # Update JSON file await ctx.send(f"Group {groupName} removed from {event}") @@ -817,7 +817,7 @@ async def archive(self, ctx: Context, event: ArgEvent): # Archive event and export EventDatabase.archiveEvent(event) try: - eventMessageList = await msgFnc.getEventMessage(event, self.bot) + eventMessageList = await msgFnc.getEventMessages(event, self.bot) except MessageNotFound: await ctx.send(f"Internal error: event {event} without " "a message found") @@ -825,8 +825,8 @@ async def archive(self, ctx: Context, event: ArgEvent): for eventMessage in eventMessageList: await eventMessage.delete() - # Create new message - await msgFnc.createEventMessage(event, self.bot.eventarchivechannel) + # Create messages + await msgFnc.createEventMessages(event, self.bot.eventarchivechannel) await ctx.send(f"Event {event} archived") @@ -834,7 +834,7 @@ async def _delete(self, event: Event, archived=False): # TODO: Move to a more appropriate location EventDatabase.removeEvent(event.id, archived=archived) try: - eventMessageList = await msgFnc.getEventMessage( + eventMessageList = await msgFnc.getEventMessages( event, self.bot, archived=archived) except MessageNotFound: # Message already deleted, nothing to be done @@ -963,7 +963,7 @@ async def _load(self, _: Context, event: Event, data: str, raise ValueError("Malformed data") if target: # Display the loaded event in the command channel - await msgFnc.createEventMessage(event, target, update_id=False) + await msgFnc.createEventMessages(event, target, update_id=False) await self._update_event(event) # @command() @@ -983,19 +983,15 @@ async def _update_event(self, event: Event, import_db=False, event = EventDatabase.getEventByMessage(event.messageIDList[0]) try: - message = await msgFnc.getEventMessage(event, self.bot) + messages = await msgFnc.getEventMessages(event, self.bot) except MessageNotFound: - message = [await msgFnc.createEventMessage(event, - self.bot.eventchannel)] + messages = await msgFnc.createEventMessages(event, + self.bot.eventchannel) - await msgFnc.updateMessageEmbed(eventMessageList=message, - updatedEvent=event, - channel=self.bot.eventchannel) + await msgFnc.updateMessageEmbeds(messages, event, + self.bot.eventchannel) + await msgFnc.updateReactions(event, bot=self.bot, reorder=reorder) - message = await msgFnc.getEventMessage(event, self.bot) - - await msgFnc.updateReactions(event=event, messageList=message, - reorder=reorder) if export: EventDatabase.toJson() diff --git a/converters.py b/converters.py index 1175f77..11a3952 100644 --- a/converters.py +++ b/converters.py @@ -1,6 +1,6 @@ import re from datetime import date, datetime, time -from typing import List, cast +from typing import List, Union, cast from discord import Message from discord.ext.commands.context import Context @@ -182,9 +182,9 @@ async def convert(cls, _: Context, arg: str) -> time: "Has to be HH:MM or HHMM") -class ArgMessage(List[Message]): +class ArgMessage(Message): @classmethod - async def convert(cls, ctx: Context, arg: str) -> List[Message]: + async def convert(cls, ctx: Context, arg: Union[str, int]) -> Message: try: event_id = int(arg) except ValueError as e: @@ -198,3 +198,11 @@ async def convert(cls, ctx: Context, arg: str) -> List[Message]: raise BadArgument(str(e)) from e return message + + +class ArgMessages(list): + @classmethod + async def convert(cls, ctx: Context, arg: str) -> List[Message]: + event = await ArgEvent.convert(ctx, arg) + return [await ArgMessage.convert(ctx, id) + for id in event.messageIDList] diff --git a/eventListener.py b/eventListener.py index f05e99b..705820f 100644 --- a/eventListener.py +++ b/eventListener.py @@ -43,7 +43,8 @@ async def on_ready(self): self.bot.processing = False @Cog.listener() - async def on_raw_reaction_add(self, payload: RawReactionActionEvent): + async def on_raw_reaction_add(self, payload: RawReactionActionEvent) \ + -> None: if payload.member == self.bot.user or \ payload.channel_id != self.bot.eventchannel.id: @@ -77,11 +78,10 @@ async def on_raw_reaction_add(self, payload: RawReactionActionEvent): f"({user.name}#{user.discriminator})\n" f"{message.jump_url}") return - else: - await self._handle_signup(event, payload.emoji, user, message) + await self._handle_signup(event, payload.emoji, user) async def _handle_signup(self, event: Event, partial_emoji: PartialEmoji, - user: User, message: Message): + user: User) -> None: # Get emoji string if partial_emoji.is_custom_emoji(): emoji: Union[PartialEmoji, str] = partial_emoji @@ -135,9 +135,9 @@ async def _handle_signup(self, event: Event, partial_emoji: PartialEmoji, old_role = f"{removed_role.display_name} -> " # Update discord embed - eventMessages = await msgFnc.getEventMessage(event, self.bot) - await msgFnc.updateMessageEmbed(eventMessages, event, - self.bot.eventchannel) + eventMessages = await msgFnc.getEventMessages(event, self.bot) + await msgFnc.updateMessageEmbeds(eventMessages, event, + self.bot.eventchannel) EventDatabase.toJson() delta_message = "" diff --git a/messageFunctions.py b/messageFunctions.py index 4ca0754..43010c7 100644 --- a/messageFunctions.py +++ b/messageFunctions.py @@ -9,22 +9,33 @@ from operationbot import OperationBot -async def getEventMessage(event: Event, bot: OperationBot, archived=False) \ - -> List[Message]: - """Get a message related to an event.""" +async def getEventMessage(event: Event, bot: OperationBot, archived=False, + message_id=None) \ + -> Message: + """Get the a single message related to an event. + + if message_id is not set, returns the first (main) message""" if archived: channel = bot.eventarchivechannel else: channel = bot.eventchannel - + if message_id is None: + message_id = event.messageIDList[0] try: - messageIDList = [] - for messageID in event.messageIDList: - messageIDList.append(await channel.fetch_message(messageID)) - return messageIDList + return await channel.fetch_message(message_id) except NotFound as e: raise MessageNotFound("No event message found with " - f"message ID {event.messageIDList}") from e + f"message ID {message_id}") from e + + +async def getEventMessages(event: Event, bot: OperationBot, archived=False) \ + -> List[Message]: + """Get all messages related to an event.""" + messages = [] + for messageID in event.messageIDList: + messages.append(await getEventMessage( + event, bot, archived=archived, message_id=messageID)) + return messages async def sortEventMessages(bot: OperationBot): @@ -36,49 +47,45 @@ async def sortEventMessages(bot: OperationBot): event: Event for event in EventDatabase.events.values(): try: - messageList = await getEventMessage(event, bot) + messageList = await getEventMessages(event, bot) except MessageNotFound as e: raise MessageNotFound(f"sortEventMessages: {e}") from e - await updateMessageEmbed(messageList, event, bot.eventchannel) + await updateMessageEmbeds(messageList, event, bot.eventchannel) await updateReactions(event, messageList=messageList) -# from EventDatabase -async def createEventMessage(event: Event, channel: TextChannel, - update_id=True) -> Message: - """Create a new event message.""" - # Create embeds and messages +async def createEventMessages(event: Event, channel: TextChannel, + update_id=True) -> List[Message]: + """Create new event messages.""" embeds = event.createEmbeds() + messages = [] + message_ids = [] + for embed in embeds: + message = await channel.send(embed=embed) + messages.append(message) + message_ids.append(message.id) if update_id: - event.messageIDList.clear() - for embed in embeds: - message = await channel.send(embed=embed) - event.messageIDList.append(message.id) - else: - for embed in embeds: - message = await channel.send(embed=embed) + event.messageIDList = list(message_ids) - return message + return messages -# was: EventDatabase.updateEvent -async def updateMessageEmbed(eventMessageList: List[Message], - updatedEvent: Event, channel: TextChannel) \ +async def updateMessageEmbeds(eventMessageList: List[Message], + event: Event, channel: TextChannel) \ -> None: """Update the embed and footer of a message.""" - newEventEmbedList = updatedEvent.createEmbeds() - if len(newEventEmbedList) == len(eventMessageList): - for i in range(len(newEventEmbedList)): - await eventMessageList[i].edit(embed=newEventEmbedList[i]) + embeds = event.createEmbeds() + if len(embeds) == len(eventMessageList): + for message, embed in zip(eventMessageList, embeds): + await message.edit(embed=embed) else: - for eventMessage in eventMessageList: - await eventMessage.delete() - await createEventMessage(updatedEvent, channel) + for message in eventMessageList: + await message.delete() + await createEventMessages(event, channel) -# from EventDatabase async def updateReactions(event: Event, messageList: List[Message] = None, - bot=None, reorder=False): + bot: OperationBot = None, reorder=False): """ Update reactions of an event message. @@ -87,11 +94,13 @@ async def updateReactions(event: Event, messageList: List[Message] = None, reinserted in the correct order. """ # TODO make efficient again (if necessary, works quiet well rn) + # TODO: Only add missing reactions / remove old ones instead of removing + # everything if not messageList: if bot is None: raise ValueError("Requires either the `messageList` or `bot` " "argument to be provided") - messageList = await getEventMessage(event, bot) + messageList = await getEventMessages(event, bot) reactions: List[Union[Emoji, str]] = event.getReactions() reactionsCurrent = [] @@ -149,40 +158,22 @@ async def syncMessages(events: Dict[int, Event], bot: OperationBot): sorted_events = sorted(list(events.values()), key=lambda event: event.date) for event in sorted_events: try: - messageList = await getEventMessage(event, bot) + message = await getEventMessage(event, bot) except MessageNotFound: print(f"Missing a message for event {event}, creating") - await createEventMessage(event, bot.eventchannel) + await createEventMessages(event, bot.eventchannel) else: - if messageEventId(messageList[0]) == event.id: - print(f"Found message {messageList[0].id} for event {event}") + if messageEventId(message) == event.id: + print(f"Found message {message.id} for event {event}") else: print(f"Found incorrect message for event {event}, deleting " f"and creating") # Technically multiple events might have the same saved # messageID but it's simpler to just recreate messages here if # the event ID doesn't match + messageList = await getEventMessages(event, bot) for message in messageList: await message.delete() - await createEventMessage(event, bot.eventchannel) + await createEventMessages(event, bot.eventchannel) await sortEventMessages(bot) - - -# async def importMessages(events: Dict[int, Event], bot): -# found = 0 -# async for message in bot.eventchannel.history(): -# if len(message.embeds) > 0: -# print("embeds", message.embeds) -# footer = message.embeds[0].footer.text -# print("footer", footer) -# event_id = int(footer.split(' ')[-1]) -# if event_id in events: -# events[event_id].messageID = message.id -# found += 1 -# else: -# print("Found a message {} with unknown event id {}" -# .format(message.id, event_id)) -# if found >= len(events): -# print("Found all messages") -# break From 105e88c201b65ced8d91824f5ccbce9b4ad2eb80 Mon Sep 17 00:00:00 2001 From: Sami Laine Date: Wed, 22 Sep 2021 00:07:26 +0200 Subject: [PATCH 11/16] Handle multiple messages when syncing and sorting --- event.py | 4 ++++ eventDatabase.py | 19 ++++++++++++++----- messageFunctions.py | 44 +++++++++++++++++++++++++++----------------- 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/event.py b/event.py index 0427362..9a7e613 100644 --- a/event.py +++ b/event.py @@ -245,6 +245,10 @@ def _create_embed(self, title: str) -> Embed: f"{mods}") return Embed(title=title, description=description, colour=self.color) + def createEmbed(self) -> Embed: + """Return the first embed for the event""" + return self.createEmbeds()[0] + def createEmbeds(self) -> List[Embed]: """Return a list of embeds for the event""" eventEmbed = self._create_embed(self.title) diff --git a/eventDatabase.py b/eventDatabase.py index 4058d2f..9216db1 100644 --- a/eventDatabase.py +++ b/eventDatabase.py @@ -1,7 +1,7 @@ import json import os from datetime import datetime -from typing import Any, Dict, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple from discord import Emoji @@ -127,13 +127,13 @@ def getArchivedEventByID(cls, eventID: int): @classmethod def sortEvents(cls): - sortedEvents = [] - messageIDLists = [] + sortedEvents: List[Event] = [] + messageIDLists: List[int] = [] # Store existing events for event in cls.events.values(): sortedEvents.append(event) - messageIDLists.append(event.messageIDList) + messageIDLists += event.messageIDList # Sort events based on date and time sortedEvents.sort(key=lambda event: event.date, reverse=True) @@ -143,7 +143,16 @@ def sortEvents(cls): cls.events = {} for event in sortedEvents: # event = sortedEvents[index] - event.messageIDList = messageIDLists.pop() + + event.messageIDList = [] + for _ in event.createEmbeds(): + # Each event requires one message per embed + try: + message_id = messageIDLists.pop() + except IndexError: + # No more messages left, rest will be created later + message_id = 0 + event.messageIDList.append(message_id) cls.events[event.id] = event @classmethod diff --git a/messageFunctions.py b/messageFunctions.py index 43010c7..66e6336 100644 --- a/messageFunctions.py +++ b/messageFunctions.py @@ -154,26 +154,36 @@ def messageEventId(message: Message) -> int: async def syncMessages(events: Dict[int, Event], bot: OperationBot): - # TODO: Handle multiple message IDs sorted_events = sorted(list(events.values()), key=lambda event: event.date) for event in sorted_events: - try: - message = await getEventMessage(event, bot) - except MessageNotFound: - print(f"Missing a message for event {event}, creating") - await createEventMessages(event, bot.eventchannel) - else: - if messageEventId(message) == event.id: - print(f"Found message {message.id} for event {event}") + missing_ids = [] + for message_id in event.messageIDList: + try: + message = await getEventMessage(event, bot, + message_id=message_id) + except MessageNotFound: + print(f"Missing a message for event {event}, creating") + await _send_message(event, bot.eventchannel) + missing_ids.append(message_id) else: - print(f"Found incorrect message for event {event}, deleting " - f"and creating") - # Technically multiple events might have the same saved - # messageID but it's simpler to just recreate messages here if - # the event ID doesn't match - messageList = await getEventMessages(event, bot) - for message in messageList: + if messageEventId(message) == event.id: + print(f"Found message {message.id} for event {event}") + else: + print(f"Found incorrect message for event {event}, " + f"deleting and creating") + # Technically multiple events might have the same saved + # messageID but it's simpler to just recreate messages here + # if the event ID doesn't match await message.delete() - await createEventMessages(event, bot.eventchannel) + await _send_message(event, bot.eventchannel) + missing_ids.append(message_id) + # Remove missing or deleted IDs + event.messageIDList = [x for x in event.messageIDList + if x not in missing_ids] await sortEventMessages(bot) + + +async def _send_message(event: Event, channel: TextChannel): + message = await channel.send(embed=event.createEmbed()) + event.messageIDList.append(message.id) From 14d9c86023995d6fc0a4c64476654304ea23c0d7 Mon Sep 17 00:00:00 2001 From: Sami Laine Date: Wed, 22 Sep 2021 00:08:05 +0200 Subject: [PATCH 12/16] Update reactions in a separate loop Embeds are quick to update so they're updated first. Then the slow process of adding reactions begins. --- messageFunctions.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/messageFunctions.py b/messageFunctions.py index 66e6336..bcb4a90 100644 --- a/messageFunctions.py +++ b/messageFunctions.py @@ -46,12 +46,11 @@ async def sortEventMessages(bot: OperationBot): event: Event for event in EventDatabase.events.values(): - try: - messageList = await getEventMessages(event, bot) - except MessageNotFound as e: - raise MessageNotFound(f"sortEventMessages: {e}") from e + messageList = await getEventMessages(event, bot) await updateMessageEmbeds(messageList, event, bot.eventchannel) - await updateReactions(event, messageList=messageList) + for event in EventDatabase.events.values(): + # Updating reactions takes a while, so we do it in a separate task + await updateReactions(event, bot=bot) async def createEventMessages(event: Event, channel: TextChannel, From 2d83bdddc3f57afc877ff8eec864c0a87066e928 Mon Sep 17 00:00:00 2001 From: Sami Laine Date: Fri, 24 Sep 2021 00:39:14 +0200 Subject: [PATCH 13/16] Only create missing messages instead of deleting all --- cogs/repl.py | 4 +-- commandListener.py | 2 +- messageFunctions.py | 82 ++++++++++++++++++++++++++++++++++----------- 3 files changed, 66 insertions(+), 22 deletions(-) diff --git a/cogs/repl.py b/cogs/repl.py index 6522b59..76a513b 100644 --- a/cogs/repl.py +++ b/cogs/repl.py @@ -5,7 +5,7 @@ import textwrap import traceback from contextlib import redirect_stdout -from typing import Set +from typing import Callable, Set, cast import discord from discord.ext import commands @@ -60,7 +60,7 @@ async def _eval(self, ctx: Context, *, body: str): except SyntaxError as e: return await ctx.send(self.get_syntax_error(e)) - func = env['func'] + func = cast(Callable, env['func']) try: with redirect_stdout(stdout): ret = await func() diff --git a/commandListener.py b/commandListener.py index 2458870..606e624 100644 --- a/commandListener.py +++ b/commandListener.py @@ -1023,7 +1023,7 @@ async def shutdown(self, ctx: Context): @Cog.listener() @staticmethod async def on_command_error(ctx: Context, error: Exception): - # pylint: disable=no-self-use, no-else-return + # pylint: disable=no-else-return if isinstance(error, MissingRequiredArgument): await ctx.send(f"Missing argument. See: `{CMD}help {ctx.command}`") return diff --git a/messageFunctions.py b/messageFunctions.py index bcb4a90..7e941a5 100644 --- a/messageFunctions.py +++ b/messageFunctions.py @@ -9,16 +9,21 @@ from operationbot import OperationBot -async def getEventMessage(event: Event, bot: OperationBot, archived=False, - message_id=None) \ +async def getEventMessage(event: Event, bot: OperationBot = None, + archived=False, message_id=None, + channel: TextChannel = None) \ -> Message: """Get the a single message related to an event. if message_id is not set, returns the first (main) message""" - if archived: - channel = bot.eventarchivechannel - else: - channel = bot.eventchannel + if channel is None: + if bot is None: + raise ValueError("Requires either the `channel` or `bot` " + "argument to be provided") + if archived: + channel = bot.eventarchivechannel + else: + channel = bot.eventchannel if message_id is None: message_id = event.messageIDList[0] try: @@ -28,13 +33,20 @@ async def getEventMessage(event: Event, bot: OperationBot, archived=False, f"message ID {message_id}") from e -async def getEventMessages(event: Event, bot: OperationBot, archived=False) \ +async def getEventMessages(event: Event, bot: OperationBot = None, + archived=False, channel: TextChannel = None) \ -> List[Message]: - """Get all messages related to an event.""" + """Get all messages related to an event. + + Raises MessageNotFound if the message is missing or if the event embeds + require more messages than can currently be found.""" messages = [] for messageID in event.messageIDList: messages.append(await getEventMessage( - event, bot, archived=archived, message_id=messageID)) + event, bot=bot, archived=archived, message_id=messageID, + channel=channel)) + if len(messages) < len(event.createEmbeds()): + raise MessageNotFound("Not all event messages found") return messages @@ -55,14 +67,47 @@ async def sortEventMessages(bot: OperationBot): async def createEventMessages(event: Event, channel: TextChannel, update_id=True) -> List[Message]: - """Create new event messages.""" - embeds = event.createEmbeds() + """Create new or missing event messages.""" + try: + all_messages = await getEventMessages(event, channel=channel) + except MessageNotFound: + pass + else: + # All messages were found without issues, nothing to do + return all_messages + messages = [] - message_ids = [] - for embed in embeds: - message = await channel.send(embed=embed) - messages.append(message) - message_ids.append(message.id) + not_found: List[int] = [] + for message_id in event.messageIDList: + try: + message = await getEventMessage(event, channel=channel) + messages.append(message) + except MessageNotFound: + not_found.append(message_id) + + # Get all message IDs that corresponded to an existing message + message_ids = [x for x in event.messageIDList if x not in not_found] + + embeds = event.createEmbeds() + difference = len(message_ids) - len(embeds) + if difference > 0: + # We have extra messages that need to be deleted. We could try to reuse + # the messages for other events instead of deleting, but keeping track + # of that would be too complicated. + for message_id in message_ids[:difference]: + message_ids.remove(message_id) + message = await channel.fetch_message(message_id) + await message.delete() + elif difference < 0: + # We have too few messages, create new ones + for i in range(abs(difference)): + # The embeds will be correct if there were no existing messages + # to begin with (when running the `show` command). Otherwise the + # will be updated afterwards anyway. + message = await channel.send(embed=embeds[i]) + messages.append(message) + message_ids.append(message.id) + if update_id: event.messageIDList = list(message_ids) @@ -78,9 +123,8 @@ async def updateMessageEmbeds(eventMessageList: List[Message], for message, embed in zip(eventMessageList, embeds): await message.edit(embed=embed) else: - for message in eventMessageList: - await message.delete() - await createEventMessages(event, channel) + messages = await createEventMessages(event, channel) + await updateMessageEmbeds(messages, event, channel) async def updateReactions(event: Event, messageList: List[Message] = None, From b6d0519462beeac13a291c2fe67a7736cd908b6e Mon Sep 17 00:00:00 2001 From: Sami Laine Date: Sat, 25 Sep 2021 17:15:34 +0200 Subject: [PATCH 14/16] Improve role removal commands Create removerole, removemainrole, deprecate removereaction --- commandListener.py | 45 ++++++++++++++++++++++++++------------------- event.py | 10 +++++----- role.py | 3 ++- roleGroup.py | 8 ++++---- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/commandListener.py b/commandListener.py index 606e624..a66a09c 100644 --- a/commandListener.py +++ b/commandListener.py @@ -482,8 +482,15 @@ async def addrole(self, ctx: Context, event: ArgEvent, *, await self._add_role(event, rolename) await ctx.send(f"Role {rolename} added to event {event}") + async def _remove_role(self, ctx: Context, event: ArgEvent, role: ArgRole, + check_additional=True): + role_name = role.name + event.remove_role(role, check_additional) + await self._update_event(event, reorder=False) + await ctx.send(f"Role {role_name} removed from {event}") + # Remove additional role from event command - @command(aliases=['rr']) + @command(aliases=['rr', 'removeadditionalrole', 'rar']) async def removerole(self, ctx: Context, event: ArgEvent, *, role: ArgRole): """ @@ -491,10 +498,17 @@ async def removerole(self, ctx: Context, event: ArgEvent, *, Example: removerole 1 Y1 (Bradley) Driver """ - role_name = role.name - event.removeAdditionalRole(role) - await self._update_event(event, reorder=False) - await ctx.send(f"Role {role_name} removed from {event}") + await self._remove_role(ctx, event, role, check_additional=True) + + @command(aliases=['rmr']) + async def removemainrole(self, ctx: Context, event: ArgEvent, + role: ArgRole): + """ + Remove a main role from the event. + + Example: removerole 1 B2 + """ + await self._remove_role(ctx, event, role, check_additional=False) @command(aliases=['rnr', 'rename']) async def renamerole(self, ctx: Context, event: ArgEvent, @@ -511,24 +525,17 @@ async def renamerole(self, ctx: Context, event: ArgEvent, await ctx.send(f"Role renamed. Old name: {old_name}, " f"new name: {role} @ {event}") - # TODO this doesnt work @command(aliases=['rra']) async def removereaction(self, ctx: Context, event: ArgEvent, - reaction: str): + role: ArgRole): """ - Removes a role and the corresponding reaction from the event and updates the message. - """ # NOQA - self._find_remove_reaction(reaction, event) - await self._update_event(event, reorder=False) - await ctx.send(f"Reaction {reaction} removed from {event}") + DEPRECATED. Removes a role and the corresponding reaction from the event and updates the message. - def _find_remove_reaction(self, reaction: str, event: Event): - for group in event.roleGroups.values(): - for role in group.roles: - if role.name == reaction: - group.roles.remove(role) - return - raise BadArgument("No reaction found") + Deprecated: use removemainrole and removerole instead. + """ # NOQA + await ctx.send("This command is deprecated. Use `removerole` (`rr`) " + "and `removemainrole` (`rmr`) instead.") + await self._remove_role(ctx, event, role, check_additional=False) @command(aliases=['rg']) async def removegroup(self, ctx: Context, eventMessages: ArgMessages, *, diff --git a/event.py b/event.py index 9a7e613..163809a 100644 --- a/event.py +++ b/event.py @@ -325,7 +325,7 @@ def _add_default_roles(self): # Only add role if the group exists if groupName in self.roleGroups: emoji = self.normalEmojis[name] - newRole = Role(name, emoji, False) + newRole = Role(name, emoji, groupName, show_name=False) self.roleGroups[groupName].addRole(newRole) # Add an additional role to the event @@ -345,7 +345,7 @@ def addAdditionalRole(self, name: str) -> str: emoji = cfg.ADDITIONAL_ROLE_EMOJIS[self.additional_role_count] # Create role - newRole = Role(name, emoji, show_name=True) + newRole = Role(name, emoji, "Additional", show_name=True) # Add role to additional roles self.roleGroups["Additional"].addRole(newRole) @@ -362,12 +362,12 @@ def renameAdditionalRole(self, role: Role, new_name: str): self._check_additional(role) role.name = new_name - def removeAdditionalRole(self, role: Union[str, Role]): + def remove_role(self, role: Role, check_additional=True): """Remove an additional role from the event.""" # Remove role from additional roles - if isinstance(role, Role): + if check_additional: self._check_additional(role) - self.roleGroups["Additional"].removeRole(role) + self.roleGroups[role.group_name].removeRole(role) def removeRoleGroup(self, groupName: str) -> bool: """ diff --git a/role.py b/role.py index 401b894..f4f119c 100644 --- a/role.py +++ b/role.py @@ -5,13 +5,14 @@ class Role: - def __init__(self, name: str, emoji: Union[str, Emoji], + def __init__(self, name: str, emoji: Union[str, Emoji], group_name: str, show_name: bool = False): self.name = name self.emoji = emoji self.show_name = show_name self.userID: Optional[int] = None self.userName = "" + self.group_name = group_name def __str__(self): # Add name after emote if it should display diff --git a/roleGroup.py b/roleGroup.py index d2bb157..eade309 100644 --- a/roleGroup.py +++ b/roleGroup.py @@ -36,7 +36,7 @@ def addRole(self, role: Role): self.roles.append(role) # Remove role from the group - def removeRole(self, role: Union[str, Role]): + def removeRole(self, role: Union[str, Role]) -> None: try: if isinstance(role, str): name = role @@ -45,8 +45,8 @@ def removeRole(self, role: Union[str, Role]): name = role.name self.roles.remove(role) except (KeyError, ValueError) as e: - raise RoleNotFound("Could not find an additional role to remove " - f"with the name {name}") from e + raise RoleNotFound(f"Could not find a role named {name} to remove " + f"from group {self.name}") from e def __str__(self) -> str: roleGroupString = "" @@ -90,7 +90,7 @@ def fromJson(self, data: dict, emojis: Tuple[Emoji, ...], if not manual_load: # Only create new roles if we're not loading data manually from # the command channel - role = Role(roleData["name"], roleEmoji, + role = Role(roleData["name"], roleEmoji, self.name, self.get_corrected_name(roleData)) self.roles.append(role) else: From c4e23ddccf7f51571ff480e2825c766461453881 Mon Sep 17 00:00:00 2001 From: Sami Laine Date: Sat, 25 Sep 2021 17:20:31 +0200 Subject: [PATCH 15/16] Remove extra messages when necessary For example, when the 21st reaction gets removed. --- commandListener.py | 5 +++-- messageFunctions.py | 20 +++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/commandListener.py b/commandListener.py index a66a09c..4fc9f10 100644 --- a/commandListener.py +++ b/commandListener.py @@ -982,7 +982,7 @@ async def _load(self, _: Context, event: Event, data: str, # await ctx.send("Event messages created") async def _update_event(self, event: Event, import_db=False, - reorder=True, export=True): + reorder=True, export=True, exact_number=True): # TODO: Move to a more appropriate location if import_db: await self.bot.import_database() @@ -990,7 +990,8 @@ async def _update_event(self, event: Event, import_db=False, event = EventDatabase.getEventByMessage(event.messageIDList[0]) try: - messages = await msgFnc.getEventMessages(event, self.bot) + messages = await msgFnc.getEventMessages(event, self.bot, + exact_number=exact_number) except MessageNotFound: messages = await msgFnc.createEventMessages(event, self.bot.eventchannel) diff --git a/messageFunctions.py b/messageFunctions.py index 7e941a5..7022611 100644 --- a/messageFunctions.py +++ b/messageFunctions.py @@ -34,12 +34,16 @@ async def getEventMessage(event: Event, bot: OperationBot = None, async def getEventMessages(event: Event, bot: OperationBot = None, - archived=False, channel: TextChannel = None) \ + archived=False, channel: TextChannel = None, + exact_number=True) \ -> List[Message]: """Get all messages related to an event. Raises MessageNotFound if the message is missing or if the event embeds - require more messages than can currently be found.""" + require more messages than can currently be found. + + If exact_number is set, also raises an error if too many messages can be + found.""" messages = [] for messageID in event.messageIDList: messages.append(await getEventMessage( @@ -47,6 +51,8 @@ async def getEventMessages(event: Event, bot: OperationBot = None, channel=channel)) if len(messages) < len(event.createEmbeds()): raise MessageNotFound("Not all event messages found") + if exact_number and len(messages) > len(event.createEmbeds()): + raise MessageNotFound("Too many event messages found") return messages @@ -67,7 +73,7 @@ async def sortEventMessages(bot: OperationBot): async def createEventMessages(event: Event, channel: TextChannel, update_id=True) -> List[Message]: - """Create new or missing event messages.""" + """Create new or missing event messages, delete extra messages.""" try: all_messages = await getEventMessages(event, channel=channel) except MessageNotFound: @@ -76,7 +82,7 @@ async def createEventMessages(event: Event, channel: TextChannel, # All messages were found without issues, nothing to do return all_messages - messages = [] + messages: List[Message] = [] not_found: List[int] = [] for message_id in event.messageIDList: try: @@ -94,9 +100,9 @@ async def createEventMessages(event: Event, channel: TextChannel, # We have extra messages that need to be deleted. We could try to reuse # the messages for other events instead of deleting, but keeping track # of that would be too complicated. - for message_id in message_ids[:difference]: - message_ids.remove(message_id) + for message_id in message_ids[difference:]: message = await channel.fetch_message(message_id) + messages.remove(message) await message.delete() elif difference < 0: # We have too few messages, create new ones @@ -106,9 +112,9 @@ async def createEventMessages(event: Event, channel: TextChannel, # will be updated afterwards anyway. message = await channel.send(embed=embeds[i]) messages.append(message) - message_ids.append(message.id) if update_id: + message_ids = [message.id for message in messages] event.messageIDList = list(message_ids) return messages From 5feee2fc8934bff861aeb78964e754537afadcdf Mon Sep 17 00:00:00 2001 From: Sami Laine Date: Tue, 28 Sep 2021 19:49:50 +0200 Subject: [PATCH 16/16] tmp: Reorder reactions --- commandListener.py | 39 +++++++------ errors.py | 4 ++ event.py | 75 +++++++++++++------------ eventDatabase.py | 5 +- messageFunctions.py | 131 ++++++++++++++++++++++++-------------------- role.py | 7 ++- roleGroup.py | 12 +++- 7 files changed, 156 insertions(+), 117 deletions(-) diff --git a/commandListener.py b/commandListener.py index 4fc9f10..57e0f27 100644 --- a/commandListener.py +++ b/commandListener.py @@ -10,15 +10,16 @@ from discord import Member from discord.channel import TextChannel from discord.emoji import Emoji -from discord.ext.commands import (BadArgument, Cog, Context, - MissingRequiredArgument, command) -from discord.ext.commands.errors import CommandError, CommandInvokeError +from discord.ext.commands import BadArgument, Cog, Context, command +from discord.ext.commands.errors import (CommandError, CommandInvokeError, + MissingRequiredArgument) import config as cfg import messageFunctions as msgFnc from converters import (ArgArchivedEvent, ArgDate, ArgDateTime, ArgEvent, ArgMessages, ArgRole, ArgTime, UnquotedStr) -from errors import MessageNotFound, RoleError, UnexpectedRole +from errors import (ExtraMessagesFound, MessageNotFound, RoleError, + UnexpectedRole) from event import Event from eventDatabase import EventDatabase from operationbot import OperationBot @@ -153,7 +154,7 @@ async def _create_event(self, ctx: Context, _date: datetime, # Create event and sort events, export event: Event = EventDatabase.createEvent(_date, sideop=sideop, platoon_size=platoon_size) - await msgFnc.createEventMessages(event, self.bot.eventchannel) + await msgFnc.get_or_create_messages(event, self.bot.eventchannel) if not batch: await msgFnc.sortEventMessages(self.bot) EventDatabase.toJson() # Update JSON file @@ -165,8 +166,8 @@ async def _create_event(self, ctx: Context, _date: datetime, async def show(self, ctx: Context, event: ArgEvent): message = await msgFnc.getEventMessage(event, self.bot) await ctx.send(message.jump_url) - await msgFnc.createEventMessages(event, cast(TextChannel, ctx.channel), - update_id=False) + await msgFnc.get_or_create_messages(event, cast(TextChannel, + ctx.channel), update_id=False) # Create event command @command(aliases=['c']) @@ -448,7 +449,7 @@ async def _add_role(self, event: Event, rolename: str, batch=False): await self._update_event(event, reorder=False) raise e if not batch: - await self._update_event(event, reorder=False, export=(not batch)) + await self._update_event(event, reorder=False) @command(aliases=['ar']) async def addrole(self, ctx: Context, event: ArgEvent, *, @@ -833,7 +834,8 @@ async def archive(self, ctx: Context, event: ArgEvent): await eventMessage.delete() # Create messages - await msgFnc.createEventMessages(event, self.bot.eventarchivechannel) + await msgFnc.get_or_create_messages( + event, self.bot.eventarchivechannel) await ctx.send(f"Event {event} archived") @@ -970,7 +972,7 @@ async def _load(self, _: Context, event: Event, data: str, raise ValueError("Malformed data") if target: # Display the loaded event in the command channel - await msgFnc.createEventMessages(event, target, update_id=False) + await msgFnc.get_or_create_messages(event, target, update_id=False) await self._update_event(event) # @command() @@ -992,13 +994,16 @@ async def _update_event(self, event: Event, import_db=False, try: messages = await msgFnc.getEventMessages(event, self.bot, exact_number=exact_number) - except MessageNotFound: - messages = await msgFnc.createEventMessages(event, - self.bot.eventchannel) - - await msgFnc.updateMessageEmbeds(messages, event, - self.bot.eventchannel) - await msgFnc.updateReactions(event, bot=self.bot, reorder=reorder) + except (MessageNotFound, ExtraMessagesFound) as e: + messages = await msgFnc.get_or_create_messages( + event, self.bot.eventchannel) + if isinstance(e, MessageNotFound): + # New messages were created, we need to reorder the messages + await msgFnc.sortEventMessages(self.bot) + else: + await msgFnc.updateMessageEmbeds(messages, event, + self.bot.eventchannel) + await msgFnc.updateReactions(event, bot=self.bot, reorder=reorder) if export: EventDatabase.toJson() diff --git a/errors.py b/errors.py index 46f7530..9addd19 100644 --- a/errors.py +++ b/errors.py @@ -10,6 +10,10 @@ class MessageNotFound(Exception): pass +class ExtraMessagesFound(Exception): + pass + + class UnexpectedRole(Exception): pass diff --git a/event.py b/event.py index 163809a..2c75031 100644 --- a/event.py +++ b/event.py @@ -7,7 +7,7 @@ import config as cfg from additional_role_group import AdditionalRoleGroup from errors import RoleError, RoleGroupNotFound, RoleNotFound, RoleTaken -from role import Role +from role import ReactionEmoji, Role from roleGroup import RoleGroup from secret import PLATOON_SIZE @@ -243,15 +243,19 @@ def _create_embed(self, title: str) -> Embed: f"{server_port}" f"{event_description}" f"{mods}") - return Embed(title=title, description=description, colour=self.color) + embed = Embed(title=title, description=description, colour=self.color) + embed.set_footer(text="Event ID: " + str(self.id)) + return embed - def createEmbed(self) -> Embed: + def create_dummy_embed(self) -> Embed: """Return the first embed for the event""" - return self.createEmbeds()[0] + return self._create_embed(self.title) - def createEmbeds(self) -> List[Embed]: - """Return a list of embeds for the event""" + def createEmbeds(self) -> Tuple[List[Embed], List[List[ReactionEmoji]]]: + """Return a list of embeds and their corresponding reactions for the + event""" eventEmbed = self._create_embed(self.title) + reactions = [] # Add field to embed for every rolegroup for group in self.roleGroups.values(): @@ -259,31 +263,34 @@ def createEmbeds(self) -> List[Embed]: # The Additional group is handled separately eventEmbed.add_field(name=group.name, value=str(group), inline=group.isInline) + reactions += group.get_reactions() elif group.name == "Dummy": eventEmbed.add_field(name="\N{ZERO WIDTH SPACE}", value="\N{ZERO WIDTH SPACE}", inline=group.isInline) - eventEmbed.set_footer(text="Event ID: " + str(self.id)) if len(self.roleGroups["Additional"]) == 0: # There are no additional roles, the embed is ready - return [eventEmbed] + return ([eventEmbed], [reactions]) # Handle additional roles if len(self.getReactions()) <= REACTIONS_PER_MESSAGE: # All roles fit in a single message - group = self.roleGroups["Additional"] - eventEmbed.add_field(name=group.name, value=str(group), - inline=group.isInline) - return [eventEmbed] + additional = self.roleGroups["Additional"] + eventEmbed.add_field(name=additional.name, value=str(additional), + inline=additional.isInline) + return ([eventEmbed], [reactions + additional.get_reactions()]) - return [eventEmbed] + self.createAdditionalEmbeds() + embeds, additional_reactions = self.createAdditionalEmbeds() + return ([eventEmbed] + embeds, [reactions] + additional_reactions) - def createAdditionalEmbeds(self) -> List[Embed]: + def createAdditionalEmbeds(self) -> Tuple[List[Embed], + List[List[ReactionEmoji]]]: """Creates additional embeds. The number of embeds depend on the Additional roles group""" - embeds = [] + embeds: List[Embed] = [] + all_reactions: List[List[ReactionEmoji]] = [] group = self.roleGroups["Additional"] # Substract 1 because REACTIONS_PER_MESSAGE roles still fit in a single @@ -292,10 +299,12 @@ def createAdditionalEmbeds(self) -> List[Embed]: for embed_number in range(embed_count): role_list = "" + reactions = [] first = embed_number * REACTIONS_PER_MESSAGE last = (embed_number + 1) * REACTIONS_PER_MESSAGE for role in group.roles[first:last]: role_list += f'{str(role)}\n' + reactions.append(role.emoji) if role_list == "": # Didn't add any roles -> skipping this embed. Discord doesn't # like embeds with empty fields. This should only happen if @@ -310,8 +319,9 @@ def createAdditionalEmbeds(self) -> List[Embed]: value=role_list, inline=False) eventEmbed.set_footer(text="Event ID: " + str(self.id)) embeds.append(eventEmbed) + all_reactions.append(reactions) - return embeds + return (embeds, all_reactions) # Add default role groups def _add_default_role_groups(self): @@ -332,15 +342,16 @@ def _add_default_roles(self): def addAdditionalRole(self, name: str) -> str: # check if this role already exists - for roleGroup in self.roleGroups.values(): - role: Role - for role in roleGroup.roles: - if role.name == name: - raise RoleError(f"Role with name {name} already exists, " - "not adding new role") + try: + self.findRoleWithName(name) + except RoleNotFound: + pass + else: + raise RoleError(f"Role with name {name} already exists, " + "not adding new role") # Find next emoji for additional role - if self.countReactions() >= MAX_REACTIONS: + if self.reaction_count >= MAX_REACTIONS: raise RoleError(f"Too many roles, not adding role {name}") emoji = cfg.ADDITIONAL_ROLE_EMOJIS[self.additional_role_count] @@ -411,19 +422,12 @@ def _getNormalEmojis(self, guildEmojis) -> Dict[str, Emoji]: return normalEmojis - def getReactions(self) -> List[Union[str, Emoji]]: + def getReactions(self) -> List[ReactionEmoji]: """Return reactions of all roles and extra reactions""" reactions = [] - for roleGroup in self.roleGroups.values(): - role: Role - for role in roleGroup.roles: - emoji = role.emoji - # Skip the ZEUS reaction. Zeuses can only be signed up using - # the signup command - if not (isinstance(emoji, Emoji) - and emoji.name == cfg.EMOJI_ZEUS): - reactions.append(role.emoji) + for role_group in self.roleGroups.values(): + reactions += role_group.get_reactions() if self.sideop: if cfg.ATTENDANCE_EMOJI: @@ -431,11 +435,12 @@ def getReactions(self) -> List[Union[str, Emoji]]: return reactions - def countReactions(self) -> int: + @property + def reaction_count(self) -> int: """Count how many reactions a message should have.""" return len(self.getReactions()) - def getReactionsOfGroup(self, groupName: str) -> List[Union[str, Emoji]]: + def getReactionsOfGroup(self, groupName: str) -> List[ReactionEmoji]: """Find reactions of a given role group.""" reactions = [] diff --git a/eventDatabase.py b/eventDatabase.py index 9216db1..5c1fd65 100644 --- a/eventDatabase.py +++ b/eventDatabase.py @@ -145,13 +145,14 @@ def sortEvents(cls): # event = sortedEvents[index] event.messageIDList = [] - for _ in event.createEmbeds(): + embeds, _ = event.createEmbeds() + for _ in embeds: # Each event requires one message per embed try: message_id = messageIDLists.pop() except IndexError: # No more messages left, rest will be created later - message_id = 0 + continue event.messageIDList.append(message_id) cls.events[event.id] = event diff --git a/messageFunctions.py b/messageFunctions.py index 7022611..7f1f495 100644 --- a/messageFunctions.py +++ b/messageFunctions.py @@ -1,12 +1,17 @@ from typing import Dict, List, Union, cast -from discord import Emoji, Message, NotFound, TextChannel +from discord import Message, NotFound, TextChannel from discord.embeds import Embed +from discord.emoji import Emoji +from discord.errors import Forbidden +from discord.partial_emoji import PartialEmoji +from discord.reaction import Reaction -from errors import MessageNotFound +from errors import ExtraMessagesFound, MessageNotFound, RoleError from event import Event from eventDatabase import EventDatabase from operationbot import OperationBot +from role import ReactionEmoji async def getEventMessage(event: Event, bot: OperationBot = None, @@ -49,10 +54,11 @@ async def getEventMessages(event: Event, bot: OperationBot = None, messages.append(await getEventMessage( event, bot=bot, archived=archived, message_id=messageID, channel=channel)) - if len(messages) < len(event.createEmbeds()): + embeds, _ = event.createEmbeds() + if len(messages) < len(embeds): raise MessageNotFound("Not all event messages found") - if exact_number and len(messages) > len(event.createEmbeds()): - raise MessageNotFound("Too many event messages found") + if exact_number and len(messages) > len(embeds): + raise ExtraMessagesFound("Too many event messages found") return messages @@ -62,21 +68,20 @@ async def sortEventMessages(bot: OperationBot): Raises MessageNotFound if messages are missing.""" EventDatabase.sortEvents() - event: Event for event in EventDatabase.events.values(): - messageList = await getEventMessages(event, bot) + messageList = await get_or_create_messages(event, bot.eventchannel) await updateMessageEmbeds(messageList, event, bot.eventchannel) for event in EventDatabase.events.values(): # Updating reactions takes a while, so we do it in a separate task await updateReactions(event, bot=bot) -async def createEventMessages(event: Event, channel: TextChannel, - update_id=True) -> List[Message]: +async def get_or_create_messages(event: Event, channel: TextChannel, + update_id=True) -> List[Message]: """Create new or missing event messages, delete extra messages.""" try: all_messages = await getEventMessages(event, channel=channel) - except MessageNotFound: + except (MessageNotFound, ExtraMessagesFound): pass else: # All messages were found without issues, nothing to do @@ -86,7 +91,8 @@ async def createEventMessages(event: Event, channel: TextChannel, not_found: List[int] = [] for message_id in event.messageIDList: try: - message = await getEventMessage(event, channel=channel) + message = await getEventMessage(event, message_id=message_id, + channel=channel) messages.append(message) except MessageNotFound: not_found.append(message_id) @@ -94,15 +100,17 @@ async def createEventMessages(event: Event, channel: TextChannel, # Get all message IDs that corresponded to an existing message message_ids = [x for x in event.messageIDList if x not in not_found] - embeds = event.createEmbeds() - difference = len(message_ids) - len(embeds) + embeds, _ = event.createEmbeds() + existing_messages = len(message_ids) + difference = existing_messages - len(embeds) if difference > 0: # We have extra messages that need to be deleted. We could try to reuse # the messages for other events instead of deleting, but keeping track # of that would be too complicated. for message_id in message_ids[difference:]: message = await channel.fetch_message(message_id) - messages.remove(message) + if message in messages: + messages.remove(message) await message.delete() elif difference < 0: # We have too few messages, create new ones @@ -110,7 +118,7 @@ async def createEventMessages(event: Event, channel: TextChannel, # The embeds will be correct if there were no existing messages # to begin with (when running the `show` command). Otherwise the # will be updated afterwards anyway. - message = await channel.send(embed=embeds[i]) + message = await channel.send(embed=embeds[existing_messages + i]) messages.append(message) if update_id: @@ -124,12 +132,12 @@ async def updateMessageEmbeds(eventMessageList: List[Message], event: Event, channel: TextChannel) \ -> None: """Update the embed and footer of a message.""" - embeds = event.createEmbeds() + embeds, _ = event.createEmbeds() if len(embeds) == len(eventMessageList): for message, embed in zip(eventMessageList, embeds): await message.edit(embed=embed) else: - messages = await createEventMessages(event, channel) + messages = await get_or_create_messages(event, channel) await updateMessageEmbeds(messages, event, channel) @@ -151,47 +159,47 @@ async def updateReactions(event: Event, messageList: List[Message] = None, "argument to be provided") messageList = await getEventMessages(event, bot) - reactions: List[Union[Emoji, str]] = event.getReactions() - reactionsCurrent = [] - reactionEmojisCurrent = {} - for message in messageList: - reactionsCurrent.extend(message.reactions) - - # Find current reaction emojis - for reaction in reactionsCurrent: - reactionEmojisCurrent[reaction.emoji] = reaction - - if list(reactionEmojisCurrent) == reactions: - # Emojis are already correct, no need for further edits - return - - for message in messageList: - await message.clear_reactions() - - if len(reactions) <= event.getReactionsPerMessage(): - # Add Emojis for the first embed with additional Roles - for emoji in reactions: - await messageList[0].add_reaction(emoji) - else: - # Add Emojis for the first embed without additional Roles - for i in range((len(reactions) - event.additional_role_count)): - emoji = reactions.pop(0) - await messageList[0].add_reaction(emoji) - - # Add Emojis to following embeds - counter = 0 - messageNumber = 1 - for i in range(len(reactions)): - emoji = reactions.pop(0) - await messageList[messageNumber].add_reaction(emoji) - - counter += 1 - if counter == event.getReactionsPerMessage(): - messageNumber += 1 - counter = 0 + # TODO: reactions is a list of lists: outer list per embed, inner list + # reactions of that embed. Should loop over messages and lists of reactions + _, all_reactions = event.createEmbeds() + for message, reactions in zip(messageList, all_reactions): + current_reactions: Dict[Union[Emoji, PartialEmoji, str], Reaction] = {} + new_reactions: List[ReactionEmoji] = [] + # Find current reaction emojis + for reaction in message.reactions: + current_reactions[reaction.emoji] = reaction + + if list(current_reactions) == reactions: + # Emojis are already correct, moving to next message + continue + + if reorder: + # Re-adding all reactions in order to put them in the correct order + await message.clear_reactions() + new_reactions = reactions + else: + # Find emojis to remove + for emoji, reaction in current_reactions.items(): + if emoji not in reactions: + await message.clear_reaction(reaction) + + # Find emojis to add + for new_reaction in reactions: + if new_reaction not in current_reactions.keys(): + new_reactions.append(new_reaction) + + # Add missing emojis + for new_reaction in new_reactions: + try: + await message.add_reaction(new_reaction) + except Forbidden as e: + if e.code == 30010: + raise RoleError( + f"Too many reactions, not adding role {new_reaction}. " + "This should not happen.") from e -def messageEventId(message: Message) -> int: +def _messageEventId(message: Message) -> int: if len(message.embeds) == 0: raise ValueError("Message has no embeds") footer = message.embeds[0].footer @@ -206,16 +214,19 @@ async def syncMessages(events: Dict[int, Event], bot: OperationBot): sorted_events = sorted(list(events.values()), key=lambda event: event.date) for event in sorted_events: missing_ids = [] + new_ids = [] for message_id in event.messageIDList: try: message = await getEventMessage(event, bot, message_id=message_id) except MessageNotFound: print(f"Missing a message for event {event}, creating") - await _send_message(event, bot.eventchannel) + message = await bot.eventchannel.send( + embed=event.create_dummy_embed()) + new_ids.append(message.id) missing_ids.append(message_id) else: - if messageEventId(message) == event.id: + if _messageEventId(message) == event.id: print(f"Found message {message.id} for event {event}") else: print(f"Found incorrect message for event {event}, " @@ -228,11 +239,11 @@ async def syncMessages(events: Dict[int, Event], bot: OperationBot): missing_ids.append(message_id) # Remove missing or deleted IDs event.messageIDList = [x for x in event.messageIDList - if x not in missing_ids] + if x not in missing_ids] + new_ids await sortEventMessages(bot) async def _send_message(event: Event, channel: TextChannel): - message = await channel.send(embed=event.createEmbed()) + message = await channel.send(embed=event.create_dummy_embed()) event.messageIDList.append(message.id) diff --git a/role.py b/role.py index f4f119c..8011856 100644 --- a/role.py +++ b/role.py @@ -3,9 +3,12 @@ from discord import Emoji +ReactionEmoji = Union[Emoji, str] + + class Role: - def __init__(self, name: str, emoji: Union[str, Emoji], group_name: str, + def __init__(self, name: str, emoji: ReactionEmoji, group_name: str, show_name: bool = False): self.name = name self.emoji = emoji @@ -49,7 +52,7 @@ def fromJson(self, data: dict, manual_load=False): self.userName = data["userName"] @property - def display_name(self) -> Union[str, Emoji]: + def display_name(self) -> Union[str, ReactionEmoji]: if self.show_name: return f"{self.emoji} {self.name}" return self.emoji diff --git a/roleGroup.py b/roleGroup.py index eade309..cd76884 100644 --- a/roleGroup.py +++ b/roleGroup.py @@ -4,7 +4,7 @@ import config as cfg from errors import RoleNotFound, UnexpectedRole -from role import Role +from role import ReactionEmoji, Role class RoleGroup: @@ -48,6 +48,16 @@ def removeRole(self, role: Union[str, Role]) -> None: raise RoleNotFound(f"Could not find a role named {name} to remove " f"from group {self.name}") from e + def get_reactions(self) -> List[ReactionEmoji]: + reactions = [] + for role in self.roles: + emoji = role.emoji + # Skip the ZEUS reaction. Zeuses can only be signed up using + # the signup command + if not (isinstance(emoji, Emoji) and emoji.name == cfg.EMOJI_ZEUS): + reactions.append(role.emoji) + return reactions + def __str__(self) -> str: roleGroupString = ""