first commit
This commit is contained in:
89
internal/spot/parser.go
Normal file
89
internal/spot/parser.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package spot
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Regex pour les deux formats de spots cluster
|
||||
var (
|
||||
// Format standard : DX de SPOTTER: FREQ DX MODE COMMENT TIME
|
||||
SpotRe = regexp.MustCompile(`(?i)DX\sde\s([\w\d\/]+?)(?:-[#\d-]+)?\s*:\s*(\d+\.\d+)\s+([\w\d\/]+)\s+(?:(CW|SSB|FT8|FT4|RTTY|USB|LSB|FM)\s+)?(.+?)\s+(\d{4}Z)`)
|
||||
|
||||
// Format court : FREQ DX DATE TIME COMMENT <SPOTTER>
|
||||
SpotReShort = regexp.MustCompile(`^(\d+\.\d+)\s+([\w\d\/]+)\s+\d{2}-\w{3}-\d{4}\s+(\d{4}Z)\s+(.+?)\s*<([\w\d\/]+)>\s*$`)
|
||||
|
||||
// Détection rapide du format court
|
||||
ShortSpotDetectRe = regexp.MustCompile(`^\d+\.\d+\s+[\w\d\/]+\s+\d{2}-\w{3}-\d{4}`)
|
||||
)
|
||||
|
||||
// ParseResult contient le spot parsé et une éventuelle erreur
|
||||
type ParseResult struct {
|
||||
Spot *Spot
|
||||
Err error
|
||||
Skipped bool // true si la ligne n'est pas un spot (pas une erreur)
|
||||
}
|
||||
|
||||
// ParseLine tente de parser une ligne brute du cluster en Spot
|
||||
// Retourne nil si la ligne n'est pas un spot DX
|
||||
func ParseLine(line string, clusterName string) *Spot {
|
||||
// Détecter si c'est un spot
|
||||
isSpot := strings.Contains(line, "DX de ") || ShortSpotDetectRe.MatchString(line)
|
||||
if !isSpot {
|
||||
return nil
|
||||
}
|
||||
|
||||
match := SpotRe.FindStringSubmatch(line)
|
||||
if len(match) > 0 {
|
||||
return parseStandardFormat(match, clusterName)
|
||||
}
|
||||
|
||||
match = SpotReShort.FindStringSubmatch(line)
|
||||
if len(match) > 0 {
|
||||
return parseShortFormat(match, clusterName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseStandardFormat(match []string, clusterName string) *Spot {
|
||||
freqKHz := parseFreq(match[2])
|
||||
return &Spot{
|
||||
Spotter: match[1],
|
||||
FrequencyKHz: freqKHz,
|
||||
DX: match[3],
|
||||
Mode: match[4],
|
||||
Comment: strings.TrimSpace(match[5]),
|
||||
Time: match[6],
|
||||
ClusterName: clusterName,
|
||||
Source: SourceCluster,
|
||||
}
|
||||
}
|
||||
|
||||
func parseShortFormat(match []string, clusterName string) *Spot {
|
||||
freqKHz := parseFreq(match[1])
|
||||
return &Spot{
|
||||
FrequencyKHz: freqKHz,
|
||||
DX: match[2],
|
||||
Time: match[3],
|
||||
Comment: strings.TrimSpace(match[4]),
|
||||
Spotter: match[5],
|
||||
ClusterName: clusterName,
|
||||
Source: SourceCluster,
|
||||
}
|
||||
}
|
||||
|
||||
// parseFreq parse une fréquence string en kHz float64
|
||||
// Gère kHz (>1000) et MHz (<1000) automatiquement
|
||||
func parseFreq(s string) float64 {
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
// Si < 1000 c'est en MHz, on convertit en kHz
|
||||
if f < 1000 {
|
||||
return f * 1000.0
|
||||
}
|
||||
return f
|
||||
}
|
||||
97
internal/spot/spot.go
Normal file
97
internal/spot/spot.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package spot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SpotSource identifie l'origine d'un spot
|
||||
type SpotSource int
|
||||
|
||||
const (
|
||||
SourceCluster SpotSource = iota // Spot reçu depuis un cluster DX telnet
|
||||
SourceManual // Spot ajouté manuellement depuis l'UI
|
||||
)
|
||||
|
||||
// Spot est la struct universelle — remplace TelnetSpot + FlexSpot
|
||||
// Plus de conversion entre les deux, tout passe par cette struct unique
|
||||
type Spot struct {
|
||||
// --- Persistance ---
|
||||
ID int64
|
||||
|
||||
// --- Identité ---
|
||||
DX string
|
||||
Spotter string
|
||||
|
||||
// --- Fréquence ---
|
||||
// FrequencyKHz est la source de vérité interne
|
||||
// FrequencyMHz est le format string attendu par FlexRadio (ex: "14.195000")
|
||||
FrequencyKHz float64
|
||||
FrequencyMHz string
|
||||
Band string
|
||||
|
||||
// --- Mode ---
|
||||
Mode string
|
||||
|
||||
// --- Métadonnées cluster ---
|
||||
Comment string
|
||||
Time string // Format "1234Z"
|
||||
Timestamp int64 // Unix timestamp
|
||||
ReceivedAt time.Time
|
||||
ClusterName string
|
||||
Source SpotSource
|
||||
|
||||
// --- DXCC ---
|
||||
DXCC string
|
||||
CountryName string
|
||||
|
||||
// --- Flags Log4OM (calculés depuis la DB Log4OM) ---
|
||||
NewDXCC bool
|
||||
NewBand bool
|
||||
NewMode bool
|
||||
NewSlot bool
|
||||
CallsignWorked bool
|
||||
|
||||
// --- Watchlist ---
|
||||
InWatchlist bool
|
||||
WatchlistNotify bool
|
||||
|
||||
// --- POTA / SOTA ---
|
||||
POTARef string
|
||||
SOTARef string
|
||||
ParkName string
|
||||
SummitName string
|
||||
|
||||
// --- FlexRadio panadapter ---
|
||||
FlexSpotNumber int
|
||||
CommandNumber int
|
||||
Color string
|
||||
BackgroundColor string
|
||||
Priority string
|
||||
LifeTime string
|
||||
OriginalComment string
|
||||
}
|
||||
|
||||
// FreqMHzString retourne la fréquence formatée pour FlexRadio
|
||||
// ex: 14195.0 kHz → "14.195000"
|
||||
func (s *Spot) FreqMHzString() string {
|
||||
if s.FrequencyMHz != "" {
|
||||
return s.FrequencyMHz
|
||||
}
|
||||
if s.FrequencyKHz > 1000 {
|
||||
return formatMHz(s.FrequencyKHz / 1000.0)
|
||||
}
|
||||
return formatMHz(s.FrequencyKHz)
|
||||
}
|
||||
|
||||
// FreqKHz retourne la fréquence en kHz depuis le champ disponible
|
||||
func (s *Spot) FreqKHz() float64 {
|
||||
if s.FrequencyKHz > 0 {
|
||||
return s.FrequencyKHz
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func formatMHz(mhz float64) string {
|
||||
return fmt.Sprintf("%.6f", mhz)
|
||||
}
|
||||
Reference in New Issue
Block a user