OokamiPupV2/bots.py

130 lines
4.1 KiB
Python

# bots.py
import os
import json
import asyncio
import logging
import sys
import traceback
import time
from collections import Counter
from discord.ext import commands
from dotenv import load_dotenv
from bot_discord import DiscordBot
from bot_twitch import TwitchBot
# 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)
# Global settings
ERROR_LOG_FILE = "error_log.txt"
DISCORD_USER_ID = os.getenv("DISCORD_OWNER_ID") # Your Discord user ID for DM alerts
DISCORD_BOT_TOKEN = os.getenv("DISCORD_BOT_TOKEN") # Discord bot token
ERROR_CACHE = [] # Stores pending errors
LAST_ERROR_SENT = 0 # Timestamp of last Discord error message
ERROR_COOLDOWN = 900 # 15 minutes (900 sec) cooldown between Discord reports
ERROR_BATCH_TIMEOUT = 5 # 5 seconds batching delay before sending
# Setup logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
stream=sys.stdout
)
logger = logging.getLogger("Main")
###############################
# Error Handling & Discord Alerts
###############################
async def send_discord_error_report(bot):
"""
Sends cached errors as a Discord DM with cooldowns and batching.
If it fails, logs to error_log.txt instead.
"""
global ERROR_CACHE, LAST_ERROR_SENT
if not ERROR_CACHE:
return # No errors to send
# Wait for batching timeout
await asyncio.sleep(ERROR_BATCH_TIMEOUT)
# Compress duplicate errors
error_counter = Counter(ERROR_CACHE)
compressed_errors = [f"{err} (Occurred {count} times)" if count > 1 else err for err, count in error_counter.items()]
error_message = "\n".join(compressed_errors)
# Ensure message fits in Discord limits
if len(error_message) > 2000:
error_message = error_message[:1990] + "\n... (truncated)"
# Update timestamp
LAST_ERROR_SENT = time.time()
try:
user = await bot.fetch_user(DISCORD_USER_ID)
await user.send(f"**Bot Error Report**\n\n{error_message}")
logger.info(f"[ERROR REPORT] Sent {len(ERROR_CACHE)} errors at {time.strftime('%H:%M:%S')} | Discord Send Status: Success")
except Exception as e:
# If Discord DM fails, log errors to a file
with open(ERROR_LOG_FILE, "a") as f:
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
f.write(f"\n----- {len(ERROR_CACHE)} Errors Occurred at {timestamp} -----\n")
for err in ERROR_CACHE:
f.write(f"{err}\n")
logger.error(f"[ERROR REPORT] Failed to send Discord DM. Errors saved to {ERROR_LOG_FILE}")
# Clear cache after sending
ERROR_CACHE = []
def log_error(error_msg):
"""
Handles error logging with cooldowns.
If multiple errors occur in a short time, they are cached and sent in batch.
"""
global ERROR_CACHE, LAST_ERROR_SENT
ERROR_CACHE.append(error_msg)
if time.time() - LAST_ERROR_SENT >= ERROR_COOLDOWN:
try:
discord_bot
except NameError:
discord_bot = DiscordBot(config_data, logger)
asyncio.create_task(send_discord_error_report(discord_bot))
###############################
# Main Event Loop
###############################
async def main():
bot_start_time = time.time()
global discord_bot, twitch_bot
discord_bot = DiscordBot(config_data, logger)
twitch_bot = TwitchBot(config_data, logger)
discord_task = asyncio.create_task(discord_bot.run(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_error(f"Fatal Error: {e}\n{error_trace}")