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(