diff --git a/src/bot/main.py b/src/bot/main.py index 4076f6c..2583f0d 100644 --- a/src/bot/main.py +++ b/src/bot/main.py @@ -1,6 +1,189 @@ -def main(): - print("Hello world!") +import asyncio +import logging +import logging.handlers +import os +import sys -if __name__ == "__main__": - main() +import discord +from discord.ext import commands + +from bot.env import Environment + + +_log: logging.Logger + + +def setup_logging() -> logging.Logger: + root_logger = logging.getLogger(__name__) + + log_level_map = { + "NOTSET": logging.NOTSET, + "DEBUG": logging.DEBUG, + "INFO": logging.INFO, + "WARNING": logging.WARNING, + "ERROR": logging.ERROR, + "CRITICAL": logging.CRITICAL, + } + + env_log_level = Environment.bot_log_level() + log_level: int = log_level_map.get(env_log_level, logging.INFO) + + root_logger.setLevel(log_level) + + dt_fmt = "%Y-%m-%d %H:%M:%S" + + log_formatter = logging.Formatter( + "[{asctime}] [{levelname}] {name}: {message}", dt_fmt, style="{" + ) + + # Log everything to rotating files + log_file_handler = logging.handlers.RotatingFileHandler( + filename="bot.log", encoding="utf-8", maxBytes=32 * 1024 * 1024, backupCount=5 + ) + log_file_handler.setFormatter(log_formatter) + root_logger.addHandler(log_file_handler) + + # Log everything to stderr also + log_stream_handler = logging.StreamHandler() + log_stream_handler.setFormatter(log_formatter) + root_logger.addHandler(log_stream_handler) + + # Also log errors to a separate file for easier debugging + log_error_file_handler = logging.FileHandler( + filename="bot_err.log", encoding="utf-8" + ) + log_error_file_handler.setFormatter(log_formatter) + log_error_file_handler.setLevel(logging.ERROR) + root_logger.addHandler(log_error_file_handler) + + return root_logger + + +def setup_bot() -> commands.Bot: + intents = discord.Intents.all() + + desc_paragraphs = [ + """Der VW Phaeton ist eine viertürige Stufenheck-Limousine der Oberklasse + der Marke Volkswagen. Die Herstellung war ein Gemeinschaftsprojekt des + Volkswagenwerks Zwickau mit der Gläsernen Manufaktur Dresden, wo die + Endmontage großteils in Handarbeit erfolgte. Vom Produktionsstart am + 11. Dezember 2001 bis zum Produktionsende am 18. März 2016 wurden + 84.235 Fahrzeuge gebaut.""", + """In der Nacht zum 11. Oktober 2008 kam Haider in Lambichl im Südwesten + der Landeshauptstadt Klagenfurt ♁(Lage) auf der Loiblpass-Straße bei + einem Verkehrsunfall ums Leben. Nach dem Besuch mehrerer + Veranstaltungen und Gaststätten hatte sich Haider, stark alkoholisiert + (1,8 ‰ Blutalkoholkonzentration), allein auf den Weg zu seinem Haus im + Bärental gemacht. Nach Angaben des Leiters der Staatsanwaltschaft + Klagenfurt fuhr Haider bei Nebel mit stark überhöhter Geschwindigkeit + mit seinem Dienstwagen VW Phaeton V6.""", + ] + + description = "\n\n".join( + " ".join(line.strip() for line in paragraph) for paragraph in desc_paragraphs + ) + + command_prefix = Environment.bot_prefix() + + return commands.Bot( + command_prefix=command_prefix, description=description, intents=intents + ) + + +def read_token_file() -> str: + bot_token_path = Environment.bot_token_path() + + if not bot_token_path.exists(): + raise FileNotFoundError( + f'Cannot read bot token - path "{bot_token_path}" does not exist' + ) + + try: + token = bot_token_path.read_text("utf-8").strip() + return token + + except Exception as e: + _log.error( + 'Failed to read token from path "{bot_token_path}"', exc_info=sys.exc_info() + ) + raise e + + +bot = setup_bot() + + +@bot.event +async def on_error(event: str, *args, **kwargs): + _log.error( + f"Encountered unexpected exception while processing event {event} ({args = }; {kwargs = }):", + exc_info=sys.exc_info(), + ) + + +@bot.event +async def on_ready(): + _log.info("Ready: Connected to Discord") + + +@bot.event +async def on_resumed(): + _log.info("Resumed: Session with Discord resumed") + + +@bot.event +async def on_shard_ready(shard_id: int): + _log.info(f"Ready: Shard ID {shard_id} connected to Discord") + + +@bot.event +async def on_shard_resumed(shard_id: int): + _log.info(f"Resumed: Shard ID {shard_id} resumed session with Discord") + + +@bot.event +async def on_message(message: discord.Message): + _log.debug(f"Read message ({message.id = }): {message.content}") + + +@bot.event +async def on_command(ctx: commands.Context): + _log.debug( + f"Command invoked:" + f" {ctx.command = }; {ctx.message.author = }; {ctx.message.id = }" + ) + + +@bot.event +async def on_command_completion(ctx: commands.Context): + _log.debug( + f"Command completed:" + f" {ctx.command = }; {ctx.message.author = }; {ctx.message.id = }" + ) + + +@bot.event +async def on_command_error(ctx: commands.Context, error: commands.CommandError): + _log.error( + f"Failed to run command:" + f" {ctx.command = }; {ctx.message.author = }; {ctx.message.id = }", + exc_info=error, + ) + + +async def main(): + _log.debug("Entered main()") + + token = read_token_file() + + async with bot: + await bot.start(token=token, reconnect=True) + + +_log = setup_logging() +_log.debug("Logging initialised") + +_log.debug(f"{sys.argv = }") +_log.debug(f"{os.getcwd() = }") + +asyncio.run(main())