268 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			268 lines
		
	
	
		
			8.9 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.CountryName + "]"
 | 
						|
 | 
						|
	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 && Cfg.Gotify.WatchList {
 | 
						|
		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)
 | 
						|
}
 |