Allow alerts to have more than one DX callsign, and parse the "as CALL" fields from NG3K. Not yet working #38

This commit is contained in:
Ian Renton
2025-10-10 11:37:59 +01:00
parent 5c3adcdd4d
commit cc816d8662
7 changed files with 47 additions and 32 deletions

View File

@@ -1,3 +1,4 @@
import re
from datetime import datetime
import pytz
@@ -11,6 +12,7 @@ from data.alert import Alert
class NG3K(HTTPAlertProvider):
POLL_INTERVAL_SEC = 3600
ALERTS_URL = "https://www.ng3k.com/adxo.xml"
AS_CALL_PATTERN = re.compile("as ([a-z0-9/]+)", re.IGNORECASE)
def __init__(self, provider_config):
super().__init__(provider_config, self.ALERTS_URL, self.POLL_INTERVAL_SEC)
@@ -49,18 +51,26 @@ class NG3K(HTTPAlertProvider):
end_timestamp = datetime.strptime(end_year + " " + end_mon + " " + end_day + " 23:59", "%Y %b %d %H:%M").replace(
tzinfo=pytz.UTC).timestamp()
dx_country = parts[1]
dx_call = parts[2]
qsl_info = parts[3]
# Sometimes the DX callsign is "real", sometimes you just get a prefix with the real working callsigns being
# provided in the "by" field. e.g. call="JW", by="By LA7XK as JW7XK, LA6VM as JW6VM, LA9DL as JW9DL". So
# if there are "as" callsigns in the "by" field, we extract them and replace the main call with them.
# TODO this doesn't work yet, debug
extra_parts = parts[5].split("; ")
by = extra_parts[0]
dx_calls = [parts[2].upper()]
match = self.AS_CALL_PATTERN.match(by)
if match:
dx_calls = [x.group(1) for x in re.finditer(self.AS_CALL_PATTERN, by)]
dx_country = parts[1]
qsl_info = parts[3]
bands = extra_parts[1]
modes = extra_parts[2] if len(extra_parts) > 3 else ""
comment = extra_parts[-1]
# Convert to our alert format
alert = Alert(source=self.name,
dx_call=dx_call.upper(),
dx_calls=dx_calls,
dx_country=dx_country,
freqs_modes=bands + (("; " + modes) if modes != "" else ""),
comment=by + "; " + comment + "; " + qsl_info,

View File

@@ -21,7 +21,7 @@ class POTA(HTTPAlertProvider):
# Convert to our alert format
alert = Alert(source=self.name,
source_id=source_alert["scheduledActivitiesId"],
dx_call=source_alert["activator"].upper(),
dx_calls=[source_alert["activator"].upper()],
freqs_modes=source_alert["frequencies"],
comment=source_alert["comments"],
sig="POTA",

View File

@@ -21,7 +21,7 @@ class SOTA(HTTPAlertProvider):
# Convert to our alert format
alert = Alert(source=self.name,
source_id=source_alert["id"],
dx_call=source_alert["activatingCallsign"].upper(),
dx_calls=[source_alert["activatingCallsign"].upper()],
dx_name=source_alert["activatorName"].upper(),
freqs_modes=source_alert["frequency"],
comment=source_alert["comments"],

View File

@@ -21,7 +21,7 @@ class WWFF(HTTPAlertProvider):
# Convert to our alert format
alert = Alert(source=self.name,
source_id=source_alert["id"],
dx_call=source_alert["activator_call"].upper(),
dx_calls=[source_alert["activator_call"].upper()],
freqs_modes=source_alert["band"] + " " + source_alert["mode"],
comment=source_alert["remarks"],
sig="WWFF",

View File

@@ -17,10 +17,10 @@ from core.utils import infer_continent_from_callsign, \
class Alert:
# Unique identifier for the alert
id: str = None
# Callsign of the operator that has been alertted
dx_call: str = None
# Name of the operator that has been alertted
dx_name: str = None
# Callsigns of the operators that has been alerted
dx_calls: list = None
# Names of the operators that has been alerted
dx_names: list = None
# Country of the DX operator
dx_country: str = None
# Country flag of the DX operator
@@ -88,24 +88,24 @@ class Alert:
self.received_time_iso = datetime.fromtimestamp(self.received_time, pytz.UTC).isoformat()
# DX country, continent, zones etc. from callsign
if self.dx_call and not self.dx_country:
self.dx_country = infer_country_from_callsign(self.dx_call)
if self.dx_call and not self.dx_continent:
self.dx_continent = infer_continent_from_callsign(self.dx_call)
if self.dx_call and not self.dx_cq_zone:
self.dx_cq_zone = infer_cq_zone_from_callsign(self.dx_call)
if self.dx_call and not self.dx_itu_zone:
self.dx_itu_zone = infer_itu_zone_from_callsign(self.dx_call)
if self.dx_call and not self.dx_dxcc_id:
self.dx_dxcc_id = infer_dxcc_id_from_callsign(self.dx_call)
if self.dx_calls and self.dx_calls[0] and not self.dx_country:
self.dx_country = infer_country_from_callsign(self.dx_calls[0])
if self.dx_calls and self.dx_calls[0] and not self.dx_continent:
self.dx_continent = infer_continent_from_callsign(self.dx_calls[0])
if self.dx_calls and self.dx_calls[0] and not self.dx_cq_zone:
self.dx_cq_zone = infer_cq_zone_from_callsign(self.dx_calls[0])
if self.dx_calls and self.dx_calls[0] and not self.dx_itu_zone:
self.dx_itu_zone = infer_itu_zone_from_callsign(self.dx_calls[0])
if self.dx_calls and self.dx_calls[0] and not self.dx_dxcc_id:
self.dx_dxcc_id = infer_dxcc_id_from_callsign(self.dx_calls[0])
if self.dx_dxcc_id and not self.dx_flag:
self.dx_flag = DXCC_FLAGS[self.dx_dxcc_id]
# DX operator details lookup, using QRZ.com. This should be the last resort compared to taking the data from
# the actual alertting service, e.g. we don't want to accidentally use a user's QRZ.com home lat/lon instead of
# the one from the park reference they're at.
if self.dx_call and not self.dx_name:
self.dx_name = infer_name_from_callsign(self.dx_call)
if self.dx_calls and not self.dx_names:
self.dx_names = list(map(lambda c: infer_name_from_callsign(c), self.dx_calls))
# Always create an ID based on a hash of every parameter *except* received_time. This is used as the index
# to a map, which as a byproduct avoids us having multiple duplicate copies of the object that are identical

View File

@@ -708,17 +708,21 @@ components:
type: string
description: Unique identifier based on a hash of the alert to distinguish this one from any others.
example: 442c5d56ac467341f1943e8596685073b38f5a5d4c3802ca1e16ecf98967956c
dx_call:
dx_calls:
type: array
description: Callsigns of the operator(s) that are going to be activating
items:
type: string
description: Callsign of the operator that is going to be activating
example: M0TRT
dx_name:
type: array
description: Names of the operator(s) that are going to be activating
items:
type: string
description: Name of the operator that is going to be activating
example: Ian
dx_country:
type: string
description: Country of the DX operator
description: Country of the DX operator. This, and the subsequent fields, assume that all activators will be in the same country!
example: United Kingdom
dx_flag:
type: string

View File

@@ -171,7 +171,8 @@ function addAlertRowsToTable(tbody, alerts) {
// Populate the row
$tr.append(`<td class='nowrap'>${start_time_formatted}</td>`);
$tr.append(`<td class='nowrap'>${end_time_formatted}</td>`);
$tr.append(`<td class='nowrap'><span class='flag-wrapper hideonmobile' title='${dx_country}'>${dx_flag}</span><a class='dx-link' href='https://qrz.com/db/${a["dx_call"]}' target='_new'>${a["dx_call"]}</a></td>`);
// TODO take a["dx_calls"] and add a callsign to the following td for every one in the list.
$tr.append(`<td class='nowrap'><span class='flag-wrapper hideonmobile' title='${dx_country}'>${dx_flag}</span><a class='dx-link' href='https://qrz.com/db/${call}' target='_new'>${call}</a></td>`);
$tr.append(`<td class='hideonmobile'>${freqsModesText}</td>`);
$tr.append(`<td class='hideonmobile'>${commentText}</td>`);
$tr.append(`<td class='nowrap hideonmobile'><span class='icon-wrapper'><i class='fa-solid fa-${a["icon"]}'></i></span> ${sigSourceText}</td>`);