From f28ae00d7d4bcab06216c03b3d92d7e05373398e Mon Sep 17 00:00:00 2001 From: neoarz Date: Sun, 21 Sep 2025 08:47:15 -0400 Subject: [PATCH] feat(shutdown): Improve bot shutdown handling and logging Added graceful shutdown support via signals in bot.py, improved shutdown command to use SIGTERM, and enhanced logging for shutdown events. Updated UDID command description for clarity. --- CONTRIBUTING.md | 1 + bot.py | 51 +++++++++++++++++++++++++++++++++++++++++- cogs/owner/shutdown.py | 14 +++++++++++- cogs/sidestore/udid.py | 2 +- 4 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ad35e5a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +print("Hello World") diff --git a/bot.py b/bot.py index d2cdb6c..93506c6 100644 --- a/bot.py +++ b/bot.py @@ -1,8 +1,10 @@ +import asyncio import json import logging import os import platform import random +import signal import sys import time @@ -113,6 +115,7 @@ class DiscordBot(commands.Bot): self.bot_prefix = os.getenv("PREFIX") self.invite_link = os.getenv("INVITE_LINK") self.start_time = time.time() + self._shutdown = False async def init_db(self) -> None: async with aiosqlite.connect( @@ -229,6 +232,30 @@ class DiscordBot(commands.Bot): else: return f"{seconds}s" + async def close(self) -> None: + if self._shutdown: + return + self._shutdown = True + + self.logger.info("Starting shutdown process...") + + if self.status_task and not self.status_task.is_being_cancelled(): + self.status_task.cancel() + self.logger.info("Status task cancelled") + + if self.database and self.database.connection: + try: + await self.database.connection.close() + self.logger.warning("Database connection closed") + except Exception as e: + self.logger.error(f"Error closing database connection: {e}") + + try: + await super().close() + self.logger.critical("Bot shutdown complete") + except Exception as e: + self.logger.error(f"Error during bot shutdown: {e}") + async def on_message(self, message: discord.Message) -> None: """ The code in this event is executed every time someone sends a message, with or without the prefix @@ -264,6 +291,11 @@ class DiscordBot(commands.Bot): full_command_name = context.command.qualified_name split = full_command_name.split(" ") executed_command = str(split[0]) + + # Skip logging for shutdown command as it logs manually before shutdown + if executed_command.lower() == "shutdown": + return + if context.guild is not None: self.logger.info( f"Executed {executed_command} command in {context.guild.name} (ID: {context.guild.id}) by {context.author} (ID: {context.author.id})" @@ -336,5 +368,22 @@ 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() -bot.run(os.getenv("TOKEN")) + +if __name__ == "__main__": + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + try: + bot.run(os.getenv("TOKEN")) + except KeyboardInterrupt: + pass + except: + pass diff --git a/cogs/owner/shutdown.py b/cogs/owner/shutdown.py index 12c2c26..54b1165 100644 --- a/cogs/owner/shutdown.py +++ b/cogs/owner/shutdown.py @@ -1,3 +1,5 @@ +import os +import signal import discord from discord.ext import commands from discord.ext.commands import Context @@ -28,6 +30,16 @@ class Shutdown(commands.Cog, name="shutdown"): :param context: The hybrid command context. """ + # Log command execution before shutdown starts + if context.guild is not None: + self.bot.logger.info( + f"Executed shutdown command in {context.guild.name} (ID: {context.guild.id}) by {context.author} (ID: {context.author.id})" + ) + else: + self.bot.logger.info( + f"Executed shutdown command by {context.author} (ID: {context.author.id}) in DMs" + ) + embed = discord.Embed( title="Shutdown", description="Shutting down. Bye! ", @@ -35,7 +47,7 @@ class Shutdown(commands.Cog, name="shutdown"): ).set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") await self.send_embed(context, embed) - await self.bot.close() + os.kill(os.getpid(), signal.SIGTERM) async def cog_command_error(self, context: Context, error) -> None: if isinstance(error, commands.NotOwner): diff --git a/cogs/sidestore/udid.py b/cogs/sidestore/udid.py index 9ecb574..83e19bd 100644 --- a/cogs/sidestore/udid.py +++ b/cogs/sidestore/udid.py @@ -10,7 +10,7 @@ class Udid(commands.Cog, name="udid"): self.bot = bot @commands.hybrid_command( - name="udid", description="Information about SideStore UDID error" + name="udid", description="SideStore could not determine device UDID" ) async def udid(self, context: Context) -> None: embed = discord.Embed(