131 lines
4.1 KiB
Python
131 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
|
|
###############################
|
|
|
|
global bot_start_time
|
|
bot_start_time = time.time()
|
|
async def main():
|
|
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}")
|