Refactor activity names to use an enum instead of a string to avoid typos #147

This commit is contained in:
Ian Renton
2026-09-18 18:09:41 +01:00
parent ab79e7e01c
commit e5caf7353d
61 changed files with 735 additions and 638 deletions
+13 -13
View File
@@ -3,9 +3,9 @@ import re
from pyhamtools.locator import latlong_to_locator, locator_to_latlong from pyhamtools.locator import latlong_to_locator, locator_to_latlong
from core.constants import ACTIVITIES from core.activity_utils import get_activity_by_name
from core.data_store import DATA_STORE from core.data_store import DATA_STORE
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from core.geo_utils import wab_wai_square_to_lat_lon from core.geo_utils import wab_wai_square_to_lat_lon
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
@@ -30,8 +30,8 @@ def get_activity_ref_info(activity_name, ref_id):
activity_ref = ActivityRef(sig=activity_name, id=ref_id) activity_ref = ActivityRef(sig=activity_name, id=ref_id)
# We can always get the reference type and the icon from the activity itself # We can always get the reference type and the icon from the activity itself
for activity in ACTIVITIES: activity = get_activity_by_name(activity_name)
if activity.name.upper() == activity_name.upper(): if activity:
activity_ref.ref_type = activity.ref_type activity_ref.ref_type = activity.ref_type
activity_ref.icon = activity.icon activity_ref.icon = activity.icon
@@ -41,7 +41,7 @@ def get_activity_ref_info(activity_name, ref_id):
# DME fudge. Our database has leading zeros padding to 5 digits which is the expected format, but not all # DME fudge. Our database has leading zeros padding to 5 digits which is the expected format, but not all
# activators add leading zeros. We also need to normalise "DME 01234" to "DME-01234" to match what's in our # activators add leading zeros. We also need to normalise "DME 01234" to "DME-01234" to match what's in our
# database. # database.
if activity_name.upper() == "DME": if activity_name.upper() == ActivityName.DME:
match = re.match(r"DME[\- ](\d{3,5})", ref_id, re.IGNORECASE) match = re.match(r"DME[\- ](\d{3,5})", ref_id, re.IGNORECASE)
if match: if match:
number = match.group(1) number = match.group(1)
@@ -49,21 +49,21 @@ def get_activity_ref_info(activity_name, ref_id):
# DTMBA spotters sometimes include spaces and dashes, our regex allows them but they must be removed here so we # DTMBA spotters sometimes include spaces and dashes, our regex allows them but they must be removed here so we
# can look up against the official list which doesn't have them # can look up against the official list which doesn't have them
if activity_name.upper() == "DTMBA": if activity_name.upper() == ActivityName.DTMBA:
ref_id = ref_id.replace("-", "").replace(" ", "") ref_id = ref_id.replace("-", "").replace(" ", "")
### NO DATA ACTIVITIES ### ### NO DATA ACTIVITIES ###
# #
# If the activity is HEMA or BIWOTA, we have no way to either generate useful data or look it up on a # If the activity is HEMA or BIWOTA, we have no way to either generate useful data or look it up on a
# reference list, so just skip the lookup here. # reference list, so just skip the lookup here.
if activity_name.upper() == "HEMA" or activity_name.upper() == "BIWOTA": if activity_name.upper() == ActivityName.HEMA or activity_name.upper() == ActivityName.BIWOTA:
return activity_ref return activity_ref
### PROGRAMMATIC DATA GENERATION INSTEAD OF LOOKUPS ### ### PROGRAMMATIC DATA GENERATION INSTEAD OF LOOKUPS ###
# #
# If the activity is Tiles, WAB, WAI or BOTA (Beaches), we don't have anything to look up from the data # If the activity is Tiles, WAB, WAI or BOTA (Beaches), we don't have anything to look up from the data
# store, we can calculate all the information we are going to get directly. # store, we can calculate all the information we are going to get directly.
if activity_name.upper() == "TILES": if activity_name.upper() == ActivityName.TILES.upper():
# Tiles on the Air just uses Maidenhead 6-digit squares, so ID, Name and Grid are all the same # Tiles on the Air just uses Maidenhead 6-digit squares, so ID, Name and Grid are all the same
if not activity_ref.name: if not activity_ref.name:
activity_ref.name = activity_ref.id activity_ref.name = activity_ref.id
@@ -75,7 +75,7 @@ def get_activity_ref_info(activity_name, ref_id):
activity_ref.longitude = ll[1] activity_ref.longitude = ll[1]
return activity_ref return activity_ref
elif activity_name.upper() == "WAB" or activity_name.upper() == "WAI": elif activity_name.upper() == ActivityName.WAB or activity_name.upper() == ActivityName.WAI:
ll = wab_wai_square_to_lat_lon(ref_id) ll = wab_wai_square_to_lat_lon(ref_id)
if ll: if ll:
activity_ref.name = ref_id activity_ref.name = ref_id
@@ -87,7 +87,7 @@ def get_activity_ref_info(activity_name, ref_id):
logger.warning("Invalid lat/lon received for WAB/WAI reference") logger.warning("Invalid lat/lon received for WAB/WAI reference")
return activity_ref return activity_ref
elif activity_name.upper() == "BOTA": elif activity_name.upper() == ActivityName.BOTA:
# For BOTA all we can ever generate is the URL, there is no data file or lookup for lat/longs # For BOTA all we can ever generate is the URL, there is no data file or lookup for lat/longs
if not activity_ref.name: if not activity_ref.name:
activity_ref.name = activity_ref.id activity_ref.name = activity_ref.id
@@ -97,11 +97,11 @@ def get_activity_ref_info(activity_name, ref_id):
) )
return activity_ref return activity_ref
elif activity_name.upper() == "GMA Islands": elif activity_name.upper() == ActivityName.GMA_ISLANDS.upper():
# GMA Islands is a bit of a mess of GMA and IOTA references. Try looking them both up and see what returns # GMA Islands is a bit of a mess of GMA and IOTA references. Try looking them both up and see what returns
# the best result. # the best result.
iota_lookup = get_activity_ref_info("IOTA", ref_id) iota_lookup = get_activity_ref_info(ActivityName.IOTA, ref_id)
gma_lookup = get_activity_ref_info("GMA", ref_id) gma_lookup = get_activity_ref_info(ActivityName.GMA, ref_id)
for key, value in iota_lookup.__dict__.items(): for key, value in iota_lookup.__dict__.items():
if value is not None and activity_ref.__dict__.get(key) is None: if value is not None and activity_ref.__dict__.get(key) is None:
activity_ref.__dict__[key] = value activity_ref.__dict__[key] = value
+20 -12
View File
@@ -1,23 +1,31 @@
from core.constants import ACTIVITIES from data.activities import ACTIVITIES
def get_activity_by_name(name):
"""Utility function to resolve an arbitrary, case-insensitive activity name string (e.g. from a spot comment, a
provider, or an API request) to the matching known Activity. Returns None if no match is found."""
if not name:
return None
for activity_name, activity in ACTIVITIES.items():
if activity_name.upper() == name.upper():
return activity
return None
def get_ref_regex_for_activity(activity): def get_ref_regex_for_activity(activity):
"""Utility function to get the regex string for an activity reference for a named activity. If no match is """Utility function to get the regex string for an activity reference for a named activity. If no match is
found, None will be returned.""" found, None will be returned."""
for a in ACTIVITIES: found = get_activity_by_name(activity)
if a.name.upper() == activity.upper(): return found.ref_regex if found else None
return a.ref_regex
return None
def get_icon_for_activity(activity): def get_icon_for_activity(activity):
"""Utility function to get the icon for a named activity. If no match is found, None will be returned.""" """Utility function to get the icon for a named activity. If no match is found, None will be returned."""
for a in ACTIVITIES: found = get_activity_by_name(activity)
if a.name.upper() == activity.upper(): return found.icon if found else None
return a.icon
return None
def get_activity_name_from_comment_name(activity): def get_activity_name_from_comment_name(activity):
@@ -25,11 +33,11 @@ def get_activity_name_from_comment_name(activity):
but there are some cases (e.g. is "TOTA" Towers, Tiles or Toilets?) where we need to transform one to the but there are some cases (e.g. is "TOTA" Towers, Tiles or Toilets?) where we need to transform one to the
other.""" other."""
for a in ACTIVITIES: for activity_name, a in ACTIVITIES.items():
if any(n.upper() == activity.upper() for n in a.comment_names): if any(n.upper() == activity.upper() for n in a.comment_names):
return a.name return activity_name
return None return None
# Regex matching any activity's "comment name", i.e. how it may be referred to in spot comments # Regex matching any activity's "comment name", i.e. how it may be referred to in spot comments
ANY_ACTIVITY_REGEX = rf"({'|'.join(n for a in ACTIVITIES for n in a.comment_names)})" ANY_ACTIVITY_REGEX = rf"({'|'.join(n for a in ACTIVITIES.values() for n in a.comment_names)})"
-438
View File
@@ -1,6 +1,4 @@
from core.config import SERVER_OWNER_CALLSIGN from core.config import SERVER_OWNER_CALLSIGN
from core.enums import ActivityRefType, ActivityType
from data.activity import Activity
from data.band import Band from data.band import Band
# General software # General software
@@ -10,442 +8,6 @@ SOFTWARE_VERSION = "2.2-pre"
HTTP_HEADERS = {"User-Agent": f"Spothole v{SOFTWARE_VERSION} (operated by {SERVER_OWNER_CALLSIGN})"} HTTP_HEADERS = {"User-Agent": f"Spothole v{SOFTWARE_VERSION} (operated by {SERVER_OWNER_CALLSIGN})"}
HAMQTH_PRG = f"Spothole v{SOFTWARE_VERSION} operated by {SERVER_OWNER_CALLSIGN}".replace(" ", "_") HAMQTH_PRG = f"Spothole v{SOFTWARE_VERSION} operated by {SERVER_OWNER_CALLSIGN}".replace(" ", "_")
# Activities
ACTIVITIES = [
Activity(
name="Contest",
comment_names=["CONTEST"],
description="Contest",
sig_type=ActivityType.TRADITIONAL,
icon="fa-trophy",
refs_globally_unique=False,
),
Activity(
name="DXpedition",
comment_names=[],
description="Radio expedition to a remote location",
sig_type=ActivityType.TRADITIONAL,
icon="fa-book-atlas",
refs_globally_unique=False,
),
Activity(
name="Satellite",
comment_names=[],
description="Amateur Radio Satellite",
sig_type=ActivityType.TRADITIONAL,
icon="fa-satellite",
refs_globally_unique=False,
),
Activity(
name="EME",
comment_names=[],
description="Earth-Moon-Earth (Moonbounce)",
sig_type=ActivityType.TRADITIONAL,
icon="fa-moon",
refs_globally_unique=False,
),
Activity(
name="/AM",
comment_names=[],
description="Aeronautical Mobile",
sig_type=ActivityType.TRADITIONAL,
icon="fa-plane",
refs_globally_unique=False,
),
Activity(
name="/MM",
comment_names=[],
description="Maritime Mobile",
sig_type=ActivityType.TRADITIONAL,
icon="fa-sailboat",
refs_globally_unique=False,
),
Activity(
name="QRP",
comment_names=["QRP"],
description="Low power",
sig_type=ActivityType.TRADITIONAL,
icon="fa-volume-low",
refs_globally_unique=False,
),
Activity(
name="POTA",
comment_names=["POTA"],
description="Parks on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.PARK,
ref_regex=r"[A-Z]{2}\-\d{4,5}|K\-TEST",
icon="fa-tree",
refs_globally_unique=False,
),
Activity(
name="SOTA",
comment_names=["SOTA"],
description="Summits on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.SUMMIT,
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}",
icon="fa-mountain-sun",
refs_globally_unique=False,
),
Activity(
name="WWFF",
comment_names=["WWFF"],
description="World Wide Flora & Fauna",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.PARK,
ref_regex=r"[A-Z0-9]{1,3}FF\-\d{4}",
icon="fa-seedling",
refs_globally_unique=True,
),
Activity(
name="GMA",
comment_names=["GMA"],
description="Global Mountain Activity",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.SUMMIT,
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}",
icon="fa-person-hiking",
refs_globally_unique=False,
),
Activity(
name="WWBOTA",
comment_names=["WWBOTA", "BOTA"],
description="Worldwide Bunkers on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.BUNKER,
ref_regex=r"B\/[A-Z0-9]{1,3}\-\d{3,4}",
icon="fa-radiation",
refs_globally_unique=True,
),
Activity(
name="HEMA",
comment_names=["HEMA"],
description="HuMPs Excluding Marilyns Award",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.SUMMIT,
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{3}\-\d{3}",
icon="fa-mound",
refs_globally_unique=False,
),
Activity(
name="IOTA",
comment_names=["IOTA"],
description="Islands on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.ISLAND,
ref_regex=r"[A-Z]{2}\-\d{3}",
icon="fa-book-atlas",
refs_globally_unique=False,
),
Activity(
name="GMA Islands",
comment_names=[],
description="Global Mountain Activity - Islands",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.ISLAND,
ref_regex=r"(([A-Z]{2}\-\d{3})|([A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}))",
icon="fa-person-hiking",
refs_globally_unique=False,
),
Activity(
name="ARLHS",
comment_names=["ARLHS"],
description="Amateur Radio Lighthouse Society",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.LIGHTHOUSE,
ref_regex=r"[A-Z]{3}[\- ]\d{3,4}",
icon="fa-house-flood-water",
refs_globally_unique=False,
),
Activity(
name="ILLW",
comment_names=["ILLW"],
description="International Lighthouse & Lightship Weekend",
sig_type=ActivityType.EVENT,
ref_type=ActivityRefType.LIGHTHOUSE,
ref_regex=r"[A-Z]{2}\d{4}",
icon="fa-house-flood-water",
refs_globally_unique=False,
),
Activity(
name="MOTA",
comment_names=["MOTA"],
description="Mills on the Air",
sig_type=ActivityType.EVENT,
ref_type=ActivityRefType.MILL,
ref_regex=r"X\d{4,6}",
icon="fa-fan",
refs_globally_unique=True,
),
Activity(
name="SIOTA",
comment_names=["SIOTA"],
description="Silos on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.SILO,
ref_regex=r"[A-Z]{2}\-[A-Z]{3}\d",
icon="fa-wheat-awn",
refs_globally_unique=False,
),
Activity(
name="WCA",
comment_names=["WCA"],
description="World Castles Award",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.CASTLE,
ref_regex=r"[A-Z0-9]{1,3}\-\d{5}",
icon="fa-chess-rook",
refs_globally_unique=False,
),
Activity(
name="ZLOTA",
comment_names=["ZLOTA"],
description="New Zealand on the Air",
sig_type=ActivityType.REGIONAL,
ref_type=None,
ref_regex=r"ZL[A-Z]/[A-Z]{2}\-\d{3,4}",
icon="fa-kiwi-bird",
region_flag="🇳🇿",
refs_globally_unique=True,
),
Activity(
name="WOTA",
comment_names=["WOTA"],
description="Wainwrights on the Air",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.SUMMIT,
ref_regex=r"[A-Z]{3}-[0-9]{2}",
icon="fa-w",
region_flag="🇬🇧",
refs_globally_unique=False,
),
Activity(
name="BOTA",
comment_names=[],
description="Beaches on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.BEACH,
icon="fa-umbrella-beach",
refs_globally_unique=False,
),
Activity(
name="KRMNPA",
comment_names=["KRMNPA"],
description="Keith Roget Memorial National Parks Award",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.PARK,
ref_regex=r"VKFF\-\d{4}",
icon="fa-earth-oceania",
region_flag="🇦🇺",
refs_globally_unique=False,
),
Activity(
name="SANPCPA",
comment_names=["SANPCPA"],
description="South Australian National Parks and Conservation Parks Award",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.PARK,
ref_regex=r"VKFF\-\d{4}",
icon="fa-earth-oceania",
region_flag="🇦🇺",
refs_globally_unique=False,
),
Activity(
name="LLOTA",
comment_names=["LLOTA"],
description="Lagos y Lagunas on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.LAKE,
ref_regex=r"LL[A-Z]{2}\-\d{4}",
icon="fa-water",
refs_globally_unique=True,
),
Activity(
name="Towers",
comment_names=["TOTA"],
description="Towers on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.TOWER,
ref_regex=r"[A-Z]{2,3}R\-\d{4}",
icon="fa-tower-observation",
refs_globally_unique=False,
),
Activity(
name="Tiles",
comment_names=[],
description="Tiles on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.GRID,
ref_regex=r"[A-Za-z]{2}[0-9]{2}[A-Za-z]{2}",
icon="fa-square",
refs_globally_unique=False,
),
Activity(
name="RaDAR Rally",
comment_names=["RaDAR"],
description="RaDAR Rally",
sig_type=ActivityType.EVENT,
icon="fa-headset",
refs_globally_unique=False,
),
Activity(
name="WAB",
comment_names=["WAB"],
description="Worked All Britain",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.GRID,
ref_regex=r"[A-Z]{1,2}[0-9]{2}",
icon="fa-table-cells-large",
region_flag="🇬🇧",
refs_globally_unique=False,
),
Activity(
name="WAI",
comment_names=["WAI"],
description="Worked All Ireland",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.GRID,
ref_regex=r"[A-Z][0-9]{2}",
icon="fa-table-cells-large",
region_flag="🇮🇪",
refs_globally_unique=False,
),
Activity(
name="DMF",
comment_names=["DMF"],
description="Diplôme des Moulins de France",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.MILL,
icon="fa-fan",
region_flag="🇫🇷",
refs_globally_unique=False,
),
Activity(
name="DME",
comment_names=["DME"],
description="Diploma Municipios de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.TOWN,
ref_regex=r"DME[\- ]\d{3,5}",
icon="fa-building",
region_flag="🇪🇸",
refs_globally_unique=True,
),
Activity(
name="FEA",
comment_names=["FEA"],
description="Diploma Faros de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.LIGHTHOUSE,
# FEA references are technically [DE]\-\d{4}(\.\d)? but spotters always seem to miss out the D- or E-
# prefix and just use FEA-1234 or FEA 1234, so allow for that. The FEA activity ref data provider adds both
# forms to the database.
ref_regex=r"([DE]|FEA)[\- ]\d{4}(\.\d)?",
icon="fa-house-flood-water",
region_flag="🇪🇸",
refs_globally_unique=True,
),
Activity(
name="DMUE",
comment_names=["DMUE"],
description="Diploma Museos de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.BUILDING,
ref_regex=r"MUE[A-Z]{2}-\d{3}",
icon="fa-landmark",
region_flag="🇪🇸",
refs_globally_unique=True,
),
Activity(
name="DMVE",
comment_names=["DMVE"],
description="Diploma Monumentos y Vestigios de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.BUILDING,
ref_regex=r"MV[A-Z]{1,2}-\d{4}",
icon="fa-monument",
region_flag="🇪🇸",
refs_globally_unique=True,
),
Activity(
name="DCE",
comment_names=["DCE"],
description="Diploma Castillos de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.CASTLE,
ref_regex=r"C[A-Z]{1,2}-\d{3}",
icon="fa-chess-rook",
region_flag="🇪🇸",
refs_globally_unique=False,
),
Activity(
name="DEFE",
comment_names=["DEFE"],
description="Diploma Estaciones de Ferrocarril de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.BUILDING,
ref_regex=r"EF[A-Z]{1,2}-\d{3}",
icon="fa-train",
region_flag="🇪🇸",
refs_globally_unique=True,
),
Activity(
name="DTMBA",
comment_names=["DTMBA"],
description="Diploma Teatri Musei e Belle Arti",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.BUILDING,
ref_regex=r"I-?[0-9]{3,4}\s?[A-Z]{2}",
icon="fa-landmark",
region_flag="🇮🇹",
refs_globally_unique=True,
),
Activity(
name="BIWOTA",
comment_names=["BIWOTA"],
description="British Inland Waterways on the Air",
sig_type=ActivityType.EVENT,
ref_type=ActivityRefType.WATERWAY,
icon="fa-ship",
region_flag="🇬🇧",
refs_globally_unique=False,
),
Activity(
name="COTA",
comment_names=["COTA"],
description="Castles on the Air",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.CASTLE,
ref_regex=r"[A-Z]{3}\-[0-9]{3,5}",
icon="fa-chess-rook",
region_flag="🇩🇪",
refs_globally_unique=False,
),
Activity(
name="PGA",
comment_names=["PGA"],
description="Polish Gmina Award",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.REGION,
ref_regex=r"[A-Z]{2}[0-9]{2}",
icon="fa-g",
region_flag="🇵🇱",
refs_globally_unique=False,
),
Activity(
name="Toilets",
comment_names=[],
description="Toilets on the Air",
sig_type=ActivityType.EVENT,
ref_type=ActivityRefType.TOILET,
ref_regex=r"T\-[0-9]{2}",
icon="fa-toilet",
region_flag="🏴‍☠️",
refs_globally_unique=True,
),
]
# Band definitions # Band definitions
BANDS = [ BANDS = [
Band(name="2200m", start_freq=135700, end_freq=137800), Band(name="2200m", start_freq=135700, end_freq=137800),
+54
View File
@@ -90,6 +90,60 @@ class LocationSourceForCallsign(str, Enum):
DXCC = "DXCC" DXCC = "DXCC"
# Definitions of every activity Spothole knows about, keyed by the ActivityName enum. An enum is used for the
# names to avoid typos when using literal strings like "POTA" all over the place.
class ActivityName(str, Enum):
"""Canonical name of every activity. Spothole uses these rather than literals like "POTA" around the code
to ensure I don't accidentally introduce typos."""
CONTEST = "Contest"
DXPEDITION = "DXpedition"
SATELLITE = "Satellite"
EME = "EME"
AERONAUTICAL_MOBILE = "/AM"
MARITIME_MOBILE = "/MM"
QRP = "QRP"
POTA = "POTA"
SOTA = "SOTA"
WWFF = "WWFF"
GMA = "GMA"
WWBOTA = "WWBOTA"
HEMA = "HEMA"
IOTA = "IOTA"
GMA_ISLANDS = "GMA Islands"
ARLHS = "ARLHS"
ILLW = "ILLW"
MOTA = "MOTA"
SIOTA = "SIOTA"
WCA = "WCA"
ZLOTA = "ZLOTA"
WOTA = "WOTA"
BOTA = "BOTA"
KRMNPA = "KRMNPA"
SANPCPA = "SANPCPA"
LLOTA = "LLOTA"
TOWERS = "Towers"
TILES = "Tiles"
RADAR_RALLY = "RaDAR Rally"
WAB = "WAB"
WAI = "WAI"
DMF = "DMF"
DME = "DME"
FEA = "FEA"
DMUE = "DMUE"
DMVE = "DMVE"
DCE = "DCE"
DEFE = "DEFE"
DTMBA = "DTMBA"
BIWOTA = "BIWOTA"
COTA = "COTA"
PGA = "PGA"
TOILETS = "Toilets"
def __str__(self):
return str(self.value)
class ActivityRefType(str, Enum): class ActivityRefType(str, Enum):
"""Type of an activity reference.""" """Type of an activity reference."""
+444
View File
@@ -0,0 +1,444 @@
from core.enums import ActivityName, ActivityType, ActivityRefType
from data.activity import Activity
ACTIVITIES: dict[ActivityName, Activity] = {
ActivityName.CONTEST: Activity(
name=ActivityName.CONTEST,
# No sensible way to determine *which* contest, but if we set comment_names=["CONTEST"] then at least
# any spots with "contest" in the comment will get allocated to this activity.
comment_names=["CONTEST"],
description="Contest",
sig_type=ActivityType.TRADITIONAL,
icon="fa-trophy",
refs_globally_unique=False,
),
ActivityName.DXPEDITION: Activity(
name=ActivityName.DXPEDITION,
# DXpedition stations are never really spotted with "DXpedition" in the comments, but we can assign
# this activity to a spot other ways.
comment_names=[],
description="Radio expedition to a remote location",
sig_type=ActivityType.TRADITIONAL,
icon="fa-book-atlas",
refs_globally_unique=False,
),
ActivityName.SATELLITE: Activity(
name=ActivityName.SATELLITE,
comment_names=[],
description="Amateur Radio Satellite",
sig_type=ActivityType.TRADITIONAL,
icon="fa-satellite",
refs_globally_unique=False,
),
ActivityName.EME: Activity(
name=ActivityName.EME,
comment_names=[],
description="Earth-Moon-Earth (Moonbounce)",
sig_type=ActivityType.TRADITIONAL,
icon="fa-moon",
refs_globally_unique=False,
),
ActivityName.AERONAUTICAL_MOBILE: Activity(
name=ActivityName.AERONAUTICAL_MOBILE,
# Don't pick /AM out of comments, spot.py will handle picking it out of the callsign
comment_names=[],
description="Aeronautical Mobile",
sig_type=ActivityType.TRADITIONAL,
icon="fa-plane",
refs_globally_unique=False,
),
ActivityName.MARITIME_MOBILE: Activity(
name=ActivityName.MARITIME_MOBILE,
# Don't pick /MM out of comments, spot.py will handle picking it out of the callsign
comment_names=[],
description="Maritime Mobile",
sig_type=ActivityType.TRADITIONAL,
icon="fa-sailboat",
refs_globally_unique=False,
),
ActivityName.QRP: Activity(
name=ActivityName.QRP,
comment_names=["QRP"],
description="Low power",
sig_type=ActivityType.TRADITIONAL,
icon="fa-volume-low",
refs_globally_unique=False,
),
ActivityName.POTA: Activity(
name=ActivityName.POTA,
comment_names=["POTA"],
description="Parks on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.PARK,
ref_regex=r"[A-Z]{2}\-\d{4,5}|K\-TEST",
icon="fa-tree",
refs_globally_unique=False,
),
ActivityName.SOTA: Activity(
name=ActivityName.SOTA,
comment_names=["SOTA"],
description="Summits on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.SUMMIT,
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}",
icon="fa-mountain-sun",
refs_globally_unique=False,
),
ActivityName.WWFF: Activity(
name=ActivityName.WWFF,
comment_names=["WWFF"],
description="World Wide Flora & Fauna",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.PARK,
ref_regex=r"[A-Z0-9]{1,3}FF\-\d{4}",
icon="fa-seedling",
refs_globally_unique=True,
),
ActivityName.GMA: Activity(
name=ActivityName.GMA,
comment_names=["GMA"],
description="Global Mountain Activity",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.SUMMIT,
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}",
icon="fa-person-hiking",
refs_globally_unique=False,
),
ActivityName.WWBOTA: Activity(
name=ActivityName.WWBOTA,
comment_names=["WWBOTA", "BOTA"],
description="Worldwide Bunkers on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.BUNKER,
ref_regex=r"B\/[A-Z0-9]{1,3}\-\d{3,4}",
icon="fa-radiation",
refs_globally_unique=True,
),
ActivityName.HEMA: Activity(
name=ActivityName.HEMA,
comment_names=["HEMA"],
description="HuMPs Excluding Marilyns Award",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.SUMMIT,
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{3}\-\d{3}",
icon="fa-mound",
refs_globally_unique=False,
),
ActivityName.IOTA: Activity(
name=ActivityName.IOTA,
comment_names=["IOTA"],
description="Islands on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.ISLAND,
ref_regex=r"[A-Z]{2}\-\d{3}",
icon="fa-book-atlas",
refs_globally_unique=False,
),
ActivityName.GMA_ISLANDS: Activity(
name=ActivityName.GMA_ISLANDS,
comment_names=[],
description="Global Mountain Activity - Islands",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.ISLAND,
ref_regex=r"(([A-Z]{2}\-\d{3})|([A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}))",
icon="fa-person-hiking",
refs_globally_unique=False,
),
ActivityName.ARLHS: Activity(
name=ActivityName.ARLHS,
comment_names=["ARLHS"],
description="Amateur Radio Lighthouse Society",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.LIGHTHOUSE,
ref_regex=r"[A-Z]{3}[\- ]\d{3,4}",
icon="fa-house-flood-water",
refs_globally_unique=False,
),
ActivityName.ILLW: Activity(
name=ActivityName.ILLW,
comment_names=["ILLW"],
description="International Lighthouse & Lightship Weekend",
sig_type=ActivityType.EVENT,
ref_type=ActivityRefType.LIGHTHOUSE,
ref_regex=r"[A-Z]{2}\d{4}",
icon="fa-house-flood-water",
refs_globally_unique=False,
),
ActivityName.MOTA: Activity(
name=ActivityName.MOTA,
comment_names=["MOTA"],
description="Mills on the Air",
sig_type=ActivityType.EVENT,
ref_type=ActivityRefType.MILL,
ref_regex=r"X\d{4,6}",
icon="fa-fan",
refs_globally_unique=True,
),
ActivityName.SIOTA: Activity(
name=ActivityName.SIOTA,
comment_names=["SIOTA"],
description="Silos on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.SILO,
ref_regex=r"[A-Z]{2}\-[A-Z]{3}\d",
icon="fa-wheat-awn",
refs_globally_unique=False,
),
ActivityName.WCA: Activity(
name=ActivityName.WCA,
comment_names=["WCA"],
description="World Castles Award",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.CASTLE,
ref_regex=r"[A-Z0-9]{1,3}\-\d{5}",
icon="fa-chess-rook",
refs_globally_unique=False,
),
ActivityName.ZLOTA: Activity(
name=ActivityName.ZLOTA,
comment_names=["ZLOTA"],
description="New Zealand on the Air",
sig_type=ActivityType.REGIONAL,
ref_type=None,
ref_regex=r"ZL[A-Z]/[A-Z]{2}\-\d{3,4}",
icon="fa-kiwi-bird",
region_flag="🇳🇿",
refs_globally_unique=True,
),
ActivityName.WOTA: Activity(
name=ActivityName.WOTA,
comment_names=["WOTA"],
description="Wainwrights on the Air",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.SUMMIT,
ref_regex=r"[A-Z]{3}-[0-9]{2}",
icon="fa-w",
region_flag="🇬🇧",
refs_globally_unique=False,
),
ActivityName.BOTA: Activity(
name=ActivityName.BOTA,
comment_names=[],
description="Beaches on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.BEACH,
icon="fa-umbrella-beach",
refs_globally_unique=False,
),
ActivityName.KRMNPA: Activity(
name=ActivityName.KRMNPA,
comment_names=["KRMNPA"],
description="Keith Roget Memorial National Parks Award",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.PARK,
ref_regex=r"VKFF\-\d{4}",
icon="fa-earth-oceania",
region_flag="🇦🇺",
refs_globally_unique=False,
),
ActivityName.SANPCPA: Activity(
name=ActivityName.SANPCPA,
comment_names=["SANPCPA"],
description="South Australian National Parks and Conservation Parks Award",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.PARK,
ref_regex=r"VKFF\-\d{4}",
icon="fa-earth-oceania",
region_flag="🇦🇺",
refs_globally_unique=False,
),
ActivityName.LLOTA: Activity(
name=ActivityName.LLOTA,
comment_names=["LLOTA"],
description="Lagos y Lagunas on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.LAKE,
ref_regex=r"LL[A-Z]{2}\-\d{4}",
icon="fa-water",
refs_globally_unique=True,
),
ActivityName.TOWERS: Activity(
name=ActivityName.TOWERS,
comment_names=["TOTA"],
description="Towers on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.TOWER,
ref_regex=r"[A-Z]{2,3}R\-\d{4}",
icon="fa-tower-observation",
refs_globally_unique=False,
),
ActivityName.TILES: Activity(
name=ActivityName.TILES,
comment_names=[],
description="Tiles on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.GRID,
ref_regex=r"[A-Za-z]{2}[0-9]{2}[A-Za-z]{2}",
icon="fa-square",
refs_globally_unique=False,
),
ActivityName.RADAR_RALLY: Activity(
name=ActivityName.RADAR_RALLY,
comment_names=["RaDAR"],
description="RaDAR Rally",
sig_type=ActivityType.EVENT,
icon="fa-headset",
refs_globally_unique=False,
),
ActivityName.WAB: Activity(
name=ActivityName.WAB,
comment_names=["WAB"],
description="Worked All Britain",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.GRID,
ref_regex=r"[A-Z]{1,2}[0-9]{2}",
icon="fa-table-cells-large",
region_flag="🇬🇧",
refs_globally_unique=False,
),
ActivityName.WAI: Activity(
name=ActivityName.WAI,
comment_names=["WAI"],
description="Worked All Ireland",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.GRID,
ref_regex=r"[A-Z][0-9]{2}",
icon="fa-table-cells-large",
region_flag="🇮🇪",
refs_globally_unique=False,
),
ActivityName.DMF: Activity(
name=ActivityName.DMF,
comment_names=["DMF"],
description="Diplôme des Moulins de France",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.MILL,
icon="fa-fan",
region_flag="🇫🇷",
refs_globally_unique=False,
),
ActivityName.DME: Activity(
name=ActivityName.DME,
comment_names=["DME"],
description="Diploma Municipios de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.TOWN,
ref_regex=r"DME[\- ]\d{3,5}",
icon="fa-building",
region_flag="🇪🇸",
refs_globally_unique=True,
),
ActivityName.FEA: Activity(
name=ActivityName.FEA,
comment_names=["FEA"],
description="Diploma Faros de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.LIGHTHOUSE,
# FEA references are technically [DE]\-\d{4}(\.\d)? but spotters always seem to miss out the D- or E-
# prefix and just use FEA-1234 or FEA 1234, so allow for that. The FEA activity ref data provider adds both
# forms to the database.
ref_regex=r"([DE]|FEA)[\- ]\d{4}(\.\d)?",
icon="fa-house-flood-water",
region_flag="🇪🇸",
refs_globally_unique=True,
),
ActivityName.DMUE: Activity(
name=ActivityName.DMUE,
comment_names=["DMUE"],
description="Diploma Museos de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.BUILDING,
ref_regex=r"MUE[A-Z]{2}-\d{3}",
icon="fa-landmark",
region_flag="🇪🇸",
refs_globally_unique=True,
),
ActivityName.DMVE: Activity(
name=ActivityName.DMVE,
comment_names=["DMVE"],
description="Diploma Monumentos y Vestigios de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.BUILDING,
ref_regex=r"MV[A-Z]{1,2}-\d{4}",
icon="fa-monument",
region_flag="🇪🇸",
refs_globally_unique=True,
),
ActivityName.DCE: Activity(
name=ActivityName.DCE,
comment_names=["DCE"],
description="Diploma Castillos de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.CASTLE,
ref_regex=r"C[A-Z]{1,2}-\d{3}",
icon="fa-chess-rook",
region_flag="🇪🇸",
refs_globally_unique=False,
),
ActivityName.DEFE: Activity(
name=ActivityName.DEFE,
comment_names=["DEFE"],
description="Diploma Estaciones de Ferrocarril de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.BUILDING,
ref_regex=r"EF[A-Z]{1,2}-\d{3}",
icon="fa-train",
region_flag="🇪🇸",
refs_globally_unique=True,
),
ActivityName.DTMBA: Activity(
name=ActivityName.DTMBA,
comment_names=["DTMBA"],
description="Diploma Teatri Musei e Belle Arti",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.BUILDING,
ref_regex=r"I-?[0-9]{3,4}\s?[A-Z]{2}",
icon="fa-landmark",
region_flag="🇮🇹",
refs_globally_unique=True,
),
ActivityName.BIWOTA: Activity(
name=ActivityName.BIWOTA,
comment_names=["BIWOTA"],
description="British Inland Waterways on the Air",
sig_type=ActivityType.EVENT,
ref_type=ActivityRefType.WATERWAY,
icon="fa-ship",
region_flag="🇬🇧",
refs_globally_unique=False,
),
ActivityName.COTA: Activity(
name=ActivityName.COTA,
comment_names=["COTA"],
description="Castles on the Air",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.CASTLE,
ref_regex=r"[A-Z]{3}\-[0-9]{3,5}",
icon="fa-chess-rook",
region_flag="🇩🇪",
refs_globally_unique=False,
),
ActivityName.PGA: Activity(
name=ActivityName.PGA,
comment_names=["PGA"],
description="Polish Gmina Award",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.REGION,
ref_regex=r"[A-Z]{2}[0-9]{2}",
icon="fa-g",
region_flag="🇵🇱",
refs_globally_unique=False,
),
ActivityName.TOILETS: Activity(
name=ActivityName.TOILETS,
comment_names=[],
description="Toilets on the Air",
sig_type=ActivityType.EVENT,
ref_type=ActivityRefType.TOILET,
ref_regex=r"T\-[0-9]{2}",
icon="fa-toilet",
region_flag="🏴‍☠️",
refs_globally_unique=True,
),
}
+2 -2
View File
@@ -1,6 +1,6 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from core.enums import ActivityRefType, ActivityType from core.enums import ActivityName, ActivityRefType, ActivityType
@dataclass @dataclass
@@ -15,7 +15,7 @@ class Activity:
match what references (such as parks and summits) look like for that programme.""" match what references (such as parks and summits) look like for that programme."""
# Activity name as used in the UI and API, e.g. "Towers" # Activity name as used in the UI and API, e.g. "Towers"
name: str name: ActivityName
# Description, e.g. "Towers on the Air" # Description, e.g. "Towers on the Air"
description: str description: str
# Type, either Worldwide, Regional or Event. Used for sorting in the web UI. # Type, either Worldwide, Regional or Event. Used for sorting in the web UI.
+11 -10
View File
@@ -18,9 +18,9 @@ from core.activity_utils import (
) )
from core.call_lookup_helper import get_call_info from core.call_lookup_helper import get_call_info
from core.config import MAX_SPOT_AGE from core.config import MAX_SPOT_AGE
from core.constants import ACTIVITIES, PROPAGATION_MODES from core.constants import PROPAGATION_MODES
from core.data_store import DATA_STORE from core.data_store import DATA_STORE
from core.enums import Continent, LocationSourceForSpot, Mode, ModeSource, ModeType from core.enums import ActivityName, Continent, LocationSourceForSpot, Mode, ModeSource, ModeType
from core.geo_utils import lat_lon_to_cq_zone, lat_lon_to_itu_zone from core.geo_utils import lat_lon_to_cq_zone, lat_lon_to_itu_zone
from core.utils import ( from core.utils import (
get_flag_for_dxcc, get_flag_for_dxcc,
@@ -29,6 +29,7 @@ from core.utils import (
infer_mode_from_frequency, infer_mode_from_frequency,
infer_mode_type_from_mode, infer_mode_type_from_mode,
) )
from data.activities import ACTIVITIES
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -311,7 +312,7 @@ class Spot:
# name, but where the activity reference is unique-looking enough that we can't confuse it with any other # name, but where the activity reference is unique-looking enough that we can't confuse it with any other
# activity. # activity.
if self.comment: if self.comment:
for activity in ACTIVITIES: for activity in ACTIVITIES.values():
if activity.refs_globally_unique and activity.ref_regex: if activity.refs_globally_unique and activity.ref_regex:
ref_matches = re.finditer( ref_matches = re.finditer(
r"(^|\W)(" + activity.ref_regex + r")($|\W)", self.comment, re.IGNORECASE r"(^|\W)(" + activity.ref_regex + r")($|\W)", self.comment, re.IGNORECASE
@@ -343,7 +344,7 @@ class Spot:
): ):
self.dx_latitude = activity_ref.latitude self.dx_latitude = activity_ref.latitude
self.dx_longitude = activity_ref.longitude self.dx_longitude = activity_ref.longitude
if self.sig == "WAB" or self.sig == "WAI" or self.sig == "Tiles": if self.sig in (ActivityName.WAB, ActivityName.WAI, ActivityName.TILES):
self.dx_location_source = LocationSourceForSpot.GRID self.dx_location_source = LocationSourceForSpot.GRID
else: else:
self.dx_location_source = LocationSourceForSpot.SIG_REF_LOOKUP self.dx_location_source = LocationSourceForSpot.SIG_REF_LOOKUP
@@ -380,16 +381,16 @@ class Spot:
# Set activities based on propagation mode # Set activities based on propagation mode
if self.propagation_mode == "Satellite" and not self.sig: if self.propagation_mode == "Satellite" and not self.sig:
self.sig = "Satellite" self.sig = ActivityName.SATELLITE
if self.propagation_mode == "Earth-Moon-Earth" and not self.sig: if self.propagation_mode == "Earth-Moon-Earth" and not self.sig:
self.sig = "EME" self.sig = ActivityName.EME
# Set activities based on the DX callsign suffix # Set activities based on the DX callsign suffix
if self.dx_call and not self.sig: if self.dx_call and not self.sig:
if self.dx_call.upper().endswith("/AM"): if self.dx_call.upper().endswith(ActivityName.AERONAUTICAL_MOBILE):
self.sig = "/AM" self.sig = ActivityName.AERONAUTICAL_MOBILE
elif self.dx_call.upper().endswith("/MM"): elif self.dx_call.upper().endswith(ActivityName.MARITIME_MOBILE):
self.sig = "/MM" self.sig = ActivityName.MARITIME_MOBILE
# Parse "de_grid -> dx_grid" structures from the comment # Parse "de_grid -> dx_grid" structures from the comment
if self.comment: if self.comment:
+2 -2
View File
@@ -1,7 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import ( from providers.activityrefdata.file_download_activity_ref_data_provider import (
FileDownloadActivityRefDataProvider, FileDownloadActivityRefDataProvider,
@@ -12,7 +12,7 @@ class ARLHS(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Amateur Radio Light House Society""" """Activity ref data provider for Amateur Radio Light House Society"""
POLL_INTERVAL_DAYS = 30 POLL_INTERVAL_DAYS = 30
ACTIVITY = "ARLHS" ACTIVITY = ActivityName.ARLHS
DATA_URL = "https://www.gma.rocks/download/lighthouse.csv" DATA_URL = "https://www.gma.rocks/download/lighthouse.csv"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -2,7 +2,7 @@ from time import sleep
from pyhamtools.locator import latlong_to_locator from pyhamtools.locator import latlong_to_locator
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider
@@ -11,7 +11,7 @@ class COTA(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Castles on the Air""" """Activity ref data provider for Castles on the Air"""
POLL_INTERVAL_DAYS = 30 POLL_INTERVAL_DAYS = 30
ACTIVITY = "COTA" ACTIVITY = ActivityName.COTA
DATA_URL = "https://www.cotagroup.org/cotagroup/map/data/castles-all-7d90ee2a5e1175e5dece1bbf9dc87504.json" DATA_URL = "https://www.cotagroup.org/cotagroup/map/data/castles-all-7d90ee2a5e1175e5dece1bbf9dc87504.json"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -3,7 +3,7 @@ from time import sleep
import pandas as pd import pandas as pd
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider
@@ -12,7 +12,7 @@ class DCE(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Diploma Castillos de España""" """Activity ref data provider for Diploma Castillos de España"""
POLL_INTERVAL_DAYS = 365 POLL_INTERVAL_DAYS = 365
ACTIVITY = "DCE" ACTIVITY = ActivityName.DCE
DATA_URL = "https://www.acracb.org/dce/descargas/General/directorio_referencias_dce.xls" DATA_URL = "https://www.acracb.org/dce/descargas/General/directorio_referencias_dce.xls"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -3,7 +3,7 @@ from time import sleep
import pandas as pd import pandas as pd
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider
@@ -12,7 +12,7 @@ class DEFE(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Diploma Estationes de Ferrocarril de España""" """Activity ref data provider for Diploma Estationes de Ferrocarril de España"""
POLL_INTERVAL_DAYS = 365 POLL_INTERVAL_DAYS = 365
ACTIVITY = "DEFE" ACTIVITY = ActivityName.DEFE
DATA_URL = "https://www.acracb.org/defe/descargas/General/directorio_referencias_defe.xls" DATA_URL = "https://www.acracb.org/defe/descargas/General/directorio_referencias_defe.xls"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -3,7 +3,7 @@ from time import sleep
from pyhamtools.locator import latlong_to_locator from pyhamtools.locator import latlong_to_locator
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.local_file_activity_ref_data_provider import ( from providers.activityrefdata.local_file_activity_ref_data_provider import (
LocalFileActivityRefDataProvider, LocalFileActivityRefDataProvider,
@@ -13,7 +13,7 @@ from providers.activityrefdata.local_file_activity_ref_data_provider import (
class DME(LocalFileActivityRefDataProvider): class DME(LocalFileActivityRefDataProvider):
"""Activity ref data provider for Diploma Municipios de Espana""" """Activity ref data provider for Diploma Municipios de Espana"""
ACTIVITY = "DME" ACTIVITY = ActivityName.DME
PATH = "datafiles/MUNICIPIOS.csv" PATH = "datafiles/MUNICIPIOS.csv"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -1,7 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider
@@ -10,7 +10,7 @@ class DMUE(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Diploma Museos de España""" """Activity ref data provider for Diploma Museos de España"""
POLL_INTERVAL_DAYS = 365 POLL_INTERVAL_DAYS = 365
ACTIVITY = "DMUE" ACTIVITY = ActivityName.DMUE
DATA_URL = "https://dmue.radiogalena.es/nom_dmue.csv" DATA_URL = "https://dmue.radiogalena.es/nom_dmue.csv"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -3,7 +3,7 @@ from time import sleep
import pandas as pd import pandas as pd
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider
@@ -12,7 +12,7 @@ class DMVE(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Diploma Monumentos y Vestigios de España""" """Activity ref data provider for Diploma Monumentos y Vestigios de España"""
POLL_INTERVAL_DAYS = 365 POLL_INTERVAL_DAYS = 365
ACTIVITY = "DMVE" ACTIVITY = ActivityName.DMVE
DATA_URL = "https://www.acracb.org/dmve/descargas/General/directorio_referencias_dmve.xls" DATA_URL = "https://www.acracb.org/dmve/descargas/General/directorio_referencias_dmve.xls"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -1,6 +1,6 @@
from time import sleep from time import sleep
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider
@@ -9,7 +9,7 @@ class DTMBA(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Diploma Teatri Musei Belle Arti""" """Activity ref data provider for Diploma Teatri Musei Belle Arti"""
POLL_INTERVAL_DAYS = 30 POLL_INTERVAL_DAYS = 30
ACTIVITY = "DTMBA" ACTIVITY = ActivityName.DTMBA
DATA_URL = "https://www.iu1fig.com/share/iz0eik/dtmba/export.php" DATA_URL = "https://www.iu1fig.com/share/iz0eik/dtmba/export.php"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -3,7 +3,7 @@ from time import sleep
import pdfplumber import pdfplumber
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider
@@ -12,7 +12,7 @@ class FEA(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Diploma Faros de España""" """Activity ref data provider for Diploma Faros de España"""
POLL_INTERVAL_DAYS = 30 POLL_INTERVAL_DAYS = 30
ACTIVITY = "FEA" ACTIVITY = ActivityName.FEA
DATA_URL = "http://ea5ol.net/Lista%20Faros.pdf" DATA_URL = "http://ea5ol.net/Lista%20Faros.pdf"
def __init__(self, provider_config): def __init__(self, provider_config):
@@ -22,7 +22,7 @@ class FileDownloadActivityRefDataProvider(ActivityRefDataProvider):
self._url = url self._url = url
self._poll_interval = poll_interval self._poll_interval = poll_interval
self._thread = None self._thread = None
self._url_data_cache = URLDataCache(f"sigrefdata_{sig_name}") # cache dir name kept for continuity self._url_data_cache = URLDataCache(f"activity_ref_data_{sig_name}")
def start(self): def start(self):
# Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between # Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between
+2 -2
View File
@@ -1,7 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import ( from providers.activityrefdata.file_download_activity_ref_data_provider import (
FileDownloadActivityRefDataProvider, FileDownloadActivityRefDataProvider,
@@ -12,7 +12,7 @@ class GMA(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Global Mountain Activity""" """Activity ref data provider for Global Mountain Activity"""
POLL_INTERVAL_DAYS = 30 POLL_INTERVAL_DAYS = 30
ACTIVITY = "GMA" ACTIVITY = ActivityName.GMA
DATA_URL = "https://www.gma.rocks/download/summits.csv" DATA_URL = "https://www.gma.rocks/download/summits.csv"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -1,7 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import ( from providers.activityrefdata.file_download_activity_ref_data_provider import (
FileDownloadActivityRefDataProvider, FileDownloadActivityRefDataProvider,
@@ -12,7 +12,7 @@ class ILLW(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for International Lighthouse & Lightship Weekend""" """Activity ref data provider for International Lighthouse & Lightship Weekend"""
POLL_INTERVAL_DAYS = 30 POLL_INTERVAL_DAYS = 30
ACTIVITY = "ILLW" ACTIVITY = ActivityName.ILLW
DATA_URL = "https://www.gma.rocks/download/lighthouse.csv" DATA_URL = "https://www.gma.rocks/download/lighthouse.csv"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -3,7 +3,7 @@ from time import sleep
from pyhamtools.locator import latlong_to_locator from pyhamtools.locator import latlong_to_locator
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import ( from providers.activityrefdata.file_download_activity_ref_data_provider import (
FileDownloadActivityRefDataProvider, FileDownloadActivityRefDataProvider,
@@ -16,7 +16,7 @@ class IOTA(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Islands on the Air""" """Activity ref data provider for Islands on the Air"""
POLL_INTERVAL_DAYS = 365 POLL_INTERVAL_DAYS = 365
ACTIVITY = "IOTA" ACTIVITY = ActivityName.IOTA
DATA_URL = "https://www.iota-world.org/islands-on-the-air/downloads/download-file.html?path=groups.json" DATA_URL = "https://www.iota-world.org/islands-on-the-air/downloads/download-file.html?path=groups.json"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -1
View File
@@ -1,3 +1,4 @@
from core.enums import ActivityName
from providers.activityrefdata.pnp_kml_activity_ref_data_provider import ( from providers.activityrefdata.pnp_kml_activity_ref_data_provider import (
ParksNPeaksKMLActivityRefDataProvider, ParksNPeaksKMLActivityRefDataProvider,
) )
@@ -7,7 +8,7 @@ class KRMNPA(ParksNPeaksKMLActivityRefDataProvider):
"""Activity ref data provider for the Keith Roget Memorrial National Parks Award (KRMNPA).""" """Activity ref data provider for the Keith Roget Memorrial National Parks Award (KRMNPA)."""
POLL_INTERVAL_DAYS = 365 POLL_INTERVAL_DAYS = 365
ACTIVITY = "KRMNPA" ACTIVITY = ActivityName.KRMNPA
DATA_URL = "https://parksnpeaks.org/getPOI.php?poiClass=KRMNPA&poiFormat=4" DATA_URL = "https://parksnpeaks.org/getPOI.php?poiClass=KRMNPA&poiFormat=4"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -2,7 +2,7 @@ from time import sleep
from pyhamtools.locator import locator_to_latlong from pyhamtools.locator import locator_to_latlong
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import ( from providers.activityrefdata.file_download_activity_ref_data_provider import (
FileDownloadActivityRefDataProvider, FileDownloadActivityRefDataProvider,
@@ -13,7 +13,7 @@ class LLOTA(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Lagos y Lagunas on the Air""" """Activity ref data provider for Lagos y Lagunas on the Air"""
POLL_INTERVAL_DAYS = 7 POLL_INTERVAL_DAYS = 7
ACTIVITY = "LLOTA" ACTIVITY = ActivityName.LLOTA
DATA_URL = "https://llota.app/api/public/references" DATA_URL = "https://llota.app/api/public/references"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -1,7 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import ( from providers.activityrefdata.file_download_activity_ref_data_provider import (
FileDownloadActivityRefDataProvider, FileDownloadActivityRefDataProvider,
@@ -12,7 +12,7 @@ class MOTA(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Mills on the Air""" """Activity ref data provider for Mills on the Air"""
POLL_INTERVAL_DAYS = 30 POLL_INTERVAL_DAYS = 30
ACTIVITY = "MOTA" ACTIVITY = ActivityName.MOTA
DATA_URL = "https://www.gma.rocks/download/mills.csv" DATA_URL = "https://www.gma.rocks/download/mills.csv"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -2,7 +2,7 @@ from time import sleep
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider from providers.activityrefdata.file_download_activity_ref_data_provider import FileDownloadActivityRefDataProvider
@@ -11,7 +11,7 @@ class PGA(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Polish Gmina Award""" """Activity ref data provider for Polish Gmina Award"""
POLL_INTERVAL_DAYS = 30 POLL_INTERVAL_DAYS = 30
ACTIVITY = "PGA" ACTIVITY = ActivityName.PGA
DATA_URL = "http://www.spga.pl/lista_pga2.php" DATA_URL = "http://www.spga.pl/lista_pga2.php"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -1,7 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import ( from providers.activityrefdata.file_download_activity_ref_data_provider import (
FileDownloadActivityRefDataProvider, FileDownloadActivityRefDataProvider,
@@ -12,7 +12,7 @@ class POTA(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Parks on the Air""" """Activity ref data provider for Parks on the Air"""
POLL_INTERVAL_DAYS = 7 POLL_INTERVAL_DAYS = 7
ACTIVITY = "POTA" ACTIVITY = ActivityName.POTA
DATA_URL = "https://pota.app/all_parks_ext.csv" DATA_URL = "https://pota.app/all_parks_ext.csv"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -1
View File
@@ -1,3 +1,4 @@
from core.enums import ActivityName
from providers.activityrefdata.pnp_kml_activity_ref_data_provider import ( from providers.activityrefdata.pnp_kml_activity_ref_data_provider import (
ParksNPeaksKMLActivityRefDataProvider, ParksNPeaksKMLActivityRefDataProvider,
) )
@@ -7,7 +8,7 @@ class SANPCPA(ParksNPeaksKMLActivityRefDataProvider):
"""Activity ref data provider for the South Australia National Parks and Conservation Parks Award (SANPCPA).""" """Activity ref data provider for the South Australia National Parks and Conservation Parks Award (SANPCPA)."""
POLL_INTERVAL_DAYS = 365 POLL_INTERVAL_DAYS = 365
ACTIVITY = "SANPCPA" ACTIVITY = ActivityName.SANPCPA
DATA_URL = "https://parksnpeaks.org/getPOI.php?poiClass=SANPCPA&poiFormat=4" DATA_URL = "https://parksnpeaks.org/getPOI.php?poiClass=SANPCPA&poiFormat=4"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -1,7 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import ( from providers.activityrefdata.file_download_activity_ref_data_provider import (
FileDownloadActivityRefDataProvider, FileDownloadActivityRefDataProvider,
@@ -12,7 +12,7 @@ class SIOTA(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Silos on the Air""" """Activity ref data provider for Silos on the Air"""
POLL_INTERVAL_DAYS = 30 POLL_INTERVAL_DAYS = 30
ACTIVITY = "SIOTA" ACTIVITY = ActivityName.SIOTA
DATA_URL = "https://www.silosontheair.com/data/silos.csv" DATA_URL = "https://www.silosontheair.com/data/silos.csv"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -3,7 +3,7 @@ from time import sleep
from pyhamtools.locator import latlong_to_locator from pyhamtools.locator import latlong_to_locator
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import ( from providers.activityrefdata.file_download_activity_ref_data_provider import (
FileDownloadActivityRefDataProvider, FileDownloadActivityRefDataProvider,
@@ -14,7 +14,7 @@ class SOTA(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Summits on the Air""" """Activity ref data provider for Summits on the Air"""
POLL_INTERVAL_DAYS = 30 POLL_INTERVAL_DAYS = 30
ACTIVITY = "SOTA" ACTIVITY = ActivityName.SOTA
DATA_URL = "https://storage.sota.org.uk/summitslist.csv" DATA_URL = "https://storage.sota.org.uk/summitslist.csv"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -1,6 +1,6 @@
import csv import csv
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.local_file_activity_ref_data_provider import ( from providers.activityrefdata.local_file_activity_ref_data_provider import (
LocalFileActivityRefDataProvider, LocalFileActivityRefDataProvider,
@@ -10,7 +10,7 @@ from providers.activityrefdata.local_file_activity_ref_data_provider import (
class Toilets(LocalFileActivityRefDataProvider): class Toilets(LocalFileActivityRefDataProvider):
"""Activity ref data provider for Toilets on the Air""" """Activity ref data provider for Toilets on the Air"""
ACTIVITY = "Toilets" ACTIVITY = ActivityName.TOILETS
PATH = "datafiles/toilets.csv" PATH = "datafiles/toilets.csv"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -1,7 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import ( from providers.activityrefdata.file_download_activity_ref_data_provider import (
FileDownloadActivityRefDataProvider, FileDownloadActivityRefDataProvider,
@@ -12,7 +12,7 @@ class Towers(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Towers on the Air""" """Activity ref data provider for Towers on the Air"""
POLL_INTERVAL_DAYS = 30 POLL_INTERVAL_DAYS = 30
ACTIVITY = "Towers" ACTIVITY = ActivityName.TOWERS
DATA_URL = "https://wwtota.com/servis/generate_csv.php?ref=&filter=all" DATA_URL = "https://wwtota.com/servis/generate_csv.php?ref=&filter=all"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -4,7 +4,7 @@ from time import sleep
from pyhamtools.locator import latlong_to_locator from pyhamtools.locator import latlong_to_locator
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import ( from providers.activityrefdata.file_download_activity_ref_data_provider import (
FileDownloadActivityRefDataProvider, FileDownloadActivityRefDataProvider,
@@ -17,7 +17,7 @@ class WCA(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for World Castles Award""" """Activity ref data provider for World Castles Award"""
POLL_INTERVAL_DAYS = 30 POLL_INTERVAL_DAYS = 30
ACTIVITY = "WCA" ACTIVITY = ActivityName.WCA
DATA_URL = "https://polo.ham2k.com/data/activities/wca/all-castles.csv" DATA_URL = "https://polo.ham2k.com/data/activities/wca/all-castles.csv"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -1,6 +1,6 @@
from time import sleep from time import sleep
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import ( from providers.activityrefdata.file_download_activity_ref_data_provider import (
FileDownloadActivityRefDataProvider, FileDownloadActivityRefDataProvider,
@@ -11,7 +11,7 @@ class WOTA(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Wainwrights on the Air""" """Activity ref data provider for Wainwrights on the Air"""
POLL_INTERVAL_DAYS = 365 POLL_INTERVAL_DAYS = 365
ACTIVITY = "WOTA" ACTIVITY = ActivityName.WOTA
DATA_URL = "https://www.wota.org.uk/mapping/data/summits.json" DATA_URL = "https://www.wota.org.uk/mapping/data/summits.json"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -1,7 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import ( from providers.activityrefdata.file_download_activity_ref_data_provider import (
FileDownloadActivityRefDataProvider, FileDownloadActivityRefDataProvider,
@@ -12,7 +12,7 @@ class WWBOTA(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Worldwide Bunkers on the Air""" """Activity ref data provider for Worldwide Bunkers on the Air"""
POLL_INTERVAL_DAYS = 30 POLL_INTERVAL_DAYS = 30
ACTIVITY = "WWBOTA" ACTIVITY = ActivityName.WWBOTA
DATA_URL = "https://api.wwbota.org/bunkers/?format=CSV" DATA_URL = "https://api.wwbota.org/bunkers/?format=CSV"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -1,7 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import ( from providers.activityrefdata.file_download_activity_ref_data_provider import (
FileDownloadActivityRefDataProvider, FileDownloadActivityRefDataProvider,
@@ -12,7 +12,7 @@ class WWFF(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for Worldwide Flora & Fauna""" """Activity ref data provider for Worldwide Flora & Fauna"""
POLL_INTERVAL_DAYS = 30 POLL_INTERVAL_DAYS = 30
ACTIVITY = "WWFF" ACTIVITY = ActivityName.WWFF
DATA_URL = "https://wwff.co/wwff-data/wwff_directory.csv" DATA_URL = "https://wwff.co/wwff-data/wwff_directory.csv"
def __init__(self, provider_config): def __init__(self, provider_config):
+2 -2
View File
@@ -2,7 +2,7 @@ from time import sleep
from pyhamtools.locator import latlong_to_locator from pyhamtools.locator import latlong_to_locator
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from providers.activityrefdata.file_download_activity_ref_data_provider import ( from providers.activityrefdata.file_download_activity_ref_data_provider import (
FileDownloadActivityRefDataProvider, FileDownloadActivityRefDataProvider,
@@ -13,7 +13,7 @@ class ZLOTA(FileDownloadActivityRefDataProvider):
"""Activity ref data provider for New Zealand on the Air""" """Activity ref data provider for New Zealand on the Air"""
POLL_INTERVAL_DAYS = 30 POLL_INTERVAL_DAYS = 30
ACTIVITY = "ZLOTA" ACTIVITY = ActivityName.ZLOTA
DATA_URL = "https://ontheair.nz/assets/assets.json" DATA_URL = "https://ontheair.nz/assets/assets.json"
def __init__(self, provider_config): def __init__(self, provider_config):
+3 -2
View File
@@ -3,6 +3,7 @@ from datetime import datetime, timedelta
import pytz import pytz
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from core.enums import ActivityName
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.alert import Alert from data.alert import Alert
from providers.alert.http_alert_provider import HTTPAlertProvider from providers.alert.http_alert_provider import HTTPAlertProvider
@@ -55,8 +56,8 @@ class BOTA(HTTPAlertProvider):
alert = Alert( alert = Alert(
source=self.name, source=self.name,
dx_calls=[dx_call], dx_calls=[dx_call],
sig="BOTA", sig=ActivityName.BOTA,
sig_refs=[ActivityRef(id=ref_name, sig="BOTA")], sig_refs=[ActivityRef(id=ref_name, sig=ActivityName.BOTA)],
start_time=date_time.timestamp(), start_time=date_time.timestamp(),
) )
+3 -2
View File
@@ -2,6 +2,7 @@ from datetime import datetime
import pytz import pytz
from core.enums import ActivityName
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.alert import Alert from data.alert import Alert
from providers.alert.http_alert_provider import HTTPAlertProvider from providers.alert.http_alert_provider import HTTPAlertProvider
@@ -34,11 +35,11 @@ class Hamsat(HTTPAlertProvider):
dx_calls=[source_alert["callsign"].upper()], dx_calls=[source_alert["callsign"].upper()],
freqs_modes=freqs_modes, freqs_modes=freqs_modes,
comment=source_alert["comment"], comment=source_alert["comment"],
sig="Satellite", sig=ActivityName.SATELLITE,
# Fudge an activity ref to provide the remaining bits of data we need: the satellite and the operator's grid # Fudge an activity ref to provide the remaining bits of data we need: the satellite and the operator's grid
sig_refs=[ sig_refs=[
ActivityRef( ActivityRef(
sig="Satellite", sig=ActivityName.SATELLITE,
id=f"{source_alert['satellite']['name']} from {source_alert['grids'][0]}", id=f"{source_alert['satellite']['name']} from {source_alert['grids'][0]}",
) )
], ],
+2 -1
View File
@@ -6,6 +6,7 @@ import pytz
from rss_parser import Parser from rss_parser import Parser
from rss_parser.models.rss import RSS from rss_parser.models.rss import RSS
from core.enums import ActivityName
from data.alert import Alert from data.alert import Alert
from providers.alert.http_alert_provider import HTTPAlertProvider from providers.alert.http_alert_provider import HTTPAlertProvider
@@ -88,7 +89,7 @@ class NG3K(HTTPAlertProvider):
comment=f"{by}; {comment}; {qsl_info}", comment=f"{by}; {comment}; {qsl_info}",
start_time=start_timestamp, start_time=start_timestamp,
end_time=end_timestamp, end_time=end_timestamp,
sig="DXpedition", sig=ActivityName.DXPEDITION,
) )
# Add to our list. # Add to our list.
+13 -12
View File
@@ -3,6 +3,7 @@ from datetime import datetime
import pytz import pytz
from core.enums import ActivityName
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.alert import Alert from data.alert import Alert
from providers.alert.http_alert_provider import HTTPAlertProvider from providers.alert.http_alert_provider import HTTPAlertProvider
@@ -39,7 +40,7 @@ class ParksNPeaks(HTTPAlertProvider):
activity_refs = [] activity_refs = []
# PnP can give us an alert of class "QRP" which is the only one that's not a real activity in Spothole's # PnP can give us an alert of class "QRP" which is the only one that's not a real activity in Spothole's
# list, so mask this out if we got it. # list, so mask this out if we got it.
if activity != "QRP": if activity != ActivityName.QRP:
activity_refs = [ActivityRef(id=ref_id, sig=activity, name=ref_name)] activity_refs = [ActivityRef(id=ref_id, sig=activity, name=ref_name)]
# Convert to our alert format # Convert to our alert format
@@ -56,22 +57,22 @@ class ParksNPeaks(HTTPAlertProvider):
# Log a warning for the developer if PnP gives us an unknown programme we've never seen before # Log a warning for the developer if PnP gives us an unknown programme we've never seen before
if activity and activity not in [ if activity and activity not in [
"POTA", ActivityName.POTA,
"SOTA", ActivityName.SOTA,
"WWFF", ActivityName.WWFF,
"HEMA", ActivityName.HEMA,
"SIOTA", ActivityName.SIOTA,
"ZLOTA", ActivityName.ZLOTA,
"KRMNPA", ActivityName.KRMNPA,
"SANPCPA", ActivityName.SANPCPA,
"LLOTA", ActivityName.LLOTA,
"QRP", ActivityName.QRP,
]: ]:
logger.warning(f"PNP alert found with activity {activity}, developer needs to add support for this!") logger.warning(f"PNP alert found with activity {activity}, developer needs to add support for this!")
# If this is POTA, SOTA or WWFF data we already have it through other means, so ignore. Otherwise, add to # If this is POTA, SOTA or WWFF data we already have it through other means, so ignore. Otherwise, add to
# the alert list. Note that while ZLOTA has its own spots API, it doesn't have its own alerts API. So that # the alert list. Note that while ZLOTA has its own spots API, it doesn't have its own alerts API. So that
# means the PnP *spot* provider rejects ZLOTA spots here, but the PnP *alerts* provider here allows ZLOTA. # means the PnP *spot* provider rejects ZLOTA spots here, but the PnP *alerts* provider here allows ZLOTA.
if activity not in ["POTA", "SOTA", "WWFF"]: if activity not in [ActivityName.POTA, ActivityName.SOTA, ActivityName.WWFF]:
new_alerts.append(alert) new_alerts.append(alert)
return new_alerts return new_alerts
+3 -2
View File
@@ -2,6 +2,7 @@ from datetime import datetime
import pytz import pytz
from core.enums import ActivityName
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.alert import Alert from data.alert import Alert
from providers.alert.http_alert_provider import HTTPAlertProvider from providers.alert.http_alert_provider import HTTPAlertProvider
@@ -27,11 +28,11 @@ class POTA(HTTPAlertProvider):
dx_calls=[source_alert["activator"].upper()], dx_calls=[source_alert["activator"].upper()],
freqs_modes=source_alert["frequencies"], freqs_modes=source_alert["frequencies"],
comment=source_alert["comments"], comment=source_alert["comments"],
sig="POTA", sig=ActivityName.POTA,
sig_refs=[ sig_refs=[
ActivityRef( ActivityRef(
id=source_alert["reference"], id=source_alert["reference"],
sig="POTA", sig=ActivityName.POTA,
name=source_alert["name"], name=source_alert["name"],
url=f"https://pota.app/#/park/{source_alert['reference']}", url=f"https://pota.app/#/park/{source_alert['reference']}",
) )
+2 -1
View File
@@ -3,6 +3,7 @@ import re
from icalendar import Event from icalendar import Event
from core.enums import Continent from core.enums import Continent
from core.enums import ActivityName
from data.alert import Alert from data.alert import Alert
from providers.alert.ical_alert_provider import ICALAlertProvider from providers.alert.ical_alert_provider import ICALAlertProvider
@@ -69,7 +70,7 @@ class RSGBICALAlertProvider(ICALAlertProvider):
comment=summary, comment=summary,
start_time=start_timestamp, start_time=start_timestamp,
end_time=end_timestamp, end_time=end_timestamp,
sig="Contest", sig=ActivityName.CONTEST,
) )
return alert return alert
+3 -2
View File
@@ -2,6 +2,7 @@ from datetime import datetime
import pytz import pytz
from core.enums import ActivityName
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.alert import Alert from data.alert import Alert
from providers.alert.http_alert_provider import HTTPAlertProvider from providers.alert.http_alert_provider import HTTPAlertProvider
@@ -33,11 +34,11 @@ class SOTA(HTTPAlertProvider):
dx_names=[source_alert["activatorName"].upper()], dx_names=[source_alert["activatorName"].upper()],
freqs_modes=source_alert["frequency"], freqs_modes=source_alert["frequency"],
comment=source_alert["comments"], comment=source_alert["comments"],
sig="SOTA", sig=ActivityName.SOTA,
sig_refs=[ sig_refs=[
ActivityRef( ActivityRef(
id=f"{source_alert['associationCode']}/{source_alert['summitCode']}", id=f"{source_alert['associationCode']}/{source_alert['summitCode']}",
sig="SOTA", sig=ActivityName.SOTA,
name=summit_name, name=summit_name,
activation_score=summit_points, activation_score=summit_points,
) )
+2 -1
View File
@@ -1,5 +1,6 @@
from icalendar import Event from icalendar import Event
from core.enums import ActivityName
from data.alert import Alert from data.alert import Alert
from providers.alert.ical_alert_provider import ICALAlertProvider from providers.alert.ical_alert_provider import ICALAlertProvider
@@ -34,7 +35,7 @@ class WA7BNM(ICALAlertProvider):
url=url, url=url,
start_time=start_timestamp, start_time=start_timestamp,
end_time=end_timestamp, end_time=end_timestamp,
sig="Contest", sig=ActivityName.CONTEST,
) )
return alert return alert
+2 -1
View File
@@ -7,6 +7,7 @@ import pytz
from rss_parser import Parser as RSSParser from rss_parser import Parser as RSSParser
from rss_parser.models.rss import RSS from rss_parser.models.rss import RSS
from core.enums import ActivityName
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.alert import Alert from data.alert import Alert
from providers.alert.http_alert_provider import HTTPAlertProvider from providers.alert.http_alert_provider import HTTPAlertProvider
@@ -74,7 +75,7 @@ class WOTA(HTTPAlertProvider):
dx_calls=[dx_call], dx_calls=[dx_call],
freqs_modes=freqs_modes, freqs_modes=freqs_modes,
comment=comment, comment=comment,
sig_refs=[ActivityRef(id=ref, sig="WOTA", name=ref_name)] if ref else [], sig_refs=[ActivityRef(id=ref, sig=ActivityName.WOTA, name=ref_name)] if ref else [],
start_time=time.timestamp(), start_time=time.timestamp(),
) )
+3 -2
View File
@@ -2,6 +2,7 @@ from datetime import datetime
import pytz import pytz
from core.enums import ActivityName
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.alert import Alert from data.alert import Alert
from providers.alert.http_alert_provider import HTTPAlertProvider from providers.alert.http_alert_provider import HTTPAlertProvider
@@ -27,8 +28,8 @@ class WWFF(HTTPAlertProvider):
dx_calls=[source_alert["activator_call"].upper()], dx_calls=[source_alert["activator_call"].upper()],
freqs_modes=f"{source_alert['band']} {source_alert['mode']}", freqs_modes=f"{source_alert['band']} {source_alert['mode']}",
comment=source_alert["remarks"], comment=source_alert["remarks"],
sig="WWFF", sig=ActivityName.WWFF,
sig_refs=[ActivityRef(id=source_alert["reference"], sig="WWFF")], sig_refs=[ActivityRef(id=source_alert["reference"], sig=ActivityName.WWFF)],
start_time=datetime.strptime(source_alert["utc_start"], "%Y-%m-%d %H:%M:%S") start_time=datetime.strptime(source_alert["utc_start"], "%Y-%m-%d %H:%M:%S")
.replace(tzinfo=pytz.UTC) .replace(tzinfo=pytz.UTC)
.timestamp(), .timestamp(),
+18 -17
View File
@@ -4,7 +4,8 @@ from datetime import datetime
import pytz import pytz
from core.constants import HTTP_HEADERS from core.constants import HTTP_HEADERS
from core.enums import ActivityRefType, Mode from core.enums import Mode
from core.enums import ActivityName, ActivityRefType
from core.url_data_cache import URLDataCache from core.url_data_cache import URLDataCache
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.spot import Spot from data.spot import Spot
@@ -106,40 +107,40 @@ class GMA(HTTPSpotProvider):
spot.sig_refs spot.sig_refs
and ref_info and ref_info
and "reftype" in ref_info and "reftype" in ref_info
and ref_info["reftype"] not in ["POTA", "WWFF"] and ref_info["reftype"] not in [ActivityName.POTA, ActivityName.WWFF]
and ( and (
ref_info["reftype"] != "Summit" or "sota" not in ref_info or ref_info["sota"] == "" ref_info["reftype"] != "Summit" or "sota" not in ref_info or ref_info["sota"] == ""
) )
): ):
match ref_info["reftype"]: match ref_info["reftype"]:
case "Summit": case "Summit":
spot.sig_refs[0].sig = "GMA" spot.sig_refs[0].sig = ActivityName.GMA
spot.sig_refs[0].ref_type = ActivityRefType.SUMMIT spot.sig_refs[0].ref_type = ActivityRefType.SUMMIT
spot.sig = "GMA" spot.sig = ActivityName.GMA
case "IOTA Island": case "IOTA Island":
spot.sig_refs[0].sig = "IOTA" spot.sig_refs[0].sig = ActivityName.IOTA
spot.sig_refs[0].ref_type = ActivityRefType.ISLAND spot.sig_refs[0].ref_type = ActivityRefType.ISLAND
spot.sig = "IOTA" spot.sig = ActivityName.IOTA
case "GMA Island": case "GMA Island":
spot.sig_refs[0].sig = "GMA Islands" spot.sig_refs[0].sig = ActivityName.GMA_ISLANDS
spot.sig_refs[0].ref_type = ActivityRefType.ISLAND spot.sig_refs[0].ref_type = ActivityRefType.ISLAND
spot.sig = "GMA Islands" spot.sig = ActivityName.GMA_ISLANDS
case "Lighthouse (ILLW)": case "Lighthouse (ILLW)":
spot.sig_refs[0].sig = "ILLW" spot.sig_refs[0].sig = ActivityName.ILLW
spot.sig_refs[0].ref_type = ActivityRefType.LIGHTHOUSE spot.sig_refs[0].ref_type = ActivityRefType.LIGHTHOUSE
spot.sig = "ILLW" spot.sig = ActivityName.ILLW
case "Lighthouse (ARLHS)": case "Lighthouse (ARLHS)":
spot.sig_refs[0].sig = "ARLHS" spot.sig_refs[0].sig = ActivityName.ARLHS
spot.sig_refs[0].ref_type = ActivityRefType.LIGHTHOUSE spot.sig_refs[0].ref_type = ActivityRefType.LIGHTHOUSE
spot.sig = "ARLHS" spot.sig = ActivityName.ARLHS
case "Castle": case "Castle":
spot.sig_refs[0].sig = "WCA" spot.sig_refs[0].sig = ActivityName.WCA
spot.sig_refs[0].ref_type = ActivityRefType.CASTLE spot.sig_refs[0].ref_type = ActivityRefType.CASTLE
spot.sig = "WCA" spot.sig = ActivityName.WCA
case "Mill": case "Mill":
spot.sig_refs[0].sig = "MOTA" spot.sig_refs[0].sig = ActivityName.MOTA
spot.sig_refs[0].ref_type = ActivityRefType.MILL spot.sig_refs[0].ref_type = ActivityRefType.MILL
spot.sig = "MOTA" spot.sig = ActivityName.MOTA
case _: case _:
logger.warning( logger.warning(
f"GMA spot found with ref type {ref_info['reftype']}, developer needs to add support for this!" f"GMA spot found with ref type {ref_info['reftype']}, developer needs to add support for this!"
@@ -170,7 +171,7 @@ class GMA(HTTPSpotProvider):
return new_spots return new_spots
def can_submit_spot(self, activity): def can_submit_spot(self, activity):
return activity == "GMA" return activity == ActivityName.GMA
def submit_spot(self, spot, credentials): def submit_spot(self, spot, credentials):
# TODO: Implement. # TODO: Implement.
+5 -4
View File
@@ -7,7 +7,8 @@ import requests
from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
from core.constants import HTTP_HEADERS from core.constants import HTTP_HEADERS
from core.enums import ActivityRefType, Mode from core.enums import Mode
from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -62,11 +63,11 @@ class HEMA(HTTPSpotProvider):
freq=float(freq_mode_match.group(1)) * 1000000, freq=float(freq_mode_match.group(1)) * 1000000,
mode=Mode.from_name(freq_mode_match.group(2).upper()), mode=Mode.from_name(freq_mode_match.group(2).upper()),
comment=spotter_comment_match.group(2), comment=spotter_comment_match.group(2),
sig="HEMA", sig=ActivityName.HEMA,
sig_refs=[ sig_refs=[
ActivityRef( ActivityRef(
id=spot_items[3].upper(), id=spot_items[3].upper(),
sig="HEMA", sig=ActivityName.HEMA,
name=spot_items[4], name=spot_items[4],
latitude=float(spot_items[7]), latitude=float(spot_items[7]),
longitude=float(spot_items[8]), longitude=float(spot_items[8]),
@@ -90,7 +91,7 @@ class HEMA(HTTPSpotProvider):
return new_spots return new_spots
def can_submit_spot(self, activity): def can_submit_spot(self, activity):
return activity == "HEMA" return activity == ActivityName.HEMA
def submit_spot(self, spot, credentials): def submit_spot(self, spot, credentials):
# TODO: Implement. Currently blocked awaiting their API team to make a change to allow us to spot with a # TODO: Implement. Currently blocked awaiting their API team to make a change to allow us to spot with a
+4 -3
View File
@@ -1,6 +1,7 @@
from datetime import datetime from datetime import datetime
from core.enums import ActivityRefType, Mode from core.enums import Mode
from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -34,11 +35,11 @@ class LLOTA(HTTPSpotProvider):
freq=float(source_spot["frequency"]) * 1000000, freq=float(source_spot["frequency"]) * 1000000,
mode=Mode.from_name(source_spot["mode"].upper()), mode=Mode.from_name(source_spot["mode"].upper()),
comment=comment, comment=comment,
sig="LLOTA", sig=ActivityName.LLOTA,
sig_refs=[ sig_refs=[
ActivityRef( ActivityRef(
id=source_spot["reference"], id=source_spot["reference"],
sig="LLOTA", sig=ActivityName.LLOTA,
name=source_spot["reference_name"], name=source_spot["reference_name"],
ref_type=ActivityRefType.LAKE, ref_type=ActivityRefType.LAKE,
) )
+20 -19
View File
@@ -7,6 +7,7 @@ import requests
from core.constants import HTTP_HEADERS from core.constants import HTTP_HEADERS
from core.enums import Mode from core.enums import Mode
from core.enums import ActivityName
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -21,15 +22,15 @@ class ParksNPeaks(HTTPSpotProvider):
SPOTS_URL = "https://www.parksnpeaks.org/api/ALL" SPOTS_URL = "https://www.parksnpeaks.org/api/ALL"
SUBMIT_URL = "https://www.parksnpeaks.org/api/SPOT/" SUBMIT_URL = "https://www.parksnpeaks.org/api/SPOT/"
SUBMITTABLE_ACTIVITIES = [ SUBMITTABLE_ACTIVITIES = [
"POTA", ActivityName.POTA,
"SOTA", ActivityName.SOTA,
"WWFF", ActivityName.WWFF,
"HEMA", ActivityName.HEMA,
"WOTA", ActivityName.WOTA,
"ZLOTA", ActivityName.ZLOTA,
"SIOTA", ActivityName.SIOTA,
"KRMNPA", ActivityName.KRMNPA,
"SANPCPA", ActivityName.SANPCPA,
] ]
def __init__(self, provider_config): def __init__(self, provider_config):
@@ -68,7 +69,7 @@ class ParksNPeaks(HTTPSpotProvider):
# programme with a defined set of references # programme with a defined set of references
activity = source_spot["actClass"].upper() activity = source_spot["actClass"].upper()
ref_id = source_spot["actSiteID"] ref_id = source_spot["actSiteID"]
if activity and activity != "" and activity != "QRP" and ref_id and ref_id != "": if activity and activity != "" and activity != ActivityName.QRP and ref_id and ref_id != "":
spot.sig = activity spot.sig = activity
activity_refs = [ activity_refs = [
ActivityRef( ActivityRef(
@@ -84,15 +85,15 @@ class ParksNPeaks(HTTPSpotProvider):
# Log a warning for the developer if PnP gives us an unknown programme we've never seen before # Log a warning for the developer if PnP gives us an unknown programme we've never seen before
if activity not in [ if activity not in [
"POTA", ActivityName.POTA,
"SOTA", ActivityName.SOTA,
"WWFF", ActivityName.WWFF,
"HEMA", ActivityName.HEMA,
"SIOTA", ActivityName.SIOTA,
"ZLOTA", ActivityName.ZLOTA,
"KRMNPA", ActivityName.KRMNPA,
"SANPCPA", ActivityName.SANPCPA,
"LLOTA", ActivityName.LLOTA,
]: ]:
logger.warning(f"PNP spot found with activity {activity}, developer needs to add support for this!") logger.warning(f"PNP spot found with activity {activity}, developer needs to add support for this!")
+5 -4
View File
@@ -4,7 +4,8 @@ import pytz
import requests import requests
from core.constants import HTTP_HEADERS from core.constants import HTTP_HEADERS
from core.enums import ActivityRefType, Mode from core.enums import Mode
from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -33,11 +34,11 @@ class POTA(HTTPSpotProvider):
freq=float(source_spot["frequency"]) * 1000 if source_spot["frequency"] != "INVALID" else None, freq=float(source_spot["frequency"]) * 1000 if source_spot["frequency"] != "INVALID" else None,
mode=Mode.from_name(source_spot["mode"].upper()), mode=Mode.from_name(source_spot["mode"].upper()),
comment=source_spot["comments"], comment=source_spot["comments"],
sig="POTA", sig=ActivityName.POTA,
sig_refs=[ sig_refs=[
ActivityRef( ActivityRef(
id=source_spot["reference"], id=source_spot["reference"],
sig="POTA", sig=ActivityName.POTA,
name=source_spot["name"], name=source_spot["name"],
latitude=source_spot["latitude"], latitude=source_spot["latitude"],
longitude=source_spot["longitude"], longitude=source_spot["longitude"],
@@ -58,7 +59,7 @@ class POTA(HTTPSpotProvider):
return new_spots return new_spots
def can_submit_spot(self, activity): def can_submit_spot(self, activity):
return activity == "POTA" return activity == ActivityName.POTA
def submit_spot(self, spot, credentials): def submit_spot(self, spot, credentials):
sig_ref = spot.sig_refs[0].id if spot.sig_refs else None sig_ref = spot.sig_refs[0].id if spot.sig_refs else None
+5 -4
View File
@@ -5,7 +5,8 @@ import requests
from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
from core.constants import HTTP_HEADERS from core.constants import HTTP_HEADERS
from core.enums import ActivityRefType, Mode from core.enums import Mode
from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -56,11 +57,11 @@ class SOTA(HTTPSpotProvider):
# Seen SOTA spots with no frequency! # Seen SOTA spots with no frequency!
mode=Mode.from_name(source_spot["mode"].upper()), mode=Mode.from_name(source_spot["mode"].upper()),
comment=source_spot["comments"], comment=source_spot["comments"],
sig="SOTA", sig=ActivityName.SOTA,
sig_refs=[ sig_refs=[
ActivityRef( ActivityRef(
id=source_spot["summitCode"], id=source_spot["summitCode"],
sig="SOTA", sig=ActivityName.SOTA,
name=source_spot["summitName"], name=source_spot["summitName"],
latitude=source_spot["latitude"], latitude=source_spot["latitude"],
longitude=source_spot["longitude"], longitude=source_spot["longitude"],
@@ -83,7 +84,7 @@ class SOTA(HTTPSpotProvider):
return new_spots return new_spots
def can_submit_spot(self, activity): def can_submit_spot(self, activity):
return activity == "SOTA" return activity == ActivityName.SOTA
def submit_spot(self, spot, credentials): def submit_spot(self, spot, credentials):
# TODO test this method works # TODO test this method works
+5 -4
View File
@@ -4,7 +4,8 @@ from datetime import datetime
import requests import requests
from core.constants import HTTP_HEADERS from core.constants import HTTP_HEADERS
from core.enums import ActivityRefType, LocationSourceForSpot, Mode from core.enums import LocationSourceForSpot, Mode
from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -58,13 +59,13 @@ class Tiles(HTTPSpotProvider):
freq=freq, freq=freq,
mode=Mode.from_name(source_spot["mode"].upper()), mode=Mode.from_name(source_spot["mode"].upper()),
comment=source_spot["notes"], comment=source_spot["notes"],
sig="Tiles", sig=ActivityName.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. # 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 activity reference. # Just take the grid reference itself as the single Tiles activity reference.
sig_refs=[ sig_refs=[
ActivityRef( ActivityRef(
id=source_spot["maidenhead_grid"], id=source_spot["maidenhead_grid"],
sig="Tiles", sig=ActivityName.TILES,
name=source_spot["maidenhead_grid"], name=source_spot["maidenhead_grid"],
latitude=source_spot["latitude"], latitude=source_spot["latitude"],
longitude=source_spot["longitude"], longitude=source_spot["longitude"],
@@ -84,7 +85,7 @@ class Tiles(HTTPSpotProvider):
return new_spots return new_spots
def can_submit_spot(self, activity): def can_submit_spot(self, activity):
return activity == "Tiles" return activity == ActivityName.TILES
def submit_spot(self, spot, credentials): def submit_spot(self, spot, credentials):
# Tiles on the air currently only supports *self* spots # Tiles on the air currently only supports *self* spots
+3 -3
View File
@@ -3,7 +3,7 @@ from datetime import datetime
import pytz import pytz
from core.enums import ActivityRefType from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -34,8 +34,8 @@ class Towers(HTTPSpotProvider):
dx_call=source_spot["call"].upper(), dx_call=source_spot["call"].upper(),
freq=likely_freq, freq=likely_freq,
comment=source_spot["comment"], comment=source_spot["comment"],
sig="Towers", sig=ActivityName.TOWERS,
sig_refs=[ActivityRef(id=source_spot["ref"], sig="Towers", ref_type=ActivityRefType.TOWER)], sig_refs=[ActivityRef(id=source_spot["ref"], sig=ActivityName.TOWERS, ref_type=ActivityRefType.TOWER)],
time=datetime.strptime(response_json["updated"][:10] + source_spot["time"], "%Y-%m-%d%H:%M") time=datetime.strptime(response_json["updated"][:10] + source_spot["time"], "%Y-%m-%d%H:%M")
.replace(tzinfo=pytz.utc) .replace(tzinfo=pytz.utc)
.timestamp(), .timestamp(),
+9 -4
View File
@@ -8,7 +8,8 @@ import pytz
from rss_parser import Parser from rss_parser import Parser
from rss_parser.models.rss import RSS from rss_parser.models.rss import RSS
from core.enums import ActivityRefType, Mode from core.enums import Mode
from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -90,8 +91,12 @@ class WOTA(HTTPSpotProvider):
freq=freq_hz, freq=freq_hz,
mode=Mode.from_name(mode), mode=Mode.from_name(mode),
comment=comment, comment=comment,
sig="WOTA", sig=ActivityName.WOTA,
sig_refs=[ActivityRef(id=ref, sig="WOTA", name=ref_name, ref_type=ActivityRefType.SUMMIT)] if ref else [], sig_refs=(
[ActivityRef(id=ref, sig=ActivityName.WOTA, name=ref_name, ref_type=ActivityRefType.SUMMIT)]
if ref
else []
),
time=time.timestamp(), time=time.timestamp(),
) )
@@ -105,7 +110,7 @@ class WOTA(HTTPSpotProvider):
return new_spots return new_spots
def can_submit_spot(self, activity): def can_submit_spot(self, activity):
return activity == "WOTA" return activity == ActivityName.WOTA
def submit_spot(self, spot, credentials): def submit_spot(self, spot, credentials):
# TODO Ask M5TEA if he's happy to share how this is done from his app # TODO Ask M5TEA if he's happy to share how this is done from his app
+5 -4
View File
@@ -1,7 +1,8 @@
import json import json
from datetime import datetime from datetime import datetime
from core.enums import ActivityRefType, Mode from core.enums import Mode
from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.spot import Spot from data.spot import Spot
from providers.spot.sse_spot_provider import SSESpotProvider from providers.spot.sse_spot_provider import SSESpotProvider
@@ -23,7 +24,7 @@ class WWBOTA(SSESpotProvider):
for ref in source_spot["references"]: for ref in source_spot["references"]:
activity_ref = ActivityRef( activity_ref = ActivityRef(
id=ref["reference"], id=ref["reference"],
sig="WWBOTA", sig=ActivityName.WWBOTA,
name=ref["name"], name=ref["name"],
latitude=ref["lat"], latitude=ref["lat"],
longitude=ref["long"], longitude=ref["long"],
@@ -38,7 +39,7 @@ class WWBOTA(SSESpotProvider):
freq=float(source_spot["freq"]) * 1000000, freq=float(source_spot["freq"]) * 1000000,
mode=Mode.from_name(source_spot["mode"].upper()) if "mode" in source_spot else None, mode=Mode.from_name(source_spot["mode"].upper()) if "mode" in source_spot else None,
comment=source_spot["comment"], comment=source_spot["comment"],
sig="WWBOTA", sig=ActivityName.WWBOTA,
sig_refs=refs, sig_refs=refs,
time=datetime.fromisoformat(source_spot["time"].replace("Z", "+00:00")).timestamp(), time=datetime.fromisoformat(source_spot["time"].replace("Z", "+00:00")).timestamp(),
# WWBOTA spots can contain multiple references for bunkers being activated simultaneously. For # WWBOTA spots can contain multiple references for bunkers being activated simultaneously. For
@@ -53,7 +54,7 @@ class WWBOTA(SSESpotProvider):
return spot if source_spot["type"] != "Test" else None return spot if source_spot["type"] != "Test" else None
def can_submit_spot(self, activity): def can_submit_spot(self, activity):
return activity == "WWBOTA" return activity == ActivityName.WWBOTA
def submit_spot(self, spot, credentials): def submit_spot(self, spot, credentials):
# TODO: Implement. WWBOTA API docs cover this: https://api.wwbota.org/#tag/Spots/operation/create_spot_spots__post # TODO: Implement. WWBOTA API docs cover this: https://api.wwbota.org/#tag/Spots/operation/create_spot_spots__post
+5 -4
View File
@@ -2,7 +2,8 @@ from datetime import datetime
import pytz import pytz
from core.enums import ActivityRefType, Mode from core.enums import Mode
from core.enums import ActivityName, ActivityRefType
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -30,11 +31,11 @@ class WWFF(HTTPSpotProvider):
freq=float(source_spot["frequency_khz"]) * 1000, freq=float(source_spot["frequency_khz"]) * 1000,
mode=Mode.from_name(source_spot["mode"].upper()), mode=Mode.from_name(source_spot["mode"].upper()),
comment=source_spot["remarks"], comment=source_spot["remarks"],
sig="WWFF", sig=ActivityName.WWFF,
sig_refs=[ sig_refs=[
ActivityRef( ActivityRef(
id=source_spot["reference"], id=source_spot["reference"],
sig="WWFF", sig=ActivityName.WWFF,
name=source_spot["reference_name"], name=source_spot["reference_name"],
latitude=source_spot["latitude"], latitude=source_spot["latitude"],
longitude=source_spot["longitude"], longitude=source_spot["longitude"],
@@ -52,7 +53,7 @@ class WWFF(HTTPSpotProvider):
return new_spots return new_spots
def can_submit_spot(self, activity): def can_submit_spot(self, activity):
return activity == "WWFF" return activity == ActivityName.WWFF
def submit_spot(self, spot, credentials): def submit_spot(self, spot, credentials):
# TODO: Implement. Spotting to WWFF should be possible, need to look up the Spotline docs or copy approach from # TODO: Implement. Spotting to WWFF should be possible, need to look up the Spotline docs or copy approach from
+4 -3
View File
@@ -3,6 +3,7 @@ from datetime import datetime
import pytz import pytz
from core.enums import Mode from core.enums import Mode
from core.enums import ActivityName
from data.activity_ref import ActivityRef from data.activity_ref import ActivityRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -35,11 +36,11 @@ class ZLOTA(HTTPSpotProvider):
freq=freq_hz, freq=freq_hz,
mode=Mode.from_name(source_spot["mode"].upper().strip()), mode=Mode.from_name(source_spot["mode"].upper().strip()),
comment=source_spot["comments"], comment=source_spot["comments"],
sig="ZLOTA", sig=ActivityName.ZLOTA,
sig_refs=[ sig_refs=[
ActivityRef( ActivityRef(
id=source_spot["reference"], id=source_spot["reference"],
sig="ZLOTA", sig=ActivityName.ZLOTA,
name=source_spot["name"], name=source_spot["name"],
) )
], ],
@@ -52,7 +53,7 @@ class ZLOTA(HTTPSpotProvider):
return new_spots return new_spots
def can_submit_spot(self, activity): def can_submit_spot(self, activity):
return activity == "ZLOTA" return activity == ActivityName.ZLOTA
def submit_spot(self, spot, credentials): def submit_spot(self, spot, credentials):
# TODO: Implement. Spotting to ZLOTA is supported via POST, see https://ontheair.nz/api # TODO: Implement. Spotting to ZLOTA is supported via POST, see https://ontheair.nz/api
+1 -1
View File
@@ -115,7 +115,7 @@
Vestigios de España (DMVE), Diploma Estaciones de Ferrocarril de España (DEFE), Diploma Teatri Musei e Belle Vestigios de España (DMVE), Diploma Estaciones de Ferrocarril de España (DEFE), Diploma Teatri Musei e Belle
Arti (DTMBA), British Inland Waterways on the Air (BIWOTA), Castles on the Air (COTA), Polish Gmina Award (PGA), Arti (DTMBA), British Inland Waterways on the Air (BIWOTA), Castles on the Air (COTA), Polish Gmina Award (PGA),
Diplôme des Moulins de France (DMF), RaDAR Rally, and Toilets on the Air.</p> Diplôme des Moulins de France (DMF), RaDAR Rally, and Toilets on the Air.</p>
<p>As of the time of writing in August 2026, I think Spothole captures most outdoor radio programmes that have a <p>As of the time of writing in August 2026, I think Spothole captures most radio programmes that have a
defined, downloadable reference list, and almost certainly those that have a spotting/alerting API. If you know defined, downloadable reference list, and almost certainly those that have a spotting/alerting API. If you know
of one I've missed, please let me know!</p> of one I've missed, please let me know!</p>
<h4 class="mt-4">Why can I filter spots by both Activity and Source? Isn't that basically the same thing?</h4> <h4 class="mt-4">Why can I filter spots by both Activity and Source? Isn't that basically the same thing?</h4>
+3 -2
View File
@@ -9,6 +9,7 @@ import tornado_eventsource.handler
from tornado import httputil from tornado import httputil
from tornado.web import Application from tornado.web import Application
from core.enums import ActivityName
from core.utils import safe_json_dumps from core.utils import safe_json_dumps
from data.lookup_credentials import extract_credentials from data.lookup_credentials import extract_credentials
@@ -168,13 +169,13 @@ def alert_allowed_by_query(alert, query):
# the alert is a dxpedition, or contests_skip_max_duration_check and the alert is a contest, it also # the alert is a dxpedition, or contests_skip_max_duration_check and the alert is a contest, it also
# always passes the check. # always passes the check.
if ( if (
alert.sig == "DXpedition" alert.sig == ActivityName.DXPEDITION
and "dxpeditions_skip_max_duration_check" in query and "dxpeditions_skip_max_duration_check" in query
and query.get("dxpeditions_skip_max_duration_check").upper() == "TRUE" and query.get("dxpeditions_skip_max_duration_check").upper() == "TRUE"
): ):
continue continue
if ( if (
alert.sig == "Contest" alert.sig == ActivityName.CONTEST
and "contests_skip_max_duration_check" in query and "contests_skip_max_duration_check" in query
and query.get("contests_skip_max_duration_check").upper() == "TRUE" and query.get("contests_skip_max_duration_check").upper() == "TRUE"
): ):
+2 -3
View File
@@ -7,9 +7,8 @@ from tornado import httputil
from tornado.web import Application from tornado.web import Application
from core.activity_lookup_helper import populate_missing_activity_ref_info from core.activity_lookup_helper import populate_missing_activity_ref_info
from core.activity_utils import get_ref_regex_for_activity from core.activity_utils import get_activity_by_name, get_ref_regex_for_activity
from core.call_lookup_helper import get_call_info from core.call_lookup_helper import get_call_info
from core.constants import ACTIVITIES
from core.geo_utils import ( from core.geo_utils import (
lat_lon_for_grid_sw_corner_plus_size, lat_lon_for_grid_sw_corner_plus_size,
lat_lon_to_cq_zone, lat_lon_to_cq_zone,
@@ -85,7 +84,7 @@ class APILookupActivityRefHandler(tornado.web.RequestHandler):
if "sig" in query_params and "id" in query_params: if "sig" in query_params and "id" in query_params:
activity = str(query_params.get("sig")).upper() activity = str(query_params.get("sig")).upper()
ref_id = str(query_params.get("id")).upper() ref_id = str(query_params.get("id")).upper()
if activity in [a.name.upper() for a in ACTIVITIES]: if get_activity_by_name(activity):
if not get_ref_regex_for_activity(activity) or re.match( if not get_ref_regex_for_activity(activity) or re.match(
get_ref_regex_for_activity(activity), ref_id get_ref_regex_for_activity(activity), ref_id
): ):
+4 -3
View File
@@ -6,9 +6,10 @@ from tornado import httputil
from tornado.web import Application from tornado.web import Application
from core.config import ALLOW_SPOTTING, MAX_SPOT_AGE from core.config import ALLOW_SPOTTING, MAX_SPOT_AGE
from core.constants import ACTIVITIES, BANDS, PROPAGATION_MODES from core.constants import BANDS, PROPAGATION_MODES
from core.enums import Continent, Mode, ModeType from core.enums import Continent, Mode, ModeType
from core.utils import safe_json_dumps from core.utils import safe_json_dumps
from data.activities import ACTIVITIES
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -39,7 +40,7 @@ class APIOptionsHandler(tornado.web.RequestHandler):
# for provider in self._spot_providers: # for provider in self._spot_providers:
# if not provider.enabled: # if not provider.enabled:
# continue # continue
# for activity in ACTIVITIES: # for activity in ACTIVITIES.values():
# if provider.can_submit_spot(activity.name): # if provider.can_submit_spot(activity.name):
# spot_submit_providers.setdefault(activity.name, []).append(provider.name) # spot_submit_providers.setdefault(activity.name, []).append(provider.name)
@@ -74,7 +75,7 @@ class APIOptionsHandler(tornado.web.RequestHandler):
"bands": BANDS, "bands": BANDS,
"modes": [m.value for m in Mode], "modes": [m.value for m in Mode],
"mode_types": [t.value for t in ModeType], "mode_types": [t.value for t in ModeType],
"sigs": ACTIVITIES, "sigs": list(ACTIVITIES.values()),
"spot_providers": spot_providers, "spot_providers": spot_providers,
"spot_providers_enabled_by_default": spot_providers_enabled_by_default, "spot_providers_enabled_by_default": spot_providers_enabled_by_default,
"alert_providers": alert_providers, "alert_providers": alert_providers,