Files
FlexDXClusterGui/spotprocessor.go
2025-10-30 23:18:16 +01:00

268 lines
8.8 KiB
Go

package main
import (
"context"
"fmt"
"strings"
"time"
)
type SpotProcessor struct {
FlexRepo *FlexDXClusterRepository
FlexClient *FlexClient
HTTPServer *HTTPServer
SpotChan chan TelnetSpot
ctx context.Context
cancel context.CancelFunc
}
func NewSpotProcessor(flexRepo *FlexDXClusterRepository, flexClient *FlexClient, httpServer *HTTPServer, spotChan chan TelnetSpot) *SpotProcessor {
ctx, cancel := context.WithCancel(context.Background())
return &SpotProcessor{
FlexRepo: flexRepo,
FlexClient: flexClient,
HTTPServer: httpServer,
SpotChan: spotChan,
ctx: ctx,
cancel: cancel,
}
}
func (sp *SpotProcessor) Start() {
Log.Info("Starting Spot Processor...")
for {
select {
case <-sp.ctx.Done():
Log.Info("Spot Processor shutting down...")
return
case spot := <-sp.SpotChan:
sp.processSpot(spot)
}
}
}
func (sp *SpotProcessor) Stop() {
Log.Info("Stopping Spot Processor...")
sp.cancel()
}
func (sp *SpotProcessor) processSpot(spot TelnetSpot) {
freq := FreqMhztoHz(spot.Frequency)
flexSpot := FlexSpot{
CommandNumber: CommandNumber,
DX: spot.DX,
FrequencyMhz: freq,
FrequencyHz: spot.Frequency,
Band: spot.Band,
Mode: spot.Mode,
Source: "FlexDXCluster",
SpotterCallsign: spot.Spotter,
TimeStamp: time.Now().Unix(),
UTCTime: spot.Time,
LifeTime: Cfg.Flex.SpotLife,
OriginalComment: spot.Comment,
Comment: spot.Comment,
Color: "#ffeaeaea",
BackgroundColor: "#ff000000",
Priority: "5",
NewDXCC: spot.NewDXCC,
NewBand: spot.NewBand,
NewMode: spot.NewMode,
NewSlot: spot.NewSlot,
Worked: spot.CallsignWorked,
InWatchlist: false,
CountryName: spot.CountryName,
DXCC: spot.DXCC,
}
flexSpot.OriginalComment = spot.Comment
flexSpot.Comment = flexSpot.Comment + " [" + flexSpot.Mode + "] [" + flexSpot.SpotterCallsign + "] [" + flexSpot.UTCTime + "]"
if sp.HTTPServer != nil && sp.HTTPServer.Watchlist != nil {
if sp.HTTPServer.Watchlist.Matches(flexSpot.DX) {
flexSpot.InWatchlist = true
// Mark as seen and update last seen time
sp.HTTPServer.Watchlist.MarkSeen(flexSpot.DX)
// Get entry to check if sound should be played
entry := sp.HTTPServer.Watchlist.GetEntry(flexSpot.DX)
if entry != nil {
Log.Infof("🎯 Watchlist match: %s (LastSeen: %s)",
flexSpot.DX, entry.LastSeenStr)
// Send notification to websocket clients for sound alert
if entry.PlaySound && sp.HTTPServer != nil {
sp.HTTPServer.broadcast <- WSMessage{
Type: "watchlistAlert",
Data: map[string]interface{}{
"callsign": flexSpot.DX,
"frequency": flexSpot.FrequencyMhz,
"band": flexSpot.Band,
"mode": flexSpot.Mode,
"countryName": flexSpot.CountryName,
"playSound": entry.PlaySound,
},
}
}
}
}
}
sp.applySpotColors(&flexSpot, spot)
sp.sendGotifyNotification(flexSpot)
flexSpot.Comment = strings.ReplaceAll(flexSpot.Comment, " ", "\u00A0")
srcFlexSpot, err := sp.FlexRepo.FindDXSameBand(flexSpot)
if err != nil {
Log.Debugf("Could not find the DX in the database: %v", err)
}
// Vérifier si le spot trouvé est valide (a un ID)
if srcFlexSpot != nil && srcFlexSpot.ID == 0 {
srcFlexSpot = nil
}
sp.handleSpotStorage(flexSpot, srcFlexSpot)
if sp.FlexClient != nil && sp.FlexClient.Enabled && sp.FlexClient.IsConnected {
sp.sendToFlexRadio(flexSpot, srcFlexSpot)
}
}
func (sp *SpotProcessor) applySpotColors(flexSpot *FlexSpot, spot TelnetSpot) {
if spot.NewDXCC {
flexSpot.Priority = "1"
flexSpot.Comment = flexSpot.Comment + " [New DXCC]"
if Cfg.General.SpotColorNewEntity != "" {
flexSpot.Color = Cfg.General.SpotColorNewEntity
flexSpot.BackgroundColor = Cfg.General.BackgroundColorNewEntity
} else {
flexSpot.Color = "#ff3bf908"
flexSpot.BackgroundColor = "#ff000000"
}
} else if spot.DX == Cfg.General.Callsign {
flexSpot.Priority = "1"
if Cfg.General.SpotColorMyCallsign != "" {
flexSpot.Color = Cfg.General.SpotColorMyCallsign
flexSpot.BackgroundColor = Cfg.General.BackgroundColorMyCallsign
} else {
flexSpot.Color = "#ffff0000"
flexSpot.BackgroundColor = "#ff000000"
}
} else if spot.CallsignWorked {
flexSpot.Priority = "5"
flexSpot.Comment = flexSpot.Comment + " [Worked]"
if Cfg.General.SpotColorWorked != "" {
flexSpot.Color = Cfg.General.SpotColorWorked
flexSpot.BackgroundColor = Cfg.General.BackgroundColorWorked
} else {
flexSpot.Color = "#ff000000"
flexSpot.BackgroundColor = "#ff00c0c0"
}
} else if spot.NewMode && spot.NewBand {
flexSpot.Priority = "1"
flexSpot.Comment = flexSpot.Comment + " [New Band & Mode]"
if Cfg.General.SpotColorNewBandMode != "" {
flexSpot.Color = Cfg.General.SpotColorNewBandMode
flexSpot.BackgroundColor = Cfg.General.BackgroundColorNewBandMode
} else {
flexSpot.Color = "#ffc603fc"
flexSpot.BackgroundColor = "#ff000000"
}
} else if spot.NewMode && !spot.NewBand {
flexSpot.Priority = "2"
flexSpot.Comment = flexSpot.Comment + " [New Mode]"
if Cfg.General.SpotColorNewMode != "" {
flexSpot.Color = Cfg.General.SpotColorNewMode
flexSpot.BackgroundColor = Cfg.General.BackgroundColorNewMode
} else {
flexSpot.Color = "#fff9a908"
flexSpot.BackgroundColor = "#ff000000"
}
} else if spot.NewBand && !spot.NewMode {
flexSpot.Color = "#fff9f508"
flexSpot.Priority = "3"
flexSpot.BackgroundColor = "#ff000000"
flexSpot.Comment = flexSpot.Comment + " [New Band]"
} else if !spot.NewBand && !spot.NewMode && !spot.NewDXCC && !spot.CallsignWorked && spot.NewSlot {
flexSpot.Color = "#ff91d2ff"
flexSpot.Priority = "5"
flexSpot.BackgroundColor = "#ff000000"
flexSpot.Comment = flexSpot.Comment + " [New Slot]"
}
}
func (sp *SpotProcessor) handleSpotStorage(flexSpot FlexSpot, srcFlexSpot *FlexSpot) {
if srcFlexSpot == nil {
sp.FlexRepo.CreateSpot(flexSpot)
CommandNumber++
if sp.HTTPServer != nil {
sp.HTTPServer.broadcast <- WSMessage{Type: "spots", Data: sp.FlexRepo.GetAllSpots("0")}
}
} else if srcFlexSpot.Band == flexSpot.Band {
sp.FlexRepo.DeleteSpotByFlexSpotNumber(fmt.Sprintf("%d", srcFlexSpot.FlexSpotNumber))
sp.FlexRepo.CreateSpot(flexSpot)
CommandNumber++
} else {
sp.FlexRepo.CreateSpot(flexSpot)
CommandNumber++
}
}
func (sp *SpotProcessor) sendToFlexRadio(flexSpot FlexSpot, srcFlexSpot *FlexSpot) {
var stringSpot string
if srcFlexSpot == nil {
stringSpot = fmt.Sprintf("C%v|spot add rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s",
flexSpot.CommandNumber, flexSpot.FrequencyMhz, flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign,
flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, flexSpot.BackgroundColor, flexSpot.Priority)
CommandNumber++
sp.FlexClient.SendSpot(stringSpot)
} else if srcFlexSpot.Band == flexSpot.Band {
stringSpot = fmt.Sprintf("C%v|spot remove %v", flexSpot.CommandNumber, srcFlexSpot.FlexSpotNumber)
sp.FlexClient.SendSpot(stringSpot)
CommandNumber++
stringSpot = fmt.Sprintf("C%v|spot add rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s",
flexSpot.CommandNumber, flexSpot.FrequencyMhz, flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign,
flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, flexSpot.BackgroundColor, flexSpot.Priority)
CommandNumber++
sp.FlexClient.SendSpot(stringSpot)
} else {
stringSpot = fmt.Sprintf("C%v|spot add rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s",
flexSpot.CommandNumber, flexSpot.FrequencyMhz, flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign,
flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, flexSpot.BackgroundColor, flexSpot.Priority)
CommandNumber++
sp.FlexClient.SendSpot(stringSpot)
}
}
func (sp *SpotProcessor) sendGotifyNotification(flexSpot FlexSpot) {
if !Cfg.Gotify.Enable {
return
}
// Cas 1 : Nouveau DXCC - toujours notifier si activé dans la config
if flexSpot.NewDXCC && Cfg.Gotify.NewDXCC {
Gotify(flexSpot)
Log.Debugf("📢 Gotify notification sent: New DXCC - %s", flexSpot.DX)
return
}
// Cas 2 : Callsign dans la watchlist ET non contacté
if flexSpot.InWatchlist && !flexSpot.Worked {
Gotify(flexSpot)
Log.Debugf("📢 Gotify notification sent: Watchlist match (not worked) - %s", flexSpot.DX)
return
}
// Tous les autres cas : pas de notification
Log.Debugf("🔇 Gotify notification skipped for %s (InWatchlist=%v, Worked=%v, NewDXCC=%v)",
flexSpot.DX, flexSpot.InWatchlist, flexSpot.Worked, flexSpot.NewDXCC)
}