mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-12-16 09:03:39 +00:00
Compare commits
5 Commits
1.0.1
...
ae075f3ac7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae075f3ac7 | ||
|
|
efa9806c64 | ||
|
|
03829831c0 | ||
|
|
4f83468309 | ||
|
|
2165ebc103 |
@@ -196,10 +196,12 @@ Finally, simply add the appropriate config to the `providers` section of `config
|
|||||||
|
|
||||||
As well as being my work, I have also gratefully received feature patches from Steven, M1SDH.
|
As well as being my work, I have also gratefully received feature patches from Steven, M1SDH.
|
||||||
|
|
||||||
The project contains a self-hosted copy of Font Awesome's free library, in the `/webasset/fa/` directory. This is subject to Font Awesome's licence and is not covered by the overall licence declared in the `LICENSE` file. This approach was taken in preference to using their hosted kits due to the popularity of this project exceeding the page view limit for their free hosted offering.
|
The project contains a self-hosted copy of Font Awesome's free library, in the `/webassets/fa/` directory. This is subject to Font Awesome's licence and is not covered by the overall licence declared in the `LICENSE` file. This approach was taken in preference to using their hosted kits due to the popularity of this project exceeding the page view limit for their free hosted offering.
|
||||||
|
|
||||||
|
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 such as jQuery, Leaflet and Bootstrap. 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 such as jQuery, Leaflet and Bootstrap. This project would not have been possible without these libraries, so many thanks to their developers.
|
||||||
|
|
||||||
Particular thanks go to QRZCQ country-files.com for providing country lookup data for amateur radio, and to the developers of `pyhamtools` for making it easy to use this data as well as QRZ.com and Clublog lookup.
|
Particular thanks go to country-files.com for providing country lookup data for amateur radio, to K0SWE for [this JSON-formatted DXCC data](https://github.com/k0swe/dxcc-json/), and to the developers of `pyhamtools` for making it easy to use country-files.com data as well as QRZ.com and Clublog lookup.
|
||||||
|
|
||||||
The project's name was suggested by Harm, DK4HAA. Thanks!
|
The project's name was suggested by Harm, DK4HAA. Thanks!
|
||||||
|
|||||||
@@ -135,4 +135,13 @@ hamqth-password: ""
|
|||||||
clublog-api-key: ""
|
clublog-api-key: ""
|
||||||
|
|
||||||
# Allow submitting spots to the Spothole API?
|
# Allow submitting spots to the Spothole API?
|
||||||
allow-spotting: true
|
allow-spotting: true
|
||||||
|
|
||||||
|
# Options for the web UI.
|
||||||
|
web-ui-options:
|
||||||
|
spot-count: [10, 25, 50, 100]
|
||||||
|
spot-count-default: 50
|
||||||
|
max-spot-age: [5, 10, 30, 60]
|
||||||
|
max-spot-age-default: 30
|
||||||
|
alert-count: [25, 50, 100, 200, 500]
|
||||||
|
alert-count-default: 100
|
||||||
@@ -16,4 +16,5 @@ MAX_SPOT_AGE = config["max-spot-age-sec"]
|
|||||||
MAX_ALERT_AGE = config["max-alert-age-sec"]
|
MAX_ALERT_AGE = config["max-alert-age-sec"]
|
||||||
SERVER_OWNER_CALLSIGN = config["server-owner-callsign"]
|
SERVER_OWNER_CALLSIGN = config["server-owner-callsign"]
|
||||||
WEB_SERVER_PORT = config["web-server-port"]
|
WEB_SERVER_PORT = config["web-server-port"]
|
||||||
ALLOW_SPOTTING = config["allow-spotting"]
|
ALLOW_SPOTTING = config["allow-spotting"]
|
||||||
|
WEB_UI_OPTIONS = config["web-ui-options"]
|
||||||
1412
core/constants.py
1412
core/constants.py
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
|||||||
import gzip
|
import gzip
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
@@ -14,7 +16,7 @@ from requests_cache import CachedSession
|
|||||||
from core.cache_utils import SEMI_STATIC_URL_DATA_CACHE
|
from core.cache_utils import SEMI_STATIC_URL_DATA_CACHE
|
||||||
from core.config import config
|
from core.config import config
|
||||||
from core.constants import BANDS, UNKNOWN_BAND, CW_MODES, PHONE_MODES, DATA_MODES, ALL_MODES, \
|
from core.constants import BANDS, UNKNOWN_BAND, CW_MODES, PHONE_MODES, DATA_MODES, ALL_MODES, \
|
||||||
QRZCQ_CALLSIGN_LOOKUP_DATA, HTTP_HEADERS, HAMQTH_PRG
|
HTTP_HEADERS, HAMQTH_PRG
|
||||||
|
|
||||||
|
|
||||||
# Singleton class that provides lookup functionality.
|
# Singleton class that provides lookup functionality.
|
||||||
@@ -46,6 +48,8 @@ class LookupHelper:
|
|||||||
self.CALL_INFO_BASIC = None
|
self.CALL_INFO_BASIC = None
|
||||||
self.LOOKUP_LIB_BASIC = None
|
self.LOOKUP_LIB_BASIC = None
|
||||||
self.COUNTRY_FILES_CTY_PLIST_DOWNLOAD_LOCATION = None
|
self.COUNTRY_FILES_CTY_PLIST_DOWNLOAD_LOCATION = None
|
||||||
|
self.DXCC_JSON_DOWNLOAD_LOCATION = None
|
||||||
|
self.DXCC_DATA = None
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
# Lookup helpers from pyhamtools. We use five (!) of these. The simplest is country-files.com, which downloads
|
# Lookup helpers from pyhamtools. We use five (!) of these. The simplest is country-files.com, which downloads
|
||||||
@@ -84,6 +88,19 @@ class LookupHelper:
|
|||||||
filename=self.CLUBLOG_XML_DOWNLOAD_LOCATION)
|
filename=self.CLUBLOG_XML_DOWNLOAD_LOCATION)
|
||||||
self.CLUBLOG_CALLSIGN_DATA_CACHE = Cache('cache/clublog_callsign_lookup_cache')
|
self.CLUBLOG_CALLSIGN_DATA_CACHE = Cache('cache/clublog_callsign_lookup_cache')
|
||||||
|
|
||||||
|
# We also get a lookup of DXCC data from K0SWE to use for additional lookups of e.g. flags.
|
||||||
|
self.DXCC_JSON_DOWNLOAD_LOCATION = "cache/dxcc.json"
|
||||||
|
success = self.download_dxcc_json()
|
||||||
|
if success:
|
||||||
|
with open(self.DXCC_JSON_DOWNLOAD_LOCATION) as f:
|
||||||
|
tmp_dxcc_data = json.load(f)["dxcc"]
|
||||||
|
# Reformat as a map for faster lookup
|
||||||
|
self.DXCC_DATA = {}
|
||||||
|
for dxcc in tmp_dxcc_data:
|
||||||
|
self.DXCC_DATA[dxcc["entityCode"]] = dxcc
|
||||||
|
else:
|
||||||
|
logging.error("Could not download DXCC data, flags and similar data may be missing!")
|
||||||
|
|
||||||
# Download the cty.plist file from country-files.com on first startup. The pyhamtools lib can actually download and use
|
# Download the cty.plist file from country-files.com on first startup. The pyhamtools lib can actually download and use
|
||||||
# this itself, but it's occasionally offline which causes it to throw an error. By downloading it separately, we can
|
# this itself, but it's occasionally offline which causes it to throw an error. By downloading it separately, we can
|
||||||
# catch errors and handle them, falling back to a previous copy of the file in the cache, and we can use the
|
# catch errors and handle them, falling back to a previous copy of the file in the cache, and we can use the
|
||||||
@@ -103,6 +120,22 @@ class LookupHelper:
|
|||||||
logging.error("Exception when downloading Clublog cty.xml", e)
|
logging.error("Exception when downloading Clublog cty.xml", e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Download the dxcc.json file on first startup.
|
||||||
|
def download_dxcc_json(self):
|
||||||
|
try:
|
||||||
|
logging.info("Downloading dxcc.json...")
|
||||||
|
response = SEMI_STATIC_URL_DATA_CACHE.get("https://raw.githubusercontent.com/k0swe/dxcc-json/refs/heads/main/dxcc.json",
|
||||||
|
headers=HTTP_HEADERS).text
|
||||||
|
|
||||||
|
with open(self.DXCC_JSON_DOWNLOAD_LOCATION, "w") as f:
|
||||||
|
f.write(response)
|
||||||
|
f.flush()
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("Exception when downloading dxcc.json", e)
|
||||||
|
return False
|
||||||
|
|
||||||
# Download the cty.xml (gzipped) file from Clublog on first startup, so we can use it in preference to querying the
|
# Download the cty.xml (gzipped) file from Clublog on first startup, so we can use it in preference to querying the
|
||||||
# database live if possible.
|
# database live if possible.
|
||||||
def download_clublog_ctyxml(self):
|
def download_clublog_ctyxml(self):
|
||||||
@@ -175,11 +208,11 @@ class LookupHelper:
|
|||||||
clublog_data = self.get_clublog_api_data_for_callsign(call)
|
clublog_data = self.get_clublog_api_data_for_callsign(call)
|
||||||
if clublog_data and "Name" in clublog_data:
|
if clublog_data and "Name" in clublog_data:
|
||||||
country = clublog_data["Name"]
|
country = clublog_data["Name"]
|
||||||
# Couldn't get anything from Clublog database, try QRZCQ data
|
# Couldn't get anything from Clublog database, try DXCC data
|
||||||
if not country:
|
if not country:
|
||||||
qrzcq_data = self.get_qrzcq_data_for_callsign(call)
|
dxcc_data = self.get_dxcc_data_for_callsign(call)
|
||||||
if qrzcq_data and "country" in qrzcq_data:
|
if dxcc_data and "name" in dxcc_data:
|
||||||
country = qrzcq_data["country"]
|
country = dxcc_data["name"]
|
||||||
return country
|
return country
|
||||||
|
|
||||||
# Infer a DXCC ID from a callsign
|
# Infer a DXCC ID from a callsign
|
||||||
@@ -208,11 +241,11 @@ class LookupHelper:
|
|||||||
clublog_data = self.get_clublog_api_data_for_callsign(call)
|
clublog_data = self.get_clublog_api_data_for_callsign(call)
|
||||||
if clublog_data and "DXCC" in clublog_data:
|
if clublog_data and "DXCC" in clublog_data:
|
||||||
dxcc = clublog_data["DXCC"]
|
dxcc = clublog_data["DXCC"]
|
||||||
# Couldn't get anything from Clublog database, try QRZCQ data
|
# Couldn't get anything from Clublog database, try DXCC data
|
||||||
if not dxcc:
|
if not dxcc:
|
||||||
qrzcq_data = self.get_qrzcq_data_for_callsign(call)
|
dxcc_data = self.get_dxcc_data_for_callsign(call)
|
||||||
if qrzcq_data and "dxcc" in qrzcq_data:
|
if dxcc_data and "entityCode" in dxcc_data:
|
||||||
dxcc = qrzcq_data["dxcc"]
|
dxcc = dxcc_data["entityCode"]
|
||||||
return dxcc
|
return dxcc
|
||||||
|
|
||||||
# Infer a continent shortcode from a callsign
|
# Infer a continent shortcode from a callsign
|
||||||
@@ -236,11 +269,12 @@ class LookupHelper:
|
|||||||
clublog_data = self.get_clublog_api_data_for_callsign(call)
|
clublog_data = self.get_clublog_api_data_for_callsign(call)
|
||||||
if clublog_data and "Continent" in clublog_data:
|
if clublog_data and "Continent" in clublog_data:
|
||||||
continent = clublog_data["Continent"]
|
continent = clublog_data["Continent"]
|
||||||
# Couldn't get anything from Clublog database, try QRZCQ data
|
# Couldn't get anything from Clublog database, try DXCC data
|
||||||
if not continent:
|
if not continent:
|
||||||
qrzcq_data = self.get_qrzcq_data_for_callsign(call)
|
dxcc_data = self.get_dxcc_data_for_callsign(call)
|
||||||
if qrzcq_data and "continent" in qrzcq_data:
|
# Some DXCCs are in two continents, if so don't use the continent data as we can't be sure
|
||||||
continent = qrzcq_data["continent"]
|
if dxcc_data and "continent" in dxcc_data and len(dxcc_data["continent"]) == 1:
|
||||||
|
continent = dxcc_data["continent"][0]
|
||||||
return continent
|
return continent
|
||||||
|
|
||||||
# Infer a CQ zone from a callsign
|
# Infer a CQ zone from a callsign
|
||||||
@@ -269,11 +303,12 @@ class LookupHelper:
|
|||||||
clublog_data = self.get_clublog_api_data_for_callsign(call)
|
clublog_data = self.get_clublog_api_data_for_callsign(call)
|
||||||
if clublog_data and "CQZ" in clublog_data:
|
if clublog_data and "CQZ" in clublog_data:
|
||||||
cqz = clublog_data["CQZ"]
|
cqz = clublog_data["CQZ"]
|
||||||
# Couldn't get anything from Clublog database, try QRZCQ data
|
# Couldn't get anything from Clublog database, try DXCC data
|
||||||
if not cqz:
|
if not cqz:
|
||||||
qrzcq_data = self.get_qrzcq_data_for_callsign(call)
|
dxcc_data = self.get_dxcc_data_for_callsign(call)
|
||||||
if qrzcq_data and "cqz" in qrzcq_data:
|
# Some DXCCs are in multiple zones, if so don't use the zone data as we can't be sure
|
||||||
cqz = qrzcq_data["cqz"]
|
if dxcc_data and "cq" in dxcc_data and len(dxcc_data["cq"]) == 1:
|
||||||
|
cqz = dxcc_data["cq"][0]
|
||||||
return cqz
|
return cqz
|
||||||
|
|
||||||
# Infer a ITU zone from a callsign
|
# Infer a ITU zone from a callsign
|
||||||
@@ -293,13 +328,18 @@ class LookupHelper:
|
|||||||
hamqth_data = self.get_hamqth_data_for_callsign(call)
|
hamqth_data = self.get_hamqth_data_for_callsign(call)
|
||||||
if hamqth_data and "itu" in hamqth_data:
|
if hamqth_data and "itu" in hamqth_data:
|
||||||
ituz = hamqth_data["itu"]
|
ituz = hamqth_data["itu"]
|
||||||
# Couldn't get anything from HamQTH database, Clublog doesn't provide this, so try QRZCQ data
|
# Couldn't get anything from HamQTH database, Clublog doesn't provide this, so try DXCC data
|
||||||
if not ituz:
|
if not ituz:
|
||||||
qrzcq_data = self.get_qrzcq_data_for_callsign(call)
|
dxcc_data = self.get_dxcc_data_for_callsign(call)
|
||||||
if qrzcq_data and "ituz" in qrzcq_data:
|
# Some DXCCs are in multiple zones, if so don't use the zone data as we can't be sure
|
||||||
ituz = qrzcq_data["ituz"]
|
if dxcc_data and "itu" in dxcc_data and len(dxcc_data["itu"]) == 1:
|
||||||
|
ituz = dxcc_data["itu"]
|
||||||
return ituz
|
return ituz
|
||||||
|
|
||||||
|
# Get an emoji flag for a given DXCC entity ID
|
||||||
|
def get_flag_for_dxcc(self, dxcc):
|
||||||
|
return self.DXCC_DATA[dxcc]["flag"] if dxcc in self.DXCC_DATA else None
|
||||||
|
|
||||||
# Infer an operator name from a callsign (requires QRZ.com/HamQTH)
|
# Infer an operator name from a callsign (requires QRZ.com/HamQTH)
|
||||||
def infer_name_from_callsign_online_lookup(self, call):
|
def infer_name_from_callsign_online_lookup(self, call):
|
||||||
data = self.get_qrz_data_for_callsign(call)
|
data = self.get_qrz_data_for_callsign(call)
|
||||||
@@ -488,11 +528,10 @@ class LookupHelper:
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Utility method to get QRZCQ data from our constants table, if we can find it
|
# Utility method to get generic DXCC data from our lookup table, if we can find it
|
||||||
def get_qrzcq_data_for_callsign(self, call):
|
def get_dxcc_data_for_callsign(self, call):
|
||||||
# Iterate in reverse order - see comments on the data structure itself
|
for entry in self.DXCC_DATA.values():
|
||||||
for entry in reversed(QRZCQ_CALLSIGN_LOOKUP_DATA):
|
if re.match(entry["prefixRegex"], call):
|
||||||
if call.startswith(entry["prefix"]):
|
|
||||||
return entry
|
return entry
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from datetime import datetime, timedelta
|
|||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from core.constants import DXCC_FLAGS
|
|
||||||
from core.lookup_helper import lookup_helper
|
from core.lookup_helper import lookup_helper
|
||||||
from core.sig_utils import get_icon_for_sig, get_sig_ref_info
|
from core.sig_utils import get_icon_for_sig, get_sig_ref_info
|
||||||
|
|
||||||
@@ -95,8 +94,8 @@ class Alert:
|
|||||||
self.dx_itu_zone = lookup_helper.infer_itu_zone_from_callsign(self.dx_calls[0])
|
self.dx_itu_zone = lookup_helper.infer_itu_zone_from_callsign(self.dx_calls[0])
|
||||||
if self.dx_calls and self.dx_calls[0] and not self.dx_dxcc_id:
|
if self.dx_calls and self.dx_calls[0] and not self.dx_dxcc_id:
|
||||||
self.dx_dxcc_id = lookup_helper.infer_dxcc_id_from_callsign(self.dx_calls[0])
|
self.dx_dxcc_id = lookup_helper.infer_dxcc_id_from_callsign(self.dx_calls[0])
|
||||||
if self.dx_dxcc_id and self.dx_dxcc_id in DXCC_FLAGS and not self.dx_flag:
|
if self.dx_dxcc_id and not self.dx_flag:
|
||||||
self.dx_flag = DXCC_FLAGS[self.dx_dxcc_id]
|
self.dx_flag = lookup_helper.get_flag_for_dxcc(self.dx_dxcc_id)
|
||||||
|
|
||||||
# Fetch SIG data. In case a particular API doesn't provide a full set of name, lat, lon & grid for a reference
|
# Fetch SIG data. In case a particular API doesn't provide a full set of name, lat, lon & grid for a reference
|
||||||
# in its initial call, we use this code to populate the rest of the data. This includes working out grid refs
|
# in its initial call, we use this code to populate the rest of the data. This includes working out grid refs
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from datetime import datetime
|
|||||||
import pytz
|
import pytz
|
||||||
from pyhamtools.locator import locator_to_latlong, latlong_to_locator
|
from pyhamtools.locator import locator_to_latlong, latlong_to_locator
|
||||||
|
|
||||||
from core.constants import DXCC_FLAGS
|
|
||||||
from core.lookup_helper import lookup_helper
|
from core.lookup_helper import lookup_helper
|
||||||
from core.sig_utils import get_icon_for_sig, get_sig_ref_info, ANY_SIG_REGEX, get_ref_regex_for_sig
|
from core.sig_utils import get_icon_for_sig, get_sig_ref_info, ANY_SIG_REGEX, get_ref_regex_for_sig
|
||||||
from data.sig_ref import SIGRef
|
from data.sig_ref import SIGRef
|
||||||
@@ -174,8 +173,8 @@ class Spot:
|
|||||||
self.dx_itu_zone = lookup_helper.infer_itu_zone_from_callsign(self.dx_call)
|
self.dx_itu_zone = lookup_helper.infer_itu_zone_from_callsign(self.dx_call)
|
||||||
if self.dx_call and not self.dx_dxcc_id:
|
if self.dx_call and not self.dx_dxcc_id:
|
||||||
self.dx_dxcc_id = lookup_helper.infer_dxcc_id_from_callsign(self.dx_call)
|
self.dx_dxcc_id = lookup_helper.infer_dxcc_id_from_callsign(self.dx_call)
|
||||||
if self.dx_dxcc_id and self.dx_dxcc_id in DXCC_FLAGS and not self.dx_flag:
|
if self.dx_dxcc_id and not self.dx_flag:
|
||||||
self.dx_flag = DXCC_FLAGS[self.dx_dxcc_id]
|
self.dx_flag = lookup_helper.get_flag_for_dxcc(self.dx_dxcc_id)
|
||||||
|
|
||||||
# Clean up spotter call if it has an SSID or -# from RBN
|
# Clean up spotter call if it has an SSID or -# from RBN
|
||||||
if self.de_call and "-" in self.de_call:
|
if self.de_call and "-" in self.de_call:
|
||||||
@@ -207,8 +206,8 @@ class Spot:
|
|||||||
self.de_continent = lookup_helper.infer_continent_from_callsign(self.de_call)
|
self.de_continent = lookup_helper.infer_continent_from_callsign(self.de_call)
|
||||||
if not self.de_dxcc_id:
|
if not self.de_dxcc_id:
|
||||||
self.de_dxcc_id = lookup_helper.infer_dxcc_id_from_callsign(self.de_call)
|
self.de_dxcc_id = lookup_helper.infer_dxcc_id_from_callsign(self.de_call)
|
||||||
if self.de_dxcc_id and self.de_dxcc_id in DXCC_FLAGS and not self.de_flag:
|
if self.de_dxcc_id and not self.de_flag:
|
||||||
self.de_flag = DXCC_FLAGS[self.de_dxcc_id]
|
self.de_flag = lookup_helper.get_flag_for_dxcc(self.de_dxcc_id)
|
||||||
|
|
||||||
# Band from frequency
|
# Band from frequency
|
||||||
if self.freq and not self.band:
|
if self.freq and not self.band:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import bottle
|
|||||||
import pytz
|
import pytz
|
||||||
from bottle import run, request, response, template
|
from bottle import run, request, response, template
|
||||||
|
|
||||||
from core.config import MAX_SPOT_AGE, ALLOW_SPOTTING
|
from core.config import MAX_SPOT_AGE, ALLOW_SPOTTING, WEB_UI_OPTIONS
|
||||||
from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS, SOFTWARE_VERSION, UNKNOWN_BAND
|
from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS, SOFTWARE_VERSION, UNKNOWN_BAND
|
||||||
from core.lookup_helper import lookup_helper
|
from core.lookup_helper import lookup_helper
|
||||||
from core.prometheus_metrics_handler import page_requests_counter, get_metrics, api_requests_counter
|
from core.prometheus_metrics_handler import page_requests_counter, get_metrics, api_requests_counter
|
||||||
@@ -469,7 +469,8 @@ class WebServer:
|
|||||||
map(lambda p: p["name"], filter(lambda p: p["enabled"], self.status_data["alert_providers"]))),
|
map(lambda p: p["name"], filter(lambda p: p["enabled"], self.status_data["alert_providers"]))),
|
||||||
"continents": CONTINENTS,
|
"continents": CONTINENTS,
|
||||||
"max_spot_age": MAX_SPOT_AGE,
|
"max_spot_age": MAX_SPOT_AGE,
|
||||||
"spot_allowed": ALLOW_SPOTTING}
|
"spot_allowed": ALLOW_SPOTTING,
|
||||||
|
"web-ui-options": WEB_UI_OPTIONS}
|
||||||
# If spotting to this server is enabled, "API" is another valid spot source even though it does not come from
|
# If spotting to this server is enabled, "API" is another valid spot source even though it does not come from
|
||||||
# one of our proviers.
|
# one of our proviers.
|
||||||
if ALLOW_SPOTTING:
|
if ALLOW_SPOTTING:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from spotproviders.http_spot_provider import HTTPSpotProvider
|
|||||||
# Spot provider for Wainwrights on the Air
|
# Spot provider for Wainwrights on the Air
|
||||||
class WOTA(HTTPSpotProvider):
|
class WOTA(HTTPSpotProvider):
|
||||||
POLL_INTERVAL_SEC = 120
|
POLL_INTERVAL_SEC = 120
|
||||||
SPOTS_URL = "http://127.0.0.1:8000/spots_rss.php"
|
SPOTS_URL = "https://www.wota.org.uk/spots_rss.php"
|
||||||
LIST_URL = "https://www.wota.org.uk/mapping/data/summits.json"
|
LIST_URL = "https://www.wota.org.uk/mapping/data/summits.json"
|
||||||
RSS_DATE_TIME_FORMAT = "%a, %d %b %Y %H:%M:%S %z"
|
RSS_DATE_TIME_FORMAT = "%a, %d %b %Y %H:%M:%S %z"
|
||||||
|
|
||||||
|
|||||||
@@ -101,11 +101,6 @@
|
|||||||
<h5 class="card-title">Number of Alerts</h5>
|
<h5 class="card-title">Number of Alerts</h5>
|
||||||
<p class="card-text spothole-card-text">Show up to
|
<p class="card-text spothole-card-text">Show up to
|
||||||
<select id="alerts-to-fetch" class="storeable-select form-select ms-2" oninput="filtersUpdated();" style="width: 5em;display: inline-block;">
|
<select id="alerts-to-fetch" class="storeable-select form-select ms-2" oninput="filtersUpdated();" style="width: 5em;display: inline-block;">
|
||||||
<option value="25">25</option>
|
|
||||||
<option value="50">50</option>
|
|
||||||
<option value="100" selected>100</option>
|
|
||||||
<option value="200">200</option>
|
|
||||||
<option value="500">500</option>
|
|
||||||
</select>
|
</select>
|
||||||
alerts
|
alerts
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -93,10 +93,6 @@
|
|||||||
<h5 class="card-title">Spot Age</h5>
|
<h5 class="card-title">Spot Age</h5>
|
||||||
<p class="card-text spothole-card-text">Last
|
<p class="card-text spothole-card-text">Last
|
||||||
<select id="max-spot-age" class="storeable-select form-select ms-2 me-2 d-inline-block" oninput="filtersUpdated();" style="width: 5em; display: inline-block;">
|
<select id="max-spot-age" class="storeable-select form-select ms-2 me-2 d-inline-block" oninput="filtersUpdated();" style="width: 5em; display: inline-block;">
|
||||||
<option value="300">5</option>
|
|
||||||
<option value="600">10</option>
|
|
||||||
<option value="1800" selected>30</option>
|
|
||||||
<option value="3600">60</option>
|
|
||||||
</select>
|
</select>
|
||||||
minutes
|
minutes
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -92,10 +92,6 @@
|
|||||||
<h5 class="card-title">Spot Age</h5>
|
<h5 class="card-title">Spot Age</h5>
|
||||||
<p class="card-text spothole-card-text">Last
|
<p class="card-text spothole-card-text">Last
|
||||||
<select id="max-spot-age" class="storeable-select form-select ms-2 me-2 d-inline-block" oninput="filtersUpdated();" style="width: 5em; display: inline-block;">
|
<select id="max-spot-age" class="storeable-select form-select ms-2 me-2 d-inline-block" oninput="filtersUpdated();" style="width: 5em; display: inline-block;">
|
||||||
<option value="300">5</option>
|
|
||||||
<option value="600">10</option>
|
|
||||||
<option value="1800" selected>30</option>
|
|
||||||
<option value="3600">60</option>
|
|
||||||
</select>
|
</select>
|
||||||
minutes
|
minutes
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -117,10 +117,6 @@
|
|||||||
<h5 class="card-title">Number of Spots</h5>
|
<h5 class="card-title">Number of Spots</h5>
|
||||||
<p class="card-text spothole-card-text">Show up to
|
<p class="card-text spothole-card-text">Show up to
|
||||||
<select id="spots-to-fetch" class="storeable-select form-select ms-2 me-2 d-inline-block" oninput="filtersUpdated();" style="width: 5em; display: inline-block;">
|
<select id="spots-to-fetch" class="storeable-select form-select ms-2 me-2 d-inline-block" oninput="filtersUpdated();" style="width: 5em; display: inline-block;">
|
||||||
<option value="10">10</option>
|
|
||||||
<option value="25">25</option>
|
|
||||||
<option value="50" selected>50</option>
|
|
||||||
<option value="100">100</option>
|
|
||||||
</select>
|
</select>
|
||||||
spots
|
spots
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -438,7 +438,7 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- General
|
- General
|
||||||
summary: Get enumeration options
|
summary: Get enumeration options
|
||||||
description: Retrieves the list of options for various enumerated types, which can be found in the spots and also provided back to the API as query parameters. While these enumerated options are defined in this spec anyway, providing them in an API call allows us to define extra parameters, like the colours associated with bands, and also allows clients to set up their filters and features without having to have internal knowledge about, for example, what bands the server knows about.
|
description: Retrieves the list of options for various enumerated types, which can be found in the spots and also provided back to the API as query parameters. While these enumerated options are defined in this spec anyway, providing them in an API call allows us to define extra parameters, like the colours associated with bands, and also allows clients to set up their filters and features without having to have internal knowledge about, for example, what bands the server knows about. The call also returns a variety of other parameters that may be of use to a web UI, including the contents of the "web-ui-options" config section, which provides guidance for web UI implementations such as the built-in one on sensible configuration options such as the number of spots/alerts to retrieve, or the maximum age of spots to retrieve.
|
||||||
operationId: options
|
operationId: options
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
@@ -490,6 +490,39 @@ paths:
|
|||||||
type: boolean
|
type: boolean
|
||||||
description: Whether the POST /spot call, to add spots to the server directly via its API, is permitted on this server.
|
description: Whether the POST /spot call, to add spots to the server directly via its API, is permitted on this server.
|
||||||
example: true
|
example: true
|
||||||
|
web-ui-options:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
spot-count:
|
||||||
|
type: array
|
||||||
|
description: An array of suggested "spot counts" that the web UI can retrieve from the API
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
example: 50
|
||||||
|
spot-count-default:
|
||||||
|
type: integer
|
||||||
|
example: 50
|
||||||
|
description: The suggested default "spot count" that the web UI should retrieve from the API
|
||||||
|
max-spot-age:
|
||||||
|
type: array
|
||||||
|
description: An array of suggested "maximum spot ages" that the web UI can retrieve from the API
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
example: 30
|
||||||
|
max-spot-age-default:
|
||||||
|
type: integer
|
||||||
|
example: 30
|
||||||
|
description: The suggested default "maximum spot age" that the web UI should retrieve from the API
|
||||||
|
alert-count:
|
||||||
|
type: array
|
||||||
|
description: An array of suggested "alert counts" that the web UI can retrieve from the API
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
example: 100
|
||||||
|
alert-count-default:
|
||||||
|
type: integer
|
||||||
|
example: 100
|
||||||
|
description: The suggested default "alert count" that the web UI should retrieve from the API
|
||||||
|
|
||||||
|
|
||||||
/lookup/call:
|
/lookup/call:
|
||||||
|
|||||||
BIN
webassets/img/flags/999.png
Normal file
BIN
webassets/img/flags/999.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 348 B |
@@ -19,4 +19,7 @@ for dxcc in data["dxcc"]:
|
|||||||
draw = ImageDraw.Draw(image)
|
draw = ImageDraw.Draw(image)
|
||||||
draw.text((0, -10), flag, font=ImageFont.truetype("/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf", 109), embedded_color=True)
|
draw.text((0, -10), flag, font=ImageFont.truetype("/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf", 109), embedded_color=True)
|
||||||
outfile = str(id) + ".png"
|
outfile = str(id) + ".png"
|
||||||
image.save(outfile, "PNG")
|
image.save(outfile, "PNG")
|
||||||
|
|
||||||
|
image = Image.new("RGBA", (140, 110), (255, 0, 0, 0))
|
||||||
|
image.save("999.png", "PNG")
|
||||||
@@ -285,6 +285,13 @@ function loadOptions() {
|
|||||||
generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]);
|
generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]);
|
||||||
generateMultiToggleFilterCard("#source-options", "source", options["alert_sources"]);
|
generateMultiToggleFilterCard("#source-options", "source", options["alert_sources"]);
|
||||||
|
|
||||||
|
// Populate the Display panel
|
||||||
|
options["web-ui-options"]["alert-count"].forEach(sc => $("#alerts-to-fetch").append($('<option>', {
|
||||||
|
value: sc,
|
||||||
|
text: sc
|
||||||
|
})));
|
||||||
|
$("#alerts-to-fetch").val(options["web-ui-options"]["alert-count-default"]);
|
||||||
|
|
||||||
// Load filters from settings storage
|
// Load filters from settings storage
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
|
|||||||
@@ -235,6 +235,13 @@ function loadOptions() {
|
|||||||
generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]);
|
generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]);
|
||||||
generateMultiToggleFilterCard("#source-options", "source", options["spot_sources"]);
|
generateMultiToggleFilterCard("#source-options", "source", options["spot_sources"]);
|
||||||
|
|
||||||
|
// Populate the Display panel
|
||||||
|
options["web-ui-options"]["max-spot-age"].forEach(sc => $("#max-spot-age").append($('<option>', {
|
||||||
|
value: sc * 60,
|
||||||
|
text: sc
|
||||||
|
})));
|
||||||
|
$("#max-spot-age").val(options["web-ui-options"]["max-spot-age-default"] * 60);
|
||||||
|
|
||||||
// Load settings from settings storage now all the controls are available
|
// Load settings from settings storage now all the controls are available
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
|
|||||||
@@ -163,6 +163,13 @@ function loadOptions() {
|
|||||||
generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]);
|
generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]);
|
||||||
generateMultiToggleFilterCard("#source-options", "source", options["spot_sources"]);
|
generateMultiToggleFilterCard("#source-options", "source", options["spot_sources"]);
|
||||||
|
|
||||||
|
// Populate the Display panel
|
||||||
|
options["web-ui-options"]["max-spot-age"].forEach(sc => $("#max-spot-age").append($('<option>', {
|
||||||
|
value: sc * 60,
|
||||||
|
text: sc
|
||||||
|
})));
|
||||||
|
$("#max-spot-age").val(options["web-ui-options"]["max-spot-age-default"] * 60);
|
||||||
|
|
||||||
// Load settings from settings storage now all the controls are available
|
// Load settings from settings storage now all the controls are available
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
|
|||||||
@@ -287,6 +287,13 @@ function loadOptions() {
|
|||||||
generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]);
|
generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]);
|
||||||
generateMultiToggleFilterCard("#source-options", "source", options["spot_sources"]);
|
generateMultiToggleFilterCard("#source-options", "source", options["spot_sources"]);
|
||||||
|
|
||||||
|
// Populate the Display panel
|
||||||
|
options["web-ui-options"]["spot-count"].forEach(sc => $("#spots-to-fetch").append($('<option>', {
|
||||||
|
value: sc,
|
||||||
|
text: sc
|
||||||
|
})));
|
||||||
|
$("#spots-to-fetch").val(options["web-ui-options"]["spot-count-default"]);
|
||||||
|
|
||||||
// Load settings from settings storage now all the controls are available
|
// Load settings from settings storage now all the controls are available
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user