mirror of
https://github.com/marcogll/talia_bot.git
synced 2026-01-13 21:35:19 +00:00
feat: Add Vikunja task management, refactor Google Calendar integration, and implement N8N webhook fallback.
This commit is contained in:
@@ -3,6 +3,12 @@
|
||||
# Las variables de entorno son valores que se guardan fuera del código por seguridad (como tokens y llaves API).
|
||||
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from pathlib import Path
|
||||
|
||||
# Cargar variables de entorno desde el archivo .env en la raíz del proyecto
|
||||
env_path = Path(__file__).parent.parent / '.env'
|
||||
load_dotenv(dotenv_path=env_path)
|
||||
|
||||
# Token del bot de Telegram (obtenido de @BotFather)
|
||||
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
||||
@@ -18,12 +24,19 @@ TEAM_CHAT_IDS = os.getenv("TEAM_CHAT_IDS", "").split(",")
|
||||
|
||||
# Ruta al archivo de credenciales de la cuenta de servicio de Google
|
||||
GOOGLE_SERVICE_ACCOUNT_FILE = os.getenv("GOOGLE_SERVICE_ACCOUNT_FILE")
|
||||
if GOOGLE_SERVICE_ACCOUNT_FILE and not os.path.isabs(GOOGLE_SERVICE_ACCOUNT_FILE):
|
||||
GOOGLE_SERVICE_ACCOUNT_FILE = str(Path(__file__).parent.parent / GOOGLE_SERVICE_ACCOUNT_FILE)
|
||||
|
||||
# ID del calendario de Google que usará el bot
|
||||
CALENDAR_ID = os.getenv("CALENDAR_ID")
|
||||
|
||||
# URL del webhook de n8n para enviar datos a otros servicios
|
||||
N8N_WEBHOOK_URL = os.getenv("N8N_WEBHOOK_URL")
|
||||
N8N_TEST_WEBHOOK_URL = os.getenv("N8N_TEST_WEBHOOK_URL")
|
||||
|
||||
# Configuración de Vikunja
|
||||
VIKUNJA_API_URL = os.getenv("VIKUNJA_API_URL", "https://tasks.soul23.cloud/api/v1")
|
||||
VIKUNJA_API_TOKEN = os.getenv("VIKUNJA_API_TOKEN")
|
||||
|
||||
# Llave de la API de OpenAI para usar modelos de lenguaje (como GPT)
|
||||
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# app/calendar.py
|
||||
# app/google_calendar.py
|
||||
# Este script maneja la integración con Google Calendar (Calendario de Google).
|
||||
# Permite buscar espacios libres y crear eventos.
|
||||
|
||||
20
app/main.py
20
app/main.py
@@ -15,7 +15,7 @@ from telegram.ext import (
|
||||
|
||||
# Importamos las configuraciones y herramientas que creamos en otros archivos
|
||||
from config import TELEGRAM_BOT_TOKEN
|
||||
from permissions import get_user_role
|
||||
from permissions import get_user_role, is_admin
|
||||
from modules.onboarding import handle_start as onboarding_handle_start
|
||||
from modules.agenda import get_agenda
|
||||
from modules.citas import request_appointment
|
||||
@@ -32,7 +32,8 @@ from modules.aprobaciones import view_pending, handle_approval_action
|
||||
from modules.servicios import get_service_info
|
||||
from modules.admin import get_system_status
|
||||
from modules.print import print_handler
|
||||
from modules.create_tag import create_tag_conv_handler
|
||||
from modules.create_tag import create_tag_conv_handler, create_tag_start
|
||||
from modules.vikunja import get_tasks
|
||||
from scheduler import schedule_daily_summary
|
||||
|
||||
# Configuramos el sistema de logs para ver mensajes de estado en la consola
|
||||
@@ -73,6 +74,7 @@ async def button_dispatcher(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
||||
# Diccionario de acciones simples (que solo devuelven texto)
|
||||
simple_handlers = {
|
||||
'view_agenda': get_agenda,
|
||||
'view_tasks': get_tasks,
|
||||
'view_requests_status': view_requests_status,
|
||||
'schedule_appointment': request_appointment,
|
||||
'get_service_info': get_service_info,
|
||||
@@ -93,6 +95,11 @@ async def button_dispatcher(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
||||
elif query.data.startswith(('approve:', 'reject:')):
|
||||
# Manejo especial para botones de aprobar o rechazar
|
||||
response_text = handle_approval_action(query.data)
|
||||
elif query.data == 'start_create_tag':
|
||||
# Iniciamos el flujo de creación de tag
|
||||
await query.message.reply_text("Iniciando creación de tag...")
|
||||
# Aquí simulamos el comando /create_tag
|
||||
return await create_tag_start(update, context)
|
||||
|
||||
# Editamos el mensaje original con la nueva información
|
||||
await query.edit_message_text(text=response_text, reply_markup=reply_markup, parse_mode='Markdown')
|
||||
@@ -127,6 +134,15 @@ def main() -> None:
|
||||
application.add_handler(create_tag_conv_handler())
|
||||
application.add_handler(CommandHandler("start", start))
|
||||
application.add_handler(CommandHandler("print", print_handler))
|
||||
|
||||
# Comando /vik restringido a administradores
|
||||
async def vik_command_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
if is_admin(update.effective_chat.id):
|
||||
await update.message.reply_text(get_tasks(), parse_mode='Markdown')
|
||||
else:
|
||||
await update.message.reply_text("No tienes permiso para usar este comando.")
|
||||
|
||||
application.add_handler(CommandHandler("vik", vik_command_handler))
|
||||
application.add_handler(CallbackQueryHandler(button_dispatcher))
|
||||
|
||||
# Iniciamos el bot (se queda escuchando mensajes)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Permite obtener y mostrar las actividades programadas para el día.
|
||||
|
||||
import datetime
|
||||
from calendar import get_events
|
||||
from google_calendar import get_events
|
||||
|
||||
def get_agenda():
|
||||
"""
|
||||
|
||||
@@ -15,8 +15,9 @@ def get_owner_menu():
|
||||
def get_admin_menu():
|
||||
"""Crea el menú de botones para los Administradores."""
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("📊 Ver estado del sistema", callback_data='view_system_status')],
|
||||
[InlineKeyboardButton("👥 Gestionar usuarios", callback_data='manage_users')],
|
||||
[InlineKeyboardButton("📋 Ver Tareas (Vikunja)", callback_data='view_tasks')],
|
||||
[InlineKeyboardButton("🏷️ Crear Tag NFC", callback_data='start_create_tag')],
|
||||
[InlineKeyboardButton("📊 Estado del sistema", callback_data='view_system_status')],
|
||||
]
|
||||
return InlineKeyboardMarkup(keyboard)
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext import ContextTypes
|
||||
from ..permissions import is_admin
|
||||
from ..config import TIMEZONE, CALENDAR_ID, N8N_WEBHOOK_URL
|
||||
from permissions import is_admin
|
||||
from config import TIMEZONE, CALENDAR_ID, N8N_WEBHOOK_URL
|
||||
|
||||
async def print_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""
|
||||
|
||||
59
app/modules/vikunja.py
Normal file
59
app/modules/vikunja.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# app/modules/vikunja.py
|
||||
# Este módulo maneja la integración con Vikunja para la gestión de tareas.
|
||||
|
||||
import requests
|
||||
import logging
|
||||
from config import VIKUNJA_API_URL, VIKUNJA_API_TOKEN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def get_vikunja_headers():
|
||||
"""Devuelve los headers necesarios para la API de Vikunja."""
|
||||
return {
|
||||
"Authorization": f"Bearer {VIKUNJA_API_TOKEN}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
def get_tasks():
|
||||
"""
|
||||
Obtiene la lista de tareas desde Vikunja.
|
||||
"""
|
||||
if not VIKUNJA_API_TOKEN:
|
||||
return "Error: VIKUNJA_API_TOKEN no configurado."
|
||||
|
||||
try:
|
||||
# Endpoint para obtener todas las tareas (ajustar según necesidad)
|
||||
response = requests.get(f"{VIKUNJA_API_URL}/tasks/all", headers=get_vikunja_headers())
|
||||
response.raise_for_status()
|
||||
tasks = response.json()
|
||||
|
||||
if not tasks:
|
||||
return "No tienes tareas pendientes en Vikunja."
|
||||
|
||||
text = "📋 *Tus Tareas en Vikunja*\n\n"
|
||||
for task in tasks[:10]: # Mostrar las primeras 10
|
||||
status = "✅" if task.get('done') else "⏳"
|
||||
text += f"{status} *{task.get('title')}*\n"
|
||||
|
||||
return text
|
||||
except Exception as e:
|
||||
logger.error(f"Error al obtener tareas de Vikunja: {e}")
|
||||
return f"Error al conectar con Vikunja: {e}"
|
||||
|
||||
def add_task(title):
|
||||
"""
|
||||
Agrega una nueva tarea a Vikunja.
|
||||
"""
|
||||
if not VIKUNJA_API_TOKEN:
|
||||
return "Error: VIKUNJA_API_TOKEN no configurado."
|
||||
|
||||
try:
|
||||
data = {"title": title}
|
||||
# Nota: Vikunja suele requerir un project_id. Aquí usamos uno genérico o el primero disponible.
|
||||
# Por ahora, este es un placeholder para el flujo /vik.
|
||||
response = requests.put(f"{VIKUNJA_API_URL}/tasks", headers=get_vikunja_headers(), json=data)
|
||||
response.raise_for_status()
|
||||
return f"✅ Tarea añadida: *{title}*"
|
||||
except Exception as e:
|
||||
logger.error(f"Error al añadir tarea a Vikunja: {e}")
|
||||
return f"Error al añadir tarea: {e}"
|
||||
@@ -3,23 +3,31 @@
|
||||
# En este caso, se comunica con n8n.
|
||||
|
||||
import requests
|
||||
from config import N8N_WEBHOOK_URL
|
||||
from config import N8N_WEBHOOK_URL, N8N_TEST_WEBHOOK_URL
|
||||
|
||||
def send_webhook(event_data):
|
||||
"""
|
||||
Envía datos de un evento al servicio n8n.
|
||||
|
||||
Parámetros:
|
||||
- event_data: Un diccionario con la información que queremos enviar.
|
||||
Usa el webhook normal y, si falla o no existe, usa el de test como fallback.
|
||||
"""
|
||||
try:
|
||||
# Hacemos una petición POST (enviar datos) a la URL configurada
|
||||
response = requests.post(N8N_WEBHOOK_URL, json=event_data)
|
||||
# Verificamos si la petición fue exitosa (status code 200-299)
|
||||
response.raise_for_status()
|
||||
# Devolvemos la respuesta del servidor en formato JSON
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
# Si hay un error en la conexión o el envío, lo mostramos
|
||||
print(f"Error al enviar el webhook: {e}")
|
||||
return None
|
||||
# Intentar con el webhook principal
|
||||
if N8N_WEBHOOK_URL:
|
||||
try:
|
||||
print(f"Intentando enviar a webhook principal: {N8N_WEBHOOK_URL}")
|
||||
response = requests.post(N8N_WEBHOOK_URL, json=event_data)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
print(f"Fallo en webhook principal: {e}")
|
||||
|
||||
# Fallback al webhook de test
|
||||
if N8N_TEST_WEBHOOK_URL:
|
||||
try:
|
||||
print(f"Intentando enviar a webhook de fallback (test): {N8N_TEST_WEBHOOK_URL}")
|
||||
response = requests.post(N8N_TEST_WEBHOOK_URL, json=event_data)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
print(f"Fallo en webhook de fallback: {e}")
|
||||
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user