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/.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..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,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. + +## 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 +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 +``` + +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 +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/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/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..8b67817 --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,183 @@ +/* FlightRadarAPI docs: tweaks on top of the Read the Docs theme. */ + +/* --- 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; +} + +@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/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/_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..b205ba2 --- /dev/null +++ b/docs/account.md @@ -0,0 +1,203 @@ +# 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) +``` +::: + +:::{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) +``` +::: +:::: + +```{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..5e0402c --- /dev/null +++ b/docs/airport.md @@ -0,0 +1,332 @@ +# 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. In the tables +below, a column per call says whether the attribute is filled in: + +- 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 + +### Identity and location + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| Attribute | Description | `get_airports()` | `get_airport()` | with 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 | `getAirports()` | `getAirport()` | with 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 | `GetAirports()` | `GetAirport()` | with 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 | `get_airports()` | `get_airport()` | with 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 | `getAirports()` | `getAirport()` | with 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 | `GetAirports()` | `GetAirport()` | with 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 | `get_airports()` | `get_airport()` | with 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 | `getAirports()` | `getAirport()` | with 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 | `GetAirports()` | `GetAirport()` | with 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 | `get_airports()` | `get_airport()` | with 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 | `getAirports()` | `getAirport()` | with 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 | `GetAirports()` | `GetAirport()` | with 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 | `get_airports()` | `get_airport()` | with 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 | `getAirports()` | `getAirport()` | with 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 | `GetAirports()` | `GetAirport()` | with 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 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 +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 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 +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 + +::::{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 + +| Method | Description | +|---|---| +| `setAirportDetails(details)` | Fill the airport in from a details payload | +| `getDistanceFrom(entity)` | Distance to another entity, in kilometers. See [Distances](distances.md). | +::: + +:::{tab-item} Go +:sync: go + +| Method | Description | +|---|---| +| `SetAirportDetails(details)` | Fill the airport in from a details payload | +| `GetDistanceFrom(other)` | Distance to another entity, in kilometers. See [Distances](distances.md). | +::: + +:::: diff --git a/docs/airports.md b/docs/airports.md new file mode 100644 index 0000000..8b301f9 --- /dev/null +++ b/docs/airports.md @@ -0,0 +1,277 @@ +# 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. +::: +:::: + +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): 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..7b204b2 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,246 @@ +# 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. +The Go package is also documented, from its source, on +[pkg.go.dev](https://pkg.go.dev/github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi). + +## Flights + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| 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 + +| 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 + +| 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). | +::: + +:::: + +## Zones and bounds + +::::{tab-set} +:sync-group: lang + +:::{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 + +| 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). | +::: + +:::{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 + +::::{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 + +::::{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 + +::::{tab-set} +:sync-group: lang + +:::{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). | +::: + +:::: + +## Account + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| 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 + +| 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 + +| 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). | +::: + +:::: diff --git a/docs/client.md b/docs/client.md new file mode 100644 index 0000000..ff9e6e6 --- /dev/null +++ b/docs/client.md @@ -0,0 +1,279 @@ +# Configuration + +The client works with no configuration. A few options are there for when the +defaults do not fit. + +::::{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 + +:::{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. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +Warnings go to the standard `logging` module, under the logger `FlightRadarAPI`. + +```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 + +Warnings go to `log/slog`, through `slog.Default()` unless `SetLogger` is called. + +```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..b655bfe --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,84 @@ +# -*- 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_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, + # 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. +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/") + +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..d203b59 --- /dev/null +++ b/docs/countries.md @@ -0,0 +1,75 @@ +# Countries + +The [airports listing](airports.md#airports-of-a-country) is narrowed by +country. Each SDK names the countries FlightRadar24 lists, 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. diff --git a/docs/distances.md b/docs/distances.md new file mode 100644 index 0000000..9ba0a87 --- /dev/null +++ b/docs/distances.md @@ -0,0 +1,129 @@ +# 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. + +::::{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 new file mode 100644 index 0000000..2142ba7 --- /dev/null +++ b/docs/errors.md @@ -0,0 +1,341 @@ +# Errors and Retries + +## The errors of the SDK + +Every error the SDK defines shares one base, so a single handler can catch them +all. + +::::{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 + +:::{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: + +::::{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: + +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 +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 + +:::{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`. + +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 +: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`. + +Retried: a `CloudflareError`, a timeout and a connection reset. An unexpected +status code is not. +::: + +:::{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. + +Retried: a Cloudflare block, a timeout and a network error. An unexpected +status code and a cancelled context are not. +::: +:::: + +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. + +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 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 + +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. + +::::{tab-set} +:sync-group: lang + +:::{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`, 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` | +| 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`, 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` | +| 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 new file mode 100644 index 0000000..ac01dd0 --- /dev/null +++ b/docs/flight.md @@ -0,0 +1,414 @@ +# 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). + +## 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 + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +| 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 + +| 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 + +| 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 new file mode 100644 index 0000000..0689087 --- /dev/null +++ b/docs/flights.md @@ -0,0 +1,332 @@ +# 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, +}) + +if err != nil { + log.Fatal(err) +} + +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. + +::::{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 +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. + +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 +: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, and a `min` or `max` comparison is +`false` when the flight has no value for that attribute. +::: + +:::{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, +and a `min_` or `max_` comparison is `false` when the flight has no value for +that field. +::: +:::: + +## 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/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/index.md b/docs/index.md index 65225ff..fee252d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,72 +1,219 @@ -# 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

+ +## FlightRadarAPI Documentation +Unofficial SDK for [FlightRadar24](https://www.flightradar24.com/), for +**Python**, **Node.js** and **Go**. Live flights, airports, airlines, zones and +more. + +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). + + + + + + + + + + + + + + + + + + + + + + +
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 +
+ +```{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 + +:::{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"); + +async function main() { + 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()); + } +} + +main(); +``` +::: + +:::{tab-item} Go +:sync: go + +```bash +go get github.com/JeanExtreme002/FlightRadarAPI/go@latest +``` + +```go +package main + +import ( + "context" + "fmt" + "log" + + "github.com/JeanExtreme002/FlightRadarAPI/go/flightradarapi" +) + +func main() { + client := flightradarapi.New() + + 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()) + } +} +``` +::: +:::: + +New here? Start with [Installation](installation.md) and the +[Quick Start](quickstart.md). + +```{toctree} +:caption: Getting Started +:hidden: + +installation +quickstart +``` -Unofficial SDK for [FlightRadar24](https://www.flightradar24.com/) for Python 3, Node.js and Go. +```{toctree} +:caption: Flights +: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). +flights +zones +tracker-config +distances +``` -See more information at [Flightradar24's terms and conditions](https://www.flightradar24.com/terms-and-conditions). +```{toctree} +:caption: Airports & Airlines +:hidden: -[![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/) +airports +airlines +``` -!!! info - This is NOT an official FlightRadar24 API. You can access their official API [here](https://fr24api.flightradar24.com/). +```{toctree} +:caption: More Data +:hidden: -
+searching +live-data +``` + +```{toctree} +:caption: Account +:hidden: -- :octicons-file-code-16:{ .lg .middle } __100% Free and Open Source!__ +account +``` - --- +```{toctree} +:caption: Client +:hidden: - 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 - ``` +client +errors +``` + +```{toctree} +:caption: API Reference +:hidden: + +api +flight +airport +countries +``` + +```{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..2cd8491 --- /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 | +|---|---| +| 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`. + +## 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..7c67ea4 --- /dev/null +++ b/docs/live-data.md @@ -0,0 +1,50 @@ +# Most Tracked, Disruptions and Eruptions + +Three calls return a snapshot of what FlightRadar24 is showing right now: + +- **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. + +None takes a parameter, and each returns the payload as FlightRadar24 sends it. + +::::{tab-set} +:sync-group: lang + +:::{tab-item} Python +:sync: python + +```python +most_tracked = fr_api.get_most_tracked() # dict +disruptions = fr_api.get_airport_disruptions() # dict +eruptions = fr_api.get_volcanic_eruptions() # dict +``` +::: + +:::{tab-item} Node.js +:sync: node + +```javascript +const mostTracked = await frApi.getMostTracked(); // object +const disruptions = await frApi.getAirportDisruptions(); // object +const eruptions = await frApi.getVolcanicEruptions(); // object +``` +::: + +:::{tab-item} Go +:sync: go + +```go +mostTracked, err := client.GetMostTracked(ctx) // map[string]any +disruptions, err := client.GetAirportDisruptions(ctx) // map[string]any +eruptions, err := client.GetVolcanicEruptions(ctx) // map[string]any +``` +::: +:::: + +```{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/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/projects.md b/docs/projects.md index abb2550..b29383b 100644 --- a/docs/projects.md +++ b/docs/projects.md @@ -1,47 +1,45 @@ ---- -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/) - - --- - - 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 } +Built something with FlightRadarAPI? Add it to this [page](https://github.com/JeanExtreme002/FlightRadarAPI/edit/main/docs/projects.md). 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/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..b2e8fba --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,176 @@ +# 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`. The examples use +`await`, so run them inside an `async` function. +::: + +:::{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/searching.md b/docs/searching.md new file mode 100644 index 0000000..d5efb28 --- /dev/null +++ b/docs/searching.md @@ -0,0 +1,65 @@ +# 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. + +`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..7b6ea31 --- /dev/null +++ b/docs/tracker-config.md @@ -0,0 +1,215 @@ +# 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). + +::::{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 +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 field names in lower case: `limit`, `maxage`, `gnd`. + +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..b6cf332 --- /dev/null +++ b/docs/zones.md @@ -0,0 +1,191 @@ +# 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"] +``` +::: +:::: + +## 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/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/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/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"}})); diff --git a/python/Makefile b/python/Makefile index 6e93445..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 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 (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,22 +245,16 @@ 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). +# 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 MkDocs site...$(NC)" - cd .. && mkdocs build - @echo "$(GREEN)Site generated in ../site/$(NC)" + $(MAKE) -C .. docs -# Serve the MkDocs site locally with auto-reload. .PHONY: docs-serve docs-serve: - @echo "$(GREEN)Serving MkDocs site at http://127.0.0.1:8000 ...$(NC)" - cd .. && mkdocs serve + $(MAKE) -C .. docs-serve # 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]