119 lines
4.3 KiB
Python
119 lines
4.3 KiB
Python
import msal
|
|
import requests
|
|
import warnings
|
|
import base64
|
|
import os
|
|
from datetime import datetime, timedelta
|
|
from config_loader import CONFIG
|
|
|
|
warnings.filterwarnings("ignore")
|
|
|
|
########################################################
|
|
def get_access_token():
|
|
"""
|
|
Obtiene un token de acceso de Microsoft Graph usando las credenciales
|
|
configuradas en config.json (Client ID, Client Secret y Tenant ID).
|
|
"""
|
|
if not CONFIG or not CONFIG.outlook:
|
|
raise ValueError("La configuración de Outlook no está cargada correctamente.")
|
|
|
|
authority = f"https://login.microsoftonline.com/{CONFIG.outlook.tenant_id}"
|
|
|
|
app = msal.ConfidentialClientApplication(
|
|
CONFIG.outlook.client_id,
|
|
authority=authority,
|
|
client_credential=CONFIG.outlook.client_secret,
|
|
verify=CONFIG.outlook.verify_ssl
|
|
)
|
|
|
|
scopes = ["https://graph.microsoft.com/.default"]
|
|
result = app.acquire_token_for_client(scopes=scopes)
|
|
|
|
if "access_token" in result:
|
|
return result["access_token"]
|
|
else:
|
|
error = result.get("error")
|
|
error_description = result.get("error_description")
|
|
correlation_id = result.get("correlation_id")
|
|
raise Exception(f"Error al obtener token: {error} - {error_description} (ID: {correlation_id})")
|
|
|
|
########################################################
|
|
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
|
|
|
|
######################################################## |