money/messages.go

133 lines
3.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package main
import (
"fmt"
"os"
"strings"
"time"
)
// MessageBundle è lUNICA struct che viaggia fino a Matrix.
// Metti qui TUTTO ciò che serve per il messaggio finale.
type MessageBundle struct {
// righe pronte (se vuote, Compose() le omette)
DecisionLine string // es. "consiglio: azione=..., motivo=..."
Instructions string // es. "ISTRUZIONI MANUALI: esegui swap ..."
// scadenza del quote (arriva dalla stessa chiamata THOR che dà fee/expected)
QuoteValidUntil time.Time
// opzionali (se vuote, Compose() le genera in base a QuoteValidUntil)
ValidityLine string // "Suggerimento valido sino a: <data>"
ExpiryNote string // "La quotazione dei costi è valida sino a <data> (≈ ... restanti) ..."
}
// Compose costruisce il testo da inviare su Matrix.
// Garanzie:
// - La riga "Suggerimento valido sino a: ..." è SEMPRE presente.
// - Se QuoteValidUntil è zero, usa fallback now+24h nella TZ utente.
func (mb MessageBundle) Compose() string {
var b strings.Builder
appendLn := func(s string) {
s = strings.TrimSpace(s)
if s == "" {
return
}
if b.Len() > 0 {
b.WriteByte('\n')
}
b.WriteString(s)
}
// 1) righe “grezze” (se presenti)
appendLn(mb.DecisionLine)
appendLn(mb.Instructions)
// 2) calcolo scadenza effettiva + riga "Suggerimento valido sino a: ..."
vu := mb.QuoteValidUntil
if vu.IsZero() {
vu = time.Now().UTC().Add(24 * time.Hour)
}
vline := strings.TrimSpace(mb.ValidityLine)
if vline == "" {
vline = buildValidityLine(vu)
}
appendLn(vline)
// 3) nota esplicativa con tempo residuo (se non fornita)
note := strings.TrimSpace(mb.ExpiryNote)
if note == "" {
note = buildExpiryNote(vu)
}
appendLn(note)
return b.String()
}
// ====== Formattazione scadenza/durate centralizzata qui ======
func buildValidityLine(validUntil time.Time) string {
loc := userLocation()
return "Suggerimento valido sino a: " + validUntil.In(loc).Format("2006-01-02 15:04:05 MST")
}
func buildExpiryNote(validUntil time.Time) string {
if validUntil.IsZero() {
return ""
}
loc := userLocation()
now := time.Now().In(loc)
local := validUntil.In(loc)
remaining := local.Sub(now)
if remaining <= 0 {
return fmt.Sprintf("ATTENZIONE: la quotazione dei costi è scaduta il %s. Dopo questa data, i costi potrebbero cambiare.",
local.Format("2006-01-02 15:04:05 MST"))
}
return fmt.Sprintf("La quotazione dei costi è valida sino a %s (≈ %s restanti). Dopo questa data, i costi potrebbero cambiare.",
local.Format("2006-01-02 15:04:05 MST"), humanDuration(remaining))
}
func humanDuration(d time.Duration) string {
if d < 0 {
d = -d
}
sec := int64(d.Seconds())
if sec < 60 {
return fmt.Sprintf("%ds", sec)
}
min := sec / 60
if min < 60 {
rem := sec % 60
if rem == 0 {
return fmt.Sprintf("%dm", min)
}
return fmt.Sprintf("%dm %ds", min, rem)
}
h := min / 60
m := min % 60
if h < 24 {
if m == 0 {
return fmt.Sprintf("%dh", h)
}
return fmt.Sprintf("%dh %dm", h, m)
}
days := h / 24
hh := h % 24
if hh == 0 && m == 0 {
return fmt.Sprintf("%dd", days)
}
if m == 0 {
return fmt.Sprintf("%dd %dh", days, hh)
}
return fmt.Sprintf("%dd %dh %dm", days, hh, m)
}
func userLocation() *time.Location {
if tz := strings.TrimSpace(os.Getenv("TZ")); tz != "" {
if loc, err := time.LoadLocation(tz); err == nil {
return loc
}
}
return time.Local
}