discordbot/run_femdom_teto_bot.py
2025-06-05 21:31:06 -06:00

238 lines
8.9 KiB
Python

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}")