diff --git a/src/Dockerfile-bot b/src/Dockerfile-bot index 8b17356..27c6a4a 100644 --- a/src/Dockerfile-bot +++ b/src/Dockerfile-bot @@ -24,4 +24,4 @@ RUN --mount=type=cache,target=/root/.cache/uv \ RUN useradd -r -M botuser && chown -R botuser:botuser /bot USER botuser -CMD ["uv", "run", "--frozen", "--no-cache", "--no-dev", "--package", "eos-bot", "python", "src/bot/main.py"] +CMD ["uv", "run", "--frozen", "--no-cache", "--no-dev", "--package", "eos-bot", "python", "-m", "src.bot.main"] diff --git a/src/api/api.py b/src/api/api.py index 9d13a05..2f5ffa3 100644 --- a/src/api/api.py +++ b/src/api/api.py @@ -1,17 +1,18 @@ import logging import os -from __logger__ import setup_logger -from core.db_helper import DB from flask import Flask, jsonify -from routes.healthchecks import health_checks -from routes.logging import logs -from routes.parameters import parameters -from routes.points import points -from routes.roles import role -from routes.settings import settings from werkzeug.exceptions import HTTPException +from src.api.__logger__ import setup_logger +from src.api.core.db_helper import DB +from src.api.routes.healthchecks import health_checks +from src.api.routes.logging import logs +from src.api.routes.parameters import parameters +from src.api.routes.points import points +from src.api.routes.roles import role +from src.api.routes.settings import settings + logger = logging.getLogger(__name__) setup_logger( level=int(os.getenv("API_LOG_LEVEL", "20")), diff --git a/src/bot/cogs/__init__.py b/src/bot/cogs/__init__.py index e69de29..f63d489 100644 --- a/src/bot/cogs/__init__.py +++ b/src/bot/cogs/__init__.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from discord.ext import commands + +if TYPE_CHECKING: + from logging import Logger + + +__all__ = ("BaseCog",) + + +class BaseCog(commands.Cog): + """The base cog which comes along with basic logging.""" + + def __init__(self, logger: None | Logger = None) -> None: + super().__init__() + self.logger: None | Logger = logger + + async def cog_load(self) -> None: + if self.logger: + self.logger.info(f"Cog: {self.qualified_name} has been loaded.") + + async def cog_unload(self) -> None: + if self.logger: + self.logger.info(f"Cog: {self.qualified_name} has been unloaded.") diff --git a/src/bot/cogs/admin/healthchecks.py b/src/bot/cogs/admin/healthchecks.py index 5c4210f..e161541 100644 --- a/src/bot/cogs/admin/healthchecks.py +++ b/src/bot/cogs/admin/healthchecks.py @@ -4,6 +4,8 @@ import discord from discord.ext import commands +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) @@ -27,8 +29,10 @@ def embed_hc(api, db): return embed -class Health(commands.Cog): +class Health(BaseCog): def __init__(self, bot: commands.Bot) -> None: + super().__init__(logger) + self.bot = bot @commands.hybrid_command() diff --git a/src/bot/cogs/admin/points.py b/src/bot/cogs/admin/points.py index 6cf44a6..fb39c20 100644 --- a/src/bot/cogs/admin/points.py +++ b/src/bot/cogs/admin/points.py @@ -4,7 +4,8 @@ import discord from discord.ext import commands -from .._checks import is_admin, is_master_guild +from src.bot.cogs import BaseCog +from src.bot.cogs._checks import is_admin, is_master_guild logger = logging.getLogger(__name__) @@ -22,7 +23,7 @@ def embed_info(title, message, color): return embed -class Points(commands.Cog): +class Points(BaseCog): """ This cog manages a points-based system for Discord guild members. @@ -39,6 +40,8 @@ class Points(commands.Cog): def __init__(self, bot: commands.Bot) -> None: """Initialization of the points Class""" + super().__init__(logger) + self.bot = bot @is_admin() diff --git a/src/bot/cogs/admin/settings.py b/src/bot/cogs/admin/settings.py index b7a35bc..3d330d1 100644 --- a/src/bot/cogs/admin/settings.py +++ b/src/bot/cogs/admin/settings.py @@ -4,7 +4,8 @@ import discord from discord.ext import commands -from .._checks import is_admin, is_master_guild +from src.bot.cogs import BaseCog +from src.bot.cogs._checks import is_admin, is_master_guild logger = logging.getLogger(__name__) @@ -18,12 +19,14 @@ def sanitize_string(input_string): return "".join(char for char in input_string if ord(char) < 128) -class Settings(commands.Cog): +class Settings(BaseCog): """ Settings Cog for managing various settings of the bot. """ def __init__(self, bot: commands.Bot) -> None: + super().__init__(logger) + self.bot = bot @is_master_guild() diff --git a/src/bot/cogs/admin/sync.py b/src/bot/cogs/admin/sync.py index 664a738..c13448a 100644 --- a/src/bot/cogs/admin/sync.py +++ b/src/bot/cogs/admin/sync.py @@ -2,13 +2,16 @@ from discord.ext import commands -from .._checks import is_admin, is_master_guild +from src.bot.cogs import BaseCog +from src.bot.cogs._checks import is_admin, is_master_guild logger = logging.getLogger(__name__) -class CommandSync(commands.Cog): +class CommandSync(BaseCog): def __init__(self, bot: commands.Bot) -> None: + super().__init__(logger) + self.bot = bot @commands.command(name="sync") diff --git a/src/bot/cogs/features/general_catfact.py b/src/bot/cogs/features/general_catfact.py index 3292bb5..5e766c4 100644 --- a/src/bot/cogs/features/general_catfact.py +++ b/src/bot/cogs/features/general_catfact.py @@ -7,15 +7,19 @@ import aiohttp from discord.ext import commands +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) -class GeneralCatFact(commands.Cog): +class GeneralCatFact(BaseCog): """ # Hits the catfact API and returns the response. """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot @commands.hybrid_command(name="catfact") diff --git a/src/bot/cogs/features/general_google.py b/src/bot/cogs/features/general_google.py index 379419e..46670f0 100644 --- a/src/bot/cogs/features/general_google.py +++ b/src/bot/cogs/features/general_google.py @@ -6,15 +6,18 @@ from discord.ext import commands +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) -class GeneralSarcasticGoogle(commands.Cog): +class GeneralSarcasticGoogle(BaseCog): """ # Creates a link to the Let me google that for you site. """ def __init__(self, bot): + super().__init__(logger) self.bot = bot @commands.hybrid_command(name="google") diff --git a/src/bot/cogs/features/monthly_yapathon.py b/src/bot/cogs/features/monthly_yapathon.py index c01fb8d..c8680f3 100644 --- a/src/bot/cogs/features/monthly_yapathon.py +++ b/src/bot/cogs/features/monthly_yapathon.py @@ -6,9 +6,11 @@ import logging import os -from discord.ext import commands, tasks +from discord.ext import tasks from discord.utils import get +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) @@ -17,12 +19,14 @@ DATE = 1 -class MonthlyYapathon(commands.Cog): +class MonthlyYapathon(BaseCog): """ This cog deals with all the stuff of monthly yapathon. """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot self.yapper_role_id = self.bot.api.get_one_role("8")[0]["roles"][2] self.announcement_channel_id = self.bot.api.get_one_setting("5")[0]["setting"][ @@ -30,7 +34,9 @@ def __init__(self, bot): ] self.appoint_monthly_yapper.start() - def cog_unload(self): + async def cog_unload(self): + await super().cog_unload() + self.appoint_monthly_yapper.cancel() @tasks.loop(time=TIME) diff --git a/src/bot/cogs/features/run_code.py b/src/bot/cogs/features/run_code.py index e061d67..876af75 100644 --- a/src/bot/cogs/features/run_code.py +++ b/src/bot/cogs/features/run_code.py @@ -11,6 +11,8 @@ from discord.ext.commands import Bot, Context from ultra_piston import File, PistonClient +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) TRUNCATED_MESSAGE: str = "\n```\n```\nOutput too long. Message truncated.\n```" @@ -20,10 +22,12 @@ # Discord character limit is 4096, but to prevent spamming, we reduce it to 1K -class UtilityRunCode(commands.Cog): +class UtilityRunCode(BaseCog): """Uses PistonAPI to run code in the server.""" def __init__(self, bot: Bot): + super().__init__(logger) + self.bot = bot PISTON_API_TOKEN = os.environ.get("PISTON_API_TOKEN") if not PISTON_API_TOKEN: diff --git a/src/bot/cogs/features/ticket.py b/src/bot/cogs/features/ticket.py index 749d932..81d34a6 100644 --- a/src/bot/cogs/features/ticket.py +++ b/src/bot/cogs/features/ticket.py @@ -8,15 +8,19 @@ from discord import app_commands from discord.ext import commands +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) -class AddTicketButton(commands.Cog): +class AddTicketButton(BaseCog): """ This is the slash command that sends our UI element. """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot @app_commands.command(description="Make a ticket and contact the Staff.") diff --git a/src/bot/cogs/logging/logging_avatars.py b/src/bot/cogs/logging/logging_avatars.py index 0030e2c..1ba538e 100644 --- a/src/bot/cogs/logging/logging_avatars.py +++ b/src/bot/cogs/logging/logging_avatars.py @@ -9,6 +9,8 @@ import discord from discord.ext import commands +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) @@ -27,12 +29,14 @@ def embed_avatar(before, after): return embed -class LoggingAvatars(commands.Cog): +class LoggingAvatars(BaseCog): """ Simple listener to on_user_update """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot self.user_log = self.bot.api.get_one_log_setting("4") # User_log diff --git a/src/bot/cogs/logging/logging_member_ban.py b/src/bot/cogs/logging/logging_member_ban.py index 9dfaaf3..eee5701 100644 --- a/src/bot/cogs/logging/logging_member_ban.py +++ b/src/bot/cogs/logging/logging_member_ban.py @@ -9,6 +9,8 @@ import discord from discord.ext import commands +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) @@ -25,13 +27,15 @@ def embed_ban(some_member, audit_log_entry): return embed -class LoggingBans(commands.Cog): +class LoggingBans(BaseCog): """ Simple listener to on_member_remove then checks the audit log for exact details """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot self.verification_role = self.bot.api.get_one_role("6")[0]["roles"][ 2 diff --git a/src/bot/cogs/logging/logging_member_kick.py b/src/bot/cogs/logging/logging_member_kick.py index 23bd971..5d25cc3 100644 --- a/src/bot/cogs/logging/logging_member_kick.py +++ b/src/bot/cogs/logging/logging_member_kick.py @@ -9,6 +9,8 @@ import discord from discord.ext import commands +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) @@ -26,13 +28,15 @@ def embed_kick(some_member, audit_log_entry): return embed -class LoggingKicks(commands.Cog): +class LoggingKicks(BaseCog): """ Simple listener to on_member_remove then checks the audit log for exact details """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot self.verification_role = self.bot.api.get_one_role("6")[0]["roles"][ 2 diff --git a/src/bot/cogs/logging/logging_member_leaves.py b/src/bot/cogs/logging/logging_member_leaves.py index 166f9d2..fda1a25 100644 --- a/src/bot/cogs/logging/logging_member_leaves.py +++ b/src/bot/cogs/logging/logging_member_leaves.py @@ -9,6 +9,8 @@ import discord from discord.ext import commands +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) @@ -25,12 +27,14 @@ def embed_leave(some_member): return embed -class LoggingLeaves(commands.Cog): +class LoggingLeaves(BaseCog): """ Simple listener to on_member_update """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot self.verification_role = self.bot.api.get_one_role("6")[0]["roles"][ 2 diff --git a/src/bot/cogs/logging/logging_message_delete.py b/src/bot/cogs/logging/logging_message_delete.py index 73b16d8..47cf7ff 100644 --- a/src/bot/cogs/logging/logging_message_delete.py +++ b/src/bot/cogs/logging/logging_message_delete.py @@ -9,6 +9,8 @@ import discord from discord.ext import commands +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) @@ -38,13 +40,15 @@ def embed_message_delete(some_member, some_message, some_moderator=None): return embed -class LoggingMessageDelete(commands.Cog): +class LoggingMessageDelete(BaseCog): """ Simple listener to on_message_delete then checks the audit log for exact details """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot self.staff_channel = self.bot.api.get_one_setting("3")[0]["setting"][ 2 diff --git a/src/bot/cogs/logging/logging_message_edit.py b/src/bot/cogs/logging/logging_message_edit.py index 2db38f3..c413862 100644 --- a/src/bot/cogs/logging/logging_message_edit.py +++ b/src/bot/cogs/logging/logging_message_edit.py @@ -9,6 +9,8 @@ import discord from discord.ext import commands +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) @@ -39,12 +41,14 @@ def embed_message_edit( return embed -class LoggingMessageEdit(commands.Cog): +class LoggingMessageEdit(BaseCog): """ Simple listener to on_message_edit """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot self.staff_channel = self.bot.api.get_one_setting("3")[0]["setting"][ 2 diff --git a/src/bot/cogs/logging/logging_name_changes.py b/src/bot/cogs/logging/logging_name_changes.py index 54843d5..3049c21 100644 --- a/src/bot/cogs/logging/logging_name_changes.py +++ b/src/bot/cogs/logging/logging_name_changes.py @@ -9,6 +9,8 @@ import discord from discord.ext import commands +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) @@ -25,12 +27,14 @@ def embed_name_change(username_before, username_after): return embed -class LoggingNameChanges(commands.Cog): +class LoggingNameChanges(BaseCog): """ Simple listener to on_member_update """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot self.user_log = self.bot.api.get_one_log_setting("4") # User_log diff --git a/src/bot/cogs/logging/logging_roles.py b/src/bot/cogs/logging/logging_roles.py index a52ce88..b5105b5 100644 --- a/src/bot/cogs/logging/logging_roles.py +++ b/src/bot/cogs/logging/logging_roles.py @@ -9,6 +9,8 @@ import discord from discord.ext import commands +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) @@ -38,12 +40,14 @@ def embed_role_remove(some_member, member_who_did_action, role_obj): return embed -class LoggingRoles(commands.Cog): +class LoggingRoles(BaseCog): """ Simple listener to on_member_update """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot self.mod_log = self.bot.api.get_one_log_setting("5") # mod_log diff --git a/src/bot/cogs/moderation/admin_automod_spam_messages.py b/src/bot/cogs/moderation/admin_automod_spam_messages.py index dd4130e..1bb9479 100644 --- a/src/bot/cogs/moderation/admin_automod_spam_messages.py +++ b/src/bot/cogs/moderation/admin_automod_spam_messages.py @@ -7,6 +7,8 @@ import discord.errors from discord.ext import commands +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) DRAIN_SECONDS_PER_TOKEN = 60 * 20 # 1 token evaporated every 20 minutes WARN_THRESHOLD = 3 @@ -55,7 +57,7 @@ def embed_spammer_quarantine(spammer, message_to_report=None, file_url=None): return embed -class ModerationSpamMessages(commands.Cog): +class ModerationSpamMessages(BaseCog): """ A cog for detecting and moderating spam messages in Discord channels. @@ -72,6 +74,9 @@ def __init__(self, bot): Args: bot (commands.Bot): The bot instance to which this cog is added. """ + + super().__init__(logger) + self.bot = bot self.records = {} self.warn_message = "Hello there!" diff --git a/src/bot/cogs/moderation/admin_ban.py b/src/bot/cogs/moderation/admin_ban.py index 8806d4a..46557ba 100644 --- a/src/bot/cogs/moderation/admin_ban.py +++ b/src/bot/cogs/moderation/admin_ban.py @@ -9,7 +9,8 @@ from discord import app_commands from discord.ext import commands -from .._checks import is_master_guild, is_moderator +from src.bot.cogs import BaseCog +from src.bot.cogs._checks import is_master_guild, is_moderator logger = logging.getLogger(__name__) @@ -27,12 +28,14 @@ def embed_info(message): return embed -class AdminBan(commands.Cog): +class AdminBan(BaseCog): """ Command to ban a user. Takes in a name, and a reason. """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot @is_moderator() diff --git a/src/bot/cogs/moderation/admin_emergencey.py b/src/bot/cogs/moderation/admin_emergencey.py index c2756be..bcd0666 100644 --- a/src/bot/cogs/moderation/admin_emergencey.py +++ b/src/bot/cogs/moderation/admin_emergencey.py @@ -4,7 +4,8 @@ import discord from discord.ext import commands -from .._checks import is_admin, is_master_guild +from src.bot.cogs import BaseCog +from src.bot.cogs._checks import is_admin, is_master_guild logger = logging.getLogger(__name__) @@ -22,8 +23,10 @@ def embed_info(message): return embed -class AdminEmergencey(commands.Cog, command_attrs=dict(hidden=True)): +class AdminEmergencey(BaseCog, command_attrs=dict(hidden=True)): def __init__(self, bot): + super().__init__(logger) + self.bot = bot @is_master_guild() diff --git a/src/bot/cogs/moderation/admin_kick.py b/src/bot/cogs/moderation/admin_kick.py index fb3a483..0c76edd 100644 --- a/src/bot/cogs/moderation/admin_kick.py +++ b/src/bot/cogs/moderation/admin_kick.py @@ -9,7 +9,8 @@ from discord import app_commands from discord.ext import commands -from .._checks import is_master_guild, is_moderator +from src.bot.cogs import BaseCog +from src.bot.cogs._checks import is_master_guild, is_moderator logger = logging.getLogger(__name__) @@ -27,12 +28,14 @@ def embed_info(message): return embed -class AdminKick(commands.Cog): +class AdminKick(BaseCog): """ Command to kick a user. Takes in a name, and a reason. """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot @is_moderator() diff --git a/src/bot/cogs/moderation/admin_mute.py b/src/bot/cogs/moderation/admin_mute.py index 87bae16..7af3cac 100644 --- a/src/bot/cogs/moderation/admin_mute.py +++ b/src/bot/cogs/moderation/admin_mute.py @@ -9,7 +9,8 @@ from discord import app_commands from discord.ext import commands -from .._checks import is_master_guild, is_moderator +from src.bot.cogs import BaseCog +from src.bot.cogs._checks import is_master_guild, is_moderator logger = logging.getLogger(__name__) @@ -27,12 +28,14 @@ def embed_info(message): return embed -class AdminMute(commands.Cog): +class AdminMute(BaseCog): """ Command to ban a user. Takes in a name, and a reason. """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot @is_moderator() diff --git a/src/bot/cogs/moderation/admin_purge.py b/src/bot/cogs/moderation/admin_purge.py index 59b9959..0692148 100644 --- a/src/bot/cogs/moderation/admin_purge.py +++ b/src/bot/cogs/moderation/admin_purge.py @@ -9,7 +9,8 @@ from discord import app_commands from discord.ext import commands -from .._checks import is_master_guild, is_moderator +from src.bot.cogs import BaseCog +from src.bot.cogs._checks import is_master_guild, is_moderator logger = logging.getLogger(__name__) @@ -39,13 +40,15 @@ def logging_is_activated(request): return True -class AdminPurge(commands.Cog): +class AdminPurge(BaseCog): """ We are limited to 100 messages per command by Discord API TODO: This command should be hidden from non-staff users. """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot self.log_channel_req = self.bot.api.get_one_log_setting("3") # chat_log diff --git a/src/bot/cogs/moderation/admin_quarantine.py b/src/bot/cogs/moderation/admin_quarantine.py index 033bd45..d711e06 100644 --- a/src/bot/cogs/moderation/admin_quarantine.py +++ b/src/bot/cogs/moderation/admin_quarantine.py @@ -11,7 +11,8 @@ from discord.ext import commands from discord.utils import get -from .._checks import is_master_guild, is_moderator +from src.bot.cogs import BaseCog +from src.bot.cogs._checks import is_master_guild, is_moderator logger = logging.getLogger(__name__) @@ -62,12 +63,14 @@ def embed_quarantine(moderator, some_member, number_of_removed_messages): return embed -class AdminQuarantine(commands.Cog): +class AdminQuarantine(BaseCog): """ Command to quarantine a user. """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot self.naughty_role = self.bot.api.get_one_role("7")[0]["roles"][ 2 diff --git a/src/bot/cogs/verification/verification_dropdown.py b/src/bot/cogs/verification/verification_dropdown.py index 7df09b7..0e5de1a 100644 --- a/src/bot/cogs/verification/verification_dropdown.py +++ b/src/bot/cogs/verification/verification_dropdown.py @@ -13,6 +13,8 @@ import discord from discord.ext import commands +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) @@ -106,13 +108,15 @@ def __init__(self, bot): self.add_item(VerificationSelector(bot)) -class Verification(commands.Cog): +class Verification(BaseCog): """ This is the class that defines the actual slash command. It uses the view above to execute actual logic. """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot # Passed in from main.py self.join_log = self.bot.api.get_one_log_setting("4")[0]["logging"][2] self.verification_channel = self.bot.api.get_one_setting("1")[0]["setting"][2] diff --git a/src/bot/cogs/verification/verification_on_join.py b/src/bot/cogs/verification/verification_on_join.py index e2d2c3a..f453362 100644 --- a/src/bot/cogs/verification/verification_on_join.py +++ b/src/bot/cogs/verification/verification_on_join.py @@ -9,10 +9,12 @@ import discord from discord.ext import commands +from src.bot.cogs import BaseCog + logger = logging.getLogger(__name__) -class LoggingVerification(commands.Cog): +class LoggingVerification(BaseCog): """ Handled with a role, and a message. the role limits the user to one channel, with a verify button. @@ -20,6 +22,8 @@ class LoggingVerification(commands.Cog): """ def __init__(self, bot): + super().__init__(logger) + self.bot = bot self.verification_channel = self.bot.api.get_one_setting("1")[0]["setting"][2] self.verification_log = self.bot.api.get_one_log_setting("1")[0]["logging"][2] diff --git a/src/bot/main.py b/src/bot/main.py index 9b993b0..3602fca 100644 --- a/src/bot/main.py +++ b/src/bot/main.py @@ -4,10 +4,11 @@ from pathlib import Path import discord -from __logger__ import setup_logger -from core.api_helper import API from discord.ext import commands +from src.bot.__logger__ import setup_logger +from src.bot.core.api_helper import API + discord.VoiceClient.warn_nacl = False logger = logging.getLogger(__name__) setup_logger( @@ -34,20 +35,41 @@ async def load_cogs(robot: commands.Bot) -> None: """ logger.info("Loading Cogs...") + base_dir = Path(__file__).parent.parent.parent cogs_path = Path(__file__).parent / "cogs" + cogs_loaded = 0 + cogs_failed = 0 for directory in cogs_path.iterdir(): if directory.is_dir() and not directory.name.startswith("_"): for file in directory.glob("*.py"): if not file.stem.startswith("_"): - logger.info(f"\\{directory.name}\\{file.name}") + relative_path = file.relative_to(base_dir) + module_name = ( + str(relative_path).replace(os.sep, ".").replace(".py", "") + ) + try: - await robot.load_extension(f"cogs.{directory.name}.{file.stem}") + await robot.load_extension(module_name) + cogs_loaded += 1 + except commands.ExtensionAlreadyLoaded: + logger.warning(f"Already loaded: {module_name}") + except commands.ExtensionFailed as e: + logger.warning(f"Failed to load {module_name}: {e}") + cogs_failed += 1 + except commands.NoEntryPointError: + logger.warning(f"No setup() function in: {module_name}") + cogs_failed += 1 except Exception as e: - logger.warning("- - - Cog failed to load!!") - logger.warning(f"- - - {e}") - - logger.info("... Success.") + logger.warning( + f"Unexpected error loading {module_name}: {e}", + exc_info=True, + stack_info=True, + ) + cogs_failed += 1 + + logger.info("Loaded: %s | Failed: %s", cogs_loaded, cogs_failed) + logger.info("Finished loading cogs.") @bot.event