mirror of
https://github.com/marcogll/talia_bot.git
synced 2026-01-13 13:25:19 +00:00
Merge pull request #57 from marcogll/feature/sprint-updates-20240112-15660961988964187929
Feature/sprint updates 20240112 15660961988964187929
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# Python base image
|
# Python base image
|
||||||
FROM python:3.9-slim
|
FROM python:3.11-slim
|
||||||
|
|
||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /talia_bot
|
WORKDIR /talia_bot
|
||||||
|
|||||||
22
Tasks.md
22
Tasks.md
@@ -13,6 +13,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
- **Due**: ASAP
|
- **Due**: ASAP
|
||||||
|
|
||||||
### [SEC-002] Hardcoded Secrets Management
|
### [SEC-002] Hardcoded Secrets Management
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: High
|
- **Priority**: High
|
||||||
- **Description**: Email credentials stored in plain text environment variables
|
- **Description**: Email credentials stored in plain text environment variables
|
||||||
- **Files affected**: `config.py`, `.env.example`
|
- **Files affected**: `config.py`, `.env.example`
|
||||||
@@ -20,6 +21,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
- **Due**: Next sprint
|
- **Due**: Next sprint
|
||||||
|
|
||||||
### [SEC-003] SQL Injection Prevention
|
### [SEC-003] SQL Injection Prevention
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Medium
|
- **Priority**: Medium
|
||||||
- **Description**: Database connection lacks connection pooling and timeout configurations
|
- **Description**: Database connection lacks connection pooling and timeout configurations
|
||||||
- **Files affected**: `db.py`
|
- **Files affected**: `db.py`
|
||||||
@@ -37,6 +39,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
- **Due**: Next sprint
|
- **Due**: Next sprint
|
||||||
|
|
||||||
### [IMP-002] Dynamic Menu Generation
|
### [IMP-002] Dynamic Menu Generation
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Medium
|
- **Priority**: Medium
|
||||||
- **Description**: `onboarding.py` has hardcoded menus instead of dynamic generation
|
- **Description**: `onboarding.py` has hardcoded menus instead of dynamic generation
|
||||||
- **Files affected**: `onboarding.py`
|
- **Files affected**: `onboarding.py`
|
||||||
@@ -44,6 +47,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
- **Due**: Future iteration
|
- **Due**: Future iteration
|
||||||
|
|
||||||
### [IMP-003] Button Dispatcher Agent
|
### [IMP-003] Button Dispatcher Agent
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Low
|
- **Priority**: Low
|
||||||
- **Description**: "Despachador de Botones" mentioned in AGENTS.md but not implemented
|
- **Description**: "Despachador de Botones" mentioned in AGENTS.md but not implemented
|
||||||
- **Files affected**: Need to create new module
|
- **Files affected**: Need to create new module
|
||||||
@@ -53,6 +57,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
## **Architecture & Code Quality** 🟠
|
## **Architecture & Code Quality** 🟠
|
||||||
|
|
||||||
### [ARCH-001] Main.py Business Logic Violation
|
### [ARCH-001] Main.py Business Logic Violation
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Medium
|
- **Priority**: Medium
|
||||||
- **Description**: `main.py` contains business logic (lines 56-95) violating "Recepcionista" agent role
|
- **Description**: `main.py` contains business logic (lines 56-95) violating "Recepcionista" agent role
|
||||||
- **Files affected**: `main.py`
|
- **Files affected**: `main.py`
|
||||||
@@ -60,6 +65,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
- **Due**: Next sprint
|
- **Due**: Next sprint
|
||||||
|
|
||||||
### [ARCH-002] Error Handling Consistency
|
### [ARCH-002] Error Handling Consistency
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Medium
|
- **Priority**: Medium
|
||||||
- **Description**: Inconsistent error handling across modules, missing try-catch blocks
|
- **Description**: Inconsistent error handling across modules, missing try-catch blocks
|
||||||
- **Files affected**: `flow_engine.py`, `printer.py`, multiple modules
|
- **Files affected**: `flow_engine.py`, `printer.py`, multiple modules
|
||||||
@@ -67,6 +73,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
- **Due**: Next sprint
|
- **Due**: Next sprint
|
||||||
|
|
||||||
### [ARCH-003] Code Duplication
|
### [ARCH-003] Code Duplication
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Low
|
- **Priority**: Low
|
||||||
- **Description**: Database connection patterns repeated, similar menu generation logic
|
- **Description**: Database connection patterns repeated, similar menu generation logic
|
||||||
- **Files affected**: Multiple files
|
- **Files affected**: Multiple files
|
||||||
@@ -76,6 +83,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
## **Performance & Optimization** 🟢
|
## **Performance & Optimization** 🟢
|
||||||
|
|
||||||
### [PERF-001] Database Connection Pooling
|
### [PERF-001] Database Connection Pooling
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Medium
|
- **Priority**: Medium
|
||||||
- **Description**: No connection pooling, missing indexes on frequently queried columns
|
- **Description**: No connection pooling, missing indexes on frequently queried columns
|
||||||
- **Files affected**: `db.py`
|
- **Files affected**: `db.py`
|
||||||
@@ -83,6 +91,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
- **Due**: Next sprint
|
- **Due**: Next sprint
|
||||||
|
|
||||||
### [PERF-002] Memory Management
|
### [PERF-002] Memory Management
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Medium
|
- **Priority**: Medium
|
||||||
- **Description**: Voice files loaded entirely into memory, no cleanup for failed uploads
|
- **Description**: Voice files loaded entirely into memory, no cleanup for failed uploads
|
||||||
- **Files affected**: `llm_engine.py`, `main.py`
|
- **Files affected**: `llm_engine.py`, `main.py`
|
||||||
@@ -90,6 +99,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
- **Due**: Next sprint
|
- **Due**: Next sprint
|
||||||
|
|
||||||
### [PERF-003] Flow Engine Memory Usage
|
### [PERF-003] Flow Engine Memory Usage
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Low
|
- **Priority**: Low
|
||||||
- **Description**: Flow engine stores all conversation data in memory
|
- **Description**: Flow engine stores all conversation data in memory
|
||||||
- **Files affected**: `flow_engine.py`
|
- **Files affected**: `flow_engine.py`
|
||||||
@@ -99,6 +109,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
## **Dependencies & Configuration** 🔵
|
## **Dependencies & Configuration** 🔵
|
||||||
|
|
||||||
### [DEP-001] Python Version Upgrade
|
### [DEP-001] Python Version Upgrade
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: High
|
- **Priority**: High
|
||||||
- **Description**: Using Python 3.9 (EOL October 2025) - should upgrade to 3.11+
|
- **Description**: Using Python 3.9 (EOL October 2025) - should upgrade to 3.11+
|
||||||
- **Files affected**: `Dockerfile`, `requirements.txt`
|
- **Files affected**: `Dockerfile`, `requirements.txt`
|
||||||
@@ -114,6 +125,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
- **Due**: ASAP
|
- **Due**: ASAP
|
||||||
|
|
||||||
### [DEP-003] Docker Security Hardening
|
### [DEP-003] Docker Security Hardening
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Medium
|
- **Priority**: Medium
|
||||||
- **Description**: Running as root user, missing security hardening
|
- **Description**: Running as root user, missing security hardening
|
||||||
- **Files affected**: `Dockerfile`, `docker-compose.yml`
|
- **Files affected**: `Dockerfile`, `docker-compose.yml`
|
||||||
@@ -123,6 +135,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
## **Bugs & Logical Errors** 🐛
|
## **Bugs & Logical Errors** 🐛
|
||||||
|
|
||||||
### [BUG-001] Flow Engine Validation
|
### [BUG-001] Flow Engine Validation
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Medium
|
- **Priority**: Medium
|
||||||
- **Description**: `flow_engine.py:72` missing validation for empty steps array
|
- **Description**: `flow_engine.py:72` missing validation for empty steps array
|
||||||
- **Files affected**: `flow_engine.py`
|
- **Files affected**: `flow_engine.py`
|
||||||
@@ -130,6 +143,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
- **Due**: Next sprint
|
- **Due**: Next sprint
|
||||||
|
|
||||||
### [BUG-002] Printer Module IMAP Search
|
### [BUG-002] Printer Module IMAP Search
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Medium
|
- **Priority**: Medium
|
||||||
- **Description**: `printer.py:88` IMAP search doesn't handle large email counts
|
- **Description**: `printer.py:88` IMAP search doesn't handle large email counts
|
||||||
- **Files affected**: `printer.py`
|
- **Files affected**: `printer.py`
|
||||||
@@ -137,6 +151,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
- **Due**: Next sprint
|
- **Due**: Next sprint
|
||||||
|
|
||||||
### [BUG-003] Identity Module String Comparison
|
### [BUG-003] Identity Module String Comparison
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Low
|
- **Priority**: Low
|
||||||
- **Description**: `identity.py:42` string comparison for ADMIN_ID could fail if numeric
|
- **Description**: `identity.py:42` string comparison for ADMIN_ID could fail if numeric
|
||||||
- **Files affected**: `identity.py`
|
- **Files affected**: `identity.py`
|
||||||
@@ -146,6 +161,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
## **Documentation & Testing** 📚
|
## **Documentation & Testing** 📚
|
||||||
|
|
||||||
### [DOC-001] Documentation Consistency
|
### [DOC-001] Documentation Consistency
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Low
|
- **Priority**: Low
|
||||||
- **Description**: AGENTS.md vs implementation inconsistencies
|
- **Description**: AGENTS.md vs implementation inconsistencies
|
||||||
- **Files affected**: `AGENTS.md`, various modules
|
- **Files affected**: `AGENTS.md`, various modules
|
||||||
@@ -153,6 +169,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
- **Due**: Future iteration
|
- **Due**: Future iteration
|
||||||
|
|
||||||
### [TEST-001] Test Coverage
|
### [TEST-001] Test Coverage
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Low
|
- **Priority**: Low
|
||||||
- **Description**: Missing comprehensive test coverage
|
- **Description**: Missing comprehensive test coverage
|
||||||
- **Files affected**: All modules
|
- **Files affected**: All modules
|
||||||
@@ -160,6 +177,7 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
- **Due**: Future iteration
|
- **Due**: Future iteration
|
||||||
|
|
||||||
### [TEST-002] Code Quality Tools
|
### [TEST-002] Code Quality Tools
|
||||||
|
- **Status**: TODO
|
||||||
- **Priority**: Low
|
- **Priority**: Low
|
||||||
- **Description**: Missing code quality tools (black, flake8, mypy)
|
- **Description**: Missing code quality tools (black, flake8, mypy)
|
||||||
- **Files affected**: Repository configuration
|
- **Files affected**: Repository configuration
|
||||||
@@ -170,12 +188,12 @@ This document tracks all pending tasks, improvements, and issues identified in t
|
|||||||
|
|
||||||
## **Sprint Planning**
|
## **Sprint Planning**
|
||||||
|
|
||||||
### **Current Sprint (High Priority)**
|
### **Previous Sprint (High Priority)**
|
||||||
- **[DONE]** [SEC-001] File upload security validation
|
- **[DONE]** [SEC-001] File upload security validation
|
||||||
- **[DONE]** [DEP-002] Package security updates
|
- **[DONE]** [DEP-002] Package security updates
|
||||||
- **[DONE]** [IMP-001] Whisper transcription agent
|
- **[DONE]** [IMP-001] Whisper transcription agent
|
||||||
|
|
||||||
### **Next Sprint (Medium Priority)**
|
### **Current Sprint (Medium Priority)**
|
||||||
- [SEC-002] Secret management implementation
|
- [SEC-002] Secret management implementation
|
||||||
- [SEC-003] Database connection pooling
|
- [SEC-003] Database connection pooling
|
||||||
- [DEP-001] Python version upgrade
|
- [DEP-001] Python version upgrade
|
||||||
|
|||||||
19
bot/db.py
19
bot/db.py
@@ -4,16 +4,27 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import threading
|
||||||
|
|
||||||
DATABASE_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data", "users.db")
|
DATABASE_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data", "users.db")
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Use a thread-local object to manage the database connection
|
||||||
|
local = threading.local()
|
||||||
|
|
||||||
def get_db_connection():
|
def get_db_connection():
|
||||||
"""Creates a connection to the SQLite database."""
|
"""Creates a connection to the SQLite database."""
|
||||||
conn = sqlite3.connect(DATABASE_FILE)
|
if not hasattr(local, "conn"):
|
||||||
conn.row_factory = sqlite3.Row
|
local.conn = sqlite3.connect(DATABASE_FILE, check_same_thread=False)
|
||||||
return conn
|
local.conn.row_factory = sqlite3.Row
|
||||||
|
return local.conn
|
||||||
|
|
||||||
|
def close_db_connection():
|
||||||
|
"""Closes the database connection."""
|
||||||
|
if hasattr(local, "conn"):
|
||||||
|
local.conn.close()
|
||||||
|
del local.conn
|
||||||
|
|
||||||
def setup_database():
|
def setup_database():
|
||||||
"""Sets up the database tables if they don't exist."""
|
"""Sets up the database tables if they don't exist."""
|
||||||
@@ -50,7 +61,7 @@ def setup_database():
|
|||||||
logger.error(f"Database error during setup: {e}")
|
logger.error(f"Database error during setup: {e}")
|
||||||
finally:
|
finally:
|
||||||
if conn:
|
if conn:
|
||||||
conn.close()
|
close_db_connection()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# This allows us to run the script directly to initialize the database
|
# This allows us to run the script directly to initialize the database
|
||||||
|
|||||||
244
bot/main.py
244
bot/main.py
@@ -2,19 +2,16 @@
|
|||||||
# Este es el archivo principal del bot. Aquí se inicia todo y se configuran los comandos.
|
# Este es el archivo principal del bot. Aquí se inicia todo y se configuran los comandos.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
from telegram import Update
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
Application,
|
Application,
|
||||||
CommandHandler,
|
CommandHandler,
|
||||||
CallbackQueryHandler,
|
CallbackQueryHandler,
|
||||||
ConversationHandler,
|
|
||||||
MessageHandler,
|
MessageHandler,
|
||||||
ContextTypes,
|
ContextTypes,
|
||||||
filters,
|
filters,
|
||||||
TypeHandler,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ensure package imports work even if the file is executed directly
|
# Ensure package imports work even if the file is executed directly
|
||||||
@@ -28,23 +25,11 @@ if __package__ is None:
|
|||||||
from bot.config import TELEGRAM_BOT_TOKEN
|
from bot.config import TELEGRAM_BOT_TOKEN
|
||||||
from bot.modules.identity import get_user_role
|
from bot.modules.identity import get_user_role
|
||||||
from bot.modules.onboarding import handle_start as onboarding_handle_start
|
from bot.modules.onboarding import handle_start as onboarding_handle_start
|
||||||
from bot.modules.onboarding import get_admin_secondary_menu
|
from bot.modules.printer import handle_document, check_print_status
|
||||||
from bot.modules.agenda import get_agenda
|
from bot.db import setup_database, close_db_connection
|
||||||
from bot.modules.citas import request_appointment
|
|
||||||
from bot.modules.equipo import (
|
|
||||||
view_requests_status,
|
|
||||||
)
|
|
||||||
from bot.modules.aprobaciones import view_pending, handle_approval_action
|
|
||||||
from bot.modules.admin import get_system_status
|
|
||||||
import os
|
|
||||||
from bot.modules.debug import print_handler
|
|
||||||
from bot.modules.vikunja import vikunja_conv_handler, get_projects_list, get_tasks_list
|
|
||||||
from bot.modules.printer import send_file_to_printer, check_print_status
|
|
||||||
from bot.db import setup_database
|
|
||||||
from bot.modules.flow_engine import FlowEngine
|
from bot.modules.flow_engine import FlowEngine
|
||||||
from bot.modules.transcription import transcribe_audio
|
from bot.modules.dispatcher import button_dispatcher
|
||||||
from bot.modules.file_validation import validate_document
|
from bot.modules.message_handler import text_and_voice_handler
|
||||||
|
|
||||||
from bot.scheduler import schedule_daily_summary
|
from bot.scheduler import schedule_daily_summary
|
||||||
|
|
||||||
# Configuramos el sistema de logs para ver mensajes de estado en la consola
|
# Configuramos el sistema de logs para ver mensajes de estado en la consola
|
||||||
@@ -53,47 +38,6 @@ logging.basicConfig(
|
|||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def send_step_message(update: Update, step: dict):
|
|
||||||
"""Helper to send a message for a flow step, including options if available."""
|
|
||||||
text = step["question"]
|
|
||||||
reply_markup = None
|
|
||||||
|
|
||||||
options = []
|
|
||||||
if "options" in step and step["options"]:
|
|
||||||
options = step["options"]
|
|
||||||
elif "input_type" in step:
|
|
||||||
if step["input_type"] == "dynamic_keyboard_vikunja_projects":
|
|
||||||
projects = get_projects_list()
|
|
||||||
# Assuming project has 'title' or 'id'
|
|
||||||
options = [p.get('title', 'Unknown') for p in projects]
|
|
||||||
elif step["input_type"] == "dynamic_keyboard_vikunja_tasks":
|
|
||||||
# NOTE: We ideally need the project_id selected in previous step.
|
|
||||||
# For now, defaulting to project 1 or generic fetch
|
|
||||||
tasks = get_tasks_list(1)
|
|
||||||
options = [t.get('title', 'Unknown') for t in tasks]
|
|
||||||
|
|
||||||
if options:
|
|
||||||
keyboard = []
|
|
||||||
# Create a row for each option or group them
|
|
||||||
row = []
|
|
||||||
for option in options:
|
|
||||||
# Check if option is simple string or object (not implemented in JSONs seen so far)
|
|
||||||
# Ensure callback_data is not too long
|
|
||||||
cb_data = str(option)[:64]
|
|
||||||
row.append(InlineKeyboardButton(str(option), callback_data=cb_data))
|
|
||||||
if len(row) >= 2:
|
|
||||||
keyboard.append(row)
|
|
||||||
row = []
|
|
||||||
if row:
|
|
||||||
keyboard.append(row)
|
|
||||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
|
||||||
|
|
||||||
if update.callback_query:
|
|
||||||
await update.callback_query.edit_message_text(text=text, reply_markup=reply_markup)
|
|
||||||
else:
|
|
||||||
await update.message.reply_text(text=text, reply_markup=reply_markup)
|
|
||||||
|
|
||||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
"""
|
"""
|
||||||
Se ejecuta cuando el usuario escribe /start.
|
Se ejecuta cuando el usuario escribe /start.
|
||||||
@@ -108,7 +52,6 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|||||||
logger.info(f"User {chat_id} started a new conversation, clearing any previous state.")
|
logger.info(f"User {chat_id} started a new conversation, clearing any previous state.")
|
||||||
|
|
||||||
user_role = get_user_role(chat_id)
|
user_role = get_user_role(chat_id)
|
||||||
|
|
||||||
logger.info(f"Usuario {chat_id} inició conversación con el rol: {user_role}")
|
logger.info(f"Usuario {chat_id} inició conversación con el rol: {user_role}")
|
||||||
|
|
||||||
# Obtenemos el texto y los botones de bienvenida desde el módulo de onboarding
|
# Obtenemos el texto y los botones de bienvenida desde el módulo de onboarding
|
||||||
@@ -118,81 +61,6 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|||||||
await update.message.reply_text(response_text, reply_markup=reply_markup)
|
await update.message.reply_text(response_text, reply_markup=reply_markup)
|
||||||
|
|
||||||
|
|
||||||
async def text_and_voice_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
||||||
"""Handles text and voice messages for the flow engine."""
|
|
||||||
user_id = update.effective_user.id
|
|
||||||
flow_engine = context.bot_data["flow_engine"]
|
|
||||||
|
|
||||||
state = flow_engine.get_conversation_state(user_id)
|
|
||||||
if not state:
|
|
||||||
# If there's no active conversation, treat it as a start command
|
|
||||||
# await start(update, context) # Changed behavior: Don't auto-start, might be annoying
|
|
||||||
return
|
|
||||||
|
|
||||||
user_response = update.message.text
|
|
||||||
if update.message.voice:
|
|
||||||
voice = update.message.voice
|
|
||||||
temp_dir = 'temp_files'
|
|
||||||
os.makedirs(temp_dir, exist_ok=True)
|
|
||||||
file_path = os.path.join(temp_dir, f"{voice.file_id}.ogg")
|
|
||||||
|
|
||||||
try:
|
|
||||||
voice_file = await context.bot.get_file(voice.file_id)
|
|
||||||
await voice_file.download_to_drive(file_path)
|
|
||||||
logger.info(f"Voice message saved to {file_path}")
|
|
||||||
|
|
||||||
user_response = transcribe_audio(file_path)
|
|
||||||
logger.info(f"Transcription result: '{user_response}'")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error during voice transcription: {e}")
|
|
||||||
user_response = "Error al procesar el mensaje de voz."
|
|
||||||
finally:
|
|
||||||
if os.path.exists(file_path):
|
|
||||||
os.remove(file_path)
|
|
||||||
|
|
||||||
result = flow_engine.handle_response(user_id, user_response)
|
|
||||||
|
|
||||||
if result["status"] == "in_progress":
|
|
||||||
await send_step_message(update, result["step"])
|
|
||||||
elif result["status"] == "complete":
|
|
||||||
if "sales_pitch" in result:
|
|
||||||
await update.message.reply_text(result["sales_pitch"])
|
|
||||||
elif "nfc_tag" in result:
|
|
||||||
await update.message.reply_text(result["nfc_tag"], parse_mode='Markdown')
|
|
||||||
else:
|
|
||||||
await update.message.reply_text("Gracias por completar el flujo.")
|
|
||||||
elif result["status"] == "error":
|
|
||||||
await update.message.reply_text(result["message"])
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_document(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
||||||
"""Handles documents sent to the bot for printing."""
|
|
||||||
document = update.message.document
|
|
||||||
user_id = update.effective_user.id
|
|
||||||
|
|
||||||
# Validate the document before processing
|
|
||||||
is_valid, message = validate_document(document)
|
|
||||||
if not is_valid:
|
|
||||||
await update.message.reply_text(message)
|
|
||||||
return
|
|
||||||
|
|
||||||
file = await context.bot.get_file(document.file_id)
|
|
||||||
|
|
||||||
# Create a directory for temporary files if it doesn't exist
|
|
||||||
temp_dir = 'temp_files'
|
|
||||||
os.makedirs(temp_dir, exist_ok=True)
|
|
||||||
file_path = os.path.join(temp_dir, document.file_name)
|
|
||||||
|
|
||||||
await file.download_to_drive(file_path)
|
|
||||||
|
|
||||||
response = await send_file_to_printer(file_path, user_id, document.file_name)
|
|
||||||
await update.message.reply_text(response)
|
|
||||||
|
|
||||||
# Clean up the downloaded file
|
|
||||||
os.remove(file_path)
|
|
||||||
|
|
||||||
|
|
||||||
async def check_print_status_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def check_print_status_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
"""Command to check print status."""
|
"""Command to check print status."""
|
||||||
user_id = update.effective_user.id
|
user_id = update.effective_user.id
|
||||||
@@ -209,97 +77,6 @@ async def reset_conversation(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
|||||||
logger.info(f"User {user_id} reset their conversation.")
|
logger.info(f"User {user_id} reset their conversation.")
|
||||||
|
|
||||||
|
|
||||||
async def button_dispatcher(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
||||||
"""
|
|
||||||
Esta función maneja los clics en los botones del menú.
|
|
||||||
Dependiendo de qué botón se presione, ejecuta una acción diferente.
|
|
||||||
"""
|
|
||||||
query = update.callback_query
|
|
||||||
await query.answer()
|
|
||||||
logger.info(f"El despachador recibió una consulta: {query.data}")
|
|
||||||
|
|
||||||
response_text = "Acción no reconocida."
|
|
||||||
reply_markup = None
|
|
||||||
|
|
||||||
simple_handlers = {
|
|
||||||
'view_agenda': get_agenda,
|
|
||||||
'view_requests_status': view_requests_status,
|
|
||||||
'schedule_appointment': request_appointment,
|
|
||||||
'view_system_status': get_system_status,
|
|
||||||
'manage_users': lambda: "Función de gestión de usuarios no implementada.",
|
|
||||||
}
|
|
||||||
|
|
||||||
complex_handlers = {
|
|
||||||
'admin_menu': get_admin_secondary_menu,
|
|
||||||
'view_pending': view_pending,
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
if query.data in simple_handlers:
|
|
||||||
handler = simple_handlers[query.data]
|
|
||||||
logger.info(f"Ejecutando simple_handler para: {query.data}")
|
|
||||||
if asyncio.iscoroutinefunction(handler):
|
|
||||||
response_text = await handler()
|
|
||||||
else:
|
|
||||||
response_text = handler()
|
|
||||||
elif query.data in complex_handlers:
|
|
||||||
handler = complex_handlers[query.data]
|
|
||||||
logger.info(f"Ejecutando complex_handler para: {query.data}")
|
|
||||||
if asyncio.iscoroutinefunction(handler):
|
|
||||||
response_text, reply_markup = await handler()
|
|
||||||
else:
|
|
||||||
response_text, reply_markup = handler()
|
|
||||||
elif query.data.startswith(('approve:', 'reject:')):
|
|
||||||
logger.info(f"Ejecutando acción de aprobación: {query.data}")
|
|
||||||
response_text = handle_approval_action(query.data)
|
|
||||||
else:
|
|
||||||
# Check if the button is a flow trigger
|
|
||||||
flow_engine = context.bot_data["flow_engine"]
|
|
||||||
flow_to_start = next((flow for flow in flow_engine.flows if flow.get("trigger_button") == query.data), None)
|
|
||||||
|
|
||||||
if flow_to_start:
|
|
||||||
logger.info(f"Iniciando flujo: {flow_to_start['id']}")
|
|
||||||
initial_step = flow_engine.start_flow(update.effective_user.id, flow_to_start["id"])
|
|
||||||
if initial_step:
|
|
||||||
await send_step_message(update, initial_step)
|
|
||||||
else:
|
|
||||||
logger.error("No se pudo iniciar el flujo (paso inicial vacío).")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Check if the user is in a flow and clicked an option
|
|
||||||
state = flow_engine.get_conversation_state(update.effective_user.id)
|
|
||||||
if state:
|
|
||||||
logger.info(f"Procesando paso de flujo para usuario {update.effective_user.id}. Data: {query.data}")
|
|
||||||
result = flow_engine.handle_response(update.effective_user.id, query.data)
|
|
||||||
|
|
||||||
if result["status"] == "in_progress":
|
|
||||||
logger.info("Flujo en progreso, enviando siguiente paso.")
|
|
||||||
await send_step_message(update, result["step"])
|
|
||||||
elif result["status"] == "complete":
|
|
||||||
logger.info("Flujo completado.")
|
|
||||||
if "sales_pitch" in result:
|
|
||||||
await query.edit_message_text(result["sales_pitch"])
|
|
||||||
elif "nfc_tag" in result:
|
|
||||||
await query.edit_message_text(result["nfc_tag"], parse_mode='Markdown')
|
|
||||||
else:
|
|
||||||
await query.edit_message_text("Gracias por completar el flujo.")
|
|
||||||
elif result["status"] == "error":
|
|
||||||
logger.error(f"Error en el flujo: {result['message']}")
|
|
||||||
await query.edit_message_text(f"Error: {result['message']}")
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.warning(f"Consulta no manejada por el despachador: {query.data}")
|
|
||||||
# Only update text if no flow was started
|
|
||||||
await query.edit_message_text(text=response_text)
|
|
||||||
return
|
|
||||||
|
|
||||||
except Exception as exc:
|
|
||||||
logger.exception(f"Error al procesar la acción {query.data}: {exc}")
|
|
||||||
response_text = "❌ Ocurrió un error al procesar tu solicitud. Intenta de nuevo."
|
|
||||||
reply_markup = None
|
|
||||||
|
|
||||||
await query.edit_message_text(text=response_text, reply_markup=reply_markup, parse_mode='Markdown')
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Función principal que arranca el bot."""
|
"""Función principal que arranca el bot."""
|
||||||
if not TELEGRAM_BOT_TOKEN:
|
if not TELEGRAM_BOT_TOKEN:
|
||||||
@@ -310,25 +87,24 @@ def main() -> None:
|
|||||||
|
|
||||||
application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
|
application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
|
||||||
|
|
||||||
# Instantiate and store the flow engine in bot_data
|
|
||||||
flow_engine = FlowEngine()
|
flow_engine = FlowEngine()
|
||||||
application.bot_data["flow_engine"] = flow_engine
|
application.bot_data["flow_engine"] = flow_engine
|
||||||
|
|
||||||
schedule_daily_summary(application)
|
schedule_daily_summary(application)
|
||||||
|
|
||||||
application.add_handler(CommandHandler("start", start))
|
application.add_handler(CommandHandler("start", start))
|
||||||
application.add_handler(CommandHandler("reset", reset_conversation)) # Added reset command
|
application.add_handler(CommandHandler("reset", reset_conversation))
|
||||||
application.add_handler(CommandHandler("print", print_handler))
|
|
||||||
application.add_handler(CommandHandler("check_print_status", check_print_status_command))
|
application.add_handler(CommandHandler("check_print_status", check_print_status_command))
|
||||||
|
|
||||||
application.add_handler(MessageHandler(filters.Document.ALL, handle_document))
|
application.add_handler(MessageHandler(filters.Document.ALL, handle_document))
|
||||||
|
|
||||||
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND | filters.VOICE, text_and_voice_handler))
|
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND | filters.VOICE, text_and_voice_handler))
|
||||||
|
|
||||||
application.add_handler(CallbackQueryHandler(button_dispatcher))
|
application.add_handler(CallbackQueryHandler(button_dispatcher))
|
||||||
|
|
||||||
logger.info("Iniciando Talía Bot...")
|
logger.info("Iniciando Talía Bot...")
|
||||||
|
try:
|
||||||
application.run_polling()
|
application.run_polling()
|
||||||
|
finally:
|
||||||
|
close_db_connection()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
138
bot/modules/dispatcher.py
Normal file
138
bot/modules/dispatcher.py
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
# bot/modules/dispatcher.py
|
||||||
|
# This module is responsible for dispatching user interactions to the correct handlers.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
|
from telegram.ext import ContextTypes
|
||||||
|
|
||||||
|
from bot.modules.onboarding import get_admin_secondary_menu
|
||||||
|
from bot.modules.agenda import get_agenda
|
||||||
|
from bot.modules.citas import request_appointment
|
||||||
|
from bot.modules.equipo import view_requests_status
|
||||||
|
from bot.modules.aprobaciones import view_pending, handle_approval_action
|
||||||
|
from bot.modules.admin import get_system_status
|
||||||
|
from bot.modules.vikunja import get_projects_list, get_tasks_list
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
async def send_step_message(update: Update, step: dict):
|
||||||
|
"""Helper to send a message for a flow step, including options if available."""
|
||||||
|
text = step["question"]
|
||||||
|
reply_markup = None
|
||||||
|
|
||||||
|
options = []
|
||||||
|
if "options" in step and step["options"]:
|
||||||
|
options = step["options"]
|
||||||
|
elif "input_type" in step:
|
||||||
|
if step["input_type"] == "dynamic_keyboard_vikunja_projects":
|
||||||
|
projects = get_projects_list()
|
||||||
|
options = [p.get('title', 'Unknown') for p in projects]
|
||||||
|
elif step["input_type"] == "dynamic_keyboard_vikunja_tasks":
|
||||||
|
tasks = get_tasks_list(1)
|
||||||
|
options = [t.get('title', 'Unknown') for t in tasks]
|
||||||
|
|
||||||
|
if options:
|
||||||
|
keyboard = []
|
||||||
|
row = []
|
||||||
|
for option in options:
|
||||||
|
cb_data = str(option)[:64]
|
||||||
|
row.append(InlineKeyboardButton(str(option), callback_data=cb_data))
|
||||||
|
if len(row) >= 2:
|
||||||
|
keyboard.append(row)
|
||||||
|
row = []
|
||||||
|
if row:
|
||||||
|
keyboard.append(row)
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
|
||||||
|
if update.callback_query:
|
||||||
|
await update.callback_query.edit_message_text(text=text, reply_markup=reply_markup)
|
||||||
|
else:
|
||||||
|
await update.message.reply_text(text=text, reply_markup=reply_markup)
|
||||||
|
|
||||||
|
async def button_dispatcher(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
"""
|
||||||
|
Handles button clicks and dispatches them to the appropriate handlers.
|
||||||
|
"""
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
logger.info(f"El despachador recibió una consulta: {query.data}")
|
||||||
|
|
||||||
|
response_text = "Acción no reconocida."
|
||||||
|
reply_markup = None
|
||||||
|
|
||||||
|
simple_handlers = {
|
||||||
|
'view_agenda': get_agenda,
|
||||||
|
'view_requests_status': view_requests_status,
|
||||||
|
'schedule_appointment': request_appointment,
|
||||||
|
'view_system_status': get_system_status,
|
||||||
|
'manage_users': lambda: "Función de gestión de usuarios no implementada.",
|
||||||
|
}
|
||||||
|
|
||||||
|
complex_handlers = {
|
||||||
|
'admin_menu': get_admin_secondary_menu,
|
||||||
|
'view_pending': view_pending,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
if query.data in simple_handlers:
|
||||||
|
handler = simple_handlers[query.data]
|
||||||
|
logger.info(f"Ejecutando simple_handler para: {query.data}")
|
||||||
|
if asyncio.iscoroutinefunction(handler):
|
||||||
|
response_text = await handler()
|
||||||
|
else:
|
||||||
|
response_text = handler()
|
||||||
|
elif query.data in complex_handlers:
|
||||||
|
handler = complex_handlers[query.data]
|
||||||
|
logger.info(f"Ejecutando complex_handler para: {query.data}")
|
||||||
|
if asyncio.iscoroutinefunction(handler):
|
||||||
|
response_text, reply_markup = await handler()
|
||||||
|
else:
|
||||||
|
response_text, reply_markup = handler()
|
||||||
|
elif query.data.startswith(('approve:', 'reject:')):
|
||||||
|
logger.info(f"Ejecutando acción de aprobación: {query.data}")
|
||||||
|
response_text = handle_approval_action(query.data)
|
||||||
|
else:
|
||||||
|
flow_engine = context.bot_data["flow_engine"]
|
||||||
|
flow_to_start = next((flow for flow in flow_engine.flows if flow.get("trigger_button") == query.data), None)
|
||||||
|
|
||||||
|
if flow_to_start:
|
||||||
|
logger.info(f"Iniciando flujo: {flow_to_start['id']}")
|
||||||
|
initial_step = flow_engine.start_flow(update.effective_user.id, flow_to_start["id"])
|
||||||
|
if initial_step:
|
||||||
|
await send_step_message(update, initial_step)
|
||||||
|
else:
|
||||||
|
logger.error("No se pudo iniciar el flujo (paso inicial vacío).")
|
||||||
|
return
|
||||||
|
|
||||||
|
state = flow_engine.get_conversation_state(update.effective_user.id)
|
||||||
|
if state:
|
||||||
|
logger.info(f"Procesando paso de flujo para usuario {update.effective_user.id}. Data: {query.data}")
|
||||||
|
result = flow_engine.handle_response(update.effective_user.id, query.data)
|
||||||
|
|
||||||
|
if result["status"] == "in_progress":
|
||||||
|
logger.info("Flujo en progreso, enviando siguiente paso.")
|
||||||
|
await send_step_message(update, result["step"])
|
||||||
|
elif result["status"] == "complete":
|
||||||
|
logger.info("Flujo completado.")
|
||||||
|
if "sales_pitch" in result:
|
||||||
|
await query.edit_message_text(result["sales_pitch"])
|
||||||
|
elif "nfc_tag" in result:
|
||||||
|
await query.edit_message_text(result["nfc_tag"], parse_mode='Markdown')
|
||||||
|
else:
|
||||||
|
await query.edit_message_text("Gracias por completar el flujo.")
|
||||||
|
elif result["status"] == "error":
|
||||||
|
logger.error(f"Error en el flujo: {result['message']}")
|
||||||
|
await query.edit_message_text(f"Error: {result['message']}")
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.warning(f"Consulta no manejada por el despachador: {query.data}")
|
||||||
|
await query.edit_message_text(text=response_text)
|
||||||
|
return
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception(f"Error al procesar la acción {query.data}: {exc}")
|
||||||
|
response_text = "❌ Ocurrió un error al procesar tu solicitud. Intenta de nuevo."
|
||||||
|
reply_markup = None
|
||||||
|
|
||||||
|
await query.edit_message_text(text=response_text, reply_markup=reply_markup, parse_mode='Markdown')
|
||||||
@@ -52,11 +52,11 @@ class FlowEngine:
|
|||||||
|
|
||||||
def get_conversation_state(self, user_id):
|
def get_conversation_state(self, user_id):
|
||||||
"""Gets the current conversation state for a user from the database."""
|
"""Gets the current conversation state for a user from the database."""
|
||||||
|
try:
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute("SELECT flow_id, current_step_id, collected_data FROM conversations WHERE user_id = ?", (user_id,))
|
cursor.execute("SELECT flow_id, current_step_id, collected_data FROM conversations WHERE user_id = ?", (user_id,))
|
||||||
state = cursor.fetchone()
|
state = cursor.fetchone()
|
||||||
conn.close()
|
|
||||||
if state:
|
if state:
|
||||||
return {
|
return {
|
||||||
"flow_id": state['flow_id'],
|
"flow_id": state['flow_id'],
|
||||||
@@ -64,12 +64,15 @@ class FlowEngine:
|
|||||||
"collected_data": json.loads(state['collected_data']) if state['collected_data'] else {}
|
"collected_data": json.loads(state['collected_data']) if state['collected_data'] else {}
|
||||||
}
|
}
|
||||||
return None
|
return None
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
logger.error(f"Database error in get_conversation_state: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def start_flow(self, user_id, flow_id):
|
def start_flow(self, user_id, flow_id):
|
||||||
"""Starts a new flow for a user."""
|
"""Starts a new flow for a user."""
|
||||||
flow = self.get_flow(flow_id)
|
flow = self.get_flow(flow_id)
|
||||||
if not flow or 'steps' not in flow or not flow['steps']:
|
if not flow or 'steps' not in flow or not isinstance(flow['steps'], list) or not flow['steps']:
|
||||||
logger.error(f"Flow '{flow_id}' is invalid or has no steps.")
|
logger.error(f"Flow '{flow_id}' is invalid, has no steps, or steps is not a non-empty list.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
initial_step = flow['steps'][0]
|
initial_step = flow['steps'][0]
|
||||||
@@ -78,6 +81,7 @@ class FlowEngine:
|
|||||||
|
|
||||||
def update_conversation_state(self, user_id, flow_id, step_id, collected_data):
|
def update_conversation_state(self, user_id, flow_id, step_id, collected_data):
|
||||||
"""Creates or updates the conversation state in the database."""
|
"""Creates or updates the conversation state in the database."""
|
||||||
|
try:
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
@@ -85,7 +89,8 @@ class FlowEngine:
|
|||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
""", (user_id, flow_id, step_id, json.dumps(collected_data)))
|
""", (user_id, flow_id, step_id, json.dumps(collected_data)))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
except sqlite3.Error as e:
|
||||||
|
logger.error(f"Database error in update_conversation_state: {e}")
|
||||||
|
|
||||||
def handle_response(self, user_id, response_data):
|
def handle_response(self, user_id, response_data):
|
||||||
"""
|
"""
|
||||||
@@ -147,8 +152,10 @@ class FlowEngine:
|
|||||||
|
|
||||||
def end_flow(self, user_id):
|
def end_flow(self, user_id):
|
||||||
"""Ends a flow for a user by deleting their conversation state."""
|
"""Ends a flow for a user by deleting their conversation state."""
|
||||||
|
try:
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute("DELETE FROM conversations WHERE user_id = ?", (user_id,))
|
cursor.execute("DELETE FROM conversations WHERE user_id = ?", (user_id,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
except sqlite3.Error as e:
|
||||||
|
logger.error(f"Database error in end_flow: {e}")
|
||||||
|
|||||||
57
bot/modules/message_handler.py
Normal file
57
bot/modules/message_handler.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# bot/modules/message_handler.py
|
||||||
|
# This module handles the processing of text and voice messages.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from telegram import Update
|
||||||
|
from telegram.ext import ContextTypes
|
||||||
|
|
||||||
|
from bot.modules.transcription import transcribe_audio
|
||||||
|
from bot.modules.dispatcher import send_step_message
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
async def text_and_voice_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
"""Handles text and voice messages for the flow engine."""
|
||||||
|
user_id = update.effective_user.id
|
||||||
|
flow_engine = context.bot_data["flow_engine"]
|
||||||
|
|
||||||
|
state = flow_engine.get_conversation_state(user_id)
|
||||||
|
if not state:
|
||||||
|
return
|
||||||
|
|
||||||
|
user_response = update.message.text
|
||||||
|
if update.message.voice:
|
||||||
|
voice = update.message.voice
|
||||||
|
temp_dir = 'temp_files'
|
||||||
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
|
file_path = os.path.join(temp_dir, f"{voice.file_id}.ogg")
|
||||||
|
|
||||||
|
try:
|
||||||
|
voice_file = await context.bot.get_file(voice.file_id)
|
||||||
|
await voice_file.download_to_drive(file_path)
|
||||||
|
logger.info(f"Voice message saved to {file_path}")
|
||||||
|
|
||||||
|
user_response = transcribe_audio(file_path)
|
||||||
|
logger.info(f"Transcription result: '{user_response}'")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during voice transcription: {e}")
|
||||||
|
user_response = "Error al procesar el mensaje de voz."
|
||||||
|
finally:
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
os.remove(file_path)
|
||||||
|
|
||||||
|
result = flow_engine.handle_response(user_id, user_response)
|
||||||
|
|
||||||
|
if result["status"] == "in_progress":
|
||||||
|
await send_step_message(update, result["step"])
|
||||||
|
elif result["status"] == "complete":
|
||||||
|
if "sales_pitch" in result:
|
||||||
|
await update.message.reply_text(result["sales_pitch"])
|
||||||
|
elif "nfc_tag" in result:
|
||||||
|
await update.message.reply_text(result["nfc_tag"], parse_mode='Markdown')
|
||||||
|
else:
|
||||||
|
await update.message.reply_text("Gracias por completar el flujo.")
|
||||||
|
elif result["status"] == "error":
|
||||||
|
await update.message.reply_text(result["message"])
|
||||||
@@ -5,10 +5,13 @@ import smtplib
|
|||||||
import imaplib
|
import imaplib
|
||||||
import email
|
import email
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.mime.base import MIMEBase
|
from email.mime.base import MIMEBase
|
||||||
from email import encoders
|
from email import encoders
|
||||||
|
from telegram import Update
|
||||||
|
from telegram.ext import ContextTypes
|
||||||
|
|
||||||
from bot.config import (
|
from bot.config import (
|
||||||
SMTP_SERVER,
|
SMTP_SERVER,
|
||||||
@@ -21,9 +24,36 @@ from bot.config import (
|
|||||||
PRINTER_EMAIL,
|
PRINTER_EMAIL,
|
||||||
)
|
)
|
||||||
from bot.modules.identity import is_admin
|
from bot.modules.identity import is_admin
|
||||||
|
from bot.modules.file_validation import validate_document
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
async def handle_document(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
"""Handles documents sent to the bot for printing."""
|
||||||
|
document = update.message.document
|
||||||
|
user_id = update.effective_user.id
|
||||||
|
|
||||||
|
is_valid, message = validate_document(document)
|
||||||
|
if not is_valid:
|
||||||
|
await update.message.reply_text(message)
|
||||||
|
return
|
||||||
|
|
||||||
|
file = await context.bot.get_file(document.file_id)
|
||||||
|
|
||||||
|
temp_dir = 'temp_files'
|
||||||
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
|
file_path = os.path.join(temp_dir, document.file_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await file.download_to_drive(file_path)
|
||||||
|
|
||||||
|
response = await send_file_to_printer(file_path, user_id, document.file_name)
|
||||||
|
await update.message.reply_text(response)
|
||||||
|
finally:
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
os.remove(file_path)
|
||||||
|
|
||||||
|
|
||||||
async def send_file_to_printer(file_path: str, user_id: int, file_name: str):
|
async def send_file_to_printer(file_path: str, user_id: int, file_name: str):
|
||||||
"""
|
"""
|
||||||
Sends a file to the printer via email.
|
Sends a file to the printer via email.
|
||||||
@@ -93,6 +123,9 @@ async def check_print_status(user_id: int):
|
|||||||
if not email_ids:
|
if not email_ids:
|
||||||
return "No hay actualizaciones de estado de impresión."
|
return "No hay actualizaciones de estado de impresión."
|
||||||
|
|
||||||
|
# Fetch only the last 10 unseen emails
|
||||||
|
email_ids = email_ids[-10:]
|
||||||
|
|
||||||
statuses = []
|
statuses = []
|
||||||
for e_id in email_ids:
|
for e_id in email_ids:
|
||||||
_, msg_data = mail.fetch(e_id, "(RFC822)")
|
_, msg_data = mail.fetch(e_id, "(RFC822)")
|
||||||
@@ -108,7 +141,10 @@ async def check_print_status(user_id: int):
|
|||||||
statuses.append(f"Trabajo de impresión recibido: {msg['subject']}")
|
statuses.append(f"Trabajo de impresión recibido: {msg['subject']}")
|
||||||
else:
|
else:
|
||||||
statuses.append(f"Nuevo correo: {msg['subject']}")
|
statuses.append(f"Nuevo correo: {msg['subject']}")
|
||||||
|
# Mark the email as seen
|
||||||
|
mail.store(e_id, "+FLAGS", "\\Seen")
|
||||||
|
|
||||||
|
mail.close()
|
||||||
mail.logout()
|
mail.logout()
|
||||||
|
|
||||||
if not statuses:
|
if not statuses:
|
||||||
|
|||||||
Reference in New Issue
Block a user