More privatization, and some error helpers (#2976)

* More privatization, and some error helpers

This makes a lot more things private. Continued from #2967, fixes #2984
Adds public methods for various things.

Below is a brief summary of things available elsewhere, though this
particular set of changes may warrant a detailed section in the release notes.

 - bot.db.locale -> redbot.core.i18n.get_locale
   - Note: This one already existed.
 - bot.db.help -> redbot.core.commands.help.HelpSettings
 - bot db whitelist/blaclist? -> bot.allowed_by_whitelist_blacklist
   - This has also been made a single cannonical function for this
   purpose including check usage
 - bot color? -> bot.get_embed_color/bot.get_embed_colour
 - bot.id.api_tokens? ->

   - bot.get_shared_api_tokens
   - bot.set_shared_api_tokens
   - bot.remove_shared_api_tokens

 -bot.db.prefix -> bot.get_valid_prefixes
   - (Note: This is a wrapper around bot.get_prefix)

 Other changes include
  - removing `bot.counter` as it was never used anywhere
  - Adding properties with helpful error messages for moved and renamed
  things
  - making bot.uptime a property with an error on set
  - adding a migration to the bot config for shared_api_tokens

* Remove overly encompassing message redaction, eval is a risk, dont run in dev if you cant manage it

* address Flame's feedback

* rephrase example

* changelog extras

* You saw nothing
This commit is contained in:
Michael H 2019-09-26 12:55:05 -04:00 committed by GitHub
parent 62dcebff94
commit 25614620db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 295 additions and 86 deletions

View File

@ -0,0 +1 @@
Removes bot._counter, Makes a few more attrs private (cog_mgr, main_dir)

View File

@ -65,7 +65,7 @@ class Downloader(commands.Cog):
The default cog install path. The default cog install path.
""" """
return await self.bot.cog_mgr.install_path() return await self.bot._cog_mgr.install_path()
async def installed_cogs(self) -> Tuple[Installable]: async def installed_cogs(self) -> Tuple[Installable]:
"""Get info on installed cogs. """Get info on installed cogs.

View File

@ -0,0 +1,6 @@
adds a few methods and classes replacing direct config access (which is no longer supported)
- ``redbot.core.Red.allowed_by_whitelist_blacklist``
- ``redbot.core.Red.get_valid_prefixes``
- ``redbot.core.Red.clear_shared_api_tokens``
- ``redbot.core.commands.help.HelpSettings``

View File

@ -2,11 +2,12 @@ import asyncio
import inspect import inspect
import logging import logging
import os import os
from collections import Counter from collections import namedtuple
from datetime import datetime
from enum import Enum from enum import Enum
from importlib.machinery import ModuleSpec from importlib.machinery import ModuleSpec
from pathlib import Path from pathlib import Path
from typing import Optional, Union, List, Dict from typing import Optional, Union, List, Dict, NoReturn
import discord import discord
from discord.ext.commands import when_mentioned_or from discord.ext.commands import when_mentioned_or
@ -18,9 +19,14 @@ from .rpc import RPCMixin
from .utils import common_filters from .utils import common_filters
CUSTOM_GROUPS = "CUSTOM_GROUPS" CUSTOM_GROUPS = "CUSTOM_GROUPS"
SHARED_API_TOKENS = "SHARED_API_TOKENS"
log = logging.getLogger("redbot") log = logging.getLogger("redbot")
__all__ = ["RedBase", "Red", "ExitCodes"]
NotMessage = namedtuple("NotMessage", "guild")
def _is_submodule(parent, child): def _is_submodule(parent, child):
return parent == child or child.startswith(parent + ".") return parent == child or child.startswith(parent + ".")
@ -63,7 +69,6 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d
invite_perm=0, invite_perm=0,
disabled_commands=[], disabled_commands=[],
disabled_command_msg="That command is disabled.", disabled_command_msg="That command is disabled.",
api_tokens={},
extra_owner_destinations=[], extra_owner_destinations=[],
owner_opt_out_list=[], owner_opt_out_list=[],
schema_version=0, schema_version=0,
@ -87,6 +92,9 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d
self._config.init_custom(CUSTOM_GROUPS, 2) self._config.init_custom(CUSTOM_GROUPS, 2)
self._config.register_custom(CUSTOM_GROUPS) self._config.register_custom(CUSTOM_GROUPS)
self._config.init_custom(SHARED_API_TOKENS, 2)
self._config.register_custom(SHARED_API_TOKENS)
async def prefix_manager(bot, message): async def prefix_manager(bot, message):
if not cli_flags.prefix: if not cli_flags.prefix:
global_prefix = await bot._config.prefix() global_prefix = await bot._config.prefix()
@ -117,14 +125,12 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d
if "command_not_found" not in kwargs: if "command_not_found" not in kwargs:
kwargs["command_not_found"] = "Command {} not found.\n{}" kwargs["command_not_found"] = "Command {} not found.\n{}"
self._counter = Counter()
self._uptime = None self._uptime = None
self._checked_time_accuracy = None self._checked_time_accuracy = None
self._color = discord.Embed.Empty # This is needed or color ends up 0x000000 self._color = discord.Embed.Empty # This is needed or color ends up 0x000000
self.main_dir = bot_dir self._main_dir = bot_dir
self._cog_mgr = CogManager()
self.cog_mgr = CogManager()
super().__init__(*args, help_command=None, **kwargs) super().__init__(*args, help_command=None, **kwargs)
# Do not manually use the help formatter attribute here, see `send_help_for`, # Do not manually use the help formatter attribute here, see `send_help_for`,
@ -134,9 +140,165 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d
self._permissions_hooks: List[commands.CheckPredicate] = [] self._permissions_hooks: List[commands.CheckPredicate] = []
@property
def cog_mgr(self) -> NoReturn:
raise AttributeError("Please don't mess with the cog manager internals.")
@property
def uptime(self) -> datetime:
""" Allow access to the value, but we don't want cog creators setting it """
return self._uptime
@uptime.setter
def uptime(self, value) -> NoReturn:
raise RuntimeError(
"Hey, we're cool with sharing info about the uptime, but don't try and assign to it please."
)
@property
def db(self) -> NoReturn:
raise AttributeError(
"We really don't want you touching the bot config directly. "
"If you need something in here, take a look at the exposed methods "
"and use the one which corresponds to your needs or "
"open an issue if you need an additional method for your use case."
)
@property
def counter(self) -> NoReturn:
raise AttributeError(
"Please make your own counter object by importing ``Counter`` from ``collections``."
)
@property
def color(self) -> NoReturn:
raise AttributeError("Please fetch the embed color with `get_embed_color`")
@property
def colour(self) -> NoReturn:
raise AttributeError("Please fetch the embed colour with `get_embed_colour`")
async def allowed_by_whitelist_blacklist(
self,
who: Optional[Union[discord.Member, discord.User]] = None,
*,
who_id: Optional[int] = None,
guild_id: Optional[int] = None,
role_ids: Optional[List[int]] = None,
) -> bool:
"""
This checks if a user or member is allowed to run things,
as considered by Red's whitelist and blacklist.
If given a user object, this function will check the global lists
If given a member, this will additionally check guild lists
If omiting a user or member, you must provide a value for ``who_id``
You may also provide a value for ``guild_id`` in this case
If providing a member by guild and member ids,
you should supply ``role_ids`` as well
Parameters
----------
who : Optional[Union[discord.Member, discord.User]]
The user or member object to check
Other Parameters
----------------
who_id : Optional[int]
The id of the user or member to check
If not providing a value for ``who``, this is a required parameter.
guild_id : Optional[int]
When used in conjunction with a provided value for ``who_id``, checks
the lists for the corresponding guild as well.
role_ids : Optional[List[int]]
When used with both ``who_id`` and ``guild_id``, checks the role ids provided.
This is required for accurate checking of members in a guild if providing ids.
Raises
------
TypeError
Did not provide ``who`` or ``who_id``
"""
# Contributor Note:
# All config calls are delayed until needed in this section
# All changes should be made keeping in mind that this is also used as a global check
guild = None
mocked = False # used for an accurate delayed role id expansion later.
if not who:
if not who_id:
raise TypeError("Must provide a value for either `who` or `who_id`")
mocked = True
who = discord.Object(id=who_id)
if guild_id:
guild = discord.Object(id=guild_id)
else:
guild = getattr(who, "guild", None)
if await self.is_owner(who):
return True
global_whitelist = await self._config.whitelist()
if global_whitelist:
if who.id not in global_whitelist:
return False
else:
# blacklist is only used when whitelist doesn't exist.
global_blacklist = await self._config.blacklist()
if who.id in global_blacklist:
return False
if guild:
# The delayed expansion of ids to check saves time in the DM case.
# Converting to a set reduces the total lookup time in section
if mocked:
ids = {i for i in (who.id, *(role_ids or [])) if i != guild.id}
else:
# DEP-WARN
# This uses member._roles (getattr is for the user case)
# If this is removed upstream (undocumented)
# there is a silent failure potential, and role blacklist/whitelists will break.
ids = {i for i in (who.id, *(getattr(who, "_roles", []))) if i != guild.id}
guild_whitelist = await self._config.guild(guild).whitelist()
if guild_whitelist:
if ids.isdisjoint(guild_whitelist):
return False
else:
guild_blacklist = self._config.guild(guild).blacklist()
if not ids.isdisjoint(guild_blacklist):
return False
return True
async def get_valid_prefixes(self, guild: Optional[discord.Guild] = None) -> List[str]:
"""
This gets the valid prefixes for a guild.
If not provided a guild (or passed None) it will give the DM prefixes.
This is just a fancy wrapper around ``get_prefix``
Parameters
----------
guild : Optional[discord.Guild]
The guild you want prefixes for. Omit (or pass None) for the DM prefixes
Returns
-------
List[str]
If a guild was specified, the valid prefixes in that guild.
If a guild was not specified, the valid prefixes for DMs
"""
return await self.get_prefix(NotMessage(guild))
async def get_embed_color(self, location: discord.abc.Messageable) -> discord.Color: async def get_embed_color(self, location: discord.abc.Messageable) -> discord.Color:
""" """
Get the embed color for a location. Get the embed color for a location. This takes into account all related settings.
Parameters Parameters
---------- ----------
@ -170,6 +332,24 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d
await self._schema_0_to_1() await self._schema_0_to_1()
schema_version += 1 schema_version += 1
await self._config.schema_version.set(schema_version) await self._config.schema_version.set(schema_version)
if schema_version == 1:
await self._schema_1_to_2()
schema_version += 1
await self._config.schema_version.set(schema_version)
async def _schema_1_to_2(self):
"""
This contains the migration of shared API tokens to a custom config scope
"""
log.info("Moving shared API tokens to a custom group")
all_shared_api_tokens = await self._config.get_raw("api_tokens", default={})
for service_name, token_mapping in all_shared_api_tokens.items():
service_partial = self._config.custom(SHARED_API_TOKENS, service_name)
async with service_partial.all() as basically_bulk_update:
basically_bulk_update.update(token_mapping)
await self._config.clear_raw("api_tokens")
async def _schema_0_to_1(self): async def _schema_0_to_1(self):
""" """
@ -320,7 +500,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d
A Mapping of token names to tokens. A Mapping of token names to tokens.
This mapping exists because some services have multiple tokens. This mapping exists because some services have multiple tokens.
""" """
return await self._config.api_tokens.get_raw(service_name, default={}) return await self._config.custom(SHARED_API_TOKENS, service_name).all()
async def set_shared_api_tokens(self, service_name: str, **tokens: str): async def set_shared_api_tokens(self, service_name: str, **tokens: str):
""" """
@ -330,10 +510,44 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d
``set api`` command ``set api`` command
This will not clear existing values not specified. This will not clear existing values not specified.
Parameters
----------
service_name: str
The service to set tokens for
**tokens
token_name -> token
Examples
--------
Setting the api_key for youtube from a value in a variable ``my_key``
>>> await ctx.bot.set_shared_api_tokens("youtube", api_key=my_key)
""" """
async with self._config.api_tokens.get_attr(service_name)() as method_abuse: async with self._config.custom(SHARED_API_TOKENS, service_name).all() as group:
method_abuse.update(**tokens) group.update(tokens)
async def remove_shared_api_tokens(self, service_name: str, *token_names: str):
"""
Removes shared API tokens
Parameters
----------
service_name: str
The service to remove tokens for
*token_names: str
The name of each token to be removed
Examples
--------
Removing the api_key for youtube
>>> await ctx.bot.remove_shared_api_tokens("youtube", "api_key")
"""
async with self._config.custom(SHARED_API_TOKENS, service_name).all() as group:
for name in token_names:
group.pop(name, None)
async def get_context(self, message, *, cls=commands.Context): async def get_context(self, message, *, cls=commands.Context):
return await super().get_context(message, cls=cls) return await super().get_context(message, cls=cls)

View File

@ -21,7 +21,7 @@ def interactive_config(red, token_set, prefix_set):
print("That doesn't look like a valid token.") print("That doesn't look like a valid token.")
token = "" token = ""
if token: if token:
loop.run_until_complete(red.db.token.set(token)) loop.run_until_complete(red._config.token.set(token))
if not prefix_set: if not prefix_set:
prefix = "" prefix = ""
@ -39,7 +39,7 @@ def interactive_config(red, token_set, prefix_set):
if not confirm("> "): if not confirm("> "):
prefix = "" prefix = ""
if prefix: if prefix:
loop.run_until_complete(red.db.prefix.set([prefix])) loop.run_until_complete(red._config.prefix.set([prefix]))
return token return token

View File

@ -317,7 +317,7 @@ class CogManagerUI(commands.Cog):
""" """
Lists current cog paths in order of priority. Lists current cog paths in order of priority.
""" """
cog_mgr = ctx.bot.cog_mgr cog_mgr = ctx.bot._cog_mgr
install_path = await cog_mgr.install_path() install_path = await cog_mgr.install_path()
core_path = cog_mgr.CORE_PATH core_path = cog_mgr.CORE_PATH
cog_paths = await cog_mgr.user_defined_paths() cog_paths = await cog_mgr.user_defined_paths()
@ -344,7 +344,7 @@ class CogManagerUI(commands.Cog):
return return
try: try:
await ctx.bot.cog_mgr.add_path(path) await ctx.bot._cog_mgr.add_path(path)
except ValueError as e: except ValueError as e:
await ctx.send(str(e)) await ctx.send(str(e))
else: else:
@ -362,14 +362,14 @@ class CogManagerUI(commands.Cog):
await ctx.send(_("Path numbers must be positive.")) await ctx.send(_("Path numbers must be positive."))
return return
cog_paths = await ctx.bot.cog_mgr.user_defined_paths() cog_paths = await ctx.bot._cog_mgr.user_defined_paths()
try: try:
to_remove = cog_paths.pop(path_number) to_remove = cog_paths.pop(path_number)
except IndexError: except IndexError:
await ctx.send(_("That is an invalid path number.")) await ctx.send(_("That is an invalid path number."))
return return
await ctx.bot.cog_mgr.remove_path(to_remove) await ctx.bot._cog_mgr.remove_path(to_remove)
await ctx.send(_("Path successfully removed.")) await ctx.send(_("Path successfully removed."))
@commands.command() @commands.command()
@ -385,7 +385,7 @@ class CogManagerUI(commands.Cog):
await ctx.send(_("Path numbers must be positive.")) await ctx.send(_("Path numbers must be positive."))
return return
all_paths = await ctx.bot.cog_mgr.user_defined_paths() all_paths = await ctx.bot._cog_mgr.user_defined_paths()
try: try:
to_move = all_paths.pop(from_) to_move = all_paths.pop(from_)
except IndexError: except IndexError:
@ -398,7 +398,7 @@ class CogManagerUI(commands.Cog):
await ctx.send(_("Invalid 'to' index.")) await ctx.send(_("Invalid 'to' index."))
return return
await ctx.bot.cog_mgr.set_paths(all_paths) await ctx.bot._cog_mgr.set_paths(all_paths)
await ctx.send(_("Paths reordered.")) await ctx.send(_("Paths reordered."))
@commands.command() @commands.command()
@ -413,14 +413,14 @@ class CogManagerUI(commands.Cog):
""" """
if path: if path:
if not path.is_absolute(): if not path.is_absolute():
path = (ctx.bot.main_dir / path).resolve() path = (ctx.bot._main_dir / path).resolve()
try: try:
await ctx.bot.cog_mgr.set_install_path(path) await ctx.bot._cog_mgr.set_install_path(path)
except ValueError: except ValueError:
await ctx.send(_("That path does not exist.")) await ctx.send(_("That path does not exist."))
return return
install_path = await ctx.bot.cog_mgr.install_path() install_path = await ctx.bot._cog_mgr.install_path()
await ctx.send( await ctx.send(
_("The bot will install new cogs to the `{}` directory.").format(install_path) _("The bot will install new cogs to the `{}` directory.").format(install_path)
) )
@ -433,7 +433,7 @@ class CogManagerUI(commands.Cog):
""" """
loaded = set(ctx.bot.extensions.keys()) loaded = set(ctx.bot.extensions.keys())
all_cogs = set(await ctx.bot.cog_mgr.available_modules()) all_cogs = set(await ctx.bot._cog_mgr.available_modules())
unloaded = all_cogs - loaded unloaded = all_cogs - loaded

View File

@ -1,3 +1,8 @@
# Warning: The implementation below touches several private attributes.
# While this implementation will be updated, and public interfaces maintained, derived classes
# should not assume these private attributes are version safe, and use the provided HelpSettings
# class for these settings.
# This is a full replacement of discord.py's help command # This is a full replacement of discord.py's help command
# #
# At a later date, there should be things added to support extra formatter # At a later date, there should be things added to support extra formatter
@ -29,6 +34,7 @@
import asyncio import asyncio
from collections import namedtuple from collections import namedtuple
from dataclasses import dataclass
from typing import Union, List, AsyncIterator, Iterable, cast from typing import Union, List, AsyncIterator, Iterable, cast
import discord import discord
@ -40,7 +46,7 @@ from ..i18n import Translator
from ..utils import menus, fuzzy_command_search, format_fuzzy_results from ..utils import menus, fuzzy_command_search, format_fuzzy_results
from ..utils.chat_formatting import box, pagify from ..utils.chat_formatting import box, pagify
__all__ = ["red_help", "RedHelpFormatter"] __all__ = ["red_help", "RedHelpFormatter", "HelpSettings"]
T_ = Translator("Help", __file__) T_ = Translator("Help", __file__)
@ -53,6 +59,36 @@ EmbedField = namedtuple("EmbedField", "name value inline")
EMPTY_STRING = "\N{ZERO WIDTH SPACE}" EMPTY_STRING = "\N{ZERO WIDTH SPACE}"
@dataclass(frozen=True)
class HelpSettings:
"""
A representation of help settings.
"""
page_char_limit: int = 1000
max_pages_in_guild: int = 2
use_menus: bool = False
show_hidden: bool = False
verify_checks: bool = True
verify_exists: bool = False
tagline: str = ""
# Contrib Note: This is intentional to not accept the bot object
# There are plans to allow guild and user specific help settings
# Adding a non-context based method now would involve a breaking change later.
# At a later date, more methods should be exposed for non-context based creation.
#
# This is also why we aren't just caching the
# current state of these settings on the bot object.
@classmethod
async def from_context(cls, context: Context):
"""
Get the HelpSettings for the current context
"""
settings = await context.bot._config.help.all()
return cls(**settings)
class NoCommand(Exception): class NoCommand(Exception):
pass pass

View File

@ -87,7 +87,7 @@ class CoreLogic:
for name in cog_names: for name in cog_names:
try: try:
spec = await bot.cog_mgr.find_cog(name) spec = await bot._cog_mgr.find_cog(name)
if spec: if spec:
cogspecs.append((spec, name)) cogspecs.append((spec, name))
else: else:
@ -1146,7 +1146,7 @@ class Core(commands.Cog, CoreLogic):
""" """
if ctx.channel.permissions_for(ctx.me).manage_messages: if ctx.channel.permissions_for(ctx.me).manage_messages:
await ctx.message.delete() await ctx.message.delete()
await ctx.bot._config.api_tokens.set_raw(service, value=tokens) await ctx.bot.set_shared_api_tokens(service, **tokens)
await ctx.send(_("`{service}` API tokens have been set.").format(service=service)) await ctx.send(_("`{service}` API tokens have been set.").format(service=service))
@commands.group() @commands.group()
@ -2198,7 +2198,7 @@ class Core(commands.Cog, CoreLogic):
async def rpc_load(self, request): async def rpc_load(self, request):
cog_name = request.params[0] cog_name = request.params[0]
spec = await self.bot.cog_mgr.find_cog(cog_name) spec = await self.bot._cog_mgr.find_cog(cog_name)
if spec is None: if spec is None:
raise LookupError("No such cog found.") raise LookupError("No such cog found.")

View File

@ -61,19 +61,10 @@ class Dev(commands.Cog):
return pagify(msg, delims=["\n", " "], priority=True, shorten_by=10) return pagify(msg, delims=["\n", " "], priority=True, shorten_by=10)
@staticmethod @staticmethod
def sanitize_output(ctx: commands.Context, keys: dict, input_: str) -> str: def sanitize_output(ctx: commands.Context, input_: str) -> str:
"""Hides the bot's token from a string.""" """Hides the bot's token from a string."""
token = ctx.bot.http.token token = ctx.bot.http.token
r = "[EXPUNGED]" return re.sub(re.escape(token), "[EXPUNGED]", input_, re.I)
result = input_.replace(token, r)
result = result.replace(token.lower(), r)
result = result.replace(token.upper(), r)
for provider, data in keys.items():
for name, key in data.items():
result = result.replace(key, r)
result = result.replace(key.upper(), r)
result = result.replace(key.lower(), r)
return result
@commands.command() @commands.command()
@checks.is_owner() @checks.is_owner()
@ -125,9 +116,7 @@ class Dev(commands.Cog):
result = await result result = await result
self._last_result = result self._last_result = result
result = self.sanitize_output(ctx, str(result))
api_keys = await ctx.bot._config.api_tokens()
result = self.sanitize_output(ctx, api_keys, str(result))
await ctx.send_interactive(self.get_pages(result), box_lang="py") await ctx.send_interactive(self.get_pages(result), box_lang="py")
@ -191,8 +180,7 @@ class Dev(commands.Cog):
msg = "{}{}".format(printed, result) msg = "{}{}".format(printed, result)
else: else:
msg = printed msg = printed
api_keys = await ctx.bot._config.api_tokens() msg = self.sanitize_output(ctx, msg)
msg = self.sanitize_output(ctx, api_keys, msg)
await ctx.send_interactive(self.get_pages(msg), box_lang="py") await ctx.send_interactive(self.get_pages(msg), box_lang="py")
@ -276,8 +264,7 @@ class Dev(commands.Cog):
elif value: elif value:
msg = "{}".format(value) msg = "{}".format(value)
api_keys = await ctx.bot._config.api_tokens() msg = self.sanitize_output(ctx, msg)
msg = self.sanitize_output(ctx, api_keys, msg)
try: try:
await ctx.send_interactive(self.get_pages(msg), box_lang="py") await ctx.send_interactive(self.get_pages(msg), box_lang="py")

View File

@ -67,7 +67,7 @@ def init_events(bot, cli_flags):
print("Loading packages...") print("Loading packages...")
for package in packages: for package in packages:
try: try:
spec = await bot.cog_mgr.find_cog(package) spec = await bot._cog_mgr.find_cog(package)
await bot.load_extension(spec) await bot.load_extension(spec)
except Exception as e: except Exception as e:
log.exception("Failed to load package {}".format(package), exc_info=e) log.exception("Failed to load package {}".format(package), exc_info=e)
@ -243,7 +243,6 @@ def init_events(bot, cli_flags):
@bot.event @bot.event
async def on_message(message): async def on_message(message):
bot._counter["messages_read"] += 1
await bot.process_commands(message) await bot.process_commands(message)
discord_now = message.created_at discord_now = message.created_at
if ( if (
@ -260,14 +259,6 @@ def init_events(bot, cli_flags):
) )
bot._checked_time_accuracy = discord_now bot._checked_time_accuracy = discord_now
@bot.event
async def on_resumed():
bot._counter["sessions_resumed"] += 1
@bot.event
async def on_command(command):
bot._counter["processed_commands"] += 1
@bot.event @bot.event
async def on_command_add(command: commands.Command): async def on_command_add(command: commands.Command):
disabled_commands = await bot._config.disabled_commands() disabled_commands = await bot._config.disabled_commands()

View File

@ -4,34 +4,8 @@ from . import commands
def init_global_checks(bot): def init_global_checks(bot):
@bot.check_once @bot.check_once
async def global_perms(ctx): async def whiteblacklist_checks(ctx):
"""Check the user is/isn't globally whitelisted/blacklisted.""" return await ctx.bot.allowed_by_whitelist_blacklist(ctx.author)
if await bot.is_owner(ctx.author):
return True
whitelist = await bot._config.whitelist()
if whitelist:
return ctx.author.id in whitelist
return ctx.author.id not in await bot._config.blacklist()
@bot.check_once
async def local_perms(ctx: commands.Context):
"""Check the user is/isn't locally whitelisted/blacklisted."""
if await bot.is_owner(ctx.author):
return True
elif ctx.guild is None:
return True
guild_settings = bot._config.guild(ctx.guild)
local_blacklist = await guild_settings.blacklist()
local_whitelist = await guild_settings.whitelist()
_ids = [r.id for r in ctx.author.roles if not r.is_default()]
_ids.append(ctx.author.id)
if local_whitelist:
return any(i in local_whitelist for i in _ids)
return not any(i in local_blacklist for i in _ids)
@bot.check_once @bot.check_once
async def bots(ctx): async def bots(ctx):

View File

@ -5,9 +5,9 @@ __all__ = ["cog_mgr", "default_dir"]
@pytest.fixture() @pytest.fixture()
def cog_mgr(red): def cog_mgr(red):
return red.cog_mgr return red._cog_mgr
@pytest.fixture() @pytest.fixture()
def default_dir(red): def default_dir(red):
return red.main_dir return red._main_dir