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)));
+ }
});
}