Files
Pac-cogs/videoarchiver/core/response_handler.py
2024-11-18 01:01:12 +00:00

297 lines
9.4 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Module for handling command responses"""
import logging
from enum import Enum, auto
from typing import Optional, Union, Dict, Any, TypedDict, ClassVar
from datetime import datetime
import discord # type: ignore
from redbot.core.commands import Context # type: ignore
try:
# Try relative imports first
from ..utils.exceptions import ErrorSeverity
except ImportError:
# Fall back to absolute imports if relative imports fail
# from videoarchiver.utils.exceptions import ErrorSeverity
logger = logging.getLogger("VideoArchiver")
class ResponseType(Enum):
"""Types of responses"""
NORMAL = auto()
SUCCESS = auto()
ERROR = auto()
WARNING = auto()
INFO = auto()
DEBUG = auto()
class ResponseTheme(TypedDict):
"""Type definition for response theme"""
emoji: str
color: discord.Color
class ResponseFormat(TypedDict):
"""Type definition for formatted response"""
content: str
color: discord.Color
timestamp: str
class ResponseFormatter:
"""Formats responses for consistency"""
THEMES: ClassVar[Dict[ResponseType, ResponseTheme]] = {
ResponseType.SUCCESS: ResponseTheme(emoji="", color=discord.Color.green()),
ResponseType.ERROR: ResponseTheme(emoji="", color=discord.Color.red()),
ResponseType.WARNING: ResponseTheme(emoji="⚠️", color=discord.Color.gold()),
ResponseType.INFO: ResponseTheme(emoji="", color=discord.Color.blue()),
ResponseType.DEBUG: ResponseTheme(emoji="🔧", color=discord.Color.greyple()),
}
SEVERITY_MAPPING: ClassVar[Dict[ErrorSeverity, ResponseType]] = {
ErrorSeverity.LOW: ResponseType.INFO,
ErrorSeverity.MEDIUM: ResponseType.WARNING,
ErrorSeverity.HIGH: ResponseType.ERROR,
ErrorSeverity.CRITICAL: ResponseType.ERROR,
}
@classmethod
def format_response(
cls, message: str, response_type: ResponseType = ResponseType.NORMAL
) -> ResponseFormat:
"""
Format a response message.
Args:
message: Message to format
response_type: Type of response
Returns:
Formatted response dictionary
"""
theme = cls.THEMES.get(response_type)
if theme:
return ResponseFormat(
content=f"{theme['emoji']} {message}",
color=theme["color"],
timestamp=datetime.utcnow().isoformat(),
)
return ResponseFormat(
content=message,
color=discord.Color.default(),
timestamp=datetime.utcnow().isoformat(),
)
@classmethod
def get_response_type(cls, severity: ErrorSeverity) -> ResponseType:
"""
Get response type for error severity.
Args:
severity: Error severity level
Returns:
Appropriate response type
"""
return cls.SEVERITY_MAPPING.get(severity, ResponseType.ERROR)
class InteractionHandler:
"""Handles slash command interactions"""
async def send_initial_response(
self,
interaction: discord.Interaction,
content: Optional[str] = None,
embed: Optional[discord.Embed] = None,
) -> bool:
"""
Send initial interaction response.
Args:
interaction: Discord interaction
content: Optional message content
embed: Optional embed
Returns:
True if response was sent successfully
"""
try:
if not interaction.response.is_done():
if embed:
await interaction.response.send_message(
content=content, embed=embed
)
else:
await interaction.response.send_message(content=content)
return True
return False
except Exception as e:
logger.error(
f"Error sending initial interaction response: {e}", exc_info=True
)
return False
async def send_followup(
self,
interaction: discord.Interaction,
content: Optional[str] = None,
embed: Optional[discord.Embed] = None,
) -> bool:
"""
Send interaction followup.
Args:
interaction: Discord interaction
content: Optional message content
embed: Optional embed
Returns:
True if followup was sent successfully
"""
try:
if embed:
await interaction.followup.send(content=content, embed=embed)
else:
await interaction.followup.send(content=content)
return True
except Exception as e:
logger.error(f"Error sending interaction followup: {e}", exc_info=True)
return False
class ResponseManager:
"""Manages command responses"""
def __init__(self) -> None:
self.formatter = ResponseFormatter()
self.interaction_handler = InteractionHandler()
async def send_response(
self,
ctx: Context,
content: Optional[str] = None,
embed: Optional[discord.Embed] = None,
response_type: Union[ResponseType, str, ErrorSeverity] = ResponseType.NORMAL,
) -> None:
"""
Send a response to a command.
Args:
ctx: Command context
content: Optional message content
embed: Optional embed
response_type: Type of response or error severity
"""
try:
# Convert string response type to enum
if isinstance(response_type, str):
try:
response_type = ResponseType[response_type.upper()]
except KeyError:
response_type = ResponseType.NORMAL
# Convert error severity to response type
elif isinstance(response_type, ErrorSeverity):
response_type = self.formatter.get_response_type(response_type)
# Format response
if response_type != ResponseType.NORMAL and content:
formatted = self.formatter.format_response(content, response_type)
content = formatted["content"]
if not embed:
embed = discord.Embed(
color=formatted["color"],
timestamp=datetime.fromisoformat(formatted["timestamp"]),
)
# Handle response
if self._is_interaction(ctx):
await self._handle_interaction_response(ctx, content, embed)
else:
await self._handle_regular_response(ctx, content, embed)
except Exception as e:
logger.error(f"Error sending response: {e}", exc_info=True)
await self._send_fallback_response(ctx, content, embed)
def _is_interaction(self, ctx: Context) -> bool:
"""Check if context is from an interaction"""
return hasattr(ctx, "interaction") and ctx.interaction is not None
async def _handle_interaction_response(
self, ctx: Context, content: Optional[str], embed: Optional[discord.Embed]
) -> None:
"""Handle interaction response"""
try:
# Try initial response
if await self.interaction_handler.send_initial_response(
ctx.interaction, content, embed
):
return
# Try followup
if await self.interaction_handler.send_followup(
ctx.interaction, content, embed
):
return
# Fallback to regular message
await self._handle_regular_response(ctx, content, embed)
except Exception as e:
logger.error(f"Error handling interaction response: {e}", exc_info=True)
await self._send_fallback_response(ctx, content, embed)
async def _handle_regular_response(
self, ctx: Context, content: Optional[str], embed: Optional[discord.Embed]
) -> None:
"""Handle regular command response"""
try:
if embed:
await ctx.send(content=content, embed=embed)
else:
await ctx.send(content=content)
except Exception as e:
logger.error(f"Error sending regular response: {e}", exc_info=True)
await self._send_fallback_response(ctx, content, embed)
async def _send_fallback_response(
self, ctx: Context, content: Optional[str], embed: Optional[discord.Embed]
) -> None:
"""Send fallback response when other methods fail"""
try:
if embed:
await ctx.send(content=content, embed=embed)
else:
await ctx.send(content=content)
except Exception as e:
logger.error(f"Failed to send fallback response: {e}", exc_info=True)
# Global response manager instance
response_manager = ResponseManager()
async def handle_response(
ctx: Context,
content: Optional[str] = None,
embed: Optional[discord.Embed] = None,
response_type: Union[ResponseType, str, ErrorSeverity] = ResponseType.NORMAL,
) -> None:
"""
Helper function to handle responses using the response manager.
Args:
ctx: Command context
content: Optional message content
embed: Optional embed
response_type: Type of response or error severity
"""
await response_manager.send_response(ctx, content, embed, response_type)