225 lines
9.7 KiB
Python
225 lines
9.7 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
|
|
import logging
|
|
import time
|
|
import json
|
|
import requests
|
|
from typing import Dict, Any
|
|
from modules.features.aichat.datamodelFeatureAiChat import ActionResult, ActionDocument
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
async def readEmails(self, parameters: Dict[str, Any]) -> ActionResult:
|
|
operationId = None
|
|
try:
|
|
# Init progress logger
|
|
workflowId = self.services.workflow.id if self.services.workflow else f"no-workflow-{int(time.time())}"
|
|
operationId = f"outlook_read_{workflowId}_{int(time.time())}"
|
|
|
|
# Start progress tracking
|
|
parentOperationId = parameters.get('parentOperationId')
|
|
self.services.chat.progressLogStart(
|
|
operationId,
|
|
"Read Emails",
|
|
"Outlook Email Reading",
|
|
f"Folder: {parameters.get('folder', 'Inbox')}",
|
|
parentOperationId=parentOperationId
|
|
)
|
|
|
|
connectionReference = parameters.get("connectionReference")
|
|
folder = parameters.get("folder", "Inbox")
|
|
limit = parameters.get("limit", 10)
|
|
filter = parameters.get("filter")
|
|
outputMimeType = parameters.get("outputMimeType", "application/json")
|
|
|
|
if not connectionReference:
|
|
if operationId:
|
|
self.services.chat.progressLogFinish(operationId, False)
|
|
return ActionResult.isFailure(error="Connection reference is required")
|
|
|
|
self.services.chat.progressLogUpdate(operationId, 0.2, "Validating parameters")
|
|
|
|
# Validate limit parameter
|
|
if limit <= 0:
|
|
limit = 1000
|
|
logger.warning(f"Invalid limit value ({limit}), using default value 1000")
|
|
|
|
# Validate filter parameter if provided
|
|
if filter:
|
|
# Remove any potentially dangerous characters that could break the filter
|
|
filter = filter.strip()
|
|
if len(filter) > 100:
|
|
logger.warning(f"Filter too long ({len(filter)} chars), truncating to 100 characters")
|
|
filter = filter[:100]
|
|
|
|
|
|
# Get Microsoft connection
|
|
self.services.chat.progressLogUpdate(operationId, 0.3, "Getting Microsoft connection")
|
|
connection = self.connection.getMicrosoftConnection(connectionReference)
|
|
if not connection:
|
|
self.services.chat.progressLogFinish(operationId, False)
|
|
return ActionResult.isFailure(error="No valid Microsoft connection found for the provided connection reference")
|
|
|
|
# Read emails using Microsoft Graph API
|
|
self.services.chat.progressLogUpdate(operationId, 0.4, "Reading emails from Microsoft Graph API")
|
|
try:
|
|
# Microsoft Graph API endpoint for messages
|
|
graph_url = "https://graph.microsoft.com/v1.0"
|
|
headers = {
|
|
"Authorization": f"Bearer {connection['accessToken']}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
|
|
# Get the folder ID for the specified folder
|
|
folder_id = self.folderManagement.getFolderId(folder, connection)
|
|
|
|
if folder_id:
|
|
# Build the API request with folder ID
|
|
api_url = f"{graph_url}/me/mailFolders/{folder_id}/messages"
|
|
else:
|
|
# Fallback: use folder name directly (for well-known folders like "Inbox")
|
|
api_url = f"{graph_url}/me/mailFolders/{folder}/messages"
|
|
logger.warning(f"Could not find folder ID for '{folder}', using folder name directly")
|
|
params = {
|
|
"$top": limit,
|
|
"$orderby": "receivedDateTime desc"
|
|
}
|
|
|
|
if filter:
|
|
# Build proper Graph API filter parameters
|
|
filter_params = self.emailProcessing.buildGraphFilter(filter)
|
|
params.update(filter_params)
|
|
|
|
# If using $search, remove $orderby as they can't be combined
|
|
if "$search" in params:
|
|
params.pop("$orderby", None)
|
|
|
|
# If using $filter with contains(), remove $orderby as they can't be combined
|
|
# Microsoft Graph API doesn't support contains() with orderby
|
|
if "$filter" in params and "contains(" in params["$filter"].lower():
|
|
params.pop("$orderby", None)
|
|
|
|
# Filter applied
|
|
|
|
# Make the API call
|
|
|
|
|
|
response = requests.get(api_url, headers=headers, params=params)
|
|
|
|
if response.status_code != 200:
|
|
logger.error(f"Graph API error: {response.status_code} - {response.text}")
|
|
logger.error(f"Request URL: {response.url}")
|
|
logger.error(f"Request headers: {headers}")
|
|
logger.error(f"Request params: {params}")
|
|
|
|
response.raise_for_status()
|
|
|
|
self.services.chat.progressLogUpdate(operationId, 0.7, "Processing email data")
|
|
emails_data = response.json()
|
|
email_data = {
|
|
"emails": emails_data.get("value", []),
|
|
"count": len(emails_data.get("value", [])),
|
|
"folder": folder,
|
|
"filter": filter,
|
|
"apiMetadata": {
|
|
"@odata.context": emails_data.get("@odata.context"),
|
|
"@odata.count": emails_data.get("@odata.count"),
|
|
"@odata.nextLink": emails_data.get("@odata.nextLink")
|
|
}
|
|
}
|
|
|
|
|
|
|
|
except ImportError:
|
|
logger.error("requests module not available")
|
|
if operationId:
|
|
self.services.chat.progressLogFinish(operationId, False)
|
|
return ActionResult.isFailure(error="requests module not available")
|
|
except requests.exceptions.HTTPError as e:
|
|
if e.response.status_code == 400:
|
|
logger.error(f"Bad Request (400) - Invalid filter or parameter: {e.response.text}")
|
|
if operationId:
|
|
self.services.chat.progressLogFinish(operationId, False)
|
|
return ActionResult.isFailure(error=f"Invalid filter syntax. Please check your filter parameter. Error: {e.response.text}")
|
|
elif e.response.status_code == 401:
|
|
logger.error("Unauthorized (401) - Access token may be expired or invalid")
|
|
if operationId:
|
|
self.services.chat.progressLogFinish(operationId, False)
|
|
return ActionResult.isFailure(error="Authentication failed. Please check your connection and try again.")
|
|
elif e.response.status_code == 403:
|
|
logger.error("Forbidden (403) - Insufficient permissions to access emails")
|
|
if operationId:
|
|
self.services.chat.progressLogFinish(operationId, False)
|
|
return ActionResult.isFailure(error="Insufficient permissions to read emails from this folder.")
|
|
else:
|
|
logger.error(f"HTTP Error {e.response.status_code}: {e.response.text}")
|
|
if operationId:
|
|
self.services.chat.progressLogFinish(operationId, False)
|
|
return ActionResult.isFailure(error=f"HTTP Error {e.response.status_code}: {e.response.text}")
|
|
except Exception as e:
|
|
logger.error(f"Error reading emails from Microsoft Graph API: {str(e)}")
|
|
if operationId:
|
|
self.services.chat.progressLogFinish(operationId, False)
|
|
return ActionResult.isFailure(error=f"Failed to read emails: {str(e)}")
|
|
|
|
# Determine output format based on MIME type
|
|
mime_type_mapping = {
|
|
"application/json": ".json",
|
|
"text/plain": ".txt",
|
|
"text/csv": ".csv"
|
|
}
|
|
output_extension = mime_type_mapping.get(outputMimeType, ".json")
|
|
output_mime_type = outputMimeType
|
|
logger.info(f"Using output format: {output_extension} ({output_mime_type})")
|
|
|
|
|
|
|
|
# Create result data as JSON string
|
|
result_data = {
|
|
"connectionReference": connectionReference,
|
|
"folder": folder,
|
|
"limit": limit,
|
|
"filter": filter,
|
|
"emails": email_data,
|
|
"connection": {
|
|
"id": connection["id"],
|
|
"authority": "microsoft",
|
|
"reference": connectionReference
|
|
},
|
|
"timestamp": self.services.utils.timestampGetUtc()
|
|
}
|
|
|
|
validationMetadata = {
|
|
"actionType": "outlook.readEmails",
|
|
"connectionReference": connectionReference,
|
|
"folder": folder,
|
|
"limit": limit,
|
|
"filter": filter,
|
|
"emailCount": email_data.get("count", 0),
|
|
"outputMimeType": outputMimeType
|
|
}
|
|
|
|
self.services.chat.progressLogUpdate(operationId, 0.9, f"Found {email_data.get('count', 0)} emails")
|
|
self.services.chat.progressLogFinish(operationId, True)
|
|
|
|
return ActionResult.isSuccess(
|
|
documents=[ActionDocument(
|
|
documentName=f"outlook_emails_{self._format_timestamp_for_filename()}.json",
|
|
documentData=json.dumps(result_data, indent=2),
|
|
mimeType="application/json",
|
|
validationMetadata=validationMetadata
|
|
)]
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error reading emails: {str(e)}")
|
|
if operationId:
|
|
try:
|
|
self.services.chat.progressLogFinish(operationId, False)
|
|
except:
|
|
pass # Don't fail on progress logging errors
|
|
return ActionResult.isFailure(
|
|
error=str(e)
|
|
)
|
|
|