Skip to main content
Glama
utils.py11.6 kB
# src/discord_mcp/utils.py """ Utility functions for Discord operations """ import discord import aiohttp from typing import List, Optional def parse_permissions(permission_list: List[str]) -> discord.Permissions: """Convert list of permission strings to discord.Permissions object""" if not permission_list: return discord.Permissions.none() permissions = discord.Permissions.none() # Map common permission names to discord.py attributes permission_mapping = { "administrator": "administrator", "admin": "administrator", "manage_server": "manage_guild", "manage_guild": "manage_guild", "manage_channels": "manage_channels", "manage_roles": "manage_roles", "manage_messages": "manage_messages", "manage_webhooks": "manage_webhooks", "manage_emojis": "manage_emojis_and_stickers", "manage_emojis_and_stickers": "manage_emojis_and_stickers", "kick_members": "kick_members", "ban_members": "ban_members", "create_instant_invite": "create_instant_invite", "view_channels": "view_channel", "view_channel": "view_channel", "send_messages": "send_messages", "send_tts_messages": "send_tts_messages", "embed_links": "embed_links", "attach_files": "attach_files", "read_message_history": "read_message_history", "mention_everyone": "mention_everyone", "use_external_emojis": "external_emojis", "external_emojis": "external_emojis", "add_reactions": "add_reactions", "connect": "connect", "speak": "speak", "mute_members": "mute_members", "deafen_members": "deafen_members", "move_members": "move_members", "use_voice_activation": "use_voice_activation", "priority_speaker": "priority_speaker", "stream": "stream", "change_nickname": "change_nickname", "manage_nicknames": "manage_nicknames", "use_application_commands": "use_application_commands", "request_to_speak": "request_to_speak", "manage_events": "manage_events", "manage_threads": "manage_threads", "create_public_threads": "create_public_threads", "create_private_threads": "create_private_threads", "use_external_stickers": "external_stickers", "send_messages_in_threads": "send_messages_in_threads", "use_embedded_activities": "use_embedded_activities", "moderate_members": "moderate_members" } for perm in permission_list: # Convert to lowercase and handle variations perm_lower = perm.lower().replace(" ", "_").replace("-", "_") # Map the permission discord_perm = permission_mapping.get(perm_lower, perm_lower) if hasattr(permissions, discord_perm): setattr(permissions, discord_perm, True) return permissions def hex_to_color(hex_str: str) -> discord.Color: """Convert hex color string to discord.Color""" if not hex_str: return discord.Color.default() # Remove # if present if hex_str.startswith('#'): hex_str = hex_str[1:] try: # Convert hex to integer color_int = int(hex_str, 16) return discord.Color(color_int) except ValueError: # Return default color if conversion fails return discord.Color.default() async def fetch_image_bytes(url: str) -> Optional[bytes]: """Fetch image bytes from URL for emoji/sticker creation""" if not url: return None try: async with aiohttp.ClientSession() as session: async with session.get(url) as resp: if resp.status == 200: return await resp.read() return None except Exception: return None def format_channel_type(channel_type: discord.ChannelType) -> str: """Format channel type for display""" type_emojis = { discord.ChannelType.text: "💬", discord.ChannelType.voice: "🔊", discord.ChannelType.category: "📁", discord.ChannelType.news: "📢", discord.ChannelType.stage_voice: "🎤", discord.ChannelType.forum: "💭", discord.ChannelType.private_thread: "🧵", discord.ChannelType.public_thread: "🧵", discord.ChannelType.news_thread: "📰" } emoji = type_emojis.get(channel_type, "📋") return f"{emoji} {channel_type.name.replace('_', ' ').title()}" def format_permissions(permissions: discord.Permissions) -> List[str]: """Format permissions object into readable list""" perm_list = [] # Check important permissions important_perms = [ ("administrator", "Administrator"), ("manage_guild", "Manage Server"), ("manage_channels", "Manage Channels"), ("manage_roles", "Manage Roles"), ("manage_messages", "Manage Messages"), ("kick_members", "Kick Members"), ("ban_members", "Ban Members"), ("send_messages", "Send Messages"), ("view_channel", "View Channels"), ("connect", "Connect to Voice"), ("speak", "Speak in Voice") ] for perm_attr, perm_name in important_perms: if getattr(permissions, perm_attr, False): perm_list.append(perm_name) return perm_list def validate_server_id(server_id: str) -> bool: """Validate that server_id is a valid Discord ID""" try: # Discord IDs are 64-bit integers id_int = int(server_id) return 17 <= len(server_id) <= 20 # Discord IDs are typically 17-20 digits except ValueError: return False def validate_channel_id(channel_id: str) -> bool: """Validate that channel_id is a valid Discord ID""" return validate_server_id(channel_id) # Same validation def validate_user_id(user_id: str) -> bool: """Validate that user_id is a valid Discord ID""" return validate_server_id(user_id) # Same validation def validate_role_id(role_id: str) -> bool: """Validate that role_id is a valid Discord ID""" return validate_server_id(role_id) # Same validation def truncate_text(text: str, max_length: int = 2000) -> str: """Truncate text to fit Discord message limits""" if len(text) <= max_length: return text return text[:max_length - 3] + "..." def format_member_activity(member: discord.Member) -> str: """Format member activity status""" status_emojis = { discord.Status.online: "🟢", discord.Status.idle: "🟡", discord.Status.dnd: "🔴", discord.Status.offline: "⚫" } emoji = status_emojis.get(member.status, "⚫") return f"{emoji} {member.status.name.title()}" def calculate_setup_complexity(description: str) -> str: """Calculate setup complexity based on description""" complexity_indicators = { "simple": ["basic", "simple", "minimal", "small"], "medium": ["moderate", "standard", "normal", "team"], "complex": ["advanced", "complex", "enterprise", "large", "comprehensive"] } description_lower = description.lower() for level, indicators in complexity_indicators.items(): if any(indicator in description_lower for indicator in indicators): return level # Default to medium complexity return "medium" def extract_mentioned_features(description: str) -> List[str]: """Extract mentioned features from description""" feature_keywords = { "voice": ["voice", "talk", "call", "speaking"], "stage": ["stage", "presentation", "lecture", "announce"], "forum": ["forum", "discussion", "topic", "threads"], "moderation": ["moderation", "mod", "rules", "ban", "kick"], "roles": ["roles", "permissions", "hierarchy", "staff"], "channels": ["channels", "rooms", "areas", "sections"], "automation": ["auto", "bot", "automatic", "trigger"], "events": ["events", "schedule", "calendar", "meetings"], "categories": ["categories", "organization", "structure"] } description_lower = description.lower() found_features = [] for feature, keywords in feature_keywords.items(): if any(keyword in description_lower for keyword in keywords): found_features.append(feature) return found_features class PermissionValidator: """Validate and check Discord permissions""" @staticmethod def has_required_permissions(member: discord.Member, required_perms: List[str]) -> tuple[bool, List[str]]: """Check if member has required permissions""" missing_perms = [] for perm in required_perms: if not getattr(member.guild_permissions, perm.lower(), False): missing_perms.append(perm) return len(missing_perms) == 0, missing_perms @staticmethod def get_dangerous_permissions() -> List[str]: """Get list of dangerous permissions that should be carefully managed""" return [ "administrator", "manage_guild", "manage_roles", "ban_members", "kick_members", "manage_channels", "manage_webhooks" ] @staticmethod def validate_permission_hierarchy(guild: discord.Guild, bot_member: discord.Member, target_role: discord.Role) -> bool: """Validate that bot can manage the target role""" return bot_member.top_role.position > target_role.position class ErrorFormatter: """Format errors for user-friendly display""" @staticmethod def format_discord_error(error: Exception) -> str: """Format Discord errors for display""" if isinstance(error, discord.Forbidden): return "❌ Permission denied. The bot lacks necessary permissions for this action." elif isinstance(error, discord.NotFound): return "❌ Resource not found. The specified server, channel, or user doesn't exist." elif isinstance(error, discord.HTTPException): return f"❌ Discord API error: {error.text}" elif isinstance(error, ValueError): return f"❌ Invalid input: {str(error)}" else: return f"❌ Unexpected error: {str(error)}" @staticmethod def format_validation_error(field: str, value: str, expected: str) -> str: """Format validation errors""" return f"❌ Invalid {field}: '{value}'. Expected {expected}." def get_channel_mention(channel_id: int) -> str: """Get Discord channel mention format""" return f"<#{channel_id}>" def get_user_mention(user_id: int) -> str: """Get Discord user mention format""" return f"<@{user_id}>" def get_role_mention(role_id: int) -> str: """Get Discord role mention format""" return f"<@&{role_id}>" def format_timestamp(timestamp: str, format_type: str = "f") -> str: """Format timestamp for Discord""" return f"<t:{timestamp}:{format_type}>" # Constants for easy reference DISCORD_LIMITS = { "message_length": 2000, "embed_title": 256, "embed_description": 4096, "embed_field_name": 256, "embed_field_value": 1024, "embed_footer": 2048, "embed_author": 256, "channel_name": 100, "role_name": 100, "guild_name": 100, "emoji_name": 32, "webhook_name": 80 } DEFAULT_COLORS = { "success": 0x00ff00, "error": 0xff0000, "warning": 0xffff00, "info": 0x0099ff, "default": 0x99aab5 }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wowjinxy/mcp-discord'

If you have feedback or need assistance with the MCP directory API, please join our Discord server