This commit is contained in:
Lizandro Guarnizo
2026-05-25 10:08:40 -05:00
parent b714fa71ac
commit 46daf3d4aa
6 changed files with 80 additions and 101 deletions
+42 -8
View File
@@ -822,6 +822,12 @@ func PortalTelegramValidar(c *fiber.Ctx) error {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "No autenticado"})
}
// Si ya está vinculado (por ejemplo, vía webhook), responder éxito de inmediato.
full, err := models.GetPortalUserByID(u.ID)
if err == nil && full != nil && strings.TrimSpace(full.TelegramChatID) != "" {
return c.JSON(fiber.Map{"ok": true, "chat_id": full.TelegramChatID})
}
// Recuperar el token vigente del usuario
tkn, err := models.GetTelegramPortalTokenByUser(u.ID)
if err != nil || tkn == nil {
@@ -829,11 +835,15 @@ func PortalTelegramValidar(c *fiber.Ctx) error {
}
configs, _ := models.GetAllTelegramConfigs()
hadWebhookConflict := false
for _, cfg := range configs {
if !cfg.Activo || cfg.BotToken == "" {
continue
}
chatID, found := searchTokenInUpdates(cfg.BotToken, tkn.Token)
chatID, found, reason := searchTokenInUpdates(cfg.BotToken, tkn.Token)
if reason == "webhook_conflict" {
hadWebhookConflict = true
}
if !found {
continue
}
@@ -848,22 +858,36 @@ func PortalTelegramValidar(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"ok": true, "chat_id": fmt.Sprintf("%d", chatID)})
}
// Revalidar por si el webhook hizo la vinculación mientras corría esta petición.
full, err = models.GetPortalUserByID(u.ID)
if err == nil && full != nil && strings.TrimSpace(full.TelegramChatID) != "" {
return c.JSON(fiber.Map{"ok": true, "chat_id": full.TelegramChatID})
}
if hadWebhookConflict {
return c.Status(200).JSON(fiber.Map{
"ok": false,
"error": "El bot está en modo webhook. Envía /vincular " + tkn.Token + " al bot y espera unos segundos; luego vuelve a verificar.",
})
}
return c.Status(200).JSON(fiber.Map{"ok": false, "error": "No se encontró el mensaje. Asegúrate de enviar /vincular " + tkn.Token + " al bot."})
}
// searchTokenInUpdates llama getUpdates y busca un mensaje que contenga el token.
// Retorna el chat_id y true si lo encuentra.
func searchTokenInUpdates(botToken, token string) (int64, bool) {
func searchTokenInUpdates(botToken, token string) (int64, bool, string) {
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/getUpdates?limit=100", botToken)
resp, err := http.Get(apiURL) //nolint:noctx
if err != nil {
log.Printf("[searchTokenInUpdates] error getUpdates: %v", err)
return 0, false
return 0, false, "network_error"
}
defer resp.Body.Close()
var result struct {
OK bool `json:"ok"`
OK bool `json:"ok"`
Description string `json:"description"`
Result []struct {
Message struct {
Chat struct {
@@ -873,8 +897,18 @@ func searchTokenInUpdates(botToken, token string) (int64, bool) {
} `json:"message"`
} `json:"result"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil || !result.OK {
return 0, false
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return 0, false, "decode_error"
}
if !result.OK {
desc := strings.ToLower(strings.TrimSpace(result.Description))
if strings.Contains(desc, "can't use getupdates method while webhook is active") {
return 0, false, "webhook_conflict"
}
if desc != "" {
log.Printf("[searchTokenInUpdates] Telegram API !ok: %s", result.Description)
}
return 0, false, "api_not_ok"
}
upperToken := strings.ToUpper(token)
@@ -882,8 +916,8 @@ func searchTokenInUpdates(botToken, token string) (int64, bool) {
txt := strings.ToUpper(strings.TrimSpace(upd.Message.Text))
if strings.Contains(txt, upperToken) && upd.Message.Chat.ID != 0 {
log.Printf("[searchTokenInUpdates] token=%s chatID=%d", token, upd.Message.Chat.ID)
return upd.Message.Chat.ID, true
return upd.Message.Chat.ID, true, ""
}
}
return 0, false
return 0, false, ""
}
+1 -17
View File
@@ -4,7 +4,6 @@ import (
"fmt"
"math"
"mime/multipart"
"net/url"
"os"
"path/filepath"
"strconv"
@@ -492,25 +491,10 @@ func DownloadDocumento(c *fiber.Ctx) error {
if !strings.HasPrefix(clean, "uploads/") {
return c.Status(403).JSON(fiber.Map{"error": "Acceso denegado"})
}
nombre := item.OriginalName
if nombre == "" {
nombre = item.Nombre
}
c.Set("Content-Disposition", attachmentDisposition(nombre))
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, item.OriginalName))
return c.SendFile(clean)
}
// attachmentDisposition genera un header Content-Disposition con fallback ASCII y encoding RFC 5987.
func attachmentDisposition(name string) string {
safe := strings.Map(func(r rune) rune {
if r > 127 || r == '"' || r == '\\' || r == '/' || r == '\n' || r == '\r' {
return '_'
}
return r
}, name)
return fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, safe, url.PathEscape(name))
}
// ─── Tickets (admin) ──────────────────────────────────────────────────────────
func GetTickets(c *fiber.Ctx) error {