The watch list was a tab, and an operator working FT8 lives on the decodes one: a station they had asked to be told about turned up on a screen they were not looking at. The same answer is now docked in the widget strip, above the tabs, reduced to what is worth acting on — on the air and still needed, one row per band and mode, with the cluster's own NEW DXCC / NEW BAND / NEW SLOT badge and a click that tunes. Off by default. The "active and needed" answer costs a debounced query per visible slot, so it is written once (lib/watchlistSpots) and the tab uses it too. Auto-call: - It answers a new prefix, county, state, square or park. Those markers are orthogonal to the entity, they ranked as nothing-needed, and the engine sat through a never-worked WPX prefix calling CQ. New rung at the foot of the ladder, gated by the chase switches the badges use — which meant making those switches portable, since the backend cannot read localStorage. - It calls THROUGH a pileup. Giving up the moment the DX answered somebody else is precisely how a queue is not worked; the call and miss counters already bound the effort, and a station in mid-exchange is still never chosen as a new target. The PSK Reporter panel now follows the station auto-call is waiting for: the analysis takes a history query and a period or two to fill, so starting it when the DX comes free is starting it too late. Callbook lookup: a compound callsign with a page of its OWN keeps that page's location. QRZ files HP/WE9G under exactly that form, with the Panama square the station is operating from, and the rule that drops a home address from a portable call was throwing it away. The record's own country tells an operation's page from a home page. Changelog: entries may open with [NEW], drawn as a pill in the What's new dialog — a release is mostly fixes and the two or three genuinely new things should not have to be found by reading all of it.
66 lines
2.3 KiB
Go
66 lines
2.3 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// deniedCallHashes lists station callsigns not permitted to run this build,
|
|
// stored as SHA-256 hex of the base call so the calls themselves appear nowhere
|
|
// in the source or the compiled binary. Enforcement is best-effort by design —
|
|
// the call is entered by the operator and can be changed — so this only turns
|
|
// away straightforward use, not a determined one.
|
|
var deniedCallHashes = map[string]struct{}{
|
|
"2282a88b3e5e4eebc6b8174d005bb46d32025758b7741bdcf34f2b3621c02205": {},
|
|
"0ee09fe60817a2a4982f5e5b14a60b8dbb11cff55b3d36661213c4cbdd0933ea": {},
|
|
"46fb61e71afb40627cb6493a6a59c9d4d0231ee3cad1a2bf3fcc4cdfce36fabc": {},
|
|
"d1ae6212ec057f9d5c6f379fb57acedac7c94142c9d49667dc04b2016d03d1b6": {},
|
|
"0741c9e394b42f43191899105553b47155ddc3026da12b5360701f9c181ff123": {},
|
|
"ab4926a3a0ab76d41b5b99cd3ad0683584970c341c29427c1dfa4b3c329ce415": {},
|
|
"9d17c9c213a6cc89c12d7520bcf21c86a0cf43d17e82e74f33f3a77cb865d28a": {},
|
|
"94ee059335e587e501cc4bf90613e0814f00a7b08bc7c648fd865a2af6a22cc2": {},
|
|
}
|
|
|
|
// callDenied reports whether a callsign is on deniedCallHashes. The call is
|
|
// reduced to its base form (upper-cased, portable prefix/suffix dropped) the
|
|
// same way extsvc does, so F4XYZ/P matches F4XYZ.
|
|
func callDenied(call string) bool {
|
|
base := denyBaseCall(call)
|
|
if base == "" {
|
|
return false
|
|
}
|
|
sum := sha256.Sum256([]byte(base))
|
|
_, bad := deniedCallHashes[hex.EncodeToString(sum[:])]
|
|
return bad
|
|
}
|
|
|
|
// denyBaseCall mirrors extsvc.baseCall: for a slashed form it returns the
|
|
// longest token (the real call), otherwise the call itself, upper-cased.
|
|
func denyBaseCall(s string) string {
|
|
s = strings.ToUpper(strings.TrimSpace(s))
|
|
if !strings.Contains(s, "/") {
|
|
return s
|
|
}
|
|
best := ""
|
|
for _, part := range strings.Split(s, "/") {
|
|
if len(part) > len(best) {
|
|
best = part
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|
|
// enforceCallGate exits the process silently when any of the supplied callsigns
|
|
// is denied. Called once at startup, after the profiles are loaded: the first
|
|
// launch is where the operator enters and saves the call, so a denied build
|
|
// simply does not come back up on the next launch. No window, no message.
|
|
func enforceCallGate(calls ...string) {
|
|
for _, c := range calls {
|
|
if callDenied(c) {
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
}
|