mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-06-24 05:35:10 +00:00
Merge branch 'main' into 95-send-spots-to-xota
# Conflicts: # README.md # server/handlers/api/addspot.py # server/handlers/api/options.py # spotproviders/tiles.py # templates/about.html # templates/add_spot.html # templates/alerts.html # templates/api_only_home.html # templates/bands.html # templates/base.html # templates/conditions.html # templates/map.html # templates/spots.html # templates/status.html # webassets/css/style.css # webassets/js/add-spot.js # webassets/js/geo.js # webassets/js/ui-ham.js # webassets/js/utils.js
This commit is contained in:
@@ -37,20 +37,20 @@ class APRSIS(SpotProvider):
|
||||
|
||||
def _handle(self, data):
|
||||
# Split SSID in "from" call and store separately
|
||||
from_parts = data["from"].split("-").upper()
|
||||
dx_call = from_parts[0]
|
||||
dx_ssid = from_parts[1] if len(from_parts) > 1 else None
|
||||
via_parts = data["via"].split("-").upper()
|
||||
de_call = via_parts[0]
|
||||
de_ssid = via_parts[1] if len(via_parts) > 1 else None
|
||||
from_parts = str(data["from"]).split("-")
|
||||
dx_call = from_parts[0].upper()
|
||||
dx_ssid = from_parts[1].upper() if len(from_parts) > 1 else None
|
||||
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=data["comment"] if "comment" in data else None,
|
||||
dx_latitude=data["latitude"] if "latitude" in data else None,
|
||||
dx_longitude=data["longitude"] if "longitude" in data else None,
|
||||
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"
|
||||
|
||||
|
||||
@@ -72,7 +72,8 @@ 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_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),
|
||||
|
||||
@@ -38,10 +38,10 @@ class GMA(HTTPSpotProvider):
|
||||
time=datetime.strptime(source_spot["DATE"] + source_spot["TIME"], "%Y%m%d%H%M").replace(
|
||||
tzinfo=pytz.UTC).timestamp(),
|
||||
dx_latitude=float(source_spot["LAT"]) if (
|
||||
source_spot["LAT"] and source_spot["LAT"] != "") else None,
|
||||
source_spot["LAT"] and source_spot["LAT"] != "") else None,
|
||||
# Seen GMA spots with no (or empty) lat/lon
|
||||
dx_longitude=float(source_spot["LON"]) if (
|
||||
source_spot["LON"] and source_spot["LON"] != "") else None)
|
||||
source_spot["LON"] and source_spot["LON"] != "") else None)
|
||||
|
||||
# GMA doesn't give what programme (SIG) the reference is for until we separately look it up.
|
||||
if "REF" in source_spot:
|
||||
@@ -55,7 +55,7 @@ class GMA(HTTPSpotProvider):
|
||||
# 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 "reftype" in ref_info and ref_info["reftype"] not in ["POTA", "WWFF"] and (
|
||||
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":
|
||||
|
||||
@@ -45,6 +45,8 @@ class HEMA(HTTPSpotProvider):
|
||||
# Fiddle with some data to extract bits we need. Freq/mode and spotter/comment come in combined fields.
|
||||
freq_mode_match = re.search(self.FREQ_MODE_PATTERN, spot_items[5])
|
||||
spotter_comment_match = re.search(self.SPOTTER_COMMENT_PATTERN, spot_items[6])
|
||||
if not freq_mode_match or not spotter_comment_match:
|
||||
continue
|
||||
|
||||
# Convert to our spot format
|
||||
spot = Spot(source=self.name,
|
||||
|
||||
@@ -22,8 +22,8 @@ class LLOTA(HTTPSpotProvider):
|
||||
comment = None
|
||||
spotter = None
|
||||
if "history" in source_spot and len(source_spot["history"]) > 0:
|
||||
comment = source_spot["history"][-1]["comment"]
|
||||
spotter = source_spot["history"][-1]["spotter_callsign"]
|
||||
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"],
|
||||
|
||||
@@ -43,9 +43,9 @@ class ParksNPeaks(HTTPSpotProvider):
|
||||
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)
|
||||
m = re.search(r"\(de ([A-Za-z0-9]*)\)", spot.comment or "")
|
||||
if not spot.de_call and m:
|
||||
spot.de_call = m.group(1)
|
||||
spot.de_call = str(m.group(1))
|
||||
|
||||
# Record SIG information. Sometimes we get a "SIG" of "QRP", which we ignore as it's not a programme with a
|
||||
# defined set of references
|
||||
@@ -53,11 +53,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
|
||||
spot.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
|
||||
if "actLocation" in source_spot and source_spot["actLocation"] != "":
|
||||
spot.sig_refs[0].name = source_spot["actLocation"]
|
||||
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"]:
|
||||
|
||||
@@ -46,7 +46,7 @@ class RBN(SpotProvider):
|
||||
self.status = "Connecting"
|
||||
logging.info("RBN port " + str(self._port) + " connecting...")
|
||||
self._telnet = telnetlib3.Telnet("telnet.reversebeacon.net", self._port)
|
||||
telnet_output = self._telnet.read_until("Please enter your call: ".encode("latin-1"))
|
||||
self._telnet.read_until("Please enter your call: ".encode("latin-1"))
|
||||
self._telnet.write((SERVER_OWNER_CALLSIGN + "\n").encode("latin-1"))
|
||||
connected = True
|
||||
logging.info("RBN port " + str(self._port) + " connected.")
|
||||
@@ -63,7 +63,8 @@ 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_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),
|
||||
|
||||
@@ -46,7 +46,7 @@ class SOTA(HTTPSpotProvider):
|
||||
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,
|
||||
source_spot["frequency"] is not None) else None,
|
||||
# Seen SOTA spots with no frequency!
|
||||
mode=source_spot["mode"].upper(),
|
||||
comment=source_spot["comments"],
|
||||
|
||||
@@ -35,7 +35,8 @@ class Tiles(HTTPSpotProvider):
|
||||
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"])],
|
||||
sig_refs=[SIGRef(id=source_spot["maidenhead_grid"], sig="Tiles",
|
||||
name=source_spot["maidenhead_grid"])],
|
||||
time=datetime.fromisoformat(source_spot["created_at"].replace("Z", "+00:00")).timestamp(),
|
||||
dx_grid=source_spot["maidenhead_grid"],
|
||||
dx_latitude=source_spot["latitude"],
|
||||
@@ -94,4 +95,4 @@ def strip_extra_decimal_points(s):
|
||||
parts = s.split('.', 1)
|
||||
if len(parts) == 1:
|
||||
return s
|
||||
return parts[0] + '.' + parts[1].replace('.', '')
|
||||
return parts[0] + '.' + parts[1].replace('.', '')
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import logging
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import cast
|
||||
|
||||
import pytz
|
||||
from rss_parser import Parser
|
||||
from rss_parser.models.rss import RSS
|
||||
|
||||
from data.sig_ref import SIGRef
|
||||
from data.spot import Spot
|
||||
@@ -23,7 +25,7 @@ class WOTA(HTTPSpotProvider):
|
||||
|
||||
def _http_response_to_spots(self, http_response):
|
||||
new_spots = []
|
||||
rss = Parser.parse(http_response.content.decode())
|
||||
rss = cast(RSS, Parser.parse(http_response.content.decode()))
|
||||
# Iterate through source data
|
||||
for source_spot in rss.channel.items:
|
||||
|
||||
@@ -39,15 +41,15 @@ class WOTA(HTTPSpotProvider):
|
||||
ref_name = None
|
||||
if len(title_split) > 1:
|
||||
ref_split = title_split[1].split(" - ")
|
||||
ref = ref_split[0]
|
||||
ref = str(ref_split[0])
|
||||
if len(ref_split) > 1:
|
||||
ref_name = ref_split[1]
|
||||
ref_name = str(ref_split[1])
|
||||
|
||||
# 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_hz = float(freq_mode_split[0].replace("'",".")) * 1000000
|
||||
freq_hz = float(freq_mode_split[0].replace("'", ".")) * 1000000
|
||||
mode = None
|
||||
if len(freq_mode_split) > 1:
|
||||
mode = freq_mode_split[1].upper()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from datetime import datetime
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
from data.sig_ref import SIGRef
|
||||
from data.spot import Spot
|
||||
|
||||
@@ -22,8 +22,8 @@ class XOTA(WebsocketSpotProvider):
|
||||
|
||||
def __init__(self, provider_config):
|
||||
super().__init__(provider_config, provider_config["url"])
|
||||
locations_csv = provider_config["locations-csv"] if "locations-csv" in provider_config else None
|
||||
self.SIG = provider_config["sig"] if "sig" in provider_config else None
|
||||
locations_csv = str(provider_config["locations-csv"]) if "locations-csv" in provider_config else None
|
||||
self.SIG = str(provider_config["sig"]) if "sig" in provider_config else None
|
||||
|
||||
# Load location data
|
||||
if locations_csv:
|
||||
@@ -48,8 +48,9 @@ class XOTA(WebsocketSpotProvider):
|
||||
freq=float(source_spot["freq"]) * 1000,
|
||||
mode=source_spot["mode"].upper(),
|
||||
sig=self.SIG,
|
||||
sig_refs=[SIGRef(id=ref_id, sig=self.SIG, url=source_spot["reference"]["website"], latitude=lat,
|
||||
longitude=lon)],
|
||||
sig_refs=[
|
||||
SIGRef(id=ref_id, sig=self.SIG or "", url=source_spot["reference"]["website"], latitude=lat,
|
||||
longitude=lon)],
|
||||
time=datetime.now(pytz.UTC).timestamp(),
|
||||
dx_latitude=lat,
|
||||
dx_longitude=lon,
|
||||
|
||||
Reference in New Issue
Block a user