diff options
Diffstat (limited to '')
| -rw-r--r-- | bot/__main__.py | 2 | ||||
| -rw-r--r-- | bot/cogs/alias.py | 7 | ||||
| -rw-r--r-- | bot/cogs/antispam.py | 6 | ||||
| -rw-r--r-- | bot/cogs/cogs.py | 298 | ||||
| -rw-r--r-- | bot/cogs/defcon.py | 6 | ||||
| -rw-r--r-- | bot/cogs/doc.py | 8 | ||||
| -rw-r--r-- | bot/cogs/extensions.py | 236 | ||||
| -rw-r--r-- | bot/cogs/logging.py | 6 | ||||
| -rw-r--r-- | bot/cogs/off_topic_names.py | 6 | ||||
| -rw-r--r-- | bot/cogs/reddit.py | 6 | ||||
| -rw-r--r-- | bot/cogs/reminders.py | 6 | ||||
| -rw-r--r-- | bot/cogs/sync/cog.py | 6 | 
12 files changed, 274 insertions, 319 deletions
| diff --git a/bot/__main__.py b/bot/__main__.py index d0924be78..19a7e5ec6 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -42,7 +42,7 @@ bot.load_extension("bot.cogs.security")  bot.load_extension("bot.cogs.antispam")  bot.load_extension("bot.cogs.bot")  bot.load_extension("bot.cogs.clean") -bot.load_extension("bot.cogs.cogs") +bot.load_extension("bot.cogs.extensions")  bot.load_extension("bot.cogs.help")  # Only load this in production diff --git a/bot/cogs/alias.py b/bot/cogs/alias.py index 0f49a400c..6648805e9 100644 --- a/bot/cogs/alias.py +++ b/bot/cogs/alias.py @@ -5,6 +5,7 @@ from typing import Union  from discord import Colour, Embed, Member, User  from discord.ext.commands import Bot, Cog, Command, Context, clean_content, command, group +from bot.cogs.extensions import Extension  from bot.cogs.watchchannels.watchchannel import proxy_user  from bot.converters import TagNameConverter  from bot.pagination import LinePaginator @@ -84,9 +85,9 @@ class Alias (Cog):          await self.invoke(ctx, "site rules")      @command(name="reload", hidden=True) -    async def cogs_reload_alias(self, ctx: Context, *, cog_name: str) -> None: -        """Alias for invoking <prefix>cogs reload [cog_name].""" -        await self.invoke(ctx, "cogs reload", cog_name) +    async def extensions_reload_alias(self, ctx: Context, *extensions: Extension) -> None: +        """Alias for invoking <prefix>extensions reload [extensions...].""" +        await self.invoke(ctx, "extensions reload", *extensions)      @command(name="defon", hidden=True)      async def defcon_enable_alias(self, ctx: Context) -> None: diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index fd7e4edb0..1b394048a 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -107,14 +107,16 @@ class AntiSpam(Cog):          self.message_deletion_queue = dict()          self.queue_consumption_tasks = dict() +        self.bot.loop.create_task(self.alert_on_validation_error()) +      @property      def mod_log(self) -> ModLog:          """Allows for easy access of the ModLog cog."""          return self.bot.get_cog("ModLog") -    @Cog.listener() -    async def on_ready(self) -> None: +    async def alert_on_validation_error(self) -> None:          """Unloads the cog and alerts admins if configuration validation failed.""" +        await self.bot.wait_until_ready()          if self.validation_errors:              body = "**The following errors were encountered:**\n"              body += "\n".join(f"- {error}" for error in self.validation_errors.values()) diff --git a/bot/cogs/cogs.py b/bot/cogs/cogs.py deleted file mode 100644 index 1f6ccd09c..000000000 --- a/bot/cogs/cogs.py +++ /dev/null @@ -1,298 +0,0 @@ -import logging -import os - -from discord import Colour, Embed -from discord.ext.commands import Bot, Cog, Context, group - -from bot.constants import ( -    Emojis, MODERATION_ROLES, Roles, URLs -) -from bot.decorators import with_role -from bot.pagination import LinePaginator - -log = logging.getLogger(__name__) - -KEEP_LOADED = ["bot.cogs.cogs", "bot.cogs.modlog"] - - -class Cogs(Cog): -    """Cog management commands.""" - -    def __init__(self, bot: Bot): -        self.bot = bot -        self.cogs = {} - -        # Load up the cog names -        log.info("Initializing cog names...") -        for filename in os.listdir("bot/cogs"): -            if filename.endswith(".py") and "_" not in filename: -                if os.path.isfile(f"bot/cogs/{filename}"): -                    cog = filename[:-3] - -                    self.cogs[cog] = f"bot.cogs.{cog}" - -        # Allow reverse lookups by reversing the pairs -        self.cogs.update({v: k for k, v in self.cogs.items()}) - -    @group(name='cogs', aliases=('c',), invoke_without_command=True) -    @with_role(*MODERATION_ROLES, Roles.core_developer) -    async def cogs_group(self, ctx: Context) -> None: -        """Load, unload, reload, and list active cogs.""" -        await ctx.invoke(self.bot.get_command("help"), "cogs") - -    @cogs_group.command(name='load', aliases=('l',)) -    @with_role(*MODERATION_ROLES, Roles.core_developer) -    async def load_command(self, ctx: Context, cog: str) -> None: -        """ -        Load up an unloaded cog, given the module containing it. - -        You can specify the cog name for any cogs that are placed directly within `!cogs`, or specify the -        entire module directly. -        """ -        cog = cog.lower() - -        embed = Embed() -        embed.colour = Colour.red() - -        embed.set_author( -            name="Python Bot (Cogs)", -            url=URLs.github_bot_repo, -            icon_url=URLs.bot_avatar -        ) - -        if cog in self.cogs: -            full_cog = self.cogs[cog] -        elif "." in cog: -            full_cog = cog -        else: -            full_cog = None -            log.warning(f"{ctx.author} requested we load the '{cog}' cog, but that cog doesn't exist.") -            embed.description = f"Unknown cog: {cog}" - -        if full_cog: -            if full_cog not in self.bot.extensions: -                try: -                    self.bot.load_extension(full_cog) -                except ImportError: -                    log.exception(f"{ctx.author} requested we load the '{cog}' cog, " -                                  f"but the cog module {full_cog} could not be found!") -                    embed.description = f"Invalid cog: {cog}\n\nCould not find cog module {full_cog}" -                except Exception as e: -                    log.exception(f"{ctx.author} requested we load the '{cog}' cog, " -                                  "but the loading failed") -                    embed.description = f"Failed to load cog: {cog}\n\n{e.__class__.__name__}: {e}" -                else: -                    log.debug(f"{ctx.author} requested we load the '{cog}' cog. Cog loaded!") -                    embed.description = f"Cog loaded: {cog}" -                    embed.colour = Colour.green() -            else: -                log.warning(f"{ctx.author} requested we load the '{cog}' cog, but the cog was already loaded!") -                embed.description = f"Cog {cog} is already loaded" - -        await ctx.send(embed=embed) - -    @cogs_group.command(name='unload', aliases=('ul',)) -    @with_role(*MODERATION_ROLES, Roles.core_developer) -    async def unload_command(self, ctx: Context, cog: str) -> None: -        """ -        Unload an already-loaded cog, given the module containing it. - -        You can specify the cog name for any cogs that are placed directly within `!cogs`, or specify the -        entire module directly. -        """ -        cog = cog.lower() - -        embed = Embed() -        embed.colour = Colour.red() - -        embed.set_author( -            name="Python Bot (Cogs)", -            url=URLs.github_bot_repo, -            icon_url=URLs.bot_avatar -        ) - -        if cog in self.cogs: -            full_cog = self.cogs[cog] -        elif "." in cog: -            full_cog = cog -        else: -            full_cog = None -            log.warning(f"{ctx.author} requested we unload the '{cog}' cog, but that cog doesn't exist.") -            embed.description = f"Unknown cog: {cog}" - -        if full_cog: -            if full_cog in KEEP_LOADED: -                log.warning(f"{ctx.author} requested we unload `{full_cog}`, that sneaky pete. We said no.") -                embed.description = f"You may not unload `{full_cog}`!" -            elif full_cog in self.bot.extensions: -                try: -                    self.bot.unload_extension(full_cog) -                except Exception as e: -                    log.exception(f"{ctx.author} requested we unload the '{cog}' cog, " -                                  "but the unloading failed") -                    embed.description = f"Failed to unload cog: {cog}\n\n```{e}```" -                else: -                    log.debug(f"{ctx.author} requested we unload the '{cog}' cog. Cog unloaded!") -                    embed.description = f"Cog unloaded: {cog}" -                    embed.colour = Colour.green() -            else: -                log.warning(f"{ctx.author} requested we unload the '{cog}' cog, but the cog wasn't loaded!") -                embed.description = f"Cog {cog} is not loaded" - -        await ctx.send(embed=embed) - -    @cogs_group.command(name='reload', aliases=('r',)) -    @with_role(*MODERATION_ROLES, Roles.core_developer) -    async def reload_command(self, ctx: Context, cog: str) -> None: -        """ -        Reload an unloaded cog, given the module containing it. - -        You can specify the cog name for any cogs that are placed directly within `!cogs`, or specify the -        entire module directly. - -        If you specify "*" as the cog, every cog currently loaded will be unloaded, and then every cog present in the -        bot/cogs directory will be loaded. -        """ -        cog = cog.lower() - -        embed = Embed() -        embed.colour = Colour.red() - -        embed.set_author( -            name="Python Bot (Cogs)", -            url=URLs.github_bot_repo, -            icon_url=URLs.bot_avatar -        ) - -        if cog == "*": -            full_cog = cog -        elif cog in self.cogs: -            full_cog = self.cogs[cog] -        elif "." in cog: -            full_cog = cog -        else: -            full_cog = None -            log.warning(f"{ctx.author} requested we reload the '{cog}' cog, but that cog doesn't exist.") -            embed.description = f"Unknown cog: {cog}" - -        if full_cog: -            if full_cog == "*": -                all_cogs = [ -                    f"bot.cogs.{fn[:-3]}" for fn in os.listdir("bot/cogs") -                    if os.path.isfile(f"bot/cogs/{fn}") and fn.endswith(".py") and "_" not in fn -                ] - -                failed_unloads = {} -                failed_loads = {} - -                unloaded = 0 -                loaded = 0 - -                for loaded_cog in self.bot.extensions.copy().keys(): -                    try: -                        self.bot.unload_extension(loaded_cog) -                    except Exception as e: -                        failed_unloads[loaded_cog] = f"{e.__class__.__name__}: {e}" -                    else: -                        unloaded += 1 - -                for unloaded_cog in all_cogs: -                    try: -                        self.bot.load_extension(unloaded_cog) -                    except Exception as e: -                        failed_loads[unloaded_cog] = f"{e.__class__.__name__}: {e}" -                    else: -                        loaded += 1 - -                lines = [ -                    "**All cogs reloaded**", -                    f"**Unloaded**: {unloaded} / **Loaded**: {loaded}" -                ] - -                if failed_unloads: -                    lines.append("\n**Unload failures**") - -                    for cog, error in failed_unloads: -                        lines.append(f"{Emojis.status_dnd} **{cog}:** `{error}`") - -                if failed_loads: -                    lines.append("\n**Load failures**") - -                    for cog, error in failed_loads.items(): -                        lines.append(f"{Emojis.status_dnd} **{cog}:** `{error}`") - -                log.debug(f"{ctx.author} requested we reload all cogs. Here are the results: \n" -                          f"{lines}") - -                await LinePaginator.paginate(lines, ctx, embed, empty=False) -                return - -            elif full_cog in self.bot.extensions: -                try: -                    self.bot.unload_extension(full_cog) -                    self.bot.load_extension(full_cog) -                except Exception as e: -                    log.exception(f"{ctx.author} requested we reload the '{cog}' cog, " -                                  "but the unloading failed") -                    embed.description = f"Failed to reload cog: {cog}\n\n```{e}```" -                else: -                    log.debug(f"{ctx.author} requested we reload the '{cog}' cog. Cog reloaded!") -                    embed.description = f"Cog reload: {cog}" -                    embed.colour = Colour.green() -            else: -                log.warning(f"{ctx.author} requested we reload the '{cog}' cog, but the cog wasn't loaded!") -                embed.description = f"Cog {cog} is not loaded" - -        await ctx.send(embed=embed) - -    @cogs_group.command(name='list', aliases=('all',)) -    @with_role(*MODERATION_ROLES, Roles.core_developer) -    async def list_command(self, ctx: Context) -> None: -        """ -        Get a list of all cogs, including their loaded status. - -        Gray indicates that the cog is unloaded. Green indicates that the cog is currently loaded. -        """ -        embed = Embed() -        lines = [] -        cogs = {} - -        embed.colour = Colour.blurple() -        embed.set_author( -            name="Python Bot (Cogs)", -            url=URLs.github_bot_repo, -            icon_url=URLs.bot_avatar -        ) - -        for key, _value in self.cogs.items(): -            if "." not in key: -                continue - -            if key in self.bot.extensions: -                cogs[key] = True -            else: -                cogs[key] = False - -        for key in self.bot.extensions.keys(): -            if key not in self.cogs: -                cogs[key] = True - -        for cog, loaded in sorted(cogs.items(), key=lambda x: x[0]): -            if cog in self.cogs: -                cog = self.cogs[cog] - -            if loaded: -                status = Emojis.status_online -            else: -                status = Emojis.status_offline - -            lines.append(f"{status}  {cog}") - -        log.debug(f"{ctx.author} requested a list of all cogs. Returning a paginated list.") -        await LinePaginator.paginate(lines, ctx, embed, max_size=300, empty=False) - - -def setup(bot: Bot) -> None: -    """Cogs cog load.""" -    bot.add_cog(Cogs(bot)) -    log.info("Cog loaded: Cogs") diff --git a/bot/cogs/defcon.py b/bot/cogs/defcon.py index ae0332688..70e101baa 100644 --- a/bot/cogs/defcon.py +++ b/bot/cogs/defcon.py @@ -35,14 +35,16 @@ class Defcon(Cog):          self.channel = None          self.days = timedelta(days=0) +        self.bot.loop.create_task(self.sync_settings()) +      @property      def mod_log(self) -> ModLog:          """Get currently loaded ModLog cog instance."""          return self.bot.get_cog("ModLog") -    @Cog.listener() -    async def on_ready(self) -> None: +    async def sync_settings(self) -> None:          """On cog load, try to synchronize DEFCON settings to the API.""" +        await self.bot.wait_until_ready()          self.channel = await self.bot.fetch_channel(Channels.defcon)          try:              response = await self.bot.api_client.get('bot/bot-settings/defcon') diff --git a/bot/cogs/doc.py b/bot/cogs/doc.py index 0c5a8fce3..a13464bff 100644 --- a/bot/cogs/doc.py +++ b/bot/cogs/doc.py @@ -126,9 +126,11 @@ class Doc(commands.Cog):          self.bot = bot          self.inventories = {} -    @commands.Cog.listener() -    async def on_ready(self) -> None: -        """Refresh documentation inventory.""" +        self.bot.loop.create_task(self.init_refresh_inventory()) + +    async def init_refresh_inventory(self) -> None: +        """Refresh documentation inventory on cog initialization.""" +        await self.bot.wait_until_ready()          await self.refresh_inventory()      async def update_single( diff --git a/bot/cogs/extensions.py b/bot/cogs/extensions.py new file mode 100644 index 000000000..bb66e0b8e --- /dev/null +++ b/bot/cogs/extensions.py @@ -0,0 +1,236 @@ +import functools +import logging +import typing as t +from enum import Enum +from pkgutil import iter_modules + +from discord import Colour, Embed +from discord.ext import commands +from discord.ext.commands import Bot, Context, group + +from bot.constants import Emojis, MODERATION_ROLES, Roles, URLs +from bot.pagination import LinePaginator +from bot.utils.checks import with_role_check + +log = logging.getLogger(__name__) + +UNLOAD_BLACKLIST = {"bot.cogs.extensions", "bot.cogs.modlog"} +EXTENSIONS = frozenset( +    ext.name +    for ext in iter_modules(("bot/cogs",), "bot.cogs.") +    if ext.name[-1] != "_" +) + + +class Action(Enum): +    """Represents an action to perform on an extension.""" + +    # Need to be partial otherwise they are considered to be function definitions. +    LOAD = functools.partial(Bot.load_extension) +    UNLOAD = functools.partial(Bot.unload_extension) +    RELOAD = functools.partial(Bot.reload_extension) + + +class Extension(commands.Converter): +    """ +    Fully qualify the name of an extension and ensure it exists. + +    The * and ** values bypass this when used with the reload command. +    """ + +    async def convert(self, ctx: Context, argument: str) -> str: +        """Fully qualify the name of an extension and ensure it exists.""" +        # Special values to reload all extensions +        if argument == "*" or argument == "**": +            return argument + +        argument = argument.lower() + +        if "." not in argument: +            argument = f"bot.cogs.{argument}" + +        if argument in EXTENSIONS: +            return argument +        else: +            raise commands.BadArgument(f":x: Could not find the extension `{argument}`.") + + +class Extensions(commands.Cog): +    """Extension management commands.""" + +    def __init__(self, bot: Bot): +        self.bot = bot + +    @group(name="extensions", aliases=("ext", "exts", "c", "cogs"), invoke_without_command=True) +    async def extensions_group(self, ctx: Context) -> None: +        """Load, unload, reload, and list loaded extensions.""" +        await ctx.invoke(self.bot.get_command("help"), "extensions") + +    @extensions_group.command(name="load", aliases=("l",)) +    async def load_command(self, ctx: Context, *extensions: Extension) -> None: +        """ +        Load extensions given their fully qualified or unqualified names. + +        If '\*' or '\*\*' is given as the name, all unloaded extensions will be loaded. +        """  # noqa: W605 +        if not extensions: +            await ctx.invoke(self.bot.get_command("help"), "extensions load") +            return + +        if "*" in extensions or "**" in extensions: +            extensions = set(EXTENSIONS) - set(self.bot.extensions.keys()) + +        msg = self.batch_manage(Action.LOAD, *extensions) +        await ctx.send(msg) + +    @extensions_group.command(name="unload", aliases=("ul",)) +    async def unload_command(self, ctx: Context, *extensions: Extension) -> None: +        """ +        Unload currently loaded extensions given their fully qualified or unqualified names. + +        If '\*' or '\*\*' is given as the name, all loaded extensions will be unloaded. +        """  # noqa: W605 +        if not extensions: +            await ctx.invoke(self.bot.get_command("help"), "extensions unload") +            return + +        blacklisted = "\n".join(UNLOAD_BLACKLIST & set(extensions)) + +        if blacklisted: +            msg = f":x: The following extension(s) may not be unloaded:```{blacklisted}```" +        else: +            if "*" in extensions or "**" in extensions: +                extensions = set(self.bot.extensions.keys()) - UNLOAD_BLACKLIST + +            msg = self.batch_manage(Action.UNLOAD, *extensions) + +        await ctx.send(msg) + +    @extensions_group.command(name="reload", aliases=("r",)) +    async def reload_command(self, ctx: Context, *extensions: Extension) -> None: +        """ +        Reload extensions given their fully qualified or unqualified names. + +        If an extension fails to be reloaded, it will be rolled-back to the prior working state. + +        If '\*' is given as the name, all currently loaded extensions will be reloaded. +        If '\*\*' is given as the name, all extensions, including unloaded ones, will be reloaded. +        """  # noqa: W605 +        if not extensions: +            await ctx.invoke(self.bot.get_command("help"), "extensions reload") +            return + +        if "**" in extensions: +            extensions = EXTENSIONS +        elif "*" in extensions: +            extensions = set(self.bot.extensions.keys()) | set(extensions) +            extensions.remove("*") + +        msg = self.batch_manage(Action.RELOAD, *extensions) + +        await ctx.send(msg) + +    @extensions_group.command(name="list", aliases=("all",)) +    async def list_command(self, ctx: Context) -> None: +        """ +        Get a list of all extensions, including their loaded status. + +        Grey indicates that the extension is unloaded. +        Green indicates that the extension is currently loaded. +        """ +        embed = Embed() +        lines = [] + +        embed.colour = Colour.blurple() +        embed.set_author( +            name="Extensions List", +            url=URLs.github_bot_repo, +            icon_url=URLs.bot_avatar +        ) + +        for ext in sorted(list(EXTENSIONS)): +            if ext in self.bot.extensions: +                status = Emojis.status_online +            else: +                status = Emojis.status_offline + +            ext = ext.rsplit(".", 1)[1] +            lines.append(f"{status}  {ext}") + +        log.debug(f"{ctx.author} requested a list of all cogs. Returning a paginated list.") +        await LinePaginator.paginate(lines, ctx, embed, max_size=300, empty=False) + +    def batch_manage(self, action: Action, *extensions: str) -> str: +        """ +        Apply an action to multiple extensions and return a message with the results. + +        If only one extension is given, it is deferred to `manage()`. +        """ +        if len(extensions) == 1: +            msg, _ = self.manage(action, extensions[0]) +            return msg + +        verb = action.name.lower() +        failures = {} + +        for extension in extensions: +            _, error = self.manage(action, extension) +            if error: +                failures[extension] = error + +        emoji = ":x:" if failures else ":ok_hand:" +        msg = f"{emoji} {len(extensions) - len(failures)} / {len(extensions)} extensions {verb}ed." + +        if failures: +            failures = "\n".join(f"{ext}\n    {err}" for ext, err in failures.items()) +            msg += f"\nFailures:```{failures}```" + +        log.debug(f"Batch {verb}ed extensions.") + +        return msg + +    def manage(self, action: Action, ext: str) -> t.Tuple[str, t.Optional[str]]: +        """Apply an action to an extension and return the status message and any error message.""" +        verb = action.name.lower() +        error_msg = None + +        try: +            action.value(self.bot, ext) +        except (commands.ExtensionAlreadyLoaded, commands.ExtensionNotLoaded): +            if action is Action.RELOAD: +                # When reloading, just load the extension if it was not loaded. +                return self.manage(Action.LOAD, ext) + +            msg = f":x: Extension `{ext}` is already {verb}ed." +            log.debug(msg[4:]) +        except Exception as e: +            if hasattr(e, "original"): +                e = e.original + +            log.exception(f"Extension '{ext}' failed to {verb}.") + +            error_msg = f"{e.__class__.__name__}: {e}" +            msg = f":x: Failed to {verb} extension `{ext}`:\n```{error_msg}```" +        else: +            msg = f":ok_hand: Extension successfully {verb}ed: `{ext}`." +            log.debug(msg[10:]) + +        return msg, error_msg + +    # This cannot be static (must have a __func__ attribute). +    def cog_check(self, ctx: Context) -> bool: +        """Only allow moderators and core developers to invoke the commands in this cog.""" +        return with_role_check(ctx, *MODERATION_ROLES, Roles.core_developer) + +    # This cannot be static (must have a __func__ attribute). +    async def cog_command_error(self, ctx: Context, error: Exception) -> None: +        """Handle BadArgument errors locally to prevent the help command from showing.""" +        if isinstance(error, commands.BadArgument): +            await ctx.send(str(error)) +            error.handled = True + + +def setup(bot: Bot) -> None: +    """Load the Extensions cog.""" +    bot.add_cog(Extensions(bot)) +    log.info("Cog loaded: Extensions") diff --git a/bot/cogs/logging.py b/bot/cogs/logging.py index 8e47bcc36..c92b619ff 100644 --- a/bot/cogs/logging.py +++ b/bot/cogs/logging.py @@ -15,9 +15,11 @@ class Logging(Cog):      def __init__(self, bot: Bot):          self.bot = bot -    @Cog.listener() -    async def on_ready(self) -> None: +        self.bot.loop.create_task(self.startup_greeting()) + +    async def startup_greeting(self) -> None:          """Announce our presence to the configured devlog channel.""" +        await self.bot.wait_until_ready()          log.info("Bot connected!")          embed = Embed(description="Connected!") diff --git a/bot/cogs/off_topic_names.py b/bot/cogs/off_topic_names.py index 16717d523..2977e4ebb 100644 --- a/bot/cogs/off_topic_names.py +++ b/bot/cogs/off_topic_names.py @@ -75,14 +75,16 @@ class OffTopicNames(Cog):          self.bot = bot          self.updater_task = None +        self.bot.loop.create_task(self.init_offtopic_updater()) +      def cog_unload(self) -> None:          """Cancel any running updater tasks on cog unload."""          if self.updater_task is not None:              self.updater_task.cancel() -    @Cog.listener() -    async def on_ready(self) -> None: +    async def init_offtopic_updater(self) -> None:          """Start off-topic channel updating event loop if it hasn't already started.""" +        await self.bot.wait_until_ready()          if self.updater_task is None:              coro = update_names(self.bot)              self.updater_task = self.bot.loop.create_task(coro) diff --git a/bot/cogs/reddit.py b/bot/cogs/reddit.py index 6880aab85..0f575cece 100644 --- a/bot/cogs/reddit.py +++ b/bot/cogs/reddit.py @@ -34,6 +34,8 @@ class Reddit(Cog):          self.new_posts_task = None          self.top_weekly_posts_task = None +        self.bot.loop.create_task(self.init_reddit_polling()) +      async def fetch_posts(self, route: str, *, amount: int = 25, params: dict = None) -> List[dict]:          """A helper method to fetch a certain amount of Reddit posts at a given route."""          # Reddit's JSON responses only provide 25 posts at most. @@ -262,9 +264,9 @@ class Reddit(Cog):              max_lines=15          ) -    @Cog.listener() -    async def on_ready(self) -> None: +    async def init_reddit_polling(self) -> None:          """Initiate reddit post event loop.""" +        await self.bot.wait_until_ready()          self.reddit_channel = await self.bot.fetch_channel(Channels.reddit)          if self.reddit_channel is not None: diff --git a/bot/cogs/reminders.py b/bot/cogs/reminders.py index 6e91d2c06..b54622306 100644 --- a/bot/cogs/reminders.py +++ b/bot/cogs/reminders.py @@ -30,9 +30,11 @@ class Reminders(Scheduler, Cog):          self.bot = bot          super().__init__() -    @Cog.listener() -    async def on_ready(self) -> None: +        self.bot.loop.create_task(self.reschedule_reminders()) + +    async def reschedule_reminders(self) -> None:          """Get all current reminders from the API and reschedule them.""" +        await self.bot.wait_until_ready()          response = await self.bot.api_client.get(              'bot/reminders',              params={'active': 'true'} diff --git a/bot/cogs/sync/cog.py b/bot/cogs/sync/cog.py index b75fb26cd..aaa581f96 100644 --- a/bot/cogs/sync/cog.py +++ b/bot/cogs/sync/cog.py @@ -29,9 +29,11 @@ class Sync(Cog):      def __init__(self, bot: Bot) -> None:          self.bot = bot -    @Cog.listener() -    async def on_ready(self) -> None: +        self.bot.loop.create_task(self.sync_guild()) + +    async def sync_guild(self) -> None:          """Syncs the roles/users of the guild with the database.""" +        await self.bot.wait_until_ready()          guild = self.bot.get_guild(self.SYNC_SERVER_ID)          if guild is not None:              for syncer in self.ON_READY_SYNCERS: | 
