import logging import re from datetime import datetime from typing import cast from xml.parsers.expat import ExpatError import pytz from rss_parser import Parser from rss_parser.models.rss import RSS from core.enums import ActivityName, ActivityRefType, Mode from data.activity_ref import ActivityRef from data.spot import Spot from providers.spot.http_spot_provider import HTTPSpotProvider logger = logging.getLogger(__name__) class WOTA(HTTPSpotProvider): """Spot provider for Wainwrights on the Air""" POLL_INTERVAL_SEC = 120 SPOTS_URL = "https://www.wota.org.uk/spots_rss.php" RSS_DATE_TIME_FORMAT = "%a, %d %b %Y %H:%M:%S %z" def __init__(self, provider_config): super().__init__("WOTA", provider_config, self.SPOTS_URL, self.POLL_INTERVAL_SEC) def _http_response_to_spots(self, http_response): new_spots = [] try: rss = cast(RSS, Parser.parse(http_response.content.decode("utf-8-sig"))) # Iterate through source data for source_spot in rss.channel.items: try: # Reject GUID missing or zero if ( not source_spot.guid or not source_spot.guid.content or source_spot.guid.content == "http://www.wota.org.uk/spots/0" ): continue # Pick apart the title dx_call = None ref = None ref_name = None try: title_split = source_spot.title.split(" on ") dx_call = title_split[0] if len(title_split) > 1: ref_split = title_split[1].split(" - ") ref = str(ref_split[0]) if len(ref_split) > 1: ref_name = str(ref_split[1]) except Exception: logger.warning(f"Could not parse WOTA spot title: {source_spot.title}", exc_info=True) # Pick apart the description freq_hz = None mode = None comment = None spotter = None try: desc_split = source_spot.description.split(". ") freq_mode = desc_split[0].replace("Frequencies/modes:", "").strip() if freq_mode and freq_mode != "-": freq_mode_split = re.split(r"[\-\s]+", freq_mode) freq_hz = float(freq_mode_split[0].replace("'", ".")) * 1000000 if len(freq_mode_split) > 1: mode = freq_mode_split[1].upper() if len(desc_split) > 1: comment = desc_split[1].strip() if len(desc_split) > 2: spotter = desc_split[2].replace("Spotted by ", "").replace(".", "").upper().strip() except Exception: logger.warning( f"Could not parse WOTA spot description: {source_spot.description}", exc_info=True ) time = datetime.strptime(source_spot.pub_date.content, self.RSS_DATE_TIME_FORMAT).astimezone( pytz.UTC ) # Convert to our spot format spot = Spot( source=self.name, source_id=source_spot.guid.content, dx_call=dx_call, de_call=spotter, freq=freq_hz, mode=Mode.from_name(mode), comment=comment, sig=ActivityName.WOTA, sig_refs=( [ActivityRef(id=ref, sig=ActivityName.WOTA, name=ref_name, ref_type=ActivityRefType.SUMMIT)] if ref else [] ), time=time.timestamp(), ) new_spots.append(spot) except Exception: logger.exception("Exception parsing WOTA spot") except ExpatError: logger.warning("WOTA spot RSS feed was fetched but was invalid") return new_spots def can_submit_spot(self, activity): return activity == ActivityName.WOTA def submit_spot(self, spot, credentials): # TODO Ask M5TEA if he's happy to share how this is done from his app raise NotImplementedError("WOTA upstream spot submission is not yet implemented")