""" Matrix Client für Content Feed Publishing Publish Educational Content to Matrix Spaces/Rooms """ from matrix_nio import AsyncClient, MatrixRoom, RoomMessageText from typing import Optional, List, Dict import os import logging import json logger = logging.getLogger(__name__) class MatrixContentClient: """Matrix Client for Content Publishing""" def __init__(self): self.homeserver = os.getenv("MATRIX_HOMESERVER", "http://localhost:8008") self.access_token = os.getenv("MATRIX_ACCESS_TOKEN") self.bot_user_id = os.getenv("MATRIX_BOT_USER", "@breakpilot-bot:localhost") # Feed Rooms self.feed_room_id = os.getenv("MATRIX_FEED_ROOM", "!breakpilot-feed:localhost") self.movement_room = os.getenv("MATRIX_MOVEMENT_ROOM", "!movement:localhost") self.math_room = os.getenv("MATRIX_MATH_ROOM", "!math:localhost") self.steam_room = os.getenv("MATRIX_STEAM_ROOM", "!steam:localhost") self.client: Optional[AsyncClient] = None async def connect(self): """Connect to Matrix homeserver""" if not self.access_token: logger.warning("No Matrix access token configured - Matrix integration disabled") return False try: self.client = AsyncClient(self.homeserver, self.bot_user_id) self.client.access_token = self.access_token # Test connection whoami = await self.client.whoami() logger.info(f"✅ Matrix connected as {whoami.user_id}") return True except Exception as e: logger.error(f"❌ Matrix connection failed: {e}") return False async def disconnect(self): """Disconnect from Matrix""" if self.client: await self.client.close() async def publish_content( self, content_id: str, title: str, description: str, content_type: str, category: str, license: str, creator_name: str, thumbnail_url: Optional[str] = None, download_url: Optional[str] = None, age_range: tuple = (6, 18), tags: List[str] = [] ) -> Optional[str]: """ Publish content to Matrix feed Returns: Matrix event_id if successful, None otherwise """ if not self.client: logger.warning("Matrix client not connected") return None try: # Select room based on category room_id = self._get_room_for_category(category) # Format message message = self._format_content_message( content_id=content_id, title=title, description=description, content_type=content_type, category=category, license=license, creator_name=creator_name, thumbnail_url=thumbnail_url, download_url=download_url, age_range=age_range, tags=tags ) # Send message response = await self.client.room_send( room_id=room_id, message_type="m.educational.content", content={ "msgtype": "m.text", "body": message["plain"], "format": "org.matrix.custom.html", "formatted_body": message["html"], "info": message["metadata"] } ) logger.info(f"✅ Content published to Matrix: {content_id} → {room_id}") return response.event_id except Exception as e: logger.error(f"❌ Failed to publish content to Matrix: {e}") return None def _get_room_for_category(self, category: str) -> str: """Map content category to Matrix room""" category_map = { "movement": self.movement_room, "math": self.math_room, "steam": self.steam_room, } return category_map.get(category, self.feed_room_id) def _format_content_message( self, content_id: str, title: str, description: str, content_type: str, category: str, license: str, creator_name: str, thumbnail_url: Optional[str], download_url: Optional[str], age_range: tuple, tags: List[str] ) -> Dict[str, any]: """Format content for Matrix message""" # Content type emoji type_emoji = { "video": "📹", "pdf": "📄", "image_gallery": "🖼️", "markdown": "📝", "audio": "🎵", "h5p": "🎓" }.get(content_type, "📦") # Category emoji category_emoji = { "movement": "🏃", "math": "🔢", "steam": "🔬", "language": "📖", "arts": "🎨", "social": "🤝", "mindfulness": "🧘" }.get(category, "📚") # Plain text version plain = f""" {type_emoji} {title} {description} 📝 Von: {creator_name} {category_emoji} Kategorie: {category} 👥 Alter: {age_range[0]}-{age_range[1]} Jahre ⚖️ Lizenz: {license} 🏷️ Tags: {', '.join(tags) if tags else 'Keine'} {download_url or f'https://breakpilot.app/content/{content_id}'} """.strip() # HTML version with better formatting html = f"""
""".strip() # Metadata for indexing & reactions metadata = { "content_id": content_id, "creator": creator_name, "license": license, "category": category, "content_type": content_type, "age_range": list(age_range), "tags": tags, "download_url": download_url, "thumbnail_url": thumbnail_url } return { "plain": plain, "html": html, "metadata": metadata } async def update_content_announcement( self, event_id: str, room_id: str, new_stats: Dict[str, any] ): """Update content message with new stats (downloads, ratings)""" try: # TODO: Edit message to add stats # Matrix nio doesn't support edit yet easily - would need to send m.room.message with m.replace pass except Exception as e: logger.error(f"Failed to update content announcement: {e}") async def create_discussion_thread( self, content_id: str, room_id: str, event_id: str ) -> Optional[str]: """Create threaded discussion for content""" try: # TODO: Create thread (Matrix threading) pass except Exception as e: logger.error(f"Failed to create discussion thread: {e}") return None # Global instance matrix_client = MatrixContentClient()