feat: upload qrz.com clublog and lotw manually

This commit is contained in:
2026-05-29 00:16:59 +02:00
parent edda183c16
commit 33a7b6c4ac
12 changed files with 1113 additions and 41 deletions
+25 -3
View File
@@ -30,7 +30,7 @@ type Service string
const (
ServiceQRZ Service = "qrz" // QRZ.com Logbook
ServiceClublog Service = "clublog" // Club Log real-time upload
// ServiceLoTW to come.
ServiceLoTW Service = "lotw" // ARRL Logbook of The World (via TQSL)
)
// UploadMode selects when an auto-upload fires after a QSO is saved.
@@ -41,6 +41,10 @@ const (
ModeImmediate UploadMode = "immediate"
// ModeDelayed waits a random 12 minutes before uploading.
ModeDelayed UploadMode = "delayed"
// ModeOnClose queues QSOs and uploads them in one batch when the app
// closes. This is the LoTW-friendly mode (ARRL discourages per-QSO
// uploads), and it lets the user fix the whole session before sending.
ModeOnClose UploadMode = "on_close"
)
// ServiceConfig is the per-service user configuration. It's a superset of
@@ -49,6 +53,7 @@ const (
//
// QRZ.com → APIKey, ForceStationCallsign
// Club Log → Email, Password, Callsign, APIKey
// LoTW → TQSLPath, StationLocation, KeyPassword (signs+uploads via TQSL)
//
// AutoUpload + UploadMode are common to all (timing is per-service, so the
// user can run e.g. Club Log immediate and QRZ delayed).
@@ -58,6 +63,11 @@ type ServiceConfig struct {
Password string `json:"password"` // Club Log account password
Callsign string `json:"callsign"` // Club Log logbook (owner) callsign
ForceStationCallsign string `json:"force_station_callsign"` // QRZ
TQSLPath string `json:"tqsl_path"` // LoTW: path to tqsl.exe
StationLocation string `json:"station_location"` // LoTW: TQSL Station Location name
KeyPassword string `json:"key_password"` // LoTW: certificate private-key password (optional)
UploadFlag string `json:"upload_flag"` // LoTW: sent status that means "ready to upload" — "N" or "R"
WriteLog bool `json:"write_log"` // LoTW: pass -t to write a TQSL diagnostic log
AutoUpload bool `json:"auto_upload"`
UploadMode UploadMode `json:"upload_mode"`
}
@@ -69,17 +79,29 @@ func (c ServiceConfig) normalised() ServiceConfig {
c.Email = strings.TrimSpace(c.Email)
c.Callsign = strings.ToUpper(strings.TrimSpace(c.Callsign))
c.ForceStationCallsign = strings.ToUpper(strings.TrimSpace(c.ForceStationCallsign))
if c.UploadMode != ModeDelayed {
c.TQSLPath = strings.TrimSpace(c.TQSLPath)
c.StationLocation = strings.TrimSpace(c.StationLocation)
// Upload flag is the LoTW sent-status that marks a QSO ready to upload.
// Only "N" (no) and "R" (requested) are valid; default to "R".
if uf := strings.ToUpper(strings.TrimSpace(c.UploadFlag)); uf == "N" || uf == "R" {
c.UploadFlag = uf
} else {
c.UploadFlag = "R"
}
switch c.UploadMode {
case ModeDelayed, ModeOnClose:
// keep
default:
c.UploadMode = ModeImmediate
}
return c
}
// ExternalServices bundles every service's config for the settings UI.
// LoTW fields will be added as that service lands.
type ExternalServices struct {
QRZ ServiceConfig `json:"qrz"`
Clublog ServiceConfig `json:"clublog"`
LoTW ServiceConfig `json:"lotw"`
}
// UploadResult is the outcome of a single upload attempt.