mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-22 10:47:58 -05:00
[Utils] Finish and Refactor Predicate Utility (#2169)
* Uses classmethods to create predicates * Classmethods allow using a combination of different parameters to describe context * Some predicates assign a captured `result` to the predicate object on success * Added `ReactionPredicate` equivalent to `MessagePredicate` * Added `utils.menus.start_adding_reactions`, a non-blocking method for adding reactions asynchronously * Added documentation * Uses these new utils throughout the core bot Happened to also find some bugs in places, and places where we were waiting for events without catching `asyncio.TimeoutError` Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
"""
|
||||
Original source of reaction-based menu idea from
|
||||
https://github.com/Lunar-Dust/Dusty-Cogs/blob/master/menu/menu.py
|
||||
|
||||
Ported to Red V3 by Palm\_\_ (https://github.com/palmtree5)
|
||||
"""
|
||||
# Original source of reaction-based menu idea from
|
||||
# https://github.com/Lunar-Dust/Dusty-Cogs/blob/master/menu/menu.py
|
||||
#
|
||||
# Ported to Red V3 by Palm\_\_ (https://github.com/palmtree5)
|
||||
import asyncio
|
||||
import contextlib
|
||||
from typing import Union, Iterable
|
||||
from typing import Union, Iterable, Optional
|
||||
import discord
|
||||
|
||||
from redbot.core import commands
|
||||
from .. import commands
|
||||
from .predicates import ReactionPredicate
|
||||
|
||||
_ReactableEmoji = Union[str, discord.Emoji]
|
||||
|
||||
@@ -71,18 +70,20 @@ async def menu(
|
||||
else:
|
||||
message = await ctx.send(current_page)
|
||||
# Don't wait for reactions to be added (GH-1797)
|
||||
ctx.bot.loop.create_task(_add_menu_reactions(message, controls.keys()))
|
||||
# noinspection PyAsyncCall
|
||||
start_adding_reactions(message, controls.keys(), ctx.bot.loop)
|
||||
else:
|
||||
if isinstance(current_page, discord.Embed):
|
||||
await message.edit(embed=current_page)
|
||||
else:
|
||||
await message.edit(content=current_page)
|
||||
|
||||
def react_check(r, u):
|
||||
return u == ctx.author and r.message.id == message.id and str(r.emoji) in controls.keys()
|
||||
|
||||
try:
|
||||
react, user = await ctx.bot.wait_for("reaction_add", check=react_check, timeout=timeout)
|
||||
react, user = await ctx.bot.wait_for(
|
||||
"reaction_add",
|
||||
check=ReactionPredicate.with_emojis(tuple(controls.keys()), message, ctx.author),
|
||||
timeout=timeout,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
try:
|
||||
await message.clear_reactions()
|
||||
@@ -152,12 +153,51 @@ async def close_menu(
|
||||
return None
|
||||
|
||||
|
||||
async def _add_menu_reactions(message: discord.Message, emojis: Iterable[_ReactableEmoji]):
|
||||
"""Add the reactions"""
|
||||
# The task should exit silently if the message is deleted
|
||||
with contextlib.suppress(discord.NotFound):
|
||||
for emoji in emojis:
|
||||
await message.add_reaction(emoji)
|
||||
def start_adding_reactions(
|
||||
message: discord.Message,
|
||||
emojis: Iterable[_ReactableEmoji],
|
||||
loop: Optional[asyncio.AbstractEventLoop] = None,
|
||||
) -> asyncio.Task:
|
||||
"""Start adding reactions to a message.
|
||||
|
||||
This is a non-blocking operation - calling this will schedule the
|
||||
reactions being added, but will the calling code will continue to
|
||||
execute asynchronously. There is no need to await this function.
|
||||
|
||||
This is particularly useful if you wish to start waiting for a
|
||||
reaction whilst the reactions are still being added - in fact,
|
||||
this is exactly what `menu` uses to do that.
|
||||
|
||||
This spawns a `asyncio.Task` object and schedules it on ``loop``.
|
||||
If ``loop`` omitted, the loop will be retreived with
|
||||
`asyncio.get_event_loop`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message: discord.Message
|
||||
The message to add reactions to.
|
||||
emojis : Iterable[Union[str, discord.Emoji]]
|
||||
The emojis to react to the message with.
|
||||
loop : Optional[asyncio.AbstractEventLoop]
|
||||
The event loop.
|
||||
|
||||
Returns
|
||||
-------
|
||||
asyncio.Task
|
||||
The task for the coroutine adding the reactions.
|
||||
|
||||
"""
|
||||
|
||||
async def task():
|
||||
# The task should exit silently if the message is deleted
|
||||
with contextlib.suppress(discord.NotFound):
|
||||
for emoji in emojis:
|
||||
await message.add_reaction(emoji)
|
||||
|
||||
if loop is None:
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
return loop.create_task(task())
|
||||
|
||||
|
||||
DEFAULT_CONTROLS = {"⬅": prev_page, "❌": close_menu, "➡": next_page}
|
||||
|
||||
Reference in New Issue
Block a user