package services import ( "bytes" "encoding/base64" "encoding/json" "fmt" "io" "net/http" "net/url" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) // EnviarCorreoGoogle manda un correo de texto plano vía Gmail API en nombre // de la cuenta conectada. Asume que conexion ya pasó por RefrescarSiVence. func EnviarCorreoGoogle(conexion *models.UmindConexion, destinatario, asunto, cuerpo string) error { accessToken, err := DescifrarSecretoUmind(conexion.AccessTokenEnc) if err != nil { return fmt.Errorf("no se pudo descifrar el access token: %w", err) } mime := fmt.Sprintf("To: %s\r\nSubject: %s\r\nContent-Type: text/plain; charset=\"UTF-8\"\r\n\r\n%s", destinatario, asunto, cuerpo) raw := base64.RawURLEncoding.EncodeToString([]byte(mime)) body, _ := json.Marshal(map[string]string{"raw": raw}) req, err := http.NewRequest(http.MethodPost, "https://gmail.googleapis.com/gmail/v1/users/me/messages/send", bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+accessToken) req.Header.Set("Content-Type", "application/json") resp, err := umindOAuthHTTPClient.Do(req) if err != nil { return fmt.Errorf("no se pudo contactar Gmail: %w", err) } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { detalle, _ := io.ReadAll(io.LimitReader(resp.Body, 2048)) return fmt.Errorf("Gmail respondió %d: %s", resp.StatusCode, string(detalle)) } return nil } // LeerBandejaGoogle busca mensajes en Gmail (sintaxis de búsqueda de Gmail, // ej: "from:cliente@ejemplo.com") y devuelve un resumen liviano de cada uno. func LeerBandejaGoogle(conexion *models.UmindConexion, consulta string, limite int) ([]CorreoResumen, error) { if limite <= 0 || limite > 10 { limite = 10 } accessToken, err := DescifrarSecretoUmind(conexion.AccessTokenEnc) if err != nil { return nil, fmt.Errorf("no se pudo descifrar el access token: %w", err) } listURL := fmt.Sprintf("https://gmail.googleapis.com/gmail/v1/users/me/messages?q=%s&maxResults=%d", url.QueryEscape(consulta), limite) var lista struct { Messages []struct { ID string `json:"id"` } `json:"messages"` } if err := gmailGetJSON(listURL, accessToken, &lista); err != nil { return nil, err } resultados := make([]CorreoResumen, 0, len(lista.Messages)) for _, m := range lista.Messages { detalleURL := fmt.Sprintf("https://gmail.googleapis.com/gmail/v1/users/me/messages/%s?format=metadata&metadataHeaders=From&metadataHeaders=Subject&metadataHeaders=Date", m.ID) var msg struct { Snippet string `json:"snippet"` Payload struct { Headers []struct { Name string `json:"name"` Value string `json:"value"` } `json:"headers"` } `json:"payload"` } if err := gmailGetJSON(detalleURL, accessToken, &msg); err != nil { continue // un mensaje individual que falla no debe tirar abajo toda la búsqueda } r := CorreoResumen{Extracto: msg.Snippet} for _, h := range msg.Payload.Headers { switch h.Name { case "From": r.De = h.Value case "Subject": r.Asunto = h.Value case "Date": r.Fecha = h.Value } } resultados = append(resultados, r) } return resultados, nil } func gmailGetJSON(url, accessToken string, out interface{}) error { req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+accessToken) resp, err := umindOAuthHTTPClient.Do(req) if err != nil { return fmt.Errorf("no se pudo contactar Gmail: %w", err) } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { detalle, _ := io.ReadAll(io.LimitReader(resp.Body, 2048)) return fmt.Errorf("Gmail respondió %d: %s", resp.StatusCode, string(detalle)) } return json.NewDecoder(resp.Body).Decode(out) }