diff --git a/app.go b/app.go index abb2f2d..c11a9cf 100644 --- a/app.go +++ b/app.go @@ -96,12 +96,13 @@ const ( keyLookupFailsafe = "lookup.failsafe" keyLookupImages = "lookup.download_images" // 1 = expose QRZ ImageURL to UI - keyStationCallsign = "station.callsign" - keyStationOperator = "station.operator" - keyStationMyGrid = "station.my_grid" - keyStationCountry = "station.my_country" - keyStationSOTA = "station.my_sota_ref" - keyStationPOTA = "station.my_pota_ref" + keyStationCallsign = "station.callsign" + keyStationOperator = "station.operator" + keyWatchlistContestPattern = "watchlist.contest_pattern" // auto-add contest calls containing this + keyStationMyGrid = "station.my_grid" + keyStationCountry = "station.my_country" + keyStationSOTA = "station.my_sota_ref" + keyStationPOTA = "station.my_pota_ref" keyListsBands = "lists.bands" keyListsModes = "lists.modes" @@ -739,6 +740,7 @@ type App struct { watchlist *watchlist.Store // Tools → Watchlist (global watchlist.json) watchAlertMu sync.Mutex // throttles watchlist alerts… watchAlertAt map[string]time.Time // …per entry + watchPattern atomic.Value // auto-contest pattern (string), loaded at startup operating *operating.Repo udp *udp.Manager udpRepo *udp.Repo @@ -1391,6 +1393,7 @@ func (a *App) startup(ctx context.Context) { a.pota = pota.New(func(format string, args ...any) { applog.Printf(format, args...) }) a.watchlist = watchlist.New(filepath.Join(a.dataDir, "watchlist.json")) a.watchAlertAt = map[string]time.Time{} + a.watchPattern.Store(strings.ToUpper(strings.TrimSpace(a.settingOr(keyWatchlistContestPattern, "")))) go a.pota.Run(a.ctx) // DX Cluster (multi-server): the spot callback enriches each spot diff --git a/app_watchlist.go b/app_watchlist.go index 64c9d88..5f64bb4 100644 --- a/app_watchlist.go +++ b/app_watchlist.go @@ -11,12 +11,35 @@ import ( "strings" "time" + "hamlog/internal/applog" "hamlog/internal/qso" "hamlog/internal/watchlist" wruntime "github.com/wailsapp/wails/v2/pkg/runtime" ) +// The auto-contest pattern, DXHunter's contest_prefix: while non-empty, any +// spotted callsign CONTAINING it is added to the watchlist as a contest entry +// by itself — a special-event fleet (HB9WWA, DL0WWA, F4WWA…) is collected as +// it appears instead of typed in one by one. Held in an atomic so the spot +// pipeline never touches the settings store per spot. +func (a *App) GetWatchlistContestPattern() string { + if v := a.watchPattern.Load(); v != nil { + return v.(string) + } + return "" +} + +// SetWatchlistContestPattern stores the pattern (global, like the list itself). +// Emptying it stops the auto-add; entries already collected stay — deleting an +// operator's list because a setting changed is DXHunter behaviour this port +// deliberately drops. +func (a *App) SetWatchlistContestPattern(p string) { + p = strings.ToUpper(strings.TrimSpace(p)) + a.watchPattern.Store(p) + a.setSettingGlobal(keyWatchlistContestPattern, p) +} + // WatchlistEntries returns the list for the tab. func (a *App) WatchlistEntries() []watchlist.Entry { if a.watchlist == nil { @@ -160,6 +183,20 @@ func (a *App) watchSpot(dxCall, band, mode, country, comment string, freqHz int6 return } entry, notify, ok := a.watchlist.MarkSeen(dxCall) + if !ok { + // Not watched yet — the auto-contest pattern may claim it. Contains, not + // prefix: the event string sits anywhere in these calls (HB9WWA, F4WWA/P). + if p := a.GetWatchlistContestPattern(); p != "" && + strings.Contains(strings.ToUpper(dxCall), p) { + if err := a.watchlist.Add(dxCall, true); err == nil { + applog.Printf("watchlist: auto-added %s (contest pattern %q)", dxCall, p) + entry, notify, ok = a.watchlist.MarkSeen(dxCall) + if a.ctx != nil { + wruntime.EventsEmit(a.ctx, "watchlist:changed") + } + } + } + } if !ok || !notify || a.ctx == nil { return } diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 91c44fa..7df62f4 100644 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -602,6 +602,8 @@ export function GetUltrabeamSettings():Promise; export function GetUltrabeamStatus():Promise; +export function GetWatchlistContestPattern():Promise; + export function GetWebPublishConfig():Promise; export function GetWebPublishStatus():Promise; @@ -1228,6 +1230,8 @@ export function SetUIPref(arg1:string,arg2:string):Promise; export function SetUltrabeamDirection(arg1:number):Promise; +export function SetWatchlistContestPattern(arg1:string):Promise; + export function SetWinkeyerTrace(arg1:boolean):Promise; export function SetWorkedCallVariants(arg1:boolean):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index ecc094f..5f7c8df 100644 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -1142,6 +1142,10 @@ export function GetUltrabeamStatus() { return window['go']['main']['App']['GetUltrabeamStatus'](); } +export function GetWatchlistContestPattern() { + return window['go']['main']['App']['GetWatchlistContestPattern'](); +} + export function GetWebPublishConfig() { return window['go']['main']['App']['GetWebPublishConfig'](); } @@ -2394,6 +2398,10 @@ export function SetUltrabeamDirection(arg1) { return window['go']['main']['App']['SetUltrabeamDirection'](arg1); } +export function SetWatchlistContestPattern(arg1) { + return window['go']['main']['App']['SetWatchlistContestPattern'](arg1); +} + export function SetWinkeyerTrace(arg1) { return window['go']['main']['App']['SetWinkeyerTrace'](arg1); }