ada
This commit is contained in:
parent
3178f95dd8
commit
d43af26cb3
@ -428,14 +428,41 @@ try:
|
||||
# Try relative import first
|
||||
try:
|
||||
from .cog_management_endpoints import router as cog_management_router
|
||||
except ImportError:
|
||||
log.info("Successfully imported cog_management_endpoints via relative import")
|
||||
except ImportError as e:
|
||||
log.warning(f"Relative import of cog_management_endpoints failed: {e}")
|
||||
# Fall back to absolute import
|
||||
from cog_management_endpoints import router as cog_management_router
|
||||
try:
|
||||
from cog_management_endpoints import router as cog_management_router
|
||||
log.info("Successfully imported cog_management_endpoints via absolute import")
|
||||
except ImportError as e2:
|
||||
log.error(f"Both import attempts for cog_management_endpoints failed: {e2}")
|
||||
# Try to import the module directly to see what's available
|
||||
try:
|
||||
import sys
|
||||
log.info(f"Python path: {sys.path}")
|
||||
# Try to find the module in the current directory
|
||||
import os
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
log.info(f"Current directory: {current_dir}")
|
||||
files = os.listdir(current_dir)
|
||||
log.info(f"Files in current directory: {files}")
|
||||
|
||||
# Try to import the module with a full path
|
||||
sys.path.append(current_dir)
|
||||
import cog_management_endpoints
|
||||
log.info(f"Successfully imported cog_management_endpoints module")
|
||||
router = cog_management_endpoints.router
|
||||
log.info(f"Successfully got router from cog_management_endpoints")
|
||||
cog_management_router = router
|
||||
except Exception as e3:
|
||||
log.error(f"Failed to import cog_management_endpoints module: {e3}")
|
||||
raise e2
|
||||
|
||||
# Add the cog management router to the dashboard API app
|
||||
dashboard_api_app.include_router(cog_management_router, tags=["Cog Management"])
|
||||
log.info("Cog management endpoints loaded successfully")
|
||||
except ImportError as e:
|
||||
except Exception as e:
|
||||
log.error(f"Could not import cog management endpoints: {e}")
|
||||
log.error("Cog management endpoints will not be available")
|
||||
|
||||
|
@ -459,36 +459,78 @@ async def get_guild_settings(
|
||||
detail="Settings manager not available"
|
||||
)
|
||||
|
||||
# Get prefix
|
||||
prefix = await settings_manager.get_guild_prefix(guild_id, DEFAULT_PREFIX)
|
||||
|
||||
# Get welcome/goodbye settings
|
||||
welcome_channel_id = await settings_manager.get_setting(guild_id, 'welcome_channel_id')
|
||||
welcome_message = await settings_manager.get_setting(guild_id, 'welcome_message')
|
||||
goodbye_channel_id = await settings_manager.get_setting(guild_id, 'goodbye_channel_id')
|
||||
goodbye_message = await settings_manager.get_setting(guild_id, 'goodbye_message')
|
||||
|
||||
# Get cog enabled statuses
|
||||
cogs_enabled = await settings_manager.get_all_enabled_cogs(guild_id)
|
||||
|
||||
# Get command enabled statuses
|
||||
commands_enabled = await settings_manager.get_all_enabled_commands(guild_id)
|
||||
|
||||
# Construct response
|
||||
# Initialize settings with defaults
|
||||
settings = {
|
||||
"prefix": prefix,
|
||||
"welcome_channel_id": welcome_channel_id,
|
||||
"welcome_message": welcome_message,
|
||||
"goodbye_channel_id": goodbye_channel_id,
|
||||
"goodbye_message": goodbye_message,
|
||||
"cogs": cogs_enabled,
|
||||
"commands": commands_enabled
|
||||
"prefix": DEFAULT_PREFIX,
|
||||
"welcome_channel_id": None,
|
||||
"welcome_message": None,
|
||||
"goodbye_channel_id": None,
|
||||
"goodbye_message": None,
|
||||
"cogs": {},
|
||||
"commands": {}
|
||||
}
|
||||
|
||||
# Get prefix with error handling
|
||||
try:
|
||||
settings["prefix"] = await settings_manager.get_guild_prefix(guild_id, DEFAULT_PREFIX)
|
||||
except Exception as e:
|
||||
log.warning(f"Error getting prefix for guild {guild_id}, using default: {e}")
|
||||
# Keep default prefix
|
||||
|
||||
# Get welcome/goodbye settings with error handling
|
||||
try:
|
||||
settings["welcome_channel_id"] = await settings_manager.get_setting(guild_id, 'welcome_channel_id')
|
||||
except Exception as e:
|
||||
log.warning(f"Error getting welcome_channel_id for guild {guild_id}: {e}")
|
||||
|
||||
try:
|
||||
settings["welcome_message"] = await settings_manager.get_setting(guild_id, 'welcome_message')
|
||||
except Exception as e:
|
||||
log.warning(f"Error getting welcome_message for guild {guild_id}: {e}")
|
||||
|
||||
try:
|
||||
settings["goodbye_channel_id"] = await settings_manager.get_setting(guild_id, 'goodbye_channel_id')
|
||||
except Exception as e:
|
||||
log.warning(f"Error getting goodbye_channel_id for guild {guild_id}: {e}")
|
||||
|
||||
try:
|
||||
settings["goodbye_message"] = await settings_manager.get_setting(guild_id, 'goodbye_message')
|
||||
except Exception as e:
|
||||
log.warning(f"Error getting goodbye_message for guild {guild_id}: {e}")
|
||||
|
||||
# Get cog enabled statuses with error handling
|
||||
try:
|
||||
settings["cogs"] = await settings_manager.get_all_enabled_cogs(guild_id)
|
||||
except Exception as e:
|
||||
log.warning(f"Error getting cog enabled statuses for guild {guild_id}: {e}")
|
||||
# Keep empty dict for cogs
|
||||
|
||||
# Get command enabled statuses with error handling
|
||||
try:
|
||||
settings["commands"] = await settings_manager.get_all_enabled_commands(guild_id)
|
||||
except Exception as e:
|
||||
log.warning(f"Error getting command enabled statuses for guild {guild_id}: {e}")
|
||||
# Keep empty dict for commands
|
||||
|
||||
return settings
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except RuntimeError as e:
|
||||
# Handle event loop errors specifically
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.error(f"Event loop error getting settings for guild {guild_id}: {e}")
|
||||
# Return a more helpful error message
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Database connection error. Please try again."
|
||||
)
|
||||
else:
|
||||
log.error(f"Runtime error getting settings for guild {guild_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error getting settings: {str(e)}"
|
||||
)
|
||||
except Exception as e:
|
||||
log.error(f"Error getting settings for guild {guild_id}: {e}")
|
||||
raise HTTPException(
|
||||
@ -824,8 +866,20 @@ async def update_global_settings(
|
||||
# --- Cog and Command Management Endpoints ---
|
||||
# Note: These endpoints have been moved to cog_management_endpoints.py
|
||||
|
||||
# --- Cog Management Redirect Endpoints ---
|
||||
# These endpoints redirect to the cog management endpoints in cog_management_endpoints.py
|
||||
# --- Cog Management Endpoints ---
|
||||
# These endpoints provide direct implementation and fallback for cog management
|
||||
|
||||
# Define models needed for cog management
|
||||
class CogCommandInfo(BaseModel):
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
enabled: bool = True
|
||||
|
||||
class CogInfo(BaseModel):
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
enabled: bool = True
|
||||
commands: List[Dict[str, Any]] = []
|
||||
|
||||
@router.get("/guilds/{guild_id}/cogs", response_model=List[Any])
|
||||
async def get_guild_cogs_redirect(
|
||||
@ -833,18 +887,93 @@ async def get_guild_cogs_redirect(
|
||||
_user: dict = Depends(get_dashboard_user),
|
||||
_admin: bool = Depends(verify_dashboard_guild_admin)
|
||||
):
|
||||
"""Redirect to the cog management endpoint."""
|
||||
"""Get all cogs and their commands for a guild."""
|
||||
try:
|
||||
# Import the cog management endpoint
|
||||
# First try to use the dedicated cog management endpoint
|
||||
try:
|
||||
# Try relative import first
|
||||
from .cog_management_endpoints import get_guild_cogs
|
||||
except ImportError:
|
||||
from cog_management_endpoints import get_guild_cogs
|
||||
log.info(f"Successfully imported get_guild_cogs via relative import")
|
||||
|
||||
# Call the cog management endpoint
|
||||
return await get_guild_cogs(guild_id, _user, _admin)
|
||||
# Call the cog management endpoint
|
||||
log.info(f"Calling get_guild_cogs for guild {guild_id}")
|
||||
result = await get_guild_cogs(guild_id, _user, _admin)
|
||||
log.info(f"Successfully retrieved cogs for guild {guild_id}")
|
||||
return result
|
||||
except ImportError as e:
|
||||
log.warning(f"Relative import failed: {e}, trying absolute import")
|
||||
try:
|
||||
# Fall back to absolute import
|
||||
from cog_management_endpoints import get_guild_cogs
|
||||
log.info(f"Successfully imported get_guild_cogs via absolute import")
|
||||
|
||||
# Call the cog management endpoint
|
||||
log.info(f"Calling get_guild_cogs for guild {guild_id}")
|
||||
result = await get_guild_cogs(guild_id, _user, _admin)
|
||||
log.info(f"Successfully retrieved cogs for guild {guild_id}")
|
||||
return result
|
||||
except ImportError as e2:
|
||||
log.error(f"Both import attempts failed: {e2}")
|
||||
log.warning("Falling back to direct implementation")
|
||||
|
||||
# Fall back to direct implementation
|
||||
# Check if bot instance is available via discord_bot_sync_api
|
||||
try:
|
||||
from discordbot import discord_bot_sync_api
|
||||
bot = discord_bot_sync_api.bot_instance
|
||||
if not bot:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Bot instance not available"
|
||||
)
|
||||
except ImportError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Bot sync API not available"
|
||||
)
|
||||
|
||||
# Get all cogs from the bot
|
||||
cogs_list = []
|
||||
for cog_name, cog in bot.cogs.items():
|
||||
# Get enabled status from settings_manager
|
||||
is_enabled = await settings_manager.is_cog_enabled(guild_id, cog_name, default_enabled=True)
|
||||
|
||||
# Get commands for this cog
|
||||
commands_list = []
|
||||
for command in cog.get_commands():
|
||||
# Get command enabled status
|
||||
cmd_enabled = await settings_manager.is_command_enabled(guild_id, command.qualified_name, default_enabled=True)
|
||||
commands_list.append({
|
||||
"name": command.qualified_name,
|
||||
"description": command.help or "No description available",
|
||||
"enabled": cmd_enabled
|
||||
})
|
||||
|
||||
# Add slash commands if any
|
||||
app_commands = [cmd for cmd in bot.tree.get_commands() if hasattr(cmd, 'cog') and cmd.cog and cmd.cog.qualified_name == cog_name]
|
||||
for cmd in app_commands:
|
||||
# Get command enabled status
|
||||
cmd_enabled = await settings_manager.is_command_enabled(guild_id, cmd.name, default_enabled=True)
|
||||
if not any(c["name"] == cmd.name for c in commands_list): # Avoid duplicates
|
||||
commands_list.append({
|
||||
"name": cmd.name,
|
||||
"description": cmd.description or "No description available",
|
||||
"enabled": cmd_enabled
|
||||
})
|
||||
|
||||
cogs_list.append(CogInfo(
|
||||
name=cog_name,
|
||||
description=cog.__doc__ or "No description available",
|
||||
enabled=is_enabled,
|
||||
commands=commands_list
|
||||
))
|
||||
|
||||
return cogs_list
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Error redirecting to cog management endpoint: {e}")
|
||||
log.error(f"Error getting cogs for guild {guild_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error getting cogs: {str(e)}"
|
||||
@ -858,18 +987,84 @@ async def update_cog_status_redirect(
|
||||
_user: dict = Depends(get_dashboard_user),
|
||||
_admin: bool = Depends(verify_dashboard_guild_admin)
|
||||
):
|
||||
"""Redirect to the cog management endpoint for updating cog status."""
|
||||
"""Enable or disable a cog for a guild."""
|
||||
try:
|
||||
# Import the cog management endpoint
|
||||
# First try to use the dedicated cog management endpoint
|
||||
try:
|
||||
# Try relative import first
|
||||
from .cog_management_endpoints import update_cog_status
|
||||
except ImportError:
|
||||
from cog_management_endpoints import update_cog_status
|
||||
log.info(f"Successfully imported update_cog_status via relative import")
|
||||
|
||||
# Call the cog management endpoint
|
||||
return await update_cog_status(guild_id, cog_name, enabled, _user, _admin)
|
||||
# Call the cog management endpoint
|
||||
log.info(f"Calling update_cog_status for guild {guild_id}, cog {cog_name}, enabled={enabled}")
|
||||
result = await update_cog_status(guild_id, cog_name, enabled, _user, _admin)
|
||||
log.info(f"Successfully updated cog status for guild {guild_id}, cog {cog_name}")
|
||||
return result
|
||||
except ImportError as e:
|
||||
log.warning(f"Relative import failed: {e}, trying absolute import")
|
||||
try:
|
||||
# Fall back to absolute import
|
||||
from cog_management_endpoints import update_cog_status
|
||||
log.info(f"Successfully imported update_cog_status via absolute import")
|
||||
|
||||
# Call the cog management endpoint
|
||||
log.info(f"Calling update_cog_status for guild {guild_id}, cog {cog_name}, enabled={enabled}")
|
||||
result = await update_cog_status(guild_id, cog_name, enabled, _user, _admin)
|
||||
log.info(f"Successfully updated cog status for guild {guild_id}, cog {cog_name}")
|
||||
return result
|
||||
except ImportError as e2:
|
||||
log.error(f"Both import attempts failed: {e2}")
|
||||
log.warning("Falling back to direct implementation")
|
||||
|
||||
# Fall back to direct implementation
|
||||
# Check if settings_manager is available
|
||||
if not settings_manager or not settings_manager.pg_pool:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Settings manager not available"
|
||||
)
|
||||
|
||||
# Check if the cog exists
|
||||
try:
|
||||
from discordbot import discord_bot_sync_api
|
||||
bot = discord_bot_sync_api.bot_instance
|
||||
if not bot:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Bot instance not available"
|
||||
)
|
||||
|
||||
if cog_name not in bot.cogs:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Cog '{cog_name}' not found"
|
||||
)
|
||||
|
||||
# Check if it's a core cog
|
||||
core_cogs = getattr(bot, 'core_cogs', {'SettingsCog', 'HelpCog'})
|
||||
if cog_name in core_cogs:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Core cog '{cog_name}' cannot be disabled"
|
||||
)
|
||||
except ImportError:
|
||||
# If we can't import the bot, we'll just assume the cog exists
|
||||
log.warning("Bot sync API not available, skipping cog existence check")
|
||||
|
||||
# Update the cog enabled status
|
||||
success = await settings_manager.set_cog_enabled(guild_id, cog_name, enabled)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to update cog '{cog_name}' status"
|
||||
)
|
||||
|
||||
return {"message": f"Cog '{cog_name}' {'enabled' if enabled else 'disabled'} successfully"}
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Error redirecting to cog management endpoint for updating cog status: {e}")
|
||||
log.error(f"Error updating cog status for guild {guild_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error updating cog status: {str(e)}"
|
||||
@ -883,18 +1078,81 @@ async def update_command_status_redirect(
|
||||
_user: dict = Depends(get_dashboard_user),
|
||||
_admin: bool = Depends(verify_dashboard_guild_admin)
|
||||
):
|
||||
"""Redirect to the cog management endpoint for updating command status."""
|
||||
"""Enable or disable a command for a guild."""
|
||||
try:
|
||||
# Import the cog management endpoint
|
||||
# First try to use the dedicated cog management endpoint
|
||||
try:
|
||||
# Try relative import first
|
||||
from .cog_management_endpoints import update_command_status
|
||||
except ImportError:
|
||||
from cog_management_endpoints import update_command_status
|
||||
log.info(f"Successfully imported update_command_status via relative import")
|
||||
|
||||
# Call the cog management endpoint
|
||||
return await update_command_status(guild_id, command_name, enabled, _user, _admin)
|
||||
# Call the cog management endpoint
|
||||
log.info(f"Calling update_command_status for guild {guild_id}, command {command_name}, enabled={enabled}")
|
||||
result = await update_command_status(guild_id, command_name, enabled, _user, _admin)
|
||||
log.info(f"Successfully updated command status for guild {guild_id}, command {command_name}")
|
||||
return result
|
||||
except ImportError as e:
|
||||
log.warning(f"Relative import failed: {e}, trying absolute import")
|
||||
try:
|
||||
# Fall back to absolute import
|
||||
from cog_management_endpoints import update_command_status
|
||||
log.info(f"Successfully imported update_command_status via absolute import")
|
||||
|
||||
# Call the cog management endpoint
|
||||
log.info(f"Calling update_command_status for guild {guild_id}, command {command_name}, enabled={enabled}")
|
||||
result = await update_command_status(guild_id, command_name, enabled, _user, _admin)
|
||||
log.info(f"Successfully updated command status for guild {guild_id}, command {command_name}")
|
||||
return result
|
||||
except ImportError as e2:
|
||||
log.error(f"Both import attempts failed: {e2}")
|
||||
log.warning("Falling back to direct implementation")
|
||||
|
||||
# Fall back to direct implementation
|
||||
# Check if settings_manager is available
|
||||
if not settings_manager or not settings_manager.pg_pool:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Settings manager not available"
|
||||
)
|
||||
|
||||
# Check if the command exists
|
||||
try:
|
||||
from discordbot import discord_bot_sync_api
|
||||
bot = discord_bot_sync_api.bot_instance
|
||||
if not bot:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Bot instance not available"
|
||||
)
|
||||
|
||||
# Check if it's a prefix command
|
||||
command = bot.get_command(command_name)
|
||||
if not command:
|
||||
# Check if it's an app command
|
||||
app_commands = [cmd for cmd in bot.tree.get_commands() if cmd.name == command_name]
|
||||
if not app_commands:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Command '{command_name}' not found"
|
||||
)
|
||||
except ImportError:
|
||||
# If we can't import the bot, we'll just assume the command exists
|
||||
log.warning("Bot sync API not available, skipping command existence check")
|
||||
|
||||
# Update the command enabled status
|
||||
success = await settings_manager.set_command_enabled(guild_id, command_name, enabled)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to update command '{command_name}' status"
|
||||
)
|
||||
|
||||
return {"message": f"Command '{command_name}' {'enabled' if enabled else 'disabled'} successfully"}
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Error redirecting to cog management endpoint for updating command status: {e}")
|
||||
log.error(f"Error updating command status for guild {guild_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error updating command status: {str(e)}"
|
||||
|
@ -2,6 +2,7 @@ import asyncpg
|
||||
import redis.asyncio as redis
|
||||
import os
|
||||
import logging
|
||||
import asyncio
|
||||
from dotenv import load_dotenv
|
||||
from typing import Dict
|
||||
|
||||
@ -57,10 +58,25 @@ async def initialize_pools():
|
||||
)
|
||||
log.info(f"PostgreSQL pool connected to {POSTGRES_HOST}/{POSTGRES_DB}")
|
||||
|
||||
# Create Redis pool
|
||||
redis_pool = redis.from_url(REDIS_URL, decode_responses=True)
|
||||
await redis_pool.ping() # Test connection
|
||||
log.info(f"Redis pool connected to {REDIS_HOST}:{REDIS_PORT}")
|
||||
# Create Redis pool with connection_cls=None to avoid event loop issues
|
||||
# This creates a connection pool that doesn't bind to a specific event loop
|
||||
redis_pool = redis.from_url(
|
||||
REDIS_URL,
|
||||
decode_responses=True,
|
||||
max_connections=20, # Limit max connections
|
||||
socket_timeout=5.0, # 5 second timeout for operations
|
||||
socket_connect_timeout=3.0, # 3 second timeout for connections
|
||||
retry_on_timeout=True, # Retry on timeout
|
||||
health_check_interval=30 # Check connection health every 30 seconds
|
||||
)
|
||||
|
||||
# Test connection with a timeout
|
||||
try:
|
||||
await asyncio.wait_for(redis_pool.ping(), timeout=5.0)
|
||||
log.info(f"Redis pool connected to {REDIS_HOST}:{REDIS_PORT}")
|
||||
except asyncio.TimeoutError:
|
||||
log.error(f"Redis connection timeout when connecting to {REDIS_HOST}:{REDIS_PORT}")
|
||||
raise
|
||||
|
||||
# Initialize database schema
|
||||
await initialize_database() # Ensure tables exist
|
||||
@ -258,30 +274,56 @@ async def get_guild_prefix(guild_id: int, default_prefix: str) -> str:
|
||||
return default_prefix
|
||||
|
||||
cache_key = _get_redis_key(guild_id, "prefix")
|
||||
|
||||
# Try to get from cache with timeout and error handling
|
||||
try:
|
||||
cached_prefix = await redis_pool.get(cache_key)
|
||||
# Use a timeout to prevent hanging on Redis operations
|
||||
cached_prefix = await asyncio.wait_for(redis_pool.get(cache_key), timeout=2.0)
|
||||
if cached_prefix is not None:
|
||||
log.debug(f"Cache hit for prefix (Guild: {guild_id})")
|
||||
return cached_prefix
|
||||
except asyncio.TimeoutError:
|
||||
log.warning(f"Redis timeout getting prefix for guild {guild_id}, falling back to database")
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Redis event loop error for guild {guild_id}, falling back to database: {e}")
|
||||
else:
|
||||
log.exception(f"Redis error getting prefix for guild {guild_id}: {e}")
|
||||
except Exception as e:
|
||||
log.exception(f"Redis error getting prefix for guild {guild_id}: {e}")
|
||||
|
||||
# Cache miss or Redis error, get from database
|
||||
log.debug(f"Cache miss for prefix (Guild: {guild_id})")
|
||||
async with pg_pool.acquire() as conn:
|
||||
prefix = await conn.fetchval(
|
||||
"SELECT setting_value FROM guild_settings WHERE guild_id = $1 AND setting_key = 'prefix'",
|
||||
guild_id
|
||||
)
|
||||
|
||||
final_prefix = prefix if prefix is not None else default_prefix
|
||||
|
||||
# Cache the result (even if it's the default, to avoid future DB lookups)
|
||||
try:
|
||||
await redis_pool.set(cache_key, final_prefix, ex=3600) # Cache for 1 hour
|
||||
except Exception as e:
|
||||
log.exception(f"Redis error setting prefix for guild {guild_id}: {e}")
|
||||
async with pg_pool.acquire() as conn:
|
||||
prefix = await conn.fetchval(
|
||||
"SELECT setting_value FROM guild_settings WHERE guild_id = $1 AND setting_key = 'prefix'",
|
||||
guild_id
|
||||
)
|
||||
|
||||
return final_prefix
|
||||
final_prefix = prefix if prefix is not None else default_prefix
|
||||
|
||||
# Try to cache the result with timeout and error handling
|
||||
try:
|
||||
# Use a timeout to prevent hanging on Redis operations
|
||||
await asyncio.wait_for(
|
||||
redis_pool.set(cache_key, final_prefix, ex=3600), # Cache for 1 hour
|
||||
timeout=2.0
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
log.warning(f"Redis timeout setting prefix for guild {guild_id}")
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Redis event loop error setting prefix for guild {guild_id}: {e}")
|
||||
else:
|
||||
log.exception(f"Redis error setting prefix for guild {guild_id}: {e}")
|
||||
except Exception as e:
|
||||
log.exception(f"Redis error setting prefix for guild {guild_id}: {e}")
|
||||
|
||||
return final_prefix
|
||||
except Exception as e:
|
||||
log.exception(f"Database error getting prefix for guild {guild_id}: {e}")
|
||||
return default_prefix # Fall back to default on database error
|
||||
|
||||
async def set_guild_prefix(guild_id: int, prefix: str):
|
||||
"""Sets the command prefix for a guild and updates the cache."""
|
||||
@ -326,33 +368,64 @@ async def get_setting(guild_id: int, key: str, default=None):
|
||||
return default
|
||||
|
||||
cache_key = _get_redis_key(guild_id, "setting", key)
|
||||
|
||||
# Try to get from cache with timeout and error handling
|
||||
try:
|
||||
cached_value = await redis_pool.get(cache_key)
|
||||
# Use a timeout to prevent hanging on Redis operations
|
||||
cached_value = await asyncio.wait_for(redis_pool.get(cache_key), timeout=2.0)
|
||||
if cached_value is not None:
|
||||
# Note: Redis stores everything as strings. Consider type conversion if needed.
|
||||
log.debug(f"Cache hit for setting '{key}' (Guild: {guild_id})")
|
||||
# Handle the None marker
|
||||
if cached_value == "__NONE__":
|
||||
return default
|
||||
return cached_value
|
||||
except asyncio.TimeoutError:
|
||||
log.warning(f"Redis timeout getting setting '{key}' for guild {guild_id}, falling back to database")
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Redis event loop error for guild {guild_id}, falling back to database: {e}")
|
||||
else:
|
||||
log.exception(f"Redis error getting setting '{key}' for guild {guild_id}: {e}")
|
||||
except Exception as e:
|
||||
log.exception(f"Redis error getting setting '{key}' for guild {guild_id}: {e}")
|
||||
|
||||
# Cache miss or Redis error, get from database
|
||||
log.debug(f"Cache miss for setting '{key}' (Guild: {guild_id})")
|
||||
async with pg_pool.acquire() as conn:
|
||||
value = await conn.fetchval(
|
||||
"SELECT setting_value FROM guild_settings WHERE guild_id = $1 AND setting_key = $2",
|
||||
guild_id, key
|
||||
)
|
||||
|
||||
final_value = value if value is not None else default
|
||||
|
||||
# Cache the result (even if None or default, cache the absence or default value)
|
||||
# Store None as a special marker, e.g., "None" string, or handle appropriately
|
||||
value_to_cache = final_value if final_value is not None else "__NONE__" # Marker for None
|
||||
try:
|
||||
await redis_pool.set(cache_key, value_to_cache, ex=3600) # Cache for 1 hour
|
||||
except Exception as e:
|
||||
log.exception(f"Redis error setting cache for setting '{key}' for guild {guild_id}: {e}")
|
||||
async with pg_pool.acquire() as conn:
|
||||
value = await conn.fetchval(
|
||||
"SELECT setting_value FROM guild_settings WHERE guild_id = $1 AND setting_key = $2",
|
||||
guild_id, key
|
||||
)
|
||||
|
||||
return final_value
|
||||
final_value = value if value is not None else default
|
||||
|
||||
# Cache the result (even if None or default, cache the absence or default value)
|
||||
# Store None as a special marker, e.g., "None" string, or handle appropriately
|
||||
value_to_cache = final_value if final_value is not None else "__NONE__" # Marker for None
|
||||
|
||||
# Try to cache the result with timeout and error handling
|
||||
try:
|
||||
# Use a timeout to prevent hanging on Redis operations
|
||||
await asyncio.wait_for(
|
||||
redis_pool.set(cache_key, value_to_cache, ex=3600), # Cache for 1 hour
|
||||
timeout=2.0
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
log.warning(f"Redis timeout setting cache for setting '{key}' for guild {guild_id}")
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Redis event loop error setting cache for setting '{key}' for guild {guild_id}: {e}")
|
||||
else:
|
||||
log.exception(f"Redis error setting cache for setting '{key}' for guild {guild_id}: {e}")
|
||||
except Exception as e:
|
||||
log.exception(f"Redis error setting cache for setting '{key}' for guild {guild_id}: {e}")
|
||||
|
||||
return final_value
|
||||
except Exception as e:
|
||||
log.exception(f"Database error getting setting '{key}' for guild {guild_id}: {e}")
|
||||
return default # Fall back to default on database error
|
||||
|
||||
|
||||
async def set_setting(guild_id: int, key: str, value: str | None):
|
||||
@ -411,14 +484,25 @@ async def is_cog_enabled(guild_id: int, cog_name: str, default_enabled: bool = T
|
||||
return default_enabled
|
||||
|
||||
cache_key = _get_redis_key(guild_id, "cog_enabled", cog_name)
|
||||
|
||||
# Try to get from cache with timeout and error handling
|
||||
try:
|
||||
cached_value = await redis_pool.get(cache_key)
|
||||
# Use a timeout to prevent hanging on Redis operations
|
||||
cached_value = await asyncio.wait_for(redis_pool.get(cache_key), timeout=2.0)
|
||||
if cached_value is not None:
|
||||
log.debug(f"Cache hit for cog enabled status '{cog_name}' (Guild: {guild_id})")
|
||||
return cached_value == "True" # Redis stores strings
|
||||
except asyncio.TimeoutError:
|
||||
log.warning(f"Redis timeout getting cog enabled status for '{cog_name}' (Guild: {guild_id}), falling back to database")
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Redis event loop error for guild {guild_id}, falling back to database: {e}")
|
||||
else:
|
||||
log.exception(f"Redis error getting cog enabled status for '{cog_name}' (Guild: {guild_id}): {e}")
|
||||
except Exception as e:
|
||||
log.exception(f"Redis error getting cog enabled status for '{cog_name}' (Guild: {guild_id}): {e}")
|
||||
|
||||
# Cache miss or Redis error, get from database
|
||||
log.debug(f"Cache miss for cog enabled status '{cog_name}' (Guild: {guild_id})")
|
||||
db_enabled_status = None
|
||||
try:
|
||||
@ -427,21 +511,32 @@ async def is_cog_enabled(guild_id: int, cog_name: str, default_enabled: bool = T
|
||||
"SELECT enabled FROM enabled_cogs WHERE guild_id = $1 AND cog_name = $2",
|
||||
guild_id, cog_name
|
||||
)
|
||||
|
||||
final_status = db_enabled_status if db_enabled_status is not None else default_enabled
|
||||
|
||||
# Try to cache the result with timeout and error handling
|
||||
try:
|
||||
# Use a timeout to prevent hanging on Redis operations
|
||||
await asyncio.wait_for(
|
||||
redis_pool.set(cache_key, str(final_status), ex=3600), # Cache for 1 hour
|
||||
timeout=2.0
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
log.warning(f"Redis timeout setting cache for cog enabled status '{cog_name}' (Guild: {guild_id})")
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Redis event loop error setting cache for cog enabled status '{cog_name}' (Guild: {guild_id}): {e}")
|
||||
else:
|
||||
log.exception(f"Redis error setting cache for cog enabled status '{cog_name}' (Guild: {guild_id}): {e}")
|
||||
except Exception as e:
|
||||
log.exception(f"Redis error setting cache for cog enabled status '{cog_name}' (Guild: {guild_id}): {e}")
|
||||
|
||||
return final_status
|
||||
except Exception as e:
|
||||
log.exception(f"Database error getting cog enabled status for '{cog_name}' (Guild: {guild_id}): {e}")
|
||||
# Fallback to default on DB error after cache miss
|
||||
return default_enabled
|
||||
|
||||
final_status = db_enabled_status if db_enabled_status is not None else default_enabled
|
||||
|
||||
# Cache the result (True or False)
|
||||
try:
|
||||
await redis_pool.set(cache_key, str(final_status), ex=3600) # Cache for 1 hour
|
||||
except Exception as e:
|
||||
log.exception(f"Redis error setting cache for cog enabled status '{cog_name}' (Guild: {guild_id}): {e}")
|
||||
|
||||
return final_status
|
||||
|
||||
|
||||
async def set_cog_enabled(guild_id: int, cog_name: str, enabled: bool):
|
||||
"""Sets the enabled status for a cog in a guild and updates the cache."""
|
||||
@ -486,14 +581,25 @@ async def is_command_enabled(guild_id: int, command_name: str, default_enabled:
|
||||
return default_enabled
|
||||
|
||||
cache_key = _get_redis_key(guild_id, "cmd_enabled", command_name)
|
||||
|
||||
# Try to get from cache with timeout and error handling
|
||||
try:
|
||||
cached_value = await redis_pool.get(cache_key)
|
||||
# Use a timeout to prevent hanging on Redis operations
|
||||
cached_value = await asyncio.wait_for(redis_pool.get(cache_key), timeout=2.0)
|
||||
if cached_value is not None:
|
||||
log.debug(f"Cache hit for command enabled status '{command_name}' (Guild: {guild_id})")
|
||||
return cached_value == "True" # Redis stores strings
|
||||
except asyncio.TimeoutError:
|
||||
log.warning(f"Redis timeout getting command enabled status for '{command_name}' (Guild: {guild_id}), falling back to database")
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Redis event loop error for guild {guild_id}, falling back to database: {e}")
|
||||
else:
|
||||
log.exception(f"Redis error getting command enabled status for '{command_name}' (Guild: {guild_id}): {e}")
|
||||
except Exception as e:
|
||||
log.exception(f"Redis error getting command enabled status for '{command_name}' (Guild: {guild_id}): {e}")
|
||||
|
||||
# Cache miss or Redis error, get from database
|
||||
log.debug(f"Cache miss for command enabled status '{command_name}' (Guild: {guild_id})")
|
||||
db_enabled_status = None
|
||||
try:
|
||||
@ -502,21 +608,32 @@ async def is_command_enabled(guild_id: int, command_name: str, default_enabled:
|
||||
"SELECT enabled FROM enabled_commands WHERE guild_id = $1 AND command_name = $2",
|
||||
guild_id, command_name
|
||||
)
|
||||
|
||||
final_status = db_enabled_status if db_enabled_status is not None else default_enabled
|
||||
|
||||
# Try to cache the result with timeout and error handling
|
||||
try:
|
||||
# Use a timeout to prevent hanging on Redis operations
|
||||
await asyncio.wait_for(
|
||||
redis_pool.set(cache_key, str(final_status), ex=3600), # Cache for 1 hour
|
||||
timeout=2.0
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
log.warning(f"Redis timeout setting cache for command enabled status '{command_name}' (Guild: {guild_id})")
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Redis event loop error setting cache for command enabled status '{command_name}' (Guild: {guild_id}): {e}")
|
||||
else:
|
||||
log.exception(f"Redis error setting cache for command enabled status '{command_name}' (Guild: {guild_id}): {e}")
|
||||
except Exception as e:
|
||||
log.exception(f"Redis error setting cache for command enabled status '{command_name}' (Guild: {guild_id}): {e}")
|
||||
|
||||
return final_status
|
||||
except Exception as e:
|
||||
log.exception(f"Database error getting command enabled status for '{command_name}' (Guild: {guild_id}): {e}")
|
||||
# Fallback to default on DB error after cache miss
|
||||
return default_enabled
|
||||
|
||||
final_status = db_enabled_status if db_enabled_status is not None else default_enabled
|
||||
|
||||
# Cache the result (True or False)
|
||||
try:
|
||||
await redis_pool.set(cache_key, str(final_status), ex=3600) # Cache for 1 hour
|
||||
except Exception as e:
|
||||
log.exception(f"Redis error setting cache for command enabled status '{command_name}' (Guild: {guild_id}): {e}")
|
||||
|
||||
return final_status
|
||||
|
||||
|
||||
async def set_command_enabled(guild_id: int, command_name: str, enabled: bool):
|
||||
"""Sets the enabled status for a command in a guild and updates the cache."""
|
||||
|
Loading…
x
Reference in New Issue
Block a user