OokamiPupV2/bots.py

141 lines
4.7 KiB
Python

# bots.py
import os
import json
import asyncio
import sys
import time
import traceback
import globals
from discord.ext import commands
from dotenv import load_dotenv
from bot_discord import DiscordBot
from bot_twitch import TwitchBot
from modules.db import init_db_connection, run_db_operation
from modules.db import ensure_quotes_table
# Load environment variables
load_dotenv()
# Load bot configuration
CONFIG_PATH = "config.json"
try:
with open(CONFIG_PATH, "r") as f:
config_data = json.load(f)
except FileNotFoundError:
print("Error: config.json not found.")
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error parsing config.json: {e}")
sys.exit(1)
# Initiate logfile
logfile_path = config_data["logfile_path"]
logfile = open(logfile_path, "a")
if not config_data["log_to_terminal"] and not config_data["log_to_file"]:
print(f"!!! WARNING !!! CONSOLE AND LOGFILE OUTPUT DISABLED !!!\n!!! NO LOGS WILL BE PROVIDED !!!")
###############################
# Simple Logging System
###############################
def log(message, level="INFO", exec_info=False):
"""
A simple logging function with adjustable log levels.
Logs messages in a structured format.
Available levels:\n
DEBUG = Information useful for debugging\n
INFO = Informational messages\n
WARNING = Something happened that may lead to issues\n
ERROR = A non-critical error has happened\n
CRITICAL = A critical, but non-fatal, error\n
FATAL = Fatal error. Program exits after logging this\n\n
See 'config.json' for disabling/enabling logging levels
"""
from modules import utility
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "FATAL"]
if level not in log_levels:
level = "INFO" # Default to INFO if an invalid level is provided
if level in config_data["log_levels"] or level == "FATAL":
elapsed = time.time() - globals.get_bot_start_time()
uptime_str, _ = utility.format_uptime(elapsed)
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
log_message = f"[{timestamp} - {uptime_str}] [{level}] {message}"
# Include traceback for certain error levels
if exec_info or level in ["CRITICAL", "FATAL"]:
log_message += f"\n{traceback.format_exc()}"
# Print to terminal if enabled
if config_data["log_to_terminal"] or level == "FATAL":
print(log_message)
# Write to file if enabled
if config_data["log_to_file"]:
try:
with open(config_data["logfile_path"], "a", encoding="utf-8") as logfile:
logfile.write(f"{log_message}\n")
logfile.flush() # Ensure it gets written immediately
except Exception as e:
print(f"[WARNING] Failed to write to logfile: {e}")
# Handle fatal errors with shutdown
if level == "FATAL":
if config_data["log_to_terminal"]:
print(f"!!! FATAL ERROR LOGGED, SHUTTING DOWN !!!")
sys.exit(1)
###############################
# Main Event Loop
###############################
async def main():
global discord_bot, twitch_bot, db_conn
# Before creating your DiscordBot/TwitchBot, initialize DB
db_conn = init_db_connection(config_data, log)
if not db_conn:
# If we get None, it means FATAL. We might sys.exit(1) or handle it differently.
log("Terminating bot due to no DB connection.", "FATAL")
sys.exit(1)
# auto-create the quotes table if it doesn't exist
try:
ensure_quotes_table(db_conn, log)
except Exception as e:
log(f"Critical: unable to ensure quotes table: {e}", "FATAL")
log("Initializing bots...", "INFO")
# Create both bots
discord_bot = DiscordBot(config_data, log)
twitch_bot = TwitchBot(config_data, log)
# Provide DB connection to both bots
try:
discord_bot.set_db_connection(db_conn)
twitch_bot.set_db_connection(db_conn)
log(f"Initialized database connection to both bots", "INFO")
except Exception as e:
log(f"Unable to initialize database connection to one or both bots: {e}", "FATAL")
log("Starting Discord and Twitch bots...", "INFO")
discord_task = asyncio.create_task(discord_bot.run(os.getenv("DISCORD_BOT_TOKEN")))
twitch_task = asyncio.create_task(twitch_bot.run())
await asyncio.gather(discord_task, twitch_task)
if __name__ == "__main__":
try:
asyncio.run(main())
except Exception as e:
error_trace = traceback.format_exc()
log(f"Fatal Error: {e}\n{error_trace}", "FATAL")