From 3686f60305e773f91c61e7c49a67d2d1f2d5c28a Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sat, 26 Nov 2022 18:58:11 +0100 Subject: [PATCH 01/30] add the roles channel to the config --- bot/constants.py | 2 ++ config-default.yml | 3 +++ 2 files changed, 5 insertions(+) diff --git a/bot/constants.py b/bot/constants.py index 24862059e1..90527d66a5 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -465,6 +465,8 @@ class Channels(metaclass=YAMLGetter): big_brother_logs: int + roles: int + class Webhooks(metaclass=YAMLGetter): section = "guild" diff --git a/config-default.yml b/config-default.yml index c9d043ff7c..9a3f350085 100644 --- a/config-default.yml +++ b/config-default.yml @@ -235,6 +235,9 @@ guild: # Watch big_brother_logs: &BB_LOGS 468507907357409333 + # Roles + roles: 851270062434156586 + moderation_categories: - *MODS_CATEGORY - *MODMAIL From 579514077f9b3307dc4717af94d1f8cda595c74d Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sat, 26 Nov 2022 19:02:06 +0100 Subject: [PATCH 02/30] add the AllSelfAssignableRolesView and its corresponding ClaimAllSelfAssignableRolesButton button Note that these are still dummy views & have no behavior --- bot/exts/info/subscribe.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 36304539fb..4991f4a961 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -59,6 +59,27 @@ def get_readable_available_months(self) -> str: log = get_logger(__name__) +class AllSelfAssignableRolesView(discord.ui.View): + """A view that'll hold one button allowing interactors to get all available self-assignable roles.""" + + def __init__(self): + super(AllSelfAssignableRolesView, self).__init__(timeout=None) + + +class ClaimAllSelfAssignableRolesButton(discord.ui.Button): + """A button that adds all self assignable roles to the interactor.""" + + CUSTOM_ID = "gotta-claim-them-all" + + def __init__(self): + super().__init__( + style=discord.ButtonStyle.success, + label="Assign me a", + custom_id=self.CUSTOM_ID, + row=1 + ) + + class RoleButtonView(discord.ui.View): """A list of SingleRoleButtons to show to the member.""" @@ -150,7 +171,6 @@ def __init__(self, bot: Bot): async def cog_load(self) -> None: """Initialise the cog by resolving the role IDs in ASSIGNABLE_ROLES to role names.""" await self.bot.wait_until_guild_available() - self.guild = self.bot.get_guild(constants.Guild.id) for role in ASSIGNABLE_ROLES: From 6ab9f9cf2a5889f3f6999117458e75140a40533b Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sat, 26 Nov 2022 19:17:26 +0100 Subject: [PATCH 03/30] add the logic for attaching the persistent view This comes with another dependency to try to locate the message that needs to hold the view. If that message is not found, a new one will be created & sent. --- bot/exts/info/subscribe.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 4991f4a961..c8886c4f73 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -13,6 +13,7 @@ from bot.bot import Bot from bot.decorators import redirect_output from bot.log import get_logger +from bot.utils.channel import get_or_fetch_channel @dataclass(frozen=True) @@ -74,7 +75,7 @@ class ClaimAllSelfAssignableRolesButton(discord.ui.Button): def __init__(self): super().__init__( style=discord.ButtonStyle.success, - label="Assign me a", + label="Claim all available roles", custom_id=self.CUSTOM_ID, row=1 ) @@ -163,6 +164,8 @@ async def update_view(self, interaction: Interaction) -> None: class Subscribe(commands.Cog): """Cog to allow user to self-assign & remove the roles present in ASSIGNABLE_ROLES.""" + SELF_ASSIGNABLE_ROLES_MESSAGE = "Click on this button to earn all the currently available server roles" + def __init__(self, bot: Bot): self.bot = bot self.assignable_roles: list[AssignableRole] = [] @@ -190,6 +193,9 @@ async def cog_load(self) -> None: self.assignable_roles.sort(key=operator.attrgetter("name")) self.assignable_roles.sort(key=operator.methodcaller("is_currently_available"), reverse=True) + initial_self_assignable_roles_message = await self.__search_for_self_assignable_roles_message() + self.__attach_view_to_initial_self_assignable_roles_message(initial_self_assignable_roles_message) + @commands.cooldown(1, 10, commands.BucketType.member) @commands.command(name="subscribe", aliases=("unsubscribe",)) @redirect_output( @@ -210,6 +216,35 @@ async def subscribe_command(self, ctx: commands.Context, *_) -> None: # We don' delete_after=DELETE_MESSAGE_AFTER, ) + async def __search_for_self_assignable_roles_message(self) -> discord.Message: + """ + Searches for the message that holds the self assignable roles view. + + If the initial message isn't found, a new one will be created. + This message will always be needed to attach the persistent view to it + """ + roles_channel = await get_or_fetch_channel(constants.Channels.roles) + + async for message in roles_channel.history(limit=30): + if message.content == self.SELF_ASSIGNABLE_ROLES_MESSAGE: + log.debug(f"Found self assignable roles view message: {message.id}") + return message + + log.debug("Self assignable roles view message hasn't been found, creating a new one.") + view = AllSelfAssignableRolesView() + view.add_item(ClaimAllSelfAssignableRolesButton()) + return await roles_channel.send(self.SELF_ASSIGNABLE_ROLES_MESSAGE, view=view) + + def __attach_view_to_initial_self_assignable_roles_message(self, message: discord.Message) -> None: + """ + Attaches the persistent self assignable roles view. + + The message is searched for/created upon loading the Cog. + """ + view = AllSelfAssignableRolesView() + view.add_item(ClaimAllSelfAssignableRolesButton()) + self.bot.add_view(view, message_id=message.id) + async def setup(bot: Bot) -> None: """Load the Subscribe cog.""" From b25ad801bd99bd7055fcb5a49e13b5ff339a515b Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sat, 26 Nov 2022 19:41:39 +0100 Subject: [PATCH 04/30] add assignable_roles as a property to the ClaimAllSelfAssignableRoles button --- bot/exts/info/subscribe.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index c8886c4f73..4f4f5df587 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -72,13 +72,14 @@ class ClaimAllSelfAssignableRolesButton(discord.ui.Button): CUSTOM_ID = "gotta-claim-them-all" - def __init__(self): + def __init__(self, assignable_roles: list[AssignableRole]): super().__init__( style=discord.ButtonStyle.success, label="Claim all available roles", custom_id=self.CUSTOM_ID, row=1 ) + self.assignable_roles = assignable_roles class RoleButtonView(discord.ui.View): @@ -232,7 +233,7 @@ async def __search_for_self_assignable_roles_message(self) -> discord.Message: log.debug("Self assignable roles view message hasn't been found, creating a new one.") view = AllSelfAssignableRolesView() - view.add_item(ClaimAllSelfAssignableRolesButton()) + view.add_item(ClaimAllSelfAssignableRolesButton(self.assignable_roles)) return await roles_channel.send(self.SELF_ASSIGNABLE_ROLES_MESSAGE, view=view) def __attach_view_to_initial_self_assignable_roles_message(self, message: discord.Message) -> None: @@ -242,7 +243,7 @@ def __attach_view_to_initial_self_assignable_roles_message(self, message: discor The message is searched for/created upon loading the Cog. """ view = AllSelfAssignableRolesView() - view.add_item(ClaimAllSelfAssignableRolesButton()) + view.add_item(ClaimAllSelfAssignableRolesButton(self.assignable_roles)) self.bot.add_view(view, message_id=message.id) From 1d604034caf476a900882502b15e47a222a8dd0c Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sat, 26 Nov 2022 19:44:16 +0100 Subject: [PATCH 05/30] add implementation of the button's callback that'll handle assigning the appropriate roles --- bot/exts/info/subscribe.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 4f4f5df587..1aec0e3a05 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -81,6 +81,40 @@ def __init__(self, assignable_roles: list[AssignableRole]): ) self.assignable_roles = assignable_roles + async def callback(self, interaction: Interaction) -> t.Any: + """Assigns all missing available roles to the interactor.""" + await interaction.response.defer() + + if isinstance(interaction.user, discord.User): + log.trace("User %s is not a member", interaction.user) + await interaction.message.delete() + self.view.stop() + return + + author_roles = [role.id for role in interaction.user.roles] + assigned_roles = [] + + for role in self.assignable_roles: + if role.role_id in author_roles: + continue + if not role.is_currently_available(): + continue + + log.debug(f"Assigning role {role.name} to {interaction.user.display_name}") + await members.handle_role_change( + interaction.user, + interaction.user.add_roles, + discord.Object(role.role_id), + ) + + assigned_roles.append(role.name) + + if assigned_roles: + await interaction.followup.send( + f"The following roles have been assigned to you: {', '.join(assigned_roles)}", + ephemeral=True, + ) + class RoleButtonView(discord.ui.View): """A list of SingleRoleButtons to show to the member.""" From af588147589c17718dbcaa9c407c2368418f7f4b Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 27 Nov 2022 15:52:51 +0100 Subject: [PATCH 06/30] rely on original_message to delete view once it times out --- bot/exts/info/subscribe.py | 128 ++++++++++++++++++------------------- 1 file changed, 62 insertions(+), 66 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 1aec0e3a05..333f33b505 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -55,72 +55,18 @@ def get_readable_available_months(self) -> str: ) ITEMS_PER_ROW = 3 -DELETE_MESSAGE_AFTER = 300 # Seconds +DELETE_MESSAGE_AFTER = 20 # Seconds log = get_logger(__name__) -class AllSelfAssignableRolesView(discord.ui.View): - """A view that'll hold one button allowing interactors to get all available self-assignable roles.""" - - def __init__(self): - super(AllSelfAssignableRolesView, self).__init__(timeout=None) - - -class ClaimAllSelfAssignableRolesButton(discord.ui.Button): - """A button that adds all self assignable roles to the interactor.""" - - CUSTOM_ID = "gotta-claim-them-all" - - def __init__(self, assignable_roles: list[AssignableRole]): - super().__init__( - style=discord.ButtonStyle.success, - label="Claim all available roles", - custom_id=self.CUSTOM_ID, - row=1 - ) - self.assignable_roles = assignable_roles - - async def callback(self, interaction: Interaction) -> t.Any: - """Assigns all missing available roles to the interactor.""" - await interaction.response.defer() - - if isinstance(interaction.user, discord.User): - log.trace("User %s is not a member", interaction.user) - await interaction.message.delete() - self.view.stop() - return - - author_roles = [role.id for role in interaction.user.roles] - assigned_roles = [] - - for role in self.assignable_roles: - if role.role_id in author_roles: - continue - if not role.is_currently_available(): - continue - - log.debug(f"Assigning role {role.name} to {interaction.user.display_name}") - await members.handle_role_change( - interaction.user, - interaction.user.add_roles, - discord.Object(role.role_id), - ) - - assigned_roles.append(role.name) - - if assigned_roles: - await interaction.followup.send( - f"The following roles have been assigned to you: {', '.join(assigned_roles)}", - ephemeral=True, - ) - - class RoleButtonView(discord.ui.View): """A list of SingleRoleButtons to show to the member.""" def __init__(self, member: discord.Member): - super().__init__() + super().__init__(timeout=DELETE_MESSAGE_AFTER) + # We can't obtain the reference to the message before the view is sent + self.original_message = None self.interaction_owner = member async def interaction_check(self, interaction: Interaction) -> bool: @@ -133,6 +79,10 @@ async def interaction_check(self, interaction: Interaction) -> bool: return False return True + async def on_timeout(self) -> None: + """Delete the original message that the view was sent along with.""" + await self.original_message.delete() + class SingleRoleButton(discord.ui.Button): """A button that adds or removes a role from the member depending on it's current state.""" @@ -196,6 +146,38 @@ async def update_view(self, interaction: Interaction) -> None: self.view.stop() +class AllSelfAssignableRolesView(discord.ui.View): + """A view that'll hold one button allowing interactors to get all available self-assignable roles.""" + + def __init__(self): + super(AllSelfAssignableRolesView, self).__init__(timeout=None) + + +class ClaimAllSelfAssignableRolesButton(discord.ui.Button): + """A button that adds all self assignable roles to the interactor.""" + + CUSTOM_ID = "gotta-claim-them-all" + + def __init__(self, assignable_roles: list[AssignableRole]): + super().__init__( + style=discord.ButtonStyle.success, + label="Claim all available roles", + custom_id=self.CUSTOM_ID, + row=1 + ) + self.assignable_roles = assignable_roles + + async def callback(self, interaction: Interaction) -> t.Any: + """Assigns all missing available roles to the interactor.""" + await interaction.response.defer() + view = prepare_available_role_subscription_view(interaction, self.assignable_roles) + message = await interaction.followup.send( + view=view, + ) + # Keep reference of the message that contains the view to be deleted + view.original_message = message + + class Subscribe(commands.Cog): """Cog to allow user to self-assign & remove the roles present in ASSIGNABLE_ROLES.""" @@ -239,17 +221,14 @@ async def cog_load(self) -> None: ) async def subscribe_command(self, ctx: commands.Context, *_) -> None: # We don't actually care about the args """Display the member's current state for each role, and allow them to add/remove the roles.""" - button_view = RoleButtonView(ctx.author) - author_roles = [role.id for role in ctx.author.roles] - for index, role in enumerate(self.assignable_roles): - row = index // ITEMS_PER_ROW - button_view.add_item(SingleRoleButton(role, role.role_id in author_roles, row)) + view = prepare_available_role_subscription_view(ctx, self.assignable_roles) - await ctx.send( + message = await ctx.send( "Click the buttons below to add or remove your roles!", - view=button_view, - delete_after=DELETE_MESSAGE_AFTER, + view=view, ) + # Keep reference of the message that contains the view to be deleted + view.original_message = message async def __search_for_self_assignable_roles_message(self) -> discord.Message: """ @@ -281,6 +260,23 @@ def __attach_view_to_initial_self_assignable_roles_message(self, message: discor self.bot.add_view(view, message_id=message.id) +def prepare_available_role_subscription_view( + trigger_action: commands.Context | Interaction, + assignable_roles: list[AssignableRole] +) -> discord.ui.View: + """Prepares the view containing the self assignable roles before its sent.""" + author = trigger_action.author if isinstance(trigger_action, commands.Context) else trigger_action.user + author_roles = [role.id for role in author.roles] + button_view = RoleButtonView(member=author) + button_view.original_message = trigger_action.message + + for index, role in enumerate(assignable_roles): + row = index // ITEMS_PER_ROW + button_view.add_item(SingleRoleButton(role, role.role_id in author_roles, row)) + + return button_view + + async def setup(bot: Bot) -> None: """Load the Subscribe cog.""" if len(ASSIGNABLE_ROLES) > ITEMS_PER_ROW*5: # Discord limits views to 5 rows of buttons. From 3cedc99e7fcf342aad8b0217e42d38aad78bb419 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 27 Nov 2022 16:02:12 +0100 Subject: [PATCH 07/30] restore original value of DELETE_MESSAGE_AFTER --- bot/exts/info/subscribe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 333f33b505..dd676080b9 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -55,7 +55,7 @@ def get_readable_available_months(self) -> str: ) ITEMS_PER_ROW = 3 -DELETE_MESSAGE_AFTER = 20 # Seconds +DELETE_MESSAGE_AFTER = 300 # Seconds log = get_logger(__name__) From 6c61d34ff6ded0a744d4d6304ae0fc8a41ec204c Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 27 Nov 2022 17:43:13 +0100 Subject: [PATCH 08/30] rename ClaimAllSelfAssignableRolesButton to ShowAllSelfAssignableRolesButton --- bot/exts/info/subscribe.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index dd676080b9..c68207e1c9 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -153,15 +153,15 @@ def __init__(self): super(AllSelfAssignableRolesView, self).__init__(timeout=None) -class ClaimAllSelfAssignableRolesButton(discord.ui.Button): - """A button that adds all self assignable roles to the interactor.""" +class ShowAllSelfAssignableRolesButton(discord.ui.Button): + """A button that sends a view containing all the different roles a user can self assign at that time.""" CUSTOM_ID = "gotta-claim-them-all" def __init__(self, assignable_roles: list[AssignableRole]): super().__init__( style=discord.ButtonStyle.success, - label="Claim all available roles", + label="Show available roles", custom_id=self.CUSTOM_ID, row=1 ) @@ -246,7 +246,7 @@ async def __search_for_self_assignable_roles_message(self) -> discord.Message: log.debug("Self assignable roles view message hasn't been found, creating a new one.") view = AllSelfAssignableRolesView() - view.add_item(ClaimAllSelfAssignableRolesButton(self.assignable_roles)) + view.add_item(ShowAllSelfAssignableRolesButton(self.assignable_roles)) return await roles_channel.send(self.SELF_ASSIGNABLE_ROLES_MESSAGE, view=view) def __attach_view_to_initial_self_assignable_roles_message(self, message: discord.Message) -> None: @@ -256,7 +256,7 @@ def __attach_view_to_initial_self_assignable_roles_message(self, message: discor The message is searched for/created upon loading the Cog. """ view = AllSelfAssignableRolesView() - view.add_item(ClaimAllSelfAssignableRolesButton(self.assignable_roles)) + view.add_item(ShowAllSelfAssignableRolesButton(self.assignable_roles)) self.bot.add_view(view, message_id=message.id) From bde96fd65a7d30592f65a31eaa07dfa8ed404f68 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 27 Nov 2022 17:50:27 +0100 Subject: [PATCH 09/30] update message content of the persistent view The messages were intended for a button that will automatically assign all availables roles to the interactor This changes since it's not the intended behavior --- bot/exts/info/subscribe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index c68207e1c9..8f46fde61e 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -161,7 +161,7 @@ class ShowAllSelfAssignableRolesButton(discord.ui.Button): def __init__(self, assignable_roles: list[AssignableRole]): super().__init__( style=discord.ButtonStyle.success, - label="Show available roles", + label="Show all self assignable roles", custom_id=self.CUSTOM_ID, row=1 ) @@ -181,7 +181,7 @@ async def callback(self, interaction: Interaction) -> t.Any: class Subscribe(commands.Cog): """Cog to allow user to self-assign & remove the roles present in ASSIGNABLE_ROLES.""" - SELF_ASSIGNABLE_ROLES_MESSAGE = "Click on this button to earn all the currently available server roles" + SELF_ASSIGNABLE_ROLES_MESSAGE = "Click on this button to show all self assignable roles" def __init__(self, bot: Bot): self.bot = bot From dd4903c6654e471156f984d3f38b1207909529b0 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 27 Nov 2022 17:51:27 +0100 Subject: [PATCH 10/30] fix docstrings of the ShowAllSelfAssignableRolesButton's callback function --- bot/exts/info/subscribe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 8f46fde61e..a2ce4beb7a 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -168,7 +168,7 @@ def __init__(self, assignable_roles: list[AssignableRole]): self.assignable_roles = assignable_roles async def callback(self, interaction: Interaction) -> t.Any: - """Assigns all missing available roles to the interactor.""" + """Sends the original subscription view containing the available self assignable roles.""" await interaction.response.defer() view = prepare_available_role_subscription_view(interaction, self.assignable_roles) message = await interaction.followup.send( From bd4b0b47e045145f03baddb35523615a42eb204e Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 27 Nov 2022 17:57:07 +0100 Subject: [PATCH 11/30] rename prepare_available_role_subscription_view to prepare_self_assignable_roles_view --- bot/exts/info/subscribe.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index a2ce4beb7a..fe54a3dbe1 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -170,7 +170,7 @@ def __init__(self, assignable_roles: list[AssignableRole]): async def callback(self, interaction: Interaction) -> t.Any: """Sends the original subscription view containing the available self assignable roles.""" await interaction.response.defer() - view = prepare_available_role_subscription_view(interaction, self.assignable_roles) + view = prepare_self_assignable_roles_view(interaction, self.assignable_roles) message = await interaction.followup.send( view=view, ) @@ -221,7 +221,7 @@ async def cog_load(self) -> None: ) async def subscribe_command(self, ctx: commands.Context, *_) -> None: # We don't actually care about the args """Display the member's current state for each role, and allow them to add/remove the roles.""" - view = prepare_available_role_subscription_view(ctx, self.assignable_roles) + view = prepare_self_assignable_roles_view(ctx, self.assignable_roles) message = await ctx.send( "Click the buttons below to add or remove your roles!", @@ -260,7 +260,7 @@ def __attach_view_to_initial_self_assignable_roles_message(self, message: discor self.bot.add_view(view, message_id=message.id) -def prepare_available_role_subscription_view( +def prepare_self_assignable_roles_view( trigger_action: commands.Context | Interaction, assignable_roles: list[AssignableRole] ) -> discord.ui.View: From 54dfcf9ad4e764c6c7de9862d0b5641f7d31ef61 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 27 Nov 2022 22:31:37 +0100 Subject: [PATCH 12/30] update docs for the newly added view & button --- bot/exts/info/subscribe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index fe54a3dbe1..ab1c3cf7e3 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -147,14 +147,14 @@ async def update_view(self, interaction: Interaction) -> None: class AllSelfAssignableRolesView(discord.ui.View): - """A view that'll hold one button allowing interactors to get all available self-assignable roles.""" + """A persistent view that'll hold one button allowing interactors to toggle all available self-assignable roles.""" def __init__(self): super(AllSelfAssignableRolesView, self).__init__(timeout=None) class ShowAllSelfAssignableRolesButton(discord.ui.Button): - """A button that sends a view containing all the different roles a user can self assign at that time.""" + """A button that, when clicked, sends a view a user can interact with to assign one or all the available roles.""" CUSTOM_ID = "gotta-claim-them-all" From 28c733cc61f449fa5143242e231a1557d5533d9e Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 27 Nov 2022 22:40:39 +0100 Subject: [PATCH 13/30] rename the method that attaches the persistent view This also enhances the docstrings for a better clarification of its purpose --- bot/exts/info/subscribe.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index ab1c3cf7e3..dba36edac0 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -211,7 +211,7 @@ async def cog_load(self) -> None: self.assignable_roles.sort(key=operator.methodcaller("is_currently_available"), reverse=True) initial_self_assignable_roles_message = await self.__search_for_self_assignable_roles_message() - self.__attach_view_to_initial_self_assignable_roles_message(initial_self_assignable_roles_message) + self.__attach_persistent_roles_view(initial_self_assignable_roles_message) @commands.cooldown(1, 10, commands.BucketType.member) @commands.command(name="subscribe", aliases=("unsubscribe",)) @@ -249,15 +249,23 @@ async def __search_for_self_assignable_roles_message(self) -> discord.Message: view.add_item(ShowAllSelfAssignableRolesButton(self.assignable_roles)) return await roles_channel.send(self.SELF_ASSIGNABLE_ROLES_MESSAGE, view=view) - def __attach_view_to_initial_self_assignable_roles_message(self, message: discord.Message) -> None: + def __attach_persistent_roles_view( + self, + placeholder_message: discord.Message + ) -> None: """ - Attaches the persistent self assignable roles view. + Attaches the persistent view that toggles self assignable roles to its placeholder message. The message is searched for/created upon loading the Cog. + + Parameters + __________ + :param placeholder_message: The message that will hold the persistent view allowing + users to toggle the RoleButtonView """ view = AllSelfAssignableRolesView() view.add_item(ShowAllSelfAssignableRolesButton(self.assignable_roles)) - self.bot.add_view(view, message_id=message.id) + self.bot.add_view(view, message_id=placeholder_message.id) def prepare_self_assignable_roles_view( From c62872260bf79e03318828c3fa333bc3b4c78171 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 27 Nov 2022 22:41:29 +0100 Subject: [PATCH 14/30] remove waring in setup docstrings --- bot/exts/info/subscribe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index dba36edac0..204efb3402 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -286,7 +286,7 @@ def prepare_self_assignable_roles_view( async def setup(bot: Bot) -> None: - """Load the Subscribe cog.""" + """Load the 'Subscribe' cog.""" if len(ASSIGNABLE_ROLES) > ITEMS_PER_ROW*5: # Discord limits views to 5 rows of buttons. log.error("Too many roles for 5 rows, not loading the Subscribe cog.") else: From f748ceb5c2a432cde383488e74325dcdd364f452 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 27 Nov 2022 22:45:31 +0100 Subject: [PATCH 15/30] misc style & doc improvements --- bot/exts/info/subscribe.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 204efb3402..573d015b01 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -85,7 +85,7 @@ async def on_timeout(self) -> None: class SingleRoleButton(discord.ui.Button): - """A button that adds or removes a role from the member depending on it's current state.""" + """A button that adds or removes a role from the member depending on its current state.""" ADD_STYLE = discord.ButtonStyle.success REMOVE_STYLE = discord.ButtonStyle.red @@ -237,7 +237,7 @@ async def __search_for_self_assignable_roles_message(self) -> discord.Message: If the initial message isn't found, a new one will be created. This message will always be needed to attach the persistent view to it """ - roles_channel = await get_or_fetch_channel(constants.Channels.roles) + roles_channel: discord.TextChannel = await get_or_fetch_channel(constants.Channels.roles) async for message in roles_channel.history(limit=30): if message.content == self.SELF_ASSIGNABLE_ROLES_MESSAGE: @@ -287,7 +287,7 @@ def prepare_self_assignable_roles_view( async def setup(bot: Bot) -> None: """Load the 'Subscribe' cog.""" - if len(ASSIGNABLE_ROLES) > ITEMS_PER_ROW*5: # Discord limits views to 5 rows of buttons. + if len(ASSIGNABLE_ROLES) > ITEMS_PER_ROW * 5: # Discord limits views to 5 rows of buttons. log.error("Too many roles for 5 rows, not loading the Subscribe cog.") else: await bot.add_cog(Subscribe(bot)) From c14e75afc290a7520851758368acb24c74ba54ba Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 27 Nov 2022 22:50:46 +0100 Subject: [PATCH 16/30] add docstrings to RoleButtonView This documents the attributes the class has, which gives better context for future readers/contributors --- bot/exts/info/subscribe.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 573d015b01..4a34077f37 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -61,12 +61,23 @@ def get_readable_available_months(self) -> str: class RoleButtonView(discord.ui.View): - """A list of SingleRoleButtons to show to the member.""" + """ + A view that holds the list of SingleRoleButtons to show to the member. + + Attributes + __________ + anchor_message : discord.Message + The message to which this view will be attached + interaction_owner: discord.Member + The member that initiated the interaction + """ + + anchor_message: discord.Message def __init__(self, member: discord.Member): super().__init__(timeout=DELETE_MESSAGE_AFTER) # We can't obtain the reference to the message before the view is sent - self.original_message = None + self.anchor_message = None self.interaction_owner = member async def interaction_check(self, interaction: Interaction) -> bool: @@ -81,7 +92,7 @@ async def interaction_check(self, interaction: Interaction) -> bool: async def on_timeout(self) -> None: """Delete the original message that the view was sent along with.""" - await self.original_message.delete() + await self.anchor_message.delete() class SingleRoleButton(discord.ui.Button): @@ -175,7 +186,7 @@ async def callback(self, interaction: Interaction) -> t.Any: view=view, ) # Keep reference of the message that contains the view to be deleted - view.original_message = message + view.anchor_message = message class Subscribe(commands.Cog): @@ -228,7 +239,7 @@ async def subscribe_command(self, ctx: commands.Context, *_) -> None: # We don' view=view, ) # Keep reference of the message that contains the view to be deleted - view.original_message = message + view.anchor_message = message async def __search_for_self_assignable_roles_message(self) -> discord.Message: """ @@ -276,7 +287,7 @@ def prepare_self_assignable_roles_view( author = trigger_action.author if isinstance(trigger_action, commands.Context) else trigger_action.user author_roles = [role.id for role in author.roles] button_view = RoleButtonView(member=author) - button_view.original_message = trigger_action.message + button_view.anchor_message = trigger_action.message for index, role in enumerate(assignable_roles): row = index // ITEMS_PER_ROW From e7d0987a62ecb532a68839d8e98d33f2ddf0912f Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 27 Nov 2022 22:58:54 +0100 Subject: [PATCH 17/30] make the roles view ephemeral when sent in roles channel --- bot/exts/info/subscribe.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 4a34077f37..63a23346f0 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -151,7 +151,7 @@ async def update_view(self, interaction: Interaction) -> None: self.style = self.REMOVE_STYLE if self.assigned else self.ADD_STYLE self.label = self.LABEL_FORMAT.format(action="Remove" if self.assigned else "Add", role_name=self.role.name) try: - await interaction.message.edit(view=self.view) + await self.view.anchor_message.edit(view=self.view) except discord.NotFound: log.debug("Subscribe message for %s removed before buttons could be updated", interaction.user) self.view.stop() @@ -184,6 +184,7 @@ async def callback(self, interaction: Interaction) -> t.Any: view = prepare_self_assignable_roles_view(interaction, self.assignable_roles) message = await interaction.followup.send( view=view, + ephemeral=True ) # Keep reference of the message that contains the view to be deleted view.anchor_message = message From 0a60b5d4200219233ad5164da3ae73c5f43955e7 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sat, 3 Dec 2022 23:37:22 +0100 Subject: [PATCH 18/30] do not use name mangling This also renames to method to align more with our fetch or {action} pattern --- bot/exts/info/subscribe.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 63a23346f0..15e7ba3479 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -222,7 +222,7 @@ async def cog_load(self) -> None: self.assignable_roles.sort(key=operator.attrgetter("name")) self.assignable_roles.sort(key=operator.methodcaller("is_currently_available"), reverse=True) - initial_self_assignable_roles_message = await self.__search_for_self_assignable_roles_message() + initial_self_assignable_roles_message = await self._fetch_or_create_self_assignable_roles_message() self.__attach_persistent_roles_view(initial_self_assignable_roles_message) @commands.cooldown(1, 10, commands.BucketType.member) @@ -242,9 +242,9 @@ async def subscribe_command(self, ctx: commands.Context, *_) -> None: # We don' # Keep reference of the message that contains the view to be deleted view.anchor_message = message - async def __search_for_self_assignable_roles_message(self) -> discord.Message: + async def _fetch_or_create_self_assignable_roles_message(self) -> discord.Message: """ - Searches for the message that holds the self assignable roles view. + Fetches the message that holds the self assignable roles view. If the initial message isn't found, a new one will be created. This message will always be needed to attach the persistent view to it From 28dac62fb325e0f09404945645589a89af19bfdd Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sat, 3 Dec 2022 23:39:57 +0100 Subject: [PATCH 19/30] call super without referencing the current class --- bot/exts/info/subscribe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 15e7ba3479..92d7d23b25 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -161,7 +161,7 @@ class AllSelfAssignableRolesView(discord.ui.View): """A persistent view that'll hold one button allowing interactors to toggle all available self-assignable roles.""" def __init__(self): - super(AllSelfAssignableRolesView, self).__init__(timeout=None) + super().__init__(timeout=None) class ShowAllSelfAssignableRolesButton(discord.ui.Button): From 72b2f803a6bb915c4bb07d3a0cd3f4d141d46431 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sat, 3 Dec 2022 23:41:28 +0100 Subject: [PATCH 20/30] rename #Roles section to #Information --- config-default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index 86474c204e..ac76a670a1 100644 --- a/config-default.yml +++ b/config-default.yml @@ -235,7 +235,7 @@ guild: # Watch big_brother_logs: &BB_LOGS 468507907357409333 - # Roles + # Information roles: 851270062434156586 moderation_categories: From 24c36e5bcc8f87c045242c88b6b5baa8d12110c0 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 4 Dec 2022 11:10:53 +0100 Subject: [PATCH 21/30] use button decorator instead of subclassing discord.ui.Button This will help clarify the relationship between the button & the viw since it'll only be used there --- bot/exts/info/subscribe.py | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 92d7d23b25..55ee3d8559 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -160,25 +160,17 @@ async def update_view(self, interaction: Interaction) -> None: class AllSelfAssignableRolesView(discord.ui.View): """A persistent view that'll hold one button allowing interactors to toggle all available self-assignable roles.""" - def __init__(self): - super().__init__(timeout=None) - - -class ShowAllSelfAssignableRolesButton(discord.ui.Button): - """A button that, when clicked, sends a view a user can interact with to assign one or all the available roles.""" - - CUSTOM_ID = "gotta-claim-them-all" - def __init__(self, assignable_roles: list[AssignableRole]): - super().__init__( - style=discord.ButtonStyle.success, - label="Show all self assignable roles", - custom_id=self.CUSTOM_ID, - row=1 - ) + super().__init__(timeout=None) self.assignable_roles = assignable_roles - async def callback(self, interaction: Interaction) -> t.Any: + @discord.ui.button( + style=discord.ButtonStyle.success, + label="Show all self assignable roles", + custom_id="gotta-claim-them-all", + row=1 + ) + async def show_all_self_assignable_roles(self, interaction: Interaction, button: discord.ui.Button) -> None: """Sends the original subscription view containing the available self assignable roles.""" await interaction.response.defer() view = prepare_self_assignable_roles_view(interaction, self.assignable_roles) @@ -257,8 +249,7 @@ async def _fetch_or_create_self_assignable_roles_message(self) -> discord.Messag return message log.debug("Self assignable roles view message hasn't been found, creating a new one.") - view = AllSelfAssignableRolesView() - view.add_item(ShowAllSelfAssignableRolesButton(self.assignable_roles)) + view = AllSelfAssignableRolesView(self.assignable_roles) return await roles_channel.send(self.SELF_ASSIGNABLE_ROLES_MESSAGE, view=view) def __attach_persistent_roles_view( @@ -275,8 +266,7 @@ def __attach_persistent_roles_view( :param placeholder_message: The message that will hold the persistent view allowing users to toggle the RoleButtonView """ - view = AllSelfAssignableRolesView() - view.add_item(ShowAllSelfAssignableRolesButton(self.assignable_roles)) + view = AllSelfAssignableRolesView(self.assignable_roles) self.bot.add_view(view, message_id=placeholder_message.id) From a6fa4b1b7ce4dcff8c0ecfc4459df471ec97cde9 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 4 Dec 2022 11:12:57 +0100 Subject: [PATCH 22/30] update custom id of the AllSelfAssignableRolesView's button --- bot/exts/info/subscribe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 55ee3d8559..8eeb852b83 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -167,7 +167,7 @@ def __init__(self, assignable_roles: list[AssignableRole]): @discord.ui.button( style=discord.ButtonStyle.success, label="Show all self assignable roles", - custom_id="gotta-claim-them-all", + custom_id="toggle-available-roles-button", row=1 ) async def show_all_self_assignable_roles(self, interaction: Interaction, button: discord.ui.Button) -> None: From c630e1b9c2ca624dd272d634cd24704c11bea953 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 4 Dec 2022 11:19:28 +0100 Subject: [PATCH 23/30] make SELF_ASSIGNABLE_ROLES_MESSAGE more inviting for people to interact with --- bot/exts/info/subscribe.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 8eeb852b83..b77dcd8b0c 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -185,7 +185,11 @@ async def show_all_self_assignable_roles(self, interaction: Interaction, button: class Subscribe(commands.Cog): """Cog to allow user to self-assign & remove the roles present in ASSIGNABLE_ROLES.""" - SELF_ASSIGNABLE_ROLES_MESSAGE = "Click on this button to show all self assignable roles" + GREETING_EMOJI = ":wave:" + + SELF_ASSIGNABLE_ROLES_MESSAGE = f"Hi there {GREETING_EMOJI}," \ + "\nDid you know we had various self-assignable roles for server updates an events?"\ + "\nClick the button below to toggle them." def __init__(self, bot: Bot): self.bot = bot From 37b43a6c04af0498d96bd28ec544973d2e653941 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 4 Dec 2022 11:31:52 +0100 Subject: [PATCH 24/30] ditch prepare_self_assignable_roles_view by constructing the buttons inside the view itself --- bot/exts/info/subscribe.py | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index b77dcd8b0c..0c12da37d0 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -74,12 +74,18 @@ class RoleButtonView(discord.ui.View): anchor_message: discord.Message - def __init__(self, member: discord.Member): + def __init__(self, member: discord.Member, assignable_roles: list[AssignableRole]): super().__init__(timeout=DELETE_MESSAGE_AFTER) # We can't obtain the reference to the message before the view is sent self.anchor_message = None self.interaction_owner = member + author_roles = [role.id for role in member.roles] + + for index, role in enumerate(assignable_roles): + row = index // ITEMS_PER_ROW + self.add_item(SingleRoleButton(role, role.role_id in author_roles, row)) + async def interaction_check(self, interaction: Interaction) -> bool: """Ensure that the user clicking the button is the member who invoked the command.""" if interaction.user != self.interaction_owner: @@ -173,7 +179,7 @@ def __init__(self, assignable_roles: list[AssignableRole]): async def show_all_self_assignable_roles(self, interaction: Interaction, button: discord.ui.Button) -> None: """Sends the original subscription view containing the available self assignable roles.""" await interaction.response.defer() - view = prepare_self_assignable_roles_view(interaction, self.assignable_roles) + view = RoleButtonView(interaction.user, self.assignable_roles) message = await interaction.followup.send( view=view, ephemeral=True @@ -229,8 +235,7 @@ async def cog_load(self) -> None: ) async def subscribe_command(self, ctx: commands.Context, *_) -> None: # We don't actually care about the args """Display the member's current state for each role, and allow them to add/remove the roles.""" - view = prepare_self_assignable_roles_view(ctx, self.assignable_roles) - + view = RoleButtonView(ctx.author, self.assignable_roles) message = await ctx.send( "Click the buttons below to add or remove your roles!", view=view, @@ -274,23 +279,6 @@ def __attach_persistent_roles_view( self.bot.add_view(view, message_id=placeholder_message.id) -def prepare_self_assignable_roles_view( - trigger_action: commands.Context | Interaction, - assignable_roles: list[AssignableRole] -) -> discord.ui.View: - """Prepares the view containing the self assignable roles before its sent.""" - author = trigger_action.author if isinstance(trigger_action, commands.Context) else trigger_action.user - author_roles = [role.id for role in author.roles] - button_view = RoleButtonView(member=author) - button_view.anchor_message = trigger_action.message - - for index, role in enumerate(assignable_roles): - row = index // ITEMS_PER_ROW - button_view.add_item(SingleRoleButton(role, role.role_id in author_roles, row)) - - return button_view - - async def setup(bot: Bot) -> None: """Load the 'Subscribe' cog.""" if len(ASSIGNABLE_ROLES) > ITEMS_PER_ROW * 5: # Discord limits views to 5 rows of buttons. From 6c6b2bc358228404978c855d18c788e85fb4433a Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 4 Dec 2022 13:25:13 +0100 Subject: [PATCH 25/30] relay the newly created view to __attach_persistent_roles_view --- bot/exts/info/subscribe.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 0c12da37d0..cdf0bc86bf 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -224,8 +224,9 @@ async def cog_load(self) -> None: self.assignable_roles.sort(key=operator.attrgetter("name")) self.assignable_roles.sort(key=operator.methodcaller("is_currently_available"), reverse=True) - initial_self_assignable_roles_message = await self._fetch_or_create_self_assignable_roles_message() - self.__attach_persistent_roles_view(initial_self_assignable_roles_message) + placeholder_message_view_tuple = await self._fetch_or_create_self_assignable_roles_message() + self_assignable_roles_message, self_assignable_roles_view = placeholder_message_view_tuple + self.__attach_persistent_roles_view(self_assignable_roles_message, self_assignable_roles_view) @commands.cooldown(1, 10, commands.BucketType.member) @commands.command(name="subscribe", aliases=("unsubscribe",)) @@ -243,7 +244,7 @@ async def subscribe_command(self, ctx: commands.Context, *_) -> None: # We don' # Keep reference of the message that contains the view to be deleted view.anchor_message = message - async def _fetch_or_create_self_assignable_roles_message(self) -> discord.Message: + async def _fetch_or_create_self_assignable_roles_message(self) -> tuple[discord.Message, discord.ui.View | None]: """ Fetches the message that holds the self assignable roles view. @@ -255,15 +256,17 @@ async def _fetch_or_create_self_assignable_roles_message(self) -> discord.Messag async for message in roles_channel.history(limit=30): if message.content == self.SELF_ASSIGNABLE_ROLES_MESSAGE: log.debug(f"Found self assignable roles view message: {message.id}") - return message + return message, None log.debug("Self assignable roles view message hasn't been found, creating a new one.") view = AllSelfAssignableRolesView(self.assignable_roles) - return await roles_channel.send(self.SELF_ASSIGNABLE_ROLES_MESSAGE, view=view) + placeholder_message = await roles_channel.send(self.SELF_ASSIGNABLE_ROLES_MESSAGE, view=view) + return placeholder_message, view def __attach_persistent_roles_view( self, - placeholder_message: discord.Message + placeholder_message: discord.Message, + persistent_roles_view: discord.ui.View | None = None ) -> None: """ Attaches the persistent view that toggles self assignable roles to its placeholder message. @@ -274,9 +277,13 @@ def __attach_persistent_roles_view( __________ :param placeholder_message: The message that will hold the persistent view allowing users to toggle the RoleButtonView + :param persistent_roles_view: The view attached to the placeholder_message + If none, a new view will be created """ - view = AllSelfAssignableRolesView(self.assignable_roles) - self.bot.add_view(view, message_id=placeholder_message.id) + if not persistent_roles_view: + persistent_roles_view = AllSelfAssignableRolesView(self.assignable_roles) + + self.bot.add_view(persistent_roles_view, message_id=placeholder_message.id) async def setup(bot: Bot) -> None: From 7118c51b9b62b2a5ce0e745e4832b17f52901235 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 4 Dec 2022 13:53:34 +0100 Subject: [PATCH 26/30] edit view through interaction.response.edit_message --- bot/exts/info/subscribe.py | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index cdf0bc86bf..d52836cfde 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -66,20 +66,15 @@ class RoleButtonView(discord.ui.View): Attributes __________ - anchor_message : discord.Message - The message to which this view will be attached interaction_owner: discord.Member The member that initiated the interaction """ - anchor_message: discord.Message + interaction_owner: discord.Member def __init__(self, member: discord.Member, assignable_roles: list[AssignableRole]): super().__init__(timeout=DELETE_MESSAGE_AFTER) - # We can't obtain the reference to the message before the view is sent - self.anchor_message = None self.interaction_owner = member - author_roles = [role.id for role in member.roles] for index, role in enumerate(assignable_roles): @@ -96,10 +91,6 @@ async def interaction_check(self, interaction: Interaction) -> bool: return False return True - async def on_timeout(self) -> None: - """Delete the original message that the view was sent along with.""" - await self.anchor_message.delete() - class SingleRoleButton(discord.ui.Button): """A button that adds or removes a role from the member depending on its current state.""" @@ -157,7 +148,7 @@ async def update_view(self, interaction: Interaction) -> None: self.style = self.REMOVE_STYLE if self.assigned else self.ADD_STYLE self.label = self.LABEL_FORMAT.format(action="Remove" if self.assigned else "Add", role_name=self.role.name) try: - await self.view.anchor_message.edit(view=self.view) + await interaction.response.edit_message(view=self.view) except discord.NotFound: log.debug("Subscribe message for %s removed before buttons could be updated", interaction.user) self.view.stop() @@ -178,14 +169,11 @@ def __init__(self, assignable_roles: list[AssignableRole]): ) async def show_all_self_assignable_roles(self, interaction: Interaction, button: discord.ui.Button) -> None: """Sends the original subscription view containing the available self assignable roles.""" - await interaction.response.defer() view = RoleButtonView(interaction.user, self.assignable_roles) - message = await interaction.followup.send( + await interaction.response.send_message( view=view, ephemeral=True ) - # Keep reference of the message that contains the view to be deleted - view.anchor_message = message class Subscribe(commands.Cog): @@ -237,12 +225,11 @@ async def cog_load(self) -> None: async def subscribe_command(self, ctx: commands.Context, *_) -> None: # We don't actually care about the args """Display the member's current state for each role, and allow them to add/remove the roles.""" view = RoleButtonView(ctx.author, self.assignable_roles) - message = await ctx.send( + await ctx.send( "Click the buttons below to add or remove your roles!", view=view, + delete_after=DELETE_MESSAGE_AFTER ) - # Keep reference of the message that contains the view to be deleted - view.anchor_message = message async def _fetch_or_create_self_assignable_roles_message(self) -> tuple[discord.Message, discord.ui.View | None]: """ From 7976bc2af0fe49ddaebc63242b581b9eecb66132 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 4 Dec 2022 14:00:22 +0100 Subject: [PATCH 27/30] use follow.send to send the role update message --- bot/exts/info/subscribe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index d52836cfde..aa48be13cc 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -138,7 +138,7 @@ async def callback(self, interaction: Interaction) -> None: self.assigned = not self.assigned await self.update_view(interaction) - await interaction.response.send_message( + await interaction.followup.send( self.LABEL_FORMAT.format(action="Added" if self.assigned else "Removed", role_name=self.role.name), ephemeral=True, ) From 91a869e4a23fdee8c6407678ca178e324d6184f3 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 4 Dec 2022 15:51:00 +0100 Subject: [PATCH 28/30] do not use name mangling in _attach_persistent_roles_view --- bot/exts/info/subscribe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index aa48be13cc..f3f6e71f63 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -214,7 +214,7 @@ async def cog_load(self) -> None: placeholder_message_view_tuple = await self._fetch_or_create_self_assignable_roles_message() self_assignable_roles_message, self_assignable_roles_view = placeholder_message_view_tuple - self.__attach_persistent_roles_view(self_assignable_roles_message, self_assignable_roles_view) + self._attach_persistent_roles_view(self_assignable_roles_message, self_assignable_roles_view) @commands.cooldown(1, 10, commands.BucketType.member) @commands.command(name="subscribe", aliases=("unsubscribe",)) @@ -250,7 +250,7 @@ async def _fetch_or_create_self_assignable_roles_message(self) -> tuple[discord. placeholder_message = await roles_channel.send(self.SELF_ASSIGNABLE_ROLES_MESSAGE, view=view) return placeholder_message, view - def __attach_persistent_roles_view( + def _attach_persistent_roles_view( self, placeholder_message: discord.Message, persistent_roles_view: discord.ui.View | None = None From c55b9ffb4ef4eb245eff2fd256c42c16976a3467 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sun, 18 Dec 2022 08:47:14 +0100 Subject: [PATCH 29/30] fix Zig's nit comments --- bot/exts/info/subscribe.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index f3f6e71f63..082f8438b3 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -98,7 +98,7 @@ class SingleRoleButton(discord.ui.Button): ADD_STYLE = discord.ButtonStyle.success REMOVE_STYLE = discord.ButtonStyle.red UNAVAILABLE_STYLE = discord.ButtonStyle.secondary - LABEL_FORMAT = "{action} role {role_name}." + LABEL_FORMAT = "{action} role {role_name}" CUSTOM_ID_FORMAT = "subscribe-{role_id}" def __init__(self, role: AssignableRole, assigned: bool, row: int): @@ -181,9 +181,11 @@ class Subscribe(commands.Cog): GREETING_EMOJI = ":wave:" - SELF_ASSIGNABLE_ROLES_MESSAGE = f"Hi there {GREETING_EMOJI}," \ - "\nDid you know we had various self-assignable roles for server updates an events?"\ - "\nClick the button below to toggle them." + SELF_ASSIGNABLE_ROLES_MESSAGE = ( + f"Hi there {GREETING_EMOJI}," + "\nWe have self-assignable roles for server updates an events!" + "\nClick the button below to toggle them:" + ) def __init__(self, bot: Bot): self.bot = bot From 52d69dee620d2cb659b1fee288ed6360bded83d7 Mon Sep 17 00:00:00 2001 From: shtlrs Date: Sat, 14 Jan 2023 10:56:09 +0100 Subject: [PATCH 30/30] fix typos in the SELF_ASSIGNABLE_ROLES_MESSAGE --- bot/exts/info/subscribe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 082f8438b3..86a209214d 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -183,7 +183,7 @@ class Subscribe(commands.Cog): SELF_ASSIGNABLE_ROLES_MESSAGE = ( f"Hi there {GREETING_EMOJI}," - "\nWe have self-assignable roles for server updates an events!" + "\nWe have self-assignable roles for server updates and events!" "\nClick the button below to toggle them:" )