mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2026-01-20 23:22:58 -05:00
[Core] Make Requires.verify() wait until rules are loaded (#2857)
* Make Requires.verify() wait until rules are loaded Also ensures `Requires` objects are reset when unloaded, particularly in case a `Command` object manages to stay in memory between cog unload and load, and its permissions rules change between those events. Also, this PR re-ordered some of the event loop policy stuff, because it was required that the event loop policy be set before creating any `Requires` objects. This may or may not have an effect on other `get_event_loop()` calls elsewhere (either in our code, a dependency's, or asyncio's). Either way, these effects would be a *correction*, and any bugs that arise from it are likely to have been occurring silently beforehand. Signed-off-by: Toby Harradine <tobyharradine@gmail.com> * Remove calls to `remove_listener()` in permissions Signed-off-by: Toby Harradine <tobyharradine@gmail.com> * Fix adding rules for permissions cog/commands itself Also addresses feedback Signed-off-by: Toby Harradine <tobyharradine@gmail.com> * Clean up indentation when setting uvloop policy Signed-off-by: Toby Harradine <tobyharradine@gmail.com> * Use `set(walk_commands())` to traverse `Group` subcommands Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
This commit is contained in:
committed by
Michael H
parent
21a6384ebf
commit
f83f378528
@@ -322,6 +322,8 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d
|
||||
|
||||
super().remove_cog(cogname)
|
||||
|
||||
cog.requires.reset()
|
||||
|
||||
for meth in self.rpc_handlers.pop(cogname.upper(), ()):
|
||||
self.unregister_rpc_handler(meth)
|
||||
|
||||
@@ -424,21 +426,10 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d
|
||||
self.add_permissions_hook(hook)
|
||||
added_hooks.append(hook)
|
||||
|
||||
for command in cog.__cog_commands__:
|
||||
|
||||
if not isinstance(command, commands.Command):
|
||||
raise RuntimeError(
|
||||
f"The {cog.__class__.__name__} cog in the {cog.__module__} package,"
|
||||
" is not using Red's command module, and cannot be added. "
|
||||
"If this is your cog, please use `from redbot.core import commands`"
|
||||
"in place of `from discord.ext import commands`. For more details on "
|
||||
"this requirement, see this page: "
|
||||
"http://red-discordbot.readthedocs.io/en/v3-develop/framework_commands.html"
|
||||
)
|
||||
super().add_cog(cog)
|
||||
self.dispatch("cog_add", cog)
|
||||
for command in cog.__cog_commands__:
|
||||
self.dispatch("command_add", command)
|
||||
if "permissions" not in self.extensions:
|
||||
cog.requires.ready_event.set()
|
||||
except Exception:
|
||||
for hook in added_hooks:
|
||||
try:
|
||||
@@ -452,6 +443,29 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d
|
||||
del cog
|
||||
raise
|
||||
|
||||
def add_command(self, command: commands.Command) -> None:
|
||||
if not isinstance(command, commands.Command):
|
||||
raise RuntimeError("Commands must be instances of `redbot.core.commands.Command`")
|
||||
|
||||
super().add_command(command)
|
||||
|
||||
permissions_not_loaded = "permissions" not in self.extensions
|
||||
self.dispatch("command_add", command)
|
||||
if permissions_not_loaded:
|
||||
command.requires.ready_event.set()
|
||||
if isinstance(command, commands.Group):
|
||||
for subcommand in set(command.walk_commands()):
|
||||
self.dispatch("command_add", subcommand)
|
||||
if permissions_not_loaded:
|
||||
command.requires.ready_event.set()
|
||||
|
||||
def remove_command(self, name: str) -> None:
|
||||
command = super().remove_command(name)
|
||||
command.requires.reset()
|
||||
if isinstance(command, commands.Group):
|
||||
for subcommand in set(command.walk_commands()):
|
||||
subcommand.requires.reset()
|
||||
|
||||
def clear_permission_rules(self, guild_id: Optional[int]) -> None:
|
||||
"""Clear all permission overrides in a scope.
|
||||
|
||||
|
||||
@@ -272,6 +272,12 @@ class Requires:
|
||||
`user_perms` will be used exclusively, otherwise, for levels
|
||||
other than bot owner, the user can still run the command if
|
||||
they have the required `user_perms`.
|
||||
ready_event : asyncio.Event
|
||||
Event for when this Requires object has had its rules loaded.
|
||||
If permissions is loaded, this should be set when permissions
|
||||
has finished loading rules into this object. If permissions
|
||||
is not loaded, it should be set as soon as the command or cog
|
||||
is added.
|
||||
user_perms : Optional[discord.Permissions]
|
||||
The required permissions for users to execute the command. Can
|
||||
be ``None``, in which case the `privilege_level` will be used
|
||||
@@ -300,6 +306,7 @@ class Requires:
|
||||
):
|
||||
self.checks: List[CheckPredicate] = checks
|
||||
self.privilege_level: Optional[PrivilegeLevel] = privilege_level
|
||||
self.ready_event = asyncio.Event()
|
||||
|
||||
if isinstance(user_perms, dict):
|
||||
self.user_perms: Optional[discord.Permissions] = discord.Permissions.none()
|
||||
@@ -413,6 +420,16 @@ class Requires:
|
||||
if default is not None:
|
||||
rules[self.DEFAULT] = default
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset this Requires object to its original state.
|
||||
|
||||
This will clear all rules, including defaults. It also resets
|
||||
the `Requires.ready_event`.
|
||||
"""
|
||||
self._guild_rules.clear() # pylint: disable=no-member
|
||||
self._global_rules.clear() # pylint: disable=no-member
|
||||
self.ready_event.clear()
|
||||
|
||||
async def verify(self, ctx: "Context") -> bool:
|
||||
"""Check if the given context passes the requirements.
|
||||
|
||||
@@ -438,6 +455,8 @@ class Requires:
|
||||
Propogated from any permissions checks.
|
||||
|
||||
"""
|
||||
if not self.ready_event.is_set():
|
||||
await self.ready_event.wait()
|
||||
await self._verify_bot(ctx)
|
||||
|
||||
# Owner should never be locked out of commands for user permissions.
|
||||
|
||||
Reference in New Issue
Block a user