mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 03:08:55 -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:
parent
5d44bfabed
commit
dea9dde637
@ -22,12 +22,18 @@ Embed Helpers
|
|||||||
.. automodule:: redbot.core.utils.embed
|
.. automodule:: redbot.core.utils.embed
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
Menu Helpers
|
Reaction Menus
|
||||||
============
|
==============
|
||||||
|
|
||||||
.. automodule:: redbot.core.utils.menus
|
.. automodule:: redbot.core.utils.menus
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
Event Predicates
|
||||||
|
================
|
||||||
|
|
||||||
|
.. automodule:: redbot.core.utils.predicates
|
||||||
|
:members:
|
||||||
|
|
||||||
Mod Helpers
|
Mod Helpers
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
|||||||
@ -13,8 +13,16 @@ import time
|
|||||||
import redbot.core
|
import redbot.core
|
||||||
from redbot.core import Config, commands, checks, bank
|
from redbot.core import Config, commands, checks, bank
|
||||||
from redbot.core.data_manager import cog_data_path
|
from redbot.core.data_manager import cog_data_path
|
||||||
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS, prev_page, next_page, close_menu
|
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
|
from redbot.core.utils.menus import (
|
||||||
|
menu,
|
||||||
|
DEFAULT_CONTROLS,
|
||||||
|
prev_page,
|
||||||
|
next_page,
|
||||||
|
close_menu,
|
||||||
|
start_adding_reactions,
|
||||||
|
)
|
||||||
|
from redbot.core.utils.predicates import MessagePredicate, ReactionPredicate
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from .manager import shutdown_lavalink_server
|
from .manager import shutdown_lavalink_server
|
||||||
|
|
||||||
@ -225,22 +233,17 @@ class Audio(commands.Cog):
|
|||||||
async def dj(self, ctx):
|
async def dj(self, ctx):
|
||||||
"""Toggle DJ mode (users need a role to use audio commands)."""
|
"""Toggle DJ mode (users need a role to use audio commands)."""
|
||||||
dj_role_id = await self.config.guild(ctx.guild).dj_role()
|
dj_role_id = await self.config.guild(ctx.guild).dj_role()
|
||||||
if dj_role_id is None:
|
if dj_role_id is None and ctx.guild.get_role(dj_role_id):
|
||||||
await self._embed_msg(
|
await self._embed_msg(
|
||||||
ctx, "Please set a role to use with DJ mode. Enter the role name now."
|
ctx, "Please set a role to use with DJ mode. Enter the role name or ID now."
|
||||||
)
|
)
|
||||||
|
|
||||||
def check(m):
|
|
||||||
return m.author == ctx.author
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dj_role = await ctx.bot.wait_for("message", timeout=15.0, check=check)
|
pred = MessagePredicate.valid_role(ctx)
|
||||||
dj_role_obj = discord.utils.get(ctx.guild.roles, name=dj_role.content)
|
await ctx.bot.wait_for("message", timeout=15.0, check=pred)
|
||||||
if dj_role_obj is None:
|
await ctx.invoke(self.role, pred.result)
|
||||||
return await self._embed_msg(ctx, "No role with that name.")
|
|
||||||
await ctx.invoke(self.role, dj_role_obj)
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return await self._embed_msg(ctx, "No role entered, try again later.")
|
return await self._embed_msg(ctx, "Response timed out, try again later.")
|
||||||
|
|
||||||
dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
|
dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
|
||||||
await self.config.guild(ctx.guild).dj_enabled.set(not dj_enabled)
|
await self.config.guild(ctx.guild).dj_enabled.set(not dj_enabled)
|
||||||
@ -710,20 +713,21 @@ class Audio(commands.Cog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if player.current:
|
if player.current:
|
||||||
for i in range(4):
|
task = start_adding_reactions(message, expected[:4], ctx.bot.loop)
|
||||||
await message.add_reaction(expected[i])
|
else:
|
||||||
|
task = None
|
||||||
def check(r, u):
|
|
||||||
return (
|
|
||||||
r.message.id == message.id
|
|
||||||
and u == ctx.message.author
|
|
||||||
and any(e in str(r.emoji) for e in expected)
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
(r, u) = await self.bot.wait_for("reaction_add", check=check, timeout=10.0)
|
(r, u) = await self.bot.wait_for(
|
||||||
|
"reaction_add",
|
||||||
|
check=ReactionPredicate.with_emojis(expected, message, ctx.author),
|
||||||
|
timeout=10.0,
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return await self._clear_react(message)
|
return await self._clear_react(message)
|
||||||
|
else:
|
||||||
|
if task is not None:
|
||||||
|
task.cancel()
|
||||||
reacts = {v: k for k, v in emoji.items()}
|
reacts = {v: k for k, v in emoji.items()}
|
||||||
react = reacts[r.emoji]
|
react = reacts[r.emoji]
|
||||||
if react == "prev":
|
if react == "prev":
|
||||||
@ -1125,11 +1129,12 @@ class Audio(commands.Cog):
|
|||||||
if not playlist_name:
|
if not playlist_name:
|
||||||
await self._embed_msg(ctx, "Please enter a name for this playlist.")
|
await self._embed_msg(ctx, "Please enter a name for this playlist.")
|
||||||
|
|
||||||
def check(m):
|
|
||||||
return m.author == ctx.author and not m.content.startswith(ctx.prefix)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
playlist_name_msg = await ctx.bot.wait_for("message", timeout=15.0, check=check)
|
playlist_name_msg = await ctx.bot.wait_for(
|
||||||
|
"message",
|
||||||
|
timeout=15.0,
|
||||||
|
check=MessagePredicate.regex(fr"^(?!{ctx.prefix})", ctx),
|
||||||
|
)
|
||||||
playlist_name = playlist_name_msg.content.split(" ")[0].strip('"')
|
playlist_name = playlist_name_msg.content.split(" ")[0].strip('"')
|
||||||
if len(playlist_name) > 20:
|
if len(playlist_name) > 20:
|
||||||
return await self._embed_msg(ctx, "Try the command again with a shorter name.")
|
return await self._embed_msg(ctx, "Try the command again with a shorter name.")
|
||||||
@ -1238,11 +1243,10 @@ class Audio(commands.Cog):
|
|||||||
ctx, "Please upload the playlist file. Any other message will cancel this operation."
|
ctx, "Please upload the playlist file. Any other message will cancel this operation."
|
||||||
)
|
)
|
||||||
|
|
||||||
def check(m):
|
|
||||||
return m.author == ctx.author
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
file_message = await ctx.bot.wait_for("message", timeout=30.0, check=check)
|
file_message = await ctx.bot.wait_for(
|
||||||
|
"message", timeout=30.0, check=MessagePredicate.same_context(ctx)
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return await self._embed_msg(ctx, "No file detected, try again later.")
|
return await self._embed_msg(ctx, "No file detected, try again later.")
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -9,6 +9,7 @@ from redbot.core.bot import Red
|
|||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
from redbot.core.utils.mod import slow_deletion, mass_purge
|
from redbot.core.utils.mod import slow_deletion, mass_purge
|
||||||
from redbot.cogs.mod.log import log
|
from redbot.cogs.mod.log import log
|
||||||
|
from redbot.core.utils.predicates import MessagePredicate
|
||||||
|
|
||||||
_ = Translator("Cleanup", __file__)
|
_ = Translator("Cleanup", __file__)
|
||||||
|
|
||||||
@ -31,13 +32,10 @@ class Cleanup(commands.Cog):
|
|||||||
Tries its best to cleanup after itself if the response is positive.
|
Tries its best to cleanup after itself if the response is positive.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def author_check(message):
|
|
||||||
return message.author == ctx.author
|
|
||||||
|
|
||||||
prompt = await ctx.send(
|
prompt = await ctx.send(
|
||||||
_("Are you sure you want to delete {} messages? (y/n)").format(number)
|
_("Are you sure you want to delete {} messages? (y/n)").format(number)
|
||||||
)
|
)
|
||||||
response = await ctx.bot.wait_for("message", check=author_check)
|
response = await ctx.bot.wait_for("message", check=MessagePredicate.same_context(ctx))
|
||||||
|
|
||||||
if response.content.lower().startswith("y"):
|
if response.content.lower().startswith("y"):
|
||||||
await prompt.delete()
|
await prompt.delete()
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import discord
|
|||||||
from redbot.core import Config, checks, commands
|
from redbot.core import Config, checks, commands
|
||||||
from redbot.core.utils.chat_formatting import box, pagify
|
from redbot.core.utils.chat_formatting import box, pagify
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
|
from redbot.core.utils.predicates import MessagePredicate
|
||||||
|
|
||||||
_ = Translator("CustomCommands", __file__)
|
_ = Translator("CustomCommands", __file__)
|
||||||
|
|
||||||
@ -58,14 +59,11 @@ class CommandObj:
|
|||||||
).format("customcommand", "customcommand", "exit()")
|
).format("customcommand", "customcommand", "exit()")
|
||||||
await ctx.send(intro)
|
await ctx.send(intro)
|
||||||
|
|
||||||
def check(m):
|
|
||||||
return m.channel == ctx.channel and m.author == ctx.message.author
|
|
||||||
|
|
||||||
responses = []
|
responses = []
|
||||||
args = None
|
args = None
|
||||||
while True:
|
while True:
|
||||||
await ctx.send(_("Add a random response:"))
|
await ctx.send(_("Add a random response:"))
|
||||||
msg = await self.bot.wait_for("message", check=check)
|
msg = await self.bot.wait_for("message", check=MessagePredicate.same_context(ctx))
|
||||||
|
|
||||||
if msg.content.lower() == "exit()":
|
if msg.content.lower() == "exit()":
|
||||||
break
|
break
|
||||||
@ -130,18 +128,27 @@ class CommandObj:
|
|||||||
|
|
||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
|
|
||||||
def check(m):
|
|
||||||
return m.channel == ctx.channel and m.author == ctx.author
|
|
||||||
|
|
||||||
if ask_for and not response:
|
if ask_for and not response:
|
||||||
await ctx.send(_("Do you want to create a 'randomized' cc? {}").format("y/n"))
|
await ctx.send(_("Do you want to create a 'randomized' custom command? (y/n)"))
|
||||||
|
|
||||||
msg = await self.bot.wait_for("message", check=check)
|
pred = MessagePredicate.yes_or_no(ctx)
|
||||||
if msg.content.lower() == "y":
|
try:
|
||||||
|
await self.bot.wait_for("message", check=pred, timeout=30)
|
||||||
|
except TimeoutError:
|
||||||
|
await ctx.send(_("Response timed out, please try again later."))
|
||||||
|
return
|
||||||
|
if pred.result is True:
|
||||||
response = await self.get_responses(ctx=ctx)
|
response = await self.get_responses(ctx=ctx)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("What response do you want?"))
|
await ctx.send(_("What response do you want?"))
|
||||||
response = (await self.bot.wait_for("message", check=check)).content
|
try:
|
||||||
|
resp = await self.bot.wait_for(
|
||||||
|
"message", check=MessagePredicate.same_context(ctx), timeout=180
|
||||||
|
)
|
||||||
|
except TimeoutError:
|
||||||
|
await ctx.send(_("Response timed out, please try again later."))
|
||||||
|
return
|
||||||
|
response = resp.content
|
||||||
|
|
||||||
if response:
|
if response:
|
||||||
# test to raise
|
# test to raise
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from redbot.core.bot import Red
|
|||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
from redbot.cogs.dataconverter.core_specs import SpecResolver
|
from redbot.cogs.dataconverter.core_specs import SpecResolver
|
||||||
from redbot.core.utils.chat_formatting import box
|
from redbot.core.utils.chat_formatting import box
|
||||||
|
from redbot.core.utils.predicates import MessagePredicate
|
||||||
|
|
||||||
_ = Translator("DataConverter", __file__)
|
_ = Translator("DataConverter", __file__)
|
||||||
|
|
||||||
@ -48,11 +49,10 @@ class DataConverter(commands.Cog):
|
|||||||
|
|
||||||
menu_message = await ctx.send(box(menu))
|
menu_message = await ctx.send(box(menu))
|
||||||
|
|
||||||
def pred(m):
|
|
||||||
return m.channel == ctx.channel and m.author == ctx.author
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = await self.bot.wait_for("message", check=pred, timeout=60)
|
message = await self.bot.wait_for(
|
||||||
|
"message", check=MessagePredicate.same_context(ctx), timeout=60
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return await ctx.send(_("Try this again when you are more ready"))
|
return await ctx.send(_("Try this again when you are more ready"))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
import discord
|
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
from redbot.core.utils.predicates import MessagePredicate
|
||||||
|
|
||||||
__all__ = ["do_install_agreement"]
|
__all__ = ["do_install_agreement"]
|
||||||
|
|
||||||
@ -21,13 +21,12 @@ async def do_install_agreement(ctx: commands.Context):
|
|||||||
if downloader is None or downloader.already_agreed:
|
if downloader is None or downloader.already_agreed:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def does_agree(msg: discord.Message):
|
|
||||||
return ctx.author == msg.author and ctx.channel == msg.channel and msg.content == "I agree"
|
|
||||||
|
|
||||||
await ctx.send(REPO_INSTALL_MSG)
|
await ctx.send(REPO_INSTALL_MSG)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await ctx.bot.wait_for("message", check=does_agree, timeout=30)
|
await ctx.bot.wait_for(
|
||||||
|
"message", check=MessagePredicate.lower_equal_to("i agree", ctx), timeout=30
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await ctx.send("Your response has timed out, please try again.")
|
await ctx.send("Your response has timed out, please try again.")
|
||||||
return False
|
return False
|
||||||
|
|||||||
@ -11,6 +11,8 @@ from redbot.core import checks, commands, config
|
|||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
from redbot.core.utils.chat_formatting import box
|
from redbot.core.utils.chat_formatting import box
|
||||||
|
from redbot.core.utils.menus import start_adding_reactions
|
||||||
|
from redbot.core.utils.predicates import ReactionPredicate, MessagePredicate
|
||||||
|
|
||||||
from .converters import CogOrCommand, RuleType, ClearableRuleType
|
from .converters import CogOrCommand, RuleType, ClearableRuleType
|
||||||
|
|
||||||
@ -20,9 +22,6 @@ COG = "COG"
|
|||||||
COMMAND = "COMMAND"
|
COMMAND = "COMMAND"
|
||||||
GLOBAL = 0
|
GLOBAL = 0
|
||||||
|
|
||||||
# noinspection PyDictDuplicateKeys
|
|
||||||
REACTS = {"\N{WHITE HEAVY CHECK MARK}": True, "\N{NEGATIVE SQUARED CROSS MARK}": False}
|
|
||||||
Y_OR_N = {"y": True, "yes": True, "n": False, "no": False}
|
|
||||||
# The strings in the schema are constants and should get extracted, but not translated until
|
# The strings in the schema are constants and should get extracted, but not translated until
|
||||||
# runtime.
|
# runtime.
|
||||||
translate = _
|
translate = _
|
||||||
@ -566,35 +565,29 @@ class Permissions(commands.Cog):
|
|||||||
"""Ask "Are you sure?" and get the response as a bool."""
|
"""Ask "Are you sure?" and get the response as a bool."""
|
||||||
if ctx.guild is None or ctx.guild.me.permissions_in(ctx.channel).add_reactions:
|
if ctx.guild is None or ctx.guild.me.permissions_in(ctx.channel).add_reactions:
|
||||||
msg = await ctx.send(_("Are you sure?"))
|
msg = await ctx.send(_("Are you sure?"))
|
||||||
for emoji in REACTS.keys():
|
# noinspection PyAsyncCall
|
||||||
await msg.add_reaction(emoji)
|
task = start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS, ctx.bot.loop)
|
||||||
|
pred = ReactionPredicate.yes_or_no(msg, ctx.author)
|
||||||
try:
|
try:
|
||||||
reaction, user = await ctx.bot.wait_for(
|
await ctx.bot.wait_for("reaction_add", check=pred, timeout=30)
|
||||||
"reaction_add",
|
|
||||||
check=lambda r, u: (
|
|
||||||
r.message.id == msg.id and u == ctx.author and r.emoji in REACTS
|
|
||||||
),
|
|
||||||
timeout=30,
|
|
||||||
)
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
agreed = False
|
await ctx.send(_("Response timed out."))
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
agreed = REACTS.get(reaction.emoji)
|
task.cancel()
|
||||||
|
agreed = pred.result
|
||||||
|
finally:
|
||||||
await msg.delete()
|
await msg.delete()
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Are you sure? (y/n)"))
|
await ctx.send(_("Are you sure? (y/n)"))
|
||||||
|
pred = MessagePredicate.yes_or_no(ctx)
|
||||||
try:
|
try:
|
||||||
message = await ctx.bot.wait_for(
|
await ctx.bot.wait_for("message", check=pred, timeout=30)
|
||||||
"message",
|
|
||||||
check=lambda m: m.author == ctx.author
|
|
||||||
and m.channel == ctx.channel
|
|
||||||
and m.content in Y_OR_N,
|
|
||||||
timeout=30,
|
|
||||||
)
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
agreed = False
|
await ctx.send(_("Response timed out."))
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
agreed = Y_OR_N.get(message.content.lower())
|
agreed = pred.result
|
||||||
|
|
||||||
if agreed is False:
|
if agreed is False:
|
||||||
await ctx.send(_("Action cancelled."))
|
await ctx.send(_("Action cancelled."))
|
||||||
|
|||||||
@ -11,6 +11,7 @@ from redbot.core.utils.chat_formatting import pagify, box
|
|||||||
from redbot.core.utils.antispam import AntiSpam
|
from redbot.core.utils.antispam import AntiSpam
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
|
from redbot.core.utils.predicates import MessagePredicate
|
||||||
from redbot.core.utils.tunnel import Tunnel
|
from redbot.core.utils.tunnel import Tunnel
|
||||||
|
|
||||||
|
|
||||||
@ -136,13 +137,14 @@ class Reports(commands.Cog):
|
|||||||
output += "\n{}".format(prompt)
|
output += "\n{}".format(prompt)
|
||||||
|
|
||||||
for page in pagify(output, delims=["\n"]):
|
for page in pagify(output, delims=["\n"]):
|
||||||
dm = await author.send(box(page))
|
await author.send(box(page))
|
||||||
|
|
||||||
def pred(m):
|
|
||||||
return m.author == author and m.channel == dm.channel
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = await self.bot.wait_for("message", check=pred, timeout=45)
|
message = await self.bot.wait_for(
|
||||||
|
"message",
|
||||||
|
check=MessagePredicate.same_context(channel=author.dm_channel, user=author),
|
||||||
|
timeout=45,
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await author.send(_("You took too long to select. Try again later."))
|
await author.send(_("You took too long to select. Try again later."))
|
||||||
return None
|
return None
|
||||||
@ -247,7 +249,7 @@ class Reports(commands.Cog):
|
|||||||
val = await self.send_report(_m, guild)
|
val = await self.send_report(_m, guild)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
dm = await author.send(
|
await author.send(
|
||||||
_(
|
_(
|
||||||
"Please respond to this message with your Report."
|
"Please respond to this message with your Report."
|
||||||
"\nYour report should be a single message"
|
"\nYour report should be a single message"
|
||||||
@ -256,11 +258,12 @@ class Reports(commands.Cog):
|
|||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
return await ctx.send(_("This requires DMs enabled."))
|
return await ctx.send(_("This requires DMs enabled."))
|
||||||
|
|
||||||
def pred(m):
|
|
||||||
return m.author == author and m.channel == dm.channel
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = await self.bot.wait_for("message", check=pred, timeout=180)
|
message = await self.bot.wait_for(
|
||||||
|
"message",
|
||||||
|
check=MessagePredicate.same_context(ctx, channel=author.dm_channel),
|
||||||
|
timeout=180,
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return await author.send(_("You took too long. Try again later."))
|
return await author.send(_("You took too long. Try again later."))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import discord
|
|||||||
|
|
||||||
from redbot.core import Config, checks, commands
|
from redbot.core import Config, checks, commands
|
||||||
from redbot.core.i18n import Translator
|
from redbot.core.i18n import Translator
|
||||||
|
from redbot.core.utils.predicates import MessagePredicate
|
||||||
|
|
||||||
_ = Translator("Warnings", __file__)
|
_ = Translator("Warnings", __file__)
|
||||||
|
|
||||||
@ -95,11 +96,10 @@ async def get_command_for_exceeded_points(ctx: commands.Context):
|
|||||||
|
|
||||||
await ctx.send(_("You may enter your response now."))
|
await ctx.send(_("You may enter your response now."))
|
||||||
|
|
||||||
def same_author_check(m):
|
|
||||||
return m.author == ctx.author
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
msg = await ctx.bot.wait_for("message", check=same_author_check, timeout=30)
|
msg = await ctx.bot.wait_for(
|
||||||
|
"message", check=MessagePredicate.same_context(ctx), timeout=30
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
@ -140,11 +140,10 @@ async def get_command_for_dropping_points(ctx: commands.Context):
|
|||||||
|
|
||||||
await ctx.send(_("You may enter your response now."))
|
await ctx.send(_("You may enter your response now."))
|
||||||
|
|
||||||
def same_author_check(m):
|
|
||||||
return m.author == ctx.author
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
msg = await ctx.bot.wait_for("message", check=same_author_check, timeout=30)
|
msg = await ctx.bot.wait_for(
|
||||||
|
"message", check=MessagePredicate.same_context(ctx), timeout=30
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -15,6 +15,7 @@ from redbot.core.i18n import Translator, cog_i18n
|
|||||||
from redbot.core.utils.mod import is_admin_or_superior
|
from redbot.core.utils.mod import is_admin_or_superior
|
||||||
from redbot.core.utils.chat_formatting import warning, pagify
|
from redbot.core.utils.chat_formatting import warning, pagify
|
||||||
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
|
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
|
||||||
|
from redbot.core.utils.predicates import MessagePredicate
|
||||||
|
|
||||||
_ = Translator("Warnings", __file__)
|
_ = Translator("Warnings", __file__)
|
||||||
|
|
||||||
@ -363,12 +364,11 @@ class Warnings(commands.Cog):
|
|||||||
"""Handles getting description and points for custom reasons"""
|
"""Handles getting description and points for custom reasons"""
|
||||||
to_add = {"points": 0, "description": ""}
|
to_add = {"points": 0, "description": ""}
|
||||||
|
|
||||||
def same_author_check(m):
|
|
||||||
return m.author == ctx.author
|
|
||||||
|
|
||||||
await ctx.send(_("How many points should be given for this reason?"))
|
await ctx.send(_("How many points should be given for this reason?"))
|
||||||
try:
|
try:
|
||||||
msg = await ctx.bot.wait_for("message", check=same_author_check, timeout=30)
|
msg = await ctx.bot.wait_for(
|
||||||
|
"message", check=MessagePredicate.same_context(ctx), timeout=30
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await ctx.send(_("Ok then."))
|
await ctx.send(_("Ok then."))
|
||||||
return
|
return
|
||||||
@ -385,7 +385,9 @@ class Warnings(commands.Cog):
|
|||||||
|
|
||||||
await ctx.send(_("Enter a description for this reason."))
|
await ctx.send(_("Enter a description for this reason."))
|
||||||
try:
|
try:
|
||||||
msg = await ctx.bot.wait_for("message", check=same_author_check, timeout=30)
|
msg = await ctx.bot.wait_for(
|
||||||
|
"message", check=MessagePredicate.same_context(ctx), timeout=30
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await ctx.send(_("Ok then."))
|
await ctx.send(_("Ok then."))
|
||||||
return
|
return
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from discord.ext import commands
|
|||||||
|
|
||||||
from .requires import PermState
|
from .requires import PermState
|
||||||
from ..utils.chat_formatting import box
|
from ..utils.chat_formatting import box
|
||||||
|
from ..utils.predicates import MessagePredicate
|
||||||
from ..utils import common_filters
|
from ..utils import common_filters
|
||||||
|
|
||||||
TICK = "\N{WHITE HEAVY CHECK MARK}"
|
TICK = "\N{WHITE HEAVY CHECK MARK}"
|
||||||
@ -141,10 +142,6 @@ class Context(commands.Context):
|
|||||||
messages = tuple(messages)
|
messages = tuple(messages)
|
||||||
ret = []
|
ret = []
|
||||||
|
|
||||||
more_check = lambda m: (
|
|
||||||
m.author == self.author and m.channel == self.channel and m.content.lower() == "more"
|
|
||||||
)
|
|
||||||
|
|
||||||
for idx, page in enumerate(messages, 1):
|
for idx, page in enumerate(messages, 1):
|
||||||
if box_lang is None:
|
if box_lang is None:
|
||||||
msg = await self.send(page)
|
msg = await self.send(page)
|
||||||
@ -165,7 +162,11 @@ class Context(commands.Context):
|
|||||||
"".format(is_are, n_remaining, plural)
|
"".format(is_are, n_remaining, plural)
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
resp = await self.bot.wait_for("message", check=more_check, timeout=timeout)
|
resp = await self.bot.wait_for(
|
||||||
|
"message",
|
||||||
|
check=MessagePredicate.lower_equal_to("more", self),
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await query.delete()
|
await query.delete()
|
||||||
break
|
break
|
||||||
@ -175,7 +176,7 @@ class Context(commands.Context):
|
|||||||
except (discord.HTTPException, AttributeError):
|
except (discord.HTTPException, AttributeError):
|
||||||
# In case the bot can't delete other users' messages,
|
# In case the bot can't delete other users' messages,
|
||||||
# or is not a bot account
|
# or is not a bot account
|
||||||
# or chanel is a DM
|
# or channel is a DM
|
||||||
await query.delete()
|
await query.delete()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ from redbot.core import __version__
|
|||||||
from redbot.core import checks
|
from redbot.core import checks
|
||||||
from redbot.core import i18n
|
from redbot.core import i18n
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
from .utils.predicates import MessagePredicate
|
||||||
from .utils.chat_formatting import pagify, box, inline
|
from .utils.chat_formatting import pagify, box, inline
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -438,73 +439,63 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def leave(self, ctx):
|
async def leave(self, ctx):
|
||||||
"""Leaves server"""
|
"""Leaves server"""
|
||||||
author = ctx.author
|
await ctx.send("Are you sure you want me to leave this server? (y/n)")
|
||||||
guild = ctx.guild
|
|
||||||
|
|
||||||
await ctx.send("Are you sure you want me to leave this server? Type yes to confirm.")
|
pred = MessagePredicate.yes_or_no(ctx)
|
||||||
|
try:
|
||||||
def conf_check(m):
|
await self.bot.wait_for("message", check=MessagePredicate.yes_or_no(ctx))
|
||||||
return m.author == author
|
except asyncio.TimeoutError:
|
||||||
|
await ctx.send("Response timed out.")
|
||||||
response = await self.bot.wait_for("message", check=conf_check)
|
return
|
||||||
|
else:
|
||||||
if response.content.lower().strip() == "yes":
|
if pred.result is True:
|
||||||
await ctx.send("Alright. Bye :wave:")
|
await ctx.send("Alright. Bye :wave:")
|
||||||
log.debug("Leaving '{}'".format(guild.name))
|
log.debug("Leaving guild '{}'".format(ctx.guild.name))
|
||||||
await guild.leave()
|
await ctx.guild.leave()
|
||||||
|
else:
|
||||||
|
await ctx.send("Alright, I'll stay then :)")
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def servers(self, ctx):
|
async def servers(self, ctx):
|
||||||
"""Lists and allows to leave servers"""
|
"""Lists and allows to leave servers"""
|
||||||
owner = ctx.author
|
|
||||||
guilds = sorted(list(self.bot.guilds), key=lambda s: s.name.lower())
|
guilds = sorted(list(self.bot.guilds), key=lambda s: s.name.lower())
|
||||||
msg = ""
|
msg = ""
|
||||||
|
responses = []
|
||||||
for i, server in enumerate(guilds, 1):
|
for i, server in enumerate(guilds, 1):
|
||||||
msg += "{}: {}\n".format(i, server.name)
|
msg += "{}: {}\n".format(i, server.name)
|
||||||
|
responses.append(str(i))
|
||||||
msg += "\nTo leave a server, just type its number."
|
|
||||||
|
|
||||||
for page in pagify(msg, ["\n"]):
|
for page in pagify(msg, ["\n"]):
|
||||||
await ctx.send(page)
|
await ctx.send(page)
|
||||||
|
|
||||||
def msg_check(m):
|
query = await ctx.send("To leave a server, just type its number.")
|
||||||
return m.author == owner
|
|
||||||
|
|
||||||
while msg is not None:
|
|
||||||
try:
|
|
||||||
msg = await self.bot.wait_for("message", check=msg_check, timeout=15)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
await ctx.send("I guess not.")
|
|
||||||
break
|
|
||||||
try:
|
|
||||||
msg = int(msg.content) - 1
|
|
||||||
if msg < 0:
|
|
||||||
break
|
|
||||||
await self.leave_confirmation(guilds[msg], owner, ctx)
|
|
||||||
break
|
|
||||||
except (IndexError, ValueError, AttributeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def leave_confirmation(self, server, owner, ctx):
|
|
||||||
await ctx.send("Are you sure you want me to leave {}? (yes/no)".format(server.name))
|
|
||||||
|
|
||||||
def conf_check(m):
|
|
||||||
return m.author == owner
|
|
||||||
|
|
||||||
|
pred = MessagePredicate.contained_in(responses, ctx)
|
||||||
try:
|
try:
|
||||||
msg = await self.bot.wait_for("message", check=conf_check, timeout=15)
|
await self.bot.wait_for("message", check=pred, timeout=15)
|
||||||
if msg.content.lower().strip() in ("yes", "y"):
|
except asyncio.TimeoutError:
|
||||||
if server.owner == ctx.bot.user:
|
await query.delete()
|
||||||
await ctx.send("I cannot leave a guild I am the owner of.")
|
else:
|
||||||
return
|
await self.leave_confirmation(guilds[pred.result], ctx)
|
||||||
await server.leave()
|
|
||||||
if server != ctx.guild:
|
async def leave_confirmation(self, guild, ctx):
|
||||||
|
if guild.owner.id == ctx.bot.user.id:
|
||||||
|
await ctx.send("I cannot leave a guild I am the owner of.")
|
||||||
|
return
|
||||||
|
|
||||||
|
await ctx.send("Are you sure you want me to leave {}? (yes/no)".format(guild.name))
|
||||||
|
pred = MessagePredicate.yes_or_no(ctx)
|
||||||
|
try:
|
||||||
|
await self.bot.wait_for("message", check=pred, timeout=15)
|
||||||
|
if pred.result is True:
|
||||||
|
await guild.leave()
|
||||||
|
if guild != ctx.guild:
|
||||||
await ctx.send("Done.")
|
await ctx.send("Done.")
|
||||||
else:
|
else:
|
||||||
await ctx.send("Alright then.")
|
await ctx.send("Alright then.")
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await ctx.send("I guess not.")
|
await ctx.send("Response timed out.")
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
@ -892,10 +883,6 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@commands.cooldown(1, 60 * 10, commands.BucketType.default)
|
@commands.cooldown(1, 60 * 10, commands.BucketType.default)
|
||||||
async def owner(self, ctx):
|
async def owner(self, ctx):
|
||||||
"""Sets Red's main owner"""
|
"""Sets Red's main owner"""
|
||||||
|
|
||||||
def check(m):
|
|
||||||
return m.author == ctx.author and m.channel == ctx.channel
|
|
||||||
|
|
||||||
# According to the Python docs this is suitable for cryptographic use
|
# According to the Python docs this is suitable for cryptographic use
|
||||||
random = SystemRandom()
|
random = SystemRandom()
|
||||||
length = random.randint(25, 35)
|
length = random.randint(25, 35)
|
||||||
@ -919,10 +906,14 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = await ctx.bot.wait_for("message", check=check, timeout=60)
|
message = await ctx.bot.wait_for(
|
||||||
|
"message", check=MessagePredicate.same_context(ctx), timeout=60
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
self.owner.reset_cooldown(ctx)
|
self.owner.reset_cooldown(ctx)
|
||||||
await ctx.send(_("The set owner request has timed out."))
|
await ctx.send(
|
||||||
|
_("The `{prefix}set owner` request has timed out.").format(prefix=ctx.prefix)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if message.content.strip() == token:
|
if message.content.strip() == token:
|
||||||
self.owner.reset_cooldown(ctx)
|
self.owner.reset_cooldown(ctx)
|
||||||
@ -1146,18 +1137,20 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
)
|
)
|
||||||
await ctx.send(_("Would you like to receive a copy via DM? (y/n)"))
|
await ctx.send(_("Would you like to receive a copy via DM? (y/n)"))
|
||||||
|
|
||||||
def same_author_check(m):
|
pred = MessagePredicate.yes_or_no(ctx)
|
||||||
return m.author == ctx.author and m.channel == ctx.channel
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
msg = await ctx.bot.wait_for("message", check=same_author_check, timeout=60)
|
await ctx.bot.wait_for("message", check=pred, timeout=60)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await ctx.send(_("Ok then."))
|
await ctx.send(_("Response timed out."))
|
||||||
else:
|
else:
|
||||||
if msg.content.lower().strip() == "y":
|
if pred.result is True:
|
||||||
await ctx.author.send(
|
await ctx.send(_("OK, it's on its way!"))
|
||||||
_("Here's a copy of the backup"), file=discord.File(str(backup_file))
|
async with ctx.author.typing():
|
||||||
)
|
await ctx.author.send(
|
||||||
|
_("Here's a copy of the backup"), file=discord.File(str(backup_file))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await ctx.send(_("OK then."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("That directory doesn't seem to exist..."))
|
await ctx.send(_("That directory doesn't seem to exist..."))
|
||||||
|
|
||||||
|
|||||||
@ -8,9 +8,11 @@ from contextlib import redirect_stdout
|
|||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
from . import checks, commands
|
from . import checks, commands
|
||||||
from .i18n import Translator
|
from .i18n import Translator
|
||||||
from .utils.chat_formatting import box, pagify
|
from .utils.chat_formatting import box, pagify
|
||||||
|
from .utils.predicates import MessagePredicate
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Notice:
|
Notice:
|
||||||
@ -218,12 +220,8 @@ class Dev(commands.Cog):
|
|||||||
self.sessions.add(ctx.channel.id)
|
self.sessions.add(ctx.channel.id)
|
||||||
await ctx.send(_("Enter code to execute or evaluate. `exit()` or `quit` to exit."))
|
await ctx.send(_("Enter code to execute or evaluate. `exit()` or `quit` to exit."))
|
||||||
|
|
||||||
msg_check = lambda m: (
|
|
||||||
m.author == ctx.author and m.channel == ctx.channel and m.content.startswith("`")
|
|
||||||
)
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
response = await ctx.bot.wait_for("message", check=msg_check)
|
response = await ctx.bot.wait_for("message", check=MessagePredicate.regex(r"^`", ctx))
|
||||||
|
|
||||||
cleaned = self.cleanup_code(response.content)
|
cleaned = self.cleanup_code(response.content)
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
"""
|
# Original source of reaction-based menu idea from
|
||||||
Original source of reaction-based menu idea from
|
# https://github.com/Lunar-Dust/Dusty-Cogs/blob/master/menu/menu.py
|
||||||
https://github.com/Lunar-Dust/Dusty-Cogs/blob/master/menu/menu.py
|
#
|
||||||
|
# Ported to Red V3 by Palm\_\_ (https://github.com/palmtree5)
|
||||||
Ported to Red V3 by Palm\_\_ (https://github.com/palmtree5)
|
|
||||||
"""
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import contextlib
|
import contextlib
|
||||||
from typing import Union, Iterable
|
from typing import Union, Iterable, Optional
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
from redbot.core import commands
|
from .. import commands
|
||||||
|
from .predicates import ReactionPredicate
|
||||||
|
|
||||||
_ReactableEmoji = Union[str, discord.Emoji]
|
_ReactableEmoji = Union[str, discord.Emoji]
|
||||||
|
|
||||||
@ -71,18 +70,20 @@ async def menu(
|
|||||||
else:
|
else:
|
||||||
message = await ctx.send(current_page)
|
message = await ctx.send(current_page)
|
||||||
# Don't wait for reactions to be added (GH-1797)
|
# 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:
|
else:
|
||||||
if isinstance(current_page, discord.Embed):
|
if isinstance(current_page, discord.Embed):
|
||||||
await message.edit(embed=current_page)
|
await message.edit(embed=current_page)
|
||||||
else:
|
else:
|
||||||
await message.edit(content=current_page)
|
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:
|
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:
|
except asyncio.TimeoutError:
|
||||||
try:
|
try:
|
||||||
await message.clear_reactions()
|
await message.clear_reactions()
|
||||||
@ -152,12 +153,51 @@ async def close_menu(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def _add_menu_reactions(message: discord.Message, emojis: Iterable[_ReactableEmoji]):
|
def start_adding_reactions(
|
||||||
"""Add the reactions"""
|
message: discord.Message,
|
||||||
# The task should exit silently if the message is deleted
|
emojis: Iterable[_ReactableEmoji],
|
||||||
with contextlib.suppress(discord.NotFound):
|
loop: Optional[asyncio.AbstractEventLoop] = None,
|
||||||
for emoji in emojis:
|
) -> asyncio.Task:
|
||||||
await message.add_reaction(emoji)
|
"""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}
|
DEFAULT_CONTROLS = {"⬅": prev_page, "❌": close_menu, "➡": next_page}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user