diff --git a/data/spot.py b/data/spot.py index c3f1d90..f51d852 100644 --- a/data/spot.py +++ b/data/spot.py @@ -57,8 +57,9 @@ class Spot: band_contrast_color: str = None # Time of the spot time: datetime = None - # Time that this software received the spot. This is used with the "since" call to our API to receive all data that - # is new to us, even if by a quirk of the API it might be older than the list time the client polled the API. + # Time that this software received the spot. This is used with the "since_received" call to our API to receive all + # data that is new to us, even if by a quirk of the API it might be older than the list time the client polled the + # API. received_time: datetime = datetime.now(pytz.UTC), # Comment left by the spotter, if any comment: str = None @@ -72,7 +73,7 @@ class Spot: activation_score: int = None # Maidenhead grid locator for the spot. This could be from a geographical reference e.g. POTA, or just from the country grid: str = None - # Latitude & longitude, in degrees. This could be from a geographical reference e.g. POTA, or just from the country + # Latitude & longitude, in degrees. This could be from a geographical reference e.g. POTA, or from a QRZ lookup latitude: float = None longitude: float = None # QRT state. Some APIs return spots marked as QRT. Otherwise we can check the comments. diff --git a/server/webserver.py b/server/webserver.py index 31e3a3c..fd35d2c 100644 --- a/server/webserver.py +++ b/server/webserver.py @@ -97,6 +97,9 @@ class WebServer: case "mode": modes = query.get(k).split(",") spots = [s for s in spots if s.mode in modes] + case "mode_family": + mode_families = query.get(k).split(",") + spots = [s for s in spots if s.mode_family in mode_families] case "dx_continent": dxconts = query.get(k).split(",") spots = [s for s in spots if s.dx_continent in dxconts] diff --git a/webassets/apidocs/openapi.yml b/webassets/apidocs/openapi.yml new file mode 100644 index 0000000..621240f --- /dev/null +++ b/webassets/apidocs/openapi.yml @@ -0,0 +1,443 @@ +openapi: 3.0.4 +info: + title: Unnamed Spotting API + description: |- + A utility to aggregate spots from amateur radio DX clusters and xOTA spotting sites, and provide an open JSON API as well as a website to browse the data. + contact: + email: ian@ianrenton.com + license: + name: The Unlicense + url: https://unlicense.org/#the-unlicense + version: 1.0.12 +servers: + - url: https://metaspot.m0trt.radio/api +paths: + /spots: + get: + tags: + - spots + summary: Retrieve a set of spots. + description: The main API call that retrieves spots from the system. Supply this with no query parameters to retrieve all spots known to the system. Supply query parameters to filter what is retrieved. + operationId: spots + parameters: + - name: limit + in: query + description: Limit the number of spots in the response + required: false + schema: + type: integer + - name: since + in: query + description: Limit the spots to only ones at this time or later. Time in UTC seconds since UNIX epoch. + required: false + schema: + 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. + required: false + schema: + type: integer + - 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." + required: false + schema: + type: string + enum: + - POTA + - SOTA + - WWFF + - WWBOTA + - GMA + - HEMA + - ParksNPeaks + - Cluster + - RBN + - 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." + required: false + schema: + type: string + enum: + - POTA + - SOTA + - WWFF + - WWBOTA + - GMA + - HEMA + - name: band + in: query + description: "Limit the spots to only ones from one or more bands. To select more than one band, supply a comma-separated list." + required: false + schema: + type: string + enum: + - 160m + - 80m + - 60m + - 40m + - 30m + - 20m + - 17m + - 15m + - 12m + - 10m + - 6m + - 4m + - 2m + - 70cm + - 23cm + - 13cm + - Unknown + - name: mode + in: query + description: "Limit the spots to only ones from one or more modes. To select more than one mode, supply a comma-separated list." + required: false + schema: + type: string + enum: + - CW + - PHONE + - SSB + - USB + - LSB + - AM + - FM + - DV + - DMR + - DSTAR + - C4FM + - M17 + - DIGI + - DATA + - FT8 + - FT4 + - RTTY + - SSTV + - JS8 + - HELL + - BPSK + - PSK + - BPSK31 + - OLIVIA + - name: mode_family + in: query + description: "Limit the spots to only ones from one or more families modes. To select more than one mode family, supply a comma-separated list." + required: false + schema: + type: string + enum: + - CW + - PHONE + - DATA + - 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." + required: false + schema: + type: string + enum: + - EU + - NA + - SA + - AS + - AF + - OC + - AN + - name: de_continent + in: query + description: "Limit the spots to only ones where the spotteris on the given continent(s). To select more than one continent, supply a comma-separated list." + required: false + schema: + type: string + enum: + - EU + - NA + - SA + - AS + - AF + - OC + - AN + responses: + '200': + description: Successfully retrieved spots. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Spot' + /status: + get: + tags: + - status + summary: Retrieve the server status. + description: Query information about the server for use in a diagnostics display + operationId: status + responses: + '200': + description: Successfully retrieved status. + content: + application/json: + schema: + type: object + properties: + "Web Server": + type: object + properties: + status: + type: string + example: OK + last_ran: + type: string + example: 2025-09-28T20:31:00+00:00 + "Cleanup Timer": + type: object + properties: + status: + type: string + example: OK + last_page_access: + type: string + example: 2025-09-28T20:31:00+00:00 + last_api_access: + type: string + example: 2025-09-28T20:31:00+00:00 + "POTA...": + type: object + properties: + status: + type: string + example: OK + last_updated: + type: string + example: 2025-09-28T20:31:00+00:00 + last_spot: + type: string + example: 2025-09-28T20:31:00+00:00 + +components: + schemas: + Spot: + type: object + properties: + dx_call: + type: string + description: Callsign of the operator that has been spotted + example: M0TRT + de_call: + type: string + description: Callsign of the operator that has spotted them + example: M0TEST + dx_name: + type: string + description: Name of the operator that has been spotted + example: Ian + dx_country: + type: string + description: Country of the DX operator + example: United Kingdom + de_country: + type: string + description: Country of the spotter + example: United Kingdom + dx_flag: + type: string + description: Country flag of the DX operator + example: "" + de_flag: + type: string + description: Country flag of the spotter + example: "" + dx_continent: + type: string + description: Continent of the DX operator + enum: + - EU + - NA + - SA + - AS + - AF + - OC + - AN + example: EU + de_continent: + type: string + enum: + - EU + - NA + - SA + - AS + - AF + - OC + - AN + description: Continent of the spotter + example: EU + dx_dxcc_id: + type: integer + description: DXCC ID of the DX operator + example: 235 + de_dxcc_id: + type: integer + description: DXCC ID of the spotter + example: 235 + dx_cq_zone: + type: integer + description: CQ zone of the DX operator + example: 27 + dx_itu_zone: + type: integer + description: ITU zone of the DX operator + example: 14 + dx_aprs_ssid: + type: string + description: If this is an APRS spot, what SSID was the DX operator using? + example: "" + mode: + type: string + description: Reported mode. + enum: + - CW + - PHONE + - SSB + - USB + - LSB + - AM + - FM + - DV + - DMR + - DSTAR + - C4FM + - M17 + - DIGI + - DATA + - FT8 + - FT4 + - RTTY + - SSTV + - JS8 + - HELL + - BPSK + - PSK + - BPSK31 + - OLIVIA + example: SSB + mode_family: + type: string + description: Inferred mode "family". + enum: + - CW + - PHONE + - DATA + example: PHONE + freq: + type: number + description: Frequency, in kHz + example: 7150.5 + band: + type: string + description: Band, defined by the frequency. + enum: + - 160m + - 80m + - 60m + - 40m + - 30m + - 20m + - 17m + - 15m + - 12m + - 10m + - 6m + - 4m + - 2m + - 70cm + - 23cm + - 13cm + - Unknown + example: 40m + band_color: + type: string + description: Colour to use for the band + example: "#aaaaff" + band_contrast_color: + type: string + description: Contrast colour to use for text on a background of band_color + example: black + time: + type: string + description: Time of the spot, ISO 8601 format + example: 2025-09-28T19:12:41Z + received_time: + type: string + description: Time that this software received the spot, ISO 8601 format. This is used with the "since_received" call to our API to receive all data that is new to us, even if by a quirk of the API it might be older than the list time the client polled the API. + example: 2025-09-28T19:12:41Z + comment: + type: string + description: Comment left by the spotter, if any + example: "59 in NY 73" + sig: + type: string + description: Special Interest Group (SIG), e.g. outdoor activity programme such as POTA + enum: + - POTA + - SOTA + - WWFF + - WWBOTA + - GMA + - HEMA + example: POTA + sig_refs: + type: array + items: + type: string + description: SIG references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO + example: GB-0001 + sig_refs_names: + type: array + items: + type: string + description: SIG reference names + example: Null Country Park + activation_score: + type: integer + description: Activation score. SOTA only + example: 0 + grid: + type: string + description: Maidenhead grid locator for the spot. This could be from a geographical reference e.g. POTA, or just from the country + example: IO91aa + latitude: + type: number + description: Latitude, in degrees. This could be from a geographical reference e.g. POTA, or from a QRZ lookup + example: 51.2345 + longitude: + type: number + description: Latitude, in degrees. This could be from a geographical reference e.g. POTA, or from a QRZ lookup + example: -1.2345 + qrt: + type: boolean + description: QRT state. Some APIs return spots marked as QRT. Otherwise we can check the comments. + example: false + source: + type: string + description: Where we got the spot from. + enum: + - POTA + - SOTA + - WWFF + - WWBOTA + - GMA + - HEMA + - ParksNPeaks + - Cluster + - RBN + - APRS-IS + example: POTA + source_id: + type: string + description: The ID the source gave it, if any. + example: "GUID-123456" \ No newline at end of file