fix: different bugs on eQSL

This commit is contained in:
2026-07-09 18:36:24 +02:00
parent 1f74e4d234
commit f3bf0b2f5c
3 changed files with 218 additions and 9 deletions
+57 -9
View File
@@ -6868,7 +6868,14 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
}
emit("ADIF head: " + snip)
}
keyIDs, _ := a.qso.DedupeKeyIDs(ctx)
// Scope to THIS profile's / forced callsign so a QRZ logbook holding
// several of the operator's calls (F4BPO, TM2Q…) only confirms/adds QSOs
// for the active call. Windowed + phone-mode-tolerant matching (same as
// eQSL/LoTW).
qrzOwner := a.uploadOwnerCall(extsvc.ServiceQRZ)
mIdx, _ := a.qso.BuildMatchIndex(ctx, qrzOwner)
const qrzMatchWindow = 10 * time.Minute
qrzSkippedOtherCall := 0
// QRZ confirmations are QRZ-specific (not award-valid), so NEW is
// judged only against other QRZ confirmations.
sets, _ := a.qso.ConfirmedSlots(ctx, []string{"qrzcom_qso_download_status"})
@@ -6905,6 +6912,13 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
if sinceDate != "" && !q.QSODate.IsZero() && q.QSODate.UTC().Format("2006-01-02") < sinceDate {
return nil
}
// Skip a QSO logged under a DIFFERENT one of the operator's callsigns.
if qrzOwner != "" {
if rc := strings.ToUpper(strings.TrimSpace(rec["station_callsign"])); rc != "" && rc != qrzOwner {
qrzSkippedOtherCall++
return nil
}
}
total++
date := rec["qrzcom_qso_download_date"]
if date == "" {
@@ -6913,8 +6927,7 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
a.enrichContactedFromCty(&q)
line := fmt.Sprintf("Callsign: %s Date: %s Band: %s Mode: %s",
q.Callsign, q.QSODate.UTC().Format("2006-01-02 15:04"), q.Band, q.Mode)
key := qso.DedupeKey(q.Callsign, q.QSODate.UTC().Format("2006-01-02T15:04"), q.Band, q.Mode)
id, found := keyIDs[key]
id, found := mIdx.Match(q.Callsign, q.Band, q.Mode, q.QSODate.UTC(), qrzMatchWindow)
switch {
case found:
if alreadyQrz[id] {
@@ -6928,8 +6941,11 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
q.QRZComUploadStatus = "Y"
q.QRZComDownloadStatus = "Y"
q.QRZComDownloadDate = date
if q.StationCallsign == "" {
q.StationCallsign = qrzOwner
}
if newID, e := a.qso.Add(ctx, q); e == nil {
keyIDs[key] = newID
mIdx.Add(q.Callsign, q.Band, q.Mode, q.QSODate.UTC(), newID)
added++
emit(line + " ### ADDED ###")
}
@@ -6972,6 +6988,9 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
sort.Strings(keys)
emit(fmt.Sprintf("Parsed %d record(s). Fields seen: %s", parsed, strings.Join(keys, ", ")))
emit(fmt.Sprintf("Confirmed %d, added %d (of %d returned)", matched, added, total))
if qrzSkippedOtherCall > 0 {
emit(fmt.Sprintf("Skipped %d QSO(s) logged under another of your callsigns (scoped to %s).", qrzSkippedOtherCall, qrzOwner))
}
// (last-download date already stored right after the fetch above)
case extsvc.ServiceEQSL:
@@ -6987,12 +7006,23 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
done(matched, total)
return
}
keyIDs, kerr := a.qso.DedupeKeyIDs(ctx)
// Scope everything to THIS profile's / forced callsign so an eQSL account
// holding several of the operator's calls (F4BPO, TM2Q…) only confirms/adds
// QSOs for the active call.
eqslOwner := a.uploadOwnerCall(extsvc.ServiceEQSL)
// Tolerant match index: an eQSL confirmation carries the OTHER station's
// logged time, often a minute or two off ours — so we match within a
// ±10-minute window. The mode must still match, except the phone sidebands
// SSB/USB/LSB are treated as one (a station may confirm USB where we logged
// SSB); FT8/FT4/CW/… stay exact.
mIdx, kerr := a.qso.BuildMatchIndex(ctx, eqslOwner)
if kerr != nil {
emit("Error reading local log: " + kerr.Error())
done(matched, total)
return
}
const eqslMatchWindow = 10 * time.Minute
skippedOtherCall := 0
// eQSL confirmations aren't ARRL-award-valid (only LoTW + paper QSL are),
// so NEW is judged only against other eQSL confirmations.
sets, _ := a.qso.ConfirmedSlots(ctx, []string{"eqsl_rcvd"})
@@ -7003,6 +7033,15 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
if !ok {
return nil
}
// Skip a confirmation logged under a DIFFERENT one of the operator's
// callsigns (the eQSL account may hold several) — it belongs to that
// call's log, not this one.
if eqslOwner != "" {
if rc := strings.ToUpper(strings.TrimSpace(rec["station_callsign"])); rc != "" && rc != eqslOwner {
skippedOtherCall++
return nil
}
}
total++
// eQSL stamps the confirmation date in QSLRDATE (fall back to today).
date := rec["qslrdate"]
@@ -7010,8 +7049,8 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
date = time.Now().UTC().Format("20060102")
}
a.enrichContactedFromCty(&q)
key := qso.DedupeKey(q.Callsign, q.QSODate.UTC().Format("2006-01-02T15:04"), q.Band, q.Mode)
if id, found := keyIDs[key]; found {
id, found := mIdx.Match(q.Callsign, q.Band, q.Mode, q.QSODate.UTC(), eqslMatchWindow)
if found {
if e := a.qso.MarkEQSLConfirmed(ctx, id, date); e == nil {
matched++
}
@@ -7019,8 +7058,11 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
q.EQSLSent = "Y"
q.EQSLRcvd = "Y"
q.EQSLRcvdDate = date
if q.StationCallsign == "" {
q.StationCallsign = eqslOwner // stamp the active call on adds
}
if newID, e := a.qso.Add(ctx, q); e == nil {
keyIDs[key] = newID
mIdx.Add(q.Callsign, q.Band, q.Mode, q.QSODate.UTC(), newID)
added++
}
} else {
@@ -7061,6 +7103,9 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
for _, u := range unmatched {
emit(" ⚠ no local QSO for: " + u)
}
if skippedOtherCall > 0 {
emit(fmt.Sprintf("Skipped %d confirmation(s) logged under another of your callsigns (scoped to %s).", skippedOtherCall, eqslOwner))
}
if a.settings != nil {
a.setSetting(a.profileScope()+keyExtEQSLLastDownload, time.Now().UTC().Format("2006-01-02"))
}
@@ -7312,7 +7357,10 @@ func (a *App) uploadOwnerCall(svc extsvc.Service) string {
case extsvc.ServiceHRDLog:
owner = cfg.HRDLog.Callsign
case extsvc.ServiceEQSL:
owner = cfg.EQSL.Username
// eQSL has no Force-callsign field; its Username is just the account login
// (which may hold several of the operator's calls). Scope by the ACTIVE
// PROFILE's callsign instead — the operating identity — via the fallback
// below, so a download on the F4BPO profile only touches F4BPO QSOs.
}
owner = strings.ToUpper(strings.TrimSpace(owner))
if owner == "" && a.profiles != nil {