Files
soft_usite/pkg/services/umind_canal_telegram_service.go
T
Lizandro GuarnizoandClaude Sonnet 5 da0bffe661 feat: orquestador uMind (SPA Vue) + tools custom + canales Telegram/WhatsApp
SPA nueva en /orchestrator (Vue 3 + Vite, servida por el mismo binario Go
bajo /orchestrator para que la cookie de sesión funcione sin tocar CORS),
reemplaza al panel Alpine.js como punto de entrada del menú.

Backend, todo aditivo sobre el motor de uMind ya existente:
- UmindHerramienta: tools custom por tenant que llaman un webhook HTTP,
  integradas al loop de function-calling existente. Cliente HTTP con
  guardas SSRF (bloqueo de IPs privadas/loopback/link-local resuelto en el
  momento de conectar, no antes, para cerrar la ventana de DNS rebinding)
  que no existían en el proyecto.
- UmindCanal: Telegram y WhatsApp Business Cloud API como canales
  adicionales del mismo agente que ya atiende el widget web, ambos
  reusando ProcessWidgetMessage. WhatsApp valida X-Hub-Signature-256.
  Credenciales cifradas en reposo con el mismo AES-GCM+APP_KEY que ya usa
  el proyecto para la contraseña SMTP (primer uso para secretos de uMind).
- Se conecta middlewares.Limit() (rate limiter que existía pero no se
  usaba en ningún lado) al widget público y a los webhooks nuevos.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 22:16:20 -05:00

57 lines
2.0 KiB
Go

package services
import (
"fmt"
"net/http"
"net/url"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)
// ProcesarMensajeTelegramUmind adapta un mensaje entrante del canal Telegram
// de un tenant al mismo motor que atiende el widget web
// (ProcessWidgetMessage) y responde usando el bot token propio del canal
// (no el bot interno de staff). La sesión se separa por chat_id con un
// prefijo para no colisionar con session_ids del widget.
func ProcesarMensajeTelegramUmind(canal *models.UmindCanal, chatID int64, texto string) error {
tenant, err := models.GetUmindTenantByID(canal.TenantID)
if err != nil || !tenant.Activo {
return fmt.Errorf("tenant no encontrado o inactivo: %w", err)
}
credenciales, err := DescifrarCredencialesCanal(canal.CredencialesEnc)
if err != nil {
return fmt.Errorf("credenciales del canal corruptas: %w", err)
}
botToken := credenciales["bot_token"]
if botToken == "" {
return fmt.Errorf("el canal no tiene bot_token configurado")
}
sessionID := fmt.Sprintf("tg:%d", chatID)
respuesta, err := ProcessWidgetMessage(tenant, sessionID, texto)
if err != nil {
return fmt.Errorf("error del agente: %w", err)
}
return (&TelegramService{}).SendMessageWithToken(chatID, respuesta, botToken)
}
// RegistrarWebhookTelegram le dice a Telegram a qué URL mandar los updates
// del bot — se llama una vez al crear el canal (o al reconfigurar el token).
func RegistrarWebhookTelegram(botToken, webhookURL string) error {
if botToken == "" || webhookURL == "" {
return fmt.Errorf("bot_token y webhookURL son requeridos")
}
api := fmt.Sprintf("https://api.telegram.org/bot%s/setWebhook?url=%s", botToken, url.QueryEscape(webhookURL))
resp, err := telegramHTTPClient.Get(api)
if err != nil {
return fmt.Errorf("no se pudo contactar la API de Telegram: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("Telegram respondió %d al registrar el webhook", resp.StatusCode)
}
return nil
}