mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-21 18:27:59 -05:00
Permissions redesign (#2149)
API changes: - Cogs must now inherit from `commands.Cog` (see #2151 for discussion and more details) - All functions which are not decorators in the `redbot.core.checks` module are now deprecated in favour of their counterparts in `redbot.core.utils.mod`. This is to make this module more consistent and end the confusing naming convention. - `redbot.core.checks.check_overrides` function is now gone, overrideable checks can now be created with the `@commands.permissions_check` decorator - Command, Group, Cog and Context have some new attributes and methods, but they are for internal use so shouldn't concern cog creators (unless they're making a permissions cog!). - `__permissions_check_before` and `__permissions_check_after` have been replaced: A cog method named `__permissions_hook` will be evaluated as permissions hooks in the same way `__permissions_check_before` previously was. Permissions hooks can also be added/removed/verified through the new `*_permissions_hook()` methods on the bot object, and they will be verified even when permissions is unloaded. - New utility method `redbot.core.utils.chat_formatting.humanize_list` - New dependency [`schema`](https://github.com/keleshev/schema) User-facing changes: - When a `@bot_has_permissions` check fails, the bot will respond saying what permissions were actually missing. - All YAML-related `[p]permissions` subcommands now reside under the `[p]permissions acl` sub-group (tbh I still think the whole cog has too many top-level commands) - The YAML schema for these commands has been changed - A rule cannot be set as allow and deny at the same time (previously this would just default to allow) Documentation: - New documentation for `redbot.core.commands.requires` and `redbot.core.checks` modules - Renewed documentation for the permissions cog - `sphinx.ext.doctest` is now enabled Note: standard discord.py checks will still behave exactly the same way, in fact they are checked before `Requires` is looked at, so they are not overrideable. Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
This commit is contained in:
@@ -5,17 +5,12 @@ from collections import Counter
|
||||
from enum import Enum
|
||||
from importlib.machinery import ModuleSpec
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
from typing import Optional, Union, List
|
||||
|
||||
import discord
|
||||
import sys
|
||||
from discord.ext.commands import when_mentioned_or
|
||||
|
||||
# This supresses the PyNaCl warning that isn't relevant here
|
||||
from discord.voice_client import VoiceClient
|
||||
|
||||
VoiceClient.warn_nacl = False
|
||||
|
||||
from .cog_manager import CogManager
|
||||
from . import Config, i18n, commands
|
||||
from .rpc import RPCMixin
|
||||
@@ -124,6 +119,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
self.add_command(help_)
|
||||
|
||||
self._sentry_mgr = None
|
||||
self._permissions_hooks: List[commands.CheckPredicate] = []
|
||||
|
||||
def enable_sentry(self):
|
||||
"""Enable Sentry logging for Red."""
|
||||
@@ -200,7 +196,8 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
async def get_context(self, message, *, cls=commands.Context):
|
||||
return await super().get_context(message, cls=cls)
|
||||
|
||||
def list_packages(self):
|
||||
@staticmethod
|
||||
def list_packages():
|
||||
"""Lists packages present in the cogs the folder"""
|
||||
return os.listdir("cogs")
|
||||
|
||||
@@ -234,7 +231,26 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
|
||||
self.extensions[name] = lib
|
||||
|
||||
def remove_cog(self, cogname):
|
||||
def remove_cog(self, cogname: str):
|
||||
cog = self.get_cog(cogname)
|
||||
if cog is None:
|
||||
return
|
||||
|
||||
for when in ("before", "after"):
|
||||
try:
|
||||
hook = getattr(cog, f"_{cog.__class__.__name__}__red_permissions_{when}")
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
self.remove_permissions_hook(hook, when)
|
||||
|
||||
try:
|
||||
hook = getattr(cog, f"_{cog.__class__.__name__}__red_permissions_before")
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
self.remove_permissions_hook(hook)
|
||||
|
||||
super().remove_cog(cogname)
|
||||
|
||||
for meth in self.rpc_handlers.pop(cogname.upper(), ()):
|
||||
@@ -365,9 +381,19 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
|
||||
await destination.send(content=content, **kwargs)
|
||||
|
||||
def add_cog(self, cog):
|
||||
def add_cog(self, cog: commands.Cog):
|
||||
if not isinstance(cog, commands.Cog):
|
||||
raise RuntimeError(
|
||||
f"The {cog.__class__.__name__} cog in the {cog.__module__} package does "
|
||||
f"not inherit from the commands.Cog base class. The cog author must update "
|
||||
f"the cog to adhere to this requirement."
|
||||
)
|
||||
if not hasattr(cog, "requires"):
|
||||
commands.Cog.__init__(cog)
|
||||
for attr in dir(cog):
|
||||
_attr = getattr(cog, attr)
|
||||
if attr == f"_{cog.__class__.__name__}__permissions_hook":
|
||||
self.add_permissions_hook(_attr)
|
||||
if isinstance(_attr, discord.ext.commands.Command) and not isinstance(
|
||||
_attr, commands.Command
|
||||
):
|
||||
@@ -380,6 +406,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
"http://red-discordbot.readthedocs.io/en/v3-develop/framework_commands.html"
|
||||
)
|
||||
super().add_cog(cog)
|
||||
self.dispatch("cog_add", cog)
|
||||
|
||||
def add_command(self, command: commands.Command):
|
||||
if not isinstance(command, commands.Command):
|
||||
@@ -388,6 +415,76 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
super().add_command(command)
|
||||
self.dispatch("command_add", command)
|
||||
|
||||
def clear_permission_rules(self, guild_id: Optional[int]) -> None:
|
||||
"""Clear all permission overrides in a scope.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
guild_id : Optional[int]
|
||||
The guild ID to wipe permission overrides for. If
|
||||
``None``, this will clear all global rules and leave all
|
||||
guild rules untouched.
|
||||
|
||||
"""
|
||||
for cog in self.cogs.values():
|
||||
cog.requires.clear_all_rules(guild_id)
|
||||
for command in self.walk_commands():
|
||||
command.requires.clear_all_rules(guild_id)
|
||||
|
||||
def add_permissions_hook(self, hook: commands.CheckPredicate) -> None:
|
||||
"""Add a permissions hook.
|
||||
|
||||
Permissions hooks are check predicates which are called before
|
||||
calling `Requires.verify`, and they can optionally return an
|
||||
override: ``True`` to allow, ``False`` to deny, and ``None`` to
|
||||
default to normal behaviour.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
hook
|
||||
A command check predicate which returns ``True``, ``False``
|
||||
or ``None``.
|
||||
|
||||
"""
|
||||
self._permissions_hooks.append(hook)
|
||||
|
||||
def remove_permissions_hook(self, hook: commands.CheckPredicate) -> None:
|
||||
"""Remove a permissions hook.
|
||||
|
||||
Parameters are the same as those in `add_permissions_hook`.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If the permissions hook has not been added.
|
||||
|
||||
"""
|
||||
self._permissions_hooks.remove(hook)
|
||||
|
||||
async def verify_permissions_hooks(self, ctx: commands.Context) -> Optional[bool]:
|
||||
"""Run permissions hooks.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ctx : commands.Context
|
||||
The context for the command being invoked.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[bool]
|
||||
``False`` if any hooks returned ``False``, ``True`` if any
|
||||
hooks return ``True`` and none returned ``False``, ``None``
|
||||
otherwise.
|
||||
|
||||
"""
|
||||
hook_results = []
|
||||
for hook in self._permissions_hooks:
|
||||
result = await discord.utils.maybe_coroutine(hook, ctx)
|
||||
if result is not None:
|
||||
hook_results.append(result)
|
||||
if hook_results:
|
||||
return all(hook_results)
|
||||
|
||||
|
||||
class Red(RedBase, discord.AutoShardedClient):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user