181 lines
7.1 KiB
Python
181 lines
7.1 KiB
Python
# bots.py
|
|
import os
|
|
import json
|
|
import asyncio
|
|
import sys
|
|
import time
|
|
import traceback
|
|
import globals
|
|
from functools import partial
|
|
|
|
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, ensure_users_table, ensure_chatlog_table, checkenable_db_fk
|
|
|
|
from modules import db, utility
|
|
|
|
# Load environment variables
|
|
load_dotenv()
|
|
|
|
# Load bot configuration
|
|
config_data = globals.load_config_file()
|
|
|
|
# Initiate logfile
|
|
logfile_path = config_data["logging"]["logfile_path"]
|
|
logfile = open(logfile_path, "a")
|
|
cur_logfile_path = f"cur_{logfile_path}"
|
|
cur_logfile = open(cur_logfile_path, "w")
|
|
if not config_data["logging"]["terminal"]["log_to_terminal"] and not config_data["logging"]["file"]["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["logging"]["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
|
|
# 'FATAL' errors override settings
|
|
# Checks config file to see enabled/disabled logging levels
|
|
if config_data["logging"]["terminal"]["log_to_terminal"] or level == "FATAL":
|
|
config_level_format = f"log_{level.lower()}"
|
|
if config_data["logging"]["terminal"][config_level_format] or level == "FATAL":
|
|
print(log_message)
|
|
|
|
# Write to file if enabled
|
|
# 'FATAL' errors override settings
|
|
# Checks config file to see enabled/disabled logging levels
|
|
if config_data["logging"]["file"]["log_to_file"] or level == "FATAL":
|
|
config_level_format = f"log_{level.lower()}"
|
|
if config_data["logging"]["file"][config_level_format] or level == "FATAL":
|
|
try:
|
|
lf = config_data["logging"]["logfile_path"]
|
|
clf = f"cur_{lf}"
|
|
with open(lf, "a", encoding="utf-8") as logfile: # Write to permanent logfile
|
|
logfile.write(f"{log_message}\n")
|
|
logfile.flush() # Ensure it gets written immediately
|
|
with open(clf, "a", encoding="utf-8") as c_logfile: # Write to this-run logfile
|
|
c_logfile.write(f"{log_message}\n")
|
|
c_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":
|
|
print(f"!!! FATAL ERROR LOGGED, SHUTTING DOWN !!!")
|
|
sys.exit(1)
|
|
|
|
###############################
|
|
# Main Event Loop
|
|
###############################
|
|
|
|
async def main():
|
|
global discord_bot, twitch_bot, db_conn
|
|
|
|
# Log initial start
|
|
log("--------------- BOT STARTUP ---------------")
|
|
# Before creating your DiscordBot/TwitchBot, initialize DB
|
|
try:
|
|
db_conn = db.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)
|
|
except Exception as e:
|
|
log(f"Unable to initialize database!: {e}", "FATAL")
|
|
|
|
try: # Ensure FKs are enabled
|
|
db.checkenable_db_fk(db_conn, log)
|
|
except Exception as e:
|
|
log(f"Unable to ensure Foreign keys are enabled: {e}", "WARNING")
|
|
# auto-create the quotes table if it doesn't exist
|
|
tables = {
|
|
"Bot events table": partial(db.ensure_bot_events_table, db_conn, log),
|
|
"Quotes table": partial(db.ensure_quotes_table, db_conn, log),
|
|
"Users table": partial(db.ensure_users_table, db_conn, log),
|
|
"Chatlog table": partial(db.ensure_chatlog_table, db_conn, log),
|
|
"Howls table": partial(db.ensure_userhowls_table, db_conn, log),
|
|
"Discord activity table": partial(db.ensure_discord_activity_table, db_conn, log),
|
|
"Account linking table": partial(db.ensure_link_codes_table, db_conn, log)
|
|
}
|
|
|
|
try:
|
|
for table, func in tables.items():
|
|
func() # Call the function with db_conn and log already provided
|
|
log(f"{table} ensured.", "DEBUG")
|
|
except Exception as e:
|
|
log(f"Unable to ensure DB tables exist: {e}", "FATAL")
|
|
|
|
log("Initializing bots...")
|
|
|
|
# Create both bots
|
|
discord_bot = DiscordBot(config_data, log)
|
|
twitch_bot = TwitchBot(config_data, log)
|
|
|
|
# Log startup
|
|
utility.log_bot_startup(db_conn, 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")
|
|
except Exception as e:
|
|
log(f"Unable to initialize database connection to one or both bots: {e}", "FATAL")
|
|
|
|
log("Starting Discord and Twitch bots...")
|
|
|
|
discord_task = asyncio.create_task(discord_bot.run(os.getenv("DISCORD_BOT_TOKEN")))
|
|
twitch_task = asyncio.create_task(twitch_bot.run())
|
|
|
|
from modules.utility import dev_func
|
|
enable_dev_func = False
|
|
if enable_dev_func:
|
|
dev_func_result = dev_func(db_conn, log, enable_dev_func)
|
|
log(f"dev_func output: {dev_func_result}")
|
|
|
|
await asyncio.gather(discord_task, twitch_task)
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
asyncio.run(main())
|
|
except KeyboardInterrupt:
|
|
utility.log_bot_shutdown(db_conn, log, intent="User Shutdown")
|
|
except Exception as e:
|
|
error_trace = traceback.format_exc()
|
|
log(f"Fatal Error: {e}\n{error_trace}", "FATAL")
|
|
utility.log_bot_shutdown(db_conn, log)
|