From 4948d2edf756bb75ecaecf07c9ea845660677926 Mon Sep 17 00:00:00 2001 From: neoarz Date: Sun, 28 Sep 2025 16:20:28 -0400 Subject: [PATCH] refactor(bot): bot utilities into separate modules Moved logging, signal handling, and uptime calculation logic from bot.py into dedicated utils modules for better organization and reusability. Updated imports and usage in bot.py and utils/__init__.py accordingly. --- bot.py | 76 +++-------------------------------------------- utils/__init__.py | 8 ++++- utils/logging.py | 54 +++++++++++++++++++++++++++++++++ utils/signal.py | 13 ++++++++ utils/time.py | 18 +++++++++++ 5 files changed, 96 insertions(+), 73 deletions(-) create mode 100644 utils/logging.py create mode 100644 utils/signal.py create mode 100644 utils/time.py diff --git a/bot.py b/bot.py index 6481414..1605fac 100644 --- a/bot.py +++ b/bot.py @@ -15,7 +15,7 @@ from discord.ext.commands import Context from dotenv import load_dotenv from database import DatabaseManager -from utils.ascii_art import ascii +from utils import ascii, setup_logger, get_uptime, setup_signal_handlers load_dotenv() @@ -50,54 +50,7 @@ intents.messages = True -class LoggingFormatter(logging.Formatter): - # Colors - black = "\x1b[30m" - red = "\x1b[31m" - green = "\x1b[32m" - yellow = "\x1b[33m" - blue = "\x1b[34m" - gray = "\x1b[38m" - # Styles - reset = "\x1b[0m" - bold = "\x1b[1m" - - COLORS = { - logging.DEBUG: gray + bold, - logging.INFO: blue + bold, - logging.WARNING: yellow + bold, - logging.ERROR: red, - logging.CRITICAL: red + bold, - } - - def format(self, record): - log_color = self.COLORS[record.levelno] - format = "(black){asctime}(reset) (levelcolor){levelname:<8}(reset) (green){name}(reset) {message}" - format = format.replace("(black)", self.black + self.bold) - format = format.replace("(reset)", self.reset) - format = format.replace("(levelcolor)", log_color) - format = format.replace("(green)", self.green + self.bold) - formatter = logging.Formatter(format, "%Y-%m-%d %H:%M:%S", style="{") - return formatter.format(record) - - -logger = logging.getLogger("discord_bot") -logger.setLevel(logging.INFO) - -console_handler = logging.StreamHandler() -console_handler.setFormatter(LoggingFormatter()) -log_file_path = os.getenv("LOG_FILE", "logs/discord.log") -log_dir = os.path.dirname(log_file_path) -if log_dir: - os.makedirs(log_dir, exist_ok=True) -file_handler = logging.FileHandler(filename=log_file_path, encoding="utf-8", mode="w") -file_handler_formatter = logging.Formatter( - "[{asctime}] [{levelname:<8}] {name}: {message}", "%Y-%m-%d %H:%M:%S", style="{" -) -file_handler.setFormatter(file_handler_formatter) - -logger.addHandler(console_handler) -logger.addHandler(file_handler) +logger = setup_logger() class DiscordBot(commands.Bot): @@ -223,20 +176,7 @@ class DiscordBot(commands.Bot): ) def get_uptime(self) -> str: - uptime_seconds = int(time.time() - self.start_time) - days = uptime_seconds // 86400 - hours = (uptime_seconds % 86400) // 3600 - minutes = (uptime_seconds % 3600) // 60 - seconds = uptime_seconds % 60 - - if days > 0: - return f"{days}d {hours}h {minutes}m {seconds}s" - elif hours > 0: - return f"{hours}h {minutes}m {seconds}s" - elif minutes > 0: - return f"{minutes}m {seconds}s" - else: - return f"{seconds}s" + return get_uptime(self.start_time) async def close(self) -> None: if self._shutdown: @@ -374,13 +314,6 @@ class DiscordBot(commands.Bot): raise error -def signal_handler(signum, frame): - logger.info("Shutdown requested. Closing bot...") - if bot.loop and not bot.loop.is_closed(): - asyncio.create_task(bot.close()) - bot.loop.call_soon_threadsafe(bot.loop.stop) - - bot = DiscordBot() if __name__ == "__main__": @@ -388,8 +321,7 @@ if __name__ == "__main__": print(ascii) - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) + setup_signal_handlers(bot) try: bot.run(os.getenv("TOKEN")) diff --git a/utils/__init__.py b/utils/__init__.py index 9cffff4..74c5cac 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,3 +1,9 @@ from .ascii_art import ascii, ascii_plain, gradient_text, gradient_text_selective +from .logging import LoggingFormatter, setup_logger +from .time import get_uptime +from .signal import setup_signal_handlers -__all__ = ['ascii', 'ascii_plain', 'gradient_text', 'gradient_text_selective'] \ No newline at end of file +__all__ = [ + 'ascii', 'ascii_plain', 'gradient_text', 'gradient_text_selective', + 'LoggingFormatter', 'setup_logger', 'get_uptime', 'setup_signal_handlers' +] \ No newline at end of file diff --git a/utils/logging.py b/utils/logging.py new file mode 100644 index 0000000..c5c86ec --- /dev/null +++ b/utils/logging.py @@ -0,0 +1,54 @@ +import logging +import os + + +class LoggingFormatter(logging.Formatter): + black = "\x1b[30m" + red = "\x1b[31m" + green = "\x1b[32m" + yellow = "\x1b[33m" + blue = "\x1b[34m" + gray = "\x1b[38m" + reset = "\x1b[0m" + bold = "\x1b[1m" + + COLORS = { + logging.DEBUG: gray + bold, + logging.INFO: blue + bold, + logging.WARNING: yellow + bold, + logging.ERROR: red, + logging.CRITICAL: red + bold, + } + + def format(self, record): + log_color = self.COLORS[record.levelno] + format = "(black){asctime}(reset) (levelcolor){levelname:<8}(reset) (green){name}(reset) {message}" + format = format.replace("(black)", self.black + self.bold) + format = format.replace("(reset)", self.reset) + format = format.replace("(levelcolor)", log_color) + format = format.replace("(green)", self.green + self.bold) + formatter = logging.Formatter(format, "%Y-%m-%d %H:%M:%S", style="{") + return formatter.format(record) + + +def setup_logger(): + logger = logging.getLogger("discord_bot") + logger.setLevel(logging.INFO) + + console_handler = logging.StreamHandler() + console_handler.setFormatter(LoggingFormatter()) + + log_file_path = os.getenv("LOG_FILE", "logs/discord.log") + log_dir = os.path.dirname(log_file_path) + if log_dir: + os.makedirs(log_dir, exist_ok=True) + file_handler = logging.FileHandler(filename=log_file_path, encoding="utf-8", mode="w") + file_handler_formatter = logging.Formatter( + "[{asctime}] [{levelname:<8}] {name}: {message}", "%Y-%m-%d %H:%M:%S", style="{" + ) + file_handler.setFormatter(file_handler_formatter) + + logger.addHandler(console_handler) + logger.addHandler(file_handler) + + return logger diff --git a/utils/signal.py b/utils/signal.py new file mode 100644 index 0000000..678f1f8 --- /dev/null +++ b/utils/signal.py @@ -0,0 +1,13 @@ +import asyncio +import signal + + +def setup_signal_handlers(bot): + def signal_handler(signum, frame): + bot.logger.info("Shutdown requested. Closing bot...") + if bot.loop and not bot.loop.is_closed(): + asyncio.create_task(bot.close()) + bot.loop.call_soon_threadsafe(bot.loop.stop) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) diff --git a/utils/time.py b/utils/time.py new file mode 100644 index 0000000..51adcdc --- /dev/null +++ b/utils/time.py @@ -0,0 +1,18 @@ +import time + + +def get_uptime(start_time: float) -> str: + uptime_seconds = int(time.time() - start_time) + days = uptime_seconds // 86400 + hours = (uptime_seconds % 86400) // 3600 + minutes = (uptime_seconds % 3600) // 60 + seconds = uptime_seconds % 60 + + if days > 0: + return f"{days}d {hours}h {minutes}m {seconds}s" + elif hours > 0: + return f"{hours}h {minutes}m {seconds}s" + elif minutes > 0: + return f"{minutes}m {seconds}s" + else: + return f"{seconds}s"