146 lines
4.6 KiB
Go
Executable File
146 lines
4.6 KiB
Go
Executable File
package services
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// ContactData estructura para la vCard extendida
|
|
type ContactDataVcf struct {
|
|
FirstName string `json:"first_name"`
|
|
LastName string `json:"last_name"`
|
|
Nickname string `json:"nickname"`
|
|
Phone string `json:"phone"`
|
|
Email string `json:"email"`
|
|
Org string `json:"org"`
|
|
Title string `json:"title"`
|
|
Role string `json:"role"`
|
|
Website string `json:"website"`
|
|
PhotoURL string `json:"photo_url"`
|
|
Address string `json:"address"` // Ejemplo: ";;Calle 123;Ciudad;Estado;12345;País"
|
|
Birthday string `json:"birthday"` // Formato: "1990-01-01"
|
|
Gender string `json:"gender"` // "M", "F", "O", etc.
|
|
UID string `json:"uid"` // UUID o identificador único
|
|
Categories string `json:"categories"` // Ejemplo: "Amigos,Trabajo"
|
|
Note string `json:"note"`
|
|
IMPP string `json:"impp"` // Ejemplo: "aim:usuario"
|
|
Timezone string `json:"timezone"` // Ejemplo: "-0500"
|
|
Language string `json:"language"` // Ejemplo: "es"
|
|
ColorHex string `json:"color"`
|
|
BgColor string `json:"bg_color"`
|
|
Instagram string `json:"instagram"`
|
|
Facebook string `json:"facebook"`
|
|
TikTok string `json:"tiktok"`
|
|
LinkedIn string `json:"linkedin"`
|
|
VcardID string `json:"vcard_id"`
|
|
}
|
|
|
|
// GenerateVCardVcf genera el contenido de una vCard extendida como archivo .vcf (sin QR)
|
|
func GenerateVCardVcf(data ContactDataVcf) ([]byte, error) {
|
|
now := time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
|
|
|
var vcardBuilder strings.Builder
|
|
vcardBuilder.WriteString("BEGIN:VCARD\n")
|
|
vcardBuilder.WriteString("VERSION:3.0\n")
|
|
|
|
vcardBuilder.WriteString(fmt.Sprintf("N:%s;%s;;;\n", data.LastName, data.FirstName))
|
|
vcardBuilder.WriteString(fmt.Sprintf("FN:%s %s\n", data.FirstName, data.LastName))
|
|
|
|
if data.Nickname != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("NICKNAME:%s\n", data.Nickname))
|
|
}
|
|
if data.Org != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("ORG:%s\n", data.Org))
|
|
}
|
|
if data.Title != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("TITLE:%s\n", data.Title))
|
|
}
|
|
if data.Role != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("ROLE:%s\n", data.Role))
|
|
}
|
|
if data.Phone != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("TEL;TYPE=CELL:%s\n", data.Phone))
|
|
}
|
|
if data.Email != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("EMAIL:%s\n", data.Email))
|
|
}
|
|
if data.Website != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("URL:%s\n", data.Website))
|
|
}
|
|
if data.PhotoURL != "" {
|
|
err := agregarFotoWebp(&vcardBuilder, data.PhotoURL)
|
|
if err != nil {
|
|
log.Println("Error agregando la foto:", err)
|
|
}
|
|
}
|
|
|
|
if data.Address != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("ADR:%s\n", data.Address))
|
|
}
|
|
if data.Birthday != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("BDAY:%s\n", data.Birthday))
|
|
}
|
|
if data.Gender != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("GENDER:%s\n", data.Gender))
|
|
}
|
|
if data.UID != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("UID:%s\n", data.UID))
|
|
}
|
|
if data.Note != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("NOTE:%s\n", data.Note))
|
|
}
|
|
if data.Categories != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("CATEGORIES:%s\n", data.Categories))
|
|
}
|
|
if data.IMPP != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("IMPP:%s\n", data.IMPP))
|
|
}
|
|
if data.Timezone != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("TZ:%s\n", data.Timezone))
|
|
}
|
|
if data.Language != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("LANG:%s\n", data.Language))
|
|
}
|
|
if data.Instagram != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("X-INSTAGRAM:%s\n", data.Instagram))
|
|
}
|
|
if data.Facebook != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("X-FACEBOOK:%s\n", data.Facebook))
|
|
}
|
|
if data.TikTok != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("X-TIKTOK:%s\n", data.TikTok))
|
|
}
|
|
if data.LinkedIn != "" {
|
|
vcardBuilder.WriteString(fmt.Sprintf("X-LINKEDIN:%s\n", data.LinkedIn))
|
|
}
|
|
|
|
vcardBuilder.WriteString(fmt.Sprintf("REV:%s\n", now))
|
|
vcardBuilder.WriteString("END:VCARD")
|
|
|
|
return []byte(vcardBuilder.String()), nil
|
|
}
|
|
|
|
func agregarFotoWebp(vcardBuilder *strings.Builder, photoURL string) error {
|
|
resp, err := http.Get(photoURL)
|
|
if err != nil {
|
|
return fmt.Errorf("error descargando imagen: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
imageBytes, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("error leyendo imagen: %w", err)
|
|
}
|
|
|
|
base64Image := base64.StdEncoding.EncodeToString(imageBytes)
|
|
|
|
// Opcional: insertar saltos de línea cada 75 caracteres si quieres seguir la RFC, pero no es obligatorio
|
|
vcardBuilder.WriteString(fmt.Sprintf("PHOTO;ENCODING=b;TYPE=WEBP:%s\n", base64Image))
|
|
return nil
|
|
}
|