mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-20 06:17:41 +00:00
171 lines
8.3 KiB
Python
171 lines
8.3 KiB
Python
import logging
|
|
from datetime import datetime
|
|
|
|
import pytz
|
|
|
|
from core.constants import HTTP_HEADERS
|
|
from core.url_data_cache import URLDataCache
|
|
from data.sig_ref import SIGRef
|
|
from data.spot import Spot
|
|
from providers.spot.http_spot_provider import HTTPSpotProvider
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class GMA(HTTPSpotProvider):
|
|
"""Spot provider for General Mountain Activity"""
|
|
|
|
POLL_INTERVAL_SEC = 120
|
|
SPOTS_URL = "https://www.gma.rocks/api/spots/25/"
|
|
# GMA spots don't contain the details of the programme they are for, we need a separate lookup for that
|
|
REF_INFO_URL_ROOT = "https://www.gma.rocks/api/ref/?"
|
|
|
|
def __init__(self, provider_config):
|
|
# Ensure there is an API key in our config, and set up the query URL using it. If no key is provided,
|
|
# disable this spot provider.
|
|
self._api_key = provider_config.get("api_key", "")
|
|
if self._api_key == "":
|
|
provider_config["enabled"] = False
|
|
logger.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,
|
|
)
|
|
|
|
def _http_response_to_spots(self, http_response):
|
|
new_spots = []
|
|
# Iterate through source data
|
|
if "RCD" in http_response.json():
|
|
for source_spot in http_response.json()["RCD"]:
|
|
# Convert to our spot format
|
|
# Seen GMA spots with no (or empty) lat/lon
|
|
lat = float(source_spot["LAT"]) if (source_spot["LAT"] and source_spot["LAT"] != "") else None
|
|
lon = float(source_spot["LON"]) if (source_spot["LON"] and source_spot["LON"] != "") else None
|
|
|
|
# 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()
|
|
)
|
|
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",
|
|
)
|
|
|
|
# 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,
|
|
)
|
|
# 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"
|
|
):
|
|
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 ref_info
|
|
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"
|
|
spot.sig = "GMA"
|
|
case "IOTA Island":
|
|
spot.sig_refs[0].sig = "IOTA"
|
|
spot.sig = "IOTA"
|
|
case "GMA Island":
|
|
spot.sig_refs[0].sig = "GMA Island"
|
|
spot.sig = "GMA Island"
|
|
case "Lighthouse (ILLW)":
|
|
spot.sig_refs[0].sig = "ILLW"
|
|
spot.sig = "ILLW"
|
|
case "Lighthouse (ARLHS)":
|
|
spot.sig_refs[0].sig = "ARLHS"
|
|
spot.sig = "ARLHS"
|
|
case "Castle":
|
|
spot.sig_refs[0].sig = "WCA"
|
|
spot.sig = "WCA"
|
|
case "Mill":
|
|
spot.sig_refs[0].sig = "MOTA"
|
|
spot.sig = "MOTA"
|
|
case _:
|
|
logger.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"]
|
|
|
|
elif not ref_response.from_cache:
|
|
if not ref_response.ok:
|
|
logger.warning(
|
|
f"HTTP {ref_response.status_code} when looking up GMA ref {source_spot['REF']}"
|
|
)
|
|
else:
|
|
logger.debug(
|
|
f"GMA API had no data for {source_spot['REF']}, it may not be a valid reference."
|
|
)
|
|
except Exception:
|
|
logger.exception(
|
|
f"Exception when looking up {self.REF_INFO_URL_ROOT}{source_spot['REF']}, SIG data will not be populated for the spot."
|
|
)
|
|
|
|
# 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)
|
|
else:
|
|
logger.warning(f"The GMA API returned an unexpected response (HTTP {http_response.status_code}).")
|
|
|
|
return new_spots
|
|
|
|
def can_submit_spot(self, sig):
|
|
return sig == "GMA"
|
|
|
|
def submit_spot(self, spot, credentials):
|
|
# TODO: Implement.
|
|
# Spotting to GMA is documented: https://www.cqgma.org/api/doc/apigma_spot.pdf We (or the user) need a GMA account, and to send the password in plaintext(!!)
|
|
raise NotImplementedError("GMA upstream spot submission is not yet implemented")
|