[Core] Support already loaded packages in [p]load (#2116)

This commit is contained in:
El Laggron 2018-10-07 23:18:28 +02:00 committed by Toby Harradine
parent ee7e8aa782
commit 76bbcf2f8c
4 changed files with 124 additions and 97 deletions

View File

@ -11,10 +11,10 @@ import discord
import sys import sys
from discord.ext.commands import when_mentioned_or from discord.ext.commands import when_mentioned_or
from . import Config, i18n, commands, errors
from .cog_manager import CogManager from .cog_manager import CogManager
from . import Config, i18n, commands
from .rpc import RPCMixin
from .help_formatter import Help, help as help_ from .help_formatter import Help, help as help_
from .rpc import RPCMixin
from .sentry import SentryManager from .sentry import SentryManager
from .utils import common_filters from .utils import common_filters
@ -217,7 +217,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
async def load_extension(self, spec: ModuleSpec): async def load_extension(self, spec: ModuleSpec):
name = spec.name.split(".")[-1] name = spec.name.split(".")[-1]
if name in self.extensions: if name in self.extensions:
raise discord.ClientException(f"there is already a package named {name} loaded") raise errors.PackageAlreadyLoaded(spec)
lib = spec.loader.load_module() lib = spec.loader.load_module()
if not hasattr(lib, "setup"): if not hasattr(lib, "setup"):

View File

@ -13,7 +13,7 @@ from collections import namedtuple
from pathlib import Path from pathlib import Path
from random import SystemRandom from random import SystemRandom
from string import ascii_letters, digits from string import ascii_letters, digits
from typing import TYPE_CHECKING, Union from typing import TYPE_CHECKING, Union, Tuple, List, Optional, Iterable, Sequence, Dict
import aiohttp import aiohttp
import discord import discord
@ -25,6 +25,7 @@ from redbot.core import (
VersionInfo, VersionInfo,
checks, checks,
commands, commands,
errors,
i18n, i18n,
) )
from .utils.predicates import MessagePredicate from .utils.predicates import MessagePredicate
@ -59,7 +60,9 @@ class CoreLogic:
self.bot.register_rpc_handler(self._version_info) self.bot.register_rpc_handler(self._version_info)
self.bot.register_rpc_handler(self._invite_url) self.bot.register_rpc_handler(self._invite_url)
async def _load(self, cog_names: list): async def _load(
self, cog_names: Iterable[str]
) -> Tuple[List[str], List[str], List[str], List[str]]:
""" """
Loads cogs by name. Loads cogs by name.
Parameters Parameters
@ -69,11 +72,12 @@ class CoreLogic:
Returns Returns
------- -------
tuple tuple
3 element tuple of loaded, failed, and not found cogs. 4-tuple of loaded, failed, not found and already loaded cogs.
""" """
failed_packages = [] failed_packages = []
loaded_packages = [] loaded_packages = []
notfound_packages = [] notfound_packages = []
alreadyloaded_packages = []
bot = self.bot bot = self.bot
@ -98,6 +102,8 @@ class CoreLogic:
try: try:
self._cleanup_and_refresh_modules(spec.name) self._cleanup_and_refresh_modules(spec.name)
await bot.load_extension(spec) await bot.load_extension(spec)
except errors.PackageAlreadyLoaded:
alreadyloaded_packages.append(name)
except Exception as e: except Exception as e:
log.exception("Package loading failed", exc_info=e) log.exception("Package loading failed", exc_info=e)
@ -109,9 +115,10 @@ class CoreLogic:
await bot.add_loaded_package(name) await bot.add_loaded_package(name)
loaded_packages.append(name) loaded_packages.append(name)
return loaded_packages, failed_packages, notfound_packages return loaded_packages, failed_packages, notfound_packages, alreadyloaded_packages
def _cleanup_and_refresh_modules(self, module_name: str): @staticmethod
def _cleanup_and_refresh_modules(module_name: str) -> None:
"""Interally reloads modules so that changes are detected""" """Interally reloads modules so that changes are detected"""
splitted = module_name.split(".") splitted = module_name.split(".")
@ -123,6 +130,7 @@ class CoreLogic:
else: else:
importlib._bootstrap._exec(lib.__spec__, lib) importlib._bootstrap._exec(lib.__spec__, lib)
# noinspection PyTypeChecker
modules = itertools.accumulate(splitted, "{}.{}".format) modules = itertools.accumulate(splitted, "{}.{}".format)
for m in modules: for m in modules:
maybe_reload(m) maybe_reload(m)
@ -131,7 +139,10 @@ class CoreLogic:
for child_name, lib in children.items(): for child_name, lib in children.items():
importlib._bootstrap._exec(lib.__spec__, lib) importlib._bootstrap._exec(lib.__spec__, lib)
def _get_package_strings(self, packages: list, fmt: str, other: tuple = None): @staticmethod
def _get_package_strings(
packages: List[str], fmt: str, other: Optional[Tuple[str, ...]] = None
) -> str:
""" """
Gets the strings needed for the load, unload and reload commands Gets the strings needed for the load, unload and reload commands
""" """
@ -147,7 +158,7 @@ class CoreLogic:
final_string = fmt.format(**form) final_string = fmt.format(**form)
return final_string return final_string
async def _unload(self, cog_names: list): async def _unload(self, cog_names: Iterable[str]) -> Tuple[List[str], List[str]]:
""" """
Unloads cogs with the given names. Unloads cogs with the given names.
@ -175,14 +186,16 @@ class CoreLogic:
return unloaded_packages, failed_packages return unloaded_packages, failed_packages
async def _reload(self, cog_names): async def _reload(
self, cog_names: Sequence[str]
) -> Tuple[List[str], List[str], List[str], List[str]]:
await self._unload(cog_names) await self._unload(cog_names)
loaded, load_failed, not_found = await self._load(cog_names) loaded, load_failed, not_found, already_loaded = await self._load(cog_names)
return loaded, load_failed, not_found return loaded, load_failed, not_found, already_loaded
async def _name(self, name: str = None): async def _name(self, name: Optional[str] = None) -> str:
""" """
Gets or sets the bot's username. Gets or sets the bot's username.
@ -201,7 +214,7 @@ class CoreLogic:
return self.bot.user.name return self.bot.user.name
async def _prefixes(self, prefixes: list = None): async def _prefixes(self, prefixes: Optional[Sequence[str]] = None) -> List[str]:
""" """
Gets or sets the bot's global prefixes. Gets or sets the bot's global prefixes.
@ -220,7 +233,8 @@ class CoreLogic:
await self.bot.db.prefix.set(prefixes) await self.bot.db.prefix.set(prefixes)
return await self.bot.db.prefix() return await self.bot.db.prefix()
async def _version_info(self): @classmethod
async def _version_info(cls) -> Dict[str, str]:
""" """
Version information for Red and discord.py Version information for Red and discord.py
@ -231,7 +245,7 @@ class CoreLogic:
""" """
return {"redbot": __version__, "discordpy": discord.__version__} return {"redbot": __version__, "discordpy": discord.__version__}
async def _invite_url(self): async def _invite_url(self) -> str:
""" """
Generates the invite URL for the bot. Generates the invite URL for the bot.
@ -248,11 +262,8 @@ class CoreLogic:
class Core(commands.Cog, CoreLogic): class Core(commands.Cog, CoreLogic):
"""Commands related to core functions""" """Commands related to core functions"""
def __init__(self, bot):
super().__init__(bot)
@commands.command(hidden=True) @commands.command(hidden=True)
async def ping(self, ctx): async def ping(self, ctx: commands.Context):
"""Pong.""" """Pong."""
await ctx.send("Pong.") await ctx.send("Pong.")
@ -313,7 +324,7 @@ class Core(commands.Cog, CoreLogic):
passed = self.get_bot_uptime() passed = self.get_bot_uptime()
await ctx.send("Been up for: **{}** (since {} UTC)".format(passed, since)) await ctx.send("Been up for: **{}** (since {} UTC)".format(passed, since))
def get_bot_uptime(self, *, brief=False): def get_bot_uptime(self, *, brief: bool = False):
# Courtesy of Danny # Courtesy of Danny
now = datetime.datetime.utcnow() now = datetime.datetime.utcnow()
delta = now - self.bot.uptime delta = now - self.bot.uptime
@ -416,7 +427,7 @@ class Core(commands.Cog, CoreLogic):
@commands.command() @commands.command()
@checks.is_owner() @checks.is_owner()
async def traceback(self, ctx, public: bool = False): async def traceback(self, ctx: commands.Context, public: bool = False):
"""Sends to the owner the last command exception that has occurred """Sends to the owner the last command exception that has occurred
If public (yes is specified), it will be sent to the chat instead""" If public (yes is specified), it will be sent to the chat instead"""
@ -433,14 +444,14 @@ class Core(commands.Cog, CoreLogic):
@commands.command() @commands.command()
@checks.is_owner() @checks.is_owner()
async def invite(self, ctx): async def invite(self, ctx: commands.Context):
"""Show's Red's invite url""" """Show's Red's invite url"""
await ctx.author.send(await self._invite_url()) await ctx.author.send(await self._invite_url())
@commands.command() @commands.command()
@commands.guild_only() @commands.guild_only()
@checks.is_owner() @checks.is_owner()
async def leave(self, ctx): async def leave(self, ctx: commands.Context):
"""Leaves server""" """Leaves server"""
await ctx.send("Are you sure you want me to leave this server? (y/n)") await ctx.send("Are you sure you want me to leave this server? (y/n)")
@ -460,7 +471,7 @@ class Core(commands.Cog, CoreLogic):
@commands.command() @commands.command()
@checks.is_owner() @checks.is_owner()
async def servers(self, ctx): async def servers(self, ctx: commands.Context):
"""Lists and allows to leave servers""" """Lists and allows to leave servers"""
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 = ""
@ -502,18 +513,21 @@ class Core(commands.Cog, CoreLogic):
@commands.command() @commands.command()
@checks.is_owner() @checks.is_owner()
async def load(self, ctx, *, cog_name: str): async def load(self, ctx: commands.Context, *cogs: str):
"""Loads packages""" """Loads packages"""
cog_names = [c.strip() for c in cog_name.split(" ")]
async with ctx.typing(): async with ctx.typing():
loaded, failed, not_found = await self._load(cog_names) loaded, failed, not_found, already_loaded = await self._load(cogs)
if loaded: if loaded:
fmt = "Loaded {packs}." fmt = "Loaded {packs}."
formed = self._get_package_strings(loaded, fmt) formed = self._get_package_strings(loaded, fmt)
await ctx.send(formed) await ctx.send(formed)
if already_loaded:
fmt = "The package{plural} {packs} {other} already loaded."
formed = self._get_package_strings(already_loaded, fmt, ("is", "are"))
await ctx.send(formed)
if failed: if failed:
fmt = ( fmt = (
"Failed to load package{plural} {packs}. Check your console or " "Failed to load package{plural} {packs}. Check your console or "
@ -529,12 +543,9 @@ class Core(commands.Cog, CoreLogic):
@commands.command() @commands.command()
@checks.is_owner() @checks.is_owner()
async def unload(self, ctx, *, cog_name: str): async def unload(self, ctx: commands.Context, *cogs: str):
"""Unloads packages""" """Unloads packages"""
unloaded, failed = await self._unload(cogs)
cog_names = [c.strip() for c in cog_name.split(" ")]
unloaded, failed = await self._unload(cog_names)
if unloaded: if unloaded:
fmt = "Package{plural} {packs} {other} unloaded." fmt = "Package{plural} {packs} {other} unloaded."
@ -548,10 +559,10 @@ class Core(commands.Cog, CoreLogic):
@commands.command(name="reload") @commands.command(name="reload")
@checks.is_owner() @checks.is_owner()
async def reload(self, ctx, *cogs: str): async def reload(self, ctx: commands.Context, *cogs: str):
"""Reloads packages""" """Reloads packages"""
async with ctx.typing(): async with ctx.typing():
loaded, failed, not_found = await self._reload(cogs) loaded, failed, not_found, already_loaded = await self._reload(cogs)
if loaded: if loaded:
fmt = "Package{plural} {packs} {other} reloaded." fmt = "Package{plural} {packs} {other} reloaded."
@ -570,34 +581,30 @@ class Core(commands.Cog, CoreLogic):
@commands.command(name="shutdown") @commands.command(name="shutdown")
@checks.is_owner() @checks.is_owner()
async def _shutdown(self, ctx, silently: bool = False): async def _shutdown(self, ctx: commands.Context, silently: bool = False):
"""Shuts down the bot""" """Shuts down the bot"""
wave = "\N{WAVING HAND SIGN}" wave = "\N{WAVING HAND SIGN}"
skin = "\N{EMOJI MODIFIER FITZPATRICK TYPE-3}" skin = "\N{EMOJI MODIFIER FITZPATRICK TYPE-3}"
try: # We don't want missing perms to stop our shutdown with contextlib.suppress(discord.HTTPException):
if not silently: if not silently:
await ctx.send(_("Shutting down... ") + wave + skin) await ctx.send(_("Shutting down... ") + wave + skin)
except:
pass
await ctx.bot.shutdown() await ctx.bot.shutdown()
@commands.command(name="restart") @commands.command(name="restart")
@checks.is_owner() @checks.is_owner()
async def _restart(self, ctx, silently: bool = False): async def _restart(self, ctx: commands.Context, silently: bool = False):
"""Attempts to restart Red """Attempts to restart Red
Makes Red quit with exit code 26 Makes Red quit with exit code 26
The restart is not guaranteed: it must be dealt The restart is not guaranteed: it must be dealt
with by the process manager in use""" with by the process manager in use"""
try: with contextlib.suppress(discord.HTTPException):
if not silently: if not silently:
await ctx.send(_("Restarting...")) await ctx.send(_("Restarting..."))
except:
pass
await ctx.bot.shutdown(restart=True) await ctx.bot.shutdown(restart=True)
@commands.group(name="set") @commands.group(name="set")
async def _set(self, ctx): async def _set(self, ctx: commands.Context):
"""Changes Red's settings""" """Changes Red's settings"""
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
if ctx.guild: if ctx.guild:
@ -629,7 +636,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command() @_set.command()
@checks.guildowner() @checks.guildowner()
@commands.guild_only() @commands.guild_only()
async def adminrole(self, ctx, *, role: discord.Role): async def adminrole(self, ctx: commands.Context, *, role: discord.Role):
"""Sets the admin role for this server""" """Sets the admin role for this server"""
await ctx.bot.db.guild(ctx.guild).admin_role.set(role.id) await ctx.bot.db.guild(ctx.guild).admin_role.set(role.id)
await ctx.send(_("The admin role for this guild has been set.")) await ctx.send(_("The admin role for this guild has been set."))
@ -637,7 +644,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command() @_set.command()
@checks.guildowner() @checks.guildowner()
@commands.guild_only() @commands.guild_only()
async def modrole(self, ctx, *, role: discord.Role): async def modrole(self, ctx: commands.Context, *, role: discord.Role):
"""Sets the mod role for this server""" """Sets the mod role for this server"""
await ctx.bot.db.guild(ctx.guild).mod_role.set(role.id) await ctx.bot.db.guild(ctx.guild).mod_role.set(role.id)
await ctx.send(_("The mod role for this guild has been set.")) await ctx.send(_("The mod role for this guild has been set."))
@ -645,7 +652,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(aliases=["usebotcolor"]) @_set.command(aliases=["usebotcolor"])
@checks.guildowner() @checks.guildowner()
@commands.guild_only() @commands.guild_only()
async def usebotcolour(self, ctx): async def usebotcolour(self, ctx: commands.Context):
""" """
Toggle whether to use the bot owner-configured colour for embeds. Toggle whether to use the bot owner-configured colour for embeds.
@ -663,7 +670,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command() @_set.command()
@checks.guildowner() @checks.guildowner()
@commands.guild_only() @commands.guild_only()
async def serverfuzzy(self, ctx): async def serverfuzzy(self, ctx: commands.Context):
""" """
Toggle whether to enable fuzzy command search for the server. Toggle whether to enable fuzzy command search for the server.
@ -679,7 +686,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command() @_set.command()
@checks.is_owner() @checks.is_owner()
async def fuzzy(self, ctx): async def fuzzy(self, ctx: commands.Context):
""" """
Toggle whether to enable fuzzy command search in DMs. Toggle whether to enable fuzzy command search in DMs.
@ -695,7 +702,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(aliases=["color"]) @_set.command(aliases=["color"])
@checks.is_owner() @checks.is_owner()
async def colour(self, ctx, *, colour: discord.Colour = None): async def colour(self, ctx: commands.Context, *, colour: discord.Colour = None):
""" """
Sets a default colour to be used for the bot's embeds. Sets a default colour to be used for the bot's embeds.
@ -713,7 +720,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command() @_set.command()
@checks.is_owner() @checks.is_owner()
async def avatar(self, ctx, url: str): async def avatar(self, ctx: commands.Context, url: str):
"""Sets Red's avatar""" """Sets Red's avatar"""
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.get(url) as r: async with session.get(url) as r:
@ -737,7 +744,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(name="game") @_set.command(name="game")
@checks.bot_in_a_guild() @checks.bot_in_a_guild()
@checks.is_owner() @checks.is_owner()
async def _game(self, ctx, *, game: str = None): async def _game(self, ctx: commands.Context, *, game: str = None):
"""Sets Red's playing status""" """Sets Red's playing status"""
if game: if game:
@ -751,7 +758,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(name="listening") @_set.command(name="listening")
@checks.bot_in_a_guild() @checks.bot_in_a_guild()
@checks.is_owner() @checks.is_owner()
async def _listening(self, ctx, *, listening: str = None): async def _listening(self, ctx: commands.Context, *, listening: str = None):
"""Sets Red's listening status""" """Sets Red's listening status"""
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online
@ -765,7 +772,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(name="watching") @_set.command(name="watching")
@checks.bot_in_a_guild() @checks.bot_in_a_guild()
@checks.is_owner() @checks.is_owner()
async def _watching(self, ctx, *, watching: str = None): async def _watching(self, ctx: commands.Context, *, watching: str = None):
"""Sets Red's watching status""" """Sets Red's watching status"""
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online
@ -779,7 +786,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command() @_set.command()
@checks.bot_in_a_guild() @checks.bot_in_a_guild()
@checks.is_owner() @checks.is_owner()
async def status(self, ctx, *, status: str): async def status(self, ctx: commands.Context, *, status: str):
"""Sets Red's status """Sets Red's status
Available statuses: Available statuses:
@ -808,7 +815,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command() @_set.command()
@checks.bot_in_a_guild() @checks.bot_in_a_guild()
@checks.is_owner() @checks.is_owner()
async def stream(self, ctx, streamer=None, *, stream_title=None): async def stream(self, ctx: commands.Context, streamer=None, *, stream_title=None):
"""Sets Red's streaming status """Sets Red's streaming status
Leaving both streamer and stream_title empty will clear it.""" Leaving both streamer and stream_title empty will clear it."""
@ -829,7 +836,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(name="username", aliases=["name"]) @_set.command(name="username", aliases=["name"])
@checks.is_owner() @checks.is_owner()
async def _username(self, ctx, *, username: str): async def _username(self, ctx: commands.Context, *, username: str):
"""Sets Red's username""" """Sets Red's username"""
try: try:
await self._name(name=username) await self._name(name=username)
@ -848,7 +855,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(name="nickname") @_set.command(name="nickname")
@checks.admin() @checks.admin()
@commands.guild_only() @commands.guild_only()
async def _nickname(self, ctx, *, nickname: str = None): async def _nickname(self, ctx: commands.Context, *, nickname: str = None):
"""Sets Red's nickname""" """Sets Red's nickname"""
try: try:
await ctx.guild.me.edit(nick=nickname) await ctx.guild.me.edit(nick=nickname)
@ -859,7 +866,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(aliases=["prefixes"]) @_set.command(aliases=["prefixes"])
@checks.is_owner() @checks.is_owner()
async def prefix(self, ctx, *prefixes): async def prefix(self, ctx: commands.Context, *prefixes: str):
"""Sets Red's global prefix(es)""" """Sets Red's global prefix(es)"""
if not prefixes: if not prefixes:
await ctx.send_help() await ctx.send_help()
@ -870,7 +877,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(aliases=["serverprefixes"]) @_set.command(aliases=["serverprefixes"])
@checks.admin() @checks.admin()
@commands.guild_only() @commands.guild_only()
async def serverprefix(self, ctx, *prefixes): async def serverprefix(self, ctx: commands.Context, *prefixes: str):
"""Sets Red's server prefix(es)""" """Sets Red's server prefix(es)"""
if not prefixes: if not prefixes:
await ctx.bot.db.guild(ctx.guild).prefix.set([]) await ctx.bot.db.guild(ctx.guild).prefix.set([])
@ -882,7 +889,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command() @_set.command()
@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: commands.Context):
"""Sets Red's main owner""" """Sets Red's main owner"""
# 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()
@ -926,7 +933,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command() @_set.command()
@checks.is_owner() @checks.is_owner()
async def token(self, ctx, token: str): async def token(self, ctx: commands.Context, token: str):
"""Change bot token.""" """Change bot token."""
if not isinstance(ctx.channel, discord.DMChannel): if not isinstance(ctx.channel, discord.DMChannel):
@ -1071,7 +1078,7 @@ class Core(commands.Cog, CoreLogic):
@commands.command() @commands.command()
@checks.is_owner() @checks.is_owner()
async def backup(self, ctx, backup_path: str = None): async def backup(self, ctx: commands.Context, backup_path: str = None):
"""Creates a backup of all data for the instance.""" """Creates a backup of all data for the instance."""
from redbot.core.data_manager import basic_config, instance_name from redbot.core.data_manager import basic_config, instance_name
from redbot.core.drivers.red_json import JSON from redbot.core.drivers.red_json import JSON
@ -1134,7 +1141,7 @@ class Core(commands.Cog, CoreLogic):
tar.add(str(f), recursive=False) tar.add(str(f), recursive=False)
print(str(backup_file)) print(str(backup_file))
await ctx.send( await ctx.send(
_("A backup has been made of this instance. It is at {}.").format((backup_file)) _("A backup has been made of this instance. It is at {}.").format(backup_file)
) )
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)"))
@ -1157,7 +1164,7 @@ class Core(commands.Cog, CoreLogic):
@commands.command() @commands.command()
@commands.cooldown(1, 60, commands.BucketType.user) @commands.cooldown(1, 60, commands.BucketType.user)
async def contact(self, ctx, *, message: str): async def contact(self, ctx: commands.Context, *, message: str):
"""Sends a message to the owner""" """Sends a message to the owner"""
guild = ctx.message.guild guild = ctx.message.guild
owner = discord.utils.get(ctx.bot.get_all_members(), id=ctx.bot.owner_id) owner = discord.utils.get(ctx.bot.get_all_members(), id=ctx.bot.owner_id)
@ -1200,7 +1207,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send( await ctx.send(
_("I cannot send your message, I'm unable to find my owner... *sigh*") _("I cannot send your message, I'm unable to find my owner... *sigh*")
) )
except: except discord.HTTPException:
await ctx.send(_("I'm unable to deliver your message. Sorry.")) await ctx.send(_("I'm unable to deliver your message. Sorry."))
else: else:
await ctx.send(_("Your message has been sent.")) await ctx.send(_("Your message has been sent."))
@ -1212,14 +1219,14 @@ class Core(commands.Cog, CoreLogic):
await ctx.send( await ctx.send(
_("I cannot send your message, I'm unable to find my owner... *sigh*") _("I cannot send your message, I'm unable to find my owner... *sigh*")
) )
except: except discord.HTTPException:
await ctx.send(_("I'm unable to deliver your message. Sorry.")) await ctx.send(_("I'm unable to deliver your message. Sorry."))
else: else:
await ctx.send(_("Your message has been sent.")) await ctx.send(_("Your message has been sent."))
@commands.command() @commands.command()
@checks.is_owner() @checks.is_owner()
async def dm(self, ctx, user_id: int, *, message: str): async def dm(self, ctx: commands.Context, user_id: int, *, message: str):
"""Sends a DM to a user """Sends a DM to a user
This command needs a user id to work. This command needs a user id to work.
@ -1253,7 +1260,7 @@ class Core(commands.Cog, CoreLogic):
try: try:
await destination.send(embed=e) await destination.send(embed=e)
except: except discord.HTTPException:
await ctx.send( await ctx.send(
_("Sorry, I couldn't deliver your message to {}").format(destination) _("Sorry, I couldn't deliver your message to {}").format(destination)
) )
@ -1263,7 +1270,7 @@ class Core(commands.Cog, CoreLogic):
response = "{}\nMessage:\n\n{}".format(description, message) response = "{}\nMessage:\n\n{}".format(description, message)
try: try:
await destination.send("{}\n{}".format(box(response), content)) await destination.send("{}\n{}".format(box(response), content))
except: except discord.HTTPException:
await ctx.send( await ctx.send(
_("Sorry, I couldn't deliver your message to {}").format(destination) _("Sorry, I couldn't deliver your message to {}").format(destination)
) )
@ -1272,7 +1279,7 @@ class Core(commands.Cog, CoreLogic):
@commands.group() @commands.group()
@checks.is_owner() @checks.is_owner()
async def whitelist(self, ctx): async def whitelist(self, ctx: commands.Context):
""" """
Whitelist management commands. Whitelist management commands.
""" """
@ -1290,7 +1297,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("User added to whitelist.")) await ctx.send(_("User added to whitelist."))
@whitelist.command(name="list") @whitelist.command(name="list")
async def whitelist_list(self, ctx): async def whitelist_list(self, ctx: commands.Context):
""" """
Lists whitelisted users. Lists whitelisted users.
""" """
@ -1304,7 +1311,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(box(page)) await ctx.send(box(page))
@whitelist.command(name="remove") @whitelist.command(name="remove")
async def whitelist_remove(self, ctx, user: discord.User): async def whitelist_remove(self, ctx: commands.Context, user: discord.User):
""" """
Removes user from whitelist. Removes user from whitelist.
""" """
@ -1321,7 +1328,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("User was not in the whitelist.")) await ctx.send(_("User was not in the whitelist."))
@whitelist.command(name="clear") @whitelist.command(name="clear")
async def whitelist_clear(self, ctx): async def whitelist_clear(self, ctx: commands.Context):
""" """
Clears the whitelist. Clears the whitelist.
""" """
@ -1330,19 +1337,19 @@ class Core(commands.Cog, CoreLogic):
@commands.group() @commands.group()
@checks.is_owner() @checks.is_owner()
async def blacklist(self, ctx): async def blacklist(self, ctx: commands.Context):
""" """
blacklist management commands. blacklist management commands.
""" """
pass pass
@blacklist.command(name="add") @blacklist.command(name="add")
async def blacklist_add(self, ctx, user: discord.User): async def blacklist_add(self, ctx: commands.Context, user: discord.User):
""" """
Adds a user to the blacklist. Adds a user to the blacklist.
""" """
if await ctx.bot.is_owner(user): if await ctx.bot.is_owner(user):
ctx.send(_("You cannot blacklist an owner!")) await ctx.send(_("You cannot blacklist an owner!"))
return return
async with ctx.bot.db.blacklist() as curr_list: async with ctx.bot.db.blacklist() as curr_list:
@ -1352,7 +1359,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("User added to blacklist.")) await ctx.send(_("User added to blacklist."))
@blacklist.command(name="list") @blacklist.command(name="list")
async def blacklist_list(self, ctx): async def blacklist_list(self, ctx: commands.Context):
""" """
Lists blacklisted users. Lists blacklisted users.
""" """
@ -1366,7 +1373,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(box(page)) await ctx.send(box(page))
@blacklist.command(name="remove") @blacklist.command(name="remove")
async def blacklist_remove(self, ctx, user: discord.User): async def blacklist_remove(self, ctx: commands.Context, user: discord.User):
""" """
Removes user from blacklist. Removes user from blacklist.
""" """
@ -1383,7 +1390,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("User was not in the blacklist.")) await ctx.send(_("User was not in the blacklist."))
@blacklist.command(name="clear") @blacklist.command(name="clear")
async def blacklist_clear(self, ctx): async def blacklist_clear(self, ctx: commands.Context):
""" """
Clears the blacklist. Clears the blacklist.
""" """
@ -1393,14 +1400,14 @@ class Core(commands.Cog, CoreLogic):
@commands.group() @commands.group()
@commands.guild_only() @commands.guild_only()
@checks.admin_or_permissions(administrator=True) @checks.admin_or_permissions(administrator=True)
async def localwhitelist(self, ctx): async def localwhitelist(self, ctx: commands.Context):
""" """
Whitelist management commands. Whitelist management commands.
""" """
pass pass
@localwhitelist.command(name="add") @localwhitelist.command(name="add")
async def localwhitelist_add(self, ctx, *, user_or_role: str): async def localwhitelist_add(self, ctx: commands.Context, *, user_or_role: str):
""" """
Adds a user or role to the whitelist. Adds a user or role to the whitelist.
""" """
@ -1421,7 +1428,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("Role added to whitelist.")) await ctx.send(_("Role added to whitelist."))
@localwhitelist.command(name="list") @localwhitelist.command(name="list")
async def localwhitelist_list(self, ctx): async def localwhitelist_list(self, ctx: commands.Context):
""" """
Lists whitelisted users and roles. Lists whitelisted users and roles.
""" """
@ -1435,7 +1442,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(box(page)) await ctx.send(box(page))
@localwhitelist.command(name="remove") @localwhitelist.command(name="remove")
async def localwhitelist_remove(self, ctx, *, user_or_role: str): async def localwhitelist_remove(self, ctx: commands.Context, *, user_or_role: str):
""" """
Removes user or role from whitelist. Removes user or role from whitelist.
""" """
@ -1465,7 +1472,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("Role was not in the whitelist.")) await ctx.send(_("Role was not in the whitelist."))
@localwhitelist.command(name="clear") @localwhitelist.command(name="clear")
async def localwhitelist_clear(self, ctx): async def localwhitelist_clear(self, ctx: commands.Context):
""" """
Clears the whitelist. Clears the whitelist.
""" """
@ -1475,14 +1482,14 @@ class Core(commands.Cog, CoreLogic):
@commands.group() @commands.group()
@commands.guild_only() @commands.guild_only()
@checks.admin_or_permissions(administrator=True) @checks.admin_or_permissions(administrator=True)
async def localblacklist(self, ctx): async def localblacklist(self, ctx: commands.Context):
""" """
blacklist management commands. blacklist management commands.
""" """
pass pass
@localblacklist.command(name="add") @localblacklist.command(name="add")
async def localblacklist_add(self, ctx, *, user_or_role: str): async def localblacklist_add(self, ctx: commands.Context, *, user_or_role: str):
""" """
Adds a user or role to the blacklist. Adds a user or role to the blacklist.
""" """
@ -1495,7 +1502,7 @@ class Core(commands.Cog, CoreLogic):
user = True user = True
if user and await ctx.bot.is_owner(obj): if user and await ctx.bot.is_owner(obj):
ctx.send(_("You cannot blacklist an owner!")) await ctx.send(_("You cannot blacklist an owner!"))
return return
async with ctx.bot.db.guild(ctx.guild).blacklist() as curr_list: async with ctx.bot.db.guild(ctx.guild).blacklist() as curr_list:
@ -1508,7 +1515,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("Role added to blacklist.")) await ctx.send(_("Role added to blacklist."))
@localblacklist.command(name="list") @localblacklist.command(name="list")
async def localblacklist_list(self, ctx): async def localblacklist_list(self, ctx: commands.Context):
""" """
Lists blacklisted users and roles. Lists blacklisted users and roles.
""" """
@ -1522,7 +1529,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(box(page)) await ctx.send(box(page))
@localblacklist.command(name="remove") @localblacklist.command(name="remove")
async def localblacklist_remove(self, ctx, *, user_or_role: str): async def localblacklist_remove(self, ctx: commands.Context, *, user_or_role: str):
""" """
Removes user or role from blacklist. Removes user or role from blacklist.
""" """
@ -1552,7 +1559,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("Role was not in the blacklist.")) await ctx.send(_("Role was not in the blacklist."))
@localblacklist.command(name="clear") @localblacklist.command(name="clear")
async def localblacklist_clear(self, ctx): async def localblacklist_clear(self, ctx: commands.Context):
""" """
Clears the blacklist. Clears the blacklist.
""" """
@ -1703,8 +1710,8 @@ class Core(commands.Cog, CoreLogic):
@autoimmune_group.command(name="list") @autoimmune_group.command(name="list")
async def autoimmune_list(self, ctx: commands.Context): async def autoimmune_list(self, ctx: commands.Context):
""" """
Get's the current members and roles Get's the current members and roles
configured for automatic moderation action immunity configured for automatic moderation action immunity
""" """
ai_ids = await ctx.bot.db.guild(ctx.guild).autoimmune_ids() ai_ids = await ctx.bot.db.guild(ctx.guild).autoimmune_ids()

16
redbot/core/errors.py Normal file
View File

@ -0,0 +1,16 @@
import importlib.machinery
class RedError(Exception):
"""Base error class for Red-related errors."""
class PackageAlreadyLoaded(RedError):
"""Raised when trying to load an already-loaded package."""
def __init__(self, spec: importlib.machinery.ModuleSpec, *args, **kwargs):
super().__init__(*args, **kwargs)
self.spec: importlib.machinery.ModuleSpec = spec
def __str__(self) -> str:
return f"There is already a package named {self.spec.name.split('.')[-1]} loaded"

View File

@ -3,8 +3,6 @@ import re
from pathlib import Path from pathlib import Path
from typing import Callable, Union from typing import Callable, Union
from . import commands
__all__ = ["get_locale", "set_locale", "reload_locales", "cog_i18n", "Translator"] __all__ = ["get_locale", "set_locale", "reload_locales", "cog_i18n", "Translator"]
_current_locale = "en_us" _current_locale = "en_us"
@ -219,6 +217,12 @@ class Translator(Callable[[str], str]):
self.translations.update({untranslated: translated}) self.translations.update({untranslated: translated})
# This import to be down here to avoid circular import issues.
# This will be cleaned up at a later date
# noinspection PyPep8
from . import commands
def cog_i18n(translator: Translator): def cog_i18n(translator: Translator):
"""Get a class decorator to link the translator to this cog.""" """Get a class decorator to link the translator to this cog."""