first commit

This commit is contained in:
2026-03-17 20:20:23 +01:00
commit 354c7a9d99
32 changed files with 3253 additions and 0 deletions

89
internal/spot/parser.go Normal file
View 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
}