mirror of
https://github.com/marcogll/talia_bot.git
synced 2026-01-13 13:25:19 +00:00
feat: implement IMAP confirmation loop for print flow
Adds a confirmation loop to the printing feature. - Creates a new `imap_listener.py` module to check for confirmation emails from the printer. - Updates `main.py` to use a JSON payload in the email subject, containing a unique `job_id` and the `telegram_id` of the user. - After sending a print job, the bot now waits for a specified time and then checks the IMAP inbox for a matching confirmation. - Notifies the user via Telegram whether the print job was successful or if a confirmation was not received. - Updates `flows.json` with a clearer message for the user during the print process.
This commit is contained in:
@@ -41,9 +41,11 @@ from talia_bot.modules.vikunja import get_projects, add_comment_to_task, update_
|
|||||||
from talia_bot.db import setup_database
|
from talia_bot.db import setup_database
|
||||||
from talia_bot.modules.flow_engine import FlowEngine
|
from talia_bot.modules.flow_engine import FlowEngine
|
||||||
from talia_bot.modules.transcription import transcribe_audio
|
from talia_bot.modules.transcription import transcribe_audio
|
||||||
|
import uuid
|
||||||
from talia_bot.modules.llm_engine import analyze_client_pitch
|
from talia_bot.modules.llm_engine import analyze_client_pitch
|
||||||
from talia_bot.modules.calendar import create_event
|
from talia_bot.modules.calendar import create_event
|
||||||
from talia_bot.modules.mailer import send_email_with_attachment
|
from talia_bot.modules.mailer import send_email_with_attachment
|
||||||
|
from talia_bot.modules.imap_listener import check_for_confirmation
|
||||||
from talia_bot.config import ADMIN_ID, VIKUNJA_INBOX_PROJECT_ID
|
from talia_bot.config import ADMIN_ID, VIKUNJA_INBOX_PROJECT_ID
|
||||||
|
|
||||||
from talia_bot.scheduler import schedule_daily_summary
|
from talia_bot.scheduler import schedule_daily_summary
|
||||||
@@ -313,11 +315,21 @@ async def handle_flow_resolution(update: Update, context: ContextTypes.DEFAULT_T
|
|||||||
|
|
||||||
elif resolution_type == "resolution_email_sent":
|
elif resolution_type == "resolution_email_sent":
|
||||||
file_info = collected_data.get("UPLOAD_FILE")
|
file_info = collected_data.get("UPLOAD_FILE")
|
||||||
|
user_id = update.effective_user.id
|
||||||
|
|
||||||
if isinstance(file_info, dict):
|
if isinstance(file_info, dict):
|
||||||
file_id = file_info.get("file_id")
|
file_id = file_info.get("file_id")
|
||||||
file_name = file_info.get("file_name")
|
file_name = file_info.get("file_name")
|
||||||
|
|
||||||
if file_id and file_name:
|
if file_id and file_name:
|
||||||
|
job_id = str(uuid.uuid4())
|
||||||
|
subject_data = {
|
||||||
|
"job_id": job_id,
|
||||||
|
"telegram_id": user_id,
|
||||||
|
"filename": file_name
|
||||||
|
}
|
||||||
|
subject = f"DATA:{json.dumps(subject_data)}"
|
||||||
|
|
||||||
file_obj = await context.bot.get_file(file_id)
|
file_obj = await context.bot.get_file(file_id)
|
||||||
file_buffer = io.BytesIO()
|
file_buffer = io.BytesIO()
|
||||||
await file_obj.download_to_memory(file_buffer)
|
await file_obj.download_to_memory(file_buffer)
|
||||||
@@ -326,9 +338,21 @@ async def handle_flow_resolution(update: Update, context: ContextTypes.DEFAULT_T
|
|||||||
success = await send_email_with_attachment(
|
success = await send_email_with_attachment(
|
||||||
file_content=file_buffer.getvalue(),
|
file_content=file_buffer.getvalue(),
|
||||||
filename=file_name,
|
filename=file_name,
|
||||||
subject=f"Print Job: {file_name}"
|
subject=subject
|
||||||
)
|
)
|
||||||
if not success:
|
|
||||||
|
if success:
|
||||||
|
final_message = f"Recibido. 📨\n\nTu trabajo de impresión ha sido enviado (Job ID: {job_id}). Te notificaré cuando la impresora confirme que ha sido impreso."
|
||||||
|
|
||||||
|
# Esperar y verificar la confirmación
|
||||||
|
await asyncio.sleep(60) # Espera de 60 segundos
|
||||||
|
confirmation_data = await asyncio.to_thread(check_for_confirmation, job_id)
|
||||||
|
|
||||||
|
if confirmation_data:
|
||||||
|
await context.bot.send_message(chat_id=user_id, text=f"✅ ¡Éxito! Tu archivo '{file_name}' ha sido impreso correctamente.")
|
||||||
|
else:
|
||||||
|
await context.bot.send_message(chat_id=user_id, text=f"⚠️ El trabajo de impresión para '{file_name}' fue enviado, pero no he recibido una confirmación de la impresora. Por favor, verifica la bandeja de la impresora.")
|
||||||
|
else:
|
||||||
final_message = "❌ Hubo un error al enviar el archivo a la impresora."
|
final_message = "❌ Hubo un error al enviar el archivo a la impresora."
|
||||||
else:
|
else:
|
||||||
final_message = "❌ No se encontró la información del archivo."
|
final_message = "❌ No se encontró la información del archivo."
|
||||||
|
|||||||
79
talia_bot/modules/imap_listener.py
Normal file
79
talia_bot/modules/imap_listener.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# talia_bot/modules/imap_listener.py
|
||||||
|
import imaplib
|
||||||
|
import email
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from email.header import decode_header
|
||||||
|
|
||||||
|
from talia_bot.config import IMAP_SERVER, IMAP_USER, IMAP_PASSWORD
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def check_for_confirmation(job_id: str):
|
||||||
|
"""
|
||||||
|
Checks for a print confirmation email via IMAP.
|
||||||
|
Returns the parsed data from the email subject if a confirmation is found, else None.
|
||||||
|
"""
|
||||||
|
if not all([IMAP_SERVER, IMAP_USER, IMAP_PASSWORD]):
|
||||||
|
logger.error("IMAP settings are not fully configured.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
mail = imaplib.IMAP4_SSL(IMAP_SERVER)
|
||||||
|
mail.login(IMAP_USER, IMAP_PASSWORD)
|
||||||
|
mail.select("inbox")
|
||||||
|
|
||||||
|
# Buscar correos no leídos del remitente específico
|
||||||
|
status, messages = mail.search(None, '(UNSEEN FROM "noreply@print.epsonconnect.com")')
|
||||||
|
if status != "OK":
|
||||||
|
logger.error("Failed to search for emails.")
|
||||||
|
mail.logout()
|
||||||
|
return None
|
||||||
|
|
||||||
|
for num in messages[0].split():
|
||||||
|
status, data = mail.fetch(num, "(RFC822)")
|
||||||
|
if status != "OK":
|
||||||
|
continue
|
||||||
|
|
||||||
|
msg = email.message_from_bytes(data[0][1])
|
||||||
|
|
||||||
|
# Decodificar el asunto del correo
|
||||||
|
subject, encoding = decode_header(msg["Subject"])[0]
|
||||||
|
if isinstance(subject, bytes):
|
||||||
|
subject = subject.decode(encoding if encoding else "utf-8")
|
||||||
|
|
||||||
|
# Buscar la línea que contiene el asunto original
|
||||||
|
body = ""
|
||||||
|
if msg.is_multipart():
|
||||||
|
for part in msg.walk():
|
||||||
|
if part.get_content_type() == "text/plain":
|
||||||
|
body = part.get_payload(decode=True).decode()
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
body = msg.get_payload(decode=True).decode()
|
||||||
|
|
||||||
|
for line in body.splitlines():
|
||||||
|
if line.strip().startswith("Subject:"):
|
||||||
|
original_subject = line.strip()[len("Subject:"):].strip()
|
||||||
|
# El asunto está encapsulado en `DATA:{...}`
|
||||||
|
if original_subject.startswith("DATA:"):
|
||||||
|
try:
|
||||||
|
json_data_str = original_subject[len("DATA:"):].strip()
|
||||||
|
job_data = json.loads(json_data_str)
|
||||||
|
|
||||||
|
if job_data.get("job_id") == job_id:
|
||||||
|
logger.info(f"Confirmation found for job_id: {job_id}")
|
||||||
|
# Marcar el correo como leído
|
||||||
|
mail.store(num, '+FLAGS', '\\Seen')
|
||||||
|
mail.logout()
|
||||||
|
return job_data
|
||||||
|
except (json.JSONDecodeError, KeyError) as e:
|
||||||
|
logger.warning(f"Could not parse job data from subject: {original_subject}. Error: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
mail.logout()
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to check email via IMAP: {e}")
|
||||||
|
return None
|
||||||
Reference in New Issue
Block a user