mirror of
https://github.com/pacnpal/Pac-cogs.git
synced 2025-12-20 10:51:05 -05:00
Core Systems:
Component-based architecture with lifecycle management Enhanced error handling and recovery mechanisms Comprehensive state management and tracking Event-driven architecture with monitoring Queue Management: Multiple processing strategies for different scenarios Advanced state management with recovery Comprehensive metrics and health monitoring Sophisticated cleanup system with multiple strategies Processing Pipeline: Enhanced message handling with validation Improved URL extraction and processing Better queue management and monitoring Advanced cleanup mechanisms Overall Benefits: Better code organization and maintainability Improved error handling and recovery Enhanced monitoring and reporting More robust and reliable system
This commit is contained in:
225
videoarchiver/config/channel_manager.py
Normal file
225
videoarchiver/config/channel_manager.py
Normal file
@@ -0,0 +1,225 @@
|
||||
"""Module for managing Discord channel configurations"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
import discord
|
||||
|
||||
from .exceptions import ConfigurationError as ConfigError, DiscordAPIError
|
||||
|
||||
logger = logging.getLogger("ChannelManager")
|
||||
|
||||
class ChannelManager:
|
||||
"""Manages Discord channel configurations"""
|
||||
|
||||
def __init__(self, config_manager):
|
||||
self.config_manager = config_manager
|
||||
|
||||
async def get_channel(
|
||||
self,
|
||||
guild: discord.Guild,
|
||||
channel_type: str
|
||||
) -> Optional[discord.TextChannel]:
|
||||
"""Get a channel by type
|
||||
|
||||
Args:
|
||||
guild: Discord guild
|
||||
channel_type: Type of channel (archive, notification, log)
|
||||
|
||||
Returns:
|
||||
Optional[discord.TextChannel]: Channel if found and valid
|
||||
|
||||
Raises:
|
||||
ConfigError: If channel type is invalid
|
||||
DiscordAPIError: If channel exists but is invalid type
|
||||
"""
|
||||
try:
|
||||
if channel_type not in ["archive", "notification", "log"]:
|
||||
raise ConfigError(f"Invalid channel type: {channel_type}")
|
||||
|
||||
settings = await self.config_manager.get_guild_settings(guild.id)
|
||||
channel_id = settings.get(f"{channel_type}_channel")
|
||||
|
||||
if channel_id is None:
|
||||
return None
|
||||
|
||||
channel = guild.get_channel(channel_id)
|
||||
if channel is None:
|
||||
logger.warning(f"Channel {channel_id} not found in guild {guild.id}")
|
||||
return None
|
||||
|
||||
if not isinstance(channel, discord.TextChannel):
|
||||
raise DiscordAPIError(f"Channel {channel_id} is not a text channel")
|
||||
|
||||
return channel
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get {channel_type} channel for guild {guild.id}: {e}")
|
||||
raise ConfigError(f"Failed to get channel: {str(e)}")
|
||||
|
||||
async def get_monitored_channels(
|
||||
self,
|
||||
guild: discord.Guild
|
||||
) -> List[discord.TextChannel]:
|
||||
"""Get all monitored channels for a guild
|
||||
|
||||
Args:
|
||||
guild: Discord guild
|
||||
|
||||
Returns:
|
||||
List[discord.TextChannel]: List of monitored channels
|
||||
|
||||
Raises:
|
||||
ConfigError: If channel retrieval fails
|
||||
"""
|
||||
try:
|
||||
settings = await self.config_manager.get_guild_settings(guild.id)
|
||||
monitored_channel_ids = settings["monitored_channels"]
|
||||
|
||||
# If no channels are set to be monitored, return all text channels
|
||||
if not monitored_channel_ids:
|
||||
return [
|
||||
channel for channel in guild.channels
|
||||
if isinstance(channel, discord.TextChannel)
|
||||
]
|
||||
|
||||
# Otherwise, return only the specified channels
|
||||
channels: List[discord.TextChannel] = []
|
||||
invalid_channels: List[int] = []
|
||||
|
||||
for channel_id in monitored_channel_ids:
|
||||
channel = guild.get_channel(channel_id)
|
||||
if channel and isinstance(channel, discord.TextChannel):
|
||||
channels.append(channel)
|
||||
else:
|
||||
invalid_channels.append(channel_id)
|
||||
logger.warning(f"Invalid monitored channel {channel_id} in guild {guild.id}")
|
||||
|
||||
# Clean up invalid channels if found
|
||||
if invalid_channels:
|
||||
await self._remove_invalid_channels(guild.id, invalid_channels)
|
||||
|
||||
return channels
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get monitored channels for guild {guild.id}: {e}")
|
||||
raise ConfigError(f"Failed to get monitored channels: {str(e)}")
|
||||
|
||||
async def verify_channel_permissions(
|
||||
self,
|
||||
channel: discord.TextChannel,
|
||||
required_permissions: List[str]
|
||||
) -> Tuple[bool, List[str]]:
|
||||
"""Verify bot has required permissions in a channel
|
||||
|
||||
Args:
|
||||
channel: Channel to check
|
||||
required_permissions: List of required permission names
|
||||
|
||||
Returns:
|
||||
Tuple[bool, List[str]]: (Has all permissions, List of missing permissions)
|
||||
"""
|
||||
try:
|
||||
bot_member = channel.guild.me
|
||||
channel_perms = channel.permissions_for(bot_member)
|
||||
|
||||
missing_perms = [
|
||||
perm for perm in required_permissions
|
||||
if not getattr(channel_perms, perm, False)
|
||||
]
|
||||
|
||||
return not bool(missing_perms), missing_perms
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking channel permissions: {e}")
|
||||
return False, ["Failed to check permissions"]
|
||||
|
||||
async def add_monitored_channel(
|
||||
self,
|
||||
guild_id: int,
|
||||
channel_id: int
|
||||
) -> None:
|
||||
"""Add a channel to monitored channels
|
||||
|
||||
Args:
|
||||
guild_id: Guild ID
|
||||
channel_id: Channel ID to add
|
||||
|
||||
Raises:
|
||||
ConfigError: If channel cannot be added
|
||||
"""
|
||||
try:
|
||||
await self.config_manager.add_to_list(
|
||||
guild_id,
|
||||
"monitored_channels",
|
||||
channel_id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to add monitored channel {channel_id}: {e}")
|
||||
raise ConfigError(f"Failed to add monitored channel: {str(e)}")
|
||||
|
||||
async def remove_monitored_channel(
|
||||
self,
|
||||
guild_id: int,
|
||||
channel_id: int
|
||||
) -> None:
|
||||
"""Remove a channel from monitored channels
|
||||
|
||||
Args:
|
||||
guild_id: Guild ID
|
||||
channel_id: Channel ID to remove
|
||||
|
||||
Raises:
|
||||
ConfigError: If channel cannot be removed
|
||||
"""
|
||||
try:
|
||||
await self.config_manager.remove_from_list(
|
||||
guild_id,
|
||||
"monitored_channels",
|
||||
channel_id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to remove monitored channel {channel_id}: {e}")
|
||||
raise ConfigError(f"Failed to remove monitored channel: {str(e)}")
|
||||
|
||||
async def _remove_invalid_channels(
|
||||
self,
|
||||
guild_id: int,
|
||||
channel_ids: List[int]
|
||||
) -> None:
|
||||
"""Remove invalid channels from monitored channels
|
||||
|
||||
Args:
|
||||
guild_id: Guild ID
|
||||
channel_ids: List of invalid channel IDs to remove
|
||||
"""
|
||||
try:
|
||||
for channel_id in channel_ids:
|
||||
await self.remove_monitored_channel(guild_id, channel_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Error removing invalid channels: {e}")
|
||||
|
||||
async def get_channel_info(
|
||||
self,
|
||||
guild: discord.Guild
|
||||
) -> Dict[str, Optional[discord.TextChannel]]:
|
||||
"""Get all configured channels for a guild
|
||||
|
||||
Args:
|
||||
guild: Discord guild
|
||||
|
||||
Returns:
|
||||
Dict[str, Optional[discord.TextChannel]]: Dictionary of channel types to channels
|
||||
"""
|
||||
try:
|
||||
return {
|
||||
'archive': await self.get_channel(guild, "archive"),
|
||||
'notification': await self.get_channel(guild, "notification"),
|
||||
'log': await self.get_channel(guild, "log")
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting channel info: {e}")
|
||||
return {
|
||||
'archive': None,
|
||||
'notification': None,
|
||||
'log': None
|
||||
}
|
||||
242
videoarchiver/config/role_manager.py
Normal file
242
videoarchiver/config/role_manager.py
Normal file
@@ -0,0 +1,242 @@
|
||||
"""Module for managing Discord role configurations"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Set, Tuple
|
||||
import discord
|
||||
|
||||
from .exceptions import ConfigurationError as ConfigError
|
||||
|
||||
logger = logging.getLogger("RoleManager")
|
||||
|
||||
class RoleManager:
|
||||
"""Manages Discord role configurations"""
|
||||
|
||||
def __init__(self, config_manager):
|
||||
self.config_manager = config_manager
|
||||
|
||||
async def check_user_roles(
|
||||
self,
|
||||
member: discord.Member
|
||||
) -> Tuple[bool, Optional[str]]:
|
||||
"""Check if user has permission based on allowed roles
|
||||
|
||||
Args:
|
||||
member: Discord member to check
|
||||
|
||||
Returns:
|
||||
Tuple[bool, Optional[str]]: (Has permission, Reason if denied)
|
||||
|
||||
Raises:
|
||||
ConfigError: If role check fails
|
||||
"""
|
||||
try:
|
||||
allowed_roles = await self.config_manager.get_setting(
|
||||
member.guild.id,
|
||||
"allowed_roles"
|
||||
)
|
||||
|
||||
# If no roles are set, allow all users
|
||||
if not allowed_roles:
|
||||
return True, None
|
||||
|
||||
# Check user roles
|
||||
user_roles = {role.id for role in member.roles}
|
||||
allowed_role_set = set(allowed_roles)
|
||||
|
||||
if user_roles & allowed_role_set: # Intersection
|
||||
return True, None
|
||||
|
||||
# Get role names for error message
|
||||
missing_roles = await self._get_role_names(
|
||||
member.guild,
|
||||
allowed_role_set
|
||||
)
|
||||
return False, f"Missing required roles: {', '.join(missing_roles)}"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to check roles for user {member.id} in guild {member.guild.id}: {e}")
|
||||
raise ConfigError(f"Failed to check user roles: {str(e)}")
|
||||
|
||||
async def add_allowed_role(
|
||||
self,
|
||||
guild_id: int,
|
||||
role_id: int
|
||||
) -> None:
|
||||
"""Add a role to allowed roles
|
||||
|
||||
Args:
|
||||
guild_id: Guild ID
|
||||
role_id: Role ID to add
|
||||
|
||||
Raises:
|
||||
ConfigError: If role cannot be added
|
||||
"""
|
||||
try:
|
||||
await self.config_manager.add_to_list(
|
||||
guild_id,
|
||||
"allowed_roles",
|
||||
role_id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to add allowed role {role_id}: {e}")
|
||||
raise ConfigError(f"Failed to add allowed role: {str(e)}")
|
||||
|
||||
async def remove_allowed_role(
|
||||
self,
|
||||
guild_id: int,
|
||||
role_id: int
|
||||
) -> None:
|
||||
"""Remove a role from allowed roles
|
||||
|
||||
Args:
|
||||
guild_id: Guild ID
|
||||
role_id: Role ID to remove
|
||||
|
||||
Raises:
|
||||
ConfigError: If role cannot be removed
|
||||
"""
|
||||
try:
|
||||
await self.config_manager.remove_from_list(
|
||||
guild_id,
|
||||
"allowed_roles",
|
||||
role_id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to remove allowed role {role_id}: {e}")
|
||||
raise ConfigError(f"Failed to remove allowed role: {str(e)}")
|
||||
|
||||
async def get_allowed_roles(
|
||||
self,
|
||||
guild: discord.Guild
|
||||
) -> List[discord.Role]:
|
||||
"""Get all allowed roles for a guild
|
||||
|
||||
Args:
|
||||
guild: Discord guild
|
||||
|
||||
Returns:
|
||||
List[discord.Role]: List of allowed roles
|
||||
|
||||
Raises:
|
||||
ConfigError: If roles cannot be retrieved
|
||||
"""
|
||||
try:
|
||||
settings = await self.config_manager.get_guild_settings(guild.id)
|
||||
role_ids = settings["allowed_roles"]
|
||||
|
||||
roles = []
|
||||
invalid_roles = []
|
||||
|
||||
for role_id in role_ids:
|
||||
role = guild.get_role(role_id)
|
||||
if role:
|
||||
roles.append(role)
|
||||
else:
|
||||
invalid_roles.append(role_id)
|
||||
logger.warning(f"Invalid role {role_id} in guild {guild.id}")
|
||||
|
||||
# Clean up invalid roles if found
|
||||
if invalid_roles:
|
||||
await self._remove_invalid_roles(guild.id, invalid_roles)
|
||||
|
||||
return roles
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get allowed roles for guild {guild.id}: {e}")
|
||||
raise ConfigError(f"Failed to get allowed roles: {str(e)}")
|
||||
|
||||
async def verify_role_hierarchy(
|
||||
self,
|
||||
guild: discord.Guild,
|
||||
role: discord.Role
|
||||
) -> Tuple[bool, Optional[str]]:
|
||||
"""Verify bot's role hierarchy position for managing a role
|
||||
|
||||
Args:
|
||||
guild: Discord guild
|
||||
role: Role to check
|
||||
|
||||
Returns:
|
||||
Tuple[bool, Optional[str]]: (Can manage role, Reason if not)
|
||||
"""
|
||||
try:
|
||||
bot_member = guild.me
|
||||
bot_top_role = bot_member.top_role
|
||||
|
||||
if role >= bot_top_role:
|
||||
return False, f"Role {role.name} is higher than or equal to bot's highest role"
|
||||
|
||||
return True, None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking role hierarchy: {e}")
|
||||
return False, "Failed to check role hierarchy"
|
||||
|
||||
async def _get_role_names(
|
||||
self,
|
||||
guild: discord.Guild,
|
||||
role_ids: Set[int]
|
||||
) -> List[str]:
|
||||
"""Get role names from role IDs
|
||||
|
||||
Args:
|
||||
guild: Discord guild
|
||||
role_ids: Set of role IDs
|
||||
|
||||
Returns:
|
||||
List[str]: List of role names
|
||||
"""
|
||||
role_names = []
|
||||
for role_id in role_ids:
|
||||
role = guild.get_role(role_id)
|
||||
if role:
|
||||
role_names.append(role.name)
|
||||
return role_names
|
||||
|
||||
async def _remove_invalid_roles(
|
||||
self,
|
||||
guild_id: int,
|
||||
role_ids: List[int]
|
||||
) -> None:
|
||||
"""Remove invalid roles from allowed roles
|
||||
|
||||
Args:
|
||||
guild_id: Guild ID
|
||||
role_ids: List of invalid role IDs to remove
|
||||
"""
|
||||
try:
|
||||
for role_id in role_ids:
|
||||
await self.remove_allowed_role(guild_id, role_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Error removing invalid roles: {e}")
|
||||
|
||||
async def get_role_info(
|
||||
self,
|
||||
guild: discord.Guild
|
||||
) -> Dict[str, Any]:
|
||||
"""Get role configuration information
|
||||
|
||||
Args:
|
||||
guild: Discord guild
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Dictionary containing role information
|
||||
"""
|
||||
try:
|
||||
allowed_roles = await self.get_allowed_roles(guild)
|
||||
bot_member = guild.me
|
||||
|
||||
return {
|
||||
'allowed_roles': allowed_roles,
|
||||
'bot_top_role': bot_member.top_role,
|
||||
'bot_permissions': bot_member.guild_permissions,
|
||||
'role_count': len(allowed_roles)
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting role info: {e}")
|
||||
return {
|
||||
'allowed_roles': [],
|
||||
'bot_top_role': None,
|
||||
'bot_permissions': None,
|
||||
'role_count': 0
|
||||
}
|
||||
211
videoarchiver/config/settings_formatter.py
Normal file
211
videoarchiver/config/settings_formatter.py
Normal file
@@ -0,0 +1,211 @@
|
||||
"""Module for formatting configuration settings"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any, List
|
||||
from datetime import datetime
|
||||
import discord
|
||||
|
||||
from .exceptions import ConfigurationError as ConfigError
|
||||
|
||||
logger = logging.getLogger("SettingsFormatter")
|
||||
|
||||
class SettingsFormatter:
|
||||
"""Formats configuration settings for display"""
|
||||
|
||||
def __init__(self):
|
||||
self.embed_color = discord.Color.blue()
|
||||
|
||||
async def format_settings_embed(
|
||||
self,
|
||||
guild: discord.Guild,
|
||||
settings: Dict[str, Any]
|
||||
) -> discord.Embed:
|
||||
"""Format guild settings into a Discord embed
|
||||
|
||||
Args:
|
||||
guild: Discord guild
|
||||
settings: Guild settings dictionary
|
||||
|
||||
Returns:
|
||||
discord.Embed: Formatted settings embed
|
||||
|
||||
Raises:
|
||||
ConfigError: If formatting fails
|
||||
"""
|
||||
try:
|
||||
embed = discord.Embed(
|
||||
title="Video Archiver Settings",
|
||||
color=self.embed_color,
|
||||
timestamp=datetime.utcnow()
|
||||
)
|
||||
|
||||
# Add sections
|
||||
await self._add_core_settings(embed, guild, settings)
|
||||
await self._add_channel_settings(embed, guild, settings)
|
||||
await self._add_permission_settings(embed, guild, settings)
|
||||
await self._add_video_settings(embed, settings)
|
||||
await self._add_operation_settings(embed, settings)
|
||||
await self._add_site_settings(embed, settings)
|
||||
|
||||
embed.set_footer(text="Last updated")
|
||||
return embed
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to format settings embed: {e}")
|
||||
raise ConfigError(f"Failed to format settings: {str(e)}")
|
||||
|
||||
async def _add_core_settings(
|
||||
self,
|
||||
embed: discord.Embed,
|
||||
guild: discord.Guild,
|
||||
settings: Dict[str, Any]
|
||||
) -> None:
|
||||
"""Add core settings to embed"""
|
||||
embed.add_field(
|
||||
name="Core Settings",
|
||||
value="\n".join([
|
||||
f"**Enabled:** {settings['enabled']}",
|
||||
f"**Database Enabled:** {settings['use_database']}",
|
||||
f"**Update Check Disabled:** {settings['disable_update_check']}"
|
||||
]),
|
||||
inline=False
|
||||
)
|
||||
|
||||
async def _add_channel_settings(
|
||||
self,
|
||||
embed: discord.Embed,
|
||||
guild: discord.Guild,
|
||||
settings: Dict[str, Any]
|
||||
) -> None:
|
||||
"""Add channel settings to embed"""
|
||||
# Get channels with error handling
|
||||
channels = await self._get_channel_mentions(guild, settings)
|
||||
|
||||
embed.add_field(
|
||||
name="Channel Settings",
|
||||
value="\n".join([
|
||||
f"**Archive Channel:** {channels['archive']}",
|
||||
f"**Notification Channel:** {channels['notification']}",
|
||||
f"**Log Channel:** {channels['log']}",
|
||||
f"**Monitored Channels:**\n{channels['monitored']}"
|
||||
]),
|
||||
inline=False
|
||||
)
|
||||
|
||||
async def _add_permission_settings(
|
||||
self,
|
||||
embed: discord.Embed,
|
||||
guild: discord.Guild,
|
||||
settings: Dict[str, Any]
|
||||
) -> None:
|
||||
"""Add permission settings to embed"""
|
||||
allowed_roles = await self._get_role_names(guild, settings["allowed_roles"])
|
||||
|
||||
embed.add_field(
|
||||
name="Permission Settings",
|
||||
value=f"**Allowed Roles:**\n{allowed_roles}",
|
||||
inline=False
|
||||
)
|
||||
|
||||
async def _add_video_settings(
|
||||
self,
|
||||
embed: discord.Embed,
|
||||
settings: Dict[str, Any]
|
||||
) -> None:
|
||||
"""Add video settings to embed"""
|
||||
embed.add_field(
|
||||
name="Video Settings",
|
||||
value="\n".join([
|
||||
f"**Format:** {settings['video_format']}",
|
||||
f"**Max Quality:** {settings['video_quality']}p",
|
||||
f"**Max File Size:** {settings['max_file_size']}MB"
|
||||
]),
|
||||
inline=False
|
||||
)
|
||||
|
||||
async def _add_operation_settings(
|
||||
self,
|
||||
embed: discord.Embed,
|
||||
settings: Dict[str, Any]
|
||||
) -> None:
|
||||
"""Add operation settings to embed"""
|
||||
embed.add_field(
|
||||
name="Operation Settings",
|
||||
value="\n".join([
|
||||
f"**Delete After Repost:** {settings['delete_after_repost']}",
|
||||
f"**Message Duration:** {settings['message_duration']} hours",
|
||||
f"**Concurrent Downloads:** {settings['concurrent_downloads']}",
|
||||
f"**Max Retries:** {settings['max_retries']}",
|
||||
f"**Retry Delay:** {settings['retry_delay']}s"
|
||||
]),
|
||||
inline=False
|
||||
)
|
||||
|
||||
async def _add_site_settings(
|
||||
self,
|
||||
embed: discord.Embed,
|
||||
settings: Dict[str, Any]
|
||||
) -> None:
|
||||
"""Add site settings to embed"""
|
||||
enabled_sites = settings["enabled_sites"]
|
||||
sites_text = ", ".join(enabled_sites) if enabled_sites else "All sites"
|
||||
|
||||
embed.add_field(
|
||||
name="Enabled Sites",
|
||||
value=sites_text,
|
||||
inline=False
|
||||
)
|
||||
|
||||
async def _get_channel_mentions(
|
||||
self,
|
||||
guild: discord.Guild,
|
||||
settings: Dict[str, Any]
|
||||
) -> Dict[str, str]:
|
||||
"""Get channel mentions with error handling"""
|
||||
try:
|
||||
# Get channel objects
|
||||
archive_channel = guild.get_channel(settings["archive_channel"])
|
||||
notification_channel = guild.get_channel(settings["notification_channel"])
|
||||
log_channel = guild.get_channel(settings["log_channel"])
|
||||
|
||||
# Get monitored channels
|
||||
monitored_channels = []
|
||||
for channel_id in settings["monitored_channels"]:
|
||||
channel = guild.get_channel(channel_id)
|
||||
if channel and isinstance(channel, discord.TextChannel):
|
||||
monitored_channels.append(channel.mention)
|
||||
|
||||
return {
|
||||
"archive": archive_channel.mention if archive_channel else "Not set",
|
||||
"notification": notification_channel.mention if notification_channel else "Same as archive",
|
||||
"log": log_channel.mention if log_channel else "Not set",
|
||||
"monitored": "\n".join(monitored_channels) if monitored_channels else "All channels"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting channel mentions: {e}")
|
||||
return {
|
||||
"archive": "Error",
|
||||
"notification": "Error",
|
||||
"log": "Error",
|
||||
"monitored": "Error getting channels"
|
||||
}
|
||||
|
||||
async def _get_role_names(
|
||||
self,
|
||||
guild: discord.Guild,
|
||||
role_ids: List[int]
|
||||
) -> str:
|
||||
"""Get role names with error handling"""
|
||||
try:
|
||||
role_names = []
|
||||
for role_id in role_ids:
|
||||
role = guild.get_role(role_id)
|
||||
if role:
|
||||
role_names.append(role.name)
|
||||
|
||||
return ", ".join(role_names) if role_names else "All roles (no restrictions)"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting role names: {e}")
|
||||
return "Error getting roles"
|
||||
135
videoarchiver/config/validation_manager.py
Normal file
135
videoarchiver/config/validation_manager.py
Normal file
@@ -0,0 +1,135 @@
|
||||
"""Module for validating configuration settings"""
|
||||
|
||||
import logging
|
||||
from typing import Any, Dict, List, Union
|
||||
from .exceptions import ConfigurationError as ConfigError
|
||||
|
||||
logger = logging.getLogger("ConfigValidation")
|
||||
|
||||
class ValidationManager:
|
||||
"""Manages validation of configuration settings"""
|
||||
|
||||
# Valid settings constraints
|
||||
VALID_VIDEO_FORMATS = ["mp4", "webm", "mkv"]
|
||||
MAX_QUALITY_RANGE = (144, 4320) # 144p to 4K
|
||||
MAX_FILE_SIZE_RANGE = (1, 100) # 1MB to 100MB
|
||||
MAX_CONCURRENT_DOWNLOADS = 5
|
||||
MAX_MESSAGE_DURATION = 168 # 1 week in hours
|
||||
MAX_RETRIES = 10
|
||||
MAX_RETRY_DELAY = 30
|
||||
|
||||
def validate_setting(self, setting: str, value: Any) -> None:
|
||||
"""Validate a setting value against constraints
|
||||
|
||||
Args:
|
||||
setting: Name of the setting to validate
|
||||
value: Value to validate
|
||||
|
||||
Raises:
|
||||
ConfigError: If validation fails
|
||||
"""
|
||||
try:
|
||||
validator = getattr(self, f"_validate_{setting}", None)
|
||||
if validator:
|
||||
validator(value)
|
||||
else:
|
||||
self._validate_generic(setting, value)
|
||||
except Exception as e:
|
||||
logger.error(f"Validation error for {setting}: {e}")
|
||||
raise ConfigError(f"Validation error for {setting}: {str(e)}")
|
||||
|
||||
def _validate_video_format(self, value: str) -> None:
|
||||
"""Validate video format setting"""
|
||||
if value not in self.VALID_VIDEO_FORMATS:
|
||||
raise ConfigError(
|
||||
f"Invalid video format. Must be one of: {', '.join(self.VALID_VIDEO_FORMATS)}"
|
||||
)
|
||||
|
||||
def _validate_video_quality(self, value: int) -> None:
|
||||
"""Validate video quality setting"""
|
||||
if not isinstance(value, int) or not (
|
||||
self.MAX_QUALITY_RANGE[0] <= value <= self.MAX_QUALITY_RANGE[1]
|
||||
):
|
||||
raise ConfigError(
|
||||
f"Video quality must be between {self.MAX_QUALITY_RANGE[0]} and {self.MAX_QUALITY_RANGE[1]}"
|
||||
)
|
||||
|
||||
def _validate_max_file_size(self, value: Union[int, float]) -> None:
|
||||
"""Validate max file size setting"""
|
||||
if not isinstance(value, (int, float)) or not (
|
||||
self.MAX_FILE_SIZE_RANGE[0] <= value <= self.MAX_FILE_SIZE_RANGE[1]
|
||||
):
|
||||
raise ConfigError(
|
||||
f"Max file size must be between {self.MAX_FILE_SIZE_RANGE[0]} and {self.MAX_FILE_SIZE_RANGE[1]} MB"
|
||||
)
|
||||
|
||||
def _validate_concurrent_downloads(self, value: int) -> None:
|
||||
"""Validate concurrent downloads setting"""
|
||||
if not isinstance(value, int) or not (1 <= value <= self.MAX_CONCURRENT_DOWNLOADS):
|
||||
raise ConfigError(
|
||||
f"Concurrent downloads must be between 1 and {self.MAX_CONCURRENT_DOWNLOADS}"
|
||||
)
|
||||
|
||||
def _validate_message_duration(self, value: int) -> None:
|
||||
"""Validate message duration setting"""
|
||||
if not isinstance(value, int) or not (0 <= value <= self.MAX_MESSAGE_DURATION):
|
||||
raise ConfigError(
|
||||
f"Message duration must be between 0 and {self.MAX_MESSAGE_DURATION} hours"
|
||||
)
|
||||
|
||||
def _validate_max_retries(self, value: int) -> None:
|
||||
"""Validate max retries setting"""
|
||||
if not isinstance(value, int) or not (0 <= value <= self.MAX_RETRIES):
|
||||
raise ConfigError(
|
||||
f"Max retries must be between 0 and {self.MAX_RETRIES}"
|
||||
)
|
||||
|
||||
def _validate_retry_delay(self, value: int) -> None:
|
||||
"""Validate retry delay setting"""
|
||||
if not isinstance(value, int) or not (1 <= value <= self.MAX_RETRY_DELAY):
|
||||
raise ConfigError(
|
||||
f"Retry delay must be between 1 and {self.MAX_RETRY_DELAY} seconds"
|
||||
)
|
||||
|
||||
def _validate_message_template(self, value: str) -> None:
|
||||
"""Validate message template setting"""
|
||||
if not isinstance(value, str):
|
||||
raise ConfigError("Message template must be a string")
|
||||
|
||||
# Check for required placeholders
|
||||
required_placeholders = ["{username}", "{channel}"]
|
||||
for placeholder in required_placeholders:
|
||||
if placeholder not in value:
|
||||
raise ConfigError(f"Message template must contain {placeholder}")
|
||||
|
||||
def _validate_boolean(self, value: bool) -> None:
|
||||
"""Validate boolean settings"""
|
||||
if not isinstance(value, bool):
|
||||
raise ConfigError("Value must be a boolean")
|
||||
|
||||
def _validate_list(self, value: List[Any]) -> None:
|
||||
"""Validate list settings"""
|
||||
if not isinstance(value, list):
|
||||
raise ConfigError("Value must be a list")
|
||||
|
||||
def _validate_generic(self, setting: str, value: Any) -> None:
|
||||
"""Generic validation for settings without specific validators"""
|
||||
if setting.endswith("_channel") and value is not None:
|
||||
if not isinstance(value, int):
|
||||
raise ConfigError(f"{setting} must be a channel ID (int) or None")
|
||||
elif setting in ["enabled", "delete_after_repost", "disable_update_check", "use_database"]:
|
||||
self._validate_boolean(value)
|
||||
elif setting in ["monitored_channels", "allowed_roles", "enabled_sites"]:
|
||||
self._validate_list(value)
|
||||
|
||||
def validate_all_settings(self, settings: Dict[str, Any]) -> None:
|
||||
"""Validate all settings in a configuration dictionary
|
||||
|
||||
Args:
|
||||
settings: Dictionary of settings to validate
|
||||
|
||||
Raises:
|
||||
ConfigError: If any validation fails
|
||||
"""
|
||||
for setting, value in settings.items():
|
||||
self.validate_setting(setting, value)
|
||||
Reference in New Issue
Block a user