mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-08 20:28:55 -05:00
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:
parent
62dcebff94
commit
25614620db
1
changelog.d/2976.breaking.rst
Normal file
1
changelog.d/2976.breaking.rst
Normal file
@ -0,0 +1 @@
|
|||||||
|
Removes bot._counter, Makes a few more attrs private (cog_mgr, main_dir)
|
||||||
@ -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.
|
||||||
|
|||||||
6
redbot/core/2976.feature.rst
Normal file
6
redbot/core/2976.feature.rst
Normal 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``
|
||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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.")
|
||||||
|
|
||||||
|
|||||||
@ -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")
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user