mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-20 22:37:44 +00:00
Use ruff linter to fix issues and provide consistent formatting
This commit is contained in:
+11
-10
@@ -43,16 +43,17 @@ class APRSIS(SpotProvider):
|
||||
via_parts = str(data["via"]).split("-")
|
||||
de_call = via_parts[0].upper()
|
||||
de_ssid = via_parts[1].upper() if len(via_parts) > 1 else None
|
||||
spot = Spot(source="APRS-IS",
|
||||
dx_call=dx_call,
|
||||
dx_ssid=dx_ssid,
|
||||
de_call=de_call,
|
||||
de_ssid=de_ssid,
|
||||
comment=str(data["comment"]) if "comment" in data else None,
|
||||
dx_latitude=float(data["latitude"]) if "latitude" in data else None,
|
||||
dx_longitude=float(data["longitude"]) if "longitude" in data else None,
|
||||
time=datetime.now(
|
||||
pytz.UTC).timestamp()) # APRS-IS spots are live so we can assume spot time is "now"
|
||||
spot = Spot(
|
||||
source="APRS-IS",
|
||||
dx_call=dx_call,
|
||||
dx_ssid=dx_ssid,
|
||||
de_call=de_call,
|
||||
de_ssid=de_ssid,
|
||||
comment=str(data["comment"]) if "comment" in data else None,
|
||||
dx_latitude=float(data["latitude"]) if "latitude" in data else None,
|
||||
dx_longitude=float(data["longitude"]) if "longitude" in data else None,
|
||||
time=datetime.now(pytz.UTC).timestamp(),
|
||||
) # APRS-IS spots are live so we can assume spot time is "now"
|
||||
|
||||
# Add to our list
|
||||
self._submit(spot)
|
||||
|
||||
+23
-13
@@ -18,10 +18,12 @@ class DXCluster(SpotProvider):
|
||||
|
||||
_LINE_PATTERN_EXCLUDE_RBN = re.compile(
|
||||
r"^DX de ([a-z0-9/]+):\s+([0-9.]+)\s+([a-z0-9/]+)\s+(.*)\s+(\d{4}Z)",
|
||||
re.IGNORECASE)
|
||||
re.IGNORECASE,
|
||||
)
|
||||
_LINE_PATTERN_ALLOW_RBN = re.compile(
|
||||
r"^DX de ([a-z0-9/]+)-?#?:\s+([0-9.]+)\s+([a-z0-9/]+)\s+(.*)\s+(\d{4}Z)",
|
||||
re.IGNORECASE)
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
def __init__(self, provider_config):
|
||||
"""Constructor requires hostname and port"""
|
||||
@@ -31,10 +33,13 @@ class DXCluster(SpotProvider):
|
||||
self._hostname = provider_config["host"]
|
||||
self._port = provider_config["port"]
|
||||
self._login_prompt = provider_config["login_prompt"] if "login_prompt" in provider_config else "login:"
|
||||
self._login_callsign = provider_config[
|
||||
"login_callsign"] if "login_callsign" in provider_config else SERVER_OWNER_CALLSIGN
|
||||
self._login_callsign = (
|
||||
provider_config["login_callsign"] if "login_callsign" in provider_config else SERVER_OWNER_CALLSIGN
|
||||
)
|
||||
self._allow_rbn_spots = provider_config["allow_rbn_spots"] if "allow_rbn_spots" in provider_config else False
|
||||
self._spot_line_pattern = self._LINE_PATTERN_ALLOW_RBN if self._allow_rbn_spots else self._LINE_PATTERN_EXCLUDE_RBN
|
||||
self._spot_line_pattern = (
|
||||
self._LINE_PATTERN_ALLOW_RBN if self._allow_rbn_spots else self._LINE_PATTERN_EXCLUDE_RBN
|
||||
)
|
||||
self._telnet = None
|
||||
self._thread = Thread(target=self._handle, name=f"DXClusterSpotProvider-{self.name}")
|
||||
self._thread.daemon = True
|
||||
@@ -77,14 +82,19 @@ class DXCluster(SpotProvider):
|
||||
match = self._spot_line_pattern.match(telnet_output.decode("latin-1"))
|
||||
if match:
|
||||
spot_time = datetime.strptime(match.group(5), "%H%MZ")
|
||||
spot_datetime = datetime.combine(datetime.now(pytz.UTC).date(), spot_time.time(),
|
||||
tzinfo=pytz.UTC)
|
||||
spot = Spot(source=self.name,
|
||||
dx_call=match.group(3),
|
||||
de_call=match.group(1),
|
||||
freq=float(match.group(2)) * 1000,
|
||||
comment=match.group(4).strip(),
|
||||
time=spot_datetime.timestamp())
|
||||
spot_datetime = datetime.combine(
|
||||
datetime.now(pytz.UTC).date(),
|
||||
spot_time.time(),
|
||||
tzinfo=pytz.UTC,
|
||||
)
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
dx_call=match.group(3),
|
||||
de_call=match.group(1),
|
||||
freq=float(match.group(2)) * 1000,
|
||||
comment=match.group(4).strip(),
|
||||
time=spot_datetime.timestamp(),
|
||||
)
|
||||
|
||||
# Add to our list
|
||||
self._submit(spot)
|
||||
|
||||
+64
-29
@@ -27,7 +27,12 @@ class GMA(HTTPSpotProvider):
|
||||
logging.warning("GMA spot provider configured but no api key was provided, this API will not be queried.")
|
||||
self._url_data_cache = URLDataCache("GMA")
|
||||
|
||||
super().__init__("GMA", provider_config, f"{self.SPOTS_URL}?key={self._api_key}", self.POLL_INTERVAL_SEC)
|
||||
super().__init__(
|
||||
"GMA",
|
||||
provider_config,
|
||||
f"{self.SPOTS_URL}?key={self._api_key}",
|
||||
self.POLL_INTERVAL_SEC,
|
||||
)
|
||||
|
||||
def _http_response_to_spots(self, http_response):
|
||||
new_spots = []
|
||||
@@ -41,43 +46,67 @@ class GMA(HTTPSpotProvider):
|
||||
|
||||
# Seen some real janky times from GMA, if we don't understand it just ignore this spot
|
||||
try:
|
||||
time = datetime.strptime(source_spot["DATE"] + source_spot["TIME"], "%Y%m%d%H%M").replace(
|
||||
tzinfo=pytz.UTC).timestamp()
|
||||
time = (
|
||||
datetime.strptime(source_spot["DATE"] + source_spot["TIME"], "%Y%m%d%H%M")
|
||||
.replace(tzinfo=pytz.UTC)
|
||||
.timestamp()
|
||||
)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
spot = Spot(source=self.name,
|
||||
dx_call=source_spot["ACTIVATOR"].upper(),
|
||||
de_call=source_spot["SPOTTER"].upper(),
|
||||
# Seen GMA spots with no frequency or with "QRT" in this field
|
||||
freq=float(source_spot["QRG"]) * 1000 if (
|
||||
source_spot["QRG"] != "" and source_spot["QRG"] != "QRT") else None,
|
||||
# Filter out some weird mode strings
|
||||
mode=source_spot["MODE"].upper() if "<>" not in source_spot["MODE"] else None,
|
||||
comment=source_spot["TEXT"],
|
||||
sig_refs=[SIGRef(id=source_spot["REF"], sig="", name=source_spot["NAME"], latitude=lat,
|
||||
longitude=lon)],
|
||||
time=time,
|
||||
dx_latitude=lat,
|
||||
dx_longitude=lon,
|
||||
qrt=source_spot["QRG"] == "QRT")
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
dx_call=source_spot["ACTIVATOR"].upper(),
|
||||
de_call=source_spot["SPOTTER"].upper(),
|
||||
# Seen GMA spots with no frequency or with "QRT" in this field
|
||||
freq=float(source_spot["QRG"]) * 1000
|
||||
if (source_spot["QRG"] != "" and source_spot["QRG"] != "QRT")
|
||||
else None,
|
||||
# Filter out some weird mode strings
|
||||
mode=source_spot["MODE"].upper() if "<>" not in source_spot["MODE"] else None,
|
||||
comment=source_spot["TEXT"],
|
||||
sig_refs=[
|
||||
SIGRef(
|
||||
id=source_spot["REF"],
|
||||
sig="",
|
||||
name=source_spot["NAME"],
|
||||
latitude=lat,
|
||||
longitude=lon,
|
||||
)
|
||||
],
|
||||
time=time,
|
||||
dx_latitude=lat,
|
||||
dx_longitude=lon,
|
||||
qrt=source_spot["QRG"] == "QRT",
|
||||
)
|
||||
|
||||
# GMA doesn't give what programme (SIG) the reference is for until we separately look it up.
|
||||
if "REF" in source_spot:
|
||||
try:
|
||||
ref_response = self._url_data_cache.get(self.REF_INFO_URL_ROOT + source_spot["REF"],
|
||||
headers=HTTP_HEADERS)
|
||||
ref_response = self._url_data_cache.get(
|
||||
self.REF_INFO_URL_ROOT + source_spot["REF"],
|
||||
headers=HTTP_HEADERS,
|
||||
)
|
||||
# Sometimes this is blank even if it's a 200 response, so handle that
|
||||
if ref_response.ok and ref_response.text is not None and ref_response.text != "" and ref_response.text != "\n":
|
||||
if (
|
||||
ref_response.ok
|
||||
and ref_response.text is not None
|
||||
and ref_response.text != ""
|
||||
and ref_response.text != "\n"
|
||||
):
|
||||
ref_info = ref_response.json()
|
||||
# If this is POTA, SOTA or WWFF data we already have it through other means, so ignore. POTA and WWFF
|
||||
# spots come through with reftype=POTA or reftype=WWFF. SOTA is harder to figure out because both SOTA
|
||||
# and GMA summits come through with reftype=Summit, so we must check for the presence of a "sota" entry
|
||||
# to determine if it's a SOTA summit.
|
||||
if spot.sig_refs and "reftype" in ref_info and ref_info["reftype"] not in ["POTA",
|
||||
"WWFF"] and (
|
||||
ref_info["reftype"] != "Summit" or "sota" not in ref_info or ref_info[
|
||||
"sota"] == ""):
|
||||
if (
|
||||
spot.sig_refs
|
||||
and "reftype" in ref_info
|
||||
and ref_info["reftype"] not in ["POTA", "WWFF"]
|
||||
and (
|
||||
ref_info["reftype"] != "Summit" or "sota" not in ref_info or ref_info["sota"] == ""
|
||||
)
|
||||
):
|
||||
match ref_info["reftype"]:
|
||||
case "Summit":
|
||||
spot.sig_refs[0].sig = "GMA"
|
||||
@@ -98,7 +127,9 @@ class GMA(HTTPSpotProvider):
|
||||
spot.sig_refs[0].sig = "MOTA"
|
||||
spot.sig = "MOTA"
|
||||
case _:
|
||||
logging.warning(f"GMA spot found with ref type {ref_info['reftype']}, developer needs to add support for this!")
|
||||
logging.warning(
|
||||
f"GMA spot found with ref type {ref_info['reftype']}, developer needs to add support for this!"
|
||||
)
|
||||
spot.sig_refs[0].sig = ref_info["reftype"]
|
||||
spot.sig = ref_info["reftype"]
|
||||
|
||||
@@ -109,12 +140,16 @@ class GMA(HTTPSpotProvider):
|
||||
elif not ref_response.from_cache:
|
||||
if not ref_response.ok:
|
||||
logging.warning(
|
||||
f"HTTP {ref_response.status_code} when looking up GMA ref {source_spot['REF']}")
|
||||
f"HTTP {ref_response.status_code} when looking up GMA ref {source_spot['REF']}"
|
||||
)
|
||||
else:
|
||||
logging.warning(
|
||||
f"GMA API returned a malformed response when looking up ref {source_spot['REF']}")
|
||||
f"GMA API returned a malformed response when looking up ref {source_spot['REF']}"
|
||||
)
|
||||
except:
|
||||
logging.exception(f"Exception when looking up {self.REF_INFO_URL_ROOT}{source_spot['REF']}, ignoring this spot for now")
|
||||
logging.exception(
|
||||
f"Exception when looking up {self.REF_INFO_URL_ROOT}{source_spot['REF']}, ignoring this spot for now"
|
||||
)
|
||||
else:
|
||||
logging.warning(f"The GMA API returned an unexpected response (HTTP {http_response.status_code}).")
|
||||
|
||||
|
||||
+24
-14
@@ -4,7 +4,7 @@ from datetime import datetime
|
||||
|
||||
import pytz
|
||||
import requests
|
||||
from requests.exceptions import ConnectionError, ReadTimeout, ConnectTimeout
|
||||
from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
|
||||
|
||||
from core.constants import HTTP_HEADERS
|
||||
from data.sig_ref import SIGRef
|
||||
@@ -52,25 +52,35 @@ class HEMA(HTTPSpotProvider):
|
||||
continue
|
||||
|
||||
# Convert to our spot format
|
||||
spot = Spot(source=self.name,
|
||||
dx_call=spot_items[2].upper(),
|
||||
de_call=spotter_comment_match.group(1).upper(),
|
||||
freq=float(freq_mode_match.group(1)) * 1000000,
|
||||
mode=freq_mode_match.group(2).upper(),
|
||||
comment=spotter_comment_match.group(2),
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
dx_call=spot_items[2].upper(),
|
||||
de_call=spotter_comment_match.group(1).upper(),
|
||||
freq=float(freq_mode_match.group(1)) * 1000000,
|
||||
mode=freq_mode_match.group(2).upper(),
|
||||
comment=spotter_comment_match.group(2),
|
||||
sig="HEMA",
|
||||
sig_refs=[
|
||||
SIGRef(
|
||||
id=spot_items[3].upper(),
|
||||
sig="HEMA",
|
||||
sig_refs=[SIGRef(id=spot_items[3].upper(), sig="HEMA", name=spot_items[4],
|
||||
latitude=float(spot_items[7]), longitude=float(spot_items[8]))],
|
||||
time=datetime.strptime(spot_items[0], "%d/%m/%Y %H:%M").replace(
|
||||
tzinfo=pytz.UTC).timestamp(),
|
||||
dx_latitude=float(spot_items[7]),
|
||||
dx_longitude=float(spot_items[8]))
|
||||
name=spot_items[4],
|
||||
latitude=float(spot_items[7]),
|
||||
longitude=float(spot_items[8]),
|
||||
)
|
||||
],
|
||||
time=datetime.strptime(spot_items[0], "%d/%m/%Y %H:%M")
|
||||
.replace(tzinfo=pytz.UTC)
|
||||
.timestamp(),
|
||||
dx_latitude=float(spot_items[7]),
|
||||
dx_longitude=float(spot_items[8]),
|
||||
)
|
||||
|
||||
# Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other
|
||||
# code will do that for us.
|
||||
new_spots.append(spot)
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
logging.warning(f"Timeout when accessing HEMA spots API.")
|
||||
logging.warning("Timeout when accessing HEMA spots API.")
|
||||
except ConnectionError:
|
||||
logging.warning("Connection error when accessing HEMA spots API.")
|
||||
return new_spots
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from threading import Thread, Event
|
||||
from threading import Event, Thread
|
||||
|
||||
import pytz
|
||||
import requests
|
||||
|
||||
+17
-9
@@ -25,16 +25,24 @@ class LLOTA(HTTPSpotProvider):
|
||||
comment = str(source_spot["history"][-1]["comment"])
|
||||
spotter = str(source_spot["history"][-1]["spotter_callsign"])
|
||||
# Convert to our spot format
|
||||
spot = Spot(source=self.name,
|
||||
source_id=source_spot["id"],
|
||||
dx_call=source_spot["callsign"].upper(),
|
||||
de_call=spotter.upper() if spotter else None,
|
||||
freq=float(source_spot["frequency"]) * 1000000,
|
||||
mode=source_spot["mode"].upper(),
|
||||
comment=comment,
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
source_id=source_spot["id"],
|
||||
dx_call=source_spot["callsign"].upper(),
|
||||
de_call=spotter.upper() if spotter else None,
|
||||
freq=float(source_spot["frequency"]) * 1000000,
|
||||
mode=source_spot["mode"].upper(),
|
||||
comment=comment,
|
||||
sig="LLOTA",
|
||||
sig_refs=[
|
||||
SIGRef(
|
||||
id=source_spot["reference"],
|
||||
sig="LLOTA",
|
||||
sig_refs=[SIGRef(id=source_spot["reference"], sig="LLOTA", name=source_spot["reference_name"])],
|
||||
time=datetime.fromisoformat(source_spot["updated_at"].replace("Z", "+00:00")).timestamp())
|
||||
name=source_spot["reference_name"],
|
||||
)
|
||||
],
|
||||
time=datetime.fromisoformat(source_spot["updated_at"].replace("Z", "+00:00")).timestamp(),
|
||||
)
|
||||
|
||||
# Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do
|
||||
# that for us.
|
||||
|
||||
@@ -18,7 +18,17 @@ class ParksNPeaks(HTTPSpotProvider):
|
||||
SPOTS_URL = "https://www.parksnpeaks.org/api/ALL"
|
||||
SUBMIT_URL = "https://www.parksnpeaks.org/api/SPOT/"
|
||||
SIOTA_LIST_URL = "https://www.silosontheair.com/data/silos.csv"
|
||||
SUBMITTABLE_SIGS = ["POTA", "SOTA", "WWFF", "HEMA", "WOTA", "ZLOTA", "SIOTA", "KRMNPA", "SANPCPA"]
|
||||
SUBMITTABLE_SIGS = [
|
||||
"POTA",
|
||||
"SOTA",
|
||||
"WWFF",
|
||||
"HEMA",
|
||||
"WOTA",
|
||||
"ZLOTA",
|
||||
"SIOTA",
|
||||
"KRMNPA",
|
||||
"SANPCPA",
|
||||
]
|
||||
|
||||
def __init__(self, provider_config):
|
||||
super().__init__("ParksNPeaks", provider_config, self.SPOTS_URL, self.POLL_INTERVAL_SEC)
|
||||
@@ -29,18 +39,23 @@ class ParksNPeaks(HTTPSpotProvider):
|
||||
if http_response and http_response != "":
|
||||
for source_spot in http_response.json():
|
||||
# Convert to our spot format
|
||||
spot = Spot(source=self.name,
|
||||
source_id=source_spot["actID"],
|
||||
dx_call=source_spot["actCallsign"].upper(),
|
||||
de_call=source_spot["actSpoter"].upper() if source_spot["actSpoter"] != "" else None,
|
||||
# typo exists in API
|
||||
freq=float(source_spot["actFreq"].replace(",", "").replace("+-", "")
|
||||
.replace("+/-", "").strip()) * 1000000 if (source_spot["actFreq"] != "") else None,
|
||||
# Seen PNP spots with empty frequency, and with comma-separated thousands digits
|
||||
mode=source_spot["actMode"].upper(),
|
||||
comment=source_spot["actComments"],
|
||||
time=datetime.strptime(source_spot["actTime"], "%Y-%m-%d %H:%M:%S").replace(
|
||||
tzinfo=pytz.UTC).timestamp())
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
source_id=source_spot["actID"],
|
||||
dx_call=source_spot["actCallsign"].upper(),
|
||||
de_call=source_spot["actSpoter"].upper() if source_spot["actSpoter"] != "" else None,
|
||||
# typo exists in API
|
||||
freq=float(source_spot["actFreq"].replace(",", "").replace("+-", "").replace("+/-", "").strip())
|
||||
* 1000000
|
||||
if (source_spot["actFreq"] != "")
|
||||
else None,
|
||||
# Seen PNP spots with empty frequency, and with comma-separated thousands digits
|
||||
mode=source_spot["actMode"].upper(),
|
||||
comment=source_spot["actComments"],
|
||||
time=datetime.strptime(source_spot["actTime"], "%Y-%m-%d %H:%M:%S")
|
||||
.replace(tzinfo=pytz.UTC)
|
||||
.timestamp(),
|
||||
)
|
||||
|
||||
# Extract a de_call if it's in the comment but not in the "actSpoter" field
|
||||
m = re.search(r"\(de ([A-Za-z0-9]*)\)", spot.comment or "")
|
||||
@@ -53,7 +68,12 @@ class ParksNPeaks(HTTPSpotProvider):
|
||||
sig_ref = source_spot["actSiteID"]
|
||||
if sig and sig != "" and sig != "QRP" and sig_ref and sig_ref != "":
|
||||
spot.sig = sig
|
||||
sig_refs = [SIGRef(id=source_spot["actSiteID"], sig=source_spot["actClass"].upper())]
|
||||
sig_refs = [
|
||||
SIGRef(
|
||||
id=source_spot["actSiteID"],
|
||||
sig=source_spot["actClass"].upper(),
|
||||
)
|
||||
]
|
||||
spot.sig_refs = sig_refs
|
||||
|
||||
# Free text location is not present in all spots, so only add it if it's set
|
||||
@@ -61,7 +81,16 @@ class ParksNPeaks(HTTPSpotProvider):
|
||||
sig_refs[0].name = source_spot["actLocation"]
|
||||
|
||||
# Log a warning for the developer if PnP gives us an unknown programme we've never seen before
|
||||
if sig not in ["POTA", "SOTA", "WWFF", "SIOTA", "ZLOTA", "KRMNPA", "SANPCPA", "LLOTA"]:
|
||||
if sig not in [
|
||||
"POTA",
|
||||
"SOTA",
|
||||
"WWFF",
|
||||
"SIOTA",
|
||||
"ZLOTA",
|
||||
"KRMNPA",
|
||||
"SANPCPA",
|
||||
"LLOTA",
|
||||
]:
|
||||
logging.warning(f"PNP spot found with sig {sig}, developer needs to add support for this!")
|
||||
|
||||
# Add new spot to the list
|
||||
@@ -77,7 +106,8 @@ class ParksNPeaks(HTTPSpotProvider):
|
||||
api_key = credentials.get("api_key", "")
|
||||
if not user_id or not api_key:
|
||||
raise ValueError(
|
||||
"Parks N Peaks user ID and API key are required. Get yours from your Parks N Peaks account.")
|
||||
"Parks N Peaks user ID and API key are required. Get yours from your Parks N Peaks account."
|
||||
)
|
||||
sig_ref = spot.sig_refs[0].id if spot.sig_refs else ""
|
||||
body = {
|
||||
"actClass": spot.sig or "",
|
||||
|
||||
+24
-14
@@ -24,21 +24,31 @@ class POTA(HTTPSpotProvider):
|
||||
# Iterate through source data
|
||||
for source_spot in http_response.json():
|
||||
# Convert to our spot format
|
||||
spot = Spot(source=self.name,
|
||||
source_id=source_spot["spotId"],
|
||||
dx_call=source_spot["activator"].upper(),
|
||||
de_call=source_spot["spotter"].upper(),
|
||||
freq=float(source_spot["frequency"]) * 1000,
|
||||
mode=source_spot["mode"].upper(),
|
||||
comment=source_spot["comments"],
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
source_id=source_spot["spotId"],
|
||||
dx_call=source_spot["activator"].upper(),
|
||||
de_call=source_spot["spotter"].upper(),
|
||||
freq=float(source_spot["frequency"]) * 1000,
|
||||
mode=source_spot["mode"].upper(),
|
||||
comment=source_spot["comments"],
|
||||
sig="POTA",
|
||||
sig_refs=[
|
||||
SIGRef(
|
||||
id=source_spot["reference"],
|
||||
sig="POTA",
|
||||
sig_refs=[SIGRef(id=source_spot["reference"], sig="POTA", name=source_spot["name"],
|
||||
latitude=source_spot["latitude"], longitude=source_spot["longitude"])],
|
||||
time=datetime.strptime(source_spot["spotTime"], "%Y-%m-%dT%H:%M:%S").replace(
|
||||
tzinfo=pytz.UTC).timestamp(),
|
||||
dx_grid=source_spot["grid6"],
|
||||
dx_latitude=source_spot["latitude"],
|
||||
dx_longitude=source_spot["longitude"])
|
||||
name=source_spot["name"],
|
||||
latitude=source_spot["latitude"],
|
||||
longitude=source_spot["longitude"],
|
||||
)
|
||||
],
|
||||
time=datetime.strptime(source_spot["spotTime"], "%Y-%m-%dT%H:%M:%S")
|
||||
.replace(tzinfo=pytz.UTC)
|
||||
.timestamp(),
|
||||
dx_grid=source_spot["grid6"],
|
||||
dx_latitude=source_spot["latitude"],
|
||||
dx_longitude=source_spot["longitude"],
|
||||
)
|
||||
|
||||
# Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do
|
||||
# that for us.
|
||||
|
||||
+15
-9
@@ -18,7 +18,8 @@ class RBN(SpotProvider):
|
||||
|
||||
_LINE_PATTERN = re.compile(
|
||||
r"^DX de ([a-z0-9/]+)-.*:\s+([0-9.]+)\s+([a-z0-9/]+)\s+(.*)\s+(\d{4}Z)",
|
||||
re.IGNORECASE)
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
def __init__(self, provider_config):
|
||||
"""Constructor requires port number."""
|
||||
@@ -64,14 +65,19 @@ class RBN(SpotProvider):
|
||||
match = self._LINE_PATTERN.match(telnet_output.decode("latin-1"))
|
||||
if match:
|
||||
spot_time = datetime.strptime(match.group(5), "%H%MZ")
|
||||
spot_datetime = datetime.combine(datetime.now(pytz.UTC).date(), spot_time.time(),
|
||||
tzinfo=pytz.UTC)
|
||||
spot = Spot(source=self.name,
|
||||
dx_call=match.group(3),
|
||||
de_call=match.group(1),
|
||||
freq=float(match.group(2)) * 1000,
|
||||
comment=match.group(4).strip(),
|
||||
time=spot_datetime.timestamp())
|
||||
spot_datetime = datetime.combine(
|
||||
datetime.now(pytz.UTC).date(),
|
||||
spot_time.time(),
|
||||
tzinfo=pytz.UTC,
|
||||
)
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
dx_call=match.group(3),
|
||||
de_call=match.group(1),
|
||||
freq=float(match.group(2)) * 1000,
|
||||
comment=match.group(4).strip(),
|
||||
time=spot_datetime.timestamp(),
|
||||
)
|
||||
|
||||
# Add to our list
|
||||
self._submit(spot)
|
||||
|
||||
+36
-23
@@ -2,9 +2,9 @@ import logging
|
||||
from datetime import datetime
|
||||
|
||||
import requests
|
||||
from requests.exceptions import ConnectionError, ReadTimeout, ConnectTimeout
|
||||
from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
|
||||
|
||||
from core.constants import HTTP_HEADERS, SSB_SUB_MODES, DV_SUB_MODES
|
||||
from core.constants import DV_SUB_MODES, HTTP_HEADERS, SSB_SUB_MODES
|
||||
from data.sig_ref import SIGRef
|
||||
from data.spot import Spot
|
||||
from providers.spot.http_spot_provider import HTTPSpotProvider
|
||||
@@ -41,24 +41,33 @@ class SOTA(HTTPSpotProvider):
|
||||
# Iterate through source data
|
||||
for source_spot in source_data:
|
||||
# Convert to our spot format
|
||||
spot = Spot(source=self.name,
|
||||
source_id=source_spot["id"],
|
||||
dx_call=source_spot["activatorCallsign"].upper(),
|
||||
dx_name=source_spot["activatorName"],
|
||||
de_call=source_spot["callsign"].upper(),
|
||||
freq=(float(source_spot["frequency"]) * 1000000) if (
|
||||
source_spot["frequency"] is not None) else None,
|
||||
# Seen SOTA spots with no frequency!
|
||||
mode=source_spot["mode"].upper(),
|
||||
comment=source_spot["comments"],
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
source_id=source_spot["id"],
|
||||
dx_call=source_spot["activatorCallsign"].upper(),
|
||||
dx_name=source_spot["activatorName"],
|
||||
de_call=source_spot["callsign"].upper(),
|
||||
freq=(float(source_spot["frequency"]) * 1000000)
|
||||
if (source_spot["frequency"] is not None)
|
||||
else None,
|
||||
# Seen SOTA spots with no frequency!
|
||||
mode=source_spot["mode"].upper(),
|
||||
comment=source_spot["comments"],
|
||||
sig="SOTA",
|
||||
sig_refs=[
|
||||
SIGRef(
|
||||
id=source_spot["summitCode"],
|
||||
sig="SOTA",
|
||||
sig_refs=[SIGRef(id=source_spot["summitCode"], sig="SOTA",
|
||||
name=source_spot["summitName"], latitude=source_spot["latitude"],
|
||||
longitude=source_spot["longitude"],
|
||||
activation_score=source_spot["points"])],
|
||||
dx_latitude=source_spot["latitude"],
|
||||
dx_longitude=source_spot["longitude"],
|
||||
time=datetime.fromisoformat(source_spot["timeStamp"].replace("Z", "+00:00")).timestamp())
|
||||
name=source_spot["summitName"],
|
||||
latitude=source_spot["latitude"],
|
||||
longitude=source_spot["longitude"],
|
||||
activation_score=source_spot["points"],
|
||||
)
|
||||
],
|
||||
dx_latitude=source_spot["latitude"],
|
||||
dx_longitude=source_spot["longitude"],
|
||||
time=datetime.fromisoformat(source_spot["timeStamp"].replace("Z", "+00:00")).timestamp(),
|
||||
)
|
||||
|
||||
# Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do
|
||||
# that for us.
|
||||
@@ -66,7 +75,7 @@ class SOTA(HTTPSpotProvider):
|
||||
except ConnectionError:
|
||||
logging.warning("Connection error when accessing SOTA spots API")
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
logging.warning(f"Timeout when accessing SOTA spots API.")
|
||||
logging.warning("Timeout when accessing SOTA spots API.")
|
||||
return new_spots
|
||||
|
||||
def can_submit_spot(self, sig):
|
||||
@@ -102,10 +111,14 @@ class SOTA(HTTPSpotProvider):
|
||||
"mode": mode or "",
|
||||
"callsign": spot.de_call,
|
||||
"comments": spot.comment or "",
|
||||
"type": "TEST" # todo replatce with NORMAL/QRT once testing complete
|
||||
"type": "TEST", # todo replatce with NORMAL/QRT once testing complete
|
||||
}
|
||||
headers = {
|
||||
**HTTP_HEADERS,
|
||||
"Authorization": f"bearer {access_token}",
|
||||
"id_token": id_token,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
headers = {**HTTP_HEADERS, "Authorization": f"bearer {access_token}", "id_token": id_token,
|
||||
"Content-Type": "application/json"}
|
||||
response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30))
|
||||
if not response.ok:
|
||||
raise RuntimeError(f"SOTA API returned {response.status_code!s}: {response.text}")
|
||||
|
||||
@@ -32,7 +32,7 @@ class SpotProvider:
|
||||
|
||||
# Sort the batch so that earliest ones go in first. This helps keep the ordering correct when spots are fired
|
||||
# off to SSE listeners.
|
||||
spots = sorted(spots, key=lambda s: (s.time if s and s.time else 0))
|
||||
spots = sorted(spots, key=lambda s: s.time if s and s.time else 0)
|
||||
for spot in spots:
|
||||
if datetime.fromtimestamp(spot.time, pytz.UTC) > self.last_spot_time:
|
||||
# Fill in any blanks and add to the list
|
||||
|
||||
@@ -37,8 +37,7 @@ class SSESpotProvider(SpotProvider):
|
||||
try:
|
||||
event_source.close()
|
||||
except Exception:
|
||||
logging.exception(
|
||||
f"Exception closing SSE connection for {self.name} during stop()")
|
||||
logging.exception(f"Exception closing SSE connection for {self.name} during stop()")
|
||||
|
||||
if self._thread:
|
||||
self._thread.join(timeout=15)
|
||||
@@ -60,14 +59,20 @@ class SSESpotProvider(SpotProvider):
|
||||
try:
|
||||
logging.debug(f"Connecting to {self.name} spot API...")
|
||||
self.status = "Connecting"
|
||||
with EventSource(self._url, headers=HTTP_HEADERS, latest_event_id=self._last_event_id, timeout=10,
|
||||
on_open=self._on_open, on_error=self._on_error) as event_source:
|
||||
with EventSource(
|
||||
self._url,
|
||||
headers=HTTP_HEADERS,
|
||||
latest_event_id=self._last_event_id,
|
||||
timeout=10,
|
||||
on_open=self._on_open,
|
||||
on_error=self._on_error,
|
||||
) as event_source:
|
||||
self._set_event_source(event_source)
|
||||
try:
|
||||
for event in event_source:
|
||||
if self._stop_event.is_set():
|
||||
break
|
||||
if event.type == 'message':
|
||||
if event.type == "message":
|
||||
try:
|
||||
self._last_event_id = event.last_event_id
|
||||
new_spot = self._sse_message_to_spot(event.data)
|
||||
@@ -80,7 +85,8 @@ class SSESpotProvider(SpotProvider):
|
||||
|
||||
except Exception:
|
||||
logging.exception(
|
||||
f"Exception processing message from SSE Spot Provider ({self.name})")
|
||||
f"Exception processing message from SSE Spot Provider ({self.name})"
|
||||
)
|
||||
finally:
|
||||
self._set_event_source(None)
|
||||
|
||||
@@ -89,7 +95,7 @@ class SSESpotProvider(SpotProvider):
|
||||
logging.exception(f"Exception in SSE Spot Provider ({self.name})")
|
||||
else:
|
||||
self.status = "Disconnected"
|
||||
self._stop_event.wait(timeout=5) # Wait before trying to reconnect
|
||||
self._stop_event.wait(timeout=5) # Wait before trying to reconnect
|
||||
|
||||
def _sse_message_to_spot(self, message_data):
|
||||
"""Convert an SSE message received from the API into a spot. The whole message data is provided here so the subclass
|
||||
|
||||
+47
-26
@@ -14,8 +14,22 @@ class Tiles(HTTPSpotProvider):
|
||||
POLL_INTERVAL_SEC = 120
|
||||
SPOTS_URL = "https://icneuzxitdqtofutxbla.supabase.co/functions/v1/spots?active_hours=24"
|
||||
SUBMIT_URL = "https://icneuzxitdqtofutxbla.supabase.co/functions/v1/self-spot"
|
||||
VALID_MODES = ["SSB", "CW", "FT8", "FT4", "FM", "DMR", "D-STAR", "M17", "AX.25", "JS8Call", "PSK31", "Olivia",
|
||||
"VarAC", "Other"]
|
||||
VALID_MODES = [
|
||||
"SSB",
|
||||
"CW",
|
||||
"FT8",
|
||||
"FT4",
|
||||
"FM",
|
||||
"DMR",
|
||||
"D-STAR",
|
||||
"M17",
|
||||
"AX.25",
|
||||
"JS8Call",
|
||||
"PSK31",
|
||||
"Olivia",
|
||||
"VarAC",
|
||||
"Other",
|
||||
]
|
||||
|
||||
def __init__(self, provider_config):
|
||||
super().__init__("Tiles", provider_config, self.SPOTS_URL, self.POLL_INTERVAL_SEC)
|
||||
@@ -25,25 +39,33 @@ class Tiles(HTTPSpotProvider):
|
||||
# Iterate through source data
|
||||
for source_spot in http_response.json()["spots"]:
|
||||
# Convert to our spot format
|
||||
spot = Spot(source=self.name,
|
||||
source_id=source_spot["id"],
|
||||
dx_call=source_spot["call_sign"].upper(),
|
||||
# No separate spotter callsign, assume all spots are self-spots
|
||||
de_call=source_spot["call_sign"].upper(),
|
||||
freq=float(strip_extra_decimal_points(source_spot["frequency"])) * 1000000,
|
||||
mode=source_spot["mode"].upper(),
|
||||
comment=source_spot["notes"],
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
source_id=source_spot["id"],
|
||||
dx_call=source_spot["call_sign"].upper(),
|
||||
# No separate spotter callsign, assume all spots are self-spots
|
||||
de_call=source_spot["call_sign"].upper(),
|
||||
freq=float(strip_extra_decimal_points(source_spot["frequency"])) * 1000000,
|
||||
mode=source_spot["mode"].upper(),
|
||||
comment=source_spot["notes"],
|
||||
sig="Tiles",
|
||||
# Tiles spots can include POTA & SOTA references, but ignore those on the basis that we will get them separately from the POTA/SOTA providers anyway.
|
||||
# Just take the grid reference itself as the single Tiles SIG reference.
|
||||
sig_refs=[
|
||||
SIGRef(
|
||||
id=source_spot["maidenhead_grid"],
|
||||
sig="Tiles",
|
||||
# Tiles spots can include POTA & SOTA references, but ignore those on the basis that we will get them separately from the POTA/SOTA providers anyway.
|
||||
# Just take the grid reference itself as the single Tiles SIG reference.
|
||||
sig_refs=[SIGRef(id=source_spot["maidenhead_grid"], sig="Tiles",
|
||||
name=source_spot["maidenhead_grid"], latitude=source_spot["latitude"],
|
||||
longitude=source_spot["longitude"])],
|
||||
time=datetime.fromisoformat(source_spot["created_at"].replace("Z", "+00:00")).timestamp(),
|
||||
dx_grid=source_spot["maidenhead_grid"],
|
||||
dx_latitude=source_spot["latitude"],
|
||||
dx_longitude=source_spot["longitude"],
|
||||
dx_location_source="GRID")
|
||||
name=source_spot["maidenhead_grid"],
|
||||
latitude=source_spot["latitude"],
|
||||
longitude=source_spot["longitude"],
|
||||
)
|
||||
],
|
||||
time=datetime.fromisoformat(source_spot["created_at"].replace("Z", "+00:00")).timestamp(),
|
||||
dx_grid=source_spot["maidenhead_grid"],
|
||||
dx_latitude=source_spot["latitude"],
|
||||
dx_longitude=source_spot["longitude"],
|
||||
dx_location_source="GRID",
|
||||
)
|
||||
|
||||
# Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do
|
||||
# that for us.
|
||||
@@ -56,7 +78,6 @@ class Tiles(HTTPSpotProvider):
|
||||
def submit_spot(self, spot, credentials):
|
||||
# Tiles on the air currently only supports *self* spots
|
||||
if spot.dx_call == spot.de_call:
|
||||
|
||||
# Figure out a valid mode. Borrowed this from PoLo :)
|
||||
# https://github.com/ham2k/app-polo/blob/main/src/extensions/activities/sota/SOTAPostSelfSpot.js
|
||||
if spot.mode:
|
||||
@@ -80,24 +101,24 @@ class Tiles(HTTPSpotProvider):
|
||||
"lat": spot.dx_latitude or None,
|
||||
"lon": spot.dx_longitude or None,
|
||||
"qrt": spot.qrt or False,
|
||||
"pin": credentials.get("offline_spot_gateway_pin", "")
|
||||
"pin": credentials.get("offline_spot_gateway_pin", ""),
|
||||
}
|
||||
headers = {**HTTP_HEADERS, "Content-Type": "application/json"}
|
||||
response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30))
|
||||
if not response.ok:
|
||||
raise RuntimeError(
|
||||
f"Tiles on the Air API returned {response.status_code!s}: {response.text}")
|
||||
raise RuntimeError(f"Tiles on the Air API returned {response.status_code!s}: {response.text}")
|
||||
else:
|
||||
raise RuntimeError("The Tiles on the Air API requires a mode to be set.")
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"The Tiles on the Air API only supports self-spots, the DX call and spotter call must match.")
|
||||
"The Tiles on the Air API only supports self-spots, the DX call and spotter call must match."
|
||||
)
|
||||
|
||||
|
||||
# Utility function to keep the first decimal point in a given string but remove any others. Used to parse Tiles'
|
||||
# strange frequency format where we can sometimes have e.g. "14.123.5".
|
||||
def strip_extra_decimal_points(s):
|
||||
parts = s.split('.', 1)
|
||||
parts = s.split(".", 1)
|
||||
if len(parts) == 1:
|
||||
return s
|
||||
return f"{parts[0]}.{parts[1].replace('.', '')}"
|
||||
|
||||
@@ -26,14 +26,17 @@ class Towers(HTTPSpotProvider):
|
||||
likely_freq = float(source_spot["freq"]) * 1000
|
||||
if likely_freq < 1000000:
|
||||
likely_freq = likely_freq * 1000
|
||||
spot = Spot(source=self.name,
|
||||
dx_call=source_spot["call"].upper(),
|
||||
freq=likely_freq,
|
||||
comment=source_spot["comment"],
|
||||
sig="Towers",
|
||||
sig_refs=[SIGRef(id=source_spot["ref"], sig="Towers")],
|
||||
time=datetime.strptime(response_json["updated"][:10] + source_spot["time"],
|
||||
"%Y-%m-%d%H:%M").timestamp())
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
dx_call=source_spot["call"].upper(),
|
||||
freq=likely_freq,
|
||||
comment=source_spot["comment"],
|
||||
sig="Towers",
|
||||
sig_refs=[SIGRef(id=source_spot["ref"], sig="Towers")],
|
||||
time=datetime.strptime(
|
||||
response_json["updated"][:10] + source_spot["time"], "%Y-%m-%d%H:%M"
|
||||
).timestamp(),
|
||||
)
|
||||
|
||||
# Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do
|
||||
# that for us.
|
||||
|
||||
@@ -36,42 +36,56 @@ class UKPacketNet(HTTPSpotProvider):
|
||||
# First build a "full" comment combining some of the extra info
|
||||
comment = listed_port["comment"] if "comment" in listed_port else ""
|
||||
comment = f"{comment} {listed_port['mode']}" if "mode" in listed_port else comment
|
||||
comment = f"{comment} {listed_port['modulation']}" if "modulation" in listed_port else comment
|
||||
comment = f"{comment} {listed_port['baud']!s} baud" if "baud" in listed_port and listed_port[
|
||||
"baud"] > 0 else comment
|
||||
comment = (
|
||||
f"{comment} {listed_port['modulation']}" if "modulation" in listed_port else comment
|
||||
)
|
||||
comment = (
|
||||
f"{comment} {listed_port['baud']!s} baud"
|
||||
if "baud" in listed_port and listed_port["baud"] > 0
|
||||
else comment
|
||||
)
|
||||
|
||||
# Get frequency from the comment if it's not set properly in the data structure. This is
|
||||
# very hacky but a lot of node comments contain their frequency as the first or second
|
||||
# word of their comment, but not in the proper data structure field.
|
||||
freq = listed_port["freq"] if "freq" in listed_port and listed_port[
|
||||
"freq"] > 0 else None
|
||||
freq = (
|
||||
listed_port["freq"] if "freq" in listed_port and listed_port["freq"] > 0 else None
|
||||
)
|
||||
if not freq and comment:
|
||||
possible_freq = comment.split(" ")[0].upper().replace("MHZ", "")
|
||||
if re.match(r"^[0-9.]+$",
|
||||
possible_freq) and possible_freq != "1200" and possible_freq != "9600":
|
||||
if (
|
||||
re.match(r"^[0-9.]+$", possible_freq)
|
||||
and possible_freq != "1200"
|
||||
and possible_freq != "9600"
|
||||
):
|
||||
freq = float(possible_freq) * 1000000
|
||||
if not freq and len(comment.split(" ")) > 1:
|
||||
possible_freq = comment.split(" ")[1].upper().replace("MHZ", "")
|
||||
if re.match(r"^[0-9.]+$",
|
||||
possible_freq) and possible_freq != "1200" and possible_freq != "9600":
|
||||
if (
|
||||
re.match(r"^[0-9.]+$", possible_freq)
|
||||
and possible_freq != "1200"
|
||||
and possible_freq != "9600"
|
||||
):
|
||||
freq = float(possible_freq) * 1000000
|
||||
# Check for a found frequency likely having been in kHz, sorry to all GHz packet folks
|
||||
if freq and freq > 1000000000:
|
||||
freq = freq / 1000
|
||||
|
||||
# Now build the spot object
|
||||
spot = Spot(source=self.name,
|
||||
dx_call=heard["callsign"].upper(),
|
||||
de_call=node["callsign"].upper(),
|
||||
freq=freq,
|
||||
mode="PKT",
|
||||
comment=comment,
|
||||
time=datetime.strptime(heard["lastHeard"], "%Y-%m-%d %H:%M:%S").replace(
|
||||
tzinfo=pytz.UTC).timestamp(),
|
||||
de_grid=node["location"]["locator"] if "locator" in node[
|
||||
"location"] else None,
|
||||
de_latitude=node["location"]["coords"]["lat"],
|
||||
de_longitude=node["location"]["coords"]["lon"])
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
dx_call=heard["callsign"].upper(),
|
||||
de_call=node["callsign"].upper(),
|
||||
freq=freq,
|
||||
mode="PKT",
|
||||
comment=comment,
|
||||
time=datetime.strptime(heard["lastHeard"], "%Y-%m-%d %H:%M:%S")
|
||||
.replace(tzinfo=pytz.UTC)
|
||||
.timestamp(),
|
||||
de_grid=node["location"]["locator"] if "locator" in node["location"] else None,
|
||||
de_latitude=node["location"]["coords"]["lat"],
|
||||
de_longitude=node["location"]["coords"]["lon"],
|
||||
)
|
||||
|
||||
# Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do
|
||||
# that for us.
|
||||
@@ -84,8 +98,9 @@ class UKPacketNet(HTTPSpotProvider):
|
||||
# data, and we can use that to look these up.
|
||||
for spot in new_spots:
|
||||
if spot.dx_call in nodes:
|
||||
spot.dx_grid = nodes[spot.dx_call]["location"]["locator"] if "locator" in nodes[spot.dx_call][
|
||||
"location"] else None
|
||||
spot.dx_grid = (
|
||||
nodes[spot.dx_call]["location"]["locator"] if "locator" in nodes[spot.dx_call]["location"] else None
|
||||
)
|
||||
spot.dx_latitude = nodes[spot.dx_call]["location"]["coords"]["lat"]
|
||||
spot.dx_longitude = nodes[spot.dx_call]["location"]["coords"]["lon"]
|
||||
|
||||
|
||||
@@ -60,8 +60,7 @@ class WebsocketSpotProvider(SpotProvider):
|
||||
logging.debug(f"Received data from {self.name} spot API.")
|
||||
|
||||
except Exception:
|
||||
logging.exception(
|
||||
f"Exception processing message from Websocket Spot Provider ({self.name})")
|
||||
logging.exception(f"Exception processing message from Websocket Spot Provider ({self.name})")
|
||||
|
||||
except Exception as e:
|
||||
self.status = "Error"
|
||||
|
||||
+18
-13
@@ -28,10 +28,13 @@ class WOTA(HTTPSpotProvider):
|
||||
rss = cast(RSS, Parser.parse(http_response.content.decode("utf-8-sig")))
|
||||
# Iterate through source data
|
||||
for source_spot in rss.channel.items:
|
||||
|
||||
try:
|
||||
# Reject GUID missing or zero
|
||||
if not source_spot.guid or not source_spot.guid.content or source_spot.guid.content == "http://www.wota.org.uk/spots/0":
|
||||
if (
|
||||
not source_spot.guid
|
||||
or not source_spot.guid.content
|
||||
or source_spot.guid.content == "http://www.wota.org.uk/spots/0"
|
||||
):
|
||||
continue
|
||||
|
||||
# Pick apart the title
|
||||
@@ -48,7 +51,7 @@ class WOTA(HTTPSpotProvider):
|
||||
# Pick apart the description
|
||||
desc_split = source_spot.description.split(". ")
|
||||
freq_mode = desc_split[0].replace("Frequencies/modes:", "").strip()
|
||||
freq_mode_split = re.split(r'[\-\s]+', freq_mode)
|
||||
freq_mode_split = re.split(r"[\-\s]+", freq_mode)
|
||||
freq_hz = float(freq_mode_split[0].replace("'", ".")) * 1000000
|
||||
mode = None
|
||||
if len(freq_mode_split) > 1:
|
||||
@@ -64,16 +67,18 @@ class WOTA(HTTPSpotProvider):
|
||||
time = datetime.strptime(source_spot.pub_date.content, self.RSS_DATE_TIME_FORMAT).astimezone(pytz.UTC)
|
||||
|
||||
# Convert to our spot format
|
||||
spot = Spot(source=self.name,
|
||||
source_id=source_spot.guid.content,
|
||||
dx_call=dx_call,
|
||||
de_call=spotter,
|
||||
freq=freq_hz,
|
||||
mode=mode,
|
||||
comment=comment,
|
||||
sig="WOTA",
|
||||
sig_refs=[SIGRef(id=ref, sig="WOTA", name=ref_name)] if ref else [],
|
||||
time=time.timestamp())
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
source_id=source_spot.guid.content,
|
||||
dx_call=dx_call,
|
||||
de_call=spotter,
|
||||
freq=freq_hz,
|
||||
mode=mode,
|
||||
comment=comment,
|
||||
sig="WOTA",
|
||||
sig_refs=[SIGRef(id=ref, sig="WOTA", name=ref_name)] if ref else [],
|
||||
time=time.timestamp(),
|
||||
)
|
||||
|
||||
new_spots.append(spot)
|
||||
except Exception as e:
|
||||
|
||||
+24
-17
@@ -20,25 +20,32 @@ class WWBOTA(SSESpotProvider):
|
||||
# n-fer activations.
|
||||
refs = []
|
||||
for ref in source_spot["references"]:
|
||||
sigref = SIGRef(id=ref["reference"], sig="WWBOTA", name=ref["name"], latitude=ref["lat"],
|
||||
longitude=ref["long"])
|
||||
sigref = SIGRef(
|
||||
id=ref["reference"],
|
||||
sig="WWBOTA",
|
||||
name=ref["name"],
|
||||
latitude=ref["lat"],
|
||||
longitude=ref["long"],
|
||||
)
|
||||
refs.append(sigref)
|
||||
|
||||
spot = Spot(source=self.name,
|
||||
dx_call=source_spot["call"].upper(),
|
||||
de_call=source_spot["spotter"].upper(),
|
||||
freq=float(source_spot["freq"]) * 1000000,
|
||||
mode=source_spot["mode"].upper(),
|
||||
comment=source_spot["comment"],
|
||||
sig="WWBOTA",
|
||||
sig_refs=refs,
|
||||
time=datetime.fromisoformat(source_spot["time"].replace("Z", "+00:00")).timestamp(),
|
||||
# WWBOTA spots can contain multiple references for bunkers being activated simultaneously. For
|
||||
# now, we will just pick the first one to use as our grid, latitude and longitude.
|
||||
dx_grid=source_spot["references"][0]["locator"],
|
||||
dx_latitude=source_spot["references"][0]["lat"],
|
||||
dx_longitude=source_spot["references"][0]["long"],
|
||||
qrt=source_spot["type"] == "QRT")
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
dx_call=source_spot["call"].upper(),
|
||||
de_call=source_spot["spotter"].upper(),
|
||||
freq=float(source_spot["freq"]) * 1000000,
|
||||
mode=source_spot["mode"].upper(),
|
||||
comment=source_spot["comment"],
|
||||
sig="WWBOTA",
|
||||
sig_refs=refs,
|
||||
time=datetime.fromisoformat(source_spot["time"].replace("Z", "+00:00")).timestamp(),
|
||||
# WWBOTA spots can contain multiple references for bunkers being activated simultaneously. For
|
||||
# now, we will just pick the first one to use as our grid, latitude and longitude.
|
||||
dx_grid=source_spot["references"][0]["locator"],
|
||||
dx_latitude=source_spot["references"][0]["lat"],
|
||||
dx_longitude=source_spot["references"][0]["long"],
|
||||
qrt=source_spot["type"] == "QRT",
|
||||
)
|
||||
|
||||
# WWBOTA does support a special "Test" spot type, we need to avoid adding that.
|
||||
return spot if source_spot["type"] != "Test" else None
|
||||
|
||||
+21
-12
@@ -21,19 +21,28 @@ class WWFF(HTTPSpotProvider):
|
||||
# Iterate through source data
|
||||
for source_spot in http_response.json():
|
||||
# Convert to our spot format
|
||||
spot = Spot(source=self.name,
|
||||
source_id=source_spot["id"],
|
||||
dx_call=source_spot["activator"].upper(),
|
||||
de_call=source_spot["spotter"].upper(),
|
||||
freq=float(source_spot["frequency_khz"]) * 1000,
|
||||
mode=source_spot["mode"].upper(),
|
||||
comment=source_spot["remarks"],
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
source_id=source_spot["id"],
|
||||
dx_call=source_spot["activator"].upper(),
|
||||
de_call=source_spot["spotter"].upper(),
|
||||
freq=float(source_spot["frequency_khz"]) * 1000,
|
||||
mode=source_spot["mode"].upper(),
|
||||
comment=source_spot["remarks"],
|
||||
sig="WWFF",
|
||||
sig_refs=[
|
||||
SIGRef(
|
||||
id=source_spot["reference"],
|
||||
sig="WWFF",
|
||||
sig_refs=[SIGRef(id=source_spot["reference"], sig="WWFF", name=source_spot["reference_name"],
|
||||
latitude=source_spot["latitude"], longitude=source_spot["longitude"])],
|
||||
time=datetime.fromtimestamp(source_spot["spot_time"], tz=pytz.UTC).timestamp(),
|
||||
dx_latitude=source_spot["latitude"],
|
||||
dx_longitude=source_spot["longitude"])
|
||||
name=source_spot["reference_name"],
|
||||
latitude=source_spot["latitude"],
|
||||
longitude=source_spot["longitude"],
|
||||
)
|
||||
],
|
||||
time=datetime.fromtimestamp(source_spot["spot_time"], tz=pytz.UTC).timestamp(),
|
||||
dx_latitude=source_spot["latitude"],
|
||||
dx_longitude=source_spot["longitude"],
|
||||
)
|
||||
|
||||
# Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do
|
||||
# that for us.
|
||||
|
||||
+17
-10
@@ -29,14 +29,21 @@ class XOTA(WebsocketSpotProvider):
|
||||
string = b.decode("utf-8")
|
||||
source_spot = json.loads(string)
|
||||
ref_id = f"{self._sig_ref_prefix} {source_spot['reference']['title']}"
|
||||
spot = Spot(source=self.name,
|
||||
source_id=source_spot["id"],
|
||||
dx_call=source_spot["stationCallSign"].upper(),
|
||||
freq=float(source_spot["freq"]) * 1000,
|
||||
mode=source_spot["mode"].upper(),
|
||||
sig=self.SIG,
|
||||
sig_refs=[
|
||||
SIGRef(id=ref_id, sig=self.SIG or "", url=source_spot["reference"]["website"])],
|
||||
time=datetime.now(pytz.UTC).timestamp(),
|
||||
qrt=source_spot["state"] != "active")
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
source_id=source_spot["id"],
|
||||
dx_call=source_spot["stationCallSign"].upper(),
|
||||
freq=float(source_spot["freq"]) * 1000,
|
||||
mode=source_spot["mode"].upper(),
|
||||
sig=self.SIG,
|
||||
sig_refs=[
|
||||
SIGRef(
|
||||
id=ref_id,
|
||||
sig=self.SIG or "",
|
||||
url=source_spot["reference"]["website"],
|
||||
)
|
||||
],
|
||||
time=datetime.now(pytz.UTC).timestamp(),
|
||||
qrt=source_spot["state"] != "active",
|
||||
)
|
||||
return spot
|
||||
|
||||
+19
-10
@@ -26,17 +26,26 @@ class ZLOTA(HTTPSpotProvider):
|
||||
freq_hz = freq_hz * 1000
|
||||
|
||||
# Convert to our spot format
|
||||
spot = Spot(source=self.name,
|
||||
source_id=source_spot["id"],
|
||||
dx_call=source_spot["activator"].upper(),
|
||||
de_call=source_spot["spotter"].upper(),
|
||||
freq=freq_hz,
|
||||
mode=source_spot["mode"].upper().strip(),
|
||||
comment=source_spot["comments"],
|
||||
spot = Spot(
|
||||
source=self.name,
|
||||
source_id=source_spot["id"],
|
||||
dx_call=source_spot["activator"].upper(),
|
||||
de_call=source_spot["spotter"].upper(),
|
||||
freq=freq_hz,
|
||||
mode=source_spot["mode"].upper().strip(),
|
||||
comment=source_spot["comments"],
|
||||
sig="ZLOTA",
|
||||
sig_refs=[
|
||||
SIGRef(
|
||||
id=source_spot["reference"],
|
||||
sig="ZLOTA",
|
||||
sig_refs=[SIGRef(id=source_spot["reference"], sig="ZLOTA", name=source_spot["name"])],
|
||||
time=datetime.fromisoformat(source_spot["referenced_time"].replace("Z", "+00:00")).astimezone(
|
||||
pytz.UTC).timestamp())
|
||||
name=source_spot["name"],
|
||||
)
|
||||
],
|
||||
time=datetime.fromisoformat(source_spot["referenced_time"].replace("Z", "+00:00"))
|
||||
.astimezone(pytz.UTC)
|
||||
.timestamp(),
|
||||
)
|
||||
|
||||
new_spots.append(spot)
|
||||
return new_spots
|
||||
|
||||
Reference in New Issue
Block a user