From f833389734b2ce35307a005d8e26c5850cdc689a Mon Sep 17 00:00:00 2001 From: retke Date: Tue, 23 Mar 2021 10:30:45 +0100 Subject: [PATCH 01/13] Add user param to menus --- redbot/core/utils/menus.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/redbot/core/utils/menus.py b/redbot/core/utils/menus.py index a0c61282891..5ee9327cf5a 100644 --- a/redbot/core/utils/menus.py +++ b/redbot/core/utils/menus.py @@ -19,6 +19,7 @@ async def menu( pages: Union[List[str], List[discord.Embed]], controls: dict, message: discord.Message = None, + user: discord.User = None, page: int = 0, timeout: float = 30.0, ): @@ -46,6 +47,8 @@ async def menu( message: discord.Message The message representing the menu. Usually :code:`None` when first opening the menu + user: discord.User + The user allowed to interact with the menu. Defaults to ctx.author page: int The current page number of the menu timeout: float @@ -90,7 +93,9 @@ async def menu( try: react, user = await ctx.bot.wait_for( "reaction_add", - check=ReactionPredicate.with_emojis(tuple(controls.keys()), message, ctx.author), + check=ReactionPredicate.with_emojis( + tuple(controls.keys()), message, user or ctx.author + ), timeout=timeout, ) except asyncio.TimeoutError: From aa68d79779ceaad94283559b703dab716e63e3b1 Mon Sep 17 00:00:00 2001 From: retke Date: Fri, 2 Apr 2021 15:27:28 +0200 Subject: [PATCH 02/13] Pass the arguments to controls --- redbot/core/utils/menus.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/redbot/core/utils/menus.py b/redbot/core/utils/menus.py index 5ee9327cf5a..1351e96d495 100644 --- a/redbot/core/utils/menus.py +++ b/redbot/core/utils/menus.py @@ -19,9 +19,9 @@ async def menu( pages: Union[List[str], List[discord.Embed]], controls: dict, message: discord.Message = None, - user: discord.User = None, page: int = 0, timeout: float = 30.0, + user: discord.User = None, ): """ An emoji-based menu @@ -47,12 +47,12 @@ async def menu( message: discord.Message The message representing the menu. Usually :code:`None` when first opening the menu - user: discord.User - The user allowed to interact with the menu. Defaults to ctx.author page: int The current page number of the menu timeout: float The time (in seconds) to wait for a reaction + user: discord.User + The user allowed to interact with the menu. Defaults to ctx.author Raises ------ @@ -118,7 +118,7 @@ async def menu( return else: return await controls[react.emoji]( - ctx, pages, controls, message, page, timeout, react.emoji + ctx, pages, controls, message, page, timeout, user, react.emoji ) @@ -129,6 +129,7 @@ async def next_page( message: discord.Message, page: int, timeout: float, + user: discord.User, emoji: str, ): perms = message.channel.permissions_for(ctx.me) @@ -139,7 +140,7 @@ async def next_page( page = 0 # Loop around to the first item else: page = page + 1 - return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout) + return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout, user=user) async def prev_page( @@ -149,6 +150,7 @@ async def prev_page( message: discord.Message, page: int, timeout: float, + user: discord.User, emoji: str, ): perms = message.channel.permissions_for(ctx.me) @@ -159,7 +161,7 @@ async def prev_page( page = len(pages) - 1 # Loop around to the last item else: page = page - 1 - return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout) + return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout, user=user) async def close_menu( @@ -169,6 +171,7 @@ async def close_menu( message: discord.Message, page: int, timeout: float, + user: discord.User, emoji: str, ): with contextlib.suppress(discord.NotFound): From 0ae17037dcf25e129ed85ed6fb77d1355f3285fb Mon Sep 17 00:00:00 2001 From: retke Date: Fri, 9 Apr 2021 11:26:22 +0200 Subject: [PATCH 03/13] Make the user param keyword-only and optional --- redbot/core/utils/menus.py | 40 +++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/redbot/core/utils/menus.py b/redbot/core/utils/menus.py index 1351e96d495..c5de65b27d1 100644 --- a/redbot/core/utils/menus.py +++ b/redbot/core/utils/menus.py @@ -21,6 +21,7 @@ async def menu( message: discord.Message = None, page: int = 0, timeout: float = 30.0, + *, user: discord.User = None, ): """ @@ -52,7 +53,10 @@ async def menu( timeout: float The time (in seconds) to wait for a reaction user: discord.User - The user allowed to interact with the menu. Defaults to ctx.author + The user allowed to interact with the menu. Defaults to ctx.author. + This is a keyword-only argument. + + *Added in 3.4.10* Raises ------ @@ -117,9 +121,14 @@ async def menu( except discord.NotFound: return else: - return await controls[react.emoji]( - ctx, pages, controls, message, page, timeout, user, react.emoji - ) + if user is not None: + return await controls[react.emoji]( + ctx, pages, controls, message, page, timeout, react.emoji, user=user + ) + else: + return await controls[react.emoji]( + ctx, pages, controls, message, page, timeout, react.emoji + ) async def next_page( @@ -129,8 +138,9 @@ async def next_page( message: discord.Message, page: int, timeout: float, - user: discord.User, emoji: str, + *, + user: discord.User = None, ): perms = message.channel.permissions_for(ctx.me) if perms.manage_messages: # Can manage messages, so remove react @@ -140,7 +150,12 @@ async def next_page( page = 0 # Loop around to the first item else: page = page + 1 - return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout, user=user) + if user is not None: + return await menu( + ctx, pages, controls, message=message, page=page, timeout=timeout, user=user + ) + else: + return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout) async def prev_page( @@ -150,8 +165,9 @@ async def prev_page( message: discord.Message, page: int, timeout: float, - user: discord.User, emoji: str, + *, + user: discord.User = None, ): perms = message.channel.permissions_for(ctx.me) if perms.manage_messages: # Can manage messages, so remove react @@ -161,7 +177,12 @@ async def prev_page( page = len(pages) - 1 # Loop around to the last item else: page = page - 1 - return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout, user=user) + if user is not None: + return await menu( + ctx, pages, controls, message=message, page=page, timeout=timeout, user=user + ) + else: + return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout) async def close_menu( @@ -171,8 +192,9 @@ async def close_menu( message: discord.Message, page: int, timeout: float, - user: discord.User, emoji: str, + *, + user: discord.User = None, ): with contextlib.suppress(discord.NotFound): await message.delete() From 230c2c848a6a85e08b03ce78581e200f12126ac7 Mon Sep 17 00:00:00 2001 From: retke Date: Fri, 9 Apr 2021 11:35:00 +0200 Subject: [PATCH 04/13] Docs --- redbot/core/utils/menus.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/redbot/core/utils/menus.py b/redbot/core/utils/menus.py index c5de65b27d1..11c614f791d 100644 --- a/redbot/core/utils/menus.py +++ b/redbot/core/utils/menus.py @@ -36,6 +36,10 @@ async def menu( This parameter should be the last one, and none of the parameters in the handling functions are optional + .. warning:: If you're using the `user` param, you need to pass it + as a keyword-only argument, and set :obj:`None` as the + default in your function. + Parameters ---------- ctx: commands.Context From 6ce27fcbc6dcb6cd2bce8e7a2b87e42effb85029 Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Fri, 25 Jun 2021 03:42:51 +0200 Subject: [PATCH 05/13] Remove outdated docstring (+ we don't use "added in" in our docs) --- redbot/core/utils/menus.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/redbot/core/utils/menus.py b/redbot/core/utils/menus.py index 11c614f791d..ed9bd61fdda 100644 --- a/redbot/core/utils/menus.py +++ b/redbot/core/utils/menus.py @@ -60,8 +60,6 @@ async def menu( The user allowed to interact with the menu. Defaults to ctx.author. This is a keyword-only argument. - *Added in 3.4.10* - Raises ------ RuntimeError From e5dfc471528409f186ffa1e1ab07e29b8f0f3696 Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 17 Mar 2024 02:42:02 +0100 Subject: [PATCH 06/13] Remove unnecessary documentation --- redbot/core/utils/menus.py | 1 - 1 file changed, 1 deletion(-) diff --git a/redbot/core/utils/menus.py b/redbot/core/utils/menus.py index 7615c92ec86..4be71f50e7c 100644 --- a/redbot/core/utils/menus.py +++ b/redbot/core/utils/menus.py @@ -106,7 +106,6 @@ async def menu( The time (in seconds) to wait for a reaction user: Optional[discord.User] The user allowed to interact with the menu. Defaults to ctx.author. - This is a keyword-only argument. Raises ------ From d13b17e9579d47768e542c1180c529e7288d04ce Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 17 Mar 2024 02:47:48 +0100 Subject: [PATCH 07/13] Add usage to existing code --- redbot/core/commands/help.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/redbot/core/commands/help.py b/redbot/core/commands/help.py index 66920780d59..f2e2899e4a6 100644 --- a/redbot/core/commands/help.py +++ b/redbot/core/commands/help.py @@ -878,14 +878,10 @@ async def send_pages( m = await (destination.send(embed=pages[0]) if embed else destination.send(pages[0])) c = menus.DEFAULT_CONTROLS if len(pages) > 1 else {"\N{CROSS MARK}": menus.close_menu} # Allow other things to happen during menu timeout/interaction. - if use_DMs: - menu_ctx = await ctx.bot.get_context(m) - # Monkeypatch so help listens for reactions from the original author, not the bot - menu_ctx.author = ctx.author - else: - menu_ctx = ctx asyncio.create_task( - menus.menu(menu_ctx, pages, c, message=m, timeout=help_settings.react_timeout) + menus.menu( + ctx, pages, c, user=ctx.author, message=m, timeout=help_settings.react_timeout + ) ) # menu needs reactions added manually since we fed it a message menus.start_adding_reactions(m, c.keys()) From 217dcca74349a1be41141076fd574f04e6d012ec Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 17 Mar 2024 23:13:42 +0100 Subject: [PATCH 08/13] black --- redbot/core/utils/menus.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/redbot/core/utils/menus.py b/redbot/core/utils/menus.py index 4be71f50e7c..e3fa1c9cdb5 100644 --- a/redbot/core/utils/menus.py +++ b/redbot/core/utils/menus.py @@ -209,7 +209,9 @@ async def menu( return try: - predicates = ReactionPredicate.with_emojis(tuple(controls.keys()), message, user or ctx.author) + predicates = ReactionPredicate.with_emojis( + tuple(controls.keys()), message, user or ctx.author + ) tasks = [ asyncio.create_task(ctx.bot.wait_for("reaction_add", check=predicates)), asyncio.create_task(ctx.bot.wait_for("reaction_remove", check=predicates)), From 3444250d156f6b86aa37d2f1e0dbae150a9939ed Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 17 Mar 2024 23:16:07 +0100 Subject: [PATCH 09/13] fix docs --- redbot/core/utils/menus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/redbot/core/utils/menus.py b/redbot/core/utils/menus.py index e3fa1c9cdb5..9e8acd2d00f 100644 --- a/redbot/core/utils/menus.py +++ b/redbot/core/utils/menus.py @@ -81,7 +81,7 @@ async def menu( This parameter should be the last one, and none of the parameters in the handling functions are optional - .. warning:: If you're using the `user` param, you need to pass it + .. warning:: If you're using the ``user`` param, you need to pass it as a keyword-only argument, and set :obj:`None` as the default in your function. @@ -105,7 +105,7 @@ async def menu( timeout: float The time (in seconds) to wait for a reaction user: Optional[discord.User] - The user allowed to interact with the menu. Defaults to ctx.author. + The user allowed to interact with the menu. Defaults to ``ctx.author``. Raises ------ From b440e45b8320a9e426a16acec76534945d4ae394 Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 17 Mar 2024 23:18:46 +0100 Subject: [PATCH 10/13] update var --- redbot/core/utils/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/redbot/core/utils/views.py b/redbot/core/utils/views.py index 68ae32e2fc7..60c05b5af87 100644 --- a/redbot/core/utils/views.py +++ b/redbot/core/utils/views.py @@ -256,6 +256,7 @@ async def start( Send the message ephemerally. This only works if the context is from a slash command interaction. """ + self._fallback_author_to_ctx = True if user is not None: self.author = user self.ctx = ctx From 26eb98042e4369a5fc29d57016b418d6013cc5ec Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Mon, 18 Mar 2024 00:54:09 +0100 Subject: [PATCH 11/13] Restructure menu() documentation --- redbot/core/utils/menus.py | 76 ++++++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 11 deletions(-) diff --git a/redbot/core/utils/menus.py b/redbot/core/utils/menus.py index 5fe30a32941..3490c0c152e 100644 --- a/redbot/core/utils/menus.py +++ b/redbot/core/utils/menus.py @@ -69,25 +69,79 @@ async def menu( """ An emoji-based menu - .. note:: All pages should be of the same type + All functions for handling what a particular emoji does + should be coroutines (i.e. :code:`async def`). Additionally, + they must take all of the parameters of this function, in + addition to a string representing the emoji reacted with. + This parameter should be the 7th one, and none of the + parameters in the handling functions are optional. - .. note:: All functions for handling what a particular emoji does - should be coroutines (i.e. :code:`async def`). Additionally, - they must take all of the parameters of this function, in - addition to a string representing the emoji reacted with. - This parameter should be the last one, and none of the - parameters in the handling functions are optional + .. warning:: - .. warning:: If you're using the ``user`` param, you need to pass it - as a keyword-only argument, and set :obj:`None` as the - default in your function. + If you're using the ``user`` param, you need to pass it + as a keyword-only argument, and set :obj:`None` as the + default in your function. + + Examples + -------- + + Simple menu using default controls:: + + from redbot.core.utils.menus import menu + + pages = ["Hello", "Hi", "Bonjour", "Salut"] + await menu(ctx, pages) + + Menu with a custom control performing an action (deleting an item from pages list):: + + from redbot.core.utils import menus + + items = ["Apple", "Banana", "Cucumber", "Dragonfruit"] + + def generate_pages(): + return [f"{fruit} is an awesome fruit!" for fruit in items] + + async def delete_item_action(ctx, pages, controls, message, page, timeout, emoji): + fruit = items.pop(page) # lookup and remove corresponding fruit name + await ctx.send(f"I guess you don't like {fruit}, huh? Deleting...") + pages = generate_pages() + if not pages: + return await menus.close_menu(ctx, pages, controls, message, page, timeout) + page = min(page, len(pages) - 1) + return await menus.menu(ctx, pages, controls, message, page, timeout) + + pages = generate_pages() + controls = {**menus.DEFAULT_CONTROLS, "\\N{NO ENTRY SIGN}": delete_item_action} + await menus.menu(ctx, pages, controls) + + Menu with custom controls that output a result (confirmation prompt):: + + from redbot.core.utils.menus import menu + + async def control_yes(*args, **kwargs): + return True + + async def control_no(*args, **kwargs): + return False + + msg = "Do you wish to continue?" + controls = { + "\\N{WHITE HEAVY CHECK MARK}": control_yes, + "\\N{CROSS MARK}": control_no, + } + reply = await menu(ctx, [msg], controls) + if reply: + await ctx.send("Continuing...") + else: + await ctx.send("Okay, I'm not going to perform the requested action.") Parameters ---------- ctx: commands.Context The command context - pages: `list` of `str` or `discord.Embed` + pages: Union[List[str], List[discord.Embed]] The pages of the menu. + All pages need to be of the same type (either `str` or `discord.Embed`). controls: Optional[Mapping[str, Callable]] A mapping of emoji to the function which handles the action for the emoji. The signature of the function should be the same as of this function From b15fc0e5862d5ffb73d34f39364eb2b8a783e3cc Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Mon, 18 Mar 2024 00:54:51 +0100 Subject: [PATCH 12/13] Add provisional API notes --- redbot/core/utils/menus.py | 12 ++++++++++++ redbot/core/utils/views.py | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/redbot/core/utils/menus.py b/redbot/core/utils/menus.py index 3490c0c152e..53af71d660b 100644 --- a/redbot/core/utils/menus.py +++ b/redbot/core/utils/menus.py @@ -76,6 +76,12 @@ async def menu( This parameter should be the 7th one, and none of the parameters in the handling functions are optional. + .. warning:: + + The ``user`` parameter is considered provisional. + If no issues arise, we plan on including it under developer guarantees + in the first release made after 2024-05-18. + .. warning:: If you're using the ``user`` param, you need to pass it @@ -158,6 +164,12 @@ async def control_no(*args, **kwargs): user: Optional[discord.User] The user allowed to interact with the menu. Defaults to ``ctx.author``. + .. warning:: + + This parameter is provisional. + If no issues arise, we plan on including it under developer guarantees + in the first release made after 2024-05-18. + Raises ------ RuntimeError diff --git a/redbot/core/utils/views.py b/redbot/core/utils/views.py index 60c05b5af87..245d0528a7d 100644 --- a/redbot/core/utils/views.py +++ b/redbot/core/utils/views.py @@ -245,6 +245,12 @@ async def start( """ Used to start the menu displaying the first page requested. + .. warning:: + + The ``user`` parameter is considered provisional. + If no issues arise, we plan on including it under developer guarantees + in the first release made after 2024-05-18. + Parameters ---------- ctx: `commands.Context` @@ -252,6 +258,12 @@ async def start( user: discord.User The user allowed to interact with the menu. If this is ``None``, ``ctx.author`` will be able to interact with the menu. + + .. warning:: + + This parameter is provisional. + If no issues arise, we plan on including it under developer guarantees + in the first release made after 2024-05-18. ephemeral: `bool` Send the message ephemerally. This only works if the context is from a slash command interaction. From 5c5e48f6afa20b1417eee295c3f962c5ad24c071 Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Mon, 18 Mar 2024 01:16:33 +0100 Subject: [PATCH 13/13] Cross-ref --- redbot/core/utils/menus.py | 4 ++-- redbot/core/utils/views.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/redbot/core/utils/menus.py b/redbot/core/utils/menus.py index 53af71d660b..d3a52dde78e 100644 --- a/redbot/core/utils/menus.py +++ b/redbot/core/utils/menus.py @@ -78,7 +78,7 @@ async def menu( .. warning:: - The ``user`` parameter is considered provisional. + The ``user`` parameter is considered `provisional `. If no issues arise, we plan on including it under developer guarantees in the first release made after 2024-05-18. @@ -166,7 +166,7 @@ async def control_no(*args, **kwargs): .. warning:: - This parameter is provisional. + This parameter is `provisional `. If no issues arise, we plan on including it under developer guarantees in the first release made after 2024-05-18. diff --git a/redbot/core/utils/views.py b/redbot/core/utils/views.py index 245d0528a7d..51fb9efa9d6 100644 --- a/redbot/core/utils/views.py +++ b/redbot/core/utils/views.py @@ -247,7 +247,7 @@ async def start( .. warning:: - The ``user`` parameter is considered provisional. + The ``user`` parameter is considered `provisional `. If no issues arise, we plan on including it under developer guarantees in the first release made after 2024-05-18. @@ -261,7 +261,7 @@ async def start( .. warning:: - This parameter is provisional. + This parameter is `provisional `. If no issues arise, we plan on including it under developer guarantees in the first release made after 2024-05-18. ephemeral: `bool`