| """ |
| ATOM Discord Integration Module |
| Integrates Discord seamlessly into ATOM's unified communication ecosystem |
| """ |
|
|
| import asyncio |
| from dataclasses import asdict, dataclass |
| from datetime import datetime, timedelta, timezone |
| from enum import Enum |
| import json |
| import logging |
| import os |
| from typing import Any, Dict, List, Optional, Union |
|
|
| |
| try: |
| from atom_ingestion_pipeline import AtomIngestionPipeline |
| from atom_memory_service import AtomMemoryService |
| from atom_search_service import AtomSearchService |
| from atom_workflow_service import AtomWorkflowService |
| from discord_analytics_engine import discord_analytics_engine |
| from discord_enhanced_service import ( |
| DiscordChannel, |
| DiscordGuild, |
| DiscordMessage, |
| DiscordUser, |
| discord_enhanced_service, |
| ) |
|
|
| from core.models import UnifiedWorkspace |
| except ImportError as e: |
| logging.warning(f"Discord integration services not available: {e}") |
| discord_enhanced_service = None |
| discord_analytics_engine = None |
| DiscordGuild = None |
| DiscordChannel = None |
| DiscordMessage = None |
| DiscordUser = None |
| UnifiedWorkspace = None |
|
|
| logger = logging.getLogger(__name__) |
|
|
| |
| class AtomDiscordIntegration: |
| """Main integration class for Discord within ATOM ecosystem""" |
| |
| def __init__(self, config: Dict[str, Any]): |
| self.config = config |
| self.atom_memory = config.get('atom_memory_service') |
| self.atom_search = config.get('atom_search_service') |
| self.atom_workflow = config.get('atom_workflow_service') |
| self.atom_ingestion = config.get('atom_ingestion_pipeline') |
| self.db = config.get('database') |
|
|
| |
| self.discord_service = discord_enhanced_service |
| self.discord_analytics = discord_analytics_engine |
|
|
| |
| self.workspace_sync = None |
| if self.db: |
| try: |
| from integrations.workspace_sync_service import WorkspaceSyncService |
| self.workspace_sync = WorkspaceSyncService(self.db) |
| logger.info("Workspace sync service initialized for Discord integration") |
| except ImportError as e: |
| logger.warning(f"Workspace sync service not available: {e}") |
|
|
| |
| self.is_initialized = False |
| self.active_guilds: List[DiscordGuild] = [] |
| self.communication_channels: List[Dict[str, Any]] = [] |
| self.unified_messages: List[Dict[str, Any]] = [] |
| |
| logger.info("ATOM Discord Integration initialized") |
| |
| async def initialize(self) -> bool: |
| """Initialize Discord integration with ATOM services""" |
| try: |
| if not all([self.discord_service, self.atom_memory, self.atom_search]): |
| logger.error("Required services not available for Discord integration") |
| return False |
| |
| |
| await self._start_integration_workers() |
| |
| |
| await self._initialize_unified_data() |
| |
| |
| await self._setup_cross_platform_handlers() |
| |
| self.is_initialized = True |
| logger.info("Discord integration with ATOM ecosystem initialized successfully") |
| return True |
| |
| except Exception as e: |
| logger.error(f"Error initializing Discord integration: {e}") |
| return False |
| |
| async def get_unified_workspaces(self, user_id: str) -> List[Dict[str, Any]]: |
| """Get unified workspaces across all platforms including Discord""" |
| try: |
| |
| discord_guilds = await self.discord_service.get_guilds(user_id) |
| |
| |
| unified_workspaces = [] |
| |
| for guild in discord_guilds: |
| unified_workspace = { |
| 'id': f"discord_{guild.guild_id}", |
| 'name': guild.name, |
| 'type': 'discord', |
| 'platform': 'Discord', |
| 'status': 'connected' if guild.is_connected else 'disconnected', |
| 'member_count': guild.member_count, |
| 'channel_count': guild.channel_count, |
| 'icon_url': guild.icon_url, |
| 'description': guild.description, |
| 'integration_data': { |
| 'guild_id': guild.guild_id, |
| 'owner_id': guild.owner_id, |
| 'owner_name': guild.owner_name, |
| 'region': guild.region, |
| 'features': guild.features, |
| 'premium_tier': guild.premium_tier, |
| 'verification_level': guild.verification_level, |
| 'roles_count': guild.roles_count, |
| 'emojis_count': guild.emojis_count, |
| 'created_at': guild.created_at.isoformat() if guild.created_at else None |
| }, |
| 'capabilities': { |
| 'messaging': True, |
| 'voice_calls': True, |
| 'video_calls': True, |
| 'screen_sharing': True, |
| 'file_sharing': True, |
| 'live_streaming': True, |
| 'workflows': True, |
| 'analytics': True, |
| 'gaming_features': True, |
| 'role_management': True, |
| 'bot_automation': True, |
| 'server_boosting': True |
| } |
| } |
| unified_workspaces.append(unified_workspace) |
| |
| |
| self.active_guilds = discord_guilds |
| |
| return unified_workspaces |
| |
| except Exception as e: |
| logger.error(f"Error getting unified Discord workspaces: {e}") |
| return [] |
| |
| async def get_unified_channels(self, workspace_id: str, user_id: str = None) -> List[Dict[str, Any]]: |
| """Get unified channels across platforms for given workspace""" |
| try: |
| |
| if workspace_id.startswith('discord_'): |
| discord_workspace_id = workspace_id[8:] |
| else: |
| return [] |
| |
| |
| guild = self._get_guild_by_id(discord_workspace_id) |
| if not guild: |
| return [] |
| |
| |
| channels = await self.discord_service.get_guild_channels(discord_workspace_id) |
| |
| |
| unified_channels = [] |
| |
| for channel in channels: |
| unified_channel = { |
| 'id': f"discord_{channel.channel_id}", |
| 'name': channel.name, |
| 'display_name': channel.name, |
| 'description': channel.topic, |
| 'type': channel.type.value.lower().replace('_', '-'), |
| 'platform': 'Discord', |
| 'workspace_id': workspace_id, |
| 'workspace_name': guild.name, |
| 'status': 'active' if not channel.is_archived else 'archived', |
| 'member_count': channel.member_count, |
| 'message_count': channel.message_count, |
| 'unread_count': 0, |
| 'last_activity': channel.last_modified_at, |
| 'is_private': channel.is_private, |
| 'is_muted': False, |
| 'is_favorite': False, |
| 'is_text': channel.is_text, |
| 'is_voice': channel.is_voice, |
| 'is_stage': channel.is_stage, |
| 'is_news': channel.is_news, |
| 'is_thread': channel.is_thread, |
| 'is_archived': channel.is_archived, |
| 'position': channel.position, |
| 'parent_id': channel.parent_id, |
| 'permissions': channel.permissions, |
| 'rate_limit': channel.rate_limit_per_user, |
| 'integration_data': { |
| 'channel_id': channel.channel_id, |
| 'channel_type': channel.type.value, |
| 'guild_id': channel.guild_id, |
| 'nsfw': channel.nsfw, |
| 'bitrate': channel.bitrate, |
| 'user_limit': channel.user_limit, |
| 'default_auto_archive_duration': channel.default_auto_archive_duration, |
| 'flags': channel.flags, |
| 'permission_overwrites': channel.permission_overwrites, |
| 'last_pin_timestamp': channel.last_pin_timestamp, |
| 'rtc_region': channel.rtc_region |
| }, |
| 'capabilities': { |
| 'messaging': channel.is_text, |
| 'file_sharing': channel.is_text, |
| 'voice_calls': channel.is_voice, |
| 'video_calls': channel.is_voice or channel.is_stage, |
| 'screen_sharing': channel.is_voice or channel.is_stage, |
| 'voice_chat': channel.is_voice, |
| 'streaming': channel.is_voice, |
| 'announcements': channel.is_news, |
| 'threads': channel.is_text, |
| 'reactions': channel.is_text |
| } |
| } |
| unified_channels.append(unified_channel) |
| |
| |
| self.communication_channels.extend(unified_channels) |
| |
| return unified_channels |
| |
| except Exception as e: |
| logger.error(f"Error getting unified Discord channels: {e}") |
| return [] |
| |
| async def send_unified_message(self, workspace_id: str, channel_id: str, |
| content: str, options: Dict[str, Any] = None) -> Dict[str, Any]: |
| """Send unified message across platforms including Discord""" |
| try: |
| options = options or {} |
| |
| |
| if channel_id.startswith('discord_'): |
| discord_channel_id = channel_id[8:] |
| |
| |
| discord_result = await self.discord_service.send_message( |
| guild_id=options.get('guild_id'), |
| channel_id=discord_channel_id, |
| content=content, |
| embed=options.get('embed'), |
| components=options.get('components'), |
| tts=options.get('tts', False) |
| ) |
| |
| if discord_result.get('ok'): |
| |
| await self._store_message_in_memory(discord_result, 'discord', options) |
| |
| |
| await self._index_message_in_search(discord_result, 'discord', options) |
| |
| |
| await self._trigger_workflows(discord_result, 'discord_message_sent', options) |
| |
| return { |
| 'ok': True, |
| 'message_id': discord_result.get('message_id'), |
| 'platform': 'Discord', |
| 'timestamp': datetime.utcnow().isoformat(), |
| 'channel_id': channel_id, |
| 'workspace_id': workspace_id |
| } |
| else: |
| return discord_result |
| |
| |
| else: |
| return {'ok': False, 'error': 'Unsupported platform'} |
| |
| except Exception as e: |
| logger.error(f"Error sending unified Discord message: {e}") |
| return {'ok': False, 'error': str(e)} |
| |
| async def get_unified_messages(self, workspace_id: str, channel_id: str, |
| limit: int = 100, options: Dict[str, Any] = None) -> List[Dict[str, Any]]: |
| """Get unified messages across platforms including Discord""" |
| try: |
| options = options or {} |
| unified_messages = [] |
| |
| |
| if channel_id.startswith('discord_'): |
| discord_channel_id = channel_id[8:] |
| discord_workspace_id = workspace_id[8:] |
| |
| |
| discord_messages = await self.discord_service.get_channel_messages( |
| guild_id=discord_workspace_id, |
| channel_id=discord_channel_id, |
| limit=limit, |
| before=options.get('before'), |
| after=options.get('after'), |
| around=options.get('around') |
| ) |
| |
| |
| for message in discord_messages: |
| unified_message = { |
| 'id': f"discord_{message.message_id}", |
| 'content': message.content, |
| 'html_content': message.content, |
| 'platform': 'Discord', |
| 'workspace_id': workspace_id, |
| 'channel_id': channel_id, |
| 'user_id': f"discord_{message.user_id}", |
| 'user_name': message.user_name, |
| 'user_display_name': message.user_display_name, |
| 'user_discriminator': message.user_discriminator, |
| 'user_avatar': message.user_avatar, |
| 'timestamp': message.timestamp, |
| 'thread_id': f"discord_{message.thread_id}" if message.thread_id else None, |
| 'reply_to_id': message.reply_to_id, |
| 'message_type': self._convert_discord_message_type(message.type), |
| 'subject': None, |
| 'is_edited': message.is_edited, |
| 'edit_timestamp': message.edited_timestamp, |
| 'is_pinned': message.is_pinned, |
| 'is_crossposted': message.is_crossposted, |
| 'is_command': message.is_command, |
| 'is_bot': message.is_bot, |
| 'is_webhook': message.is_webhook, |
| 'is_system': message.is_system, |
| 'reactions': self._convert_discord_reactions(message.reactions), |
| 'attachments': self._convert_discord_attachments(message.attachments), |
| 'mentions': self._convert_discord_mentions(message.mentions), |
| 'files': self._convert_discord_attachments(message.attachments), |
| 'embeds': self._convert_discord_embeds(message.embeds), |
| 'components': message.components, |
| 'stickers': message.stickers, |
| 'role_mentions': message.mention_roles, |
| 'channel_mentions': message.mention_channels, |
| 'mention_everyone': message.mention_everyone, |
| 'tts': message.tts, |
| 'pinned': message.pinned, |
| 'integration_data': { |
| 'message_id': message.message_id, |
| 'user_id': message.user_id, |
| 'user_discriminator': message.user_discriminator, |
| 'message_type': message.type, |
| 'flags': message.flags, |
| 'member': message.member, |
| 'referenced_message': message.referenced_message, |
| 'interaction': message.interaction, |
| 'application_id': message.application_id, |
| 'webhook_id': message.webhook_id, |
| 'position': message.position, |
| 'message_snapshots': message.message_snapshots |
| }, |
| 'metadata': { |
| 'has_thread': bool(message.thread_id), |
| 'has_reactions': bool(message.reactions), |
| 'has_attachments': bool(message.attachments), |
| 'has_embeds': bool(message.embeds), |
| 'has_components': bool(message.components), |
| 'has_stickers': bool(message.stickers), |
| 'is_tts': message.tts, |
| 'mentions_everyone': message.mention_everyone |
| } |
| } |
| unified_messages.append(unified_message) |
| |
| |
| self.unified_messages.extend(unified_messages) |
| |
| |
| unified_messages.sort(key=lambda x: x['timestamp'], reverse=True) |
| |
| return unified_messages[:limit] |
| |
| except Exception as e: |
| logger.error(f"Error getting unified Discord messages: {e}") |
| return [] |
| |
| async def unified_search(self, query: str, workspace_id: str = None, |
| channel_id: str = None, options: Dict[str, Any] = None) -> List[Dict[str, Any]]: |
| """Perform unified search across platforms including Discord""" |
| try: |
| options = options or {} |
| unified_results = [] |
| |
| |
| if channel_id and channel_id.startswith('discord_'): |
| discord_channel_id = channel_id[8:] |
| discord_workspace_id = workspace_id[8:] if workspace_id and workspace_id.startswith('discord_') else None |
| |
| discord_results = await self.discord_service.search_messages( |
| guild_id=discord_workspace_id, |
| channel_id=discord_channel_id, |
| query=query, |
| limit=options.get('limit', 50), |
| before=options.get('before'), |
| after=options.get('after') |
| ) |
| |
| if discord_results.get('ok'): |
| |
| |
| pass |
| |
| |
| |
| |
| unified_results.sort(key=lambda x: x['relevance_score'], reverse=True) |
| |
| return unified_results[:options.get('limit', 50)] |
| |
| except Exception as e: |
| logger.error(f"Error in unified Discord search: {e}") |
| return [] |
| |
| async def create_unified_workflow(self, workflow_data: Dict[str, Any]) -> Dict[str, Any]: |
| """Create unified workflow that can operate across platforms including Discord""" |
| try: |
| |
| discord_involved = False |
| for trigger in workflow_data.get('triggers', []): |
| if trigger.get('platform') == 'discord' or 'discord' in trigger.get('event', '').lower(): |
| discord_involved = True |
| break |
| |
| for action in workflow_data.get('actions', []): |
| if action.get('platform') == 'discord' or 'discord' in action.get('action', '').lower(): |
| discord_involved = True |
| break |
| |
| if not discord_involved: |
| |
| if self.atom_workflow: |
| return await self.atom_workflow.create_workflow(workflow_data) |
| else: |
| return {'ok': False, 'error': 'Workflow service not available'} |
| |
| |
| |
| |
| return { |
| 'ok': True, |
| 'workflow_id': f"discord_workflow_{int(datetime.utcnow().timestamp())}", |
| 'platform': 'discord', |
| 'message': 'Discord workflow created successfully' |
| } |
| |
| except Exception as e: |
| logger.error(f"Error creating unified Discord workflow: {e}") |
| return {'ok': False, 'error': str(e)} |
| |
| async def get_unified_analytics(self, metric: str, time_range: str, |
| workspace_id: str = None, options: Dict[str, Any] = None) -> Dict[str, Any]: |
| """Get unified analytics across platforms including Discord""" |
| try: |
| options = options or {} |
| |
| |
| if self.discord_analytics: |
| discord_analytics = await self.discord_analytics.get_analytics( |
| metric=metric, |
| time_range=time_range, |
| workspace_id=workspace_id[8:] if workspace_id and workspace_id.startswith('discord_') else None, |
| filters=options.get('filters', {}) |
| ) |
| else: |
| discord_analytics = [] |
| |
| |
| unified_analytics = { |
| 'platform': 'Discord', |
| 'metric': metric, |
| 'time_range': time_range, |
| 'workspace_id': workspace_id, |
| 'data_points': [ |
| { |
| 'timestamp': point.timestamp.isoformat(), |
| 'value': point.value, |
| 'dimensions': point.dimensions, |
| 'metadata': point.metadata |
| } |
| for point in discord_analytics |
| ], |
| 'total_points': len(discord_analytics) |
| } |
| |
| |
| |
| return unified_analytics |
| |
| except Exception as e: |
| logger.error(f"Error getting unified Discord analytics: {e}") |
| return {'ok': False, 'error': str(e)} |
| |
| |
| async def _start_integration_workers(self): |
| """Start background integration workers""" |
| |
| asyncio.create_task(self._discord_message_ingestion_worker()) |
| |
| |
| asyncio.create_task(self._discord_event_processing_worker()) |
| |
| |
| asyncio.create_task(self._unified_search_indexing_worker()) |
| |
| async def _initialize_unified_data(self): |
| """Initialize unified data structures""" |
| |
| if self.atom_memory: |
| try: |
| |
| workspaces_data = await self.atom_memory.query({ |
| 'type': 'unified_workspace', |
| 'platform': 'discord' |
| }) |
| |
| |
| channels_data = await self.atom_memory.query({ |
| 'type': 'unified_channel', |
| 'platform': 'discord' |
| }) |
| |
| |
| messages_data = await self.atom_memory.query({ |
| 'type': 'unified_message', |
| 'platform': 'discord' |
| }) |
| |
| logger.info(f"Loaded unified Discord data: {len(workspaces_data)} workspaces, {len(channels_data)} channels, {len(messages_data)} messages") |
| |
| except Exception as e: |
| logger.error(f"Error loading unified Discord data: {e}") |
| |
| async def _setup_cross_platform_handlers(self): |
| """Setup cross-platform event handlers""" |
| |
| |
| |
| if self.discord_service: |
| self.discord_service.event_handlers[DiscordEventType.MESSAGE_CREATE].append( |
| self._handle_discord_message_cross_platform |
| ) |
| |
| self.discord_service.event_handlers[DiscordEventType.GUILD_CREATE].append( |
| self._handle_discord_guild_event_cross_platform |
| ) |
| |
| self.discord_service.event_handlers[DiscordEventType.VOICE_STATE_UPDATE].append( |
| self._handle_discord_voice_event_cross_platform |
| ) |
| |
| async def _handle_discord_message_cross_platform(self, event_data: Dict[str, Any]): |
| """Handle Discord message cross-platform integration""" |
| try: |
| |
| await self._store_message_in_memory(event_data, 'discord') |
| |
| |
| await self._index_message_in_search(event_data, 'discord') |
| |
| |
| await self._trigger_workflows(event_data, 'discord_message_cross_platform') |
| |
| except Exception as e: |
| logger.error(f"Error handling Discord message cross-platform: {e}") |
| |
| async def _handle_discord_guild_event_cross_platform(self, event_data: Dict[str, Any]): |
| """Handle Discord guild event cross-platform integration""" |
| try: |
| |
| await self._update_workspace_cross_platform(event_data, 'discord') |
| |
| |
| await self._trigger_workflows(event_data, 'discord_guild_event_cross_platform') |
| |
| except Exception as e: |
| logger.error(f"Error handling Discord guild event cross-platform: {e}") |
| |
| async def _handle_discord_voice_event_cross_platform(self, event_data: Dict[str, Any]): |
| """Handle Discord voice event cross-platform integration""" |
| try: |
| |
| await self._update_voice_state_cross_platform(event_data, 'discord') |
| |
| |
| await self._trigger_workflows(event_data, 'discord_voice_event_cross_platform') |
| |
| except Exception as e: |
| logger.error(f"Error handling Discord voice event cross-platform: {e}") |
| |
| def _get_guild_by_id(self, guild_id: str) -> Optional[DiscordGuild]: |
| """Get Discord guild by ID""" |
| try: |
| |
| |
| return DiscordGuild( |
| guild_id=guild_id, |
| name='Discord Server', |
| owner_id='owner_id', |
| owner_name='Server Owner' |
| ) |
| except Exception as e: |
| logger.error(f"Error getting Discord guild by ID: {e}") |
| return None |
| |
| def _convert_discord_message_type(self, message_type: int) -> str: |
| """Convert Discord message type to unified format""" |
| type_mapping = { |
| 0: 'default', |
| 1: 'recipient_add', |
| 2: 'recipient_remove', |
| 3: 'call', |
| 4: 'channel_name_change', |
| 5: 'channel_icon_change', |
| 6: 'channel_pinned_message', |
| 7: 'guild_member_join', |
| 8: 'guild_boost', |
| 9: 'guild_boost_tier_1', |
| 10: 'guild_boost_tier_2', |
| 11: 'guild_boost_tier_3', |
| 12: 'channel_follow_add', |
| 14: 'guild_discovery_disqualified', |
| 15: 'guild_discovery_requalified', |
| 16: 'guild_discovery_grace_period_initial_warning', |
| 17: 'guild_discovery_grace_period_final_warning', |
| 18: 'thread_created', |
| 19: 'reply', |
| 20: 'chat_input_command', |
| 21: 'thread_starter_message', |
| 22: 'guild_invite_reminder', |
| 23: 'context_menu_command', |
| 24: 'auto_moderation_action' |
| } |
| return type_mapping.get(message_type, 'unknown') |
| |
| def _convert_discord_reactions(self, reactions: List[Dict]) -> List[Dict]: |
| """Convert Discord reactions to unified format""" |
| unified_reactions = [] |
| for reaction in reactions: |
| unified_reactions.append({ |
| 'emoji': reaction.get('emoji', {}).get('name', '❓'), |
| 'id': reaction.get('emoji', {}).get('id'), |
| 'animated': reaction.get('emoji', {}).get('animated', False), |
| 'count': reaction.get('count', 1), |
| 'me': reaction.get('me', False) |
| }) |
| return unified_reactions |
| |
| def _convert_discord_attachments(self, attachments: List[Dict]) -> List[Dict]: |
| """Convert Discord attachments to unified format""" |
| unified_attachments = [] |
| for attachment in attachments: |
| unified_attachments.append({ |
| 'id': attachment.get('id'), |
| 'title': attachment.get('filename'), |
| 'content_type': attachment.get('content_type'), |
| 'download_url': attachment.get('url'), |
| 'proxy_url': attachment.get('proxy_url'), |
| 'size': attachment.get('size', 0), |
| 'width': attachment.get('width'), |
| 'height': attachment.get('height'), |
| 'type': 'discord_attachment' |
| }) |
| return unified_attachments |
| |
| def _convert_discord_mentions(self, mentions: List[Dict]) -> List[Dict]: |
| """Convert Discord mentions to unified format""" |
| unified_mentions = [] |
| for mention in mentions: |
| unified_mentions.append({ |
| 'id': mention.get('id'), |
| 'username': mention.get('username'), |
| 'discriminator': mention.get('discriminator'), |
| 'display_name': mention.get('display_name'), |
| 'avatar': mention.get('avatar'), |
| 'type': 'user', |
| 'platform': 'Discord' |
| }) |
| return unified_mentions |
| |
| def _convert_discord_embeds(self, embeds: List[Dict]) -> List[Dict]: |
| """Convert Discord embeds to unified format""" |
| unified_embeds = [] |
| for embed in embeds: |
| unified_embeds.append({ |
| 'title': embed.get('title'), |
| 'description': embed.get('description'), |
| 'url': embed.get('url'), |
| 'type': embed.get('type'), |
| 'color': embed.get('color'), |
| 'timestamp': embed.get('timestamp'), |
| 'footer': embed.get('footer'), |
| 'image': embed.get('image'), |
| 'thumbnail': embed.get('thumbnail'), |
| 'video': embed.get('video'), |
| 'author': embed.get('author'), |
| 'fields': embed.get('fields', []) |
| }) |
| return unified_embeds |
| |
| async def _store_message_in_memory(self, message_data: Dict[str, Any], platform: str, options: Dict[str, Any] = None): |
| """Store message in unified ATOM memory""" |
| try: |
| if not self.atom_memory: |
| return |
| |
| memory_data = { |
| 'type': 'unified_message', |
| 'platform': platform, |
| 'message_id': message_data.get('message_id'), |
| 'content': message_data.get('content'), |
| 'user_id': message_data.get('user_id'), |
| 'user_name': message_data.get('user_name'), |
| 'user_discriminator': message_data.get('user_discriminator'), |
| 'channel_id': message_data.get('channel_id'), |
| 'workspace_id': message_data.get('workspace_id'), |
| 'timestamp': message_data.get('timestamp') or datetime.utcnow().isoformat(), |
| 'edited_timestamp': message_data.get('edited_timestamp'), |
| 'reactions': message_data.get('reactions', []), |
| 'attachments': message_data.get('attachments', []), |
| 'embeds': message_data.get('embeds', []), |
| 'mentions': message_data.get('mentions', []), |
| 'integration_data': message_data, |
| 'options': options or {}, |
| 'indexed': False, |
| 'synced': True |
| } |
| |
| await self.atom_memory.store(memory_data) |
| |
| except Exception as e: |
| logger.error(f"Error storing Discord message in unified memory: {e}") |
| |
| async def _index_message_in_search(self, message_data: Dict[str, Any], platform: str, options: Dict[str, Any] = None): |
| """Index message in unified ATOM search""" |
| try: |
| if not self.atom_search: |
| return |
| |
| search_data = { |
| 'type': 'unified_message', |
| 'platform': platform, |
| 'id': f"{platform}_{message_data.get('message_id')}", |
| 'title': f"Message from {message_data.get('user_name', 'Unknown')}", |
| 'content': message_data.get('content'), |
| 'metadata': { |
| 'user_id': message_data.get('user_id'), |
| 'user_name': message_data.get('user_name'), |
| 'user_discriminator': message_data.get('user_discriminator'), |
| 'channel_id': message_data.get('channel_id'), |
| 'workspace_id': message_data.get('workspace_id'), |
| 'timestamp': message_data.get('timestamp') or datetime.utcnow().isoformat(), |
| 'platform': platform, |
| 'has_attachments': bool(message_data.get('attachments')), |
| 'has_embeds': bool(message_data.get('embeds')), |
| 'has_reactions': bool(message_data.get('reactions')), |
| 'is_bot': message_data.get('is_bot', False), |
| 'is_webhook': message_data.get('is_webhook', False), |
| 'is_system': message_data.get('is_system', False), |
| 'integration_data': message_data |
| } |
| } |
| |
| await self.atom_search.index(search_data) |
| |
| except Exception as e: |
| logger.error(f"Error indexing Discord message in unified search: {e}") |
| |
| async def _trigger_workflows(self, event_data: Dict[str, Any], event_type: str, options: Dict[str, Any] = None): |
| """Trigger workflows for cross-platform events""" |
| try: |
| if not self.atom_workflow: |
| return |
| |
| workflow_trigger = { |
| 'event_type': event_type, |
| 'platform': 'discord', |
| 'data': event_data, |
| 'timestamp': datetime.utcnow().isoformat(), |
| 'options': options or {} |
| } |
| |
| await self.atom_workflow.trigger_workflows(workflow_trigger) |
| |
| except Exception as e: |
| logger.error(f"Error triggering workflows for Discord event: {e}") |
| |
| async def _update_workspace_cross_platform(self, event_data: Dict[str, Any], platform: str): |
| """Update workspace information across platforms""" |
| try: |
| if not self.workspace_sync: |
| logger.debug("Workspace sync service not available, skipping cross-platform update") |
| return |
|
|
| |
| guild_id = event_data.get('guild_id', '') |
| guild_name = event_data.get('guild_name', 'Unknown Guild') |
| event_type = event_data.get('type', '') |
|
|
| logger.info( |
| f"Processing cross-platform workspace update: " |
| f"platform={platform}, event_type={event_type}, guild={guild_name}" |
| ) |
|
|
| |
| if event_type in ['GUILD_UPDATE', 'GUILD_NAME_UPDATE']: |
| change_type = 'name_change' |
| elif event_type == 'GUILD_MEMBER_ADD': |
| change_type = 'member_add' |
| elif event_type == 'GUILD_MEMBER_REMOVE': |
| change_type = 'member_remove' |
| elif event_type == 'GUILD_ROLE_UPDATE': |
| change_type = 'member_role_change' |
| elif event_type == 'GUILD_CHANNEL_CREATE': |
| change_type = 'channel_add' |
| elif event_type == 'GUILD_CHANNEL_DELETE': |
| change_type = 'channel_remove' |
| else: |
| change_type = 'settings_change' |
|
|
| |
| unified_workspace = await self._get_or_create_unified_workspace( |
| guild_id=guild_id, |
| guild_name=guild_name |
| ) |
|
|
| if not unified_workspace: |
| logger.warning(f"Could not get or create unified workspace for guild {guild_id}") |
| return |
|
|
| |
| await self.workspace_sync.propagate_change( |
| workspace_id=unified_workspace.id, |
| source_platform='discord', |
| change_type=change_type, |
| change_data={ |
| 'guild_name': guild_name, |
| 'guild_id': guild_id, |
| 'event_type': event_type, |
| 'event_data': event_data |
| } |
| ) |
|
|
| logger.info( |
| f"Successfully propagated workspace change for {guild_name} " |
| f"(unified workspace ID: {unified_workspace.id})" |
| ) |
|
|
| except Exception as e: |
| logger.error(f"Error updating workspace cross-platform: {e}") |
|
|
| async def _get_or_create_unified_workspace( |
| self, |
| guild_id: str, |
| guild_name: str |
| ): |
| """ |
| Get or create a unified workspace for a Discord guild. |
| |
| Args: |
| guild_id: Discord guild ID |
| guild_name: Discord guild name |
| |
| Returns: |
| UnifiedWorkspace object or None |
| """ |
| try: |
| from sqlalchemy import or_ |
|
|
| |
| existing = self.db.query(UnifiedWorkspace).filter( |
| UnifiedWorkspace.discord_guild_id == guild_id |
| ).first() |
|
|
| if existing: |
| logger.debug(f"Found existing unified workspace {existing.id} for guild {guild_id}") |
| return existing |
|
|
| |
| |
| user_id = "system" |
|
|
| unified_workspace = self.workspace_sync.create_unified_workspace( |
| user_id=user_id, |
| name=guild_name, |
| description=f"Unified workspace for Discord guild: {guild_name}", |
| discord_guild_id=guild_id, |
| sync_config={ |
| 'auto_sync': True, |
| 'sync_members': True, |
| 'sync_settings': True, |
| 'sync_channels': True |
| } |
| ) |
|
|
| logger.info(f"Created unified workspace {unified_workspace.id} for Discord guild {guild_id}") |
| return unified_workspace |
|
|
| except Exception as e: |
| logger.error(f"Error getting or creating unified workspace: {e}") |
| return None |
|
|
| async def _update_voice_state_cross_platform(self, event_data: Dict[str, Any], platform: str): |
| """Update voice state information across platforms""" |
| try: |
| if not self.workspace_sync: |
| logger.debug("Workspace sync service not available, skipping voice state update") |
| return |
|
|
| |
| user_id = event_data.get('user_id', '') |
| guild_id = event_data.get('guild_id', '') |
| channel_id = event_data.get('channel_id', '') |
| state = event_data.get('state', 'unknown') |
|
|
| logger.info( |
| f"Processing cross-platform voice state update: " |
| f"platform={platform}, user={user_id}, state={state}, guild={guild_id}" |
| ) |
|
|
| |
| unified_workspace = self.db.query(UnifiedWorkspace).filter( |
| UnifiedWorkspace.discord_guild_id == guild_id |
| ).first() |
|
|
| if not unified_workspace: |
| logger.debug(f"No unified workspace found for guild {guild_id}") |
| return |
|
|
| |
| if not unified_workspace.voice_states: |
| unified_workspace.voice_states = {} |
|
|
| voice_state_key = f"{user_id}_{platform}" |
| unified_workspace.voice_states[voice_state_key] = { |
| 'user_id': user_id, |
| 'platform': platform, |
| 'channel_id': channel_id, |
| 'state': state, |
| 'timestamp': datetime.utcnow().isoformat() |
| } |
|
|
| |
| await self._check_voice_state_conflicts( |
| unified_workspace=unified_workspace, |
| user_id=user_id, |
| platform=platform, |
| state=state |
| ) |
|
|
| unified_workspace.updated_at = datetime.utcnow() |
| self.db.commit() |
|
|
| logger.info( |
| f"Updated voice state for user {user_id} in workspace {unified_workspace.id}" |
| ) |
|
|
| except Exception as e: |
| logger.error(f"Error updating voice state cross-platform: {e}") |
|
|
| async def _check_voice_state_conflicts( |
| self, |
| unified_workspace, |
| user_id: str, |
| platform: str, |
| state: str |
| ): |
| """ |
| Check if user is in voice on multiple platforms and log conflicts. |
| |
| Args: |
| unified_workspace: UnifiedWorkspace object |
| user_id: User ID |
| platform: Platform name |
| state: Voice state |
| """ |
| try: |
| |
| if state not in ['joined', 'unmuted']: |
| return |
|
|
| |
| conflicts = [] |
| for voice_state_key, voice_state_data in unified_workspace.voice_states.items(): |
| if voice_state_key.startswith(f"{user_id}_"): |
| other_platform = voice_state_data.get('platform') |
| other_state = voice_state_data.get('state') |
|
|
| |
| if other_platform == platform or other_state not in ['joined', 'unmuted']: |
| continue |
|
|
| |
| conflicts.append({ |
| 'platform': other_platform, |
| 'channel_id': voice_state_data.get('channel_id'), |
| 'timestamp': voice_state_data.get('timestamp') |
| }) |
|
|
| if conflicts: |
| logger.warning( |
| f"Voice state conflict detected for user {user_id}: " |
| f"user is active on {platform} and {[c['platform'] for c in conflicts]}" |
| ) |
|
|
| |
| if 'voice_conflicts' not in unified_workspace.metadata: |
| unified_workspace.metadata['voice_conflicts'] = [] |
|
|
| unified_workspace.metadata['voice_conflicts'].append({ |
| 'user_id': user_id, |
| 'platforms': [platform] + [c['platform'] for c in conflicts], |
| 'timestamp': datetime.utcnow().isoformat() |
| }) |
|
|
| except Exception as e: |
| logger.error(f"Error checking voice state conflicts: {e}") |
| |
| |
| async def _discord_message_ingestion_worker(self): |
| """Background worker for Discord message ingestion""" |
| while True: |
| try: |
| |
| |
| await asyncio.sleep(30) |
| |
| except Exception as e: |
| logger.error(f"Error in Discord message ingestion worker: {e}") |
| await asyncio.sleep(60) |
| |
| async def _discord_event_processing_worker(self): |
| """Background worker for Discord event processing""" |
| while True: |
| try: |
| |
| |
| await asyncio.sleep(10) |
| |
| except Exception as e: |
| logger.error(f"Error in Discord event processing worker: {e}") |
| await asyncio.sleep(30) |
| |
| async def _unified_search_indexing_worker(self): |
| """Background worker for unified search indexing""" |
| while True: |
| try: |
| |
| if self.atom_search and self.atom_memory: |
| unindexed_messages = await self.atom_memory.query({ |
| 'type': 'unified_message', |
| 'platform': 'discord', |
| 'indexed': False |
| }) |
| |
| for message in unindexed_messages: |
| await self._index_message_in_search(message, 'discord') |
| await self.atom_memory.update(message['id'], {'indexed': True}) |
| |
| await asyncio.sleep(60) |
| |
| except Exception as e: |
| logger.error(f"Error in unified search indexing worker: {e}") |
| await asyncio.sleep(120) |
|
|
| |
| atom_discord_integration = AtomDiscordIntegration({ |
| 'atom_memory_service': None, |
| 'atom_search_service': None, |
| 'atom_workflow_service': None, |
| 'atom_ingestion_pipeline': None |
| }) |