Files
adif-api-reverse-engineering/adif_client.py
Dasemu 68fac80520 Refactor: reorganización completa del proyecto y documentación consolidada
Esta actualización reorganiza el proyecto de reverse engineering de la API de ADIF con los siguientes cambios:

Estructura del proyecto:
- Movida documentación principal a carpeta docs/
- Consolidados archivos markdown redundantes en CLAUDE.md (contexto completo del proyecto)
- Organización de tests en carpeta tests/ con README explicativo
- APK renombrado de base.apk a adif.apk para mayor claridad

Archivos de código:
- Movidos adif_auth.py y adif_client.py a la raíz (antes en api_testing_scripts/)
- Eliminados scripts de testing obsoletos y scripts de Frida no utilizados
- Nuevos tests detallados: test_endpoints_detailed.py y test_onepaths_with_real_trains.py

Descubrimientos:
- Documentados nuevos hallazgos en docs/NEW_DISCOVERIES.md
- Actualización de onePaths funcionando con commercialNumber real (devuelve 200)
- Extraídos 1587 códigos de estación en station_codes.txt

Configuración:
- Actualizado .gitignore con mejores patrones para Python e IDEs
- Eliminados archivos temporales de depuración y logs
2025-12-05 11:22:13 +01:00

393 lines
14 KiB
Python
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Cliente completo de la API de ADIF
Implementa todos los endpoints funcionales con métodos simples de usar.
Incluye manejo de errores y validación de datos.
"""
import requests
import uuid
from datetime import datetime
from typing import List, Dict, Optional, Any
from adif_auth import AdifAuthenticator
class AdifClient:
"""Cliente para interactuar con la API de ADIF"""
def __init__(self, access_key: str, secret_key: str):
"""
Inicializa el cliente
Args:
access_key: Clave de acceso
secret_key: Clave secreta
"""
self.auth = AdifAuthenticator(access_key=access_key, secret_key=secret_key)
self.session = requests.Session()
def _make_request(
self,
url: str,
payload: Dict[str, Any],
use_stations_key: bool = False
) -> Dict[str, Any]:
"""
Realiza una petición a la API
Args:
url: URL del endpoint
payload: Datos a enviar
use_stations_key: Si True, usa USER_KEY_STATIONS en lugar de USER_KEY_CIRCULATION
Returns:
Respuesta JSON
Raises:
Exception: Si hay un error en la petición
"""
user_id = str(uuid.uuid4())
headers = self.auth.get_auth_headers("POST", url, payload, user_id=user_id)
if use_stations_key:
headers["User-key"] = self.auth.USER_KEY_STATIONS
else:
headers["User-key"] = self.auth.USER_KEY_CIRCULATION
response = self.session.post(url, json=payload, headers=headers, timeout=30)
if response.status_code == 200:
return response.json()
elif response.status_code == 204:
return {"message": "No content available", "commercialPaths": []}
elif response.status_code == 401:
raise PermissionError(
f"Unauthorized - Las claves no tienen permisos para este endpoint"
)
elif response.status_code == 400:
raise ValueError(
f"Bad Request - Payload incorrecto: {response.text}"
)
else:
raise Exception(
f"Error {response.status_code}: {response.text}"
)
def get_departures(
self,
station_code: str,
traffic_type: str = "ALL",
page_number: int = 0,
commercial_service: str = "BOTH",
commercial_stop_type: str = "BOTH"
) -> List[Dict[str, Any]]:
"""
Obtiene las salidas de una estación
Args:
station_code: Código de la estación (ej: "10200")
traffic_type: Tipo de tráfico (ALL, CERCANIAS, AVLDMD, TRAVELERS, GOODS)
page_number: Número de página (por defecto 0)
commercial_service: BOTH, YES, NOT
commercial_stop_type: BOTH, YES, NOT
Returns:
Lista de trenes
Example:
>>> client = AdifClient(ACCESS_KEY, SECRET_KEY)
>>> trains = client.get_departures("10200", "AVLDMD")
>>> for train in trains:
... print(f"{train['commercialNumber']} - Destino: {train['destination']}")
"""
url = "https://circulacion.api.adif.es/portroyalmanager/secure/circulationpaths/departures/traffictype/"
payload = {
"commercialService": commercial_service,
"commercialStopType": commercial_stop_type,
"page": {"pageNumber": page_number},
"stationCode": station_code,
"trafficType": traffic_type
}
data = self._make_request(url, payload)
return data.get("commercialPaths", [])
def get_arrivals(
self,
station_code: str,
traffic_type: str = "ALL",
page_number: int = 0,
commercial_service: str = "BOTH",
commercial_stop_type: str = "BOTH"
) -> List[Dict[str, Any]]:
"""
Obtiene las llegadas a una estación
Args:
station_code: Código de la estación (ej: "10200")
traffic_type: Tipo de tráfico (ALL, CERCANIAS, AVLDMD, TRAVELERS, GOODS)
page_number: Número de página (por defecto 0)
commercial_service: BOTH, YES, NOT
commercial_stop_type: BOTH, YES, NOT
Returns:
Lista de trenes
Example:
>>> client = AdifClient(ACCESS_KEY, SECRET_KEY)
>>> trains = client.get_arrivals("71801", "ALL")
"""
url = "https://circulacion.api.adif.es/portroyalmanager/secure/circulationpaths/arrivals/traffictype/"
payload = {
"commercialService": commercial_service,
"commercialStopType": commercial_stop_type,
"page": {"pageNumber": page_number},
"stationCode": station_code,
"trafficType": traffic_type
}
data = self._make_request(url, payload)
return data.get("commercialPaths", [])
def get_train_route(
self,
commercial_number: str,
launching_date: int,
origin_station_code: str,
destination_station_code: str,
all_control_points: bool = True
) -> List[Dict[str, Any]]:
"""
Obtiene la ruta completa de un tren (todas las paradas)
Args:
commercial_number: Número comercial del tren (ej: "03194")
launching_date: Fecha de salida en milisegundos desde epoch
origin_station_code: Código de estación de origen
destination_station_code: Código de estación de destino
all_control_points: Si True, incluye todos los puntos de control
Returns:
Lista de paradas del tren
Example:
>>> # Primero obtener un tren real
>>> trains = client.get_departures("10200", "AVLDMD")
>>> train = trains[0]
>>> info = train['commercialPathInfo']
>>> key = info['commercialPathKey']
>>>
>>> # Obtener su ruta completa
>>> route = client.get_train_route(
... commercial_number=key['commercialCirculationKey']['commercialNumber'],
... launching_date=key['commercialCirculationKey']['launchingDate'],
... origin_station_code=key['originStationCode'],
... destination_station_code=key['destinationStationCode']
... )
>>> for stop in route:
... print(f"Parada: {stop['stationCode']}")
"""
url = "https://circulacion.api.adif.es/portroyalmanager/secure/circulationpathdetails/onepaths/"
payload = {
"allControlPoints": all_control_points,
"commercialNumber": commercial_number,
"destinationStationCode": destination_station_code,
"launchingDate": launching_date,
"originStationCode": origin_station_code
}
data = self._make_request(url, payload)
commercial_paths = data.get("commercialPaths", [])
if commercial_paths:
return commercial_paths[0].get("passthroughSteps", [])
return []
def get_station_observations(
self,
station_codes: List[str]
) -> List[Dict[str, Any]]:
"""
Obtiene observaciones de estaciones
Args:
station_codes: Lista de códigos de estación
Returns:
Lista de observaciones
Example:
>>> client = AdifClient(ACCESS_KEY, SECRET_KEY)
>>> obs = client.get_station_observations(["10200", "71801"])
"""
url = "https://estaciones.api.adif.es/portroyalmanager/secure/stationsobservations/"
payload = {"stationCodes": station_codes}
data = self._make_request(url, payload, use_stations_key=True)
return data.get("stationObservations", [])
def get_all_departures_with_routes(
self,
station_code: str,
traffic_type: str = "ALL",
max_trains: int = 5
) -> List[Dict[str, Any]]:
"""
Obtiene salidas de una estación Y sus rutas completas
Args:
station_code: Código de estación
traffic_type: Tipo de tráfico
max_trains: Número máximo de trenes a procesar
Returns:
Lista de trenes con sus rutas
Example:
>>> client = AdifClient(ACCESS_KEY, SECRET_KEY)
>>> trains_with_routes = client.get_all_departures_with_routes("10200", "AVLDMD", max_trains=3)
>>> for train in trains_with_routes:
... print(f"Tren {train['commercial_number']}")
... for stop in train['route']:
... print(f" - {stop['stationCode']}")
"""
departures = self.get_departures(station_code, traffic_type)
result = []
for i, train in enumerate(departures[:max_trains]):
info = train['commercialPathInfo']
key = info['commercialPathKey']
commercial_key = key['commercialCirculationKey']
try:
route = self.get_train_route(
commercial_number=commercial_key['commercialNumber'],
launching_date=commercial_key['launchingDate'],
origin_station_code=key['originStationCode'],
destination_station_code=key['destinationStationCode']
)
result.append({
"commercial_number": commercial_key['commercialNumber'],
"traffic_type": info['trafficType'],
"origin_station": key['originStationCode'],
"destination_station": key['destinationStationCode'],
"launching_date": commercial_key['launchingDate'],
"train_info": train,
"route": route
})
except Exception as e:
print(f"⚠️ Error obteniendo ruta del tren {commercial_key['commercialNumber']}: {e}")
continue
return result
def demo():
"""Demostración del cliente"""
print("="*70)
print("DEMO DEL CLIENTE DE ADIF")
print("="*70)
ACCESS_KEY = "and20210615"
SECRET_KEY = "Jthjtr946RTt"
client = AdifClient(ACCESS_KEY, SECRET_KEY)
# 1. Salidas de Madrid Atocha
print("\n1⃣ SALIDAS DE MADRID ATOCHA (Alta Velocidad)")
print("-" * 70)
try:
departures = client.get_departures("10200", "AVLDMD")
print(f"✅ Encontrados {len(departures)} trenes")
for i, train in enumerate(departures[:3]):
info = train['commercialPathInfo']
key = info['commercialPathKey']
passthrough = train.get('passthroughStep', {})
dep_sides = passthrough.get('departurePassthroughStepSides', {})
planned_time = dep_sides.get('plannedTime', 0)
if planned_time:
time_str = datetime.fromtimestamp(planned_time/1000).strftime('%H:%M')
else:
time_str = "N/A"
print(f"\n {i+1}. Tren {key['commercialCirculationKey']['commercialNumber']}")
print(f" Destino: {key['destinationStationCode']}")
print(f" Hora salida: {time_str}")
print(f" Estado: {dep_sides.get('circulationState', 'N/A')}")
except Exception as e:
print(f"❌ Error: {e}")
# 2. Ruta completa de un tren
print("\n\n2⃣ RUTA COMPLETA DE UN TREN")
print("-" * 70)
try:
departures = client.get_departures("10200", "ALL")
if departures:
train = departures[0]
info = train['commercialPathInfo']
key = info['commercialPathKey']
commercial_key = key['commercialCirculationKey']
print(f"Consultando ruta del tren {commercial_key['commercialNumber']}...")
route = client.get_train_route(
commercial_number=commercial_key['commercialNumber'],
launching_date=commercial_key['launchingDate'],
origin_station_code=key['originStationCode'],
destination_station_code=key['destinationStationCode']
)
print(f"✅ Ruta con {len(route)} paradas:\n")
for i, stop in enumerate(route[:10]): # Primeras 10 paradas
stop_type = stop.get('stopType', 'N/A')
station_code = stop.get('stationCode', 'N/A')
# Info de salida
dep_sides = stop.get('departurePassthroughStepSides', {})
arr_sides = stop.get('arrivalPassthroughStepSides', {})
if dep_sides:
time_ms = dep_sides.get('plannedTime', 0)
if time_ms:
time_str = datetime.fromtimestamp(time_ms/1000).strftime('%H:%M')
print(f" {i+1}. {station_code} - Salida: {time_str} ({stop_type})")
elif arr_sides:
time_ms = arr_sides.get('plannedTime', 0)
if time_ms:
time_str = datetime.fromtimestamp(time_ms/1000).strftime('%H:%M')
print(f" {i+1}. {station_code} - Llegada: {time_str} ({stop_type})")
else:
print(f" {i+1}. {station_code} ({stop_type})")
except Exception as e:
print(f"❌ Error: {e}")
# 3. Observaciones de estaciones
print("\n\n3⃣ OBSERVACIONES DE ESTACIONES")
print("-" * 70)
try:
observations = client.get_station_observations(["10200", "71801"])
print(f"✅ Observaciones de {len(observations)} estaciones")
for obs in observations:
station_code = obs.get('stationCode', 'N/A')
observation_text = obs.get('observation', 'Sin observaciones')
print(f"\n Estación {station_code}:")
print(f" {observation_text}")
except Exception as e:
print(f"❌ Error: {e}")
print("\n" + "="*70)
print("DEMO COMPLETADA")
print("="*70)
if __name__ == "__main__":
demo()