From 94db47cb5bb48b988700063ffdf4e8445f90d86b Mon Sep 17 00:00:00 2001 From: JeanExtreme002 Date: Sat, 14 Nov 2026 18:00:16 -0300 Subject: [PATCH 1/8] fix(nodejs): type impersonate as the TLS profile the client reads The declarations typed it as { profile?: string }, but the implementation takes { ciphers, sigalgs, ecdhCurve }, so a TypeScript caller could not pass a working override. --- .github/docs-requirements.txt | 5 - .github/workflows/deploy-docs.yml | 53 ----- docs/{assets => _static}/favicon.ico | Bin docs/{assets => _static}/logo.png | Bin docs/go.md | 286 --------------------------- docs/nodejs.md | 164 --------------- docs/python.md | 161 --------------- mkdocs.yml | 63 ------ nodejs/FlightRadarAPI/index.d.ts | 12 +- nodejs/tests/index.test-d.ts | 4 + 10 files changed, 12 insertions(+), 736 deletions(-) delete mode 100644 .github/docs-requirements.txt delete mode 100644 .github/workflows/deploy-docs.yml rename docs/{assets => _static}/favicon.ico (100%) rename docs/{assets => _static}/logo.png (100%) delete mode 100644 docs/go.md delete mode 100644 docs/nodejs.md delete mode 100644 docs/python.md delete mode 100644 mkdocs.yml diff --git a/.github/docs-requirements.txt b/.github/docs-requirements.txt deleted file mode 100644 index 383ae2d..0000000 --- a/.github/docs-requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Pinned so a compromised release cannot be pulled in automatically: these -# packages execute during `mkdocs build`, inside the job that publishes the -# docs site. Bump deliberately, not implicitly. -mkdocs-material==9.7.7 -mkdocs-git-committers-plugin-2==2.5.0 diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml deleted file mode 100644 index e44b23f..0000000 --- a/.github/workflows/deploy-docs.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Deploy MkDocs -on: - push: - branches: - - main - paths: - - 'mkdocs.yml' - - 'docs/**' - - '.github/docs-requirements.txt' - - '.github/workflows/deploy-docs.yml' - -# Pages is served from the Actions artifact, so nothing here needs to push to -# the repository: a compromised docs dependency gets no write access. -permissions: - contents: read - pages: write - id-token: write - -# One deploy at a time, and never cancel one midway. -concurrency: - group: pages - cancel-in-progress: false - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: 3.x - - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV - - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - key: mkdocs-material-${{ env.cache_id }} - path: .cache - restore-keys: | - mkdocs-material- - - run: pip install -r .github/docs-requirements.txt - - run: mkdocs build - - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 - with: - path: site - - deploy: - needs: build - runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - id: deployment - uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1 diff --git a/docs/assets/favicon.ico b/docs/_static/favicon.ico similarity index 100% rename from docs/assets/favicon.ico rename to docs/_static/favicon.ico diff --git a/docs/assets/logo.png b/docs/_static/logo.png similarity index 100% rename from docs/assets/logo.png rename to docs/_static/logo.png diff --git a/docs/go.md b/docs/go.md deleted file mode 100644 index ce09a61..0000000 --- a/docs/go.md +++ /dev/null @@ -1,286 +0,0 @@ ---- -title: Go -description: API Documentation for Go ---- - -## Installation - -To install the FlightRadarAPI for Go, use the following command: - -```bash -go get github.com/JeanExtreme002/FlightRadarAPI/go@latest -``` - -## Basic Usage - -Import the package and create a client: - -```go -import ( - "time" - - "github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi" -) - -client := flightradarapi.New() - -// Every field's zero value means the default, so set only what you need. -client = flightradarapi.New(flightradarapi.Options{ - Timeout: 10 * time.Second, // default: 30s - MaxWorkers: 4, // default: 8 -}) -``` - -Construction cannot fail, so it needs no error handling. Every method that talks -to FlightRadar24 takes a `context.Context` and returns an `error` alongside its -result. - -### Fetching Data - -You can fetch various types of data using the following methods: - -- **Flights list:** - - ```go - flights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{}) // Returns []*Flight - ``` - -- **Airports list:** - - ```go - // Airports of specific countries - airports, err := client.GetAirports(ctx, []flightradarapi.Country{ - flightradarapi.CountryBrazil, flightradarapi.CountryUnitedStates, - }) - - // Pass nil to get every airport - allAirports, err := client.GetAirports(ctx, nil) - ``` - - FR24 lists airports one page per country (and per state for the largest - countries), so this makes one request per page: a handful for a few - countries, a few hundred for every airport, `MaxWorkers` at a time. The - listing carries the name, codes and country only; use `GetAirport(ctx, code)` - for the position and altitude of an airport. - -- **Airlines list:** - - ```go - airlines, err := client.GetAirlines(ctx) - ``` - -- **Zones list:** - - ```go - zones := client.GetZones() - ``` - -### Fetching Detailed Information - -Fetch more information about a specific flight or airport using the following methods: - -- **Flight details:** - - ```go - details, err := client.GetFlightDetails(ctx, flight) - flight.SetFlightDetails(details) - - fmt.Println("Flying to", flight.Details.DestinationAirportName) - ``` - - Ask for every flight's details in one call with `FlightSearch{Details: true}`. - Requests run `MaxWorkers` at a time (8 by default). - -- **Airport details:** - - ```go - details, err := client.GetAirportDetails(ctx, icao, 100, 1) - ``` - - !!! note - Arrivals and departures can have a limit `flightLimit` (max value is 100) to display. When you need to reach more than 100 flights you can use the `page` parameter to view other pages. - -## Advanced Usage - -### Fetching Flights Above a Specific Position - -Use the `GetBoundsByPoint(...)` method to fetch flights above a specific position. This method takes `latitude` and `longitude` for your position and `radius` for the distance in meters from your position to designate a tracking area. - -```go -// Your point is 52°34'04.7"N 13°16'57.5"E from Google Maps and radius 2km -bounds := client.GetBoundsByPoint(52.567967, 13.282644, 2000) - -flights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{Bounds: bounds}) -``` - -### Filtering Flights and Airports - -Use the `GetFlights(...)` method to search for flights by airline, bounds (customized coordinates or obtained by the `GetZones()` method), aircraft registration or aircraft type. - -```go -// You may also set a custom region, such as: bounds := "73,-12,-156,38" -bounds := client.GetBounds(client.GetZones()["northamerica"]) - -emiratesFlights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{ - Airline: "UAE", - AircraftType: "B77W", - Bounds: bounds, -}) -``` - -A single flight can be checked against several values at once, with the optional -`min_`/`max_` prefixes for numeric comparisons: - -```go -matched, err := flight.CheckInfo(map[string]any{ - "min_altitude": 6700, "max_altitude": 13000, "airline_icao": "THY", -}) -``` - -### Building Entities From a Payload You Already Have - -The constructors the Python and Node.js ports expose have counterparts here: - -```go -airport := flightradarapi.NewAirportFromBasicInfo(row) // one airports-feed row -airport = flightradarapi.NewAirportFromInfo(info) // the "details" block -airport = flightradarapi.NewAirportFromDetails(payload) // a GetAirportDetails payload -flight := flightradarapi.NewFlight("2e0f1a2", feedRow) // one live-feed row -``` - -### Fetching Airport by ICAO or IATA - -```go -luklaAirport, err := client.GetAirport(ctx, "VNLK", true) -``` - -### Calculating Distance Between Flights and Airports - -`Flight` and `Airport` both embed `Entity`, which provides the `GetDistanceFrom(...)` method. It returns the distance between the two entities in kilometers, and an error when either of them carries no position. - -```go -airport, err := client.GetAirport(ctx, "KJFK", false) -distance, err := flight.GetDistanceFrom(airport) - -fmt.Printf("The flight is %.1f km away from the airport.\n", distance) -``` - -### Downloading Flight Data :material-information-outline:{ title="This requires a premium subscription" } - -```go -if err := client.Login(ctx, "email", "password"); err != nil { - log.Fatal(err) -} - -historyData, err := client.GetHistoryData(ctx, flight, "CSV", 1706529600) -err = os.WriteFile("history_data.csv", []byte(historyData), 0o644) -``` - -!!! warning inline end - If an invalid time is provided, a blank document will be returned. - -| Parameter | Description | -| ------------- | ------------- | -| `flight` | The flight to download. This can be obtained from any other function that returns flights. | -| `fileType` | The format of the file to download. This can be either "CSV" or "KML". | -| `timestamp` | The scheduled time of departure (STD) of the flight in UTC, as a Unix timestamp. | - -### Setting and Getting Real-time Flight Tracker Parameters - -Set them with the `SetFlightTrackerConfig(...)` method. It takes a `*FlightTrackerConfig` and a map of single values; either may be `nil`. Unknown options and non-numeric values are rejected, and a rejected update leaves the current config untouched. - -Get the current configuration with `GetFlightTrackerConfig()`, which returns a copy. Note: `NewFlightTrackerConfig()` means resetting all parameters to default. - -```go -config := client.GetFlightTrackerConfig() -config.Limit = "10" - -err := client.SetFlightTrackerConfig(&config, nil) - -flights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{}) // Returns only 10 flights -``` - -### Handling Errors - -Every error wraps a sentinel, so both `errors.Is` and `errors.As` work: - -```go -airport, err := client.GetAirport(ctx, "XXX", false) - -switch { -case errors.Is(err, flightradarapi.ErrAirportNotFound): - // no such airport -case errors.Is(err, flightradarapi.ErrCloudflare): - // blocked by Cloudflare -case errors.Is(err, flightradarapi.ErrLogin): - // the endpoint needs an account -} -``` - -Transient failures — a Cloudflare block, a timeout, a network error — can be -retried with exponential backoff: - -```go -retry, err := flightradarapi.NewRetryPolicy(3) -client := flightradarapi.New(flightradarapi.Options{Retry: retry}) -``` - -### Configuring the Client - -Every field of `Options` defaults from its zero value, so set only what you need: - -```go -retry, err := flightradarapi.NewRetryPolicy(3) // 1s base, 30s cap, 500ms jitter - -client := flightradarapi.New(flightradarapi.Options{ - Timeout: 10 * time.Second, // default: 30s - MaxWorkers: 4, // default: 8 - Retry: retry, // default: no retry -}) -``` - -### TLS Impersonation - -FlightRadar24 fingerprints TLS handshakes through Cloudflare. The default client -narrows Go's offered cipher suites and curve order to Chrome's, which is enough -today. Since Go fixes its own cipher ordering, that is an approximation rather -than a byte-exact fingerprint; for full impersonation, pass a client built on -[utls](https://github.com/refraction-networking/utls) or -[tls-client](https://github.com/bogdanfinn/tls-client): - -```go -client := flightradarapi.New(flightradarapi.Options{ - HTTPClient: &http.Client{Transport: myImpersonatingTransport}, -}) -``` - -## Differences from the Python and Node.js Packages - -The behavior is the same; the surface is Go-shaped. - -The features are the same; the names differ only where Go's conventions do. The -package name is part of every identifier, so the client is `Client` rather than -`FlightRadar24API` — the way it is `http.Client` and not `http.HTTPClient`. - -| Python / Node.js | Go | -| --- | --- | -| `FlightRadar24API` | `Client`, built with `New` | -| `new FlightRadar24API({timeout, maxWorkers})` | `New(Options{Timeout: ..., MaxWorkers: ...})` | -| `FlightRadarError` (base class) | `ErrFlightRadar`, wrapped by every error here | -| `Countries.BRAZIL` | `CountryBrazil`, with `AllCountries()` to enumerate | -| `FlightRadar24API(user, password)` logs in (Python only) | `client.Login(ctx, user, password)` | -| `get_flights(airline, bounds, registration, aircraft_type, details)` | `GetFlights(ctx, FlightSearch{...})` | -| `check_info(min_altitude=6700)` | `CheckInfo(map[string]any{"min_altitude": 6700})` | -| `Airport.from_details(payload)` | `NewAirportFromDetails(payload)` | -| `(bytes, extension)` tuple | `*Image` | -| `airline["n_aircrafts"]` | `Airline.NumAircrafts` | -| `flight.destination_airport_name` | `flight.Details.DestinationAirportName` | -| default arguments (`flight_limit=100`, `limit=50`) | zero means the same default | -| `get_airports(None)` for every airport | `GetAirports(ctx, nil)` | -| exceptions | `error` values wrapping `ErrFlightRadar` | -| missing value is the string `"N/A"` | zero value: `""` or a nil pointer | -| `parsers` module (internal) | unexported functions | -| blocking calls | `context.Context` on every request | - -Every method of `FlightRadar24API` has a counterpart here, with the same -parameters, kept that way by a test that reads the Python source. diff --git a/docs/nodejs.md b/docs/nodejs.md deleted file mode 100644 index b1cfca1..0000000 --- a/docs/nodejs.md +++ /dev/null @@ -1,164 +0,0 @@ ---- -title: Node.js -description: API Documentation for Node.js ---- - -## Installation - -To install the FlightRadarAPI for Node.js, use the following npm command: - -```bash -npm install flightradarapi -``` - -## Basic Usage - -Start by importing the `FlightRadar24API` class and creating an instance of it: - -```javascript -const { FlightRadar24API } = require("flightradarapi"); -const frApi = new FlightRadar24API(); -``` - -### Fetching Data - -You can fetch various types of data using the following methods: - -- **Flights list:** - - ```javascript - let flights = await frApi.getFlights(...); // Returns a list of Flight objects - ``` - -- **Airports list:** - - ```javascript - let airports = await frApi.getAirports(...); // Returns a list of Airport objects - ``` - - FR24 lists airports one page per country (and per state for the largest - countries), so this makes one request per page: a handful for a few - countries, a few hundred for every airport, `maxWorkers` at a time. The - listing carries the name, codes and country only; use `getAirport(code)` - for the position and altitude of an airport. - -- **Airlines list:** - - ```javascript - let airlines = await frApi.getAirlines(); - ``` - -- **Zones list:** - - ```javascript - let zones = frApi.getZones(); - ``` - -### Fetching Detailed Information - -Fetch more information about a specific flight or airport using the following methods: - -- **Flight details:** - - ```javascript - let flightDetails = await frApi.getFlightDetails(flight); - flight.setFlightDetails(flightDetails); - - console.log("Flying to", flight.destinationAirportName); - ``` - -- **Airport details:** - - ```javascript - let airportDetails = await frApi.getAirportDetails(icao); - ``` - - !!! note - Arrivals and departures can have a limit `flightLimit` (max value is 100) to display. When you need to reach more than 100 flights you can use additional parameter `page` to view other pages. - -## Advanced Usage - -### Fetching Flights Above a Specific Position - -Use the `getBoundsByPoint(...)` method to fetch flights above a specific position. This method takes `latitude` and `longitude` for your position and `radius` for the distance in meters from your position to designate a tracking area. - -```javascript -// Your point is 52°34'04.7"N 13°16'57.5"E from Google Maps and radius 2km -let bounds = frApi.getBoundsByPoint(52.567967, 13.282644, 2000); - -let flights = await frApi.getFlights(null, bounds); -``` - -### Filtering Flights and Airports - -Use the `getFlights(...)` method to search for flights by area line, bounds (customized coordinates or obtained by the `getZones()` method), aircraft registration or aircraft type. - -```javascript -let airlineIcao = "UAE"; -let aircraftType = "B77W"; - -// You may also set a custom region, such as: bounds = "73,-12,-156,38" -let zone = frApi.getZones()["northamerica"]; -let bounds = frApi.getBounds(zone); - -let emiratesFlights = await frApi.getFlights( - airlineIcao, - bounds, - null, - aircraftType, -); -``` - -### Fetching Airport by ICAO or IATA - -```javascript -let luklaAirport = await frApi.getAirport("VNLK", true); -``` - -### Calculating Distance Between Flights and Airports - -The `Flight` and `Airport` classes inherit from `Entity`, which contains the `getDistanceFrom(...)` method. This method returns the distance between the self instance and another entity in kilometers. - -```javascript -let airport = await frApi.getAirport("KJFK"); -let distance = flight.getDistanceFrom(airport); - -console.log("The flight is", distance, "km away from the airport."); -``` - -## Downloading Flight Data :material-information-outline:{ title="This requires a premium subscription" } - -You can download flight data in either CSV or KML format. The method `getHistoryData(...)` is used for this purpose. It takes three parameters: - -!!! warning inline end - If an invalid time is provided, a blank document will be returned. - -| Parameter | Description | -| ------------- | ------------- | -| `flight_id` | The ID of the flight. This can be obtained from any other function that returns flight details. | -| `file_type` | The format of the file to download. This can be either "CSV" or "KML". | -| `time` | The scheduled time of departure (STD) of the flight in UTC, as a Unix timestamp. | - -Here is an example of how to use this method: - -```javascript -let historyData = await frApi.getHistoryData(flight, "csv", 1706529600); - -const buffer = Buffer.from(historyData); -fs.writeFile("history_data.csv", buffer); -``` - -### Setting and Getting Real-time Flight Tracker Parameters - -Set it by using the `setFlightTrackerConfig(...)` method. It receives a `FlightTrackerConfig` dataclass instance, but you can also use keyword arguments directly to the method. - -Get the current configuration with the `getFlightTrackerConfig()` method, that returns a `FlightTrackerConfig` instance. Note: creating a new `FlightTrackerConfig` instance means resetting all parameters to default. - -```javascript -let flightTracker = frApi.getFlightTrackerConfig(); -flightTracker.limit = 10 - -frApi.setFlightTrackerConfig(flightTracker, ...); - -let flights = await frApi.getFlights(...); // Returns only 10 flights -``` diff --git a/docs/python.md b/docs/python.md deleted file mode 100644 index d9ec6a8..0000000 --- a/docs/python.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -title: Python -description: API Documentation for Python ---- - -## Installation - -To install the FlightRadarAPI for Python, use the following pip command: - -```bash -pip install FlightRadarAPI -``` - -## Basic Usage - -Start by importing the `FlightRadar24API` class and creating an instance of it: - -```python -from FlightRadarAPI import FlightRadar24API -fr_api = FlightRadar24API() -``` - -### Fetching Data - -You can fetch various types of data using the following methods: - -- **Flights list:** - - ```python - flights = fr_api.get_flights(...) # Returns a list of Flight objects - ``` - -- **Airports list:** - - ```python - airports = fr_api.get_airports(...) # Returns a list of Airport objects - ``` - - FR24 lists airports one page per country (and per state for the largest - countries), so this makes one request per page: a handful for a few - countries, a few hundred for every airport, `max_workers` at a time. The - listing carries the name, codes and country only; use `get_airport(code)` - for the position and altitude of an airport. - -- **Airlines list:** - - ```python - airlines = fr_api.get_airlines() - ``` - -- **Zones list:** - - ```python - zones = fr_api.get_zones() - ``` - -### Fetching Detailed Information - -Fetch more information about a specific flight or airport using the following methods: - -- **Flight details:** - - ```python - flight_details = fr_api.get_flight_details(flight) - flight.set_flight_details(flight_details) - - print("Flying to", flight.destination_airport_name) - ``` - -- **Airport details:** - - ```python - airport_details = fr_api.get_airport_details(icao) - ``` - - !!! note - Arrivals and departures can have a limit `flightLimit` (max value is 100) to display. When you need to reach more than 100 flights you can use additional parameter `page` to view other pages. - -## Advanced Usage - -### Fetching Flights Above a Specific Position - -Use the `get_bounds_by_point(...)` method to fetch flights above a specific position. This method takes `latitude` and `longitude` for your position and `radius` for the distance in meters from your position to designate a tracking area. - -```python -# Your point is 52°34'04.7"N 13°16'57.5"E from Google Maps and radius 2km -bounds = fr_api.get_bounds_by_point(52.567967, 13.282644, 2000) - -flights = fr_api.get_flights(bounds = bounds) -``` - -### Filtering Flights and Airports - -Use the `get_flights(...)` method to search for flights by area line, bounds (customized coordinates or obtained by the `get_zones()` method), aircraft registration or aircraft type. - -```python -airline_icao = "UAE" -aircraft_type = "B77W" - -# You may also set a custom region, such as: bounds = "73,-12,-156,38" -zone = fr_api.get_zones()["northamerica"] -bounds = fr_api.get_bounds(zone) - -emirates_flights = fr_api.get_flights( - aircraft_type = aircraft_type, - airline = airline_icao, - bounds = bounds -) -``` - -### Fetching Airport by ICAO or IATA - -```python -lukla_airport = fr_api.get_airport(code = "VNLK", details = True) -``` - -### Calculating Distance Between Flights and Airports - -The `Flight` and `Airport` classes inherit from `Entity`, which contains the `get_distance_from(...)` method. This method returns the distance between the self instance and another entity in kilometers. - -```python -airport = fr_api.get_airport("KJFK") -distance = flight.get_distance_from(airport) - -print(f"The flight is {distance} km away from the airport.") -``` - -### Downloading Flight Data :material-information-outline:{ title="This requires a premium subscription" } - - -```py -history_data = fr_api.get_history_data(flight, file_type="csv", time=1706529600) - - with open("history_data.csv", "w") as file: - file.write(history_data) -``` - -!!! warning inline end - If an invalid time is provided, a blank document will be returned. - -| Parameter | Description | -| ------------- | ------------- | -| `flight_id` | The ID of the flight. This can be obtained from any other function that returns flight details. | -| `file_type` | The format of the file to download. This can be either "CSV" or "KML". | -| `time` | The scheduled time of departure (STD) of the flight in UTC, as a Unix timestamp. | - - -### Setting and Getting Real-time Flight Tracker Parameters - -Set it by using the `set_flight_tracker_config(...)` method. It receives a `FlightTrackerConfig` dataclass instance, but you can also use keyword arguments directly to the method. - -Get the current configuration with the `get_flight_tracker_config()` method, that returns a `FlightTrackerConfig` instance. Note: creating a new `FlightTrackerConfig` instance means resetting all parameters to default. - -```python -flight_tracker = fr_api.get_flight_tracker_config() -flight_tracker.limit = 10 - -fr_api.set_flight_tracker_config(flight_tracker, ...) - -flights = fr_api.get_flights(...) # Returns only 10 flights -``` diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index bf52fc4..0000000 --- a/mkdocs.yml +++ /dev/null @@ -1,63 +0,0 @@ -site_name: FlightRadarAPI -repo_url: https://github.com/JeanExtreme002/FlightRadarAPI -docs_dir: docs -site_description: Documentation for the FlightRadarAPI -site_author: JeanExtreme002 -edit_uri: edit/main/docs/ - -nav: - - Home: index.md - - Python: python.md - - Node.js: nodejs.md - - Go: go.md - - Projects Using FlightRadarAPI: projects.md - -theme: - name: material - logo: assets/logo.png - favicon: assets/favicon.ico - - features: - - navigation.instant - - navigation.instant.progress - - content.code.copy - - content.action.edit - - search.suggest - - search.highlight - - search.share - - content.tooltips - - content.tabs.link - - icon: - repo: fontawesome/brands/github - annotation: material/arrow-right-circle - - palette: - - primary: indigo - - accent: yellow - -extra: - social: - - icon: fontawesome/brands/github - link: https://github.com/JeanExtreme002 - -markdown_extensions: - - admonition - - pymdownx.details - - attr_list - - md_in_html - - abbr - - attr_list - - pymdownx.snippets - - pymdownx.emoji: - emoji_index: !!python/name:material.extensions.emoji.twemoji - emoji_generator: !!python/name:material.extensions.emoji.to_svg - - pymdownx.tabbed: - alternate_style: true - - pymdownx.superfences - -plugins: - - git-committers: - repository: JeanExtreme002/FlightRadarAPI - branch: main - - search \ No newline at end of file diff --git a/nodejs/FlightRadarAPI/index.d.ts b/nodejs/FlightRadarAPI/index.d.ts index 1af0f81..d484ff2 100644 --- a/nodejs/FlightRadarAPI/index.d.ts +++ b/nodejs/FlightRadarAPI/index.d.ts @@ -11,12 +11,16 @@ export interface Zone { } /** - * Options for TLS impersonation. Currently maps Chrome major versions to - * the ciphers/sigalgs/curves the runtime should use. + * TLS impersonation profile. Each field left out keeps the value of the + * bundled Chrome 136 profile. */ export interface ImpersonateOptions { - /** Chrome major version label, e.g. "chrome136". Defaults to the latest supported. */ - profile?: string; + /** OpenSSL cipher names, in the order offered. */ + ciphers?: readonly string[]; + /** OpenSSL signature algorithm names, in the order offered. */ + sigalgs?: readonly string[]; + /** Colon-separated curve names, e.g. "X25519:P-256:P-384". */ + ecdhCurve?: string; } /** diff --git a/nodejs/tests/index.test-d.ts b/nodejs/tests/index.test-d.ts index f7641a4..639d5e7 100644 --- a/nodejs/tests/index.test-d.ts +++ b/nodejs/tests/index.test-d.ts @@ -9,6 +9,9 @@ import { const api = new FlightRadar24API(); expectType(new FlightRadar24API({timeout: 5000, maxWorkers: 4})); +expectType(new FlightRadar24API({ + impersonate: {ciphers: ["TLS_AES_128_GCM_SHA256"], sigalgs: ["rsa_pss_rsae_sha256"], ecdhCurve: "X25519:P-256"}, +})); // getFlights expectType>(api.getFlights()); @@ -70,3 +73,4 @@ expectType(flight.checkInfo({airlineIcao: "TAM"})); expectError(api.getFlights(123)); expectError(api.getBounds({tl_y: 1})); expectError(new FlightRadar24API({timeout: "5000"})); +expectError(new FlightRadar24API({impersonate: {profile: "chrome136"}})); From cebef38c145cb0292bfe151964144161b17633cb Mon Sep 17 00:00:00 2001 From: JeanExtreme002 Date: Sat, 14 Nov 2026 18:00:16 -0300 Subject: [PATCH 2/8] docs: migrate the documentation to Read the Docs Replace MkDocs on GitHub Pages with Sphinx (MyST) and the Read the Docs theme. The per-language pages become one page per topic, with the examples of the three SDKs in synced tabs, plus reference pages for the client methods, Flight, Airport and the countries. Read the Docs publishes the site; the new Docs workflow only checks that the build has no warnings. --- .github/workflows/docs.yml | 47 ++++ .github/workflows/labeler.yml | 2 +- .gitignore | 4 +- .readthedocs.yaml | 17 ++ CONTRIBUTING.md | 24 +- README.md | 3 +- docs/_static/custom.css | 198 ++++++++++++++++ docs/_templates/layout.html | 60 +++++ docs/account.md | 207 +++++++++++++++++ docs/airlines.md | 181 +++++++++++++++ docs/airport.md | 323 ++++++++++++++++++++++++++ docs/airports.md | 293 ++++++++++++++++++++++++ docs/api.md | 285 +++++++++++++++++++++++ docs/client.md | 250 ++++++++++++++++++++ docs/conf.py | 82 +++++++ docs/contributing.md | 5 + docs/countries.md | 310 +++++++++++++++++++++++++ docs/distances.md | 109 +++++++++ docs/errors.md | 250 ++++++++++++++++++++ docs/flight.md | 416 ++++++++++++++++++++++++++++++++++ docs/flights.md | 325 ++++++++++++++++++++++++++ docs/index.md | 286 +++++++++++++++++------ docs/installation.md | 98 ++++++++ docs/license.md | 13 ++ docs/live-data.md | 106 +++++++++ docs/projects.md | 64 +++--- docs/quickstart.md | 175 ++++++++++++++ docs/requirements.txt | 9 + docs/sdk-differences.md | 73 ++++++ docs/searching.md | 71 ++++++ docs/tracker-config.md | 166 ++++++++++++++ docs/zones.md | 205 +++++++++++++++++ go/README.md | 2 +- nodejs/README.md | 2 +- python/Makefile | 27 +-- python/README.md | 4 +- python/pyproject.toml | 2 +- 37 files changed, 4570 insertions(+), 124 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 .readthedocs.yaml create mode 100644 docs/_static/custom.css create mode 100644 docs/_templates/layout.html create mode 100644 docs/account.md create mode 100644 docs/airlines.md create mode 100644 docs/airport.md create mode 100644 docs/airports.md create mode 100644 docs/api.md create mode 100644 docs/client.md create mode 100644 docs/conf.py create mode 100644 docs/contributing.md create mode 100644 docs/countries.md create mode 100644 docs/distances.md create mode 100644 docs/errors.md create mode 100644 docs/flight.md create mode 100644 docs/flights.md create mode 100644 docs/installation.md create mode 100644 docs/license.md create mode 100644 docs/live-data.md create mode 100644 docs/quickstart.md create mode 100644 docs/requirements.txt create mode 100644 docs/sdk-differences.md create mode 100644 docs/searching.md create mode 100644 docs/tracker-config.md create mode 100644 docs/zones.md diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..81fb1d0 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,47 @@ +name: Docs + +# The documentation builds without warnings. Read the Docs publishes it; this +# only checks that a change does not break the build. + +on: + push: + branches: [main] + paths: + - 'docs/**' + - '.readthedocs.yaml' + - 'CONTRIBUTING.md' + - 'LICENSE' + - 'python/FlightRadarAPI/__init__.py' + - '.github/workflows/docs.yml' + pull_request: + paths: + - 'docs/**' + - '.readthedocs.yaml' + - 'CONTRIBUTING.md' + - 'LICENSE' + - 'python/FlightRadarAPI/__init__.py' + - '.github/workflows/docs.yml' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # Warnings (including broken cross-references, via -n) fail the build, the + # same as on Read the Docs. + docs: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.11' + - name: Install docs dependencies + run: pip install -r docs/requirements.txt + - name: Build docs + run: python -m sphinx -n -W --keep-going -b html docs docs/_build/html diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 3d554d4..cb1a6a8 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -84,7 +84,7 @@ jobs: const isDocs = (f) => f.startsWith("docs/") || - f === "mkdocs.yml" || + f === ".readthedocs.yaml" || /\.md$/i.test(f); const isPython = (f) => f.startsWith("python/"); diff --git a/.gitignore b/.gitignore index e7f5f61..86acefa 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ venv/ .mypy_cache/ .pytest_cache/ htmlcov/ -site/ .coverage coverage.xml @@ -34,6 +33,9 @@ coverage.out .DS_Store Thumbs.db +# Docs build output +docs/_build/ + # Misc .env .cache diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..8b6e793 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,17 @@ +# Read the Docs configuration file for Sphinx + MyST. +# See https://docs.readthedocs.io/en/stable/config-file/v2.html + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +sphinx: + configuration: docs/conf.py + fail_on_warning: true + +python: + install: + - requirements: docs/requirements.txt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c061eb2..3b7c1ea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,7 +47,25 @@ is a documented reason not to. Common targets that must stay in sync: - The public surface — `FlightRadar24API` methods (`Client` in Go), the `Countries` enum (`Country` constants in Go), `FlightTrackerConfig` fields, and the `Entity` / `Airport` / `Flight` attributes consumers depend on. - `docs/go.md` documents where the Go surface deliberately differs. + `docs/sdk-differences.md` documents where the Go surface deliberately differs. + +## Documentation + +The documentation lives in `docs/`, written in Markdown (MyST) and built with +Sphinx. It is published on [Read the Docs](https://flightradarapi.readthedocs.io/) +from `main`, and every pull request gets a preview build. + +```bash +pip install -r docs/requirements.txt +python -m sphinx -n -W --keep-going -b html docs docs/_build/html +``` + +`make docs` and `make docs-serve`, in `python/`, do the same. Warnings fail the +build, here and in CI. + +Each page shows its examples in the three languages, in tabs. When a change +reaches the public surface, update the guide that covers it and the reference +pages (`docs/api.md`, `docs/flight.md`, `docs/airport.md`, `docs/countries.md`). ## Style @@ -100,5 +118,5 @@ module was tagged by hand, does what you would expect and nothing else. ## Reporting bugs and asking questions - Bugs: open a GitHub issue with the bug report template. -- Security: see [`SECURITY.md`](SECURITY.md). **Do not** report via GitHub - issues. +- Security: see [`SECURITY.md`](https://github.com/JeanExtreme002/FlightRadarAPI/blob/main/SECURITY.md). + **Do not** report via GitHub issues. diff --git a/README.md b/README.md index 19a4351..b28a9a7 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ This SDK should only be used for your own educational purposes. If you are inter **Official FR24 API**: https://fr24api.flightradar24.com/ +[![Documentation Status](https://readthedocs.org/projects/flightradarapi/badge/?version=latest)](https://flightradarapi.readthedocs.io/?badge=latest) [![Python Package](https://github.com/JeanExtreme002/FlightRadarAPI/workflows/Python%20Package/badge.svg)](https://github.com/JeanExtreme002/FlightRadarAPI/actions) [![Node.js Package](https://github.com/JeanExtreme002/FlightRadarAPI/actions/workflows/node-package.yml/badge.svg)](https://github.com/JeanExtreme002/FlightRadarAPI/actions) [![Go Package](https://github.com/JeanExtreme002/FlightRadarAPI/actions/workflows/go-package.yml/badge.svg)](https://github.com/JeanExtreme002/FlightRadarAPI/actions) @@ -35,7 +36,7 @@ go get github.com/JeanExtreme002/FlightRadarAPI/go@latest ``` ## Documentation -Explore the docs of FlightRadarAPI package, for Python, NodeJS or Go, through [FlightRadarAPI Documentation](https://JeanExtreme002.github.io/FlightRadarAPI/) page. +Explore the docs of FlightRadarAPI package, for Python, NodeJS or Go, through [FlightRadarAPI Documentation](https://flightradarapi.readthedocs.io/) page. ## Project resources **Contributing**: [`CONTRIBUTING.md`](CONTRIBUTING.md)
diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..47aab27 --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,198 @@ +/* FlightRadarAPI docs: tweaks on top of the Read the Docs theme. */ + +/* --- Sidebar logo -------------------------------------------------------- */ +.wy-side-nav-search .wy-dropdown > a img.logo, +.wy-side-nav-search > a img.logo { + width: 72px; + height: 72px; + margin: 0.2rem auto 0.5rem; + padding: 0; + background: transparent; + border-radius: 0; +} + +/* --- Repository link (injected in _templates/layout.html) --------------- */ +/* Scoped under `.wy-menu-vertical a` so it beats the theme's own link rule. */ +.wy-menu-vertical a.repo-link { + display: flex; + align-items: center; + gap: 0.65rem; + margin: 0.6rem 0.8rem 0.4rem; + padding: 0.55rem 0.7rem; + border-radius: 0.35rem; + color: #d9d9d9; + line-height: 1.25; + text-decoration: none; + transition: background 0.15s ease, color 0.15s ease; +} + +.wy-menu-vertical a.repo-link:hover, +.wy-menu-vertical a.repo-link:focus-visible { + background: rgba(255, 255, 255, 0.08); + color: #fff; +} + +.repo-link__icon { + flex-shrink: 0; +} + +.repo-link__text { + display: flex; + flex-direction: column; + min-width: 0; +} + +.repo-link__name { + overflow: hidden; + font-size: 0.8rem; + font-weight: 600; + text-overflow: ellipsis; + white-space: nowrap; +} + +.repo-link__stats { + display: flex; + gap: 0.75rem; + margin-top: 0.15rem; + font-size: 0.72rem; + opacity: 0.75; +} + +.repo-link__stats[hidden] { + display: none; +} + +.repo-link__stat { + display: inline-flex; + align-items: center; + gap: 0.25rem; +} + +/* The theme's script injects an expand button into every sidebar link. */ +.repo-link button.toctree-expand { + display: none; +} + +/* --- Badge table on the home page ---------------------------------------- */ +.badge-table { + margin-bottom: 1.5rem; + border-collapse: collapse; +} + +.badge-table th, +.badge-table td { + padding: 0.4rem 0.75rem; + border: 1px solid #e1e4e5; + vertical-align: middle; +} + +.badge-table th { + background: #f3f6f6; + text-align: center; +} + +.badge-table td a { + display: inline-block; + margin: 0.1rem 0.15rem; + line-height: 0; +} + +/* --- Home page hero ------------------------------------------------------ */ +/* The hero stands for the page title, which stays for the browser tab and + the navigation but isn't shown. */ +section#flightradarapi > h1 { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0 0 0 0); +} + +.rst-content .hero { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; + margin: 0.5rem 0 2rem; +} + +.rst-content .hero img { + width: 84px; + height: 84px; +} + +.rst-content .hero span { + font-family: "Roboto Slab", ff-tisa-web-pro, Georgia, Arial, sans-serif; + font-size: 2.6rem; + font-weight: 700; + line-height: 1; + letter-spacing: -0.02em; + color: #404040; +} + +.rst-content .hero span em { + font-style: normal; + color: #2980b9; +} + +.rst-content h2.tagline { + margin: 0 0 1rem; +} + +@media (max-width: 480px) { + .rst-content .hero img { + width: 56px; + height: 56px; + } + + .rst-content .hero span { + font-size: 1.8rem; + } +} + +/* --- Language tabs (sphinx-design) --------------------------------------- */ +.rst-content .sd-tab-set { + margin-bottom: 1.5rem; +} + +.rst-content .sd-tab-set > label { + padding: 0.6rem 1rem 0.5rem; + font-size: 0.9rem; +} + +.rst-content .sd-tab-set > input:checked + label { + border-color: #2980b9; + color: #2980b9; +} + +.rst-content .sd-tab-set > input:not(:checked) + label:hover { + color: #2980b9; +} + +.rst-content .sd-tab-content { + padding: 0.9rem 0 0; + box-shadow: 0 -0.0625rem #e1e4e5; +} + +.rst-content .sd-tab-content > div[class^="highlight"]:last-child, +.rst-content .sd-tab-content > p:last-child { + margin-bottom: 0; +} + +/* --- Wide reference tables ----------------------------------------------- */ +/* The theme never wraps table cells, which pushes three-column name tables + off the page. */ +.rst-content table.docutils td, +.rst-content table.docutils th { + white-space: normal; +} + +.rst-content table.docutils td code { + white-space: nowrap; +} + +/* --- Content width -------------------------------------------------------- */ +/* Room for the reference tables; the theme's 800px is tight for them. */ +.wy-nav-content { + max-width: 960px; +} diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html new file mode 100644 index 0000000..abd9d3f --- /dev/null +++ b/docs/_templates/layout.html @@ -0,0 +1,60 @@ +{#- Extend the Read the Docs theme with a link to the GitHub repository at the + top of the sidebar, on every page: the repo name plus its live star and fork + counts, instead of a "please star" button. The counts come from the public + GitHub API; if that fails (offline, rate limit) only the name is shown. -#} +{% extends "!layout.html" %} + +{% block menu %} + + + + {{ github_user }}/{{ github_repo }} + + + + + {{ super() }} +{% endblock %} diff --git a/docs/account.md b/docs/account.md new file mode 100644 index 0000000..3812752 --- /dev/null +++ b/docs/account.md @@ -0,0 +1,207 @@ +# Login, Bookmarks and Flight History + +Everything else in this documentation works without an account. Logging in to +your own FlightRadar24 account adds what belongs to it: your bookmarks and, with +a premium subscription, the history of a flight. + +## Logging in + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +from FlightRadarAPI import FlightRadar24API + +fr_api = FlightRadar24API() +fr_api.login("your@email.com", "your-password") +``` + +The credentials can also be given to the constructor, which then logs in right +away: + +```python +fr_api = FlightRadar24API(user="your@email.com", password="your-password") +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const { FlightRadar24API } = require("flightradarapi"); + +const frApi = new FlightRadar24API(); +await frApi.login("your@email.com", "your-password"); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +client := flightradarapi.New() + +if err := client.Login(ctx, "your@email.com", "your-password"); err != nil { + log.Fatal(err) +} +``` +::: +:::: + +A wrong email or password fails with `LoginError` (`ErrLogin` in Go), carrying +the message FlightRadar24 answered with. + +Once logged in, the flight feed and the airport details are requested with your +session too. + +## Session state + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +fr_api.is_logged_in() # True + +user = fr_api.get_login_data() # dict with the data of your account + +fr_api.logout() # True when the server confirmed it +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +frApi.isLoggedIn(); // true + +const user = frApi.getLoginData(); // object with the data of your account + +await frApi.logout(); // true when the server confirmed it +``` +::: + +:::{tab-item} Go +:sync: go + +```go +client.IsLoggedIn() // true + +user, err := client.GetLoginData() // map[string]any with the data of your account + +confirmed, err := client.Logout(ctx) // true when the server confirmed it +``` +::: +:::: + +- `get_login_data` returns a copy of the account data FlightRadar24 sent at + login. Without a session it fails with `LoginError`. +- `logout` always ends the local session and clears its cookies. It returns + whether the server confirmed the logout, and `true` when there was no session + to end. +- Logging in again replaces the current session. + +## Bookmarks + +The bookmarks saved in your account, as FlightRadar24 sends them. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +bookmarks = fr_api.get_bookmarks() # dict +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const bookmarks = await frApi.getBookmarks(); // object +``` +::: + +:::{tab-item} Go +:sync: go + +```go +bookmarks, err := client.GetBookmarks(ctx) // map[string]any +``` +::: +:::: + +## Flight history + +```{important} +Downloading the history of a flight requires a FlightRadar24 premium +subscription. +``` + +`get_history_data` downloads the track of a flight as a CSV or KML document. + +| Parameter | Meaning | +|---|---| +| flight | The flight to download, as a `Flight` from any call that returns flights. | +| file type | `"CSV"` or `"KML"`, in any letter case. | +| timestamp | The scheduled time of departure (STD) of the flight in UTC, as a Unix timestamp. | + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +history_data = fr_api.get_history_data(flight, file_type="CSV", timestamp=1706529600) + +with open("history_data.csv", "w") as file: + file.write(history_data) +``` + +The document is returned as a `str`. +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const fs = require("fs"); + +const historyData = await frApi.getHistoryData(flight, "CSV", 1706529600); + +fs.writeFileSync("history_data.csv", Buffer.from(historyData)); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +historyData, err := client.GetHistoryData(ctx, flight, "CSV", 1706529600) + +if err != nil { + log.Fatal(err) +} + +err = os.WriteFile("history_data.csv", []byte(historyData), 0o644) +``` + +The document is returned as a `string`. +::: +:::: + +```{warning} +If an invalid time is provided, a blank document is returned. +``` + +Calling it without a session fails with `LoginError`. Any file type other than +CSV or KML is rejected before the request: with `ValueError` in Python, +`TypeError` in Node.js and an error in Go. diff --git a/docs/airlines.md b/docs/airlines.md new file mode 100644 index 0000000..859e493 --- /dev/null +++ b/docs/airlines.md @@ -0,0 +1,181 @@ +# Airlines, Logos and Flags + +## Listing the airlines + +`get_airlines` returns every airline FlightRadar24 knows, with its name and +codes. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +airlines = fr_api.get_airlines() + +print(airlines[0]) +# e.g. {"Name": "TAP", "ICAO": "TAP", "IATA": "TP", "n_aircrafts": None} +``` + +Each airline is a dictionary with the keys `Name`, `ICAO`, `IATA` and +`n_aircrafts`. +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const airlines = await frApi.getAirlines(); + +console.log(airlines[0]); +// e.g. { Name: "TAP", ICAO: "TAP", IATA: "TP", n_aircrafts: null } +``` + +Each airline is an object with the keys `Name`, `ICAO`, `IATA` and +`n_aircrafts`. +::: + +:::{tab-item} Go +:sync: go + +```go +airlines, err := client.GetAirlines(ctx) + +if err != nil { + log.Fatal(err) +} + +fmt.Println(airlines[0].Name, airlines[0].ICAO, airlines[0].IATA) +``` + +Each airline is an `Airline`, with the fields `Name`, `ICAO`, `IATA` and +`NumAircrafts` (a `*int`). Marshalled to JSON, it uses the same keys as the +other SDKs: `Name`, `ICAO`, `IATA` and `n_aircrafts`. +::: +:::: + +| Key | Meaning | +|---|---| +| `Name` | Name of the airline | +| `ICAO` | ICAO code (3 characters), when the airline has one | +| `IATA` | IATA code (2 characters), when the airline has one | +| `n_aircrafts` | Size of the fleet. FlightRadar24 only publishes it for the airlines with the largest fleets; it is empty for the others. | + +The ICAO code is what the [flight search](flights.md#filtering) takes as its +airline filter. + +## Airline logos + +`get_airline_logo` downloads the logo of an airline, given its IATA and ICAO +codes. The result is the image bytes and the file extension, or nothing when +FlightRadar24 has no logo for that airline. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +logo = fr_api.get_airline_logo("EK", "UAE") + +if logo is not None: + data, extension = logo + + with open(f"emirates.{extension}", "wb") as file: + file.write(data) +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const fs = require("fs"); + +const logo = await frApi.getAirlineLogo("EK", "UAE"); + +if (logo !== null) { + const [data, extension] = logo; + + fs.writeFileSync(`emirates.${extension}`, Buffer.from(data)); +} +``` +::: + +:::{tab-item} Go +:sync: go + +```go +logo, err := client.GetAirlineLogo(ctx, "EK", "UAE") + +if err != nil { + log.Fatal(err) +} + +if logo != nil { + err = os.WriteFile("emirates."+logo.Extension, logo.Data, 0o644) +} +``` + +The result is an `*Image`, with the fields `Data` and `Extension`. It is `nil`, +with a `nil` error, when there is no logo. +::: +:::: + +## Country flags + +`get_country_flag` downloads the flag of a country, given its name. It returns +the same shape as the logo, or nothing when FlightRadar24 has no such flag. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +flag = fr_api.get_country_flag("Brazil") + +if flag is not None: + data, extension = flag + + with open(f"brazil.{extension}", "wb") as file: + file.write(data) +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const flag = await frApi.getCountryFlag("Brazil"); + +if (flag !== null) { + const [data, extension] = flag; + + fs.writeFileSync(`brazil.${extension}`, Buffer.from(data)); +} +``` +::: + +:::{tab-item} Go +:sync: go + +```go +flag, err := client.GetCountryFlag(ctx, "Brazil") + +if err != nil { + log.Fatal(err) +} + +if flag != nil { + err = os.WriteFile("brazil."+flag.Extension, flag.Data, 0o644) +} +``` +::: +:::: + +The name is matched loosely: `"United States"`, `"united-states"` and the +country of an airport (`airport.country`) all work. diff --git a/docs/airport.md b/docs/airport.md new file mode 100644 index 0000000..c8df1d8 --- /dev/null +++ b/docs/airport.md @@ -0,0 +1,323 @@ +# Airport + +An `Airport` is an [`Entity`](distances.md), so it has a position and can +measure its distance to a flight or to another airport. + +How much an airport carries depends on the call that produced it. The columns +of the tables below say which: + +| Column | Call | +|---|---| +| **List** | [Airports of a country](airports.md#airports-of-a-country): `get_airports` | +| **Code** | [One airport by code](airports.md#one-airport-by-code): `get_airport` | +| **Details** | [Airport details](airports.md#airport-details): `get_airport` with details, or `set_airport_details` | + +The names follow the convention of each language. What Python calls +`country_code` is `countryCode` in Node.js and `CountryCode` in Go. + +## Attributes + +### Identity and location + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Attribute | Description | List | Code | Details | +|---|---|:-:|:-:|:-:| +| `name` | Name of the airport | ✓ | ✓ | ✓ | +| `icao` | ICAO code | ✓ | ✓ | ✓ | +| `iata` | IATA code | ✓ | ✓ | ✓ | +| `country` | Name of the country | ✓ | ✓ | ✓ | +| `latitude` | Latitude, in degrees | empty | ✓ | ✓ | +| `longitude` | Longitude, in degrees | empty | ✓ | ✓ | +| `altitude` | Elevation of the airport, in feet | empty | ✓ | ✓ | +| `country_code` | Code of the country | | ✓ | ✓ | +| `city` | City of the airport | | ✓ | ✓ | +| `country_id` | FlightRadar24 identifier of the country | | | ✓ | +::: + +:::{tab-item} Node.js +:sync: node + +| Property | Description | List | Code | Details | +|---|---|:-:|:-:|:-:| +| `name` | Name of the airport | ✓ | ✓ | ✓ | +| `icao` | ICAO code | ✓ | ✓ | ✓ | +| `iata` | IATA code | ✓ | ✓ | ✓ | +| `country` | Name of the country | ✓ | ✓ | ✓ | +| `latitude` | Latitude, in degrees | empty | ✓ | ✓ | +| `longitude` | Longitude, in degrees | empty | ✓ | ✓ | +| `altitude` | Elevation of the airport, in feet | empty | ✓ | ✓ | +| `countryCode` | Code of the country | | ✓ | ✓ | +| `city` | City of the airport | | ✓ | ✓ | +| `countryId` | FlightRadar24 identifier of the country | | | ✓ | +::: + +:::{tab-item} Go +:sync: go + +| Field | Description | List | Code | Details | +|---|---|:-:|:-:|:-:| +| `Name` | Name of the airport | ✓ | ✓ | ✓ | +| `ICAO` | ICAO code | ✓ | ✓ | ✓ | +| `IATA` | IATA code | ✓ | ✓ | ✓ | +| `Country` | Name of the country | ✓ | ✓ | ✓ | +| `Latitude` | Latitude, in degrees | empty | ✓ | ✓ | +| `Longitude` | Longitude, in degrees | empty | ✓ | ✓ | +| `Altitude` | Elevation of the airport, in feet | empty | ✓ | ✓ | +| `CountryCode` | Code of the country | | ✓ | ✓ | +| `City` | City of the airport | | ✓ | ✓ | +| `CountryID` | FlightRadar24 identifier of the country | | | ✓ | +::: + +:::: + +### Timezone + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Attribute | Description | List | Code | Details | +|---|---|:-:|:-:|:-:| +| `timezone_name` | Name of the timezone, such as `"Asia/Kathmandu"` | | ✓ | ✓ | +| `timezone_abbr` | Abbreviation of the timezone | | ✓ | ✓ | +| `timezone_abbr_name` | Name the abbreviation stands for | | ✓ | ✓ | +| `timezone_offset` | Offset from UTC, in seconds | | ✓ | ✓ | +| `timezone_offset_hours` | Offset from UTC, as text in hours | | ✓ | ✓ | +::: + +:::{tab-item} Node.js +:sync: node + +| Property | Description | List | Code | Details | +|---|---|:-:|:-:|:-:| +| `timezoneName` | Name of the timezone, such as `"Asia/Kathmandu"` | | ✓ | ✓ | +| `timezoneAbbr` | Abbreviation of the timezone | | ✓ | ✓ | +| `timezoneAbbrName` | Name the abbreviation stands for | | ✓ | ✓ | +| `timezoneOffset` | Offset from UTC, in seconds | | ✓ | ✓ | +| `timezoneOffsetHours` | Offset from UTC, as text in hours | | ✓ | ✓ | +::: + +:::{tab-item} Go +:sync: go + +| Field | Description | List | Code | Details | +|---|---|:-:|:-:|:-:| +| `TimezoneName` | Name of the timezone, such as `"Asia/Kathmandu"` | | ✓ | ✓ | +| `TimezoneAbbr` | Abbreviation of the timezone | | ✓ | ✓ | +| `TimezoneAbbrName` | Name the abbreviation stands for | | ✓ | ✓ | +| `TimezoneOffset` | Offset from UTC, in seconds | | ✓ | ✓ | +| `TimezoneOffsetHours` | Offset from UTC, as text in hours | | ✓ | ✓ | +::: + +:::: + +### Links + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Attribute | Description | List | Code | Details | +|---|---|:-:|:-:|:-:| +| `website` | Website of the airport | | ✓ | ✓ | +| `visible` | The `visible` flag FlightRadar24 sends for the airport | | ✓ | ✓ | +| `wikipedia` | Wikipedia page of the airport | | | ✓ | +| `images` | Photos of the airport, as FlightRadar24 sends them | | | ✓ | +::: + +:::{tab-item} Node.js +:sync: node + +| Property | Description | List | Code | Details | +|---|---|:-:|:-:|:-:| +| `website` | Website of the airport | | ✓ | ✓ | +| `visible` | The `visible` flag FlightRadar24 sends for the airport | | ✓ | ✓ | +| `wikipedia` | Wikipedia page of the airport | | | ✓ | +| `images` | Photos of the airport, as FlightRadar24 sends them | | | ✓ | +::: + +:::{tab-item} Go +:sync: go + +| Field | Description | List | Code | Details | +|---|---|:-:|:-:|:-:| +| `Website` | Website of the airport | | ✓ | ✓ | +| `Visible` | The `visible` flag FlightRadar24 sends for the airport | | ✓ | ✓ | +| `Wikipedia` | Wikipedia page of the airport | | | ✓ | +| `Images` | Photos of the airport, as FlightRadar24 sends them | | | ✓ | +::: + +:::: + +### Operations + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Attribute | Description | List | Code | Details | +|---|---|:-:|:-:|:-:| +| `runways` | Runways of the airport | | | ✓ | +| `weather` | Current weather at the airport | | | ✓ | +| `arrivals` | Arrivals of the airport schedule | | | ✓ | +| `departures` | Departures of the airport schedule | | | ✓ | +| `aircraft_on_ground` | Number of aircraft on the ground | | | ✓ | +| `aircraft_visible_on_ground` | Number of those FlightRadar24 can see | | | ✓ | +::: + +:::{tab-item} Node.js +:sync: node + +| Property | Description | List | Code | Details | +|---|---|:-:|:-:|:-:| +| `runways` | Runways of the airport | | | ✓ | +| `weather` | Current weather at the airport | | | ✓ | +| `arrivals` | Arrivals of the airport schedule | | | ✓ | +| `departures` | Departures of the airport schedule | | | ✓ | +| `aircraftOnGround` | Number of aircraft on the ground | | | ✓ | +| `aircraftVisibleOnGround` | Number of those FlightRadar24 can see | | | ✓ | +::: + +:::{tab-item} Go +:sync: go + +| Field | Description | List | Code | Details | +|---|---|:-:|:-:|:-:| +| `Runways` | Runways of the airport | | | ✓ | +| `Weather` | Current weather at the airport | | | ✓ | +| `Arrivals` | Arrivals of the airport schedule | | | ✓ | +| `Departures` | Departures of the airport schedule | | | ✓ | +| `AircraftOnGround` | Number of aircraft on the ground | | | ✓ | +| `AircraftVisibleOnGround` | Number of those FlightRadar24 can see | | | ✓ | +::: + +:::: + +### Reviews + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Attribute | Description | List | Code | Details | +|---|---|:-:|:-:|:-:| +| `reviews_url` | Address of the reviews page on FlightRadar24 | | | ✓ | +| `reviews` | Number of reviews | | | ✓ | +| `evaluation` | Evaluation of the airport | | | ✓ | +| `average_rating` | Average rating | | | ✓ | +| `total_rating` | Number of ratings | | | ✓ | +::: + +:::{tab-item} Node.js +:sync: node + +| Property | Description | List | Code | Details | +|---|---|:-:|:-:|:-:| +| `reviewsUrl` | Address of the reviews page on FlightRadar24 | | | ✓ | +| `reviews` | Number of reviews | | | ✓ | +| `evaluation` | Evaluation of the airport | | | ✓ | +| `averageRating` | Average rating | | | ✓ | +| `totalRating` | Number of ratings | | | ✓ | +::: + +:::{tab-item} Go +:sync: go + +| Field | Description | List | Code | Details | +|---|---|:-:|:-:|:-:| +| `ReviewsURL` | Address of the reviews page on FlightRadar24 | | | ✓ | +| `Reviews` | Number of reviews | | | ✓ | +| `Evaluation` | Evaluation of the airport | | | ✓ | +| `AverageRating` | Average rating | | | ✓ | +| `TotalRating` | Number of ratings | | | ✓ | +::: + +:::: + +`runways`, `weather`, `arrivals`, `departures` and `images` are kept as +FlightRadar24 sends them. How many flights the schedule lists is set by the +flight limit and the page of +[`get_airport_details`](airports.md#the-raw-details-payload). + +## Missing values + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +An attribute whose column is blank **does not exist** on that airport: reading +it raises `AttributeError`. Use `getattr(airport, "city", None)` when the +airport may come from more than one call. + +On a listed airport, `latitude`, `longitude` and `altitude` are `None`. + +On the other two, a value FlightRadar24 left out is `None` or the string +`"N/A"`, depending on the attribute and on the call. The collections are empty +instead: `runways` is `[]`, and `weather`, `arrivals`, +`departures` and `images` are `{}`. +::: + +:::{tab-item} Node.js +:sync: node + +A property whose column is blank is `undefined` on that airport. + +On a listed airport, `latitude`, `longitude` and `altitude` are `null`. + +On the other two, a value FlightRadar24 left out is `null` or the string +`"N/A"`, depending on the property and on the call. The collections are empty +instead: `runways` is `[]`, and `weather`, `arrivals`, +`departures` and `images` are `{}`. +::: + +:::{tab-item} Go +:sync: go + +Every field exists on every airport. One that the call did not fill in, or that +FlightRadar24 left out, holds its zero value: `""` for text, `nil` for the +numbers and for `Visible`, which are pointers, and empty for the maps and +slices. + +The numbers are `*float64`, `Visible` is `*bool`, `Runways` is `[]any`, and +`Weather`, `Arrivals`, `Departures` and `Images` are `map[string]any`. + +`RawDetails` holds the payload the airport was built from, for what the struct +does not name. It is `nil` on a listed airport. +::: +:::: + +## Methods + +| Python | Node.js | Go | Description | +|---|---|---|---| +| `set_airport_details(details)` | `setAirportDetails(details)` | `SetAirportDetails(details)` | Fill the airport in from a details payload | +| `get_distance_from(entity)` | `getDistanceFrom(entity)` | `GetDistanceFrom(other)` | Distance to another entity, in kilometers. See [Distances](distances.md). | + +## Building an airport yourself + +The client builds the airports it returns. There is one constructor for each of +the three payloads an airport can come from. + +| Payload | Python | Node.js | Go | +|---|---|---|---| +| A row with `name`, `icao`, `iata`, `lat`, `lon`, `alt` and `country` | `Airport.from_basic_info(row)` | `Airport.fromBasicInfo(row)` | `NewAirportFromBasicInfo(row)` | +| The `details` block of an airport | `Airport.from_info(info)` | `Airport.fromInfo(info)` | `NewAirportFromInfo(info)` | +| A [details payload](airports.md#the-raw-details-payload) | `Airport.from_details(details)` | `Airport.fromDetails(details)` | `NewAirportFromDetails(details)` | + +An empty airport, to be filled in later with `set_airport_details`, is +`Airport()` in Python, `new Airport()` in Node.js and `NewAirport()` in Go. diff --git a/docs/airports.md b/docs/airports.md new file mode 100644 index 0000000..ac98e08 --- /dev/null +++ b/docs/airports.md @@ -0,0 +1,293 @@ +# Airports + +There are three ways to reach airports, and each returns a different amount of +information: + +| Call | Requests | What the airport carries | +|---|---|---| +| [One airport by code](#one-airport-by-code) | 1 | Name, codes, position, altitude, country, city, timezone | +| [One airport, with details](#airport-details) | 1 | All of the above, plus runways, weather, schedule, ratings and more | +| [Airports of a country](#airports-of-a-country) | 1 or more per country | Name, codes and country only | + +The [Airport reference](airport.md) lists every attribute and where it comes +from. + +## One airport by code + +Airports are looked up by their IATA code (3 letters) or ICAO code (4 letters). + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +airport = fr_api.get_airport("VNLK") + +print(airport.name, airport.city, airport.country) +print(airport.latitude, airport.longitude, airport.altitude) +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const airport = await frApi.getAirport("VNLK"); + +console.log(airport.name, airport.city, airport.country); +console.log(airport.latitude, airport.longitude, airport.altitude); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +airport, err := client.GetAirport(ctx, "VNLK", false) + +if errors.Is(err, flightradarapi.ErrAirportNotFound) { + fmt.Println("no such airport") + return +} +if err != nil { + log.Fatal(err) +} + +fmt.Println(airport.Name, airport.City, airport.Country) +fmt.Println(*airport.Latitude, *airport.Longitude, *airport.Altitude) +``` + +`Latitude`, `Longitude` and `Altitude` are pointers, `nil` when FlightRadar24 +sent no value. +::: +:::: + +A code that no airport answers to fails with `AirportNotFoundError` +(`ErrAirportNotFound` in Go). A code that is not 3 or 4 characters long is +rejected before any request is made. See [Errors and retries](errors.md). + +## Airport details + +Ask for the details to get the whole picture of an airport in one object. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +airport = fr_api.get_airport("VNLK", details=True) + +print(airport.timezone_name, airport.website) +print(len(airport.runways), "runways") +print(airport.weather) +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const airport = await frApi.getAirport("VNLK", true); + +console.log(airport.timezoneName, airport.website); +console.log(airport.runways.length, "runways"); +console.log(airport.weather); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +airport, err := client.GetAirport(ctx, "VNLK", true) + +if err != nil { + log.Fatal(err) +} + +fmt.Println(airport.TimezoneName, airport.Website) +fmt.Println(len(airport.Runways), "runways") +fmt.Println(airport.Weather) +``` +::: +:::: + +### The raw details payload + +`get_airport_details` returns the payload itself, as FlightRadar24 sends it. It +takes two more parameters, which control the arrivals and departures of the +airport schedule: + +| Parameter | Default | Meaning | +|---|---|---| +| flight limit | 100 | Number of flights listed in the schedule. The maximum is 100. | +| page | 1 | Page of the schedule, to go past the first 100 flights. | + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +details = fr_api.get_airport_details("KJFK", flight_limit=50, page=2) + +arrivals = details["airport"]["pluginData"]["schedule"]["arrivals"] +``` + +The payload can be turned into an `Airport` at any time: + +```python +from FlightRadarAPI import Airport + +airport = Airport.from_details(details) + +# Or, on an airport you already have +airport.set_airport_details(details) +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const details = await frApi.getAirportDetails("KJFK", 50, 2); + +const arrivals = details["airport"]["pluginData"]["schedule"]["arrivals"]; +``` + +The payload can be turned into an `Airport` at any time: + +```javascript +const { Airport } = require("flightradarapi"); + +const airport = Airport.fromDetails(details); + +// Or, on an airport you already have +airport.setAirportDetails(details); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +details, err := client.GetAirportDetails(ctx, "KJFK", 50, 2) +``` + +Zero means the default: `GetAirportDetails(ctx, "KJFK", 0, 0)` asks for 100 +flights of the first page. + +The payload can be turned into an `Airport` at any time: + +```go +airport := flightradarapi.NewAirportFromDetails(details) + +// Or, on an airport you already have +airport.SetAirportDetails(details) +``` + +`airport.RawDetails` keeps the payload the airport was built from, for the +fields the struct does not name. +::: +:::: + +A flight limit above 100 is refused by FlightRadar24, and the SDK reports its +message: as a `ValueError` in Python, a `RangeError` in Node.js and an error in +Go. + +## Airports of a country + +`get_airports` lists the airports of one or more countries, or of the whole +world. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +from FlightRadarAPI import Countries + +airports = fr_api.get_airports([Countries.BRAZIL, Countries.UNITED_STATES]) + +# A single country needs no list +airports = fr_api.get_airports(Countries.BRAZIL) + +# Every airport +all_airports = fr_api.get_airports() +``` + +Countries can also be given by name or by slug: `"United States"` and +`"united-states"` are the same as `Countries.UNITED_STATES`. Any iterable is +accepted. +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const { Countries } = require("flightradarapi"); + +let airports = await frApi.getAirports([Countries.BRAZIL, Countries.UNITED_STATES]); + +// A single country needs no array +airports = await frApi.getAirports(Countries.BRAZIL); + +// Every airport +const allAirports = await frApi.getAirports(); +``` + +Countries can also be given by name or by slug: `"United States"` and +`"united-states"` are the same as `Countries.UNITED_STATES`. Any iterable is +accepted. +::: + +:::{tab-item} Go +:sync: go + +```go +airports, err := client.GetAirports(ctx, []flightradarapi.Country{ + flightradarapi.CountryBrazil, + flightradarapi.CountryUnitedStates, +}) + +// Every airport +allAirports, err := client.GetAirports(ctx, nil) +``` + +Countries can also be given by name or by slug: +`flightradarapi.Country("United States")` and +`flightradarapi.Country("united-states")` are the same as `CountryUnitedStates`. +`AllCountries()` enumerates the constants. +::: +:::: + +The [Countries reference](countries.md) lists every country. + +A few things to know about the listing: + +- **It has no coordinates.** A listed airport carries its name, IATA and ICAO + codes and country; latitude, longitude and altitude are empty. Fetch + [one airport by code](#one-airport-by-code) when you need its position. +- **It costs one request per page.** FlightRadar24 lists airports one page per + country, and one per state for the largest countries. A few countries take a + handful of requests; every airport takes a few hundred. They run several at a + time (8 by default, see [Configuration](client.md#concurrency)). +- **The order follows your request.** Airports come grouped by country, in the + order the countries were asked for. +- **An empty selection returns nothing.** An empty list is not the same as no + argument: it selects no country, and makes no request. +- **An unknown country is skipped.** It adds no airports and a warning is + logged (see [Logging](client.md#logging)). + +## See also + +- [Airport reference](airport.md): every attribute of an airport. +- [Airport disruptions](live-data.md#airport-disruptions): delays and cancellations by airport. +- [Distances](distances.md): how far an airport is from a flight, or from another airport. diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..d71b17a --- /dev/null +++ b/docs/api.md @@ -0,0 +1,285 @@ +# Client Methods + +The client is the entry point of each SDK: `FlightRadar24API` in Python and +Node.js, `Client` in Go. Its methods are the same in the three languages, apart +from the naming convention of each. + +This page lists every one of them. The linked guides explain them with examples. + +## Signatures + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +class FlightRadar24API: + timeout: int # seconds + max_workers: int + + def __init__(self, user=None, password=None, timeout=30, max_workers=8, + impersonate=None, retry=None) + + # Flights + def get_flights(self, airline=None, bounds=None, registration=None, + aircraft_type=None, *, details=False) -> list[Flight] + def get_flight_details(self, flight: Flight) -> dict + def get_flight_tracker_config(self) -> FlightTrackerConfig + def set_flight_tracker_config(self, flight_tracker_config=None, **config) -> None + + # Zones and bounds + def get_zones(self) -> dict + def get_bounds(self, zone: dict) -> str + def get_bounds_by_point(self, latitude: float, longitude: float, radius: float) -> str + + # Airports + def get_airport(self, code: str, *, details=False) -> Airport + def get_airport_details(self, code: str, flight_limit=100, page=1) -> dict + def get_airports(self, countries=None) -> list[Airport] + def get_airport_disruptions(self) -> dict + + # Airlines and images + def get_airlines(self) -> list[dict] + def get_airline_logo(self, iata: str, icao: str) -> tuple[bytes, str] | None + def get_country_flag(self, country: str) -> tuple[bytes, str] | None + + # More data + def search(self, query: str, limit=50) -> dict + def get_most_tracked(self) -> dict + def get_volcanic_eruptions(self) -> dict + + # Account + def login(self, user: str, password: str) -> None + def logout(self) -> bool + def is_logged_in(self) -> bool + def get_login_data(self) -> dict + def get_bookmarks(self) -> dict + def get_history_data(self, flight: Flight, file_type: str, timestamp: int) -> str +``` + +`details` is keyword-only in `get_flights` and `get_airport`. +::: + +:::{tab-item} Node.js +:sync: node + +```typescript +class FlightRadar24API { + timeout: number; // milliseconds + maxWorkers: number; + + constructor(options?: { timeout?: number; maxWorkers?: number; impersonate?: object; retry?: RetryPolicy }); + + // Flights + getFlights(airline?: string | null, bounds?: string | null, registration?: string | null, + aircraftType?: string | null, details?: boolean): Promise; + getFlightDetails(flight: Flight): Promise; + getFlightTrackerConfig(): FlightTrackerConfig; + setFlightTrackerConfig(flightTrackerConfig: FlightTrackerConfig | null, config?: object): void; + + // Zones and bounds + getZones(): object; + getBounds(zone: Zone): string; + getBoundsByPoint(latitude: number, longitude: number, radius: number): string; + + // Airports + getAirport(code: string, details?: boolean): Promise; + getAirportDetails(code: string, flightLimit?: number, page?: number): Promise; + getAirports(countries?: Iterable | string): Promise; + getAirportDisruptions(): Promise; + + // Airlines and images + getAirlines(): Promise; + getAirlineLogo(iata: string, icao: string): Promise<[ArrayBuffer, string] | null>; + getCountryFlag(country: string): Promise<[ArrayBuffer, string] | null>; + + // More data + search(query: string, limit?: number): Promise>; + getMostTracked(): Promise; + getVolcanicEruptions(): Promise; + + // Account + login(user: string, password: string): Promise; + logout(): Promise; + isLoggedIn(): boolean; + getLoginData(): object; + getBookmarks(): Promise; + getHistoryData(flight: Flight, fileType: "CSV" | "KML", timestamp: number): Promise; +} +``` + +The defaults are the same as in Python: `flightLimit = 100`, `page = 1`, +`limit = 50`, `details = false`. +::: + +:::{tab-item} Go +:sync: go + +```go +func New(options ...Options) *Client + +type Client struct { + Timeout time.Duration + MaxWorkers int +} + +// Flights +func (c *Client) GetFlights(ctx context.Context, search FlightSearch) ([]*Flight, error) +func (c *Client) GetFlightDetails(ctx context.Context, flight *Flight) (map[string]any, error) +func (c *Client) GetFlightTrackerConfig() FlightTrackerConfig +func (c *Client) SetFlightTrackerConfig(config *FlightTrackerConfig, values map[string]string) error + +// Zones and bounds +func (c *Client) GetZones() map[string]Zone +func (c *Client) GetBounds(zone Zone) string +func (c *Client) GetBoundsByPoint(latitude, longitude, radius float64) string + +// Airports +func (c *Client) GetAirport(ctx context.Context, code string, details bool) (*Airport, error) +func (c *Client) GetAirportDetails(ctx context.Context, code string, flightLimit, page int) (map[string]any, error) +func (c *Client) GetAirports(ctx context.Context, countries []Country) ([]*Airport, error) +func (c *Client) GetAirportDisruptions(ctx context.Context) (map[string]any, error) + +// Airlines and images +func (c *Client) GetAirlines(ctx context.Context) ([]Airline, error) +func (c *Client) GetAirlineLogo(ctx context.Context, iata, icao string) (*Image, error) +func (c *Client) GetCountryFlag(ctx context.Context, country string) (*Image, error) + +// More data +func (c *Client) Search(ctx context.Context, query string, limit int) (map[string][]any, error) +func (c *Client) GetMostTracked(ctx context.Context) (map[string]any, error) +func (c *Client) GetVolcanicEruptions(ctx context.Context) (map[string]any, error) + +// Account +func (c *Client) Login(ctx context.Context, user, password string) error +func (c *Client) Logout(ctx context.Context) (bool, error) +func (c *Client) IsLoggedIn() bool +func (c *Client) GetLoginData() (map[string]any, error) +func (c *Client) GetBookmarks(ctx context.Context) (map[string]any, error) +func (c *Client) GetHistoryData(ctx context.Context, flight *Flight, fileType string, timestamp int64) (string, error) +``` + +Where the other SDKs have a default argument, zero means that default: +`flightLimit` 100, `page` 1, `limit` 50. The full reference, generated from the +source, is on +[pkg.go.dev](https://pkg.go.dev/github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi). +::: +:::: + +## Flights + +| Python | Node.js | Go | Description | +|---|---|---|---| +| `get_flights` | `getFlights` | `GetFlights` | Flights of the live feed, optionally filtered. See [Live flights](flights.md). | +| `get_flight_details` | `getFlightDetails` | `GetFlightDetails` | Details payload of one flight. See [Flight details](flights.md#flight-details). | +| `get_flight_tracker_config` | `getFlightTrackerConfig` | `GetFlightTrackerConfig` | Copy of the current feed settings. See [Flight tracker settings](tracker-config.md). | +| `set_flight_tracker_config` | `setFlightTrackerConfig` | `SetFlightTrackerConfig` | Change the feed settings. See [Flight tracker settings](tracker-config.md). | + +## Zones and bounds + +These make no request. + +| Python | Node.js | Go | Description | +|---|---|---|---| +| `get_zones` | `getZones` | `GetZones` | Major zones of the globe. See [Zones](zones.md#zones). | +| `get_bounds` | `getBounds` | `GetBounds` | Bounds string of a zone. See [Zones](zones.md#zones). | +| `get_bounds_by_point` | `getBoundsByPoint` | `GetBoundsByPoint` | Bounds string of the square around a point. See [Around a point](zones.md#around-a-point). | + +## Airports + +| Python | Node.js | Go | Description | +|---|---|---|---| +| `get_airport` | `getAirport` | `GetAirport` | One airport, by its IATA or ICAO code. See [Airports](airports.md#one-airport-by-code). | +| `get_airport_details` | `getAirportDetails` | `GetAirportDetails` | Details payload of one airport. See [Airport details](airports.md#the-raw-details-payload). | +| `get_airports` | `getAirports` | `GetAirports` | Airports of some countries, or all of them. See [Airports of a country](airports.md#airports-of-a-country). | +| `get_airport_disruptions` | `getAirportDisruptions` | `GetAirportDisruptions` | Current airport disruptions. See [Airport disruptions](live-data.md#airport-disruptions). | + +## Airlines and images + +| Python | Node.js | Go | Description | +|---|---|---|---| +| `get_airlines` | `getAirlines` | `GetAirlines` | Every airline. See [Airlines](airlines.md#listing-the-airlines). | +| `get_airline_logo` | `getAirlineLogo` | `GetAirlineLogo` | Logo of an airline. See [Airline logos](airlines.md#airline-logos). | +| `get_country_flag` | `getCountryFlag` | `GetCountryFlag` | Flag of a country. See [Country flags](airlines.md#country-flags). | + +## More data + +| Python | Node.js | Go | Description | +|---|---|---|---| +| `search` | `search` | `Search` | Search results, grouped by category. See [Search](searching.md). | +| `get_most_tracked` | `getMostTracked` | `GetMostTracked` | Most tracked flights. See [Most tracked flights](live-data.md#most-tracked-flights). | +| `get_volcanic_eruptions` | `getVolcanicEruptions` | `GetVolcanicEruptions` | Volcanic eruptions and ash clouds. See [Volcanic eruptions](live-data.md#volcanic-eruptions). | + +## Account + +| Python | Node.js | Go | Description | +|---|---|---|---| +| `login` | `login` | `Login` | Log in to a FlightRadar24 account. See [Logging in](account.md#logging-in). | +| `logout` | `logout` | `Logout` | End the session. See [Session state](account.md#session-state). | +| `is_logged_in` | `isLoggedIn` | `IsLoggedIn` | Whether the client holds a session. | +| `get_login_data` | `getLoginData` | `GetLoginData` | Data of the logged-in account. | +| `get_bookmarks` | `getBookmarks` | `GetBookmarks` | Bookmarks of the account. See [Bookmarks](account.md#bookmarks). | +| `get_history_data` | `getHistoryData` | `GetHistoryData` | Track of a flight, as CSV or KML. See [Flight history](account.md#flight-history). | + +## What each package exports + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +from FlightRadarAPI import ( + FlightRadar24API, # the client + Flight, Airport, Entity, # what the client returns + Countries, # country names, for get_airports + FlightTrackerConfig, # settings of the flight feed + RetryPolicy, # retrying transient failures + FlightRadarError, AirportNotFoundError, CloudflareError, + DecompressionLimitError, LoginError, +) +``` + +Anything else in the package, such as the `parsers` and `request` modules, is +internal and may change without notice. +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const { + FlightRadar24API, // the client + Flight, Airport, Entity, // what the client returns + Countries, // country names, for getAirports + FlightTrackerConfig, // settings of the flight feed + RetryPolicy, // retrying transient failures + FlightRadarError, AirportNotFoundError, CloudflareError, + DecompressionLimitError, LoginError, + version, author, +} = require("flightradarapi"); +``` + +The package also exports `APIClient`, the HTTP client `FlightRadar24API` is +built on. It is there for advanced cases; most programs never need it. +::: + +:::{tab-item} Go +:sync: go + +| Kind | Names | +|---|---| +| Client | `Client`, `New`, `Options`, `FlightSearch` | +| Results | `Flight`, `FlightDetails`, `Airport`, `Airline`, `Image`, `Zone`, `Entity`, `Positioned` | +| Building results | `NewFlight`, `NewAirport`, `NewAirportFromBasicInfo`, `NewAirportFromInfo`, `NewAirportFromDetails` | +| Countries | `Country`, the `Country...` constants, `AllCountries` | +| Flight feed settings | `FlightTrackerConfig`, `NewFlightTrackerConfig` | +| Retrying | `RetryPolicy`, `NewRetryPolicy`, `DefaultRetryBaseDelay`, `DefaultRetryMaxDelay`, `DefaultRetryJitter` | +| TLS | `TLSProfile`, `Chrome136Profile` | +| Errors | `ErrFlightRadar`, `ErrAirportNotFound`, `ErrCloudflare`, `ErrDecompressionLimit`, `ErrLogin`, and the types `AirportNotFoundError`, `CloudflareError`, `DecompressionLimitError`, `LoginError`, `StatusError` | +| Other | `SetLogger`, `Response`, `Version`, `Author`, `DefaultText`, `DefaultTimeout`, `MaxResponseBytes` | +::: +:::: diff --git a/docs/client.md b/docs/client.md new file mode 100644 index 0000000..686fafe --- /dev/null +++ b/docs/client.md @@ -0,0 +1,250 @@ +# Configuration + +The client works with no configuration. A few options are there for when the +defaults do not fit. + +| Option | Python | Node.js | Go | Default | +|---|---|---|---|---| +| [Timeout](#timeout) | `timeout` (seconds) | `timeout` (milliseconds) | `Options.Timeout` | 30 seconds | +| [Concurrency](#concurrency) | `max_workers` | `maxWorkers` | `Options.MaxWorkers` | 8 | +| [TLS impersonation](#tls-impersonation) | `impersonate` | `impersonate` | `Options.TLSProfile`, `Options.HTTPClient` | Chrome 136 | +| [Retry](errors.md#retrying) | `retry` | `retry` | `Options.Retry` | No retry | + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +from FlightRadarAPI import FlightRadar24API, RetryPolicy + +fr_api = FlightRadar24API( + timeout=10, # seconds + max_workers=4, + retry=RetryPolicy(max_attempts=3), +) +``` + +The constructor also takes `user` and `password`, to +[log in](account.md#logging-in) right away. +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const { FlightRadar24API, RetryPolicy } = require("flightradarapi"); + +const frApi = new FlightRadar24API({ + timeout: 10000, // milliseconds + maxWorkers: 4, + retry: new RetryPolicy({ maxAttempts: 3 }), +}); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +retry, err := flightradarapi.NewRetryPolicy(3) + +if err != nil { + log.Fatal(err) +} + +client := flightradarapi.New(flightradarapi.Options{ + Timeout: 10 * time.Second, + MaxWorkers: 4, + Retry: retry, +}) +``` + +Every field of `Options` defaults from its zero value, so set only what you +need. `New` takes at most one `Options`. +::: +:::: + +## Timeout + +The timeout bounds a single request, not a whole call: a call that makes several +requests, such as a flight search with details, may take longer. + +It can be changed after the client is created. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +fr_api.timeout = 60 # seconds +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +frApi.timeout = 60000; // milliseconds +``` +::: + +:::{tab-item} Go +:sync: go + +```go +client.Timeout = 60 * time.Second +``` + +Change it before making calls, not while one is in flight. For a deadline over +a whole call, cancel the `context.Context` you pass to it: + +```go +ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) +defer cancel() + +airports, err := client.GetAirports(ctx, nil) +``` +::: +:::: + +## Concurrency + +Two calls fan out into many requests: + +- a [flight search with details](flights.md#details-for-every-flight-at-once), + which makes one request per flight found; +- the [airports listing](airports.md#airports-of-a-country), which makes one + request per country and state page. + +The number of workers is how many of those requests run at the same time. It can +be changed after the client is created, through `max_workers` in Python, +`maxWorkers` in Node.js and `MaxWorkers` in Go. + +A higher number is faster, and more likely to be answered with a +[Cloudflare block](errors.md#cloudflare-blocks). + +## TLS impersonation + +FlightRadar24 sits behind Cloudflare, which tells browsers from scripts by the +way they open a TLS connection. The SDKs present the handshake of Chrome 136 by +default. Override it when FlightRadar24 updates its bot mitigation faster than +this library releases. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +`impersonate` takes the name of a +[curl_cffi](https://github.com/lexiforest/curl_cffi) browser profile. The +default is `"chrome136"`. + +```python +fr_api = FlightRadar24API(impersonate="chrome") +``` + +The profiles available depend on the version of `curl_cffi` installed. +::: + +:::{tab-item} Node.js +:sync: node + +`impersonate` takes an object with any of `ciphers`, `sigalgs` and `ecdhCurve`. +What you leave out keeps the value of the bundled Chrome 136 profile. + +```javascript +const frApi = new FlightRadar24API({ + impersonate: { + ciphers: ["TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256"], + ecdhCurve: "X25519:P-256:P-384", + }, +}); +``` + +`ciphers` and `sigalgs` are arrays of OpenSSL names, and `ecdhCurve` is a +colon-separated string. +::: + +:::{tab-item} Go +:sync: go + +`Options.TLSProfile` narrows the cipher suites, the curve order and the TLS +versions the client offers. Start from the default and change what you need: + +```go +profile := flightradarapi.Chrome136Profile() +profile.MinVersion = tls.VersionTLS13 + +client := flightradarapi.New(flightradarapi.Options{TLSProfile: &profile}) +``` + +Go fixes its own cipher ordering, so a profile is an approximation of a browser +rather than a byte-exact fingerprint. For full impersonation, pass a client +built on [utls](https://github.com/refraction-networking/utls) or +[tls-client](https://github.com/bogdanfinn/tls-client): + +```go +client := flightradarapi.New(flightradarapi.Options{ + HTTPClient: &http.Client{Transport: myImpersonatingTransport}, +}) +``` + +When you replace the HTTP client: + +- set `DisableCompression` on its transport, so the SDK decodes the responses + and can enforce its [size limit](errors.md#response-size-limit); +- leave `CheckRedirect` unset, or the cookies FlightRadar24 hands out on a + redirect are lost; +- any `Jar` is ignored: the SDK manages the cookies itself. +::: +:::: + +## Logging + +The SDKs log a warning when FlightRadar24 answers with something they did not +expect, such as a page whose layout changed or a country with no airports. A +warning never interrupts the call. + +| SDK | Where warnings go | +|---|---| +| Python | The standard `logging` module, under the logger `FlightRadarAPI` | +| Node.js | `console.warn` | +| Go | `log/slog`, through `slog.Default()` unless `SetLogger` is called | + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +import logging + +logging.basicConfig() # show the warnings +logging.getLogger("FlightRadarAPI").setLevel(logging.ERROR) # or silence them +``` +::: + +:::{tab-item} Node.js +:sync: node + +The warnings are written with `console.warn`, to the standard error stream. +::: + +:::{tab-item} Go +:sync: go + +```go +logger := slog.New(slog.NewJSONHandler(os.Stderr, nil)) + +flightradarapi.SetLogger(logger) // nil restores slog.Default() +``` + +The logger is set for the whole process, not per client. +::: +:::: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..b61d6f1 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +"""Sphinx configuration for the FlightRadarAPI documentation. + +Pages are written in Markdown (MyST) and rendered with the Read the Docs theme. +Nothing is imported from the SDKs, so the build needs none of their dependencies. +""" + +import os +import re +from datetime import datetime +from pathlib import Path + +# -- Project information ----------------------------------------------------- + +project = "FlightRadarAPI" +author = "Jean Loui Bernard Silva de Jesus" +copyright = f"{datetime.now().year}, {author}" + +# The three SDKs share one version; the Python package is where it is read from. +_init = Path(__file__).parent.parent / "python" / "FlightRadarAPI" / "__init__.py" +release = re.search(r'__version__ = "([^"]+)"', _init.read_text(encoding="utf-8")).group(1) +version = ".".join(release.split(".")[:2]) + +# -- General configuration --------------------------------------------------- + +extensions = [ + "myst_parser", + "sphinx_copybutton", + "sphinx_design", + "sphinxext.opengraph", +] + +source_suffix = {".md": "markdown"} + +master_doc = "index" +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +myst_enable_extensions = ["colon_fence", "deflist", "substitution"] +myst_heading_anchors = 3 +myst_substitutions = {"release": release} + +# -- HTML output ------------------------------------------------------------- + +html_theme = "sphinx_rtd_theme" +html_title = "FlightRadarAPI" +html_logo = "_static/logo.png" +html_favicon = "_static/favicon.ico" + +html_theme_options = { + "collapse_navigation": False, + "sticky_navigation": True, + "navigation_depth": 3, + "prev_next_buttons_location": "bottom", + "style_external_links": True, +} + +# "Edit on GitHub" link, plus the data the sidebar repository card reads. +html_context = { + "display_github": True, + "github_user": "JeanExtreme002", + "github_repo": "FlightRadarAPI", + "github_version": "main", + "conf_py_path": "/docs/", +} + +html_static_path = ["_static"] +html_css_files = ["custom.css"] + +# -- Extensions -------------------------------------------------------------- + +# Read the Docs exports the address the build is served from. +html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "https://flightradarapi.readthedocs.io/en/latest/") + +ogp_site_url = html_baseurl +ogp_site_name = "FlightRadarAPI" +ogp_image = "_static/logo.png" +ogp_image_alt = "FlightRadarAPI: unofficial SDK for FlightRadar24" +ogp_social_cards = {"enable": False} + +copybutton_prompt_text = r">>> |\.\.\. |\$ " +copybutton_prompt_is_regexp = True diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..fd36271 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,5 @@ +# Contributing + +```{include} ../CONTRIBUTING.md +:start-line: 1 +``` diff --git a/docs/countries.md b/docs/countries.md new file mode 100644 index 0000000..822332d --- /dev/null +++ b/docs/countries.md @@ -0,0 +1,310 @@ +# Countries + +The [airports listing](airports.md#airports-of-a-country) is narrowed by +country. Each SDK names the countries FlightRadar24 lists, 228 in all, so +you do not have to spell them. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +`Countries` is an `Enum`. The value of each member is the slug of the country. + +```python +from FlightRadarAPI import Countries + +Countries.BRAZIL.value # "brazil" + +airports = fr_api.get_airports([Countries.BRAZIL, Countries.UNITED_STATES]) + +for country in Countries: # every country + print(country.name, country.value) +``` +::: + +:::{tab-item} Node.js +:sync: node + +`Countries` is a frozen object. Each property holds the slug of the country. + +```javascript +const { Countries } = require("flightradarapi"); + +Countries.BRAZIL; // "brazil" + +const airports = await frApi.getAirports([Countries.BRAZIL, Countries.UNITED_STATES]); + +for (const [name, slug] of Object.entries(Countries)) { // every country + console.log(name, slug); +} +``` +::: + +:::{tab-item} Go +:sync: go + +`Country` is a string type, with one constant per country. Each constant holds +the slug of the country. + +```go +flightradarapi.CountryBrazil // "brazil" + +airports, err := client.GetAirports(ctx, []flightradarapi.Country{ + flightradarapi.CountryBrazil, + flightradarapi.CountryUnitedStates, +}) + +for _, country := range flightradarapi.AllCountries() { // every country + fmt.Println(country) +} +``` +::: +:::: + +## Names and slugs + +A slug is the name of a country the way FlightRadar24 spells it in its URLs: +lower case, with hyphens. The constants are a convenience, not a requirement. +Wherever a country is expected, a slug or a plain name works too, and is +normalized the same way: `"United States"`, `"united-states"` and +`Countries.UNITED_STATES` select the same country. + +This also means a country FlightRadar24 adds before the SDK names it can be +reached by its name. + +## All countries + +The Python and Node.js names are the same. + +| Python and Node.js | Go | Slug | +|---|---|---| +| `AFGHANISTAN` | `CountryAfghanistan` | `"afghanistan"` | +| `ALBANIA` | `CountryAlbania` | `"albania"` | +| `ALGERIA` | `CountryAlgeria` | `"algeria"` | +| `AMERICAN_SAMOA` | `CountryAmericanSamoa` | `"american-samoa"` | +| `ANGOLA` | `CountryAngola` | `"angola"` | +| `ANGUILLA` | `CountryAnguilla` | `"anguilla"` | +| `ANTARCTICA` | `CountryAntarctica` | `"antarctica"` | +| `ANTIGUA_AND_BARBUDA` | `CountryAntiguaAndBarbuda` | `"antigua-and-barbuda"` | +| `ARGENTINA` | `CountryArgentina` | `"argentina"` | +| `ARMENIA` | `CountryArmenia` | `"armenia"` | +| `ARUBA` | `CountryAruba` | `"aruba"` | +| `AUSTRALIA` | `CountryAustralia` | `"australia"` | +| `AUSTRIA` | `CountryAustria` | `"austria"` | +| `AZERBAIJAN` | `CountryAzerbaijan` | `"azerbaijan"` | +| `BAHAMAS` | `CountryBahamas` | `"bahamas"` | +| `BAHRAIN` | `CountryBahrain` | `"bahrain"` | +| `BANGLADESH` | `CountryBangladesh` | `"bangladesh"` | +| `BARBADOS` | `CountryBarbados` | `"barbados"` | +| `BELARUS` | `CountryBelarus` | `"belarus"` | +| `BELGIUM` | `CountryBelgium` | `"belgium"` | +| `BELIZE` | `CountryBelize` | `"belize"` | +| `BENIN` | `CountryBenin` | `"benin"` | +| `BERMUDA` | `CountryBermuda` | `"bermuda"` | +| `BHUTAN` | `CountryBhutan` | `"bhutan"` | +| `BOLIVIA` | `CountryBolivia` | `"bolivia"` | +| `BOSNIA_AND_HERZEGOVINA` | `CountryBosniaAndHerzegovina` | `"bosnia-and-herzegovina"` | +| `BOTSWANA` | `CountryBotswana` | `"botswana"` | +| `BRAZIL` | `CountryBrazil` | `"brazil"` | +| `BRUNEI` | `CountryBrunei` | `"brunei"` | +| `BULGARIA` | `CountryBulgaria` | `"bulgaria"` | +| `BURKINA_FASO` | `CountryBurkinaFaso` | `"burkina-faso"` | +| `BURUNDI` | `CountryBurundi` | `"burundi"` | +| `CAMBODIA` | `CountryCambodia` | `"cambodia"` | +| `CAMEROON` | `CountryCameroon` | `"cameroon"` | +| `CANADA` | `CountryCanada` | `"canada"` | +| `CAPE_VERDE` | `CountryCapeVerde` | `"cape-verde"` | +| `CAYMAN_ISLANDS` | `CountryCaymanIslands` | `"cayman-islands"` | +| `CENTRAL_AFRICAN_REPUBLIC` | `CountryCentralAfricanRepublic` | `"central-african-republic"` | +| `CHAD` | `CountryChad` | `"chad"` | +| `CHILE` | `CountryChile` | `"chile"` | +| `CHINA` | `CountryChina` | `"china"` | +| `COCOS_KEELING_ISLANDS` | `CountryCocosKeelingIslands` | `"cocos-keeling-islands"` | +| `COLOMBIA` | `CountryColombia` | `"colombia"` | +| `COMOROS` | `CountryComoros` | `"comoros"` | +| `CONGO` | `CountryCongo` | `"congo"` | +| `COOK_ISLANDS` | `CountryCookIslands` | `"cook-islands"` | +| `COSTA_RICA` | `CountryCostaRica` | `"costa-rica"` | +| `CROATIA` | `CountryCroatia` | `"croatia"` | +| `CUBA` | `CountryCuba` | `"cuba"` | +| `CURACAO` | `CountryCuracao` | `"curacao"` | +| `CYPRUS` | `CountryCyprus` | `"cyprus"` | +| `CZECHIA` | `CountryCzechia` | `"czechia"` | +| `DEMOCRATIC_REPUBLIC_OF_THE_CONGO` | `CountryDemocraticRepublicOfTheCongo` | `"democratic-republic-of-the-congo"` | +| `DENMARK` | `CountryDenmark` | `"denmark"` | +| `DJIBOUTI` | `CountryDjibouti` | `"djibouti"` | +| `DOMINICA` | `CountryDominica` | `"dominica"` | +| `DOMINICAN_REPUBLIC` | `CountryDominicanRepublic` | `"dominican-republic"` | +| `ECUADOR` | `CountryEcuador` | `"ecuador"` | +| `EGYPT` | `CountryEgypt` | `"egypt"` | +| `EL_SALVADOR` | `CountryElSalvador` | `"el-salvador"` | +| `EQUATORIAL_GUINEA` | `CountryEquatorialGuinea` | `"equatorial-guinea"` | +| `ERITREA` | `CountryEritrea` | `"eritrea"` | +| `ESTONIA` | `CountryEstonia` | `"estonia"` | +| `ESWATINI` | `CountryEswatini` | `"eswatini"` | +| `ETHIOPIA` | `CountryEthiopia` | `"ethiopia"` | +| `FALKLAND_ISLANDS_MALVINAS` | `CountryFalklandIslandsMalvinas` | `"falkland-islands-malvinas"` | +| `FAROE_ISLANDS` | `CountryFaroeIslands` | `"faroe-islands"` | +| `FIJI` | `CountryFiji` | `"fiji"` | +| `FINLAND` | `CountryFinland` | `"finland"` | +| `FRANCE` | `CountryFrance` | `"france"` | +| `FRENCH_GUIANA` | `CountryFrenchGuiana` | `"french-guiana"` | +| `FRENCH_POLYNESIA` | `CountryFrenchPolynesia` | `"french-polynesia"` | +| `GABON` | `CountryGabon` | `"gabon"` | +| `GAMBIA` | `CountryGambia` | `"gambia"` | +| `GEORGIA` | `CountryGeorgia` | `"georgia"` | +| `GERMANY` | `CountryGermany` | `"germany"` | +| `GHANA` | `CountryGhana` | `"ghana"` | +| `GIBRALTAR` | `CountryGibraltar` | `"gibraltar"` | +| `GREECE` | `CountryGreece` | `"greece"` | +| `GREENLAND` | `CountryGreenland` | `"greenland"` | +| `GRENADA` | `CountryGrenada` | `"grenada"` | +| `GUADELOUPE` | `CountryGuadeloupe` | `"guadeloupe"` | +| `GUAM` | `CountryGuam` | `"guam"` | +| `GUATEMALA` | `CountryGuatemala` | `"guatemala"` | +| `GUERNSEY` | `CountryGuernsey` | `"guernsey"` | +| `GUINEA` | `CountryGuinea` | `"guinea"` | +| `GUINEA_BISSAU` | `CountryGuineaBissau` | `"guinea-bissau"` | +| `GUYANA` | `CountryGuyana` | `"guyana"` | +| `HAITI` | `CountryHaiti` | `"haiti"` | +| `HONDURAS` | `CountryHonduras` | `"honduras"` | +| `HONG_KONG` | `CountryHongKong` | `"hong-kong"` | +| `HUNGARY` | `CountryHungary` | `"hungary"` | +| `ICELAND` | `CountryIceland` | `"iceland"` | +| `INDIA` | `CountryIndia` | `"india"` | +| `INDONESIA` | `CountryIndonesia` | `"indonesia"` | +| `IRAN` | `CountryIran` | `"iran"` | +| `IRAQ` | `CountryIraq` | `"iraq"` | +| `IRELAND` | `CountryIreland` | `"ireland"` | +| `ISLE_OF_MAN` | `CountryIsleOfMan` | `"isle-of-man"` | +| `ISRAEL` | `CountryIsrael` | `"israel"` | +| `ITALY` | `CountryItaly` | `"italy"` | +| `IVORY_COAST` | `CountryIvoryCoast` | `"ivory-coast"` | +| `JAMAICA` | `CountryJamaica` | `"jamaica"` | +| `JAPAN` | `CountryJapan` | `"japan"` | +| `JERSEY` | `CountryJersey` | `"jersey"` | +| `JORDAN` | `CountryJordan` | `"jordan"` | +| `KAZAKHSTAN` | `CountryKazakhstan` | `"kazakhstan"` | +| `KENYA` | `CountryKenya` | `"kenya"` | +| `KIRIBATI` | `CountryKiribati` | `"kiribati"` | +| `KOSOVO` | `CountryKosovo` | `"kosovo"` | +| `KUWAIT` | `CountryKuwait` | `"kuwait"` | +| `KYRGYZSTAN` | `CountryKyrgyzstan` | `"kyrgyzstan"` | +| `LAOS` | `CountryLaos` | `"laos"` | +| `LATVIA` | `CountryLatvia` | `"latvia"` | +| `LEBANON` | `CountryLebanon` | `"lebanon"` | +| `LESOTHO` | `CountryLesotho` | `"lesotho"` | +| `LIBERIA` | `CountryLiberia` | `"liberia"` | +| `LIBYA` | `CountryLibya` | `"libya"` | +| `LITHUANIA` | `CountryLithuania` | `"lithuania"` | +| `LUXEMBOURG` | `CountryLuxembourg` | `"luxembourg"` | +| `MACAO` | `CountryMacao` | `"macao"` | +| `MADAGASCAR` | `CountryMadagascar` | `"madagascar"` | +| `MALAWI` | `CountryMalawi` | `"malawi"` | +| `MALAYSIA` | `CountryMalaysia` | `"malaysia"` | +| `MALDIVES` | `CountryMaldives` | `"maldives"` | +| `MALI` | `CountryMali` | `"mali"` | +| `MALTA` | `CountryMalta` | `"malta"` | +| `MARSHALL_ISLANDS` | `CountryMarshallIslands` | `"marshall-islands"` | +| `MARTINIQUE` | `CountryMartinique` | `"martinique"` | +| `MAURITANIA` | `CountryMauritania` | `"mauritania"` | +| `MAURITIUS` | `CountryMauritius` | `"mauritius"` | +| `MAYOTTE` | `CountryMayotte` | `"mayotte"` | +| `MEXICO` | `CountryMexico` | `"mexico"` | +| `MICRONESIA` | `CountryMicronesia` | `"micronesia"` | +| `MOLDOVA` | `CountryMoldova` | `"moldova"` | +| `MONACO` | `CountryMonaco` | `"monaco"` | +| `MONGOLIA` | `CountryMongolia` | `"mongolia"` | +| `MONTENEGRO` | `CountryMontenegro` | `"montenegro"` | +| `MONTSERRAT` | `CountryMontserrat` | `"montserrat"` | +| `MOROCCO` | `CountryMorocco` | `"morocco"` | +| `MOZAMBIQUE` | `CountryMozambique` | `"mozambique"` | +| `MYANMAR_BURMA` | `CountryMyanmarBurma` | `"myanmar-burma"` | +| `NAMIBIA` | `CountryNamibia` | `"namibia"` | +| `NAURU` | `CountryNauru` | `"nauru"` | +| `NEPAL` | `CountryNepal` | `"nepal"` | +| `NETHERLANDS` | `CountryNetherlands` | `"netherlands"` | +| `NEW_CALEDONIA` | `CountryNewCaledonia` | `"new-caledonia"` | +| `NEW_ZEALAND` | `CountryNewZealand` | `"new-zealand"` | +| `NICARAGUA` | `CountryNicaragua` | `"nicaragua"` | +| `NIGER` | `CountryNiger` | `"niger"` | +| `NIGERIA` | `CountryNigeria` | `"nigeria"` | +| `NORTH_KOREA` | `CountryNorthKorea` | `"north-korea"` | +| `NORTH_MACEDONIA` | `CountryNorthMacedonia` | `"north-macedonia"` | +| `NORTHERN_MARIANA_ISLANDS` | `CountryNorthernMarianaIslands` | `"northern-mariana-islands"` | +| `NORWAY` | `CountryNorway` | `"norway"` | +| `OMAN` | `CountryOman` | `"oman"` | +| `PAKISTAN` | `CountryPakistan` | `"pakistan"` | +| `PALAU` | `CountryPalau` | `"palau"` | +| `PANAMA` | `CountryPanama` | `"panama"` | +| `PAPUA_NEW_GUINEA` | `CountryPapuaNewGuinea` | `"papua-new-guinea"` | +| `PARAGUAY` | `CountryParaguay` | `"paraguay"` | +| `PERU` | `CountryPeru` | `"peru"` | +| `PHILIPPINES` | `CountryPhilippines` | `"philippines"` | +| `POLAND` | `CountryPoland` | `"poland"` | +| `PORTUGAL` | `CountryPortugal` | `"portugal"` | +| `PUERTO_RICO` | `CountryPuertoRico` | `"puerto-rico"` | +| `QATAR` | `CountryQatar` | `"qatar"` | +| `REUNION` | `CountryReunion` | `"reunion"` | +| `ROMANIA` | `CountryRomania` | `"romania"` | +| `RUSSIA` | `CountryRussia` | `"russia"` | +| `RWANDA` | `CountryRwanda` | `"rwanda"` | +| `SAINT_HELENA` | `CountrySaintHelena` | `"saint-helena"` | +| `SAINT_KITTS_AND_NEVIS` | `CountrySaintKittsAndNevis` | `"saint-kitts-and-nevis"` | +| `SAINT_LUCIA` | `CountrySaintLucia` | `"saint-lucia"` | +| `SAINT_PIERRE_AND_MIQUELON` | `CountrySaintPierreAndMiquelon` | `"saint-pierre-and-miquelon"` | +| `SAINT_VINCENT_AND_THE_GRENADINES` | `CountrySaintVincentAndTheGrenadines` | `"saint-vincent-and-the-grenadines"` | +| `SAMOA` | `CountrySamoa` | `"samoa"` | +| `SAO_TOME_AND_PRINCIPE` | `CountrySaoTomeAndPrincipe` | `"sao-tome-and-principe"` | +| `SAUDI_ARABIA` | `CountrySaudiArabia` | `"saudi-arabia"` | +| `SENEGAL` | `CountrySenegal` | `"senegal"` | +| `SERBIA` | `CountrySerbia` | `"serbia"` | +| `SEYCHELLES` | `CountrySeychelles` | `"seychelles"` | +| `SIERRA_LEONE` | `CountrySierraLeone` | `"sierra-leone"` | +| `SINGAPORE` | `CountrySingapore` | `"singapore"` | +| `SLOVAKIA` | `CountrySlovakia` | `"slovakia"` | +| `SLOVENIA` | `CountrySlovenia` | `"slovenia"` | +| `SOLOMON_ISLANDS` | `CountrySolomonIslands` | `"solomon-islands"` | +| `SOMALIA` | `CountrySomalia` | `"somalia"` | +| `SOUTH_AFRICA` | `CountrySouthAfrica` | `"south-africa"` | +| `SOUTH_KOREA` | `CountrySouthKorea` | `"south-korea"` | +| `SOUTH_SUDAN` | `CountrySouthSudan` | `"south-sudan"` | +| `SPAIN` | `CountrySpain` | `"spain"` | +| `SRI_LANKA` | `CountrySriLanka` | `"sri-lanka"` | +| `SUDAN` | `CountrySudan` | `"sudan"` | +| `SURINAME` | `CountrySuriname` | `"suriname"` | +| `SWEDEN` | `CountrySweden` | `"sweden"` | +| `SWITZERLAND` | `CountrySwitzerland` | `"switzerland"` | +| `SYRIA` | `CountrySyria` | `"syria"` | +| `TAIWAN` | `CountryTaiwan` | `"taiwan"` | +| `TAJIKISTAN` | `CountryTajikistan` | `"tajikistan"` | +| `TANZANIA` | `CountryTanzania` | `"tanzania"` | +| `THAILAND` | `CountryThailand` | `"thailand"` | +| `TIMOR_LESTE_EAST_TIMOR` | `CountryTimorLesteEastTimor` | `"timor-leste-east-timor"` | +| `TOGO` | `CountryTogo` | `"togo"` | +| `TONGA` | `CountryTonga` | `"tonga"` | +| `TRINIDAD_AND_TOBAGO` | `CountryTrinidadAndTobago` | `"trinidad-and-tobago"` | +| `TUNISIA` | `CountryTunisia` | `"tunisia"` | +| `TURKEY` | `CountryTurkey` | `"turkey"` | +| `TURKMENISTAN` | `CountryTurkmenistan` | `"turkmenistan"` | +| `TURKS_AND_CAICOS_ISLANDS` | `CountryTurksAndCaicosIslands` | `"turks-and-caicos-islands"` | +| `TUVALU` | `CountryTuvalu` | `"tuvalu"` | +| `UGANDA` | `CountryUganda` | `"uganda"` | +| `UKRAINE` | `CountryUkraine` | `"ukraine"` | +| `UNITED_ARAB_EMIRATES` | `CountryUnitedArabEmirates` | `"united-arab-emirates"` | +| `UNITED_KINGDOM` | `CountryUnitedKingdom` | `"united-kingdom"` | +| `UNITED_STATES` | `CountryUnitedStates` | `"united-states"` | +| `UNITED_STATES_MINOR_OUTLYING_ISLANDS` | `CountryUnitedStatesMinorOutlyingIslands` | `"united-states-minor-outlying-islands"` | +| `URUGUAY` | `CountryUruguay` | `"uruguay"` | +| `UZBEKISTAN` | `CountryUzbekistan` | `"uzbekistan"` | +| `VANUATU` | `CountryVanuatu` | `"vanuatu"` | +| `VENEZUELA` | `CountryVenezuela` | `"venezuela"` | +| `VIETNAM` | `CountryVietnam` | `"vietnam"` | +| `VIRGIN_ISLANDS_BRITISH` | `CountryVirginIslandsBritish` | `"virgin-islands-british"` | +| `VIRGIN_ISLANDS_US` | `CountryVirginIslandsUs` | `"virgin-islands-us"` | +| `WALLIS_AND_FUTUNA` | `CountryWallisAndFutuna` | `"wallis-and-futuna"` | +| `YEMEN` | `CountryYemen` | `"yemen"` | +| `ZAMBIA` | `CountryZambia` | `"zambia"` | +| `ZIMBABWE` | `CountryZimbabwe` | `"zimbabwe"` | diff --git a/docs/distances.md b/docs/distances.md new file mode 100644 index 0000000..23c8a6b --- /dev/null +++ b/docs/distances.md @@ -0,0 +1,109 @@ +# Distances + +`Flight` and `Airport` are both an `Entity`: something with a latitude and a +longitude. Any two entities can measure the distance between them, in +kilometers, along the surface of the Earth. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +airport = fr_api.get_airport("KJFK") +distance = flight.get_distance_from(airport) + +print(f"The flight is {distance:.1f} km away from the airport.") +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const airport = await frApi.getAirport("KJFK"); +const distance = flight.getDistanceFrom(airport); + +console.log(`The flight is ${distance.toFixed(1)} km away from the airport.`); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +airport, err := client.GetAirport(ctx, "KJFK", false) + +if err != nil { + log.Fatal(err) +} + +distance, err := flight.GetDistanceFrom(airport) + +if err != nil { + log.Fatal(err) +} + +fmt.Printf("The flight is %.1f km away from the airport.\n", distance) +``` + +`GetDistanceFrom` takes anything that implements `Positioned`, which both +`*Flight` and `*Airport` do. +::: +:::: + +It works in every combination: flight to airport, airport to airport, flight to +flight. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +jfk = fr_api.get_airport("JFK") +heathrow = fr_api.get_airport("LHR") + +print(jfk.get_distance_from(heathrow)) # about 5540 km +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const jfk = await frApi.getAirport("JFK"); +const heathrow = await frApi.getAirport("LHR"); + +console.log(jfk.getDistanceFrom(heathrow)); // about 5540 km +``` +::: + +:::{tab-item} Go +:sync: go + +```go +jfk, err := client.GetAirport(ctx, "JFK", false) +heathrow, err := client.GetAirport(ctx, "LHR", false) + +distance, err := jfk.GetDistanceFrom(heathrow) // about 5540 km +``` +::: +:::: + +## Entities without a position + +A distance needs both positions. The airports returned by the +[airports listing](airports.md#airports-of-a-country) have none, and a flight +may be missing its own. + +| SDK | When a position is missing | +|---|---| +| Python | raises `ValueError` | +| Node.js | throws `TypeError` | +| Go | returns an error wrapping `ErrFlightRadar` | + +To measure from a listed airport, fetch it by its code first: that one carries +the coordinates. diff --git a/docs/errors.md b/docs/errors.md new file mode 100644 index 0000000..06fd016 --- /dev/null +++ b/docs/errors.md @@ -0,0 +1,250 @@ +# Errors and Retries + +## The errors of the SDK + +Every error the SDK defines shares one base, so a single handler can catch them +all. + +| Python and Node.js | Go sentinel | Go type | Raised when | +|---|---|---|---| +| `FlightRadarError` | `ErrFlightRadar` | | Base of all the errors below | +| `AirportNotFoundError` | `ErrAirportNotFound` | `*AirportNotFoundError` | No airport answers to the code given | +| `CloudflareError` | `ErrCloudflare` | `*CloudflareError` | Cloudflare blocked the request | +| `LoginError` | `ErrLogin` | `*LoginError` | The login failed, or the call needs a session and there is none | +| `DecompressionLimitError` | `ErrDecompressionLimit` | `*DecompressionLimitError` | A response is larger than the [size limit](#response-size-limit) | + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +from FlightRadarAPI import ( + AirportNotFoundError, CloudflareError, FlightRadarError, LoginError, +) + +try: + airport = fr_api.get_airport("XXXX") +except AirportNotFoundError: + print("No such airport") +except CloudflareError as error: + print("Blocked by Cloudflare:", error.response.status_code) +except FlightRadarError as error: + print("FlightRadarAPI failed:", error) +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const { + AirportNotFoundError, CloudflareError, FlightRadarError, +} = require("flightradarapi"); + +try { + const airport = await frApi.getAirport("XXXX"); +} +catch (error) { + if (error instanceof AirportNotFoundError) { + console.log("No such airport"); + } + else if (error instanceof CloudflareError) { + console.log("Blocked by Cloudflare:", error.response.status); + } + else if (error instanceof FlightRadarError) { + console.log("FlightRadarAPI failed:", error.message); + } + else { + throw error; + } +} +``` +::: + +:::{tab-item} Go +:sync: go + +Every error wraps a sentinel, so both `errors.Is` and `errors.As` work: + +```go +airport, err := client.GetAirport(ctx, "XXXX", false) + +switch { +case errors.Is(err, flightradarapi.ErrAirportNotFound): + // no such airport +case errors.Is(err, flightradarapi.ErrCloudflare): + // blocked by Cloudflare +case errors.Is(err, flightradarapi.ErrLogin): + // the call needs an account +case errors.Is(err, flightradarapi.ErrFlightRadar): + // anything else from this package +} +``` + +Use `errors.As` to read what an error carries: + +```go +var blocked *flightradarapi.CloudflareError + +if errors.As(err, &blocked) { + fmt.Println(blocked.Response.StatusCode, len(blocked.Body)) +} +``` +::: +:::: + +## Cloudflare blocks + +FlightRadar24 sits behind Cloudflare, which may block a client that makes too +many calls or that does not look like a browser. The SDK reports it as a +`CloudflareError` when the response is one of: + +- an HTTP 520; +- an HTTP 403 carrying the `cf-mitigated` header, which Cloudflare sets when it + was Cloudflare, and not FlightRadar24, that refused the request. + +The error carries the blocked response, for inspection: + +| SDK | What the error carries | +|---|---| +| Python | `error.response`: the response object, with the challenge page as its content | +| Node.js | `error.response`: the response object; `error.body`: the challenge page, as text | +| Go | `Response`: the `*http.Response`; `Body`: the challenge page, as bytes | + +When blocks keep happening: + +1. Slow down. Lower the [number of workers](client.md#concurrency) and space + your calls. +2. Add a [retry policy](#retrying), which backs off between attempts. +3. Update the package, or override the + [TLS impersonation](client.md#tls-impersonation). + +## Retrying + +By default a failed request is not retried. A `RetryPolicy` retries the +transient failures (a Cloudflare block, a timeout, a network error) with an +exponential backoff. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +from FlightRadarAPI import FlightRadar24API, RetryPolicy + +fr_api = FlightRadar24API(retry=RetryPolicy(max_attempts=3)) +``` + +| Parameter | Default | Meaning | +|---|---|---| +| `max_attempts` | `1` | Total number of attempts, including the first | +| `base_delay` | `1.0` | Seconds to wait before the second attempt | +| `max_delay` | `30.0` | Longest wait between attempts, in seconds | +| `jitter` | `0.5` | Upper bound of the random seconds added to each wait | + +A `max_attempts` below 1 or a negative delay raises `ValueError`. +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const { FlightRadar24API, RetryPolicy } = require("flightradarapi"); + +const frApi = new FlightRadar24API({ retry: new RetryPolicy({ maxAttempts: 3 }) }); +``` + +| Option | Default | Meaning | +|---|---|---| +| `maxAttempts` | `1` | Total number of attempts, including the first | +| `baseDelayMs` | `1000` | Milliseconds to wait before the second attempt | +| `maxDelayMs` | `30000` | Longest wait between attempts, in milliseconds | +| `jitterMs` | `500` | Upper bound of the random milliseconds added to each wait | + +A `maxAttempts` below 1 or a negative delay throws an `Error`. +::: + +:::{tab-item} Go +:sync: go + +```go +retry, err := flightradarapi.NewRetryPolicy(3) + +if err != nil { + log.Fatal(err) +} + +client := flightradarapi.New(flightradarapi.Options{Retry: retry}) +``` + +`NewRetryPolicy` takes the number of attempts and uses the defaults for the +rest. For other values, write the struct: + +```go +retry := &flightradarapi.RetryPolicy{ + MaxAttempts: 5, + BaseDelay: 2 * time.Second, + MaxDelay: time.Minute, + Jitter: 250 * time.Millisecond, +} +``` + +| Field | `NewRetryPolicy` | Meaning | +|---|---|---| +| `MaxAttempts` | the argument | Total number of attempts, including the first | +| `BaseDelay` | `1s` | Wait before the second attempt. Zero means `1s`. | +| `MaxDelay` | `30s` | Longest wait between attempts. Zero means `30s`. | +| `Jitter` | `500ms` | Upper bound of the random time added to each wait. Zero means none. | + +`NewRetryPolicy` returns an error for a number of attempts below 1. +::: +:::: + +The wait doubles after each attempt, up to the maximum: with the defaults, about +1, 2, 4, 8 seconds and so on, each plus the jitter. When every attempt fails, +the last error is the one you get. + +Only transient failures are retried. An airport that does not exist, a failed +login or an oversized response fails at once. + +## Response size limit + +A response body is limited to 64 MiB, both as it arrives and after it is +decompressed. A body past the limit fails with `DecompressionLimitError` +(`ErrDecompressionLimit` in Go) instead of exhausting memory. Nothing +FlightRadar24 legitimately sends comes close to it. + +## Other failures + +Not every failure is one of the errors above. + +**Invalid arguments** are rejected with the error the language uses for them: + +| Situation | Python | Node.js | +|---|---|---| +| Airport code that is not 3 or 4 characters long | `ValueError` | `TypeError` | +| Flight limit of the airport details out of range | `ValueError` | `RangeError` | +| File type of the flight history other than CSV or KML | `ValueError` | `TypeError` | +| Unknown flight tracker option | `KeyError` | `RangeError` | +| Flight tracker value that is not a number | `TypeError` | `TypeError` | +| Distance between entities without a position | `ValueError` | `TypeError` | +| Countries that are neither a string nor an iterable | `TypeError` | `TypeError` | + +In Go, all of these are plain errors that wrap `ErrFlightRadar`. + +**HTTP and network failures** come from the transport: + +| Situation | Python | Node.js | Go | +|---|---|---|---| +| Unexpected status code | `curl_cffi` `HTTPError` | `Error`, with the status in its message | `*StatusError` | +| Timeout | `curl_cffi` `Timeout` | `Error` named `TimeoutError` | error matching `context.DeadlineExceeded` | +| Network error | `curl_cffi` `RequestsError` | the error of `fetch` | error wrapping a `*url.Error` | + +In Python these are all subclasses of `curl_cffi.requests.errors.RequestsError`. +In Go they wrap `ErrFlightRadar` like every other error of the package, and a +`*StatusError` carries the `StatusCode`, `Status`, `URL` and `Body` of the +response. diff --git a/docs/flight.md b/docs/flight.md new file mode 100644 index 0000000..cd8a8cc --- /dev/null +++ b/docs/flight.md @@ -0,0 +1,416 @@ +# Flight + +A `Flight` is one aircraft of the live feed. It is an [`Entity`](distances.md), +so it has a position and can measure its distance to an airport or to another +flight. + +Its attributes come in two groups: the ones the feed sends for every flight, and +the ones added by the [flight details](flights.md#flight-details). + +The names follow the convention of each language. What Python calls +`ground_speed` is `groundSpeed` in Node.js and `GroundSpeed` in Go. + +## Attributes from the feed + +Present on every flight. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Attribute | Description | +|---|---| +| `id` | Identifier of the flight in FlightRadar24 | +| `latitude` | Latitude, in degrees | +| `longitude` | Longitude, in degrees | +| `altitude` | Altitude, in feet | +| `heading` | Heading, in degrees | +| `ground_speed` | Ground speed, in knots | +| `vertical_speed` | Vertical speed, in feet per minute | +| `on_ground` | `1` when the aircraft is on the ground, `0` otherwise | +| `time` | Time of the position, as a Unix timestamp | +| `callsign` | Callsign of the flight | +| `number` | Flight number, such as `"EK202"` | +| `squawk` | Transponder code | +| `icao_24bit` | ICAO 24-bit address of the aircraft, in hexadecimal | +| `registration` | Registration of the aircraft | +| `aircraft_code` | Model code of the aircraft, such as `"B77W"` | +| `airline_icao` | ICAO code of the airline | +| `airline_iata` | IATA code of the airline, taken from the first two characters of the flight number | +| `origin_airport_iata` | IATA code of the origin airport | +| `destination_airport_iata` | IATA code of the destination airport | +::: + +:::{tab-item} Node.js +:sync: node + +| Property | Description | +|---|---| +| `id` | Identifier of the flight in FlightRadar24 | +| `latitude` | Latitude, in degrees | +| `longitude` | Longitude, in degrees | +| `altitude` | Altitude, in feet | +| `heading` | Heading, in degrees | +| `groundSpeed` | Ground speed, in knots | +| `verticalSpeed` | Vertical speed, in feet per minute | +| `onGround` | `1` when the aircraft is on the ground, `0` otherwise | +| `time` | Time of the position, as a Unix timestamp | +| `callsign` | Callsign of the flight | +| `number` | Flight number, such as `"EK202"` | +| `squawk` | Transponder code | +| `icao24bit` | ICAO 24-bit address of the aircraft, in hexadecimal | +| `registration` | Registration of the aircraft | +| `aircraftCode` | Model code of the aircraft, such as `"B77W"` | +| `airlineIcao` | ICAO code of the airline | +| `airlineIata` | IATA code of the airline, taken from the first two characters of the flight number | +| `originAirportIata` | IATA code of the origin airport | +| `destinationAirportIata` | IATA code of the destination airport | +::: + +:::{tab-item} Go +:sync: go + +| Field | Description | +|---|---| +| `ID` | Identifier of the flight in FlightRadar24 | +| `Latitude` | Latitude, in degrees | +| `Longitude` | Longitude, in degrees | +| `Altitude` | Altitude, in feet | +| `Heading` | Heading, in degrees | +| `GroundSpeed` | Ground speed, in knots | +| `VerticalSpeed` | Vertical speed, in feet per minute | +| `OnGround` | `1` when the aircraft is on the ground, `0` otherwise | +| `Time` | Time of the position, as a Unix timestamp | +| `Callsign` | Callsign of the flight | +| `Number` | Flight number, such as `"EK202"` | +| `Squawk` | Transponder code | +| `ICAO24Bit` | ICAO 24-bit address of the aircraft, in hexadecimal | +| `Registration` | Registration of the aircraft | +| `AircraftCode` | Model code of the aircraft, such as `"B77W"` | +| `AirlineICAO` | ICAO code of the airline | +| `AirlineIATA` | IATA code of the airline, taken from the first two characters of the flight number | +| `OriginAirportIATA` | IATA code of the origin airport | +| `DestinationAirportIATA` | IATA code of the destination airport | +::: + +:::: + +## Attributes from the details + +Present after `set_flight_details` is called, or when the flights were searched +with details. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +The attributes below are set on the flight itself, next to the ones from the +feed. They do not exist before `set_flight_details` is called. +::: + +:::{tab-item} Node.js +:sync: node + +The properties below are set on the flight itself, next to the ones from the +feed. They are `undefined` before `setFlightDetails` is called. +::: + +:::{tab-item} Go +:sync: go + +The fields below belong to `FlightDetails`, reached through `flight.Details`: +`flight.Details.AirlineName`. `flight.Details` is `nil` before +`SetFlightDetails` is called. + +`FlightDetails` also has `Raw`, the whole details payload, for what the struct +does not name. +::: +:::: + +### Aircraft and airline + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Attribute | Description | +|---|---| +| `aircraft_model` | Model of the aircraft, as text | +| `aircraft_age` | Age of the aircraft | +| `aircraft_country_id` | FlightRadar24 identifier of the country of the aircraft | +| `aircraft_images` | Photos of the aircraft, as FlightRadar24 sends them | +| `aircraft_history` | Previous flights of the aircraft | +| `airline_name` | Name of the airline | +| `airline_short_name` | Short name of the airline | +::: + +:::{tab-item} Node.js +:sync: node + +| Property | Description | +|---|---| +| `aircraftModel` | Model of the aircraft, as text | +| `aircraftAge` | Age of the aircraft | +| `aircraftCountryId` | FlightRadar24 identifier of the country of the aircraft | +| `aircraftImages` | Photos of the aircraft, as FlightRadar24 sends them | +| `aircraftHistory` | Previous flights of the aircraft | +| `airlineName` | Name of the airline | +| `airlineShortName` | Short name of the airline | +::: + +:::{tab-item} Go +:sync: go + +| Field | Description | +|---|---| +| `AircraftModel` | Model of the aircraft, as text | +| `AircraftAge` | Age of the aircraft | +| `AircraftCountryID` | FlightRadar24 identifier of the country of the aircraft | +| `AircraftImages` | Photos of the aircraft, as FlightRadar24 sends them | +| `AircraftHistory` | Previous flights of the aircraft | +| `AirlineName` | Name of the airline | +| `AirlineShortName` | Short name of the airline | +::: + +:::: + +### Destination and origin airports + +Each attribute below exists twice: once for the destination and once for the +origin. Replace `destination` with `origin` for the second set: +`origin_airport_name`, `originAirportName`, `OriginAirportName`. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Attribute | Description | +|---|---| +| `destination_airport_name` | Name of the airport | +| `destination_airport_icao` | ICAO code | +| `destination_airport_latitude` | Latitude, in degrees | +| `destination_airport_longitude` | Longitude, in degrees | +| `destination_airport_altitude` | Altitude of the airport | +| `destination_airport_country_name` | Name of the country | +| `destination_airport_country_code` | Code of the country | +| `destination_airport_terminal` | Terminal of the flight | +| `destination_airport_gate` | Gate of the flight | +| `destination_airport_baggage` | Baggage claim of the flight | +| `destination_airport_timezone_name` | Name of the timezone, such as `"America/New_York"` | +| `destination_airport_timezone_abbr` | Abbreviation of the timezone | +| `destination_airport_timezone_abbr_name` | Name the abbreviation stands for | +| `destination_airport_timezone_offset` | Offset from UTC, in seconds | +| `destination_airport_timezone_offset_hours` | Offset from UTC, as text in hours | +| `destination_airport_visible` | The `visible` flag FlightRadar24 sends for the airport | +| `destination_airport_website` | Website of the airport | +::: + +:::{tab-item} Node.js +:sync: node + +| Property | Description | +|---|---| +| `destinationAirportName` | Name of the airport | +| `destinationAirportIcao` | ICAO code | +| `destinationAirportLatitude` | Latitude, in degrees | +| `destinationAirportLongitude` | Longitude, in degrees | +| `destinationAirportAltitude` | Altitude of the airport | +| `destinationAirportCountryName` | Name of the country | +| `destinationAirportCountryCode` | Code of the country | +| `destinationAirportTerminal` | Terminal of the flight | +| `destinationAirportGate` | Gate of the flight | +| `destinationAirportBaggage` | Baggage claim of the flight | +| `destinationAirportTimezoneName` | Name of the timezone, such as `"America/New_York"` | +| `destinationAirportTimezoneAbbr` | Abbreviation of the timezone | +| `destinationAirportTimezoneAbbrName` | Name the abbreviation stands for | +| `destinationAirportTimezoneOffset` | Offset from UTC, in seconds | +| `destinationAirportTimezoneOffsetHours` | Offset from UTC, as text in hours | +| `destinationAirportVisible` | The `visible` flag FlightRadar24 sends for the airport | +| `destinationAirportWebsite` | Website of the airport | +::: + +:::{tab-item} Go +:sync: go + +| Field | Description | +|---|---| +| `DestinationAirportName` | Name of the airport | +| `DestinationAirportICAO` | ICAO code | +| `DestinationAirportLatitude` | Latitude, in degrees | +| `DestinationAirportLongitude` | Longitude, in degrees | +| `DestinationAirportAltitude` | Altitude of the airport | +| `DestinationAirportCountryName` | Name of the country | +| `DestinationAirportCountryCode` | Code of the country | +| `DestinationAirportTerminal` | Terminal of the flight | +| `DestinationAirportGate` | Gate of the flight | +| `DestinationAirportBaggage` | Baggage claim of the flight | +| `DestinationAirportTimezoneName` | Name of the timezone, such as `"America/New_York"` | +| `DestinationAirportTimezoneAbbr` | Abbreviation of the timezone | +| `DestinationAirportTimezoneAbbrName` | Name the abbreviation stands for | +| `DestinationAirportTimezoneOffset` | Offset from UTC, in seconds | +| `DestinationAirportTimezoneOffsetHours` | Offset from UTC, as text in hours | +| `DestinationAirportVisible` | The `visible` flag FlightRadar24 sends for the airport | +| `DestinationAirportWebsite` | Website of the airport | +::: + +:::: + +The IATA codes of both airports come with the feed: see +`origin_airport_iata` and `destination_airport_iata` above. + +### Status, times and trail + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Attribute | Description | +|---|---| +| `status_text` | Status of the flight, as text | +| `status_icon` | Name of the status icon FlightRadar24 shows | +| `time_details` | Scheduled, estimated and real times of the flight, as FlightRadar24 sends them | +| `trail` | Positions the flight has been through | +::: + +:::{tab-item} Node.js +:sync: node + +| Property | Description | +|---|---| +| `statusText` | Status of the flight, as text | +| `statusIcon` | Name of the status icon FlightRadar24 shows | +| `timeDetails` | Scheduled, estimated and real times of the flight, as FlightRadar24 sends them | +| `trail` | Positions the flight has been through | +::: + +:::{tab-item} Go +:sync: go + +| Field | Description | +|---|---| +| `StatusText` | Status of the flight, as text | +| `StatusIcon` | Name of the status icon FlightRadar24 shows | +| `TimeDetails` | Scheduled, estimated and real times of the flight, as FlightRadar24 sends them | +| `Trail` | Positions the flight has been through | +::: + +:::: + +## Missing values + +FlightRadar24 does not send every value for every flight. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +A value FlightRadar24 left out is the string `"N/A"`. The collections are empty +instead: `aircraft_history`, `aircraft_images` and `trail` are `[]`, and +`time_details` is `{}`. + +Because of that, a numeric attribute is either a number or `"N/A"`. Check before +doing arithmetic: + +```python +if isinstance(flight.altitude, (int, float)): + print(flight.altitude * 0.3048, "m") +``` +::: + +:::{tab-item} Node.js +:sync: node + +A value FlightRadar24 left out is the string `"N/A"`. The collections are empty +instead: `aircraftHistory`, `aircraftImages` and `trail` are `[]`, and +`timeDetails` is `{}`. + +Because of that, a numeric property is either a number or `"N/A"`. Check before +doing arithmetic: + +```javascript +if (typeof flight.altitude === "number") { + console.log(flight.altitude * 0.3048, "m"); +} +``` +::: + +:::{tab-item} Go +:sync: go + +A value FlightRadar24 left out is the zero value of its field: `""` for text +and `nil` for the numbers and booleans, which are pointers. + +```go +if flight.Altitude != nil { + fmt.Println(*flight.Altitude*0.3048, "m") +} +``` + +The numbers are `*float64`, except `Time`, which is `*int64`. The `...Visible` +fields are `*bool`. `DefaultText` holds the `"N/A"` the formatting methods +return. +::: +:::: + +## Methods + +| Python | Node.js | Go | Description | +|---|---|---|---| +| `get_altitude()` | `getAltitude()` | `GetAltitude()` | Altitude with its unit. See [Formatted values](flights.md#formatted-values). | +| `get_flight_level()` | `getFlightLevel()` | `GetFlightLevel()` | Flight level with its unit | +| `get_ground_speed()` | `getGroundSpeed()` | `GetGroundSpeed()` | Ground speed with its unit | +| `get_heading()` | `getHeading()` | `GetHeading()` | Heading with its unit | +| `get_vertical_speed()` | `getVerticalSpeed()` | `GetVerticalSpeed()` | Vertical speed with its unit | +| `check_info(**info)` | `checkInfo(info)` | `CheckInfo(criteria)` | Whether the flight matches some values. See [Checking a flight](flights.md#checking-a-flight-against-criteria). | +| `set_flight_details(details)` | `setFlightDetails(details)` | `SetFlightDetails(details)` | Attach a details payload to the flight | +| `get_distance_from(entity)` | `getDistanceFrom(entity)` | `GetDistanceFrom(other)` | Distance to another entity, in kilometers. See [Distances](distances.md). | + +## Building a flight yourself + +The client builds the flights it returns. To build one from a feed entry you +already have, pass the identifier and the entry: the array FlightRadar24 sends +for that flight. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +from FlightRadarAPI import Flight + +flight = Flight("2e0f1a2", feed_row) +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const { Flight } = require("flightradarapi"); + +const flight = new Flight("2e0f1a2", feedRow); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +flight := flightradarapi.NewFlight("2e0f1a2", feedRow) // feedRow is a []any +``` +::: +:::: diff --git a/docs/flights.md b/docs/flights.md new file mode 100644 index 0000000..6807180 --- /dev/null +++ b/docs/flights.md @@ -0,0 +1,325 @@ +# Live Flights + +The Real Time Flight Tracker is the feed behind the FlightRadar24 map. Each +entry becomes a `Flight`, with its position, altitude, speed and codes. + +## Getting flights + +Without arguments, the feed returns the flights of the whole globe, up to the +`limit` of the [flight tracker settings](tracker-config.md) (5000 by default). + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +flights = fr_api.get_flights() + +for flight in flights[:10]: + print(flight) # <(B77W) A6-EBM - Altitude: 35000 - Ground Speed: 480 - Heading: 90> +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const flights = await frApi.getFlights(); + +for (const flight of flights.slice(0, 10)) { + console.log(flight.callsign, flight.aircraftCode, flight.registration); +} +``` +::: + +:::{tab-item} Go +:sync: go + +```go +flights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{}) + +if err != nil { + log.Fatal(err) +} + +for _, flight := range flights[:min(10, len(flights))] { + fmt.Println(flight) // <(B77W) A6-EBM - Altitude: 35000 - Ground Speed: 480 - Heading: 90> +} +``` +::: +:::: + +## Filtering + +Four filters narrow the search, and they can be combined: + +| Filter | Meaning | Example | +|---|---|---| +| airline | ICAO code of the airline | `"UAE"` | +| bounds | Area to look at, as a `"y1,y2,x1,x2"` string (see [Zones and bounds](zones.md)) | `"73,-12,-156,38"` | +| registration | Aircraft registration | `"A6-EBM"` | +| aircraft type | Aircraft model code | `"B77W"` | + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +zone = fr_api.get_zones()["northamerica"] +bounds = fr_api.get_bounds(zone) + +emirates_flights = fr_api.get_flights( + airline="UAE", + aircraft_type="B77W", + bounds=bounds, +) +``` + +The signature is +`get_flights(airline=None, bounds=None, registration=None, aircraft_type=None, *, details=False)`. +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const zone = frApi.getZones()["northamerica"]; +const bounds = frApi.getBounds(zone); + +// getFlights(airline, bounds, registration, aircraftType, details) +const emiratesFlights = await frApi.getFlights("UAE", bounds, null, "B77W"); +``` + +The arguments are positional: pass `null` for the filters you do not use. +::: + +:::{tab-item} Go +:sync: go + +```go +bounds := client.GetBounds(client.GetZones()["northamerica"]) + +emiratesFlights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{ + Airline: "UAE", + AircraftType: "B77W", + Bounds: bounds, +}) +``` + +`FlightSearch` has the fields `Airline`, `Bounds`, `Registration`, +`AircraftType` and `Details`. Leave out the ones you do not use. +::: +:::: + +More options, such as which data sources to include or how many flights to +return, live in the [flight tracker settings](tracker-config.md). + +## Flight details + +The feed carries the essentials only. The details of a flight (airline and +aircraft names, origin and destination airports, status, times, trail) come from +a second request, and are then attached to the flight. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +flight_details = fr_api.get_flight_details(flight) # dict, as FR24 sends it +flight.set_flight_details(flight_details) + +print("Flying to", flight.destination_airport_name) +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const flightDetails = await frApi.getFlightDetails(flight); // object, as FR24 sends it +flight.setFlightDetails(flightDetails); + +console.log("Flying to", flight.destinationAirportName); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +details, err := client.GetFlightDetails(ctx, flight) // map[string]any, as FR24 sends it + +if err != nil { + log.Fatal(err) +} + +flight.SetFlightDetails(details) + +fmt.Println("Flying to", flight.Details.DestinationAirportName) +``` + +In Go the detail values live in `flight.Details`, which is `nil` until +`SetFlightDetails` is called. `flight.Details.Raw` keeps the whole payload. +::: +:::: + +### Details for every flight at once + +Ask for the details in the search itself and they are fetched for every flight +found, several requests at a time (8 by default, see +[Configuration](client.md#concurrency)). + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +flights = fr_api.get_flights(airline="GLO", details=True) + +for flight in flights: + print(flight.callsign, "→", flight.destination_airport_name) +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const flights = await frApi.getFlights("GLO", null, null, null, true); + +for (const flight of flights) { + console.log(flight.callsign, "→", flight.destinationAirportName); +} +``` +::: + +:::{tab-item} Go +:sync: go + +```go +flights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{ + Airline: "GLO", + Details: true, +}) + +for _, flight := range flights { + fmt.Println(flight.Callsign, "→", flight.Details.DestinationAirportName) +} +``` +::: +:::: + +```{note} +This makes one extra request per flight. Combine it with a filter: asking for +the details of thousands of flights is slow and likely to get you blocked. +``` + +## Formatted values + +A flight can render its numbers with their units. + +| Python | Node.js | Go | Example | +|---|---|---|---| +| `get_altitude()` | `getAltitude()` | `GetAltitude()` | `"35000 ft"` | +| `get_flight_level()` | `getFlightLevel()` | `GetFlightLevel()` | `"350 FL"` | +| `get_ground_speed()` | `getGroundSpeed()` | `GetGroundSpeed()` | `"480 kts"` | +| `get_heading()` | `getHeading()` | `GetHeading()` | `"90°"` | +| `get_vertical_speed()` | `getVerticalSpeed()` | `GetVerticalSpeed()` | `"-1280 fpm"` | + +The flight level is shown from 10000 ft up; below that, `get_flight_level()` +returns the same as `get_altitude()`. Each of these returns `"N/A"` when the +feed did not send the value. + +## Checking a flight against criteria + +`check_info` tells whether a flight matches one or more values. A plain name +compares for equality; with the `min` or `max` prefix it compares numerically. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +flight.check_info(min_altitude=6700, max_altitude=13000, airline_icao="THY") # True or False +``` + +The prefixes are `min_` and `max_`. A name the flight does not have is ignored. +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +flight.checkInfo({ minAltitude: 6700, maxAltitude: 13000, airlineIcao: "THY" }); // true or false +``` + +The prefixes are `min` and `max`, followed by the attribute name in camelCase. +A name the flight does not have is ignored. +::: + +:::{tab-item} Go +:sync: go + +```go +matched, err := flight.CheckInfo(map[string]any{ + "min_altitude": 6700, + "max_altitude": 13000, + "airline_icao": "THY", +}) +``` + +The keys are the snake_case names the Python SDK uses, with the prefixes `min_` +and `max_`. A name that exists nowhere is an error, instead of being ignored. +::: +:::: + +A handy way to filter a list you already have: + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +cruising = [flight for flight in flights if flight.check_info(min_altitude=30000)] +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const cruising = flights.filter((flight) => flight.checkInfo({ minAltitude: 30000 })); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +var cruising []*flightradarapi.Flight + +for _, flight := range flights { + if matched, _ := flight.CheckInfo(map[string]any{"min_altitude": 30000}); matched { + cruising = append(cruising, flight) + } +} +``` +::: +:::: + +## See also + +- [Flight reference](flight.md): every attribute of a flight. +- [Distances](distances.md): how far a flight is from an airport, or from another flight. diff --git a/docs/index.md b/docs/index.md index 65225ff..2c6abfc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,72 +1,218 @@ -# FlightRadarAPI Documentation +--- +og:description: Unofficial SDK for FlightRadar24, for Python, Node.js and Go. Live flights, airports, airlines, zones and more, with the same features in every language. +myst: + html_meta: + description: "Unofficial SDK for FlightRadar24, for Python, Node.js and Go. Live flights, airports, airlines, zones and more, with the same features in every language." +--- + +# FlightRadarAPI + +

FlightRadarAPI

+ +

Unofficial SDK for FlightRadar24

+ +Live flights, airports, airlines, zones and more from +[FlightRadar24](https://www.flightradar24.com/), for **Python**, **Node.js** and +**Go**, with the same features in every language. + + + + + + + + + + + + + + + + + + + + + + +
tests + Python Package + Node.js Package + Go Package +
python + PyPI version + Python 3.10+ + Total downloads + Monthly downloads +
node.js + npm version + Node.js 18.17+ +
go + Go Reference + Go 1.26+ +
license + License +
+ +```{warning} +This is **not** an official FlightRadar24 API, and it should only be used for +your own educational purposes. If you are interested in accessing Flightradar24 +data commercially, use the [official FR24 API](https://fr24api.flightradar24.com/) +or contact [business@fr24.com](mailto:business@fr24.com). See +[Flightradar24's terms and conditions](https://www.flightradar24.com/terms-and-conditions). +``` + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```bash +pip install FlightRadarAPI +``` + +```python +from FlightRadarAPI import FlightRadar24API + +fr_api = FlightRadar24API() + +for flight in fr_api.get_flights(airline="UAE")[:5]: + print(flight.callsign, flight.get_flight_level(), flight.get_ground_speed()) +``` +::: + +:::{tab-item} Node.js +:sync: node + +```bash +npm install flightradarapi +``` + +```javascript +const { FlightRadar24API } = require("flightradarapi"); + +const frApi = new FlightRadar24API(); + +const flights = await frApi.getFlights("UAE"); + +for (const flight of flights.slice(0, 5)) { + console.log(flight.callsign, flight.getFlightLevel(), flight.getGroundSpeed()); +} +``` +::: + +:::{tab-item} Go +:sync: go + +```bash +go get github.com/JeanExtreme002/FlightRadarAPI/go@latest +``` + +```go +client := flightradarapi.New() + +flights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{Airline: "UAE"}) + +if err != nil { + log.Fatal(err) +} + +for _, flight := range flights[:min(5, len(flights))] { + fmt.Println(flight.Callsign, flight.GetFlightLevel(), flight.GetGroundSpeed()) +} +``` +::: +:::: + +## What's inside + +| Area | Topics | +|---|---| +| **Getting started** | [Installation](installation.md), [Quick Start](quickstart.md) | +| **Flights** | [Live flights](flights.md), [Zones and bounds](zones.md), [Flight tracker settings](tracker-config.md), [Distances](distances.md) | +| **Airports and airlines** | [Airports](airports.md), [Airlines, logos and flags](airlines.md) | +| **More data** | [Search](searching.md), [Most tracked, disruptions and eruptions](live-data.md) | +| **Account** | [Login, bookmarks and flight history](account.md) | +| **Client** | [Configuration](client.md), [Errors and retries](errors.md) | +| **Reference** | [Client methods](api.md), [Flight](flight.md), [Airport](airport.md), [Countries](countries.md), [Differences between the SDKs](sdk-differences.md) | + +Every page shows its examples in the three languages: pick yours in any tab and +the rest of the site follows. + +New here? Start with [Installation](installation.md) and the +[Quick Start](quickstart.md). + +```{toctree} +:caption: Getting Started +:hidden: + +installation +quickstart +``` + +```{toctree} +:caption: Flights +:hidden: + +flights +zones +tracker-config +distances +``` -Unofficial SDK for [FlightRadar24](https://www.flightradar24.com/) for Python 3, Node.js and Go. +```{toctree} +:caption: Airports & Airlines +:hidden: -This SDK should only be used for your own educational purposes. If you are interested in accessing Flightradar24 data commercially, please contact [business@fr24.com](business@fr24.com). - -See more information at [Flightradar24's terms and conditions](https://www.flightradar24.com/terms-and-conditions). - -[![Python Package](https://github.com/JeanExtreme002/FlightRadarAPI/workflows/Python%20Package/badge.svg)](https://github.com/JeanExtreme002/FlightRadarAPI/actions) -[![Node.js Package](https://github.com/JeanExtreme002/FlightRadarAPI/actions/workflows/node-package.yml/badge.svg)](https://github.com/JeanExtreme002/FlightRadarAPI/actions) -[![Go Package](https://github.com/JeanExtreme002/FlightRadarAPI/actions/workflows/go-package.yml/badge.svg)](https://github.com/JeanExtreme002/FlightRadarAPI/actions) -[![License](https://img.shields.io/pypi/l/FlightRadarAPI)](https://github.com/JeanExtreme002/FlightRadarAPI) -[![Pypi](https://img.shields.io/pypi/v/FlightRadarAPI?logo=pypi)](https://pypi.org/project/FlightRadarAPI/) -[![Python Version](https://img.shields.io/badge/python-3.10+-8A2BE2)](https://pypi.org/project/FlightRadarAPI/) -[![Npm](https://img.shields.io/npm/v/flightradarapi?logo=npm&color=red)](https://www.npmjs.com/package/flightradarapi) -[![Node Version](https://img.shields.io/badge/node-18.17+-339933)](https://www.npmjs.com/package/flightradarapi) -[![Go Reference](https://pkg.go.dev/badge/github.com/JeanExtreme002/FlightRadarAPI/go.svg)](https://pkg.go.dev/github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi) -[![Go Version](https://img.shields.io/badge/go-1.26+-00ADD8)](https://go.dev/dl/) -[![Downloads](https://static.pepy.tech/personalized-badge/flightradarapi?period=total&units=international_system&left_color=grey&right_color=orange&left_text=downloads)](https://pypi.org/project/FlightRadarAPI/) -[![Frequency](https://img.shields.io/pypi/dm/flightradarapi?style=flat&label=frequency)](https://pypi.org/project/FlightRadarAPI/) - -!!! info - This is NOT an official FlightRadar24 API. You can access their official API [here](https://fr24api.flightradar24.com/). - -
- -- :octicons-file-code-16:{ .lg .middle } __100% Free and Open Source!__ - - --- - - The code is open source and available for inspection on GitHub. - - -- :material-sticker-check-outline:{ .lg .middle } __Python, Node.js and Go__ - - --- - - Packages are available for use on Python, Node.js and Go - -
- -
- - - -
- -## Installation - -=== "Python" - - To install FlightRadarAPI for Python using [pip](https://pypi.org/project/FlightRadarAPI/), run the following command in your terminal: - - ```bash - pip install FlightRadarAPI - ``` - -=== "Node.js" - - To install FlightRadarAPI for Node.js using [npm](https://www.npmjs.com/package/flightradarapi), run the following command in your terminal: - - ```bash - npm install flightradarapi - ``` - -=== "Go" - - To install FlightRadarAPI for Go, run the following command in your terminal: - - ```bash - go get github.com/JeanExtreme002/FlightRadarAPI/go@latest - ``` +airports +airlines +``` + +```{toctree} +:caption: More Data +:hidden: + +searching +live-data +``` + +```{toctree} +:caption: Account +:hidden: + +account +``` + +```{toctree} +:caption: Client +:hidden: + +client +errors +``` + +```{toctree} +:caption: API Reference +:hidden: + +api +flight +airport +countries +sdk-differences +``` + +```{toctree} +:caption: Project +:hidden: + +projects +contributing +license +GitHub +PyPI +npm +pkg.go.dev +``` diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..034351c --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,98 @@ +# Installation + +FlightRadarAPI ships as three packages, one per language. They carry the same +features and are released together, under the same version number. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +Install from [PyPI](https://pypi.org/project/FlightRadarAPI/): + +```bash +pip install FlightRadarAPI +``` + +```python +from FlightRadarAPI import FlightRadar24API +``` +::: + +:::{tab-item} Node.js +:sync: node + +Install from [npm](https://www.npmjs.com/package/flightradarapi): + +```bash +npm install flightradarapi +``` + +```javascript +const { FlightRadar24API } = require("flightradarapi"); +``` + +TypeScript declarations are bundled with the package. +::: + +:::{tab-item} Go +:sync: go + +Add the module to your project: + +```bash +go get github.com/JeanExtreme002/FlightRadarAPI/go@latest +``` + +```go +import "github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi" +``` +::: +:::: + +## Requirements + +| SDK | Runtime | Dependencies | +|---|---|---| +| Python | Python 3.10 or later | `curl_cffi`, `Brotli`, `beautifulsoup4` | +| Node.js | Node.js 18.17 or later | `undici`, `node-html-parser` | +| Go | Go 1.26 or later | `golang.org/x/net`, `golang.org/x/text`, `github.com/andybalholm/brotli` | + +The dependencies are installed for you by `pip`, `npm` and `go get`. + +## Checking the installed version + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +import FlightRadarAPI + +print(FlightRadarAPI.__version__) +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const { version } = require("flightradarapi"); + +console.log(version); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +fmt.Println(flightradarapi.Version) +``` +::: +:::: + +This documentation describes version {{ release }}. diff --git a/docs/license.md b/docs/license.md new file mode 100644 index 0000000..bcca920 --- /dev/null +++ b/docs/license.md @@ -0,0 +1,13 @@ +# License + +FlightRadarAPI is released under the **MIT License**. The full text is below and +in the [`LICENSE`](https://github.com/JeanExtreme002/FlightRadarAPI/blob/main/LICENSE) +file. + +```{literalinclude} ../LICENSE +:language: none +``` + +FlightRadarAPI is not affiliated with or endorsed by Flightradar24. This SDK +should only be used for your own educational purposes. See +[Flightradar24's terms and conditions](https://www.flightradar24.com/terms-and-conditions). diff --git a/docs/live-data.md b/docs/live-data.md new file mode 100644 index 0000000..8ae7977 --- /dev/null +++ b/docs/live-data.md @@ -0,0 +1,106 @@ +# Most Tracked, Disruptions and Eruptions + +Three calls return a snapshot of what FlightRadar24 is showing right now. None +takes a parameter, and each returns the payload as FlightRadar24 sends it: a +`dict` in Python, an `object` in Node.js and a `map[string]any` in Go. + +```{note} +The shape of these payloads is defined by FlightRadar24 and may change. Inspect +a response before relying on a particular field. +``` + +## Most tracked flights + +The flights most people are following on FlightRadar24 at the moment. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +most_tracked = fr_api.get_most_tracked() +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const mostTracked = await frApi.getMostTracked(); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +mostTracked, err := client.GetMostTracked(ctx) +``` +::: +:::: + +## Airport disruptions + +The airports currently affected by delays and cancellations. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +disruptions = fr_api.get_airport_disruptions() +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const disruptions = await frApi.getAirportDisruptions(); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +disruptions, err := client.GetAirportDisruptions(ctx) +``` +::: +:::: + +## Volcanic eruptions + +The boundaries of the volcanic eruptions and ash clouds impacting aviation. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +eruptions = fr_api.get_volcanic_eruptions() +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const eruptions = await frApi.getVolcanicEruptions(); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +eruptions, err := client.GetVolcanicEruptions(ctx) +``` +::: +:::: diff --git a/docs/projects.md b/docs/projects.md index abb2550..08e2746 100644 --- a/docs/projects.md +++ b/docs/projects.md @@ -1,47 +1,51 @@ ---- -title: Projects using FlightRadarAPI -description: Nice projects that use FlightRadarAPI ---- +# Projects Using FlightRadarAPI -
+Research, products and weekend builds made with FlightRadarAPI. -- [__Automatic Calibration in Crowd-sourced Network of Spectrum Sensors__](https://dl.acm.org/doi/10.1145/3626111.3628187) +::::{grid} 1 1 2 2 +:gutter: 3 - --- +:::{grid-item-card} Automatic Calibration in Crowd-sourced Network of Spectrum Sensors +:link: https://dl.acm.org/doi/10.1145/3626111.3628187 - By Ali Abedi, Joshua Sanz, Anant Sahai, from University of California (UC), Berkeley +By Ali Abedi, Joshua Sanz, Anant Sahai, from University of California (UC), Berkeley +::: +:::{grid-item-card} Airline Status Tracker for top 10 Airport Departures in North America +:link: https://people.ischool.berkeley.edu/~frank.song/flight.html -- [__Airline Status Tracker for top 10 Airport Departures in North America__](https://people.ischool.berkeley.edu/~frank.song/flight.html) +By Adeleke Coker, Frank Song, Greg Chi, from University of California (UC), Berkeley, School of Information +::: - --- +:::{grid-item-card} Flight Tracker with Weather +:link: https://magpi.raspberrypi.com/articles/flight-tracker-with-weather - By Adeleke Coker, Frank Song, Greg Chi, from University of California (UC), Berkeley, School of Information +By Adam Paulson, a Reddit user going by the name C0wsaysmoo +::: -- [__Flight Tracker with Weather__](https://magpi.raspberrypi.com/articles/flight-tracker-with-weather) +:::{grid-item-card} Design and implementation project of an aircraft mobile node module for the SeamSAT-LEO constellation simulator +:link: https://upcommons.upc.edu/bitstream/handle/2117/394691/TFG.pdf?sequence=2&isAllowed=y - --- +By David Anton Dobarro, graduate in Aerospace Technologies Engineering from Universitat Politècnica de Catalunya +::: - By Adam Paulson, a Reddit user going by the name C0wsaysmoo +:::{grid-item-card} Fridge Flight Tracker +:link: https://blog.colinwaddell.com/flight-tracker/ -- [__Design and implementation project of an aircraft mobile node module for the SeamSAT-LEO constellation simulator__](https://upcommons.upc.edu/bitstream/handle/2117/394691/TFG.pdf?sequence=2&isAllowed=y) +By Colin Waddell, a Glasgow based programmer, website designer and electronics specialist +::: - --- +:::{grid-item-card} Plane spotting Telegram Bot +:link: https://github.com/ViLsonCake/avgeek-telegram-bot - By David Anton Dobarro, graduate in Aerospace Technologies Engineering from Universitat Politècnica de Catalunya +By Timofey Dmitrenko, aviation geek and Java Spring Boot programmer +::: +:::: -- [__Fridge Flight Tracker__](https://blog.colinwaddell.com/flight-tracker/) +Built something with FlightRadarAPI? Add it to this page. - --- +```{button-link} https://github.com/JeanExtreme002/FlightRadarAPI/edit/main/docs/projects.md +:color: primary - By Colin Waddell, a Glasgow based programmer, website designer and electronics specialist - -- [__Plane spotting Telegram Bot__](https://github.com/ViLsonCake/avgeek-telegram-bot) - - --- - - By Timofey Dmitrenko, aviation geek and Java Spring Boot programmer - -
- -[Contribute Your Own :writing_hand:](https://github.com/JeanExtreme002/FlightRadarAPI/edit/main/docs/projects.md){ .md-button .md-button--primary } +Contribute your own +``` diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..75159e1 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,175 @@ +# Quick Start + +Everything starts with a client. It needs no account and no API key: it reads +the same public data the FlightRadar24 website shows. + +## Create a client + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +from FlightRadarAPI import FlightRadar24API + +fr_api = FlightRadar24API() +``` + +Calls are blocking and return when the data has arrived. +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const { FlightRadar24API } = require("flightradarapi"); + +const frApi = new FlightRadar24API(); +``` + +Every method that talks to FlightRadar24 returns a `Promise`. +::: + +:::{tab-item} Go +:sync: go + +```go +package main + +import ( + "context" + "fmt" + "log" + + "github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi" +) + +func main() { + ctx := context.Background() + client := flightradarapi.New() + + // ... +} +``` + +Construction cannot fail. Every method that talks to FlightRadar24 takes a +`context.Context` and returns an `error` alongside its result. +::: +:::: + +The examples in the rest of this documentation assume the client above: +`fr_api` in Python, `frApi` in Node.js, and `client` with a `ctx` in Go. + +## Fetch the basics + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +flights = fr_api.get_flights() # list of Flight +airports = fr_api.get_airports(["brazil"]) # list of Airport +airlines = fr_api.get_airlines() # list of dict +zones = fr_api.get_zones() # dict +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const flights = await frApi.getFlights(); // Array of Flight +const airports = await frApi.getAirports(["brazil"]); // Array of Airport +const airlines = await frApi.getAirlines(); // Array of object +const zones = frApi.getZones(); // object +``` +::: + +:::{tab-item} Go +:sync: go + +```go +flights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{}) // []*Flight +airports, err := client.GetAirports(ctx, []flightradarapi.Country{ + flightradarapi.CountryBrazil, +}) // []*Airport +airlines, err := client.GetAirlines(ctx) // []Airline +zones := client.GetZones() // map[string]Zone +``` +::: +:::: + +## Follow one flight + +The live feed gives each flight its position, speed and codes. One more call +adds the details: airline and aircraft names, airports, times and trail. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +flight = fr_api.get_flights(airline="UAE")[0] + +print(flight.callsign, flight.get_altitude(), flight.get_heading()) + +flight.set_flight_details(fr_api.get_flight_details(flight)) + +print("Flying to", flight.destination_airport_name) +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const [flight] = await frApi.getFlights("UAE"); + +console.log(flight.callsign, flight.getAltitude(), flight.getHeading()); + +flight.setFlightDetails(await frApi.getFlightDetails(flight)); + +console.log("Flying to", flight.destinationAirportName); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +flights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{Airline: "UAE"}) + +if err != nil { + log.Fatal(err) +} + +flight := flights[0] + +fmt.Println(flight.Callsign, flight.GetAltitude(), flight.GetHeading()) + +details, err := client.GetFlightDetails(ctx, flight) + +if err != nil { + log.Fatal(err) +} + +flight.SetFlightDetails(details) + +fmt.Println("Flying to", flight.Details.DestinationAirportName) +``` +::: +:::: + +## Where to go next + +- [Live flights](flights.md): filtering, details and the helpers of a flight. +- [Zones and bounds](zones.md): looking at one region, or around one point. +- [Airports](airports.md): one airport, or the airports of whole countries. +- [Configuration](client.md): timeouts, concurrency and TLS impersonation. +- [Errors and retries](errors.md): what can fail and how to retry it. diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..daa77b3 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,9 @@ +# Pinned so a compromised release cannot be pulled in automatically: these +# packages execute during the docs build. Bump deliberately, not implicitly. +# sphinx-rtd-theme 3.x does not yet support Sphinx 9. +sphinx==8.2.3 +sphinx-rtd-theme==3.1.0 +myst-parser==5.2.0 +sphinx-copybutton==0.5.2 +sphinx-design==0.8.0 +sphinxext-opengraph==0.13.0 diff --git a/docs/sdk-differences.md b/docs/sdk-differences.md new file mode 100644 index 0000000..f9e2efe --- /dev/null +++ b/docs/sdk-differences.md @@ -0,0 +1,73 @@ +# Differences Between the SDKs + +The three SDKs carry the same features and behave the same way. What changes is +the shape each language gives them. + +## At a glance + +| | Python | Node.js | Go | +|---|---|---|---| +| Package | `FlightRadarAPI` | `flightradarapi` | `github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi` | +| Client | `FlightRadar24API()` | `new FlightRadar24API()` | `flightradarapi.New()`, a `*Client` | +| Client options | keyword arguments | one options object | one `Options` struct | +| Names | `snake_case` | `camelCase` | `PascalCase` | +| Calls | blocking | `Promise` | `context.Context` in, `error` out | +| Timeout unit | seconds | milliseconds | `time.Duration` | +| Retry delays | seconds | milliseconds | `time.Duration` | +| Failures | exceptions | exceptions | `error` values | +| Missing value | `"N/A"` or `None` | `"N/A"` or `null` | zero value: `""` or `nil` | +| Log in at construction | yes, with `user` and `password` | no | no | + +## Python and Node.js + +The two are close. The differences are in how arguments are passed: + +| | Python | Node.js | +|---|---|---| +| Flight search | `get_flights(airline="UAE", aircraft_type="B77W")` | `getFlights("UAE", null, null, "B77W")` | +| Details flag | keyword-only: `get_airport("VNLK", details=True)` | positional: `getAirport("VNLK", true)` | +| Tracker options | `set_flight_tracker_config(limit=10)` | `setFlightTrackerConfig(null, { limit: 10 })` | +| Checking a flight | `check_info(min_altitude=6700)` | `checkInfo({ minAltitude: 6700 })` | +| TLS impersonation | name of a `curl_cffi` profile | object with `ciphers`, `sigalgs`, `ecdhCurve` | +| Unknown tracker option | `KeyError` | `RangeError` | +| Invalid airport code or file type | `ValueError` | `TypeError` | + +## Go + +The Go package follows the conventions of the language, so its surface differs +more. The package name is part of every identifier, which is why the client is +`Client` rather than `FlightRadar24API`: the way it is `http.Client` and not +`http.HTTPClient`. + +| Python and Node.js | Go | +|---|---| +| `FlightRadar24API` | `Client`, built with `New` | +| `FlightRadar24API(timeout=..., max_workers=...)` | `New(Options{Timeout: ..., MaxWorkers: ...})` | +| `FlightRadar24API(user, password)` logs in (Python only) | `client.Login(ctx, user, password)` | +| blocking calls and promises | `context.Context` on every request | +| exceptions | `error` values wrapping `ErrFlightRadar` | +| `FlightRadarError` (base class) | `ErrFlightRadar`, wrapped by every error | +| `Countries.BRAZIL` | `CountryBrazil`, with `AllCountries()` to enumerate | +| `get_flights(airline, bounds, registration, aircraft_type, details)` | `GetFlights(ctx, FlightSearch{...})` | +| `get_airports()` for every airport | `GetAirports(ctx, nil)` | +| default arguments (`flight_limit=100`, `limit=50`) | zero means the same default | +| `set_flight_tracker_config(config, limit=10)` | `SetFlightTrackerConfig(&config, map[string]string{"limit": "10"})` | +| `check_info(min_altitude=6700)` | `CheckInfo(map[string]any{"min_altitude": 6700})` | +| `check_info` ignores a name the flight does not have | `CheckInfo` returns an error for it | +| `flight.destination_airport_name` | `flight.Details.DestinationAirportName` | +| `Airport.from_details(payload)` | `NewAirportFromDetails(payload)` | +| `Flight(flight_id, info)` | `NewFlight(flightID, info)` | +| `(bytes, extension)` tuple | `*Image`, with `Data` and `Extension` | +| airline as a dictionary: `airline["n_aircrafts"]` | `Airline` struct: `airline.NumAircrafts` | +| zone as a dictionary: `zone["tl_y"]`, `zone["subzones"]` | `Zone` struct: `zone.TLY`, `zone.Subzones` | +| missing value is the string `"N/A"` | zero value: `""` or a nil pointer | +| `impersonate` | `Options.TLSProfile`, or a whole `Options.HTTPClient` | +| warnings through `logging` and `console.warn` | warnings through `log/slog`, see `SetLogger` | +| `parsers` module (internal) | unexported functions | + +Every method of `FlightRadar24API` has a counterpart in `Client`, with the same +parameters, kept that way by a test that reads the Python source. + +The Go package is also documented on +[pkg.go.dev](https://pkg.go.dev/github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi), +from its source. diff --git a/docs/searching.md b/docs/searching.md new file mode 100644 index 0000000..f0d4142 --- /dev/null +++ b/docs/searching.md @@ -0,0 +1,71 @@ +# Searching + +`search` runs a query through the search of the FlightRadar24 website. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +results = fr_api.search("Emirates") + +for category, items in results.items(): + print(category, len(items)) +``` + +The signature is `search(query, limit=50)`. +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const results = await frApi.search("Emirates"); + +for (const [category, items] of Object.entries(results)) { + console.log(category, items.length); +} +``` + +The signature is `search(query, limit = 50)`. +::: + +:::{tab-item} Go +:sync: go + +```go +results, err := client.Search(ctx, "Emirates", 0) + +if err != nil { + log.Fatal(err) +} + +for category, items := range results { + fmt.Println(category, len(items)) +} +``` + +A limit of zero means the default of 50. +::: +:::: + +## The result + +The result groups what was found by category. Each key is a category, as +FlightRadar24 names it in the response, and each value is the list of results of +that category, as FlightRadar24 sends them. + +| SDK | Type | +|---|---| +| Python | `dict` of category to `list` | +| Node.js | `object` of category to `Array` | +| Go | `map[string][]any` | + +`limit` is sent to FlightRadar24 as the maximum number of results. + +```{note} +The categories and the fields of each result are defined by FlightRadar24 and +may change. Inspect a response before relying on a particular field. +``` diff --git a/docs/tracker-config.md b/docs/tracker-config.md new file mode 100644 index 0000000..c50aa90 --- /dev/null +++ b/docs/tracker-config.md @@ -0,0 +1,166 @@ +# Flight Tracker Settings + +The client keeps a set of options that it sends with every +[flight search](flights.md): which data sources to include, whether to show +aircraft on the ground, how many flights to return. They are the same switches +as the filters of the FlightRadar24 map, and they live in a +`FlightTrackerConfig`. + +## Options + +Every value is a string holding a number. The switches take `"1"` (on) or `"0"` +(off). + +| Python and Node.js | Go | Default | Meaning | +|---|---|---|---| +| `faa` | `FAA` | `"1"` | Include data from the FAA | +| `satellite` | `Satellite` | `"1"` | Include satellite-based tracking | +| `mlat` | `MLAT` | `"1"` | Include MLAT (multilateration) positions | +| `flarm` | `FLARM` | `"1"` | Include FLARM positions | +| `adsb` | `ADSB` | `"1"` | Include ADS-B positions | +| `gnd` | `GND` | `"1"` | Include aircraft on the ground | +| `air` | `Air` | `"1"` | Include aircraft in the air | +| `vehicles` | `Vehicles` | `"1"` | Include ground vehicles | +| `estimated` | `Estimated` | `"1"` | Include estimated positions | +| `maxage` | `MaxAge` | `"14400"` | Maximum age of a position, in seconds | +| `gliders` | `Gliders` | `"1"` | Include gliders | +| `stats` | `Stats` | `"1"` | Include the feed statistics in the response | +| `limit` | `Limit` | `"5000"` | Maximum number of flights returned | + +```{note} +These are the query parameters of FlightRadar24's own feed, and FlightRadar24 +does not document them. The meanings above describe how its map uses them. +``` + +## Changing the settings + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +Pass single options as keyword arguments: + +```python +fr_api.set_flight_tracker_config(limit=10, gnd=0) + +flights = fr_api.get_flights() # at most 10 flights, none on the ground +``` + +Or read the current settings, change them and set them back: + +```python +config = fr_api.get_flight_tracker_config() +config.limit = "10" + +fr_api.set_flight_tracker_config(config) +``` + +Both forms can be combined: `set_flight_tracker_config(config, gnd=0)` replaces +the settings with `config` and then applies the keyword arguments on top. + +Keyword values may be integers or numeric strings. An unknown option raises +`KeyError` and a value that is not a number raises `TypeError`. +::: + +:::{tab-item} Node.js +:sync: node + +Pass single options as an object, in the second argument: + +```javascript +frApi.setFlightTrackerConfig(null, { limit: 10, gnd: 0 }); + +const flights = await frApi.getFlights(); // at most 10 flights, none on the ground +``` + +Or read the current settings, change them and set them back: + +```javascript +const config = frApi.getFlightTrackerConfig(); +config.limit = 10; + +frApi.setFlightTrackerConfig(config); +``` + +Both forms can be combined: `setFlightTrackerConfig(config, { gnd: 0 })` +replaces the settings with `config` and then applies the object on top. + +Values may be numbers or numeric strings. An unknown option throws `RangeError` +and a value that is not a number throws `TypeError`, both when calling +`setFlightTrackerConfig` and when assigning to a `FlightTrackerConfig`. +::: + +:::{tab-item} Go +:sync: go + +Pass single options as a map, in the second argument: + +```go +err := client.SetFlightTrackerConfig(nil, map[string]string{"limit": "10", "gnd": "0"}) + +flights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{}) // at most 10 flights, none on the ground +``` + +The keys of the map are the lower-case names of the first column. + +Or read the current settings, change them and set them back: + +```go +config := client.GetFlightTrackerConfig() +config.Limit = "10" + +err := client.SetFlightTrackerConfig(&config, nil) +``` + +Either argument may be `nil`. With both, the config replaces the settings and +the map is applied on top. A field left empty in the config takes its default, +so `&flightradarapi.FlightTrackerConfig{Limit: "10"}` changes the limit only. + +An unknown option or a value that is not a number returns an error, and a +rejected update leaves the current settings untouched. +::: +:::: + +## Reading and resetting + +Reading the settings returns a copy: changing it has no effect until you set it +back. A new `FlightTrackerConfig` carries the defaults, so setting one resets +everything. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +from FlightRadarAPI import FlightTrackerConfig + +fr_api.set_flight_tracker_config(FlightTrackerConfig()) # back to the defaults +fr_api.set_flight_tracker_config(FlightTrackerConfig(limit="100")) # defaults, except the limit +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const { FlightTrackerConfig } = require("flightradarapi"); + +frApi.setFlightTrackerConfig(new FlightTrackerConfig()); // back to the defaults +frApi.setFlightTrackerConfig(new FlightTrackerConfig({ limit: 100 })); // defaults, except the limit +``` +::: + +:::{tab-item} Go +:sync: go + +```go +defaults := flightradarapi.NewFlightTrackerConfig() + +err := client.SetFlightTrackerConfig(&defaults, nil) // back to the defaults +``` +::: +:::: diff --git a/docs/zones.md b/docs/zones.md new file mode 100644 index 0000000..969df7f --- /dev/null +++ b/docs/zones.md @@ -0,0 +1,205 @@ +# Zones and Bounds + +The flight feed looks at a rectangle of the globe, described by a **bounds** +string. You can write one by hand, take it from a named **zone**, or compute it +around a point. + +## The bounds string + +Bounds are four comma-separated coordinates, `"y1,y2,x1,x2"`: + +| Part | Meaning | +|---|---| +| `y1` | Latitude of the top edge (north) | +| `y2` | Latitude of the bottom edge (south) | +| `x1` | Longitude of the left edge (west) | +| `x2` | Longitude of the right edge (east) | + +For example, `"72.57,33.57,-16.96,53.05"` covers Europe. Pass the string as the +bounds filter of a [flight search](flights.md#filtering). + +## Zones + +FlightRadar24 names the major regions of the globe. They are bundled with the +SDK, so reading them makes no request. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +zones = fr_api.get_zones() + +europe = zones["europe"] +# {"tl_y": 72.57, "tl_x": -16.96, "br_y": 33.57, "br_x": 53.05, "subzones": {...}} + +bounds = fr_api.get_bounds(europe) # "72.57,33.57,-16.96,53.05" +flights = fr_api.get_flights(bounds=bounds) +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const zones = frApi.getZones(); + +const europe = zones["europe"]; +// { tl_y: 72.57, tl_x: -16.96, br_y: 33.57, br_x: 53.05, subzones: {...} } + +const bounds = frApi.getBounds(europe); // "72.57,33.57,-16.96,53.05" +const flights = await frApi.getFlights(null, bounds); +``` + +`getZones()` and `getBounds()` are synchronous. +::: + +:::{tab-item} Go +:sync: go + +```go +zones := client.GetZones() + +europe := zones["europe"] +// Zone{TLY: 72.57, TLX: -16.96, BRY: 33.57, BRX: 53.05, Subzones: map[string]Zone{...}} + +bounds := client.GetBounds(europe) // "72.57,33.57,-16.96,53.05" +flights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{Bounds: bounds}) +``` +::: +:::: + +A zone is the top-left corner (`tl_y`, `tl_x`) and the bottom-right corner +(`br_y`, `br_x`) of the rectangle. Some zones are split further, under +`subzones`: + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +germany = zones["europe"]["subzones"]["germany"] +london = zones["europe"]["subzones"]["uk"]["subzones"]["london"] +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const germany = zones["europe"]["subzones"]["germany"]; +const london = zones["europe"]["subzones"]["uk"]["subzones"]["london"]; +``` +::: + +:::{tab-item} Go +:sync: go + +```go +germany := zones["europe"].Subzones["germany"] +london := zones["europe"].Subzones["uk"].Subzones["london"] +``` +::: +:::: + +### Available zones + +| Zone | Subzones | +|---|---| +| `europe` | `poland`, `germany`, `uk` (with `london`, `ireland`), `spain`, `france`, `ceur`, `scandinavia`, `italy` | +| `northamerica` | `na_n`, `na_c` (with `na_cny`, `na_cla`, `na_cat`, `na_cse`, `na_nw`, `na_ne`, `na_sw`, `na_se`, `na_cc`), `na_s` | +| `southamerica` | | +| `oceania` | | +| `asia` | `japan` | +| `africa` | | +| `atlantic` | | +| `maldives` | | +| `northatlantic` | | + +## Around a point + +To look at the sky above one place, build the bounds from its coordinates and a +radius in meters. The result is the square that extends that distance from the +point in each direction. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +# 52°34'04.7"N 13°16'57.5"E, and 2 km around it +bounds = fr_api.get_bounds_by_point(52.567967, 13.282644, 2000) + +flights = fr_api.get_flights(bounds=bounds) +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +// 52°34'04.7"N 13°16'57.5"E, and 2 km around it +const bounds = frApi.getBoundsByPoint(52.567967, 13.282644, 2000); + +const flights = await frApi.getFlights(null, bounds); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +// 52°34'04.7"N 13°16'57.5"E, and 2 km around it +bounds := client.GetBoundsByPoint(52.567967, 13.282644, 2000) + +flights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{Bounds: bounds}) +``` +::: +:::: + +## A custom region + +Any rectangle works: write the string yourself, or describe it as a zone. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +flights = fr_api.get_flights(bounds="73,-12,-156,38") + +# Or from the corners +bounds = fr_api.get_bounds({"tl_y": 73, "tl_x": -156, "br_y": -12, "br_x": 38}) +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const flights = await frApi.getFlights(null, "73,-12,-156,38"); + +// Or from the corners +const bounds = frApi.getBounds({ tl_y: 73, tl_x: -156, br_y: -12, br_x: 38 }); +``` +::: + +:::{tab-item} Go +:sync: go + +```go +flights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{Bounds: "73,-12,-156,38"}) + +// Or from the corners +bounds := client.GetBounds(flightradarapi.Zone{TLY: 73, TLX: -156, BRY: -12, BRX: 38}) +``` +::: +:::: diff --git a/go/README.md b/go/README.md index 618b070..4adb34b 100644 --- a/go/README.md +++ b/go/README.md @@ -69,4 +69,4 @@ for _, country := range flightradarapi.AllCountries() { } ``` ## Documentation -Explore the documentation of FlightRadarAPI package through [this site](https://JeanExtreme002.github.io/FlightRadarAPI/), or read the [Go reference](https://pkg.go.dev/github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi). +Explore the documentation of FlightRadarAPI package through [this site](https://flightradarapi.readthedocs.io/), or read the [Go reference](https://pkg.go.dev/github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi). diff --git a/nodejs/README.md b/nodejs/README.md index ccf63be..acc1259 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -67,4 +67,4 @@ Countries.FRANCE // "france" ``` ## Documentation -Explore the documentation of FlightRadarAPI package through [this site](https://JeanExtreme002.github.io/FlightRadarAPI/). +Explore the documentation of FlightRadarAPI package through [this site](https://flightradarapi.readthedocs.io/). diff --git a/python/Makefile b/python/Makefile index 6e93445..134fcac 100644 --- a/python/Makefile +++ b/python/Makefile @@ -64,8 +64,8 @@ help: @echo " $(YELLOW)check-deps$(NC) - Check for outdated dependencies" @echo " $(YELLOW)update-deps$(NC) - Update dependencies" @echo " $(YELLOW)security$(NC) - Run security audit" - @echo " $(YELLOW)docs$(NC) - Build the MkDocs site (output in ../site/)" - @echo " $(YELLOW)docs-serve$(NC) - Serve the MkDocs site locally with auto-reload" + @echo " $(YELLOW)docs$(NC) - Build the documentation (output in ../docs/_build/html/)" + @echo " $(YELLOW)docs-serve$(NC) - Serve the documentation locally with auto-reload" @echo " $(YELLOW)venv$(NC) - Create virtual environment" @echo " $(YELLOW)all$(NC) - Run full pipeline (install, lint, test, build)" @@ -245,22 +245,23 @@ security: $(PYTHON) -m pip_audit @echo "$(GREEN)Security audit completed!$(NC)" -# Build the MkDocs site. The configuration (`mkdocs.yml`) and the `docs/` -# directory live in the repository root, so we invoke MkDocs from one level -# up. Requires `mkdocs-material` (install with `pip install mkdocs-material` -# — kept out of `install-dev` because the docs site is rebuilt by CI, not -# every developer needs the dependency locally). +# Build the documentation with the same flags as CI and Read the Docs: warnings +# (including broken cross-references, via -n) fail the build. The sources live +# in `../docs/`; install the tooling with `pip install -r ../docs/requirements.txt` +# (kept out of `install-dev` because not every developer needs it locally). .PHONY: docs docs: - @echo "$(GREEN)Building MkDocs site...$(NC)" - cd .. && mkdocs build - @echo "$(GREEN)Site generated in ../site/$(NC)" + @echo "$(GREEN)Building the documentation...$(NC)" + $(PYTHON) -m sphinx -n -W --keep-going -b html ../docs ../docs/_build/html + @echo "$(GREEN)Documentation generated at ../docs/_build/html/index.html$(NC)" -# Serve the MkDocs site locally with auto-reload. +# Serve the documentation locally with auto-reload. sphinx-autobuild is +# installed on demand. .PHONY: docs-serve docs-serve: - @echo "$(GREEN)Serving MkDocs site at http://127.0.0.1:8000 ...$(NC)" - cd .. && mkdocs serve + @echo "$(GREEN)Serving the documentation at http://127.0.0.1:8000 ...$(NC)" + @$(PYTHON) -c "import sphinx_autobuild" 2>/dev/null || $(PIP) install sphinx-autobuild + $(PYTHON) -m sphinx_autobuild ../docs ../docs/_build/html # Full development pipeline. Uses `install-dev` (not `install`) so that # `lint`, `type-check`, and `test` have flake8 / mypy / pytest available diff --git a/python/README.md b/python/README.md index 4f045ef..b675c31 100644 --- a/python/README.md +++ b/python/README.md @@ -12,7 +12,7 @@ This SDK should only be used for your own educational purposes. If you are inter [![Npm](https://img.shields.io/npm/v/flightradarapi?logo=npm&color=red)](https://www.npmjs.com/package/flightradarapi) [![Node Version](https://img.shields.io/badge/node-18.17+-339933)](https://www.npmjs.com/package/flightradarapi) [![Go Reference](https://pkg.go.dev/badge/github.com/JeanExtreme002/FlightRadarAPI/go.svg)](https://pkg.go.dev/github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi) -[![Go Version](https://img.shields.io/badge/go-1.25+-00ADD8)](https://go.dev/dl/) +[![Go Version](https://img.shields.io/badge/go-1.26+-00ADD8)](https://go.dev/dl/) [![Downloads](https://static.pepy.tech/personalized-badge/flightradarapi?period=total&units=international_system&left_color=grey&right_color=orange&left_text=downloads)](https://pypi.org/project/FlightRadarAPI/) [![Frequency](https://img.shields.io/pypi/dm/flightradarapi?style=flat&label=frequency)](https://pypi.org/project/FlightRadarAPI/) @@ -47,4 +47,4 @@ zones = fr_api.get_zones() ``` ## Documentation -Explore the documentation of FlightRadarAPI package through [this site](https://JeanExtreme002.github.io/FlightRadarAPI/). +Explore the documentation of FlightRadarAPI package through [this site](https://flightradarapi.readthedocs.io/). diff --git a/python/pyproject.toml b/python/pyproject.toml index bf1ef3d..883b172 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -43,7 +43,7 @@ tests = [ [project.urls] "Homepage" = "https://github.com/JeanExtreme002/FlightRadarAPI" "Source Code" = "https://github.com/JeanExtreme002/FlightRadarAPI" -"Documentation" = "https://jeanextreme002.github.io/FlightRadarAPI/" +"Documentation" = "https://flightradarapi.readthedocs.io/" "Bug Reports" = "https://github.com/JeanExtreme002/FlightRadarAPI/issues" [tool.hatch.version] From f83a0ca599917ade0a7a1c66a315c2f7f00140e2 Mon Sep 17 00:00:00 2001 From: JeanExtreme002 Date: Sat, 14 Nov 2026 18:03:47 -0300 Subject: [PATCH 3/8] build: add a root Makefile for the documentation The docs belong to the whole repository, so their targets (install-docs, docs, docs-serve, docs-clean) move to a Makefile at the root. The docs targets of python/Makefile forward to it. --- CONTRIBUTING.md | 9 +++--- Makefile | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ python/Makefile | 21 +++++--------- 3 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 Makefile diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3b7c1ea..da2093f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,12 +56,13 @@ Sphinx. It is published on [Read the Docs](https://flightradarapi.readthedocs.io from `main`, and every pull request gets a preview build. ```bash -pip install -r docs/requirements.txt -python -m sphinx -n -W --keep-going -b html docs docs/_build/html +make venv # optional: creates .venv, which the targets below then use +make install-docs # installs Sphinx and the theme +make docs # builds into docs/_build/html +make docs-serve # live-reload server at http://127.0.0.1:8000 ``` -`make docs` and `make docs-serve`, in `python/`, do the same. Warnings fail the -build, here and in CI. +Run these from the repository root. Warnings fail the build, here and in CI. Each page shows its examples in the three languages, in tabs. When a change reaches the public surface, update the guide that covers it and the reference diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cec7ae7 --- /dev/null +++ b/Makefile @@ -0,0 +1,76 @@ +# Makefile for the FlightRadarAPI documentation. +# +# Each SDK has its own Makefile (python/, nodejs/, go/). This one covers what +# belongs to the repository as a whole: the documentation. + +# Variables +VENV_DIR = .venv +DOCS_DIR = docs +DOCS_BUILD_DIR = $(DOCS_DIR)/_build/html + +# Use the virtual environment when `make venv` has created it. +ifneq ($(wildcard $(VENV_DIR)/bin/python),) +PYTHON := $(VENV_DIR)/bin/python +else +PYTHON := python3 +endif +PIP = $(PYTHON) -m pip + +# Colors for output +GREEN = \033[0;32m +YELLOW = \033[0;33m +NC = \033[0m # No Color + +# Default target +.PHONY: help +help: + @echo "$(GREEN)FlightRadarAPI documentation Makefile$(NC)" + @echo "" + @echo "Setup:" + @echo " $(YELLOW)venv$(NC) - Create a virtual environment in $(VENV_DIR)" + @echo " $(YELLOW)install-docs$(NC) - Install the dependencies to build the documentation" + @echo "" + @echo "Documentation:" + @echo " $(YELLOW)docs$(NC) - Build the HTML documentation (output: $(DOCS_BUILD_DIR))" + @echo " $(YELLOW)docs-serve$(NC) - Live-reload docs server at http://127.0.0.1:8000" + @echo " $(YELLOW)docs-clean$(NC) - Remove the docs build directory" + @echo "" + @echo "The SDKs have their own Makefiles: python/, nodejs/ and go/." + +# Create virtual environment. `python3` literally: $(PYTHON) may already point +# at the environment being created. +.PHONY: venv +venv: + @echo "$(GREEN)Creating virtual environment...$(NC)" + python3 -m venv $(VENV_DIR) + @echo "$(YELLOW)To activate: source $(VENV_DIR)/bin/activate$(NC)" + +# Install the documentation dependencies. The build imports nothing from the +# SDKs, so none of them has to be installed. +.PHONY: install-docs +install-docs: + @echo "$(GREEN)Installing documentation dependencies...$(NC)" + $(PIP) install -r $(DOCS_DIR)/requirements.txt + +# Build the HTML documentation with the same flags as CI and Read the Docs: +# warnings, including broken cross-references (-n), fail the build. +.PHONY: docs +docs: + @echo "$(GREEN)Building HTML documentation...$(NC)" + $(PYTHON) -m sphinx -n -W --keep-going -b html $(DOCS_DIR) $(DOCS_BUILD_DIR) + @echo "$(GREEN)Documentation generated at $(DOCS_BUILD_DIR)/index.html$(NC)" + +# Live-reload docs server. sphinx-autobuild is installed on demand. After +# changing a toctree, restart it: it only rebuilds changed pages, so the +# sidebar on the other pages goes stale. +.PHONY: docs-serve +docs-serve: + @echo "$(GREEN)Starting live-reload docs server at http://127.0.0.1:8000$(NC)" + @$(PYTHON) -c "import sphinx_autobuild" 2>/dev/null || $(PIP) install sphinx-autobuild + $(PYTHON) -m sphinx_autobuild $(DOCS_DIR) $(DOCS_BUILD_DIR) --open-browser + +# Wipe the built docs +.PHONY: docs-clean +docs-clean: + @echo "$(GREEN)Cleaning docs build directory...$(NC)" + rm -rf $(DOCS_DIR)/_build diff --git a/python/Makefile b/python/Makefile index 134fcac..de711f1 100644 --- a/python/Makefile +++ b/python/Makefile @@ -64,8 +64,8 @@ help: @echo " $(YELLOW)check-deps$(NC) - Check for outdated dependencies" @echo " $(YELLOW)update-deps$(NC) - Update dependencies" @echo " $(YELLOW)security$(NC) - Run security audit" - @echo " $(YELLOW)docs$(NC) - Build the documentation (output in ../docs/_build/html/)" - @echo " $(YELLOW)docs-serve$(NC) - Serve the documentation locally with auto-reload" + @echo " $(YELLOW)docs$(NC) - Build the documentation (see ../Makefile)" + @echo " $(YELLOW)docs-serve$(NC) - Serve the documentation locally (see ../Makefile)" @echo " $(YELLOW)venv$(NC) - Create virtual environment" @echo " $(YELLOW)all$(NC) - Run full pipeline (install, lint, test, build)" @@ -245,23 +245,16 @@ security: $(PYTHON) -m pip_audit @echo "$(GREEN)Security audit completed!$(NC)" -# Build the documentation with the same flags as CI and Read the Docs: warnings -# (including broken cross-references, via -n) fail the build. The sources live -# in `../docs/`; install the tooling with `pip install -r ../docs/requirements.txt` -# (kept out of `install-dev` because not every developer needs it locally). +# The documentation belongs to the whole repository, so its targets live in the +# Makefile one level up (`make install-docs` there installs the tooling). These +# two forward to it for whoever is working from this directory. .PHONY: docs docs: - @echo "$(GREEN)Building the documentation...$(NC)" - $(PYTHON) -m sphinx -n -W --keep-going -b html ../docs ../docs/_build/html - @echo "$(GREEN)Documentation generated at ../docs/_build/html/index.html$(NC)" + $(MAKE) -C .. docs -# Serve the documentation locally with auto-reload. sphinx-autobuild is -# installed on demand. .PHONY: docs-serve docs-serve: - @echo "$(GREEN)Serving the documentation at http://127.0.0.1:8000 ...$(NC)" - @$(PYTHON) -c "import sphinx_autobuild" 2>/dev/null || $(PIP) install sphinx-autobuild - $(PYTHON) -m sphinx_autobuild ../docs ../docs/_build/html + $(MAKE) -C .. docs-serve # Full development pipeline. Uses `install-dev` (not `install`) so that # `lint`, `type-check`, and `test` have flake8 / mypy / pytest available From c19b7f40597fbbada062ca1e1211f54e2c844b45 Mon Sep 17 00:00:00 2001 From: JeanExtreme002 Date: Sat, 14 Nov 2026 18:07:08 -0300 Subject: [PATCH 4/8] docs: drop the logo from the sidebar --- docs/_static/custom.css | 11 ----------- docs/conf.py | 1 - 2 files changed, 12 deletions(-) diff --git a/docs/_static/custom.css b/docs/_static/custom.css index 47aab27..7a4a8fd 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -1,16 +1,5 @@ /* FlightRadarAPI docs: tweaks on top of the Read the Docs theme. */ -/* --- Sidebar logo -------------------------------------------------------- */ -.wy-side-nav-search .wy-dropdown > a img.logo, -.wy-side-nav-search > a img.logo { - width: 72px; - height: 72px; - margin: 0.2rem auto 0.5rem; - padding: 0; - background: transparent; - border-radius: 0; -} - /* --- Repository link (injected in _templates/layout.html) --------------- */ /* Scoped under `.wy-menu-vertical a` so it beats the theme's own link rule. */ .wy-menu-vertical a.repo-link { diff --git a/docs/conf.py b/docs/conf.py index b61d6f1..4593aa6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,7 +44,6 @@ html_theme = "sphinx_rtd_theme" html_title = "FlightRadarAPI" -html_logo = "_static/logo.png" html_favicon = "_static/favicon.ico" html_theme_options = { From 123304eb17cc9ad4e5b9be8166e5a8d8573e0fba Mon Sep 17 00:00:00 2001 From: JeanExtreme002 Date: Sat, 14 Nov 2026 22:41:23 -0300 Subject: [PATCH 5/8] docs: simplify the home page and drop the SDK differences page The home page opens with the description and the educational-use notice, followed by a Getting Started example; its Go tab is now a complete program. The zones table, the dependency column and the SDK differences page are removed. --- CONTRIBUTING.md | 23 +++++++------ docs/_static/custom.css | 4 --- docs/index.md | 63 +++++++++++++++++------------------ docs/installation.md | 10 +++--- docs/projects.md | 8 +---- docs/sdk-differences.md | 73 ----------------------------------------- docs/zones.md | 14 -------- 7 files changed, 47 insertions(+), 148 deletions(-) delete mode 100644 docs/sdk-differences.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index da2093f..ed6a8e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,30 +9,30 @@ touch every side. ### Python ```bash cd python -make dev-setup # creates venv, installs package + test extras + tooling +make dev-setup # creates venv, installs package + test extras + tooling source venv/bin/activate -make test # runs offline + integration -make lint # flake8 -make type-check # mypy +make test # runs offline + integration +make lint # flake8 +make type-check # mypy ``` ### Node.js ```bash cd nodejs make install -make test # mocha (all tiers) -make lint # eslint -make test-types # tsd +make test # mocha (all tiers) +make lint # eslint +make test-types # tsd ``` ### Go ```bash cd go make deps -make test # offline suite (the PR gate) -make test-integration # live FR24 suite -make lint # gofmt + go vet -make lint-strict # adds staticcheck +make test # offline suite (the PR gate) +make test-integration # live FR24 suite +make lint # gofmt + go vet +make lint-strict # adds staticcheck ``` ## Keeping the SDKs aligned @@ -47,7 +47,6 @@ is a documented reason not to. Common targets that must stay in sync: - The public surface — `FlightRadar24API` methods (`Client` in Go), the `Countries` enum (`Country` constants in Go), `FlightTrackerConfig` fields, and the `Entity` / `Airport` / `Flight` attributes consumers depend on. - `docs/sdk-differences.md` documents where the Go surface deliberately differs. ## Documentation diff --git a/docs/_static/custom.css b/docs/_static/custom.css index 7a4a8fd..8b67817 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -124,10 +124,6 @@ section#flightradarapi > h1 { color: #2980b9; } -.rst-content h2.tagline { - margin: 0 0 1rem; -} - @media (max-width: 480px) { .rst-content .hero img { width: 56px; diff --git a/docs/index.md b/docs/index.md index 2c6abfc..2f7958f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,11 +9,13 @@ myst:

FlightRadarAPI

-

Unofficial SDK for FlightRadar24

+## FlightRadarAPI Documentation +Unofficial SDK for [FlightRadar24](https://www.flightradar24.com/), for +**Python**, **Node.js** and **Go**. Live flights, airports, airlines, zones and +more. -Live flights, airports, airlines, zones and more from -[FlightRadar24](https://www.flightradar24.com/), for **Python**, **Node.js** and -**Go**, with the same features in every language. +This SDK should only be used for your own educational purposes. If you are interested in accessing Flightradar24 data commercially, please contact [business@fr24.com](mailto:business@fr24.com). See +[Flightradar24's terms and conditions](https://www.flightradar24.com/terms-and-conditions). @@ -55,14 +57,13 @@ Live flights, airports, airlines, zones and more from
-```{warning} -This is **not** an official FlightRadar24 API, and it should only be used for -your own educational purposes. If you are interested in accessing Flightradar24 -data commercially, use the [official FR24 API](https://fr24api.flightradar24.com/) -or contact [business@fr24.com](mailto:business@fr24.com). See -[Flightradar24's terms and conditions](https://www.flightradar24.com/terms-and-conditions). +```{admonition} Info +:class: note +This is not an official FlightRadar24 API. You can access their official API [here](https://fr24api.flightradar24.com/). ``` +## Getting Started + ::::{tab-set} :sync-group: lang @@ -111,36 +112,33 @@ go get github.com/JeanExtreme002/FlightRadarAPI/go@latest ``` ```go -client := flightradarapi.New() +package main -flights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{Airline: "UAE"}) +import ( + "context" + "fmt" + "log" -if err != nil { - log.Fatal(err) -} + "github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi" +) + +func main() { + client := flightradarapi.New() -for _, flight := range flights[:min(5, len(flights))] { - fmt.Println(flight.Callsign, flight.GetFlightLevel(), flight.GetGroundSpeed()) + flights, err := client.GetFlights(context.Background(), flightradarapi.FlightSearch{Airline: "UAE"}) + + if err != nil { + log.Fatal(err) + } + + for _, flight := range flights[:min(5, len(flights))] { + fmt.Println(flight.Callsign, flight.GetFlightLevel(), flight.GetGroundSpeed()) + } } ``` ::: :::: -## What's inside - -| Area | Topics | -|---|---| -| **Getting started** | [Installation](installation.md), [Quick Start](quickstart.md) | -| **Flights** | [Live flights](flights.md), [Zones and bounds](zones.md), [Flight tracker settings](tracker-config.md), [Distances](distances.md) | -| **Airports and airlines** | [Airports](airports.md), [Airlines, logos and flags](airlines.md) | -| **More data** | [Search](searching.md), [Most tracked, disruptions and eruptions](live-data.md) | -| **Account** | [Login, bookmarks and flight history](account.md) | -| **Client** | [Configuration](client.md), [Errors and retries](errors.md) | -| **Reference** | [Client methods](api.md), [Flight](flight.md), [Airport](airport.md), [Countries](countries.md), [Differences between the SDKs](sdk-differences.md) | - -Every page shows its examples in the three languages: pick yours in any tab and -the rest of the site follows. - New here? Start with [Installation](installation.md) and the [Quick Start](quickstart.md). @@ -201,7 +199,6 @@ api flight airport countries -sdk-differences ``` ```{toctree} diff --git a/docs/installation.md b/docs/installation.md index 034351c..2cd8491 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -53,11 +53,11 @@ import "github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi" ## Requirements -| SDK | Runtime | Dependencies | -|---|---|---| -| Python | Python 3.10 or later | `curl_cffi`, `Brotli`, `beautifulsoup4` | -| Node.js | Node.js 18.17 or later | `undici`, `node-html-parser` | -| Go | Go 1.26 or later | `golang.org/x/net`, `golang.org/x/text`, `github.com/andybalholm/brotli` | +| SDK | Runtime | +|---|---| +| Python | Python 3.10 or later | +| Node.js | Node.js 18.17 or later | +| Go | Go 1.26 or later | The dependencies are installed for you by `pip`, `npm` and `go get`. diff --git a/docs/projects.md b/docs/projects.md index 08e2746..b29383b 100644 --- a/docs/projects.md +++ b/docs/projects.md @@ -42,10 +42,4 @@ By Timofey Dmitrenko, aviation geek and Java Spring Boot programmer ::: :::: -Built something with FlightRadarAPI? Add it to this page. - -```{button-link} https://github.com/JeanExtreme002/FlightRadarAPI/edit/main/docs/projects.md -:color: primary - -Contribute your own -``` +Built something with FlightRadarAPI? Add it to this [page](https://github.com/JeanExtreme002/FlightRadarAPI/edit/main/docs/projects.md). diff --git a/docs/sdk-differences.md b/docs/sdk-differences.md deleted file mode 100644 index f9e2efe..0000000 --- a/docs/sdk-differences.md +++ /dev/null @@ -1,73 +0,0 @@ -# Differences Between the SDKs - -The three SDKs carry the same features and behave the same way. What changes is -the shape each language gives them. - -## At a glance - -| | Python | Node.js | Go | -|---|---|---|---| -| Package | `FlightRadarAPI` | `flightradarapi` | `github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi` | -| Client | `FlightRadar24API()` | `new FlightRadar24API()` | `flightradarapi.New()`, a `*Client` | -| Client options | keyword arguments | one options object | one `Options` struct | -| Names | `snake_case` | `camelCase` | `PascalCase` | -| Calls | blocking | `Promise` | `context.Context` in, `error` out | -| Timeout unit | seconds | milliseconds | `time.Duration` | -| Retry delays | seconds | milliseconds | `time.Duration` | -| Failures | exceptions | exceptions | `error` values | -| Missing value | `"N/A"` or `None` | `"N/A"` or `null` | zero value: `""` or `nil` | -| Log in at construction | yes, with `user` and `password` | no | no | - -## Python and Node.js - -The two are close. The differences are in how arguments are passed: - -| | Python | Node.js | -|---|---|---| -| Flight search | `get_flights(airline="UAE", aircraft_type="B77W")` | `getFlights("UAE", null, null, "B77W")` | -| Details flag | keyword-only: `get_airport("VNLK", details=True)` | positional: `getAirport("VNLK", true)` | -| Tracker options | `set_flight_tracker_config(limit=10)` | `setFlightTrackerConfig(null, { limit: 10 })` | -| Checking a flight | `check_info(min_altitude=6700)` | `checkInfo({ minAltitude: 6700 })` | -| TLS impersonation | name of a `curl_cffi` profile | object with `ciphers`, `sigalgs`, `ecdhCurve` | -| Unknown tracker option | `KeyError` | `RangeError` | -| Invalid airport code or file type | `ValueError` | `TypeError` | - -## Go - -The Go package follows the conventions of the language, so its surface differs -more. The package name is part of every identifier, which is why the client is -`Client` rather than `FlightRadar24API`: the way it is `http.Client` and not -`http.HTTPClient`. - -| Python and Node.js | Go | -|---|---| -| `FlightRadar24API` | `Client`, built with `New` | -| `FlightRadar24API(timeout=..., max_workers=...)` | `New(Options{Timeout: ..., MaxWorkers: ...})` | -| `FlightRadar24API(user, password)` logs in (Python only) | `client.Login(ctx, user, password)` | -| blocking calls and promises | `context.Context` on every request | -| exceptions | `error` values wrapping `ErrFlightRadar` | -| `FlightRadarError` (base class) | `ErrFlightRadar`, wrapped by every error | -| `Countries.BRAZIL` | `CountryBrazil`, with `AllCountries()` to enumerate | -| `get_flights(airline, bounds, registration, aircraft_type, details)` | `GetFlights(ctx, FlightSearch{...})` | -| `get_airports()` for every airport | `GetAirports(ctx, nil)` | -| default arguments (`flight_limit=100`, `limit=50`) | zero means the same default | -| `set_flight_tracker_config(config, limit=10)` | `SetFlightTrackerConfig(&config, map[string]string{"limit": "10"})` | -| `check_info(min_altitude=6700)` | `CheckInfo(map[string]any{"min_altitude": 6700})` | -| `check_info` ignores a name the flight does not have | `CheckInfo` returns an error for it | -| `flight.destination_airport_name` | `flight.Details.DestinationAirportName` | -| `Airport.from_details(payload)` | `NewAirportFromDetails(payload)` | -| `Flight(flight_id, info)` | `NewFlight(flightID, info)` | -| `(bytes, extension)` tuple | `*Image`, with `Data` and `Extension` | -| airline as a dictionary: `airline["n_aircrafts"]` | `Airline` struct: `airline.NumAircrafts` | -| zone as a dictionary: `zone["tl_y"]`, `zone["subzones"]` | `Zone` struct: `zone.TLY`, `zone.Subzones` | -| missing value is the string `"N/A"` | zero value: `""` or a nil pointer | -| `impersonate` | `Options.TLSProfile`, or a whole `Options.HTTPClient` | -| warnings through `logging` and `console.warn` | warnings through `log/slog`, see `SetLogger` | -| `parsers` module (internal) | unexported functions | - -Every method of `FlightRadar24API` has a counterpart in `Client`, with the same -parameters, kept that way by a test that reads the Python source. - -The Go package is also documented on -[pkg.go.dev](https://pkg.go.dev/github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi), -from its source. diff --git a/docs/zones.md b/docs/zones.md index 969df7f..b6cf332 100644 --- a/docs/zones.md +++ b/docs/zones.md @@ -106,20 +106,6 @@ london := zones["europe"].Subzones["uk"].Subzones["london"] ::: :::: -### Available zones - -| Zone | Subzones | -|---|---| -| `europe` | `poland`, `germany`, `uk` (with `london`, `ireland`), `spain`, `france`, `ceur`, `scandinavia`, `italy` | -| `northamerica` | `na_n`, `na_c` (with `na_cny`, `na_cla`, `na_cat`, `na_cse`, `na_nw`, `na_ne`, `na_sw`, `na_se`, `na_cc`), `na_s` | -| `southamerica` | | -| `oceania` | | -| `asia` | `japan` | -| `africa` | | -| `atlantic` | | -| `maldives` | | -| `northatlantic` | | - ## Around a point To look at the sky above one place, build the bounds from its coordinates and a From 2bdacc632852a55e4d21088494ebcbfb3ba7547b Mon Sep 17 00:00:00 2001 From: JeanExtreme002 Date: Sat, 14 Nov 2026 23:20:33 -0300 Subject: [PATCH 6/8] docs: trim the reference and show per-language tables in tabs Tables that carried a column per language now show one language per tab, in sync with the rest of the site. The full list of countries, the method signatures, the list of exported names and the sections on building a Flight or an Airport by hand are removed, and the three live-data sections become one. --- docs/account.md | 4 - docs/airport.md | 91 +++++----- docs/airports.md | 20 +- docs/api.md | 403 +++++++++++++++++++---------------------- docs/client.md | 53 ++++-- docs/countries.md | 239 +----------------------- docs/distances.md | 7 +- docs/errors.md | 155 ++++++++++++---- docs/flight.md | 64 ++++--- docs/flights.md | 47 ++++- docs/live-data.md | 94 ++-------- docs/searching.md | 6 - docs/tracker-config.md | 81 +++++++-- 13 files changed, 551 insertions(+), 713 deletions(-) diff --git a/docs/account.md b/docs/account.md index 3812752..b205ba2 100644 --- a/docs/account.md +++ b/docs/account.md @@ -165,8 +165,6 @@ history_data = fr_api.get_history_data(flight, file_type="CSV", timestamp=170652 with open("history_data.csv", "w") as file: file.write(history_data) ``` - -The document is returned as a `str`. ::: :::{tab-item} Node.js @@ -193,8 +191,6 @@ if err != nil { err = os.WriteFile("history_data.csv", []byte(historyData), 0o644) ``` - -The document is returned as a `string`. ::: :::: diff --git a/docs/airport.md b/docs/airport.md index c8df1d8..5e0402c 100644 --- a/docs/airport.md +++ b/docs/airport.md @@ -3,17 +3,13 @@ An `Airport` is an [`Entity`](distances.md), so it has a position and can measure its distance to a flight or to another airport. -How much an airport carries depends on the call that produced it. The columns -of the tables below say which: +How much an airport carries depends on the call that produced it. In the tables +below, a column per call says whether the attribute is filled in: -| Column | Call | -|---|---| -| **List** | [Airports of a country](airports.md#airports-of-a-country): `get_airports` | -| **Code** | [One airport by code](airports.md#one-airport-by-code): `get_airport` | -| **Details** | [Airport details](airports.md#airport-details): `get_airport` with details, or `set_airport_details` | - -The names follow the convention of each language. What Python calls -`country_code` is `countryCode` in Node.js and `CountryCode` in Go. +- the [airports listing](airports.md#airports-of-a-country); +- [one airport by code](airports.md#one-airport-by-code); +- an airport [with details](airports.md#airport-details): fetched with the + details flag, or filled in from a details payload. ## Attributes @@ -25,7 +21,7 @@ The names follow the convention of each language. What Python calls :::{tab-item} Python :sync: python -| Attribute | Description | List | Code | Details | +| Attribute | Description | `get_airports()` | `get_airport()` | with details | |---|---|:-:|:-:|:-:| | `name` | Name of the airport | ✓ | ✓ | ✓ | | `icao` | ICAO code | ✓ | ✓ | ✓ | @@ -42,7 +38,7 @@ The names follow the convention of each language. What Python calls :::{tab-item} Node.js :sync: node -| Property | Description | List | Code | Details | +| Property | Description | `getAirports()` | `getAirport()` | with details | |---|---|:-:|:-:|:-:| | `name` | Name of the airport | ✓ | ✓ | ✓ | | `icao` | ICAO code | ✓ | ✓ | ✓ | @@ -59,7 +55,7 @@ The names follow the convention of each language. What Python calls :::{tab-item} Go :sync: go -| Field | Description | List | Code | Details | +| Field | Description | `GetAirports()` | `GetAirport()` | with details | |---|---|:-:|:-:|:-:| | `Name` | Name of the airport | ✓ | ✓ | ✓ | | `ICAO` | ICAO code | ✓ | ✓ | ✓ | @@ -83,7 +79,7 @@ The names follow the convention of each language. What Python calls :::{tab-item} Python :sync: python -| Attribute | Description | List | Code | Details | +| Attribute | Description | `get_airports()` | `get_airport()` | with details | |---|---|:-:|:-:|:-:| | `timezone_name` | Name of the timezone, such as `"Asia/Kathmandu"` | | ✓ | ✓ | | `timezone_abbr` | Abbreviation of the timezone | | ✓ | ✓ | @@ -95,7 +91,7 @@ The names follow the convention of each language. What Python calls :::{tab-item} Node.js :sync: node -| Property | Description | List | Code | Details | +| Property | Description | `getAirports()` | `getAirport()` | with details | |---|---|:-:|:-:|:-:| | `timezoneName` | Name of the timezone, such as `"Asia/Kathmandu"` | | ✓ | ✓ | | `timezoneAbbr` | Abbreviation of the timezone | | ✓ | ✓ | @@ -107,7 +103,7 @@ The names follow the convention of each language. What Python calls :::{tab-item} Go :sync: go -| Field | Description | List | Code | Details | +| Field | Description | `GetAirports()` | `GetAirport()` | with details | |---|---|:-:|:-:|:-:| | `TimezoneName` | Name of the timezone, such as `"Asia/Kathmandu"` | | ✓ | ✓ | | `TimezoneAbbr` | Abbreviation of the timezone | | ✓ | ✓ | @@ -126,7 +122,7 @@ The names follow the convention of each language. What Python calls :::{tab-item} Python :sync: python -| Attribute | Description | List | Code | Details | +| Attribute | Description | `get_airports()` | `get_airport()` | with details | |---|---|:-:|:-:|:-:| | `website` | Website of the airport | | ✓ | ✓ | | `visible` | The `visible` flag FlightRadar24 sends for the airport | | ✓ | ✓ | @@ -137,7 +133,7 @@ The names follow the convention of each language. What Python calls :::{tab-item} Node.js :sync: node -| Property | Description | List | Code | Details | +| Property | Description | `getAirports()` | `getAirport()` | with details | |---|---|:-:|:-:|:-:| | `website` | Website of the airport | | ✓ | ✓ | | `visible` | The `visible` flag FlightRadar24 sends for the airport | | ✓ | ✓ | @@ -148,7 +144,7 @@ The names follow the convention of each language. What Python calls :::{tab-item} Go :sync: go -| Field | Description | List | Code | Details | +| Field | Description | `GetAirports()` | `GetAirport()` | with details | |---|---|:-:|:-:|:-:| | `Website` | Website of the airport | | ✓ | ✓ | | `Visible` | The `visible` flag FlightRadar24 sends for the airport | | ✓ | ✓ | @@ -166,7 +162,7 @@ The names follow the convention of each language. What Python calls :::{tab-item} Python :sync: python -| Attribute | Description | List | Code | Details | +| Attribute | Description | `get_airports()` | `get_airport()` | with details | |---|---|:-:|:-:|:-:| | `runways` | Runways of the airport | | | ✓ | | `weather` | Current weather at the airport | | | ✓ | @@ -179,7 +175,7 @@ The names follow the convention of each language. What Python calls :::{tab-item} Node.js :sync: node -| Property | Description | List | Code | Details | +| Property | Description | `getAirports()` | `getAirport()` | with details | |---|---|:-:|:-:|:-:| | `runways` | Runways of the airport | | | ✓ | | `weather` | Current weather at the airport | | | ✓ | @@ -192,7 +188,7 @@ The names follow the convention of each language. What Python calls :::{tab-item} Go :sync: go -| Field | Description | List | Code | Details | +| Field | Description | `GetAirports()` | `GetAirport()` | with details | |---|---|:-:|:-:|:-:| | `Runways` | Runways of the airport | | | ✓ | | `Weather` | Current weather at the airport | | | ✓ | @@ -212,7 +208,7 @@ The names follow the convention of each language. What Python calls :::{tab-item} Python :sync: python -| Attribute | Description | List | Code | Details | +| Attribute | Description | `get_airports()` | `get_airport()` | with details | |---|---|:-:|:-:|:-:| | `reviews_url` | Address of the reviews page on FlightRadar24 | | | ✓ | | `reviews` | Number of reviews | | | ✓ | @@ -224,7 +220,7 @@ The names follow the convention of each language. What Python calls :::{tab-item} Node.js :sync: node -| Property | Description | List | Code | Details | +| Property | Description | `getAirports()` | `getAirport()` | with details | |---|---|:-:|:-:|:-:| | `reviewsUrl` | Address of the reviews page on FlightRadar24 | | | ✓ | | `reviews` | Number of reviews | | | ✓ | @@ -236,7 +232,7 @@ The names follow the convention of each language. What Python calls :::{tab-item} Go :sync: go -| Field | Description | List | Code | Details | +| Field | Description | `GetAirports()` | `GetAirport()` | with details | |---|---|:-:|:-:|:-:| | `ReviewsURL` | Address of the reviews page on FlightRadar24 | | | ✓ | | `Reviews` | Number of reviews | | | ✓ | @@ -264,7 +260,7 @@ An attribute whose column is blank **does not exist** on that airport: reading it raises `AttributeError`. Use `getattr(airport, "city", None)` when the airport may come from more than one call. -On a listed airport, `latitude`, `longitude` and `altitude` are `None`. +On an airport from the listing, `latitude`, `longitude` and `altitude` are `None`. On the other two, a value FlightRadar24 left out is `None` or the string `"N/A"`, depending on the attribute and on the call. The collections are empty @@ -277,7 +273,7 @@ instead: `runways` is `[]`, and `weather`, `arrivals`, A property whose column is blank is `undefined` on that airport. -On a listed airport, `latitude`, `longitude` and `altitude` are `null`. +On an airport from the listing, `latitude`, `longitude` and `altitude` are `null`. On the other two, a value FlightRadar24 left out is `null` or the string `"N/A"`, depending on the property and on the call. The collections are empty @@ -303,21 +299,34 @@ does not name. It is `nil` on a listed airport. ## Methods -| Python | Node.js | Go | Description | -|---|---|---|---| -| `set_airport_details(details)` | `setAirportDetails(details)` | `SetAirportDetails(details)` | Fill the airport in from a details payload | -| `get_distance_from(entity)` | `getDistanceFrom(entity)` | `GetDistanceFrom(other)` | Distance to another entity, in kilometers. See [Distances](distances.md). | +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Method | Description | +|---|---| +| `set_airport_details(details)` | Fill the airport in from a details payload | +| `get_distance_from(entity)` | Distance to another entity, in kilometers. See [Distances](distances.md). | +::: + +:::{tab-item} Node.js +:sync: node -## Building an airport yourself +| Method | Description | +|---|---| +| `setAirportDetails(details)` | Fill the airport in from a details payload | +| `getDistanceFrom(entity)` | Distance to another entity, in kilometers. See [Distances](distances.md). | +::: -The client builds the airports it returns. There is one constructor for each of -the three payloads an airport can come from. +:::{tab-item} Go +:sync: go -| Payload | Python | Node.js | Go | -|---|---|---|---| -| A row with `name`, `icao`, `iata`, `lat`, `lon`, `alt` and `country` | `Airport.from_basic_info(row)` | `Airport.fromBasicInfo(row)` | `NewAirportFromBasicInfo(row)` | -| The `details` block of an airport | `Airport.from_info(info)` | `Airport.fromInfo(info)` | `NewAirportFromInfo(info)` | -| A [details payload](airports.md#the-raw-details-payload) | `Airport.from_details(details)` | `Airport.fromDetails(details)` | `NewAirportFromDetails(details)` | +| Method | Description | +|---|---| +| `SetAirportDetails(details)` | Fill the airport in from a details payload | +| `GetDistanceFrom(other)` | Distance to another entity, in kilometers. See [Distances](distances.md). | +::: -An empty airport, to be filled in later with `set_airport_details`, is -`Airport()` in Python, `new Airport()` in Node.js and `NewAirport()` in Go. +:::: diff --git a/docs/airports.md b/docs/airports.md index ac98e08..8b301f9 100644 --- a/docs/airports.md +++ b/docs/airports.md @@ -268,26 +268,10 @@ Countries can also be given by name or by slug: ::: :::: -The [Countries reference](countries.md) lists every country. - -A few things to know about the listing: - -- **It has no coordinates.** A listed airport carries its name, IATA and ICAO - codes and country; latitude, longitude and altitude are empty. Fetch - [one airport by code](#one-airport-by-code) when you need its position. -- **It costs one request per page.** FlightRadar24 lists airports one page per - country, and one per state for the largest countries. A few countries take a - handful of requests; every airport takes a few hundred. They run several at a - time (8 by default, see [Configuration](client.md#concurrency)). -- **The order follows your request.** Airports come grouped by country, in the - order the countries were asked for. -- **An empty selection returns nothing.** An empty list is not the same as no - argument: it selects no country, and makes no request. -- **An unknown country is skipped.** It adds no airports and a warning is - logged (see [Logging](client.md#logging)). +See [Countries](countries.md) for how countries are named. ## See also - [Airport reference](airport.md): every attribute of an airport. -- [Airport disruptions](live-data.md#airport-disruptions): delays and cancellations by airport. +- [Airport disruptions](live-data.md): delays and cancellations by airport. - [Distances](distances.md): how far an airport is from a flight, or from another airport. diff --git a/docs/api.md b/docs/api.md index d71b17a..7b204b2 100644 --- a/docs/api.md +++ b/docs/api.md @@ -5,8 +5,10 @@ Node.js, `Client` in Go. Its methods are the same in the three languages, apart from the naming convention of each. This page lists every one of them. The linked guides explain them with examples. +The Go package is also documented, from its source, on +[pkg.go.dev](https://pkg.go.dev/github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi). -## Signatures +## Flights ::::{tab-set} :sync-group: lang @@ -14,216 +16,190 @@ This page lists every one of them. The linked guides explain them with examples. :::{tab-item} Python :sync: python -```python -class FlightRadar24API: - timeout: int # seconds - max_workers: int - - def __init__(self, user=None, password=None, timeout=30, max_workers=8, - impersonate=None, retry=None) - - # Flights - def get_flights(self, airline=None, bounds=None, registration=None, - aircraft_type=None, *, details=False) -> list[Flight] - def get_flight_details(self, flight: Flight) -> dict - def get_flight_tracker_config(self) -> FlightTrackerConfig - def set_flight_tracker_config(self, flight_tracker_config=None, **config) -> None - - # Zones and bounds - def get_zones(self) -> dict - def get_bounds(self, zone: dict) -> str - def get_bounds_by_point(self, latitude: float, longitude: float, radius: float) -> str - - # Airports - def get_airport(self, code: str, *, details=False) -> Airport - def get_airport_details(self, code: str, flight_limit=100, page=1) -> dict - def get_airports(self, countries=None) -> list[Airport] - def get_airport_disruptions(self) -> dict - - # Airlines and images - def get_airlines(self) -> list[dict] - def get_airline_logo(self, iata: str, icao: str) -> tuple[bytes, str] | None - def get_country_flag(self, country: str) -> tuple[bytes, str] | None - - # More data - def search(self, query: str, limit=50) -> dict - def get_most_tracked(self) -> dict - def get_volcanic_eruptions(self) -> dict - - # Account - def login(self, user: str, password: str) -> None - def logout(self) -> bool - def is_logged_in(self) -> bool - def get_login_data(self) -> dict - def get_bookmarks(self) -> dict - def get_history_data(self, flight: Flight, file_type: str, timestamp: int) -> str -``` - -`details` is keyword-only in `get_flights` and `get_airport`. +| Method | Description | +|---|---| +| `get_flights` | Flights of the live feed, optionally filtered. See [Live flights](flights.md). | +| `get_flight_details` | Details payload of one flight. See [Flight details](flights.md#flight-details). | +| `get_flight_tracker_config` | Copy of the current feed settings. See [Flight tracker settings](tracker-config.md). | +| `set_flight_tracker_config` | Change the feed settings. See [Flight tracker settings](tracker-config.md). | ::: :::{tab-item} Node.js :sync: node -```typescript -class FlightRadar24API { - timeout: number; // milliseconds - maxWorkers: number; - - constructor(options?: { timeout?: number; maxWorkers?: number; impersonate?: object; retry?: RetryPolicy }); - - // Flights - getFlights(airline?: string | null, bounds?: string | null, registration?: string | null, - aircraftType?: string | null, details?: boolean): Promise; - getFlightDetails(flight: Flight): Promise; - getFlightTrackerConfig(): FlightTrackerConfig; - setFlightTrackerConfig(flightTrackerConfig: FlightTrackerConfig | null, config?: object): void; - - // Zones and bounds - getZones(): object; - getBounds(zone: Zone): string; - getBoundsByPoint(latitude: number, longitude: number, radius: number): string; - - // Airports - getAirport(code: string, details?: boolean): Promise; - getAirportDetails(code: string, flightLimit?: number, page?: number): Promise; - getAirports(countries?: Iterable | string): Promise; - getAirportDisruptions(): Promise; - - // Airlines and images - getAirlines(): Promise; - getAirlineLogo(iata: string, icao: string): Promise<[ArrayBuffer, string] | null>; - getCountryFlag(country: string): Promise<[ArrayBuffer, string] | null>; - - // More data - search(query: string, limit?: number): Promise>; - getMostTracked(): Promise; - getVolcanicEruptions(): Promise; - - // Account - login(user: string, password: string): Promise; - logout(): Promise; - isLoggedIn(): boolean; - getLoginData(): object; - getBookmarks(): Promise; - getHistoryData(flight: Flight, fileType: "CSV" | "KML", timestamp: number): Promise; -} -``` - -The defaults are the same as in Python: `flightLimit = 100`, `page = 1`, -`limit = 50`, `details = false`. +| Method | Description | +|---|---| +| `getFlights` | Flights of the live feed, optionally filtered. See [Live flights](flights.md). | +| `getFlightDetails` | Details payload of one flight. See [Flight details](flights.md#flight-details). | +| `getFlightTrackerConfig` | Copy of the current feed settings. See [Flight tracker settings](tracker-config.md). | +| `setFlightTrackerConfig` | Change the feed settings. See [Flight tracker settings](tracker-config.md). | ::: :::{tab-item} Go :sync: go -```go -func New(options ...Options) *Client - -type Client struct { - Timeout time.Duration - MaxWorkers int -} - -// Flights -func (c *Client) GetFlights(ctx context.Context, search FlightSearch) ([]*Flight, error) -func (c *Client) GetFlightDetails(ctx context.Context, flight *Flight) (map[string]any, error) -func (c *Client) GetFlightTrackerConfig() FlightTrackerConfig -func (c *Client) SetFlightTrackerConfig(config *FlightTrackerConfig, values map[string]string) error - -// Zones and bounds -func (c *Client) GetZones() map[string]Zone -func (c *Client) GetBounds(zone Zone) string -func (c *Client) GetBoundsByPoint(latitude, longitude, radius float64) string - -// Airports -func (c *Client) GetAirport(ctx context.Context, code string, details bool) (*Airport, error) -func (c *Client) GetAirportDetails(ctx context.Context, code string, flightLimit, page int) (map[string]any, error) -func (c *Client) GetAirports(ctx context.Context, countries []Country) ([]*Airport, error) -func (c *Client) GetAirportDisruptions(ctx context.Context) (map[string]any, error) - -// Airlines and images -func (c *Client) GetAirlines(ctx context.Context) ([]Airline, error) -func (c *Client) GetAirlineLogo(ctx context.Context, iata, icao string) (*Image, error) -func (c *Client) GetCountryFlag(ctx context.Context, country string) (*Image, error) - -// More data -func (c *Client) Search(ctx context.Context, query string, limit int) (map[string][]any, error) -func (c *Client) GetMostTracked(ctx context.Context) (map[string]any, error) -func (c *Client) GetVolcanicEruptions(ctx context.Context) (map[string]any, error) - -// Account -func (c *Client) Login(ctx context.Context, user, password string) error -func (c *Client) Logout(ctx context.Context) (bool, error) -func (c *Client) IsLoggedIn() bool -func (c *Client) GetLoginData() (map[string]any, error) -func (c *Client) GetBookmarks(ctx context.Context) (map[string]any, error) -func (c *Client) GetHistoryData(ctx context.Context, flight *Flight, fileType string, timestamp int64) (string, error) -``` - -Where the other SDKs have a default argument, zero means that default: -`flightLimit` 100, `page` 1, `limit` 50. The full reference, generated from the -source, is on -[pkg.go.dev](https://pkg.go.dev/github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi). +| Method | Description | +|---|---| +| `GetFlights` | Flights of the live feed, optionally filtered. See [Live flights](flights.md). | +| `GetFlightDetails` | Details payload of one flight. See [Flight details](flights.md#flight-details). | +| `GetFlightTrackerConfig` | Copy of the current feed settings. See [Flight tracker settings](tracker-config.md). | +| `SetFlightTrackerConfig` | Change the feed settings. See [Flight tracker settings](tracker-config.md). | ::: + :::: -## Flights +## Zones and bounds -| Python | Node.js | Go | Description | -|---|---|---|---| -| `get_flights` | `getFlights` | `GetFlights` | Flights of the live feed, optionally filtered. See [Live flights](flights.md). | -| `get_flight_details` | `getFlightDetails` | `GetFlightDetails` | Details payload of one flight. See [Flight details](flights.md#flight-details). | -| `get_flight_tracker_config` | `getFlightTrackerConfig` | `GetFlightTrackerConfig` | Copy of the current feed settings. See [Flight tracker settings](tracker-config.md). | -| `set_flight_tracker_config` | `setFlightTrackerConfig` | `SetFlightTrackerConfig` | Change the feed settings. See [Flight tracker settings](tracker-config.md). | +::::{tab-set} +:sync-group: lang -## Zones and bounds +:::{tab-item} Python +:sync: python + +| Method | Description | +|---|---| +| `get_zones` | Major zones of the globe. See [Zones](zones.md#zones). | +| `get_bounds` | Bounds string of a zone. See [Zones](zones.md#zones). | +| `get_bounds_by_point` | Bounds string of the square around a point. See [Around a point](zones.md#around-a-point). | +::: + +:::{tab-item} Node.js +:sync: node -These make no request. +| Method | Description | +|---|---| +| `getZones` | Major zones of the globe. See [Zones](zones.md#zones). | +| `getBounds` | Bounds string of a zone. See [Zones](zones.md#zones). | +| `getBoundsByPoint` | Bounds string of the square around a point. See [Around a point](zones.md#around-a-point). | +::: -| Python | Node.js | Go | Description | -|---|---|---|---| -| `get_zones` | `getZones` | `GetZones` | Major zones of the globe. See [Zones](zones.md#zones). | -| `get_bounds` | `getBounds` | `GetBounds` | Bounds string of a zone. See [Zones](zones.md#zones). | -| `get_bounds_by_point` | `getBoundsByPoint` | `GetBoundsByPoint` | Bounds string of the square around a point. See [Around a point](zones.md#around-a-point). | +:::{tab-item} Go +:sync: go + +| Method | Description | +|---|---| +| `GetZones` | Major zones of the globe. See [Zones](zones.md#zones). | +| `GetBounds` | Bounds string of a zone. See [Zones](zones.md#zones). | +| `GetBoundsByPoint` | Bounds string of the square around a point. See [Around a point](zones.md#around-a-point). | +::: + +:::: ## Airports -| Python | Node.js | Go | Description | -|---|---|---|---| -| `get_airport` | `getAirport` | `GetAirport` | One airport, by its IATA or ICAO code. See [Airports](airports.md#one-airport-by-code). | -| `get_airport_details` | `getAirportDetails` | `GetAirportDetails` | Details payload of one airport. See [Airport details](airports.md#the-raw-details-payload). | -| `get_airports` | `getAirports` | `GetAirports` | Airports of some countries, or all of them. See [Airports of a country](airports.md#airports-of-a-country). | -| `get_airport_disruptions` | `getAirportDisruptions` | `GetAirportDisruptions` | Current airport disruptions. See [Airport disruptions](live-data.md#airport-disruptions). | +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Method | Description | +|---|---| +| `get_airport` | One airport, by its IATA or ICAO code. See [Airports](airports.md#one-airport-by-code). | +| `get_airport_details` | Details payload of one airport. See [Airport details](airports.md#the-raw-details-payload). | +| `get_airports` | Airports of some countries, or all of them. See [Airports of a country](airports.md#airports-of-a-country). | +| `get_airport_disruptions` | Current airport disruptions. See [Airport disruptions](live-data.md). | +::: + +:::{tab-item} Node.js +:sync: node + +| Method | Description | +|---|---| +| `getAirport` | One airport, by its IATA or ICAO code. See [Airports](airports.md#one-airport-by-code). | +| `getAirportDetails` | Details payload of one airport. See [Airport details](airports.md#the-raw-details-payload). | +| `getAirports` | Airports of some countries, or all of them. See [Airports of a country](airports.md#airports-of-a-country). | +| `getAirportDisruptions` | Current airport disruptions. See [Airport disruptions](live-data.md). | +::: + +:::{tab-item} Go +:sync: go + +| Method | Description | +|---|---| +| `GetAirport` | One airport, by its IATA or ICAO code. See [Airports](airports.md#one-airport-by-code). | +| `GetAirportDetails` | Details payload of one airport. See [Airport details](airports.md#the-raw-details-payload). | +| `GetAirports` | Airports of some countries, or all of them. See [Airports of a country](airports.md#airports-of-a-country). | +| `GetAirportDisruptions` | Current airport disruptions. See [Airport disruptions](live-data.md). | +::: + +:::: ## Airlines and images -| Python | Node.js | Go | Description | -|---|---|---|---| -| `get_airlines` | `getAirlines` | `GetAirlines` | Every airline. See [Airlines](airlines.md#listing-the-airlines). | -| `get_airline_logo` | `getAirlineLogo` | `GetAirlineLogo` | Logo of an airline. See [Airline logos](airlines.md#airline-logos). | -| `get_country_flag` | `getCountryFlag` | `GetCountryFlag` | Flag of a country. See [Country flags](airlines.md#country-flags). | +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Method | Description | +|---|---| +| `get_airlines` | Every airline. See [Airlines](airlines.md#listing-the-airlines). | +| `get_airline_logo` | Logo of an airline. See [Airline logos](airlines.md#airline-logos). | +| `get_country_flag` | Flag of a country. See [Country flags](airlines.md#country-flags). | +::: + +:::{tab-item} Node.js +:sync: node + +| Method | Description | +|---|---| +| `getAirlines` | Every airline. See [Airlines](airlines.md#listing-the-airlines). | +| `getAirlineLogo` | Logo of an airline. See [Airline logos](airlines.md#airline-logos). | +| `getCountryFlag` | Flag of a country. See [Country flags](airlines.md#country-flags). | +::: + +:::{tab-item} Go +:sync: go + +| Method | Description | +|---|---| +| `GetAirlines` | Every airline. See [Airlines](airlines.md#listing-the-airlines). | +| `GetAirlineLogo` | Logo of an airline. See [Airline logos](airlines.md#airline-logos). | +| `GetCountryFlag` | Flag of a country. See [Country flags](airlines.md#country-flags). | +::: + +:::: ## More data -| Python | Node.js | Go | Description | -|---|---|---|---| -| `search` | `search` | `Search` | Search results, grouped by category. See [Search](searching.md). | -| `get_most_tracked` | `getMostTracked` | `GetMostTracked` | Most tracked flights. See [Most tracked flights](live-data.md#most-tracked-flights). | -| `get_volcanic_eruptions` | `getVolcanicEruptions` | `GetVolcanicEruptions` | Volcanic eruptions and ash clouds. See [Volcanic eruptions](live-data.md#volcanic-eruptions). | +::::{tab-set} +:sync-group: lang -## Account +:::{tab-item} Python +:sync: python + +| Method | Description | +|---|---| +| `search` | Search results, grouped by category. See [Search](searching.md). | +| `get_most_tracked` | Most tracked flights. See [Most tracked flights](live-data.md). | +| `get_volcanic_eruptions` | Volcanic eruptions and ash clouds. See [Volcanic eruptions](live-data.md). | +::: + +:::{tab-item} Node.js +:sync: node + +| Method | Description | +|---|---| +| `search` | Search results, grouped by category. See [Search](searching.md). | +| `getMostTracked` | Most tracked flights. See [Most tracked flights](live-data.md). | +| `getVolcanicEruptions` | Volcanic eruptions and ash clouds. See [Volcanic eruptions](live-data.md). | +::: + +:::{tab-item} Go +:sync: go + +| Method | Description | +|---|---| +| `Search` | Search results, grouped by category. See [Search](searching.md). | +| `GetMostTracked` | Most tracked flights. See [Most tracked flights](live-data.md). | +| `GetVolcanicEruptions` | Volcanic eruptions and ash clouds. See [Volcanic eruptions](live-data.md). | +::: -| Python | Node.js | Go | Description | -|---|---|---|---| -| `login` | `login` | `Login` | Log in to a FlightRadar24 account. See [Logging in](account.md#logging-in). | -| `logout` | `logout` | `Logout` | End the session. See [Session state](account.md#session-state). | -| `is_logged_in` | `isLoggedIn` | `IsLoggedIn` | Whether the client holds a session. | -| `get_login_data` | `getLoginData` | `GetLoginData` | Data of the logged-in account. | -| `get_bookmarks` | `getBookmarks` | `GetBookmarks` | Bookmarks of the account. See [Bookmarks](account.md#bookmarks). | -| `get_history_data` | `getHistoryData` | `GetHistoryData` | Track of a flight, as CSV or KML. See [Flight history](account.md#flight-history). | +:::: -## What each package exports +## Account ::::{tab-set} :sync-group: lang @@ -231,55 +207,40 @@ These make no request. :::{tab-item} Python :sync: python -```python -from FlightRadarAPI import ( - FlightRadar24API, # the client - Flight, Airport, Entity, # what the client returns - Countries, # country names, for get_airports - FlightTrackerConfig, # settings of the flight feed - RetryPolicy, # retrying transient failures - FlightRadarError, AirportNotFoundError, CloudflareError, - DecompressionLimitError, LoginError, -) -``` - -Anything else in the package, such as the `parsers` and `request` modules, is -internal and may change without notice. +| Method | Description | +|---|---| +| `login` | Log in to a FlightRadar24 account. See [Logging in](account.md#logging-in). | +| `logout` | End the session. See [Session state](account.md#session-state). | +| `is_logged_in` | Whether the client holds a session. | +| `get_login_data` | Data of the logged-in account. | +| `get_bookmarks` | Bookmarks of the account. See [Bookmarks](account.md#bookmarks). | +| `get_history_data` | Track of a flight, as CSV or KML. See [Flight history](account.md#flight-history). | ::: :::{tab-item} Node.js :sync: node -```javascript -const { - FlightRadar24API, // the client - Flight, Airport, Entity, // what the client returns - Countries, // country names, for getAirports - FlightTrackerConfig, // settings of the flight feed - RetryPolicy, // retrying transient failures - FlightRadarError, AirportNotFoundError, CloudflareError, - DecompressionLimitError, LoginError, - version, author, -} = require("flightradarapi"); -``` - -The package also exports `APIClient`, the HTTP client `FlightRadar24API` is -built on. It is there for advanced cases; most programs never need it. +| Method | Description | +|---|---| +| `login` | Log in to a FlightRadar24 account. See [Logging in](account.md#logging-in). | +| `logout` | End the session. See [Session state](account.md#session-state). | +| `isLoggedIn` | Whether the client holds a session. | +| `getLoginData` | Data of the logged-in account. | +| `getBookmarks` | Bookmarks of the account. See [Bookmarks](account.md#bookmarks). | +| `getHistoryData` | Track of a flight, as CSV or KML. See [Flight history](account.md#flight-history). | ::: :::{tab-item} Go :sync: go -| Kind | Names | +| Method | Description | |---|---| -| Client | `Client`, `New`, `Options`, `FlightSearch` | -| Results | `Flight`, `FlightDetails`, `Airport`, `Airline`, `Image`, `Zone`, `Entity`, `Positioned` | -| Building results | `NewFlight`, `NewAirport`, `NewAirportFromBasicInfo`, `NewAirportFromInfo`, `NewAirportFromDetails` | -| Countries | `Country`, the `Country...` constants, `AllCountries` | -| Flight feed settings | `FlightTrackerConfig`, `NewFlightTrackerConfig` | -| Retrying | `RetryPolicy`, `NewRetryPolicy`, `DefaultRetryBaseDelay`, `DefaultRetryMaxDelay`, `DefaultRetryJitter` | -| TLS | `TLSProfile`, `Chrome136Profile` | -| Errors | `ErrFlightRadar`, `ErrAirportNotFound`, `ErrCloudflare`, `ErrDecompressionLimit`, `ErrLogin`, and the types `AirportNotFoundError`, `CloudflareError`, `DecompressionLimitError`, `LoginError`, `StatusError` | -| Other | `SetLogger`, `Response`, `Version`, `Author`, `DefaultText`, `DefaultTimeout`, `MaxResponseBytes` | +| `Login` | Log in to a FlightRadar24 account. See [Logging in](account.md#logging-in). | +| `Logout` | End the session. See [Session state](account.md#session-state). | +| `IsLoggedIn` | Whether the client holds a session. | +| `GetLoginData` | Data of the logged-in account. | +| `GetBookmarks` | Bookmarks of the account. See [Bookmarks](account.md#bookmarks). | +| `GetHistoryData` | Track of a flight, as CSV or KML. See [Flight history](account.md#flight-history). | ::: + :::: diff --git a/docs/client.md b/docs/client.md index 686fafe..ff9e6e6 100644 --- a/docs/client.md +++ b/docs/client.md @@ -3,12 +3,43 @@ The client works with no configuration. A few options are there for when the defaults do not fit. -| Option | Python | Node.js | Go | Default | -|---|---|---|---|---| -| [Timeout](#timeout) | `timeout` (seconds) | `timeout` (milliseconds) | `Options.Timeout` | 30 seconds | -| [Concurrency](#concurrency) | `max_workers` | `maxWorkers` | `Options.MaxWorkers` | 8 | -| [TLS impersonation](#tls-impersonation) | `impersonate` | `impersonate` | `Options.TLSProfile`, `Options.HTTPClient` | Chrome 136 | -| [Retry](errors.md#retrying) | `retry` | `retry` | `Options.Retry` | No retry | +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Option | Name | Default | +|---|---|---| +| [Timeout](#timeout) | `timeout` (seconds) | 30 seconds | +| [Concurrency](#concurrency) | `max_workers` | 8 | +| [TLS impersonation](#tls-impersonation) | `impersonate` | Chrome 136 | +| [Retry](errors.md#retrying) | `retry` | No retry | +::: + +:::{tab-item} Node.js +:sync: node + +| Option | Name | Default | +|---|---|---| +| [Timeout](#timeout) | `timeout` (milliseconds) | 30 seconds | +| [Concurrency](#concurrency) | `maxWorkers` | 8 | +| [TLS impersonation](#tls-impersonation) | `impersonate` | Chrome 136 | +| [Retry](errors.md#retrying) | `retry` | No retry | +::: + +:::{tab-item} Go +:sync: go + +| Option | Name | Default | +|---|---|---| +| [Timeout](#timeout) | `Options.Timeout` | 30 seconds | +| [Concurrency](#concurrency) | `Options.MaxWorkers` | 8 | +| [TLS impersonation](#tls-impersonation) | `Options.TLSProfile`, `Options.HTTPClient` | Chrome 136 | +| [Retry](errors.md#retrying) | `Options.Retry` | No retry | +::: + +:::: ::::{tab-set} :sync-group: lang @@ -210,18 +241,14 @@ The SDKs log a warning when FlightRadar24 answers with something they did not expect, such as a page whose layout changed or a country with no airports. A warning never interrupts the call. -| SDK | Where warnings go | -|---|---| -| Python | The standard `logging` module, under the logger `FlightRadarAPI` | -| Node.js | `console.warn` | -| Go | `log/slog`, through `slog.Default()` unless `SetLogger` is called | - ::::{tab-set} :sync-group: lang :::{tab-item} Python :sync: python +Warnings go to the standard `logging` module, under the logger `FlightRadarAPI`. + ```python import logging @@ -239,6 +266,8 @@ The warnings are written with `console.warn`, to the standard error stream. :::{tab-item} Go :sync: go +Warnings go to `log/slog`, through `slog.Default()` unless `SetLogger` is called. + ```go logger := slog.New(slog.NewJSONHandler(os.Stderr, nil)) diff --git a/docs/countries.md b/docs/countries.md index 822332d..d203b59 100644 --- a/docs/countries.md +++ b/docs/countries.md @@ -1,8 +1,8 @@ # Countries The [airports listing](airports.md#airports-of-a-country) is narrowed by -country. Each SDK names the countries FlightRadar24 lists, 228 in all, so -you do not have to spell them. +country. Each SDK names the countries FlightRadar24 lists, so you do not +have to spell them. ::::{tab-set} :sync-group: lang @@ -73,238 +73,3 @@ normalized the same way: `"United States"`, `"united-states"` and This also means a country FlightRadar24 adds before the SDK names it can be reached by its name. - -## All countries - -The Python and Node.js names are the same. - -| Python and Node.js | Go | Slug | -|---|---|---| -| `AFGHANISTAN` | `CountryAfghanistan` | `"afghanistan"` | -| `ALBANIA` | `CountryAlbania` | `"albania"` | -| `ALGERIA` | `CountryAlgeria` | `"algeria"` | -| `AMERICAN_SAMOA` | `CountryAmericanSamoa` | `"american-samoa"` | -| `ANGOLA` | `CountryAngola` | `"angola"` | -| `ANGUILLA` | `CountryAnguilla` | `"anguilla"` | -| `ANTARCTICA` | `CountryAntarctica` | `"antarctica"` | -| `ANTIGUA_AND_BARBUDA` | `CountryAntiguaAndBarbuda` | `"antigua-and-barbuda"` | -| `ARGENTINA` | `CountryArgentina` | `"argentina"` | -| `ARMENIA` | `CountryArmenia` | `"armenia"` | -| `ARUBA` | `CountryAruba` | `"aruba"` | -| `AUSTRALIA` | `CountryAustralia` | `"australia"` | -| `AUSTRIA` | `CountryAustria` | `"austria"` | -| `AZERBAIJAN` | `CountryAzerbaijan` | `"azerbaijan"` | -| `BAHAMAS` | `CountryBahamas` | `"bahamas"` | -| `BAHRAIN` | `CountryBahrain` | `"bahrain"` | -| `BANGLADESH` | `CountryBangladesh` | `"bangladesh"` | -| `BARBADOS` | `CountryBarbados` | `"barbados"` | -| `BELARUS` | `CountryBelarus` | `"belarus"` | -| `BELGIUM` | `CountryBelgium` | `"belgium"` | -| `BELIZE` | `CountryBelize` | `"belize"` | -| `BENIN` | `CountryBenin` | `"benin"` | -| `BERMUDA` | `CountryBermuda` | `"bermuda"` | -| `BHUTAN` | `CountryBhutan` | `"bhutan"` | -| `BOLIVIA` | `CountryBolivia` | `"bolivia"` | -| `BOSNIA_AND_HERZEGOVINA` | `CountryBosniaAndHerzegovina` | `"bosnia-and-herzegovina"` | -| `BOTSWANA` | `CountryBotswana` | `"botswana"` | -| `BRAZIL` | `CountryBrazil` | `"brazil"` | -| `BRUNEI` | `CountryBrunei` | `"brunei"` | -| `BULGARIA` | `CountryBulgaria` | `"bulgaria"` | -| `BURKINA_FASO` | `CountryBurkinaFaso` | `"burkina-faso"` | -| `BURUNDI` | `CountryBurundi` | `"burundi"` | -| `CAMBODIA` | `CountryCambodia` | `"cambodia"` | -| `CAMEROON` | `CountryCameroon` | `"cameroon"` | -| `CANADA` | `CountryCanada` | `"canada"` | -| `CAPE_VERDE` | `CountryCapeVerde` | `"cape-verde"` | -| `CAYMAN_ISLANDS` | `CountryCaymanIslands` | `"cayman-islands"` | -| `CENTRAL_AFRICAN_REPUBLIC` | `CountryCentralAfricanRepublic` | `"central-african-republic"` | -| `CHAD` | `CountryChad` | `"chad"` | -| `CHILE` | `CountryChile` | `"chile"` | -| `CHINA` | `CountryChina` | `"china"` | -| `COCOS_KEELING_ISLANDS` | `CountryCocosKeelingIslands` | `"cocos-keeling-islands"` | -| `COLOMBIA` | `CountryColombia` | `"colombia"` | -| `COMOROS` | `CountryComoros` | `"comoros"` | -| `CONGO` | `CountryCongo` | `"congo"` | -| `COOK_ISLANDS` | `CountryCookIslands` | `"cook-islands"` | -| `COSTA_RICA` | `CountryCostaRica` | `"costa-rica"` | -| `CROATIA` | `CountryCroatia` | `"croatia"` | -| `CUBA` | `CountryCuba` | `"cuba"` | -| `CURACAO` | `CountryCuracao` | `"curacao"` | -| `CYPRUS` | `CountryCyprus` | `"cyprus"` | -| `CZECHIA` | `CountryCzechia` | `"czechia"` | -| `DEMOCRATIC_REPUBLIC_OF_THE_CONGO` | `CountryDemocraticRepublicOfTheCongo` | `"democratic-republic-of-the-congo"` | -| `DENMARK` | `CountryDenmark` | `"denmark"` | -| `DJIBOUTI` | `CountryDjibouti` | `"djibouti"` | -| `DOMINICA` | `CountryDominica` | `"dominica"` | -| `DOMINICAN_REPUBLIC` | `CountryDominicanRepublic` | `"dominican-republic"` | -| `ECUADOR` | `CountryEcuador` | `"ecuador"` | -| `EGYPT` | `CountryEgypt` | `"egypt"` | -| `EL_SALVADOR` | `CountryElSalvador` | `"el-salvador"` | -| `EQUATORIAL_GUINEA` | `CountryEquatorialGuinea` | `"equatorial-guinea"` | -| `ERITREA` | `CountryEritrea` | `"eritrea"` | -| `ESTONIA` | `CountryEstonia` | `"estonia"` | -| `ESWATINI` | `CountryEswatini` | `"eswatini"` | -| `ETHIOPIA` | `CountryEthiopia` | `"ethiopia"` | -| `FALKLAND_ISLANDS_MALVINAS` | `CountryFalklandIslandsMalvinas` | `"falkland-islands-malvinas"` | -| `FAROE_ISLANDS` | `CountryFaroeIslands` | `"faroe-islands"` | -| `FIJI` | `CountryFiji` | `"fiji"` | -| `FINLAND` | `CountryFinland` | `"finland"` | -| `FRANCE` | `CountryFrance` | `"france"` | -| `FRENCH_GUIANA` | `CountryFrenchGuiana` | `"french-guiana"` | -| `FRENCH_POLYNESIA` | `CountryFrenchPolynesia` | `"french-polynesia"` | -| `GABON` | `CountryGabon` | `"gabon"` | -| `GAMBIA` | `CountryGambia` | `"gambia"` | -| `GEORGIA` | `CountryGeorgia` | `"georgia"` | -| `GERMANY` | `CountryGermany` | `"germany"` | -| `GHANA` | `CountryGhana` | `"ghana"` | -| `GIBRALTAR` | `CountryGibraltar` | `"gibraltar"` | -| `GREECE` | `CountryGreece` | `"greece"` | -| `GREENLAND` | `CountryGreenland` | `"greenland"` | -| `GRENADA` | `CountryGrenada` | `"grenada"` | -| `GUADELOUPE` | `CountryGuadeloupe` | `"guadeloupe"` | -| `GUAM` | `CountryGuam` | `"guam"` | -| `GUATEMALA` | `CountryGuatemala` | `"guatemala"` | -| `GUERNSEY` | `CountryGuernsey` | `"guernsey"` | -| `GUINEA` | `CountryGuinea` | `"guinea"` | -| `GUINEA_BISSAU` | `CountryGuineaBissau` | `"guinea-bissau"` | -| `GUYANA` | `CountryGuyana` | `"guyana"` | -| `HAITI` | `CountryHaiti` | `"haiti"` | -| `HONDURAS` | `CountryHonduras` | `"honduras"` | -| `HONG_KONG` | `CountryHongKong` | `"hong-kong"` | -| `HUNGARY` | `CountryHungary` | `"hungary"` | -| `ICELAND` | `CountryIceland` | `"iceland"` | -| `INDIA` | `CountryIndia` | `"india"` | -| `INDONESIA` | `CountryIndonesia` | `"indonesia"` | -| `IRAN` | `CountryIran` | `"iran"` | -| `IRAQ` | `CountryIraq` | `"iraq"` | -| `IRELAND` | `CountryIreland` | `"ireland"` | -| `ISLE_OF_MAN` | `CountryIsleOfMan` | `"isle-of-man"` | -| `ISRAEL` | `CountryIsrael` | `"israel"` | -| `ITALY` | `CountryItaly` | `"italy"` | -| `IVORY_COAST` | `CountryIvoryCoast` | `"ivory-coast"` | -| `JAMAICA` | `CountryJamaica` | `"jamaica"` | -| `JAPAN` | `CountryJapan` | `"japan"` | -| `JERSEY` | `CountryJersey` | `"jersey"` | -| `JORDAN` | `CountryJordan` | `"jordan"` | -| `KAZAKHSTAN` | `CountryKazakhstan` | `"kazakhstan"` | -| `KENYA` | `CountryKenya` | `"kenya"` | -| `KIRIBATI` | `CountryKiribati` | `"kiribati"` | -| `KOSOVO` | `CountryKosovo` | `"kosovo"` | -| `KUWAIT` | `CountryKuwait` | `"kuwait"` | -| `KYRGYZSTAN` | `CountryKyrgyzstan` | `"kyrgyzstan"` | -| `LAOS` | `CountryLaos` | `"laos"` | -| `LATVIA` | `CountryLatvia` | `"latvia"` | -| `LEBANON` | `CountryLebanon` | `"lebanon"` | -| `LESOTHO` | `CountryLesotho` | `"lesotho"` | -| `LIBERIA` | `CountryLiberia` | `"liberia"` | -| `LIBYA` | `CountryLibya` | `"libya"` | -| `LITHUANIA` | `CountryLithuania` | `"lithuania"` | -| `LUXEMBOURG` | `CountryLuxembourg` | `"luxembourg"` | -| `MACAO` | `CountryMacao` | `"macao"` | -| `MADAGASCAR` | `CountryMadagascar` | `"madagascar"` | -| `MALAWI` | `CountryMalawi` | `"malawi"` | -| `MALAYSIA` | `CountryMalaysia` | `"malaysia"` | -| `MALDIVES` | `CountryMaldives` | `"maldives"` | -| `MALI` | `CountryMali` | `"mali"` | -| `MALTA` | `CountryMalta` | `"malta"` | -| `MARSHALL_ISLANDS` | `CountryMarshallIslands` | `"marshall-islands"` | -| `MARTINIQUE` | `CountryMartinique` | `"martinique"` | -| `MAURITANIA` | `CountryMauritania` | `"mauritania"` | -| `MAURITIUS` | `CountryMauritius` | `"mauritius"` | -| `MAYOTTE` | `CountryMayotte` | `"mayotte"` | -| `MEXICO` | `CountryMexico` | `"mexico"` | -| `MICRONESIA` | `CountryMicronesia` | `"micronesia"` | -| `MOLDOVA` | `CountryMoldova` | `"moldova"` | -| `MONACO` | `CountryMonaco` | `"monaco"` | -| `MONGOLIA` | `CountryMongolia` | `"mongolia"` | -| `MONTENEGRO` | `CountryMontenegro` | `"montenegro"` | -| `MONTSERRAT` | `CountryMontserrat` | `"montserrat"` | -| `MOROCCO` | `CountryMorocco` | `"morocco"` | -| `MOZAMBIQUE` | `CountryMozambique` | `"mozambique"` | -| `MYANMAR_BURMA` | `CountryMyanmarBurma` | `"myanmar-burma"` | -| `NAMIBIA` | `CountryNamibia` | `"namibia"` | -| `NAURU` | `CountryNauru` | `"nauru"` | -| `NEPAL` | `CountryNepal` | `"nepal"` | -| `NETHERLANDS` | `CountryNetherlands` | `"netherlands"` | -| `NEW_CALEDONIA` | `CountryNewCaledonia` | `"new-caledonia"` | -| `NEW_ZEALAND` | `CountryNewZealand` | `"new-zealand"` | -| `NICARAGUA` | `CountryNicaragua` | `"nicaragua"` | -| `NIGER` | `CountryNiger` | `"niger"` | -| `NIGERIA` | `CountryNigeria` | `"nigeria"` | -| `NORTH_KOREA` | `CountryNorthKorea` | `"north-korea"` | -| `NORTH_MACEDONIA` | `CountryNorthMacedonia` | `"north-macedonia"` | -| `NORTHERN_MARIANA_ISLANDS` | `CountryNorthernMarianaIslands` | `"northern-mariana-islands"` | -| `NORWAY` | `CountryNorway` | `"norway"` | -| `OMAN` | `CountryOman` | `"oman"` | -| `PAKISTAN` | `CountryPakistan` | `"pakistan"` | -| `PALAU` | `CountryPalau` | `"palau"` | -| `PANAMA` | `CountryPanama` | `"panama"` | -| `PAPUA_NEW_GUINEA` | `CountryPapuaNewGuinea` | `"papua-new-guinea"` | -| `PARAGUAY` | `CountryParaguay` | `"paraguay"` | -| `PERU` | `CountryPeru` | `"peru"` | -| `PHILIPPINES` | `CountryPhilippines` | `"philippines"` | -| `POLAND` | `CountryPoland` | `"poland"` | -| `PORTUGAL` | `CountryPortugal` | `"portugal"` | -| `PUERTO_RICO` | `CountryPuertoRico` | `"puerto-rico"` | -| `QATAR` | `CountryQatar` | `"qatar"` | -| `REUNION` | `CountryReunion` | `"reunion"` | -| `ROMANIA` | `CountryRomania` | `"romania"` | -| `RUSSIA` | `CountryRussia` | `"russia"` | -| `RWANDA` | `CountryRwanda` | `"rwanda"` | -| `SAINT_HELENA` | `CountrySaintHelena` | `"saint-helena"` | -| `SAINT_KITTS_AND_NEVIS` | `CountrySaintKittsAndNevis` | `"saint-kitts-and-nevis"` | -| `SAINT_LUCIA` | `CountrySaintLucia` | `"saint-lucia"` | -| `SAINT_PIERRE_AND_MIQUELON` | `CountrySaintPierreAndMiquelon` | `"saint-pierre-and-miquelon"` | -| `SAINT_VINCENT_AND_THE_GRENADINES` | `CountrySaintVincentAndTheGrenadines` | `"saint-vincent-and-the-grenadines"` | -| `SAMOA` | `CountrySamoa` | `"samoa"` | -| `SAO_TOME_AND_PRINCIPE` | `CountrySaoTomeAndPrincipe` | `"sao-tome-and-principe"` | -| `SAUDI_ARABIA` | `CountrySaudiArabia` | `"saudi-arabia"` | -| `SENEGAL` | `CountrySenegal` | `"senegal"` | -| `SERBIA` | `CountrySerbia` | `"serbia"` | -| `SEYCHELLES` | `CountrySeychelles` | `"seychelles"` | -| `SIERRA_LEONE` | `CountrySierraLeone` | `"sierra-leone"` | -| `SINGAPORE` | `CountrySingapore` | `"singapore"` | -| `SLOVAKIA` | `CountrySlovakia` | `"slovakia"` | -| `SLOVENIA` | `CountrySlovenia` | `"slovenia"` | -| `SOLOMON_ISLANDS` | `CountrySolomonIslands` | `"solomon-islands"` | -| `SOMALIA` | `CountrySomalia` | `"somalia"` | -| `SOUTH_AFRICA` | `CountrySouthAfrica` | `"south-africa"` | -| `SOUTH_KOREA` | `CountrySouthKorea` | `"south-korea"` | -| `SOUTH_SUDAN` | `CountrySouthSudan` | `"south-sudan"` | -| `SPAIN` | `CountrySpain` | `"spain"` | -| `SRI_LANKA` | `CountrySriLanka` | `"sri-lanka"` | -| `SUDAN` | `CountrySudan` | `"sudan"` | -| `SURINAME` | `CountrySuriname` | `"suriname"` | -| `SWEDEN` | `CountrySweden` | `"sweden"` | -| `SWITZERLAND` | `CountrySwitzerland` | `"switzerland"` | -| `SYRIA` | `CountrySyria` | `"syria"` | -| `TAIWAN` | `CountryTaiwan` | `"taiwan"` | -| `TAJIKISTAN` | `CountryTajikistan` | `"tajikistan"` | -| `TANZANIA` | `CountryTanzania` | `"tanzania"` | -| `THAILAND` | `CountryThailand` | `"thailand"` | -| `TIMOR_LESTE_EAST_TIMOR` | `CountryTimorLesteEastTimor` | `"timor-leste-east-timor"` | -| `TOGO` | `CountryTogo` | `"togo"` | -| `TONGA` | `CountryTonga` | `"tonga"` | -| `TRINIDAD_AND_TOBAGO` | `CountryTrinidadAndTobago` | `"trinidad-and-tobago"` | -| `TUNISIA` | `CountryTunisia` | `"tunisia"` | -| `TURKEY` | `CountryTurkey` | `"turkey"` | -| `TURKMENISTAN` | `CountryTurkmenistan` | `"turkmenistan"` | -| `TURKS_AND_CAICOS_ISLANDS` | `CountryTurksAndCaicosIslands` | `"turks-and-caicos-islands"` | -| `TUVALU` | `CountryTuvalu` | `"tuvalu"` | -| `UGANDA` | `CountryUganda` | `"uganda"` | -| `UKRAINE` | `CountryUkraine` | `"ukraine"` | -| `UNITED_ARAB_EMIRATES` | `CountryUnitedArabEmirates` | `"united-arab-emirates"` | -| `UNITED_KINGDOM` | `CountryUnitedKingdom` | `"united-kingdom"` | -| `UNITED_STATES` | `CountryUnitedStates` | `"united-states"` | -| `UNITED_STATES_MINOR_OUTLYING_ISLANDS` | `CountryUnitedStatesMinorOutlyingIslands` | `"united-states-minor-outlying-islands"` | -| `URUGUAY` | `CountryUruguay` | `"uruguay"` | -| `UZBEKISTAN` | `CountryUzbekistan` | `"uzbekistan"` | -| `VANUATU` | `CountryVanuatu` | `"vanuatu"` | -| `VENEZUELA` | `CountryVenezuela` | `"venezuela"` | -| `VIETNAM` | `CountryVietnam` | `"vietnam"` | -| `VIRGIN_ISLANDS_BRITISH` | `CountryVirginIslandsBritish` | `"virgin-islands-british"` | -| `VIRGIN_ISLANDS_US` | `CountryVirginIslandsUs` | `"virgin-islands-us"` | -| `WALLIS_AND_FUTUNA` | `CountryWallisAndFutuna` | `"wallis-and-futuna"` | -| `YEMEN` | `CountryYemen` | `"yemen"` | -| `ZAMBIA` | `CountryZambia` | `"zambia"` | -| `ZIMBABWE` | `CountryZimbabwe` | `"zimbabwe"` | diff --git a/docs/distances.md b/docs/distances.md index 23c8a6b..1942f2b 100644 --- a/docs/distances.md +++ b/docs/distances.md @@ -99,11 +99,8 @@ A distance needs both positions. The airports returned by the [airports listing](airports.md#airports-of-a-country) have none, and a flight may be missing its own. -| SDK | When a position is missing | -|---|---| -| Python | raises `ValueError` | -| Node.js | throws `TypeError` | -| Go | returns an error wrapping `ErrFlightRadar` | +When either position is missing, the call fails with an error instead of +returning a distance. To measure from a listed airport, fetch it by its code first: that one carries the coordinates. diff --git a/docs/errors.md b/docs/errors.md index 06fd016..c9b039a 100644 --- a/docs/errors.md +++ b/docs/errors.md @@ -5,13 +5,45 @@ Every error the SDK defines shares one base, so a single handler can catch them all. -| Python and Node.js | Go sentinel | Go type | Raised when | -|---|---|---|---| -| `FlightRadarError` | `ErrFlightRadar` | | Base of all the errors below | -| `AirportNotFoundError` | `ErrAirportNotFound` | `*AirportNotFoundError` | No airport answers to the code given | -| `CloudflareError` | `ErrCloudflare` | `*CloudflareError` | Cloudflare blocked the request | -| `LoginError` | `ErrLogin` | `*LoginError` | The login failed, or the call needs a session and there is none | -| `DecompressionLimitError` | `ErrDecompressionLimit` | `*DecompressionLimitError` | A response is larger than the [size limit](#response-size-limit) | +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Error | Raised when | +|---|---| +| `FlightRadarError` | Base of all the errors below | +| `AirportNotFoundError` | No airport answers to the code given | +| `CloudflareError` | Cloudflare blocked the request | +| `LoginError` | The login failed, or the call needs a session and there is none | +| `DecompressionLimitError` | A response is larger than the [size limit](#response-size-limit) | +::: + +:::{tab-item} Node.js +:sync: node + +| Error | Raised when | +|---|---| +| `FlightRadarError` | Base of all the errors below | +| `AirportNotFoundError` | No airport answers to the code given | +| `CloudflareError` | Cloudflare blocked the request | +| `LoginError` | The login failed, or the call needs a session and there is none | +| `DecompressionLimitError` | A response is larger than the [size limit](#response-size-limit) | +::: + +:::{tab-item} Go +:sync: go + +| Sentinel | Type | Raised when | +|---|---|---| +| `ErrFlightRadar` | | Base of all the errors below | +| `ErrAirportNotFound` | `*AirportNotFoundError` | No airport answers to the code given | +| `ErrCloudflare` | `*CloudflareError` | Cloudflare blocked the request | +| `ErrLogin` | `*LoginError` | The login failed, or the call needs a session and there is none | +| `ErrDecompressionLimit` | `*DecompressionLimitError` | A response is larger than the [size limit](#response-size-limit) | +::: +:::: ::::{tab-set} :sync-group: lang @@ -107,11 +139,29 @@ many calls or that does not look like a browser. The SDK reports it as a The error carries the blocked response, for inspection: -| SDK | What the error carries | -|---|---| -| Python | `error.response`: the response object, with the challenge page as its content | -| Node.js | `error.response`: the response object; `error.body`: the challenge page, as text | -| Go | `Response`: the `*http.Response`; `Body`: the challenge page, as bytes | +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +`error.response` is the response object, with the challenge page as its content. +::: + +:::{tab-item} Node.js +:sync: node + +`error.response` is the response object, and `error.body` is the challenge page, +as text. +::: + +:::{tab-item} Go +:sync: go + +In a `*CloudflareError`, `Response` is the `*http.Response` and `Body` is the +challenge page, as bytes. +::: +:::: When blocks keep happening: @@ -220,31 +270,60 @@ FlightRadar24 legitimately sends comes close to it. ## Other failures -Not every failure is one of the errors above. +Not every failure is one of the errors above. Invalid arguments are rejected +with the error the language uses for them, and HTTP and network failures come +from the transport. -**Invalid arguments** are rejected with the error the language uses for them: +::::{tab-set} +:sync-group: lang -| Situation | Python | Node.js | -|---|---|---| -| Airport code that is not 3 or 4 characters long | `ValueError` | `TypeError` | -| Flight limit of the airport details out of range | `ValueError` | `RangeError` | -| File type of the flight history other than CSV or KML | `ValueError` | `TypeError` | -| Unknown flight tracker option | `KeyError` | `RangeError` | -| Flight tracker value that is not a number | `TypeError` | `TypeError` | -| Distance between entities without a position | `ValueError` | `TypeError` | -| Countries that are neither a string nor an iterable | `TypeError` | `TypeError` | - -In Go, all of these are plain errors that wrap `ErrFlightRadar`. - -**HTTP and network failures** come from the transport: - -| Situation | Python | Node.js | Go | -|---|---|---|---| -| Unexpected status code | `curl_cffi` `HTTPError` | `Error`, with the status in its message | `*StatusError` | -| Timeout | `curl_cffi` `Timeout` | `Error` named `TimeoutError` | error matching `context.DeadlineExceeded` | -| Network error | `curl_cffi` `RequestsError` | the error of `fetch` | error wrapping a `*url.Error` | - -In Python these are all subclasses of `curl_cffi.requests.errors.RequestsError`. -In Go they wrap `ErrFlightRadar` like every other error of the package, and a -`*StatusError` carries the `StatusCode`, `Status`, `URL` and `Body` of the -response. +:::{tab-item} Python +:sync: python + +| Situation | Error | +|---|---| +| Airport code that is not 3 or 4 characters long | `ValueError` | +| Flight limit of the airport details out of range | `ValueError` | +| File type of the flight history other than CSV or KML | `ValueError` | +| Unknown flight tracker option | `KeyError` | +| Flight tracker value that is not a number | `TypeError` | +| Distance between entities without a position | `ValueError` | +| Countries that are neither a string nor an iterable | `TypeError` | +| Unexpected status code | `curl_cffi` `HTTPError` | +| Timeout | `curl_cffi` `Timeout` | +| Network error | `curl_cffi` `RequestsError` | + +The last three are subclasses of `curl_cffi.requests.errors.RequestsError`. +::: + +:::{tab-item} Node.js +:sync: node + +| Situation | Error | +|---|---| +| Airport code that is not 3 or 4 characters long | `TypeError` | +| Flight limit of the airport details out of range | `RangeError` | +| File type of the flight history other than CSV or KML | `TypeError` | +| Unknown flight tracker option | `RangeError` | +| Flight tracker value that is not a number | `TypeError` | +| Distance between entities without a position | `TypeError` | +| Countries that are neither a string nor an iterable | `TypeError` | +| Unexpected status code | `Error`, with the status in its message | +| Timeout | `Error` named `TimeoutError` | +| Network error | the error of `fetch` | +::: + +:::{tab-item} Go +:sync: go + +| Situation | Error | +|---|---| +| Invalid argument | plain error | +| Unexpected status code | `*StatusError` | +| Timeout | error matching `context.DeadlineExceeded` | +| Network error | error wrapping a `*url.Error` | + +All of them wrap `ErrFlightRadar`. A `*StatusError` carries the `StatusCode`, +`Status`, `URL` and `Body` of the response. +::: +:::: diff --git a/docs/flight.md b/docs/flight.md index cd8a8cc..ac01dd0 100644 --- a/docs/flight.md +++ b/docs/flight.md @@ -7,9 +7,6 @@ flight. Its attributes come in two groups: the ones the feed sends for every flight, and the ones added by the [flight details](flights.md#flight-details). -The names follow the convention of each language. What Python calls -`ground_speed` is `groundSpeed` in Node.js and `GroundSpeed` in Go. - ## Attributes from the feed Present on every flight. @@ -366,51 +363,52 @@ return. ## Methods -| Python | Node.js | Go | Description | -|---|---|---|---| -| `get_altitude()` | `getAltitude()` | `GetAltitude()` | Altitude with its unit. See [Formatted values](flights.md#formatted-values). | -| `get_flight_level()` | `getFlightLevel()` | `GetFlightLevel()` | Flight level with its unit | -| `get_ground_speed()` | `getGroundSpeed()` | `GetGroundSpeed()` | Ground speed with its unit | -| `get_heading()` | `getHeading()` | `GetHeading()` | Heading with its unit | -| `get_vertical_speed()` | `getVerticalSpeed()` | `GetVerticalSpeed()` | Vertical speed with its unit | -| `check_info(**info)` | `checkInfo(info)` | `CheckInfo(criteria)` | Whether the flight matches some values. See [Checking a flight](flights.md#checking-a-flight-against-criteria). | -| `set_flight_details(details)` | `setFlightDetails(details)` | `SetFlightDetails(details)` | Attach a details payload to the flight | -| `get_distance_from(entity)` | `getDistanceFrom(entity)` | `GetDistanceFrom(other)` | Distance to another entity, in kilometers. See [Distances](distances.md). | - -## Building a flight yourself - -The client builds the flights it returns. To build one from a feed entry you -already have, pass the identifier and the entry: the array FlightRadar24 sends -for that flight. - ::::{tab-set} :sync-group: lang :::{tab-item} Python :sync: python -```python -from FlightRadarAPI import Flight - -flight = Flight("2e0f1a2", feed_row) -``` +| Method | Description | +|---|---| +| `get_altitude()` | Altitude with its unit. See [Formatted values](flights.md#formatted-values). | +| `get_flight_level()` | Flight level with its unit | +| `get_ground_speed()` | Ground speed with its unit | +| `get_heading()` | Heading with its unit | +| `get_vertical_speed()` | Vertical speed with its unit | +| `check_info(**info)` | Whether the flight matches some values. See [Checking a flight](flights.md#checking-a-flight-against-criteria). | +| `set_flight_details(details)` | Attach a details payload to the flight | +| `get_distance_from(entity)` | Distance to another entity, in kilometers. See [Distances](distances.md). | ::: :::{tab-item} Node.js :sync: node -```javascript -const { Flight } = require("flightradarapi"); - -const flight = new Flight("2e0f1a2", feedRow); -``` +| Method | Description | +|---|---| +| `getAltitude()` | Altitude with its unit. See [Formatted values](flights.md#formatted-values). | +| `getFlightLevel()` | Flight level with its unit | +| `getGroundSpeed()` | Ground speed with its unit | +| `getHeading()` | Heading with its unit | +| `getVerticalSpeed()` | Vertical speed with its unit | +| `checkInfo(info)` | Whether the flight matches some values. See [Checking a flight](flights.md#checking-a-flight-against-criteria). | +| `setFlightDetails(details)` | Attach a details payload to the flight | +| `getDistanceFrom(entity)` | Distance to another entity, in kilometers. See [Distances](distances.md). | ::: :::{tab-item} Go :sync: go -```go -flight := flightradarapi.NewFlight("2e0f1a2", feedRow) // feedRow is a []any -``` +| Method | Description | +|---|---| +| `GetAltitude()` | Altitude with its unit. See [Formatted values](flights.md#formatted-values). | +| `GetFlightLevel()` | Flight level with its unit | +| `GetGroundSpeed()` | Ground speed with its unit | +| `GetHeading()` | Heading with its unit | +| `GetVerticalSpeed()` | Vertical speed with its unit | +| `CheckInfo(criteria)` | Whether the flight matches some values. See [Checking a flight](flights.md#checking-a-flight-against-criteria). | +| `SetFlightDetails(details)` | Attach a details payload to the flight | +| `GetDistanceFrom(other)` | Distance to another entity, in kilometers. See [Distances](distances.md). | ::: + :::: diff --git a/docs/flights.md b/docs/flights.md index 6807180..3726c2e 100644 --- a/docs/flights.md +++ b/docs/flights.md @@ -226,13 +226,46 @@ the details of thousands of flights is slow and likely to get you blocked. A flight can render its numbers with their units. -| Python | Node.js | Go | Example | -|---|---|---|---| -| `get_altitude()` | `getAltitude()` | `GetAltitude()` | `"35000 ft"` | -| `get_flight_level()` | `getFlightLevel()` | `GetFlightLevel()` | `"350 FL"` | -| `get_ground_speed()` | `getGroundSpeed()` | `GetGroundSpeed()` | `"480 kts"` | -| `get_heading()` | `getHeading()` | `GetHeading()` | `"90°"` | -| `get_vertical_speed()` | `getVerticalSpeed()` | `GetVerticalSpeed()` | `"-1280 fpm"` | +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Method | Example | +|---|---| +| `get_altitude()` | `"35000 ft"` | +| `get_flight_level()` | `"350 FL"` | +| `get_ground_speed()` | `"480 kts"` | +| `get_heading()` | `"90°"` | +| `get_vertical_speed()` | `"-1280 fpm"` | +::: + +:::{tab-item} Node.js +:sync: node + +| Method | Example | +|---|---| +| `getAltitude()` | `"35000 ft"` | +| `getFlightLevel()` | `"350 FL"` | +| `getGroundSpeed()` | `"480 kts"` | +| `getHeading()` | `"90°"` | +| `getVerticalSpeed()` | `"-1280 fpm"` | +::: + +:::{tab-item} Go +:sync: go + +| Method | Example | +|---|---| +| `GetAltitude()` | `"35000 ft"` | +| `GetFlightLevel()` | `"350 FL"` | +| `GetGroundSpeed()` | `"480 kts"` | +| `GetHeading()` | `"90°"` | +| `GetVerticalSpeed()` | `"-1280 fpm"` | +::: + +:::: The flight level is shown from 10000 ft up; below that, `get_flight_level()` returns the same as `get_altitude()`. Each of these returns `"N/A"` when the diff --git a/docs/live-data.md b/docs/live-data.md index 8ae7977..7c67ea4 100644 --- a/docs/live-data.md +++ b/docs/live-data.md @@ -1,49 +1,14 @@ # Most Tracked, Disruptions and Eruptions -Three calls return a snapshot of what FlightRadar24 is showing right now. None -takes a parameter, and each returns the payload as FlightRadar24 sends it: a -`dict` in Python, an `object` in Node.js and a `map[string]any` in Go. +Three calls return a snapshot of what FlightRadar24 is showing right now: -```{note} -The shape of these payloads is defined by FlightRadar24 and may change. Inspect -a response before relying on a particular field. -``` - -## Most tracked flights - -The flights most people are following on FlightRadar24 at the moment. - -::::{tab-set} -:sync-group: lang - -:::{tab-item} Python -:sync: python - -```python -most_tracked = fr_api.get_most_tracked() -``` -::: - -:::{tab-item} Node.js -:sync: node - -```javascript -const mostTracked = await frApi.getMostTracked(); -``` -::: - -:::{tab-item} Go -:sync: go - -```go -mostTracked, err := client.GetMostTracked(ctx) -``` -::: -:::: - -## Airport disruptions +- **Most tracked flights**: the flights most people are following at the moment. +- **Airport disruptions**: the airports currently affected by delays and + cancellations. +- **Volcanic eruptions**: the boundaries of the eruptions and ash clouds + impacting aviation. -The airports currently affected by delays and cancellations. +None takes a parameter, and each returns the payload as FlightRadar24 sends it. ::::{tab-set} :sync-group: lang @@ -52,7 +17,9 @@ The airports currently affected by delays and cancellations. :sync: python ```python -disruptions = fr_api.get_airport_disruptions() +most_tracked = fr_api.get_most_tracked() # dict +disruptions = fr_api.get_airport_disruptions() # dict +eruptions = fr_api.get_volcanic_eruptions() # dict ``` ::: @@ -60,7 +27,9 @@ disruptions = fr_api.get_airport_disruptions() :sync: node ```javascript -const disruptions = await frApi.getAirportDisruptions(); +const mostTracked = await frApi.getMostTracked(); // object +const disruptions = await frApi.getAirportDisruptions(); // object +const eruptions = await frApi.getVolcanicEruptions(); // object ``` ::: @@ -68,39 +37,14 @@ const disruptions = await frApi.getAirportDisruptions(); :sync: go ```go -disruptions, err := client.GetAirportDisruptions(ctx) +mostTracked, err := client.GetMostTracked(ctx) // map[string]any +disruptions, err := client.GetAirportDisruptions(ctx) // map[string]any +eruptions, err := client.GetVolcanicEruptions(ctx) // map[string]any ``` ::: :::: -## Volcanic eruptions - -The boundaries of the volcanic eruptions and ash clouds impacting aviation. - -::::{tab-set} -:sync-group: lang - -:::{tab-item} Python -:sync: python - -```python -eruptions = fr_api.get_volcanic_eruptions() -``` -::: - -:::{tab-item} Node.js -:sync: node - -```javascript -const eruptions = await frApi.getVolcanicEruptions(); -``` -::: - -:::{tab-item} Go -:sync: go - -```go -eruptions, err := client.GetVolcanicEruptions(ctx) +```{note} +The shape of these payloads is defined by FlightRadar24 and may change. Inspect +a response before relying on a particular field. ``` -::: -:::: diff --git a/docs/searching.md b/docs/searching.md index f0d4142..d5efb28 100644 --- a/docs/searching.md +++ b/docs/searching.md @@ -57,12 +57,6 @@ The result groups what was found by category. Each key is a category, as FlightRadar24 names it in the response, and each value is the list of results of that category, as FlightRadar24 sends them. -| SDK | Type | -|---|---| -| Python | `dict` of category to `list` | -| Node.js | `object` of category to `Array` | -| Go | `map[string][]any` | - `limit` is sent to FlightRadar24 as the maximum number of results. ```{note} diff --git a/docs/tracker-config.md b/docs/tracker-config.md index c50aa90..7b6ea31 100644 --- a/docs/tracker-config.md +++ b/docs/tracker-config.md @@ -11,21 +11,70 @@ as the filters of the FlightRadar24 map, and they live in a Every value is a string holding a number. The switches take `"1"` (on) or `"0"` (off). -| Python and Node.js | Go | Default | Meaning | -|---|---|---|---| -| `faa` | `FAA` | `"1"` | Include data from the FAA | -| `satellite` | `Satellite` | `"1"` | Include satellite-based tracking | -| `mlat` | `MLAT` | `"1"` | Include MLAT (multilateration) positions | -| `flarm` | `FLARM` | `"1"` | Include FLARM positions | -| `adsb` | `ADSB` | `"1"` | Include ADS-B positions | -| `gnd` | `GND` | `"1"` | Include aircraft on the ground | -| `air` | `Air` | `"1"` | Include aircraft in the air | -| `vehicles` | `Vehicles` | `"1"` | Include ground vehicles | -| `estimated` | `Estimated` | `"1"` | Include estimated positions | -| `maxage` | `MaxAge` | `"14400"` | Maximum age of a position, in seconds | -| `gliders` | `Gliders` | `"1"` | Include gliders | -| `stats` | `Stats` | `"1"` | Include the feed statistics in the response | -| `limit` | `Limit` | `"5000"` | Maximum number of flights returned | +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Option | Default | Meaning | +|---|---|---| +| `faa` | `"1"` | Include data from the FAA | +| `satellite` | `"1"` | Include satellite-based tracking | +| `mlat` | `"1"` | Include MLAT (multilateration) positions | +| `flarm` | `"1"` | Include FLARM positions | +| `adsb` | `"1"` | Include ADS-B positions | +| `gnd` | `"1"` | Include aircraft on the ground | +| `air` | `"1"` | Include aircraft in the air | +| `vehicles` | `"1"` | Include ground vehicles | +| `estimated` | `"1"` | Include estimated positions | +| `maxage` | `"14400"` | Maximum age of a position, in seconds | +| `gliders` | `"1"` | Include gliders | +| `stats` | `"1"` | Include the feed statistics in the response | +| `limit` | `"5000"` | Maximum number of flights returned | +::: + +:::{tab-item} Node.js +:sync: node + +| Option | Default | Meaning | +|---|---|---| +| `faa` | `"1"` | Include data from the FAA | +| `satellite` | `"1"` | Include satellite-based tracking | +| `mlat` | `"1"` | Include MLAT (multilateration) positions | +| `flarm` | `"1"` | Include FLARM positions | +| `adsb` | `"1"` | Include ADS-B positions | +| `gnd` | `"1"` | Include aircraft on the ground | +| `air` | `"1"` | Include aircraft in the air | +| `vehicles` | `"1"` | Include ground vehicles | +| `estimated` | `"1"` | Include estimated positions | +| `maxage` | `"14400"` | Maximum age of a position, in seconds | +| `gliders` | `"1"` | Include gliders | +| `stats` | `"1"` | Include the feed statistics in the response | +| `limit` | `"5000"` | Maximum number of flights returned | +::: + +:::{tab-item} Go +:sync: go + +| Field | Default | Meaning | +|---|---|---| +| `FAA` | `"1"` | Include data from the FAA | +| `Satellite` | `"1"` | Include satellite-based tracking | +| `MLAT` | `"1"` | Include MLAT (multilateration) positions | +| `FLARM` | `"1"` | Include FLARM positions | +| `ADSB` | `"1"` | Include ADS-B positions | +| `GND` | `"1"` | Include aircraft on the ground | +| `Air` | `"1"` | Include aircraft in the air | +| `Vehicles` | `"1"` | Include ground vehicles | +| `Estimated` | `"1"` | Include estimated positions | +| `MaxAge` | `"14400"` | Maximum age of a position, in seconds | +| `Gliders` | `"1"` | Include gliders | +| `Stats` | `"1"` | Include the feed statistics in the response | +| `Limit` | `"5000"` | Maximum number of flights returned | +::: + +:::: ```{note} These are the query parameters of FlightRadar24's own feed, and FlightRadar24 @@ -103,7 +152,7 @@ err := client.SetFlightTrackerConfig(nil, map[string]string{"limit": "10", "gnd" flights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{}) // at most 10 flights, none on the ground ``` -The keys of the map are the lower-case names of the first column. +The keys of the map are the field names in lower case: `limit`, `maxage`, `gnd`. Or read the current settings, change them and set them back: From 02a84ce28650ab43396b92bb5d8ab1cb3412e882 Mon Sep 17 00:00:00 2001 From: JeanExtreme002 Date: Sat, 14 Nov 2026 23:32:13 -0300 Subject: [PATCH 7/8] docs: hide the version and language selectors The site is published as a single version. --- docs/conf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 4593aa6..b655bfe 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,6 +52,9 @@ "navigation_depth": 3, "prev_next_buttons_location": "bottom", "style_external_links": True, + # The site is published as a single version: no switchers in the sidebar. + "version_selector": False, + "language_selector": False, } # "Edit on GitHub" link, plus the data the sidebar repository card reads. @@ -69,7 +72,7 @@ # -- Extensions -------------------------------------------------------------- # Read the Docs exports the address the build is served from. -html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "https://flightradarapi.readthedocs.io/en/latest/") +html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "https://flightradarapi.readthedocs.io/") ogp_site_url = html_baseurl ogp_site_name = "FlightRadarAPI" From 51919928fbdebf728ee946a8c438a7ceac21dcad Mon Sep 17 00:00:00 2001 From: JeanExtreme002 Date: Sat, 14 Nov 2026 23:32:13 -0300 Subject: [PATCH 8/8] docs: correct examples and failure descriptions flagged in review Wrap the Node.js home example in an async function, check the error in the Go details example, and describe what really happens with a missing flight value: check_info comparisons, distances, what each SDK retries and where the response size limit applies. --- docs/distances.md | 27 +++++++++++++++++++++++-- docs/errors.md | 32 +++++++++++++++++++---------- docs/flights.md | 50 +++++++++++----------------------------------- docs/index.md | 12 +++++++---- docs/quickstart.md | 3 ++- 5 files changed, 69 insertions(+), 55 deletions(-) diff --git a/docs/distances.md b/docs/distances.md index 1942f2b..9ba0a87 100644 --- a/docs/distances.md +++ b/docs/distances.md @@ -99,8 +99,31 @@ A distance needs both positions. The airports returned by the [airports listing](airports.md#airports-of-a-country) have none, and a flight may be missing its own. -When either position is missing, the call fails with an error instead of -returning a distance. +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +An airport without a position holds `None`, and the call raises `ValueError`. +A flight without one holds the string `"N/A"`, and the call raises `TypeError`. +::: + +:::{tab-item} Node.js +:sync: node + +An airport without a position holds `null`, and the call throws `TypeError`. +A flight without one holds the string `"N/A"`, and the call returns `NaN` +instead of throwing: check the result, or the coordinates, before using it. +::: + +:::{tab-item} Go +:sync: go + +A missing position is `nil`, and the call returns an error wrapping +`ErrFlightRadar`. +::: +:::: To measure from a listed airport, fetch it by its code first: that one carries the coordinates. diff --git a/docs/errors.md b/docs/errors.md index c9b039a..2142ba7 100644 --- a/docs/errors.md +++ b/docs/errors.md @@ -174,8 +174,8 @@ When blocks keep happening: ## Retrying By default a failed request is not retried. A `RetryPolicy` retries the -transient failures (a Cloudflare block, a timeout, a network error) with an -exponential backoff. +failures that may go away on their own, such as a Cloudflare block or a timeout, +with an exponential backoff. ::::{tab-set} :sync-group: lang @@ -197,6 +197,10 @@ fr_api = FlightRadar24API(retry=RetryPolicy(max_attempts=3)) | `jitter` | `0.5` | Upper bound of the random seconds added to each wait | A `max_attempts` below 1 or a negative delay raises `ValueError`. + +Retried: a `CloudflareError` and every `curl_cffi` request error. That includes +timeouts and network errors, and also an unexpected status code, such as a 404 +or a 500, which is retried like the rest. ::: :::{tab-item} Node.js @@ -216,6 +220,9 @@ const frApi = new FlightRadar24API({ retry: new RetryPolicy({ maxAttempts: 3 }) | `jitterMs` | `500` | Upper bound of the random milliseconds added to each wait | A `maxAttempts` below 1 or a negative delay throws an `Error`. + +Retried: a `CloudflareError`, a timeout and a connection reset. An unexpected +status code is not. ::: :::{tab-item} Go @@ -251,6 +258,9 @@ retry := &flightradarapi.RetryPolicy{ | `Jitter` | `500ms` | Upper bound of the random time added to each wait. Zero means none. | `NewRetryPolicy` returns an error for a number of attempts below 1. + +Retried: a Cloudflare block, a timeout and a network error. An unexpected +status code and a cancelled context are not. ::: :::: @@ -258,15 +268,17 @@ The wait doubles after each attempt, up to the maximum: with the defaults, about 1, 2, 4, 8 seconds and so on, each plus the jitter. When every attempt fails, the last error is the one you get. -Only transient failures are retried. An airport that does not exist, a failed -login or an oversized response fails at once. +An airport that does not exist, a failed login or an oversized response is +never retried. ## Response size limit -A response body is limited to 64 MiB, both as it arrives and after it is -decompressed. A body past the limit fails with `DecompressionLimitError` -(`ErrDecompressionLimit` in Go) instead of exhausting memory. Nothing -FlightRadar24 legitimately sends comes close to it. +A response body is limited to 64 MiB once decompressed. A body past the limit +fails with `DecompressionLimitError` (`ErrDecompressionLimit` in Go) instead of +exhausting memory. Nothing FlightRadar24 legitimately sends comes close to it. + +Python and Go also apply the limit to the compressed body as it arrives. Node.js +counts the decompressed bytes only. ## Other failures @@ -287,7 +299,7 @@ from the transport. | File type of the flight history other than CSV or KML | `ValueError` | | Unknown flight tracker option | `KeyError` | | Flight tracker value that is not a number | `TypeError` | -| Distance between entities without a position | `ValueError` | +| Distance between entities without a position | `ValueError`, or `TypeError` (see [Distances](distances.md#entities-without-a-position)) | | Countries that are neither a string nor an iterable | `TypeError` | | Unexpected status code | `curl_cffi` `HTTPError` | | Timeout | `curl_cffi` `Timeout` | @@ -306,7 +318,7 @@ The last three are subclasses of `curl_cffi.requests.errors.RequestsError`. | File type of the flight history other than CSV or KML | `TypeError` | | Unknown flight tracker option | `RangeError` | | Flight tracker value that is not a number | `TypeError` | -| Distance between entities without a position | `TypeError` | +| Distance between entities without a position | `TypeError`, or no error and `NaN` (see [Distances](distances.md#entities-without-a-position)) | | Countries that are neither a string nor an iterable | `TypeError` | | Unexpected status code | `Error`, with the status in its message | | Timeout | `Error` named `TimeoutError` | diff --git a/docs/flights.md b/docs/flights.md index 3726c2e..0689087 100644 --- a/docs/flights.md +++ b/docs/flights.md @@ -210,6 +210,10 @@ flights, err := client.GetFlights(ctx, flightradarapi.FlightSearch{ Details: true, }) +if err != nil { + log.Fatal(err) +} + for _, flight := range flights { fmt.Println(flight.Callsign, "→", flight.Details.DestinationAirportName) } @@ -287,6 +291,9 @@ flight.check_info(min_altitude=6700, max_altitude=13000, airline_icao="THY") # ``` The prefixes are `min_` and `max_`. A name the flight does not have is ignored. + +A `min_` or `max_` comparison raises `TypeError` when the flight has no value +for that attribute, which is then the string `"N/A"`. ::: :::{tab-item} Node.js @@ -297,7 +304,8 @@ flight.checkInfo({ minAltitude: 6700, maxAltitude: 13000, airlineIcao: "THY" }); ``` The prefixes are `min` and `max`, followed by the attribute name in camelCase. -A name the flight does not have is ignored. +A name the flight does not have is ignored, and a `min` or `max` comparison is +`false` when the flight has no value for that attribute. ::: :::{tab-item} Go @@ -312,43 +320,9 @@ matched, err := flight.CheckInfo(map[string]any{ ``` The keys are the snake_case names the Python SDK uses, with the prefixes `min_` -and `max_`. A name that exists nowhere is an error, instead of being ignored. -::: -:::: - -A handy way to filter a list you already have: - -::::{tab-set} -:sync-group: lang - -:::{tab-item} Python -:sync: python - -```python -cruising = [flight for flight in flights if flight.check_info(min_altitude=30000)] -``` -::: - -:::{tab-item} Node.js -:sync: node - -```javascript -const cruising = flights.filter((flight) => flight.checkInfo({ minAltitude: 30000 })); -``` -::: - -:::{tab-item} Go -:sync: go - -```go -var cruising []*flightradarapi.Flight - -for _, flight := range flights { - if matched, _ := flight.CheckInfo(map[string]any{"min_altitude": 30000}); matched { - cruising = append(cruising, flight) - } -} -``` +and `max_`. A name that exists nowhere is an error, instead of being ignored, +and a `min_` or `max_` comparison is `false` when the flight has no value for +that field. ::: :::: diff --git a/docs/index.md b/docs/index.md index 2f7958f..fee252d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -94,13 +94,17 @@ npm install flightradarapi ```javascript const { FlightRadar24API } = require("flightradarapi"); -const frApi = new FlightRadar24API(); +async function main() { + const frApi = new FlightRadar24API(); -const flights = await frApi.getFlights("UAE"); + const flights = await frApi.getFlights("UAE"); -for (const flight of flights.slice(0, 5)) { - console.log(flight.callsign, flight.getFlightLevel(), flight.getGroundSpeed()); + for (const flight of flights.slice(0, 5)) { + console.log(flight.callsign, flight.getFlightLevel(), flight.getGroundSpeed()); + } } + +main(); ``` ::: diff --git a/docs/quickstart.md b/docs/quickstart.md index 75159e1..b2e8fba 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -29,7 +29,8 @@ const { FlightRadar24API } = require("flightradarapi"); const frApi = new FlightRadar24API(); ``` -Every method that talks to FlightRadar24 returns a `Promise`. +Every method that talks to FlightRadar24 returns a `Promise`. The examples use +`await`, so run them inside an `async` function. ::: :::{tab-item} Go