# Copyright (c) 2025 Patrick Motsch # All rights reserved. import logging from datetime import datetime, UTC from modules.workflows.methods.methodBase import MethodBase from modules.datamodels.datamodelWorkflowActions import WorkflowActionDefinition, WorkflowActionParameter from modules.shared.frontendTypes import FrontendType # Import helpers from .helpers.connection import ConnectionHelper from .helpers.emailProcessing import EmailProcessingHelper from .helpers.folderManagement import FolderManagementHelper # Import actions from .actions.readEmails import readEmails from .actions.searchEmails import searchEmails from .actions.composeAndDraftEmailWithContext import composeAndDraftEmailWithContext from .actions.sendDraftEmail import sendDraftEmail logger = logging.getLogger(__name__) class MethodOutlook(MethodBase): """Outlook method implementation for email operations""" def __init__(self, services): """Initialize the Outlook method""" super().__init__(services) self.name = "outlook" self.description = "Handle Microsoft Outlook email operations" # Initialize helper modules self.connection = ConnectionHelper(self) self.emailProcessing = EmailProcessingHelper(self) self.folderManagement = FolderManagementHelper(self) # RBAC-Integration: Action-Definitionen mit actionId self._actions = { "readEmails": WorkflowActionDefinition( actionId="outlook.readEmails", description="Read emails and metadata from a mailbox folder", dynamicMode=True, outputType="EmailList", parameters={ "connectionReference": WorkflowActionParameter( name="connectionReference", type="ConnectionRef", frontendType=FrontendType.USER_CONNECTION, required=True, description="Microsoft connection label" ), "folder": WorkflowActionParameter( name="folder", type="str", frontendType=FrontendType.SELECT, frontendOptions="outlook.folder", required=False, default="Inbox", description="Folder to read from" ), "limit": WorkflowActionParameter( name="limit", type="int", frontendType=FrontendType.NUMBER, required=False, default=1000, description="Maximum items to return", validation={"min": 1, "max": 10000} ), "filter": WorkflowActionParameter( name="filter", type="str", frontendType=FrontendType.TEXT, required=False, description="Sender, query operators, or subject text" ), "outputMimeType": WorkflowActionParameter( name="outputMimeType", type="str", frontendType=FrontendType.SELECT, frontendOptions=["application/json", "text/plain", "text/csv"], required=False, default="application/json", description="MIME type for output file" ) }, execute=readEmails.__get__(self, self.__class__) ), "searchEmails": WorkflowActionDefinition( actionId="outlook.searchEmails", description="Search emails by query and return matching items with metadata", dynamicMode=True, outputType="EmailList", parameters={ "connectionReference": WorkflowActionParameter( name="connectionReference", type="ConnectionRef", frontendType=FrontendType.USER_CONNECTION, required=True, description="Microsoft connection label" ), "query": WorkflowActionParameter( name="query", type="str", frontendType=FrontendType.TEXT, required=True, description="Search expression" ), "folder": WorkflowActionParameter( name="folder", type="str", frontendType=FrontendType.SELECT, frontendOptions="outlook.folder", required=False, default="All", description="Folder scope or All" ), "limit": WorkflowActionParameter( name="limit", type="int", frontendType=FrontendType.NUMBER, required=False, default=1000, description="Maximum items to return", validation={"min": 1, "max": 10000} ), "outputMimeType": WorkflowActionParameter( name="outputMimeType", type="str", frontendType=FrontendType.SELECT, frontendOptions=["application/json", "text/plain", "text/csv"], required=False, default="application/json", description="MIME type for output file" ) }, execute=searchEmails.__get__(self, self.__class__) ), "composeAndDraftEmailWithContext": WorkflowActionDefinition( actionId="outlook.composeAndDraftEmailWithContext", description="Compose email content using AI from context and optional documents, then create a draft", dynamicMode=True, outputType="EmailDraft", parameters={ "connectionReference": WorkflowActionParameter( name="connectionReference", type="ConnectionRef", frontendType=FrontendType.USER_CONNECTION, required=True, description="Microsoft connection label" ), "to": WorkflowActionParameter( name="to", type="List[str]", frontendType=FrontendType.MULTISELECT, required=False, description="Recipient email addresses (optional for drafts)" ), "context": WorkflowActionParameter( name="context", type="str", uiHint="textarea", frontendType=FrontendType.TEXTAREA, required=False, description="Detailed context for AI composition (omit when emailContent provided)" ), "emailContent": WorkflowActionParameter( name="emailContent", type="Dict[str,Any]", frontendType=FrontendType.HIDDEN, required=False, description="Direct subject/body/to from upstream (skips AI composition)" ), "documentList": WorkflowActionParameter( name="documentList", type="DocumentList", frontendType=FrontendType.DOCUMENT_REFERENCE, required=False, description="Document references or inline ActionDocuments for attachments" ), "cc": WorkflowActionParameter( name="cc", type="List[str]", frontendType=FrontendType.MULTISELECT, required=False, description="CC recipients" ), "bcc": WorkflowActionParameter( name="bcc", type="List[str]", frontendType=FrontendType.MULTISELECT, required=False, description="BCC recipients" ), "emailStyle": WorkflowActionParameter( name="emailStyle", type="str", frontendType=FrontendType.SELECT, frontendOptions=["formal", "casual", "business"], required=False, default="business", description="Email style: formal, casual, or business" ), "maxLength": WorkflowActionParameter( name="maxLength", type="int", frontendType=FrontendType.NUMBER, required=False, default=1000, description="Maximum length for generated content", validation={"min": 100, "max": 10000} ) }, execute=composeAndDraftEmailWithContext.__get__(self, self.__class__) ), "sendDraftEmail": WorkflowActionDefinition( actionId="outlook.sendDraftEmail", description="Send draft email(s) using draft email JSON document(s) from action outlook.composeAndDraftEmailWithContext", dynamicMode=True, outputType="ActionResult", parameters={ "connectionReference": WorkflowActionParameter( name="connectionReference", type="ConnectionRef", frontendType=FrontendType.USER_CONNECTION, required=True, description="Microsoft connection label" ), "documentList": WorkflowActionParameter( name="documentList", type="DocumentList", frontendType=FrontendType.DOCUMENT_REFERENCE, required=True, description="Document reference(s) to draft emails in JSON format (outputs from outlook.composeAndDraftEmailWithContext function)" ) }, execute=sendDraftEmail.__get__(self, self.__class__) ) } # Validate actions after definition self._validateActions() # Register actions as methods (optional, für direkten Zugriff) self.readEmails = readEmails.__get__(self, self.__class__) self.searchEmails = searchEmails.__get__(self, self.__class__) self.composeAndDraftEmailWithContext = composeAndDraftEmailWithContext.__get__(self, self.__class__) self.sendDraftEmail = sendDraftEmail.__get__(self, self.__class__) def _format_timestamp_for_filename(self) -> str: """Format current timestamp as YYYYMMDD-hhmmss for filenames.""" return datetime.now(UTC).strftime("%Y%m%d-%H%M%S")