import asyncio import threading import discord from discord.ext import commands import os from dotenv import load_dotenv import sys import asyncio import subprocess import importlib.util import argparse import logging import asyncpg import redis.asyncio as aioredis from commands import load_all_cogs, reload_all_cogs # May need to modify or create a new load function from error_handler import handle_error, patch_discord_methods, store_interaction_content from utils import reload_script import settings_manager from db import mod_log_db from global_bot_accessor import set_bot_instance # Load environment variables from .env file load_dotenv() # --- Constants --- DEFAULT_PREFIX = "!" # Define the specific cogs for this bot FEMDOM_TETO_COGS = {'cogs.femdom_teto_cog', 'cogs.femdom_roleplay_teto_cog'} # --- Dynamic Prefix Function --- async def get_prefix(bot_instance, message): """Determines the command prefix based on guild settings or default, but disables mention as prefix.""" if not message.guild: # Use default prefix in DMs return DEFAULT_PREFIX # Fetch prefix from settings manager (cache first, then DB) # This bot might need its own prefix setting or share the main bot's # For simplicity, let's use a fixed prefix for now or a different setting key # Using a fixed prefix for this specific bot return "!" # Or a different prefix like "fd!" # --- Bot Setup --- # Set up intents (permissions) intents = discord.Intents.default() intents.message_content = True intents.members = True # --- Custom Bot Class with setup_hook for async initialization --- class FemdomTetoBot(commands.Bot): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.owner_id = int(os.getenv('OWNER_USER_ID')) # Assuming owner ID is the same self.pg_pool = None # Will be initialized in setup_hook self.redis = None # Will be initialized in setup_hook async def setup_hook(self): log.info("Running FemdomTetoBot setup_hook...") # Create Postgres pool on this loop # This bot might need its own DB or share the main bot's. Sharing is simpler for now. self.pg_pool = await asyncpg.create_pool( dsn=settings_manager.DATABASE_URL, min_size=1, max_size=10, loop=self.loop ) log.info("Postgres pool initialized and attached to bot.pg_pool.") # Create Redis client on this loop # This bot might need its own Redis or share the main bot's. Sharing is simpler for now. self.redis = await aioredis.from_url( settings_manager.REDIS_URL, max_connections=10, decode_responses=True, ) log.info("Redis client initialized and attached to bot.redis.") # This bot instance also needs to be accessible for settings_manager if it uses it # Need to decide if this bot uses the same settings as the main bot or has its own # For now, let's assume it might need access to settings_manager # set_bot_instance(self) # This would overwrite the main bot instance, need a different approach if both run simultaneously # Initialize database schema and run migrations using settings_manager # Only the main bot should likely do this. This bot will just use the existing DB. # if self.pg_pool and self.redis: # try: # await settings_manager.initialize_database() # await settings_manager.run_migrations() # except Exception as e: # log.exception("CRITICAL: Failed during settings_manager database setup (init/migrations).") # Setup the moderation log table *after* pool initialization # Only the main bot should likely do this. # if self.pg_pool: # try: # await mod_log_db.setup_moderation_log_table(self.pg_pool) # except Exception as e: # log.exception("CRITICAL: Failed to setup moderation log table in setup_hook.") # Load only the specific cogs for this bot for cog_extension in FEMDOM_TETO_COGS: try: await self.load_extension(cog_extension) log.info(f"Successfully loaded cog: {cog_extension}") except commands.ExtensionAlreadyLoaded: log.info(f"Cog already loaded: {cog_extension}") except commands.ExtensionNotFound: log.error(f"Cog not found: {cog_extension}") except Exception as e: log.exception(f"Failed to load cog {cog_extension}: {e}") log.info(f"Specific cogs loading attempted for: {FEMDOM_TETO_COGS}") log.info("FemdomTetoBot setup_hook completed.") # Create bot instance using the custom class # This bot will use a different token femdom_teto_bot = FemdomTetoBot(command_prefix=get_prefix, intents=intents) # --- Logging Setup --- logging.basicConfig(level=logging.INFO, format='%(asctime)s:%(levelname)s:%(name)s: %(message)s') log = logging.getLogger(__name__) # Logger for this script # --- Events --- @femdom_teto_bot.event async def on_ready(): log.info(f'{femdom_teto_bot.user.name} has connected to Discord!') log.info(f'Bot ID: {femdom_teto_bot.user.id}') # Set the bot's status await femdom_teto_bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name="for commands")) log.info("Bot status set.") # Patch Discord methods to store message content try: patch_discord_methods() print("Discord methods patched to store message content for error handling") # Make the store_interaction_content function available globally import builtins builtins.store_interaction_content = store_interaction_content print("Made store_interaction_content available globally") except Exception as e: print(f"Warning: Failed to patch Discord methods: {e}") import traceback traceback.print_exc() # Sync commands - This bot only has specific commands from its cogs try: print("Starting command sync process for FemdomTetoBot...") # Sync commands globally or per guild as needed for these specific cogs # For simplicity, let's sync globally for now if the commands are global app commands await femdom_teto_bot.tree.sync() print("Global command sync complete for FemdomTetoBot.") except Exception as e: print(f"Failed to sync commands for FemdomTetoBot: {e}") import traceback traceback.print_exc() # Error handling - Use the same handler @femdom_teto_bot.event async def on_command_error(ctx, error): await handle_error(ctx, error) @femdom_teto_bot.tree.error async def on_app_command_error(interaction, error): await handle_error(interaction, error) # --- Global Command Checks --- # Need to decide if this bot uses the same global checks or different ones # For now, let's skip global checks for simplicity or adapt them if needed # @femdom_teto_bot.before_invoke # async def global_command_checks(ctx: commands.Context): # pass # Implement checks if necessary async def main(): """Main async function to load cogs and start the bot.""" TOKEN = os.getenv('FEMDOM_TETO_DISCORD_TOKEN') # Use a different token if not TOKEN: raise ValueError("No FEMDOM_TETO_DISCORD_TOKEN found. Make sure to set FEMDOM_TETO_DISCORD_TOKEN in your .env file.") # This bot likely doesn't need to start the Flask or unified API servers # if API_AVAILABLE: # print("Starting unified API service...") # try: # api_thread = start_api_in_thread() # print("Unified API service started successfully") # except Exception as e: # print(f"Failed to start unified API service: {e}") try: # The bot will call setup_hook internally after login but before on_ready. await femdom_teto_bot.start(TOKEN) except Exception as e: log.exception(f"An error occurred during femdom_teto_bot.start(): {e}") finally: # Close database/cache pools if they were initialized if femdom_teto_bot.pg_pool: log.info("Closing Postgres pool in main finally block...") await femdom_teto_bot.pg_pool.close() if femdom_teto_bot.redis: log.info("Closing Redis pool in main finally block...") await femdom_teto_bot.redis.close() if not femdom_teto_bot.pg_pool and not femdom_teto_bot.redis: log.info("Pools were not initialized or already closed, skipping close_pools in main.") # Run the main async function if __name__ == '__main__': try: asyncio.run(main()) except KeyboardInterrupt: log.info("Femdom Teto Bot stopped by user.") except Exception as e: log.exception(f"An error occurred running the Femdom Teto bot: {e}")