diff --git a/TCPClient.go b/TCPClient.go index 87e929f..9d3965d 100644 --- a/TCPClient.go +++ b/TCPClient.go @@ -107,6 +107,13 @@ func (c *TCPClient) setDefaultParams() { } } +func (c *TCPClient) ReloadFilters() { + if c.LoggedIn { + Log.Info("Reloading cluster filters...") + c.SetFilters() + } +} + func (c *TCPClient) calculateBackoff() time.Duration { // Formule: min(baseDelay * 2^attempts, maxDelay) delay := time.Duration(float64(c.baseReconnectDelay) * math.Pow(2, float64(c.reconnectAttempts))) diff --git a/config default.yml b/config default.yml index 5be988b..ba5f035 100644 --- a/config default.yml +++ b/config default.yml @@ -56,4 +56,5 @@ gotify: NewDXCC: true NewBand: false NewMode: false - NewBandAndMode: false \ No newline at end of file + NewBandAndMode: false + Watchlist: false \ No newline at end of file diff --git a/config.go b/config.go index 8c256ee..2dead93 100644 --- a/config.go +++ b/config.go @@ -2,9 +2,11 @@ package main import ( "fmt" - "log" "os" + "sync" + "github.com/fsnotify/fsnotify" + log "github.com/sirupsen/logrus" "gopkg.in/yaml.v2" ) @@ -81,9 +83,16 @@ type Config struct { NewBand bool `yaml:"NewBand"` NewMode bool `yaml:"NewMode"` NewBandAndMode bool `yaml:"NewBandAndMode"` + WatchList bool `yaml:"Watchlist"` } `yaml:"gotify"` } +type ConfigWatcher struct { + watcher *fsnotify.Watcher + configPath string + mu sync.RWMutex +} + func NewConfig(configPath string) *Config { Cfg = &Config{} @@ -111,3 +120,107 @@ func ValidateConfigPath(path string) error { } return nil } + +func NewConfigWatcher(configPath string) (*ConfigWatcher, error) { + watcher, err := fsnotify.NewWatcher() + if err != nil { + return nil, err + } + + return &ConfigWatcher{ + watcher: watcher, + configPath: configPath, + }, nil +} + +func (cw *ConfigWatcher) Start() error { + if err := cw.watcher.Add(cw.configPath); err != nil { + return err + } + + go func() { + for { + select { + case event, ok := <-cw.watcher.Events: + if !ok { + return + } + if event.Op&fsnotify.Write == fsnotify.Write { + Log.Info("Config file modified, reloading...") + cw.reloadConfig() + } + case err, ok := <-cw.watcher.Errors: + if !ok { + return + } + Log.Errorf("Config watcher error: %v", err) + } + } + }() + + return nil +} + +func (cw *ConfigWatcher) reloadConfig() { + cw.mu.Lock() + defer cw.mu.Unlock() + + newCfg := &Config{} + file, err := os.Open(cw.configPath) + if err != nil { + Log.Errorf("Could not reload config: %v", err) + return + } + defer file.Close() + + d := yaml.NewDecoder(file) + if err := d.Decode(newCfg); err != nil { + Log.Errorf("Could not decode reloaded config: %v", err) + return + } + + // Sauvegarder l'ancienne config + oldCfg := Cfg + + // Appliquer la nouvelle config + Cfg = newCfg + + // Vérifier les changements qui nécessitent des actions + cw.applyConfigChanges(oldCfg, newCfg) + + Log.Info("✅ Config reloaded successfully") +} + +func (cw *ConfigWatcher) applyConfigChanges(oldCfg, newCfg *Config) { + // Log level + if oldCfg.General.LogLevel != newCfg.General.LogLevel { + switch newCfg.General.LogLevel { + case "DEBUG": + Log.SetLevel(log.DebugLevel) + case "INFO": + Log.SetLevel(log.InfoLevel) + case "WARN": + Log.SetLevel(log.WarnLevel) + default: + Log.SetLevel(log.InfoLevel) + } + Log.Infof("Log level changed to %s", newCfg.General.LogLevel) + } + + // Gotify + if oldCfg.Gotify.Enable != newCfg.Gotify.Enable { + Log.Infof("Gotify notifications %s", map[bool]string{true: "enabled", false: "disabled"}[newCfg.Gotify.Enable]) + } + + if oldCfg.Cluster.FT8 != newCfg.Cluster.FT8 || + oldCfg.Cluster.FT4 != newCfg.Cluster.FT4 || + oldCfg.Cluster.Skimmer != newCfg.Cluster.Skimmer || + oldCfg.Cluster.Beacon != newCfg.Cluster.Beacon { + Log.Info("Cluster filters changed, applying") + httpServerInstance.TCPClient.ReloadFilters() + } +} + +func (cw *ConfigWatcher) Stop() { + cw.watcher.Close() +} diff --git a/go.mod b/go.mod index 9998e7b..5e3fd78 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect diff --git a/go.sum b/go.sum index 4bf286f..636a479 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= diff --git a/main.go b/main.go index d1ca388..d11dced 100644 --- a/main.go +++ b/main.go @@ -73,10 +73,21 @@ func main() { NewConfig(cfgPath) + configWatcher, err := NewConfigWatcher(cfgPath) + if err != nil { + log.Fatalf("Could not create config watcher: %v", err) + } + defer configWatcher.Stop() + + if err := configWatcher.Start(); err != nil { + log.Fatalf("Could not start config watcher: %v", err) + } + log := NewLog() defer CloseLog() log.Info("Running FlexDXCluster version 2.1") log.Infof("Callsign: %s", Cfg.General.Callsign) + log.Info("Config hot reload enabled") DeleteDatabase("./flex.sqlite", log) diff --git a/watchlist.json b/watchlist.json index ecd5fd9..0642aec 100644 --- a/watchlist.json +++ b/watchlist.json @@ -1,44 +1,36 @@ [ { - "callsign": "3B8M", + "callsign": "3W9A", "lastSeen": "0001-01-01T00:00:00Z", "lastSeenStr": "Never", - "addedAt": "2025-10-18T17:18:32.6851135+02:00", + "addedAt": "2025-10-23T20:42:24.5787678+02:00", "spotCount": 0, "playSound": true }, { - "callsign": "PJ6Y", - "lastSeen": "2025-10-28T14:01:51.7023253+01:00", - "lastSeenStr": "4 days ago", - "addedAt": "2025-10-18T17:17:47.7237081+02:00", - "spotCount": 1333, + "callsign": "9U1RU", + "lastSeen": "2025-11-02T11:35:54.7934565+01:00", + "lastSeenStr": "Just now", + "addedAt": "2025-10-28T22:43:38.4903514+01:00", + "spotCount": 540, "playSound": true }, { - "callsign": "DP0GVN", - "lastSeen": "2025-11-01T10:58:18.732114+01:00", - "lastSeenStr": "12 hours ago", - "addedAt": "2025-10-20T07:00:51.7088369+02:00", - "spotCount": 234, - "playSound": true + "callsign": "C5R", + "lastSeen": "2025-11-02T11:25:50.8967823+01:00", + "lastSeenStr": "9 minutes ago", + "addedAt": "2025-10-18T17:18:04.5006892+02:00", + "spotCount": 1851, + "playSound": false }, { - "callsign": "SU0ERA", + "callsign": "C8K", "lastSeen": "0001-01-01T00:00:00Z", "lastSeenStr": "Never", - "addedAt": "2025-10-18T17:18:45.8848244+02:00", + "addedAt": "2025-10-18T17:18:39.8627992+02:00", "spotCount": 0, "playSound": true }, - { - "callsign": "XT2AW", - "lastSeen": "2025-10-24T04:08:09.2640864+02:00", - "lastSeenStr": "8 days ago", - "addedAt": "2025-10-18T17:17:27.3839089+02:00", - "spotCount": 136, - "playSound": true - }, { "callsign": "5R8", "lastSeen": "0001-01-01T00:00:00Z", @@ -48,139 +40,19 @@ "playSound": true }, { - "callsign": "FW5K", - "lastSeen": "2025-10-31T06:58:37.7867666+01:00", - "lastSeenStr": "1 day ago", - "addedAt": "2025-10-18T17:17:37.9061157+02:00", - "spotCount": 339, - "playSound": true - }, - { - "callsign": "TZ4AM", - "lastSeen": "2025-11-01T22:50:24.2026748+01:00", - "lastSeenStr": "1 hour ago", - "addedAt": "2025-10-18T17:19:00.3154177+02:00", - "spotCount": 130, - "playSound": true - }, - { - "callsign": "V85NPV", - "lastSeen": "2025-11-01T15:55:54.2255972+01:00", - "lastSeenStr": "7 hours ago", - "addedAt": "2025-10-18T17:18:15.8781583+02:00", - "spotCount": 32, - "playSound": true - }, - { - "callsign": "9U1RU", - "lastSeen": "2025-11-02T08:24:11.0845498+01:00", - "lastSeenStr": "Just now", - "addedAt": "2025-10-28T22:43:38.4903514+01:00", - "spotCount": 459, - "playSound": true - }, - { - "callsign": "VP8LP", - "lastSeen": "2025-10-30T12:01:48.3242696+01:00", - "lastSeenStr": "2 days ago", - "addedAt": "2025-10-18T17:18:49.0576187+02:00", - "spotCount": 46, - "playSound": true - }, - { - "callsign": "7Q1A", + "callsign": "5X2I", "lastSeen": "0001-01-01T00:00:00Z", "lastSeenStr": "Never", - "addedAt": "2025-10-24T07:36:06.609998+02:00", + "addedAt": "2025-10-18T17:17:14.6598633+02:00", "spotCount": 0, "playSound": true }, - { - "callsign": "D2A", - "lastSeen": "2025-10-24T10:08:29.9662677+02:00", - "lastSeenStr": "8 days ago", - "addedAt": "2025-10-20T22:11:35.4767205+02:00", - "spotCount": 536, - "playSound": true - }, - { - "callsign": "9L9L", - "lastSeen": "0001-01-01T00:00:00Z", - "lastSeenStr": "Never", - "addedAt": "2025-10-18T17:18:53.3401773+02:00", - "spotCount": 0, - "playSound": true - }, - { - "callsign": "3W9A", - "lastSeen": "0001-01-01T00:00:00Z", - "lastSeenStr": "Never", - "addedAt": "2025-10-23T20:42:24.5787678+02:00", - "spotCount": 0, - "playSound": true - }, - { - "callsign": "ZC4RH", - "lastSeen": "0001-01-01T00:00:00Z", - "lastSeenStr": "Never", - "addedAt": "2025-10-28T22:43:16.3202825+01:00", - "spotCount": 0, - "playSound": true - }, - { - "callsign": "4X6TT", - "lastSeen": "2025-10-31T06:35:40.581257+01:00", - "lastSeenStr": "1 day ago", - "addedAt": "2025-10-18T17:18:13.335878+02:00", - "spotCount": 11, - "playSound": true - }, - { - "callsign": "XF4B", - "lastSeen": "2025-10-29T20:42:02.8584079+01:00", - "lastSeenStr": "3 days ago", - "addedAt": "2025-10-27T13:11:16.3404549+01:00", - "spotCount": 12, - "playSound": true - }, - { - "callsign": "5K0UA", - "lastSeen": "2025-11-02T08:27:01.5669215+01:00", - "lastSeenStr": "Just now", - "addedAt": "2025-10-18T17:17:53.7390559+02:00", - "spotCount": 2670, - "playSound": true - }, - { - "callsign": "C5R", - "lastSeen": "2025-11-02T08:27:33.178474+01:00", - "lastSeenStr": "Just now", - "addedAt": "2025-10-18T17:18:04.5006892+02:00", - "spotCount": 1826, - "playSound": false - }, - { - "callsign": "YJ0CA", - "lastSeen": "2025-10-23T09:14:00.5419174+02:00", - "lastSeenStr": "9 days ago", - "addedAt": "2025-10-18T17:17:33.3921665+02:00", - "spotCount": 1, - "playSound": true - }, - { - "callsign": "H44MS", - "lastSeen": "2025-11-01T08:09:36.8196241+01:00", - "lastSeenStr": "15 hours ago", - "addedAt": "2025-10-18T17:16:49.1572859+02:00", - "spotCount": 33, - "playSound": true - }, { "callsign": "9L8MD", - "lastSeen": "2025-11-02T08:27:41.3699778+01:00", + "lastSeen": "2025-11-02T11:35:38.2605474+01:00", "lastSeenStr": "Just now", "addedAt": "2025-10-18T17:18:56.7896868+02:00", - "spotCount": 1205, + "spotCount": 1307, "playSound": true }, { @@ -192,61 +64,37 @@ "playSound": true }, { - "callsign": "VP2M", - "lastSeen": "0001-01-01T00:00:00Z", - "lastSeenStr": "Never", - "addedAt": "2025-10-18T17:17:57.308717+02:00", - "spotCount": 0, - "playSound": false + "callsign": "4X6TT", + "lastSeen": "2025-10-31T06:35:40.581257+01:00", + "lastSeenStr": "2 days ago", + "addedAt": "2025-10-18T17:18:13.335878+02:00", + "spotCount": 11, + "playSound": true }, { - "callsign": "5H3MB", - "lastSeen": "2025-11-01T20:12:44.5661106+01:00", - "lastSeenStr": "3 hours ago", - "addedAt": "2025-10-18T17:18:42.8402097+02:00", - "spotCount": 56, + "callsign": "EL2BG", + "lastSeen": "2025-11-02T11:27:57.362105+01:00", + "lastSeenStr": "7 minutes ago", + "addedAt": "2025-10-18T17:18:10.2000017+02:00", + "spotCount": 94, + "playSound": true + }, + { + "callsign": "H44MS", + "lastSeen": "2025-11-01T08:09:36.8196241+01:00", + "lastSeenStr": "1 day ago", + "addedAt": "2025-10-18T17:16:49.1572859+02:00", + "spotCount": 33, "playSound": true }, { "callsign": "TJ1GD", "lastSeen": "2025-11-01T15:21:39.1331739+01:00", - "lastSeenStr": "8 hours ago", + "lastSeenStr": "20 hours ago", "addedAt": "2025-10-18T17:18:27.6004027+02:00", "spotCount": 338, "playSound": false }, - { - "callsign": "C5Y", - "lastSeen": "2025-10-30T17:00:04.8264529+01:00", - "lastSeenStr": "2 days ago", - "addedAt": "2025-10-27T19:34:57.6714115+01:00", - "spotCount": 175, - "playSound": true - }, - { - "callsign": "YI1MB", - "lastSeen": "2025-11-01T11:07:08.2392713+01:00", - "lastSeenStr": "12 hours ago", - "addedAt": "2025-10-18T17:18:18.825584+02:00", - "spotCount": 1, - "playSound": true - }, - { - "callsign": "ZL7IO", - "lastSeen": "0001-01-01T00:00:00Z", - "lastSeenStr": "Never", - "addedAt": "2025-10-18T17:17:30.7153757+02:00", - "spotCount": 0, - "playSound": true - }, - { - "callsign": "XV9", - "lastSeen": "0001-01-01T00:00:00Z", - "lastSeenStr": "Never", - "addedAt": "2025-10-18T17:18:24.9155327+02:00", - "spotCount": 0, - "playSound": false - }, { "callsign": "5X1XA", "lastSeen": "0001-01-01T00:00:00Z", @@ -255,30 +103,6 @@ "spotCount": 0, "playSound": true }, - { - "callsign": "EL2BG", - "lastSeen": "2025-11-02T08:11:59.0624133+01:00", - "lastSeenStr": "Just now", - "addedAt": "2025-10-18T17:18:10.2000017+02:00", - "spotCount": 87, - "playSound": true - }, - { - "callsign": "A52AA", - "lastSeen": "0001-01-01T00:00:00Z", - "lastSeenStr": "Never", - "addedAt": "2025-10-28T22:44:18.1202597+01:00", - "spotCount": 0, - "playSound": true - }, - { - "callsign": "PY0FB", - "lastSeen": "0001-01-01T00:00:00Z", - "lastSeenStr": "Never", - "addedAt": "2025-10-18T17:17:24.3843986+02:00", - "spotCount": 0, - "playSound": true - }, { "callsign": "5J0EA", "lastSeen": "0001-01-01T00:00:00Z", @@ -287,36 +111,164 @@ "spotCount": 0, "playSound": true }, + { + "callsign": "5H3MB", + "lastSeen": "2025-11-01T20:12:44.5661106+01:00", + "lastSeenStr": "15 hours ago", + "addedAt": "2025-10-18T17:18:42.8402097+02:00", + "spotCount": 56, + "playSound": true + }, + { + "callsign": "9L9L", + "lastSeen": "0001-01-01T00:00:00Z", + "lastSeenStr": "Never", + "addedAt": "2025-10-18T17:18:53.3401773+02:00", + "spotCount": 0, + "playSound": true + }, + { + "callsign": "ZC4RH", + "lastSeen": "0001-01-01T00:00:00Z", + "lastSeenStr": "Never", + "addedAt": "2025-10-28T22:43:16.3202825+01:00", + "spotCount": 0, + "playSound": true + }, + { + "callsign": "A52AA", + "lastSeen": "0001-01-01T00:00:00Z", + "lastSeenStr": "Never", + "addedAt": "2025-10-28T22:44:18.1202597+01:00", + "spotCount": 0, + "playSound": true + }, + { + "callsign": "3B8M", + "lastSeen": "0001-01-01T00:00:00Z", + "lastSeenStr": "Never", + "addedAt": "2025-10-18T17:18:32.6851135+02:00", + "spotCount": 0, + "playSound": true + }, + { + "callsign": "DP0GVN", + "lastSeen": "2025-11-01T10:58:18.732114+01:00", + "lastSeenStr": "1 day ago", + "addedAt": "2025-10-20T07:00:51.7088369+02:00", + "spotCount": 234, + "playSound": true + }, + { + "callsign": "TZ4AM", + "lastSeen": "2025-11-01T22:50:24.2026748+01:00", + "lastSeenStr": "12 hours ago", + "addedAt": "2025-10-18T17:19:00.3154177+02:00", + "spotCount": 130, + "playSound": true + }, + { + "callsign": "XV9", + "lastSeen": "0001-01-01T00:00:00Z", + "lastSeenStr": "Never", + "addedAt": "2025-10-18T17:18:24.9155327+02:00", + "spotCount": 0, + "playSound": false + }, + { + "callsign": "7Q1A", + "lastSeen": "0001-01-01T00:00:00Z", + "lastSeenStr": "Never", + "addedAt": "2025-10-24T07:36:06.609998+02:00", + "spotCount": 0, + "playSound": true + }, + { + "callsign": "ZL7IO", + "lastSeen": "0001-01-01T00:00:00Z", + "lastSeenStr": "Never", + "addedAt": "2025-10-18T17:17:30.7153757+02:00", + "spotCount": 0, + "playSound": true + }, + { + "callsign": "PJ6Y", + "lastSeen": "2025-10-28T14:01:51.7023253+01:00", + "lastSeenStr": "4 days ago", + "addedAt": "2025-10-18T17:17:47.7237081+02:00", + "spotCount": 1333, + "playSound": true + }, + { + "callsign": "XF4B", + "lastSeen": "2025-10-29T20:42:02.8584079+01:00", + "lastSeenStr": "3 days ago", + "addedAt": "2025-10-27T13:11:16.3404549+01:00", + "spotCount": 12, + "playSound": true + }, + { + "callsign": "YJ0CA", + "lastSeen": "2025-10-23T09:14:00.5419174+02:00", + "lastSeenStr": "10 days ago", + "addedAt": "2025-10-18T17:17:33.3921665+02:00", + "spotCount": 1, + "playSound": true + }, + { + "callsign": "SU0ERA", + "lastSeen": "0001-01-01T00:00:00Z", + "lastSeenStr": "Never", + "addedAt": "2025-10-18T17:18:45.8848244+02:00", + "spotCount": 0, + "playSound": true + }, { "callsign": "CP7DX", "lastSeen": "2025-11-02T05:20:53.7928254+01:00", - "lastSeenStr": "Just now", + "lastSeenStr": "6 hours ago", "addedAt": "2025-10-28T22:42:54.1867739+01:00", "spotCount": 64, "playSound": true }, { - "callsign": "5X2I", - "lastSeen": "0001-01-01T00:00:00Z", - "lastSeenStr": "Never", - "addedAt": "2025-10-18T17:17:14.6598633+02:00", - "spotCount": 0, + "callsign": "VP8LP", + "lastSeen": "2025-10-30T12:01:48.3242696+01:00", + "lastSeenStr": "2 days ago", + "addedAt": "2025-10-18T17:18:49.0576187+02:00", + "spotCount": 46, "playSound": true }, { - "callsign": "C8K", - "lastSeen": "0001-01-01T00:00:00Z", - "lastSeenStr": "Never", - "addedAt": "2025-10-18T17:18:39.8627992+02:00", - "spotCount": 0, + "callsign": "FW5K", + "lastSeen": "2025-10-31T06:58:37.7867666+01:00", + "lastSeenStr": "2 days ago", + "addedAt": "2025-10-18T17:17:37.9061157+02:00", + "spotCount": 339, + "playSound": true + }, + { + "callsign": "C5Y", + "lastSeen": "2025-10-30T17:00:04.8264529+01:00", + "lastSeenStr": "2 days ago", + "addedAt": "2025-10-27T19:34:57.6714115+01:00", + "spotCount": 175, + "playSound": true + }, + { + "callsign": "YI1MB", + "lastSeen": "2025-11-01T11:07:08.2392713+01:00", + "lastSeenStr": "1 day ago", + "addedAt": "2025-10-18T17:18:18.825584+02:00", + "spotCount": 1, "playSound": true }, { "callsign": "6O3T", - "lastSeen": "2025-11-02T08:24:51.6037533+01:00", - "lastSeenStr": "Just now", + "lastSeen": "2025-11-02T11:29:00.7935157+01:00", + "lastSeenStr": "6 minutes ago", "addedAt": "2025-10-22T19:31:13.1154881+02:00", - "spotCount": 2188, + "spotCount": 2278, "playSound": true }, { @@ -326,5 +278,53 @@ "addedAt": "2025-10-18T17:17:43.6895454+02:00", "spotCount": 0, "playSound": true + }, + { + "callsign": "XT2AW", + "lastSeen": "2025-10-24T04:08:09.2640864+02:00", + "lastSeenStr": "9 days ago", + "addedAt": "2025-10-18T17:17:27.3839089+02:00", + "spotCount": 136, + "playSound": true + }, + { + "callsign": "D2A", + "lastSeen": "2025-10-24T10:08:29.9662677+02:00", + "lastSeenStr": "9 days ago", + "addedAt": "2025-10-20T22:11:35.4767205+02:00", + "spotCount": 536, + "playSound": true + }, + { + "callsign": "5K0UA", + "lastSeen": "2025-11-02T10:36:27.8311341+01:00", + "lastSeenStr": "59 minutes ago", + "addedAt": "2025-10-18T17:17:53.7390559+02:00", + "spotCount": 2691, + "playSound": true + }, + { + "callsign": "VP2M", + "lastSeen": "0001-01-01T00:00:00Z", + "lastSeenStr": "Never", + "addedAt": "2025-10-18T17:17:57.308717+02:00", + "spotCount": 0, + "playSound": false + }, + { + "callsign": "PY0FB", + "lastSeen": "0001-01-01T00:00:00Z", + "lastSeenStr": "Never", + "addedAt": "2025-10-18T17:17:24.3843986+02:00", + "spotCount": 0, + "playSound": true + }, + { + "callsign": "V85NPV", + "lastSeen": "2025-11-02T09:04:29.9410671+01:00", + "lastSeenStr": "2 hours ago", + "addedAt": "2025-10-18T17:18:15.8781583+02:00", + "spotCount": 33, + "playSound": true } ] \ No newline at end of file