From 72fb3f2a7dd9f46e9f6a9d89b5182a3f8f0cbeba Mon Sep 17 00:00:00 2001 From: kami Date: Sat, 1 Feb 2025 11:07:05 +0000 Subject: [PATCH] Upload files to "/" --- bots.py | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 bots.py diff --git a/bots.py b/bots.py new file mode 100644 index 0000000..b05470e --- /dev/null +++ b/bots.py @@ -0,0 +1,129 @@ +# 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}")