268 lines
8.6 KiB
Go
Executable File
268 lines
8.6 KiB
Go
Executable File
package controllers
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"math"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/sujit-baniya/fiber-boilerplate/app"
|
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
|
|
)
|
|
|
|
// TelegramIndex renderiza la vista de configuración de Telegram.
|
|
func TelegramIndex(c *fiber.Ctx) error {
|
|
return c.Render("telegram", fiber.Map{
|
|
"user": c.Locals("user"),
|
|
"modules": c.Locals("userModules"),
|
|
}, "layouts/main")
|
|
}
|
|
|
|
// GetTelegramConfigs devuelve todas las configs en JSON.
|
|
func GetTelegramConfigs(c *fiber.Ctx) error {
|
|
items, err := models.GetAllTelegramConfigs()
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(items)
|
|
}
|
|
|
|
// CreateTelegramConfig crea una nueva configuración.
|
|
func CreateTelegramConfig(c *fiber.Ctx) error {
|
|
var m models.TelegramConfig
|
|
if err := c.BodyParser(&m); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
if strings.TrimSpace(m.Nombre) == "" || strings.TrimSpace(m.BotToken) == "" || strings.TrimSpace(m.ChatID) == "" {
|
|
return c.Status(400).JSON(fiber.Map{"error": "nombre, bot_token y chat_id son obligatorios"})
|
|
}
|
|
m.Activo = true
|
|
if err := models.CreateTelegramConfig(&m); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.Status(201).JSON(m)
|
|
}
|
|
|
|
// UpdateTelegramConfig actualiza una configuración.
|
|
func UpdateTelegramConfig(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
var m models.TelegramConfig
|
|
if err := c.BodyParser(&m); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
m.ID = uint(id)
|
|
// Si no se envía bot_token, mantener el existente
|
|
if strings.TrimSpace(m.BotToken) == "" {
|
|
cfg, err := models.GetTelegramConfigByID(uint(id))
|
|
if err != nil {
|
|
return c.Status(404).JSON(fiber.Map{"error": "no encontrado"})
|
|
}
|
|
m.BotToken = cfg.BotToken
|
|
}
|
|
if err := models.UpdateTelegramConfig(&m); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
// DeleteTelegramConfig elimina una configuración.
|
|
func DeleteTelegramConfig(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
if err := models.DeleteTelegramConfig(uint(id)); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
// TestTelegramConfig envía un mensaje de prueba al bot configurado.
|
|
func TestTelegramConfig(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
cfg, err := models.GetTelegramConfigByID(uint(id))
|
|
if err != nil {
|
|
return c.Status(404).JSON(fiber.Map{"error": "Configuración no encontrada"})
|
|
}
|
|
msg := "✅ <b>Prueba de conexión</b>\nEsta es una notificación de prueba desde <b>u-site admin</b>."
|
|
sendErr := sendTelegramMessage(cfg.BotToken, cfg.ChatID, msg)
|
|
logEntry := &models.TelegramLog{
|
|
TelegramConfigID: cfg.ID,
|
|
Titulo: "Prueba manual",
|
|
Mensaje: msg,
|
|
Estado: "ok",
|
|
}
|
|
if sendErr != nil {
|
|
logEntry.Estado = "failed"
|
|
logEntry.ErrorMsg = sendErr.Error()
|
|
_ = models.CreateTelegramLog(logEntry)
|
|
return c.Status(422).JSON(fiber.Map{"ok": false, "error": sendErr.Error()})
|
|
}
|
|
_ = models.CreateTelegramLog(logEntry)
|
|
return c.JSON(fiber.Map{"ok": true, "message": "Mensaje enviado"})
|
|
}
|
|
|
|
// SendTelegramNotification envía un mensaje personalizado a una o varias configs.
|
|
// Body: { "config_ids": [1,2], "titulo": "...", "mensaje": "..." }
|
|
func SendTelegramNotification(c *fiber.Ctx) error {
|
|
type Req struct {
|
|
ConfigIDs []uint `json:"config_ids"`
|
|
Titulo string `json:"titulo"`
|
|
Mensaje string `json:"mensaje"`
|
|
}
|
|
var req Req
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
if strings.TrimSpace(req.Mensaje) == "" {
|
|
return c.Status(400).JSON(fiber.Map{"error": "El mensaje no puede estar vacío"})
|
|
}
|
|
if len(req.ConfigIDs) == 0 {
|
|
return c.Status(400).JSON(fiber.Map{"error": "Selecciona al menos un destino"})
|
|
}
|
|
|
|
text := req.Mensaje
|
|
if req.Titulo != "" {
|
|
text = fmt.Sprintf("<b>%s</b>\n\n%s", req.Titulo, req.Mensaje)
|
|
}
|
|
|
|
var enviados, fallidos int
|
|
for _, cid := range req.ConfigIDs {
|
|
cfg, err := models.GetTelegramConfigByID(cid)
|
|
if err != nil || !cfg.Activo {
|
|
fallidos++
|
|
continue
|
|
}
|
|
sendErr := sendTelegramMessage(cfg.BotToken, cfg.ChatID, text)
|
|
logEntry := &models.TelegramLog{
|
|
TelegramConfigID: cfg.ID,
|
|
Titulo: req.Titulo,
|
|
Mensaje: req.Mensaje,
|
|
Estado: "ok",
|
|
}
|
|
if sendErr != nil {
|
|
logEntry.Estado = "failed"
|
|
logEntry.ErrorMsg = sendErr.Error()
|
|
fallidos++
|
|
} else {
|
|
enviados++
|
|
}
|
|
_ = models.CreateTelegramLog(logEntry)
|
|
}
|
|
return c.JSON(fiber.Map{"ok": fallidos == 0, "enviados": enviados, "fallidos": fallidos})
|
|
}
|
|
|
|
// GetTelegramLogs devuelve el historial paginado de mensajes enviados.
|
|
func GetTelegramLogs(c *fiber.Ctx) error {
|
|
page, _ := strconv.Atoi(c.Query("page", "1"))
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
limit := 30
|
|
offset := (page - 1) * limit
|
|
items, total, err := models.GetTelegramLogs(limit, offset)
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{
|
|
"items": items,
|
|
"total": total,
|
|
"totalPages": int(math.Ceil(float64(total) / float64(limit))),
|
|
"page": page,
|
|
})
|
|
}
|
|
|
|
// ─── helper interno ───────────────────────────────────────────────────────────
|
|
|
|
func sendTelegramMessage(botToken, chatID, text string) error {
|
|
if botToken == "" {
|
|
return fmt.Errorf("bot_token vacío")
|
|
}
|
|
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", botToken)
|
|
payload := map[string]interface{}{
|
|
"chat_id": chatID,
|
|
"text": text,
|
|
"parse_mode": "HTML",
|
|
}
|
|
body, _ := json.Marshal(payload)
|
|
resp, err := http.Post(apiURL, "application/json", bytes.NewReader(body)) //nolint:noctx
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("Telegram respondió %d", resp.StatusCode)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ─── Webhook portal (set + info) ─────────────────────────────────────────────
|
|
|
|
// SetPortalWebhook registra el webhook del portal en la API de Telegram para el bot indicado.
|
|
// POST /app/telegram/:id/set-portal-webhook
|
|
func SetPortalWebhook(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
cfg, err := models.GetTelegramConfigByID(uint(id))
|
|
if err != nil {
|
|
return c.Status(404).JSON(fiber.Map{"error": "Configuración no encontrada"})
|
|
}
|
|
|
|
// Construir URL base desde la configuración de la app
|
|
baseURL := app.Http.Server.Url
|
|
if len(baseURL) > 0 && baseURL[len(baseURL)-1] == '/' {
|
|
baseURL = baseURL[:len(baseURL)-1]
|
|
}
|
|
webhookURL := baseURL + "/webhooks/telegram-portal"
|
|
|
|
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/setWebhook", cfg.BotToken)
|
|
payload, _ := json.Marshal(map[string]string{"url": webhookURL})
|
|
resp, err := http.Post(apiURL, "application/json", bytes.NewReader(payload)) //nolint:noctx
|
|
if err != nil {
|
|
return c.Status(502).JSON(fiber.Map{"ok": false, "error": err.Error()})
|
|
}
|
|
defer resp.Body.Close()
|
|
raw, _ := io.ReadAll(resp.Body)
|
|
|
|
var tgResp map[string]interface{}
|
|
_ = json.Unmarshal(raw, &tgResp)
|
|
log.Printf("[SetPortalWebhook] bot=%d url=%s resp=%s", id, webhookURL, string(raw))
|
|
return c.JSON(fiber.Map{"ok": tgResp["ok"], "webhook_url": webhookURL, "telegram": tgResp})
|
|
}
|
|
|
|
// GetPortalWebhookInfo consulta el estado actual del webhook en Telegram.
|
|
// GET /app/telegram/:id/webhook-info
|
|
func GetPortalWebhookInfo(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
cfg, err := models.GetTelegramConfigByID(uint(id))
|
|
if err != nil {
|
|
return c.Status(404).JSON(fiber.Map{"error": "Configuración no encontrada"})
|
|
}
|
|
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/getWebhookInfo", cfg.BotToken)
|
|
resp, err := http.Get(apiURL) //nolint:noctx
|
|
if err != nil {
|
|
return c.Status(502).JSON(fiber.Map{"ok": false, "error": err.Error()})
|
|
}
|
|
defer resp.Body.Close()
|
|
var result map[string]interface{}
|
|
_ = json.NewDecoder(resp.Body).Decode(&result)
|
|
return c.JSON(result)
|
|
}
|