diff --git a/alertproviders/ng3k.py b/alertproviders/ng3k.py index 940fbff..69f8c58 100644 --- a/alertproviders/ng3k.py +++ b/alertproviders/ng3k.py @@ -66,7 +66,8 @@ class NG3K(HTTPAlertProvider): comment=by + "; " + comment + "; " + qsl_info, icon="globe-africa", start_time=start_timestamp, - end_time=end_timestamp) + end_time=end_timestamp, + is_dxpedition=True) # Add to our list. new_alerts.append(alert) diff --git a/alertproviders/pota.py b/alertproviders/pota.py index ec56e53..e748634 100644 --- a/alertproviders/pota.py +++ b/alertproviders/pota.py @@ -31,7 +31,8 @@ class POTA(HTTPAlertProvider): start_time=datetime.strptime(source_alert["startDate"] + source_alert["startTime"], "%Y-%m-%d%H:%M").replace(tzinfo=pytz.UTC).timestamp(), end_time=datetime.strptime(source_alert["endDate"] + source_alert["endTime"], - "%Y-%m-%d%H:%M").replace(tzinfo=pytz.UTC).timestamp()) + "%Y-%m-%d%H:%M").replace(tzinfo=pytz.UTC).timestamp(), + is_dxpedition=False) # Add to our list, but exclude any old spots that POTA can sometimes give us where even the end time is # in the past. Don't worry about de-duping, removing old alerts etc. at this point; other code will do diff --git a/alertproviders/sota.py b/alertproviders/sota.py index ced3579..336eff2 100644 --- a/alertproviders/sota.py +++ b/alertproviders/sota.py @@ -30,7 +30,8 @@ class SOTA(HTTPAlertProvider): sig_refs_names=[source_alert["summitDetails"]], icon="mountain-sun", start_time=datetime.strptime(source_alert["dateActivated"], - "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=pytz.UTC).timestamp()) + "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=pytz.UTC).timestamp(), + is_dxpedition=False) # Add to our list new_alerts.append(alert) diff --git a/alertproviders/wwff.py b/alertproviders/wwff.py index 98cc34c..22d481c 100644 --- a/alertproviders/wwff.py +++ b/alertproviders/wwff.py @@ -30,7 +30,8 @@ class WWFF(HTTPAlertProvider): start_time=datetime.strptime(source_alert["utc_start"], "%Y-%m-%d %H:%M:%S").replace(tzinfo=pytz.UTC).timestamp(), end_time=datetime.strptime(source_alert["utc_end"], - "%Y-%m-%d %H:%M:%S").replace(tzinfo=pytz.UTC).timestamp()) + "%Y-%m-%d %H:%M:%S").replace(tzinfo=pytz.UTC).timestamp(), + is_dxpedition=False) # Add to our list new_alerts.append(alert) diff --git a/data/alert.py b/data/alert.py index 26aecc8..dee977f 100644 --- a/data/alert.py +++ b/data/alert.py @@ -61,6 +61,8 @@ class Alert: activation_score: int = None # Icon, from the Font Awesome set. This is fairly opinionated but is here to help the alerthole web UI and Field alertter. Does not include the "fa-" prefix. icon: str = "question" + # Whether this alert is for a DXpedition, as opposed to e.g. an xOTA programme. + is_dxpedition: bool = False # Where we got the alert from, e.g. "POTA", "SOTA"... source: str = None # The ID the source gave it, if any. diff --git a/server/webserver.py b/server/webserver.py index 97ee13c..5a0219f 100644 --- a/server/webserver.py +++ b/server/webserver.py @@ -206,9 +206,12 @@ class WebServer: case "max_duration": max_duration = int(query.get(k)) # Check the duration if end_time is provided. If end_time is not provided, assume the activation is - # "short", i.e. it always passes this check. + # "short", i.e. it always passes this check. If dxpeditions_skip_max_duration_check is true and + # the alert is a dxpedition, it also always passes the check. + dxpeditions_skip_check = bool(query.get( + "dxpeditions_skip_max_duration_check")) if "dxpeditions_skip_max_duration_check" in query.keys() else False alerts = [a for a in alerts if (a.end_time and a.end_time - a.start_time <= max_duration) or - not a.end_time] + not a.end_time or (dxpeditions_skip_check and a.is_dxpedition)] case "source": sources = query.get(k).split(",") alerts = [a for a in alerts if a.source and a.source in sources] diff --git a/webassets/apidocs/openapi.yml b/webassets/apidocs/openapi.yml index 6804dd4..d179822 100644 --- a/webassets/apidocs/openapi.yml +++ b/webassets/apidocs/openapi.yml @@ -195,19 +195,25 @@ paths: type: integer - name: received_since in: query - description: Limit the spots to only ones that the system found out about at this time or later. Time in UTC seconds since UNIX epoch. If you are using a front-end that tracks the last time it queried the API and requests spots since then, you want *this* version of the query parameter, not "since", because otherwise it may miss things. The logic is "greater than" rather than "greater than or equal to", so you can submit the time of the last received item back to this call and you will get all the more recent spots back, without duplicating the previous latest spot. + description: Limit the alerts to only ones that the system found out about at this time or later. Time in UTC seconds since UNIX epoch. If you are using a front-end that tracks the last time it queried the API and requests alerts since then, you want *this* version of the query parameter, not "since", because otherwise it may miss things. The logic is "greater than" rather than "greater than or equal to", so you can submit the time of the last received item back to this call and you will get all the more recent alerts back, without duplicating the previous latest spot. required: false schema: type: integer - name: max_duration in: query - description: Limit the spots to only ones with a duration of this many seconds or less. Duration is end time minus start time, if end time is set, otherwise "now" minus start time. This is useful to filter out people who alert POTA activations lasting months or even years. + description: Limit the alerts to only ones with a duration of this many seconds or less. Duration is end time minus start time, if end time is set, otherwise the activation is assumed to be short and therefore to always pass this check. This is useful to filter out people who alert POTA activations lasting months or even years, but note it will also include multi-day or multi-week DXpeditions that you might otherwise be interested in. See the dxpeditions_skip_max_duration_check parameter for the workaround. required: false schema: type: integer + - name: dxpeditions_skip_max_duration_check + in: query + description: Return DXpedition alerts even if they last longer than max_duration. This allows the user to filter out multi-day/multi-week POTA alerts where the operator likely won't be on the air most of the time, but keep multi-day/multi-week DXpeditions where the operator(s) likely *will* be on the air most of the time. + required: false + schema: + type: boolean - name: source in: query - description: "Limit the spots to only ones from one or more sources. To select more than one source, supply a comma-separated list." + description: "Limit the alerts to only ones from one or more sources. To select more than one source, supply a comma-separated list." required: false schema: type: string @@ -224,7 +230,7 @@ paths: - APRS-IS - name: sig in: query - description: "Limit the spots to only ones from one or more Special Interest Groups. To select more than one SIG, supply a comma-separated list." + description: "Limit the alerts to only ones from one or more Special Interest Groups. To select more than one SIG, supply a comma-separated list." required: false schema: type: string @@ -237,7 +243,7 @@ paths: - HEMA - name: dx_continent in: query - description: "Limit the spots to only ones where the DX (the operator being spotted) is on the given continent(s). To select more than one continent, supply a comma-separated list." + description: "Limit the alerts to only ones where the DX (the operator being spotted) is on the given continent(s). To select more than one continent, supply a comma-separated list." required: false schema: type: string diff --git a/webassets/js/alerts.js b/webassets/js/alerts.js index abc56d6..258296c 100644 --- a/webassets/js/alerts.js +++ b/webassets/js/alerts.js @@ -30,6 +30,9 @@ function buildQueryString() { if (maxDur != "9999999999") { str = str + "&max_duration=" + maxDur; } + if ($("#dxpeditions_skip_max_duration_check")[0].checked) { + str = str + "&dxpeditions_skip_max_duration_check=true"; + } return str; } @@ -210,7 +213,7 @@ function generateMaxDurationDropdownFilterCard(band_options) { let $col = $("
") let $card = $("
"); let $card_body = $("
"); - $card_body.append(`
Duration Limit
`); + $card_body.append(`
Duration Limit
`); $p = $("

"); $p.append("Hide any alerts lasting more than:
"); $p.append(``); - $p.append(" "); + + $p2 = $("

"); + $p2.append(``); // Compile HTML elements to return $card_body.append($p); + $card_body.append($p2); $card.append($card_body); $col.append($card); return $col; diff --git a/webassets/js/status.js b/webassets/js/status.js index b5d47cf..8c3794a 100644 --- a/webassets/js/status.js +++ b/webassets/js/status.js @@ -7,7 +7,8 @@ function loadStatus() { `Server Owner Callsign: ${jsonData["server-owner-callsign"]}`, `Server up since: ${moment().subtract(jsonData["uptime"], 'seconds').fromNow()}`, `Memory Use: ${jsonData["mem_use_mb"]} MB`, - `Total Spots: ${jsonData["num_spots"]}` + `Total Spots: ${jsonData["num_spots"]}`, + `Total Alerts: ${jsonData["num_alerts"]}` ])); $("#status-container").append(generateStatusCard("Web Server", [ `Status: ${jsonData["webserver"]["status"]}`,