From 20966cc7cfa678f11dae746523a0e13f63cf0597 Mon Sep 17 00:00:00 2001 From: Ian Renton Date: Sat, 20 Jun 2026 08:28:11 +0100 Subject: [PATCH] IDE inspection fixes and global autoformat --- README.md | 9 ++-- server/handlers/api/addspot.py | 9 ++-- server/handlers/api/options.py | 23 ++++++---- server/webserver.py | 6 ++- spotproviders/parksnpeaks.py | 3 +- spotproviders/sota.py | 5 ++- spotproviders/tiles.py | 9 ++-- webassets/apidocs/openapi.yml | 4 +- webassets/js/add-spot.js | 64 +++++++++++++------------- webassets/js/conditions.js | 2 +- webassets/js/ui-ham.js | 82 +++++++++++++++++----------------- 11 files changed, 119 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index 009cdb7..2a9893b 100644 --- a/README.md +++ b/README.md @@ -462,13 +462,16 @@ As well as being my work, I have also gratefully received feature patches from S The project contains GeoJSON files for CQ and ITU zones, in the `/datafiles/` directory. These are MIT-licenced and, to my knowledge, created by HA8TKS for his CQ and ITU zone layers for Leaflet. -The project contains a set of flag icons generated using the "Noto Color Emoji" font on a Debian system, in the `/webassets/img/flags/` directory. +The project contains a set of flag icons generated using the "Noto Color Emoji" font on a Debian system, in the +`/webassets/img/flags/` directory. -The software uses a number of Python libraries as listed in `requirements.txt`, and a number of JavaScript libraries. This project would not have been possible without these libraries, so many thanks to their developers. +The software uses a number of Python libraries as listed in `requirements.txt`, and a number of JavaScript libraries. +This project would not have been possible without these libraries, so many thanks to their developers. ### Third Party Libraries -A number of third-party libraries are self-hosted in the `/webassets/vendor/` directory. These files are subject to their own licences and are not covered by the overall licence declared in the `LICENSE` file. +A number of third-party libraries are self-hosted in the `/webassets/vendor/` directory. These files are subject to +their own licences and are not covered by the overall licence declared in the `LICENSE` file. A number of third-party libraries are self-hosted in the `/webassets/vendor/` directory. These files are subject to their own licences and are not covered by the overall licence declared in the `LICENSE` file. diff --git a/server/handlers/api/addspot.py b/server/handlers/api/addspot.py index c5ac073..0ea8af0 100644 --- a/server/handlers/api/addspot.py +++ b/server/handlers/api/addspot.py @@ -19,6 +19,7 @@ from core.sig_utils import get_ref_regex_for_sig from core.utils import serialize_everything from data.sig_ref import SIGRef from data.spot import Spot +from spotproviders.spot_provider import SpotProvider RECAPTCHA_VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify" @@ -29,6 +30,7 @@ class APISpotHandler(tornado.web.RequestHandler): def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any): self._spots = None self._web_server_metrics = None + self._spot_providers = None super().__init__(application, request, **kwargs) def initialize(self, spots, web_server_metrics, spot_providers=None): @@ -185,7 +187,7 @@ class APISpotHandler(tornado.web.RequestHandler): # Submit spot to the upstream provider provider.submit_spot(spot, upstream_credentials) # Trigger a re-poll after 1 second so the spot appears quickly - threading.Timer(1.0, lambda: provider.force_poll()).start() + threading.Timer(1.0, provider.force_poll).start() except NotImplementedError as e: upstream_warning = str(e) except Exception as e: @@ -218,7 +220,7 @@ class APISpotHandler(tornado.web.RequestHandler): self.set_header("Cache-Control", "no-store") self.set_header("Content-Type", "application/json") - def _find_provider(self, provider_name, sig): + def _find_provider(self, provider_name, sig) -> SpotProvider | None: """Find an enabled provider by name that can submit spots for the given SIG.""" for p in self._spot_providers: @@ -226,7 +228,8 @@ class APISpotHandler(tornado.web.RequestHandler): return p return None - def _verify_recaptcha(self, token): + @staticmethod + def _verify_recaptcha(token): """Verify a Google reCAPTCHA v2 token. Returns True if valid.""" try: diff --git a/server/handlers/api/options.py b/server/handlers/api/options.py index cfca2d4..fb9f6be 100644 --- a/server/handlers/api/options.py +++ b/server/handlers/api/options.py @@ -19,6 +19,7 @@ class APIOptionsHandler(tornado.web.RequestHandler): def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any): self._status_data = None self._web_server_metrics = None + self._spot_providers = None super().__init__(application, request, **kwargs) def initialize(self, status_data, web_server_metrics, spot_providers=None): @@ -42,23 +43,27 @@ class APIOptionsHandler(tornado.web.RequestHandler): if provider.can_submit_spot(sig.name): spot_submit_providers.setdefault(sig.name, []).append(provider.name) + # Spot/alert sources are filtered for only ones that are enabled in config, no point letting the user toggle + # things that aren't even available. + spot_sources: list = list( + map(lambda p: p["name"], filter(lambda p: p["enabled"], self._status_data["spot_providers"]))) + alert_sources = list( + map(lambda p: p["name"], filter(lambda p: p["enabled"], self._status_data["alert_providers"]))) + # If spotting to this server is enabled, "API" is another valid spot source even though it does not come from + # one of our providers. + if ALLOW_SPOTTING: + spot_sources.append("API") + options = {"bands": BANDS, "modes": ALL_MODES, "mode_types": MODE_TYPES, "sigs": SIGS, - # Spot/alert sources are filtered for only ones that are enabled in config, no point letting the user toggle things that aren't even available. - "spot_sources": list( - map(lambda p: p["name"], filter(lambda p: p["enabled"], self._status_data["spot_providers"]))), - "alert_sources": list( - map(lambda p: p["name"], filter(lambda p: p["enabled"], self._status_data["alert_providers"]))), + "spot_sources": spot_sources, + "alert_sources": alert_sources, "continents": CONTINENTS, "max_spot_age": MAX_SPOT_AGE, "spot_allowed": ALLOW_SPOTTING, "spot_submit_providers": spot_submit_providers} - # If spotting to this server is enabled, "API" is another valid spot source even though it does not come from - # one of our proviers. - if ALLOW_SPOTTING: - options["spot_sources"].append("API") self.write(json.dumps(options, default=serialize_everything)) self.set_status(200) diff --git a/server/webserver.py b/server/webserver.py index 7645ff8..27c28f3 100644 --- a/server/webserver.py +++ b/server/webserver.py @@ -72,12 +72,14 @@ class WebServer: {"sse_alert_queues": self._sse_alert_queues, **handler_opts}), (r"/api/v1/solar", APISolarConditionsHandler, {"solar_conditions": self._solar_conditions, **handler_opts}), (r"/api/v1/dxstats", APIDxStatsHandler, {"spots": self._spots, **handler_opts}), - (r"/api/v1/options", APIOptionsHandler, {"status_data": self._status_data, "spot_providers": self._spot_providers, **handler_opts}), + (r"/api/v1/options", APIOptionsHandler, + {"status_data": self._status_data, "spot_providers": self._spot_providers, **handler_opts}), (r"/api/v1/status", APIStatusHandler, {"status_data": self._status_data, **handler_opts}), (r"/api/v1/lookup/call", APILookupCallHandler, {**handler_opts}), (r"/api/v1/lookup/sigref", APILookupSIGRefHandler, {**handler_opts}), (r"/api/v1/lookup/grid", APILookupGridHandler, {**handler_opts}), - (r"/api/v1/spot", APISpotHandler, {"spots": self._spots, "spot_providers": self._spot_providers, **handler_opts}), + (r"/api/v1/spot", APISpotHandler, + {"spots": self._spots, "spot_providers": self._spot_providers, **handler_opts}), ] # If in API-only mode, serve a basic homepage; in normal mode, serve the usual UI routes diff --git a/spotproviders/parksnpeaks.py b/spotproviders/parksnpeaks.py index 51bacc7..6beac64 100644 --- a/spotproviders/parksnpeaks.py +++ b/spotproviders/parksnpeaks.py @@ -76,7 +76,8 @@ class ParksNPeaks(HTTPSpotProvider): user_id = credentials.get("user_id", "") api_key = credentials.get("api_key", "") if not user_id or not api_key: - raise ValueError("Parks N Peaks user ID and API key are required. Get yours from your Parks N Peaks account.") + raise ValueError( + "Parks N Peaks user ID and API key are required. Get yours from your Parks N Peaks account.") sig_ref = spot.sig_refs[0].id if spot.sig_refs else "" body = { "actClass": spot.sig or "", diff --git a/spotproviders/sota.py b/spotproviders/sota.py index b59f85d..e6c6aa4 100644 --- a/spotproviders/sota.py +++ b/spotproviders/sota.py @@ -93,9 +93,10 @@ class SOTA(HTTPSpotProvider): "mode": mode or "", "callsign": spot.de_call, "comments": spot.comment or "", - "type": "TEST" # todo replatce with NORMAL/QRT once testing complete + "type": "TEST" # todo replatce with NORMAL/QRT once testing complete } - headers = {**HTTP_HEADERS, "Authorization": "bearer " + access_token, "id_token": id_token, "Content-Type": "application/json"} + headers = {**HTTP_HEADERS, "Authorization": "bearer " + access_token, "id_token": id_token, + "Content-Type": "application/json"} response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30)) if not response.ok: raise RuntimeError("SOTA API returned " + str(response.status_code) + ": " + response.text) diff --git a/spotproviders/tiles.py b/spotproviders/tiles.py index cfc6151..b80470b 100644 --- a/spotproviders/tiles.py +++ b/spotproviders/tiles.py @@ -14,7 +14,8 @@ class Tiles(HTTPSpotProvider): POLL_INTERVAL_SEC = 120 SPOTS_URL = "https://icneuzxitdqtofutxbla.supabase.co/functions/v1/spots?active_hours=24" SUBMIT_URL = "https://icneuzxitdqtofutxbla.supabase.co/functions/v1/self-spot" - VALID_MODES = ["SSB", "CW", "FT8", "FT4", "FM", "DMR", "D-STAR", "M17", "AX.25", "JS8Call", "PSK31", "Olivia", "VarAC", "Other"] + VALID_MODES = ["SSB", "CW", "FT8", "FT4", "FM", "DMR", "D-STAR", "M17", "AX.25", "JS8Call", "PSK31", "Olivia", + "VarAC", "Other"] def __init__(self, provider_config): super().__init__(provider_config, self.SPOTS_URL, self.POLL_INTERVAL_SEC) @@ -82,11 +83,13 @@ class Tiles(HTTPSpotProvider): headers = {**HTTP_HEADERS, "Content-Type": "application/json"} response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30)) if not response.ok: - raise RuntimeError("Tiles on the Air API returned " + str(response.status_code) + ": " + response.text) + raise RuntimeError( + "Tiles on the Air API returned " + str(response.status_code) + ": " + response.text) else: raise RuntimeError("The Tiles on the Air API requires a mode to be set.") else: - raise RuntimeError("The Tiles on the Air API only supports self-spots, the DX call and spotter call must match.") + raise RuntimeError( + "The Tiles on the Air API only supports self-spots, the DX call and spotter call must match.") # Utility function to keep the first decimal point in a given string but remove any others. Used to parse Tiles' diff --git a/webassets/apidocs/openapi.yml b/webassets/apidocs/openapi.yml index 321b6da..0ab168a 100644 --- a/webassets/apidocs/openapi.yml +++ b/webassets/apidocs/openapi.yml @@ -1696,8 +1696,8 @@ components: items: type: string example: - POTA: [POTA] - SOTA: [SOTA] + POTA: [ POTA ] + SOTA: [ SOTA, GMA, ParksNPeaks ] CallLookup: type: object diff --git a/webassets/js/add-spot.js b/webassets/js/add-spot.js index 9bf5a77..12d3586 100644 --- a/webassets/js/add-spot.js +++ b/webassets/js/add-spot.js @@ -1,24 +1,28 @@ // Credentials schema per provider name. Defines the fields to collect and how to label them. -var PROVIDER_CREDENTIAL_SCHEMAS = { +const PROVIDER_CREDENTIAL_SCHEMAS = { // todo Figure out SOTA authentication // see e.g. https://github.com/ham2k/app-polo/blob/main/src/extensions/activities/sota/SOTAAccount.jsx // https://github.com/ham2k/app-polo/blob/main/src/store/apis/apiSOTA/apiSOTA.js // Refresh token? Way to show user that they need to log in again because cached credentials aren't valid? // todo type: text/password distinction on text boxes so API keys can be obscured "SOTA": [ - { key: "access_token", label: "SOTA Access Token", help: "" }, - { key: "id_token", label: "SOTA ID Token", help: "TODO SOTA authentication to provide this..." } + {key: "access_token", label: "SOTA Access Token", help: ""}, + {key: "id_token", label: "SOTA ID Token", help: "TODO SOTA authentication to provide this..."} ], "ParksNPeaks": [ - { key: "user_id", label: "Parks N Peaks User ID", help: "" }, - { key: "api_key", label: "Parks N Peaks API Key", help: "Get your API key from your Parks N Peaks account." } + {key: "user_id", label: "Parks N Peaks User ID", help: ""}, + {key: "api_key", label: "Parks N Peaks API Key", help: "Get your API key from your Parks N Peaks account."} ], "ZLOTA": [ - { key: "user_id", label: "ZLOTA User ID", help: "" }, - { key: "api_key", label: "ZLOTA User PIN", help: "Get your PIN from your ZLOTA account." } + {key: "user_id", label: "ZLOTA User ID", help: ""}, + {key: "api_key", label: "ZLOTA User PIN", help: "Get your PIN from your ZLOTA account."} ], "Tiles": [ - { key: "offline_spot_gateway_pin", label: "Offline Spot Gateway PIN", help: "Get your PIN from your Tiles on the Air account profile." } + { + key: "offline_spot_gateway_pin", + label: "Offline Spot Gateway PIN", + help: "Get your PIN from your Tiles on the Air account profile." + } ] }; @@ -62,7 +66,7 @@ function loadOptions() { function loadRecaptcha(siteKey) { window._recaptchaSiteKey = siteKey; if (!document.getElementById('recaptcha-script')) { - var script = document.createElement('script'); + const script = document.createElement('script'); script.id = 'recaptcha-script'; script.src = 'https://www.google.com/recaptcha/api.js?render=explicit&onload=renderRecaptcha'; script.async = true; @@ -87,8 +91,8 @@ function updateUpstreamArea() { return; } - var sig = $("#sig").val(); - var providers = (sig && options["spot_submit_providers"][sig]) ? options["spot_submit_providers"][sig] : []; + const sig = $("#sig").val(); + const providers = (sig && options["spot_submit_providers"][sig]) ? options["spot_submit_providers"][sig] : []; if (providers.length === 0) { $("#upstream-area").hide(); @@ -99,8 +103,8 @@ function updateUpstreamArea() { // Update the provider selector $("#upstream-provider-select").empty(); - $.each(providers, function(i, name) { - $("#upstream-provider-select").append($('