diff --git a/alertproviders/ng3k.py b/alertproviders/ng3k.py new file mode 100644 index 0000000..940fbff --- /dev/null +++ b/alertproviders/ng3k.py @@ -0,0 +1,73 @@ +from datetime import datetime + +import pytz +from rss_parser import RSSParser + +from alertproviders.http_alert_provider import HTTPAlertProvider +from data.alert import Alert + + +# Alert provider NG3K DXpedition list +class NG3K(HTTPAlertProvider): + POLL_INTERVAL_SEC = 3600 + ALERTS_URL = "https://www.ng3k.com/adxo.xml" + + def __init__(self, provider_config): + super().__init__(provider_config, self.ALERTS_URL, self.POLL_INTERVAL_SEC) + + def http_response_to_alerts(self, http_response): + new_alerts = [] + rss = RSSParser.parse(http_response.content.decode()) + # Iterate through source data + for source_alert in rss.channel.items: + # Deal with "the format"... + parts = source_alert.description.split(" --\n") + + start_string = parts[0].split("-")[0] + end_string = parts[0].split("-")[1] + + end_year = end_string.split(", ")[1].strip() + + if ", " in start_string: + start_year = start_string.split(", ")[1].strip() + start_mon = start_string.split(", ")[0][0:3].strip() + start_day = start_string.split(", ")[0][4:].strip() + else: + start_year = end_year + start_mon = start_string[0:3].strip() + start_day = start_string[4:].strip() + + if " " in end_string.split(", ")[0]: + end_mon = end_string.split(", ")[0].split(" ")[0].strip() + end_day = end_string.split(", ")[0].split(" ")[1].strip() + else: + end_day = end_string.split(", ")[0].strip() + end_mon = start_mon + + start_timestamp = datetime.strptime(start_year + " " + start_mon + " " + start_day, "%Y %b %d").replace( + tzinfo=pytz.UTC).timestamp() + end_timestamp = datetime.strptime(end_year + " " + end_mon + " " + end_day + " 23:59", "%Y %b %d %H:%M").replace( + tzinfo=pytz.UTC).timestamp() + + dx_country = parts[1] + dx_call = parts[2] + qsl_info = parts[3] + extra_parts = parts[5].split("; ") + by = extra_parts[0] + bands = extra_parts[1] + modes = extra_parts[2] if len(extra_parts) > 3 else "" + comment = extra_parts[-1] + + # Convert to our alert format + alert = Alert(source=self.name, + dx_call=dx_call.upper(), + dx_country=dx_country, + freqs_modes=bands + (("; " + modes) if modes != "" else ""), + comment=by + "; " + comment + "; " + qsl_info, + icon="globe-africa", + start_time=start_timestamp, + end_time=end_timestamp) + + # Add to our list. + new_alerts.append(alert) + return new_alerts diff --git a/config-example.yml b/config-example.yml index 341f3ab..73f6977 100644 --- a/config-example.yml +++ b/config-example.yml @@ -84,6 +84,10 @@ alert-providers: class: "WWFF" name: "WWFF" enabled: true + - + class: "NG3K" + name: "NG3K" + enabled: true # Port to open the local web server on web-server-port: 8080 diff --git a/requirements.txt b/requirements.txt index 5584971..71015ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,4 @@ aprslib~=0.7.2 diskcache~=5.6.3 psutil~=7.1.0 requests-sse~=0.5.2 +rss-parser~=2.1.1 diff --git a/webassets/apidocs/openapi.yml b/webassets/apidocs/openapi.yml index 6de802d..6804dd4 100644 --- a/webassets/apidocs/openapi.yml +++ b/webassets/apidocs/openapi.yml @@ -10,7 +10,7 @@ info: license: name: The Unlicense url: https://unlicense.org/#the-unlicense - version: 1 + version: v1 servers: - url: https://spothole.app/api/v1 paths: diff --git a/webassets/js/alerts.js b/webassets/js/alerts.js index b2eed44..abc56d6 100644 --- a/webassets/js/alerts.js +++ b/webassets/js/alerts.js @@ -27,7 +27,7 @@ function buildQueryString() { }); str = str + "limit=" + $("#alerts-to-fetch option:selected").val(); var maxDur = $("#max-duration option:selected").val(); - if (maxDur != "") { + if (maxDur != "9999999999") { str = str + "&max_duration=" + maxDur; } return str; @@ -89,7 +89,8 @@ function addAlertRowsToTable(tbody, alerts) { var useLocalTime = $("#useLocalTime")[0].checked; // Get times for the alert, and convert to local time if necessary. - var start_time = moment.unix(a["start_time"]).utc(); + var start_time_unix = moment.unix(a["start_time"]); + var start_time = start_time_unix.utc(); if (useLocalTime) { start_time = start_time.local(); } @@ -103,21 +104,27 @@ function addAlertRowsToTable(tbody, alerts) { // different year to the current year, in which case the year is inserted between month and hour. // If the time is set to local not UTC, and the date in local time is "today", we display that instead. // End time is displayed the same as above, except if the end date is the same as the start date, in which case - // just e.g. 23:45 is used. Finally, if there is no end date set, "---" is displayed. - var start_time_formatted = start_time.format("D MMM HH:mm"); + // just e.g. 23:45 is used. + // Overriding all of that, if the start time is 00:00 and the end time is 23:59 when considered in UTC, the + // hours and minutes are stripped out from the display, as we assume the server is just giving us full days. + // Finally, if there is no end date set, "---" is displayed. + var whole_days = start_time_unix.utc().format("HH:mm") == "00:00" && + (end_time_unix != null || end_time_unix > 0 || end_time_unix.utc().format("HH:mm") == "23:59"); + var hours_minutes_format = whole_days ? "" : " HH:mm"; + var start_time_formatted = start_time.format("D MMM" + hours_minutes_format); if (start_time.format("YYYY") != moment().format("YYYY")) { - start_time_formatted = start_time.format("D MMM YYYY HH:mm"); + start_time_formatted = start_time.format("D MMM YYYY" + hours_minutes_format); } else if (useLocalTime && start_time.format("D MMM YYYY") == moment().format("D MMM YYYY")) { - start_time_formatted = start_time.format("[Today] HH:mm"); + start_time_formatted = start_time.format("[Today]" + hours_minutes_format); } var end_time_formatted = "---"; if (end_time_unix != null && end_time_unix > 0 && end_time != null) { - var end_time_formatted = end_time.format("HH:mm"); + var end_time_formatted = whole_days ? start_time_formatted : end_time.format("HH:mm"); if (end_time.format("D MMM") != start_time.format("D MMM")) { if (end_time.format("YYYY") != moment().format("YYYY")) { - end_time_formatted = end_time.format("D MMM YYYY HH:mm"); + end_time_formatted = end_time.format("D MMM YYYY" + hours_minutes_format); } else { - end_time_formatted = end_time.format("D MMM HH:mm"); + end_time_formatted = end_time.format("D MMM" + hours_minutes_format); } } } @@ -212,7 +219,7 @@ function generateMaxDurationDropdownFilterCard(band_options) { - + `); $p.append(" "); // Compile HTML elements to return diff --git a/webassets/js/common.js b/webassets/js/common.js index 4b20b0b..288abde 100644 --- a/webassets/js/common.js +++ b/webassets/js/common.js @@ -108,10 +108,10 @@ function saveSettings() { // Find all storeable UI elements, store a key of "element id:property name" mapped to the value of that // property. For a checkbox, that's the "checked" property. $(".storeable-checkbox").each(function() { - localStorage.setItem($(this)[0].id + ":checked", $(this)[0].checked); + localStorage.setItem("#" + $(this)[0].id + ":checked", $(this)[0].checked); }); $(".storeable-select").each(function() { - localStorage.setItem($(this)[0].id + ":value", $(this)[0].value); + localStorage.setItem("#" + $(this)[0].id + ":value", $(this)[0].value); }); } @@ -119,8 +119,10 @@ function saveSettings() { function loadSettings() { // Find all local storage entries and push their data to the corresponding UI element Object.keys(localStorage).forEach(function(key) { - // Split the key back into an element ID and a property - var split = key.split(":"); - $("#" + split[0]).prop(split[1], JSON.parse(localStorage.getItem(key))); + if (key.startsWith("#") && key.includes(":")) { + // Split the key back into an element ID and a property + var split = key.split(":"); + $(split[0]).prop(split[1], JSON.parse(localStorage.getItem(key))); + } }); }