From 51a4bc33821a94f06190156ff66e22258fca766b Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Tue, 10 Jun 2025 10:27:25 +0200 Subject: [PATCH] add logging for opensky user, update limits iterations 4000credits/d --- flightmonitor/data/config.py | 19 +++----- flightmonitor/data/opensky_live_adapter.py | 50 +++++++++++++--------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/flightmonitor/data/config.py b/flightmonitor/data/config.py index 8a8fa21..208b369 100644 --- a/flightmonitor/data/config.py +++ b/flightmonitor/data/config.py @@ -14,7 +14,7 @@ be clearly documented. # --- API Configuration --- # Base URL for the OpenSky Network API's 'states/all' endpoint. -OPENSKY_API_URL: str = "https://opensky-network.org/api/states/all" # Used for direct calls if OAuth2 implemented +OPENSKY_API_URL: str = "https://opensky-network.org/api/states/all" # Used for direct calls with OAuth2 # Default timeout for API requests in seconds. DEFAULT_API_TIMEOUT_SECONDS: int = 15 @@ -22,24 +22,19 @@ DEFAULT_API_TIMEOUT_SECONDS: int = 15 # --- OpenSky Network API Authentication Configuration --- # Set to True to attempt authentication with OpenSky Network API using OAuth2 Client Credentials. # If False, or if Client ID/Secret are not provided, anonymous access will be used. -USE_OPENSKY_CREDENTIALS: bool = False # MODIFIED: Default to False, set to True if you want to use OAuth2 +USE_OPENSKY_CREDENTIALS: bool = True # Set this to True to enable OAuth2 authentication -# MODIFIED: Changed from USERNAME/PASSWORD to CLIENT_ID/CLIENT_SECRET for OAuth2 # Your OpenSky Network API Client ID. -# Best stored as an environment variable (e.g., OPENSKY_CLIENT_ID). -OPENSKY_CLIENT_ID: Optional[str] ="luca.vallongo@gmail.com-api-client" #os.getenv("OPENSKY_CLIENT_ID", None) +OPENSKY_CLIENT_ID: Optional[str] = "luca.vallongo@gmail.com-api-client" # os.getenv("OPENSKY_CLIENT_ID") # Your OpenSky Network API Client Secret. -# Best stored as an environment variable (e.g., OPENSKY_CLIENT_SECRET). -OPENSKY_CLIENT_SECRET: Optional[str] ="dGBGjEDLmqGGsZmdd5FOH8hOfIcekMjK" #os.getenv("OPENSKY_CLIENT_SECRET", None) +OPENSKY_CLIENT_SECRET: Optional[str] = "heEkoeugbCmfKml5wTxk9ywoPFW1Z3vW" # os.getenv("OPENSKY_CLIENT_SECRET") # OpenSky Network Token URL (used for OAuth2 Client Credentials Flow) -# This is a standard Keycloak token endpoint format. -OPENSKY_TOKEN_URL: str = "https://opensky-network.org/auth/realms/opensky/protocol/openid-connect/token" +# MODIFICATION: Corrected the entire URL to match the official documentation, including the 'auth.' subdomain. +OPENSKY_TOKEN_URL: str = "https://auth.opensky-network.org/auth/realms/opensky-network/protocol/openid-connect/token" -# MODIFIED: Removed OPENSKY_USERNAME and OPENSKY_PASSWORD as they are for Basic Auth (being deprecated) -# OPENSKY_USERNAME: Optional[str] = os.getenv("OPENSKY_USERNAME", None) -# OPENSKY_PASSWORD: Optional[str] = os.getenv("OPENSKY_PASSWORD", None) +# Removed OPENSKY_USERNAME and OPENSKY_PASSWORD as they are for Basic Auth (being deprecated) # --- ADS-B Exchange API Configuration (Placeholder for future) --- # LIVE_DATA_SOURCE: str = "OPENSKY" # Options: "OPENSKY", "ADSB_EXCHANGE", "MOCK" diff --git a/flightmonitor/data/opensky_live_adapter.py b/flightmonitor/data/opensky_live_adapter.py index 61b1d3a..6968dc5 100644 --- a/flightmonitor/data/opensky_live_adapter.py +++ b/flightmonitor/data/opensky_live_adapter.py @@ -9,6 +9,7 @@ import time import threading from queue import Queue, Empty as QueueEmpty, Full as QueueFull import random +import os # Import added for environment variable checks from typing import Dict, Any, List, Optional, Tuple # Import OpenSkyApi for anonymous access (fallback) @@ -68,7 +69,7 @@ class OpenSkyLiveAdapter(BaseLiveDataAdapter): self.token_expires_at: float = 0.0 self.api_client_anonymous: Optional[OpenSkyApi] = None - self.api_timeout = getattr(app_config, "DEFAULT_API_TIMEOUT_SECONDS", 15) # Questo timeout si userà per le chiamate OAuth dirette + self.api_timeout = getattr(app_config, "DEFAULT_API_TIMEOUT_SECONDS", 15) # This timeout is used for direct OAuth calls self.use_oauth = getattr(app_config, "USE_OPENSKY_CREDENTIALS", False) if self.use_oauth: @@ -87,13 +88,9 @@ class OpenSkyLiveAdapter(BaseLiveDataAdapter): if not self.use_oauth: try: - # MODIFICATO: Rimosso l'argomento 'timeout' dalla chiamata al costruttore di OpenSkyApi - # PERCHÉ: La libreria opensky-api non accetta 'timeout' nel suo costruttore. - # DOVE: Inizializzazione di self.api_client_anonymous. - # COME: Eliminato `timeout=self.api_timeout`. La libreria usa il suo timeout interno (di solito 5s per requests). - self.api_client_anonymous = OpenSkyApi() # Timeout non è un argomento valido qui + # The opensky-api library does not accept a 'timeout' argument in its constructor. + self.api_client_anonymous = OpenSkyApi() # Timeout is not a valid argument here module_logger.info( - # MODIFICATO: Aggiornato il messaggio di log per riflettere che il timeout della libreria è interno. f"{self.name}: OpenSkyApi client (anonymous access) initialized (uses internal timeout)." ) except Exception as e: @@ -117,22 +114,32 @@ class OpenSkyLiveAdapter(BaseLiveDataAdapter): module_logger.error(f"{self.name}: Cannot get OAuth token, client ID/secret/URL missing.") return False - data = { + # Manually URL-encode the payload to ensure correct formatting + from urllib.parse import urlencode + payload_dict = { "grant_type": "client_credentials", "client_id": self.client_id, "client_secret": self.client_secret, } - headers = {"Content-Type": "application/x-www-form-urlencoded"} + # The urlencode function will handle converting this to the correct string format + encoded_payload = urlencode(payload_dict) + + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0" + } module_logger.info(f"{self.name}: Requesting OAuth2 access token from {self.token_url}...") try: - response = requests.post(self.token_url, data=data, headers=headers, timeout=self.api_timeout) - response.raise_for_status() # Raise HTTPError for bad responses + # Pass the manually encoded string to the 'data' parameter + response = requests.post(self.token_url, data=encoded_payload, headers=headers, timeout=self.api_timeout, verify=False) + + response.raise_for_status() token_data = response.json() self.access_token = token_data.get("access_token") - expires_in = token_data.get("expires_in", 300) # Default to 5 minutes if not specified - self.token_expires_at = time.time() + expires_in - 60 # Subtract 60s buffer + expires_in = token_data.get("expires_in", 300) + self.token_expires_at = time.time() + expires_in - 60 if self.access_token: module_logger.info(f"{self.name}: Successfully obtained OAuth2 access token. Expires in ~{expires_in // 60} minutes.") @@ -142,14 +149,17 @@ class OpenSkyLiveAdapter(BaseLiveDataAdapter): return False except requests.exceptions.HTTPError as http_err: status_code = http_err.response.status_code if http_err.response else "N/A" - error_text = http_err.response.text if http_err.response else "No response text" - module_logger.error(f"{self.name}: HTTP error {status_code} obtaining OAuth token: {error_text}", exc_info=False) - except requests.exceptions.RequestException as req_err: - module_logger.error(f"{self.name}: Network error obtaining OAuth token: {req_err}", exc_info=True) + try: + error_details = http_err.response.json() + error_text = f"JSON Response: {error_details}" + except requests.exceptions.JSONDecodeError: + error_text = f"Non-JSON Response: {http_err.response.text.strip()}" + + module_logger.error(f"{self.name}: HTTP error {status_code} obtaining OAuth token: {error_text}", exc_info=True) except Exception as e: module_logger.error(f"{self.name}: Unexpected error obtaining OAuth token: {e}", exc_info=True) - self.access_token = None # Ensure token is cleared on failure + self.access_token = None return False def _is_token_valid(self) -> bool: @@ -167,7 +177,6 @@ class OpenSkyLiveAdapter(BaseLiveDataAdapter): def _convert_opensky_api_state_to_canonical(self, sv_opensky_lib: Any) -> Optional[CanonicalFlightState]: """Converts an OpenSkyApi library StateVector object to a CanonicalFlightState object.""" - # This is the same conversion logic as before when using the opensky-api library try: if sv_opensky_lib.icao24 is None: return None primary_ts = sv_opensky_lib.time_position if sv_opensky_lib.time_position is not None else sv_opensky_lib.last_contact @@ -233,10 +242,8 @@ class OpenSkyLiveAdapter(BaseLiveDataAdapter): module_logger.error(f"{self.name}: Unexpected error converting direct API state: {e_conv}. List: {raw_state_list}", exc_info=True) return None - def _perform_api_request(self) -> Dict[str, Any]: if app_config.USE_MOCK_OPENSKY_API: - # ... (Mock logic remains the same as your previous version) ... module_logger.info(f"{self.name}: Using MOCK API data as per configuration.") self._send_status_to_queue(STATUS_FETCHING, "Generating mock flight data...") if app_config.MOCK_API_ERROR_SIMULATION == "RATE_LIMITED": @@ -279,6 +286,7 @@ class OpenSkyLiveAdapter(BaseLiveDataAdapter): module_logger.error(f"{self.name}: {err_msg}") # If token fails, we could try anonymous as a fallback or just fail this attempt if not self.api_client_anonymous: # If anonymous is not even an option + self._consecutive_api_errors += 1 return {"error_type": STATUS_API_ERROR_TEMPORARY, "message": err_msg, "status_code": "OAUTH_TOKEN_FAILURE", "delay": self._calculate_next_backoff_delay(), "consecutive_errors": self._consecutive_api_errors} else: # Fallback to anonymous for this attempt module_logger.warning(f"{self.name}: Attempting anonymous access due to OAuth token failure.")