""" Outlook method module. Handles Outlook operations using the Outlook service. """ import logging from typing import Dict, Any, List, Optional from datetime import datetime, UTC import json from modules.workflow.methodBase import MethodBase, ActionResult, action logger = logging.getLogger(__name__) class OutlookService: """Service for Microsoft Outlook operations using Graph API""" def __init__(self, serviceContainer: Any): self.serviceContainer = serviceContainer def _getMicrosoftConnection(self, connectionReference: str) -> Optional[Dict[str, Any]]: """Get Microsoft connection from connection reference""" try: userConnection = self.serviceContainer.getUserConnectionFromConnectionReference(connectionReference) if not userConnection or userConnection.authority != "msft" or userConnection.status != "active": return None # Get the corresponding token for this user and authority token = self.serviceContainer.interfaceApp.getToken(userConnection.authority) if not token: logger.warning(f"No token found for user {userConnection.userId} and authority {userConnection.authority}") return None return { "id": userConnection.id, "accessToken": token.tokenAccess, "refreshToken": token.tokenRefresh, "scopes": ["Mail.ReadWrite", "User.Read"] # Default Microsoft scopes } except Exception as e: logger.error(f"Error getting Microsoft connection: {str(e)}") return None async def readMails(self, connectionReference: str, folder: str = "inbox", query: str = None, maxResults: int = 10, includeAttachments: bool = False) -> Dict[str, Any]: """Read emails from Outlook using Microsoft Graph API""" try: connection = self._getMicrosoftConnection(connectionReference) if not connection: return { "error": "No valid Microsoft connection found for the provided connection reference", "connectionReference": connectionReference } # For now, simulate email reading # In a real implementation, you would use Microsoft Graph API mail_prompt = f""" Read emails from Outlook. Folder: {folder} Query: {query or 'All emails'} Max Results: {maxResults} Include Attachments: {includeAttachments} Please provide: 1. Email messages with subject, sender, and content 2. Timestamps and priority levels 3. Attachment information if requested 4. Email threading and conversations 5. Categorization and flags """ # Use AI to simulate email data mail_data = await self.serviceContainer.interfaceAiCalls.callAiTextAdvanced(mail_prompt) return { "folder": folder, "query": query, "maxResults": maxResults, "includeAttachments": includeAttachments, "messages": mail_data, "connection": { "id": connection["id"], "authority": "microsoft", "reference": connectionReference } } except Exception as e: logger.error(f"Error reading emails: {str(e)}") return { "error": str(e) } async def sendMail(self, connectionReference: str, to: List[str], subject: str, body: str, attachments: List[str] = None) -> Dict[str, Any]: """Send email using Outlook using Microsoft Graph API""" try: connection = self._getMicrosoftConnection(connectionReference) if not connection: return { "error": "No valid Microsoft connection found for the provided connection reference", "connectionReference": connectionReference } # For now, simulate email sending # In a real implementation, you would use Microsoft Graph API send_prompt = f""" Send email using Outlook. To: {', '.join(to)} Subject: {subject} Body: {body} Attachments: {attachments or 'None'} Please provide: 1. Email composition details 2. Recipient validation 3. Attachment processing 4. Send confirmation 5. Message tracking information """ # Use AI to simulate email sending send_result = await self.serviceContainer.interfaceAiCalls.callAiTextAdvanced(send_prompt) return { "to": to, "subject": subject, "body": body, "attachments": attachments, "result": send_result, "connection": { "id": connection["id"], "authority": "microsoft", "reference": connectionReference } } except Exception as e: logger.error(f"Error sending email: {str(e)}") return { "error": str(e) } async def createFolder(self, connectionReference: str, name: str, parentFolderId: str = None) -> Dict[str, Any]: """Create folder in Outlook using Microsoft Graph API""" try: connection = self._getMicrosoftConnection(connectionReference) if not connection: return { "error": "No valid Microsoft connection found for the provided connection reference", "connectionReference": connectionReference } # For now, simulate folder creation # In a real implementation, you would use Microsoft Graph API folder_prompt = f""" Create folder in Outlook. Name: {name} Parent Folder ID: {parentFolderId or 'Root'} Please provide: 1. Folder creation details 2. Permission settings 3. Folder structure and hierarchy 4. Creation confirmation 5. Folder properties and metadata """ # Use AI to simulate folder creation folder_result = await self.serviceContainer.interfaceAiCalls.callAiTextAdvanced(folder_prompt) return { "name": name, "parentFolderId": parentFolderId, "result": folder_result, "connection": { "id": connection["id"], "authority": "microsoft", "reference": connectionReference } } except Exception as e: logger.error(f"Error creating folder: {str(e)}") return { "error": str(e) } async def moveMail(self, connectionReference: str, messageId: str, targetFolderId: str) -> Dict[str, Any]: """Move email to different folder using Microsoft Graph API""" try: connection = self._getMicrosoftConnection(connectionReference) if not connection: return { "error": "No valid Microsoft connection found for the provided connection reference", "connectionReference": connectionReference } # For now, simulate mail moving # In a real implementation, you would use Microsoft Graph API move_prompt = f""" Move email to different folder. Message ID: {messageId} Target Folder ID: {targetFolderId} Please provide: 1. Move operation details 2. Source and destination folder information 3. Message preservation and metadata 4. Move confirmation 5. Updated folder structure """ # Use AI to simulate mail moving move_result = await self.serviceContainer.interfaceAiCalls.callAiTextAdvanced(move_prompt) return { "messageId": messageId, "targetFolderId": targetFolderId, "result": move_result, "connection": { "id": connection["id"], "authority": "microsoft", "reference": connectionReference } } except Exception as e: logger.error(f"Error moving email: {str(e)}") return { "error": str(e) } class MethodOutlook(MethodBase): """Outlook method implementation for email operations""" def __init__(self, serviceContainer: Any): """Initialize the Outlook method""" super().__init__(serviceContainer) self.name = "outlook" self.description = "Handle Outlook email operations like reading and sending emails" self.outlookService = OutlookService(serviceContainer) @action async def readMails(self, parameters: Dict[str, Any]) -> ActionResult: """ Read emails from Outlook Parameters: connectionReference (str): Reference to the Microsoft connection folder (str, optional): Folder to read from (default: "inbox") query (str, optional): Search query to filter emails maxResults (int, optional): Maximum number of results (default: 10) includeAttachments (bool, optional): Whether to include attachments (default: False) """ try: connectionReference = parameters.get("connectionReference") folder = parameters.get("folder", "inbox") query = parameters.get("query") maxResults = parameters.get("maxResults", 10) includeAttachments = parameters.get("includeAttachments", False) if not connectionReference: return self._createResult( success=False, data={}, error="Connection reference is required" ) # Read emails messages = await self.outlookService.readMails( connectionReference=connectionReference, folder=folder, query=query, maxResults=maxResults, includeAttachments=includeAttachments ) return self._createResult( success=True, data=messages ) except Exception as e: logger.error(f"Error reading emails: {str(e)}") return self._createResult( success=False, data={}, error=str(e) ) @action async def sendMail(self, parameters: Dict[str, Any]) -> ActionResult: """ Send email using Outlook Parameters: connectionReference (str): Reference to the Microsoft connection to (List[str]): List of recipient email addresses subject (str): Email subject body (str): Email body attachments (List[str], optional): List of attachment file IDs """ try: connectionReference = parameters.get("connectionReference") to = parameters.get("to", []) subject = parameters.get("subject") body = parameters.get("body") attachments = parameters.get("attachments", []) if not connectionReference: return self._createResult( success=False, data={}, error="Connection reference is required" ) if not to or not subject or not body: return self._createResult( success=False, data={}, error="To, subject, and body are required" ) # Send email result = await self.outlookService.sendMail( connectionReference=connectionReference, to=to, subject=subject, body=body, attachments=attachments ) return self._createResult( success=True, data=result ) except Exception as e: logger.error(f"Error sending email: {str(e)}") return self._createResult( success=False, data={}, error=str(e) ) @action async def createFolder(self, parameters: Dict[str, Any]) -> ActionResult: """ Create folder in Outlook Parameters: connectionReference (str): Reference to the Microsoft connection name (str): Folder name parentFolderId (str, optional): Parent folder ID """ try: connectionReference = parameters.get("connectionReference") name = parameters.get("name") parentFolderId = parameters.get("parentFolderId") if not connectionReference: return self._createResult( success=False, data={}, error="Connection reference is required" ) if not name: return self._createResult( success=False, data={}, error="Folder name is required" ) # Create folder folder = await self.outlookService.createFolder( connectionReference=connectionReference, name=name, parentFolderId=parentFolderId ) return self._createResult( success=True, data=folder ) except Exception as e: logger.error(f"Error creating folder: {str(e)}") return self._createResult( success=False, data={}, error=str(e) ) @action async def moveMail(self, parameters: Dict[str, Any]) -> ActionResult: """ Move email to different folder Parameters: connectionReference (str): Reference to the Microsoft connection messageId (str): ID of the message to move targetFolderId (str): ID of the target folder """ try: connectionReference = parameters.get("connectionReference") messageId = parameters.get("messageId") targetFolderId = parameters.get("targetFolderId") if not connectionReference: return self._createResult( success=False, data={}, error="Connection reference is required" ) if not messageId or not targetFolderId: return self._createResult( success=False, data={}, error="Message ID and target folder ID are required" ) # Move email result = await self.outlookService.moveMail( connectionReference=connectionReference, messageId=messageId, targetFolderId=targetFolderId ) return self._createResult( success=True, data=result ) except Exception as e: logger.error(f"Error moving email: {str(e)}") return self._createResult( success=False, data={}, error=str(e) )