#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Lee códigos OTP de Bucéfalo (Login security code) desde IMAP. Bucéfalo manda el código 2FA desde `noreply@donotreply.acct-mgmt.com` con un cuerpo tipo: `Your one time login code for logging into crm.bucefalocrm.io is 929711.` Uso: from scripts.email_otp_reader import wait_for_bucefalo_otp code = wait_for_bucefalo_otp(since=datetime.now()) # bloquea hasta verlo o timeout Configuración: variables de entorno (carga `.env` si existe). EMAIL_IMAP_HOST, EMAIL_IMAP_PORT, EMAIL_IMAP_USER, EMAIL_IMAP_PASSWORD, EMAIL_IMAP_FOLDER (default INBOX), OTP_TIMEOUT_SECONDS, OTP_POLL_INTERVAL_SECONDS. """ import os import re import sys import time from datetime import datetime, timedelta, timezone ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if ROOT_DIR not in sys.path: sys.path.insert(0, ROOT_DIR) # Cargar .env si existe (silencioso si falta python-dotenv). try: from dotenv import load_dotenv load_dotenv(os.path.join(ROOT_DIR, ".env")) except Exception: pass # Remitente esperado del correo de 2FA de Bucéfalo / GHL. EXPECTED_SENDERS = ( "noreply@donotreply.acct-mgmt.com", "donotreply.acct-mgmt.com", ) # Patrones para extraer el código. El correo dice "is 929711." pero por robustez # probamos varios formatos. CODE_PATTERNS = ( re.compile(r"\bis\s+(\d{6})\b", re.IGNORECASE), # "is 929711" re.compile(r"\bc[oó]digo[^\d]{0,20}(\d{6})\b", re.IGNORECASE), # español re.compile(r"\bcode[^\d]{0,20}(\d{6})\b", re.IGNORECASE), # inglés re.compile(r"(?