import requests import msal import os import base64 import warnings from datetime import datetime, timedelta, timezone from config_loader import CONFIG warnings.filterwarnings("ignore") ######################################################## def get_access_token(): """Obtiene el token de acceso usando MSAL (Client Credentials Flow).""" outlook_config = CONFIG.outlook client_id = outlook_config.client_id client_secret = outlook_config.client_secret tenant_id = outlook_config.tenant_id authority = f"https://login.microsoftonline.com/{tenant_id}" # Manejo de SSL verify_ssl = outlook_config.verify_ssl ca_bundle = outlook_config.ca_bundle # MSAL usa requests internamente, podemos pasar proxies/verify # a través de la configuración si fuera necesario, pero msal tiene su propia forma # de configurar el http_client. Por simplicidad, si verify_ssl es False, # podemos intentar configurar el ambiente o pasar un cliente custom. app = msal.ConfidentialClientApplication( client_id, authority=authority, client_credential=client_secret, verify=verify_ssl if ca_bundle is None else ca_bundle ) # El scope para Graph API en modo aplicación suele ser 'https://graph.microsoft.com/.default' scopes = ["https://graph.microsoft.com/.default"] try: result = app.acquire_token_silent(scopes, account=None) if not result: result = app.acquire_token_for_client(scopes=scopes) if "access_token" in result: return result["access_token"] else: raise Exception(f"Error obteniendo token: {result.get('error_description')}") except Exception as e: print(f"Error obteniendo token: {e}") return None ######################################################## def get_messages(access_token, user_email, date_filter=None, end_date_filter=None): """Obtiene todos los mensajes de un correo específico, manejando la paginación de Graph API.""" url = f"https://graph.microsoft.com/v1.0/users/{user_email}/messages" headers = { 'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json' } outlook_config = CONFIG.outlook verify = outlook_config.verify_ssl if outlook_config.ca_bundle is None else outlook_config.ca_bundle # Solo los mensajes que contengan adjuntos filter_query = 'hasAttachments eq true' if date_filter: filter_query += f" and receivedDateTime ge {date_filter}" if end_date_filter: filter_query += f" and receivedDateTime le {end_date_filter}" params = { '$filter': filter_query, '$select': 'id,subject,receivedDateTime,hasAttachments,from', '$top': 50 # Podemos pedir hasta 999, pero 50 es un buen default para probar paginación } all_messages = [] next_link = url while next_link: # En la primera iteración usamos url + params. # En las siguientes, next_link ya trae los filtros y el token de skip en la URL. if next_link == url: response = requests.get(next_link, headers=headers, params=params, verify=verify) else: response = requests.get(next_link, headers=headers, verify=verify) if response.status_code == 200: data = response.json() messages = data.get('value', []) all_messages.extend(messages) next_link = data.get('@odata.nextLink') else: print(f"Error obteniendo mensajes para {user_email}: {response.text}") break return all_messages ######################################################## def send_email(access_token, from_email, to_recipients, subject, html_body, attachment_paths=None): """ Envía un correo electrónico con cuerpo HTML y múltiples adjuntos PDF. :param access_token: Token de acceso de Microsoft Graph API. :param from_email: Correo electrónico del remitente (cuenta de Office 365). :param to_recipients: Lista de correos electrónicos de los destinatarios. :param subject: Asunto del correo. :param html_body: Contenido del correo en formato HTML. :param attachment_paths: Lista de rutas a los archivos PDF que se adjuntarán. :return: True si se envió correctamente, False en caso contrario. """ url = f"https://graph.microsoft.com/v1.0/users/{from_email}/sendMail" headers = { 'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json' } # Configuración de SSL desde CONFIG outlook_config = CONFIG.outlook verify = outlook_config.verify_ssl if outlook_config.ca_bundle is None else outlook_config.ca_bundle # Construir lista de destinatarios para Graph API recipients_payload = [] for email in to_recipients: recipients_payload.append({ "emailAddress": { "address": email } }) # Construir lista de adjuntos attachments_payload = [] if attachment_paths: for path in attachment_paths: if os.path.exists(path): with open(path, "rb") as f: content_bytes = f.read() # Codificar el contenido en Base64 encoded_content = base64.b64encode(content_bytes).decode('utf-8') filename = os.path.basename(path) attachments_payload.append({ "@odata.type": "#microsoft.graph.fileAttachment", "name": filename, "contentType": "application/pdf", "contentBytes": encoded_content }) else: print(f"Advertencia: El archivo no existe y no se adjuntará: {path}") # Estructura del mensaje para la API email_data = { "message": { "subject": subject, "body": { "contentType": "HTML", "content": html_body }, "toRecipients": recipients_payload, "attachments": attachments_payload }, "saveToSentItems": "true" } try: response = requests.post(url, headers=headers, json=email_data, verify=verify) if response.status_code == 202: print(f"SUCCESS: Correo enviado exitosamente a: {', '.join(to_recipients)}") return True else: print(f"ERROR: Al enviar correo: {response.status_code} - {response.text}") return False except Exception as e: print(f"ERROR: Excepcion al enviar correo: {e}") return False ########################################################