Primer paso de la investigacion. Se aportan el .apk, las carpetas con el apk extraido y el apk descompilado. El archivo API_DOCUMENTATION.md es un archivo donde se anotaran los descubrimientos del funcionamiento de la API, y los .py son scripts para probar la funcionalidad de la API con los métodos que vayamos encontrando. Finalmente, los archivos .js son scripts de Frida para extraer informacion de la APP durante la ejecucion.
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.__pycache__/
|
||||
.claude
|
||||
CLAUDE.md
|
||||
.venv/
|
||||
request_bodies.log
|
||||
247
API_DOCUMENTATION.md
Normal file
247
API_DOCUMENTATION.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# Adif Elcano API - Ingeniería Reversa
|
||||
|
||||
Documentación de la API de Adif (Elcano) obtenida mediante ingeniería reversa de la aplicación móvil.
|
||||
|
||||
## URLs Base
|
||||
|
||||
```
|
||||
BASE_URL_STATIONS = https://estaciones.api.adif.es
|
||||
BASE_URL_CIRCULATION = https://circulacion.api.adif.es
|
||||
BASE_URL_ELCANOWEB = https://elcanoweb.adif.es/api/
|
||||
BASE_URL_AVISA = https://avisa.adif.es/avisa-ws/api/
|
||||
```
|
||||
|
||||
## Headers de Autenticación
|
||||
|
||||
### Para API de Circulaciones y Composiciones
|
||||
```
|
||||
Content-Type: application/json;charset=utf-8
|
||||
User-key: f4ce9fbfa9d721e39b8984805901b5df
|
||||
```
|
||||
|
||||
### Para API de Estaciones
|
||||
```
|
||||
Content-Type: application/json;charset=utf-8
|
||||
User-key: 0d021447a2fd2ac64553674d5a0c1a6f
|
||||
```
|
||||
|
||||
### Para Avisa Login
|
||||
```
|
||||
Authorization: Basic YXZpc3RhX2NsaWVudF9hbmRyb2lkOjh5WzZKNyFmSjwhXypmYXE1NyNnOSohNElwa2MjWC1BTg==
|
||||
```
|
||||
|
||||
### Para Suscripciones
|
||||
```
|
||||
Authorization: Basic ZGVpbW9zOmRlaW1vc3R0
|
||||
X-CanalMovil-Authentication: <token>
|
||||
X-CanalMovil-deviceID: <device_id>
|
||||
X-CanalMovil-pushID: <push_id>
|
||||
```
|
||||
|
||||
## Tokens
|
||||
|
||||
```
|
||||
REGISTRATION_TOKEN = Bearer b9034774-c6e4-4663-a1a8-74bf7102651b
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Estaciones
|
||||
|
||||
#### Obtener todas las estaciones
|
||||
```
|
||||
GET /portroyalmanager/secure/stations/allstations/reducedinfo/{token}/
|
||||
Base: https://estaciones.api.adif.es
|
||||
Headers: User-key para estaciones
|
||||
```
|
||||
|
||||
#### Obtener detalles de una estación
|
||||
```
|
||||
POST /portroyalmanager/secure/stations/onestation/
|
||||
Base: https://estaciones.api.adif.es
|
||||
Headers: User-key para estaciones
|
||||
|
||||
Body:
|
||||
{
|
||||
"stationCode": "string"
|
||||
}
|
||||
```
|
||||
|
||||
#### Observaciones de estación
|
||||
```
|
||||
POST /portroyalmanager/secure/stationsobservations/
|
||||
Base: https://estaciones.api.adif.es
|
||||
Headers: User-key para estaciones
|
||||
```
|
||||
|
||||
### Circulaciones (Trenes)
|
||||
|
||||
#### Salidas (Departures)
|
||||
```
|
||||
POST /portroyalmanager/secure/circulationpaths/departures/traffictype/
|
||||
Base: https://circulacion.api.adif.es
|
||||
Headers: User-key para circulaciones
|
||||
|
||||
Body:
|
||||
{
|
||||
"commercialService": "YES|NO|ALL",
|
||||
"commercialStopType": "YES|NO|ALL",
|
||||
"destinationStationCode": "string|null",
|
||||
"originStationCode": "string|null",
|
||||
"page": {
|
||||
"page": number,
|
||||
"size": number
|
||||
},
|
||||
"stationCode": "string|null",
|
||||
"trafficType": "CERCANIAS|MEDIA_DISTANCIA|LARGA_DISTANCIA|ALL"
|
||||
}
|
||||
```
|
||||
|
||||
#### Llegadas (Arrivals)
|
||||
```
|
||||
POST /portroyalmanager/secure/circulationpaths/arrivals/traffictype/
|
||||
Base: https://circulacion.api.adif.es
|
||||
Headers: User-key para circulaciones
|
||||
Body: Same as departures
|
||||
```
|
||||
|
||||
#### Entre estaciones
|
||||
```
|
||||
POST /portroyalmanager/secure/circulationpaths/betweenstations/traffictype/
|
||||
Base: https://circulacion.api.adif.es
|
||||
Headers: User-key para circulaciones
|
||||
Body: Same as departures
|
||||
```
|
||||
|
||||
#### Una ruta específica
|
||||
```
|
||||
POST /portroyalmanager/secure/circulationpathdetails/onepaths/
|
||||
Base: https://circulacion.api.adif.es
|
||||
Headers: User-key para circulaciones
|
||||
|
||||
Body:
|
||||
{
|
||||
"allControlPoints": boolean|null,
|
||||
"commercialNumber": "string|null",
|
||||
"destinationStationCode": "string|null",
|
||||
"launchingDate": timestamp|null,
|
||||
"originStationCode": "string|null"
|
||||
}
|
||||
```
|
||||
|
||||
#### Varias rutas
|
||||
```
|
||||
POST /portroyalmanager/secure/circulationpathdetails/severalpaths/
|
||||
Base: https://circulacion.api.adif.es
|
||||
Headers: User-key para circulaciones
|
||||
Body: Same as onepaths
|
||||
```
|
||||
|
||||
### Composiciones
|
||||
|
||||
#### Composición de tren
|
||||
```
|
||||
POST /portroyalmanager/secure/circulationpaths/compositions/path/
|
||||
Base: https://circulacion.api.adif.es
|
||||
Headers: User-key para circulaciones
|
||||
|
||||
Body: Same as onepaths request
|
||||
```
|
||||
|
||||
### Avisa (Sistema de incidencias)
|
||||
|
||||
#### Login
|
||||
```
|
||||
POST /avisa-ws/api/token
|
||||
Base: https://avisa.adif.es
|
||||
Headers: Basic auth token para Avisa
|
||||
|
||||
Query params:
|
||||
- grant_type: "password"
|
||||
- username: <username>
|
||||
- password: <password>
|
||||
```
|
||||
|
||||
#### Registrar cliente
|
||||
```
|
||||
POST /avisa-ws/api/v1/client
|
||||
Base: https://avisa.adif.es
|
||||
```
|
||||
|
||||
#### Estaciones Avisa
|
||||
```
|
||||
GET /avisa-ws/api/v1/station
|
||||
Base: https://avisa.adif.es
|
||||
```
|
||||
|
||||
#### Categorías de estación
|
||||
```
|
||||
GET /avisa-ws/api/v1/category
|
||||
Base: https://avisa.adif.es
|
||||
```
|
||||
|
||||
#### Incidencias
|
||||
```
|
||||
GET /avisa-ws/api/v1/incidence
|
||||
POST /avisa-ws/api/v1/incidence
|
||||
GET /avisa-ws/api/v1/incidence/{incidenceId}
|
||||
Base: https://avisa.adif.es
|
||||
```
|
||||
|
||||
### Suscripciones
|
||||
|
||||
#### Listar suscripciones
|
||||
```
|
||||
GET /api/subscriptions?platform=300
|
||||
Base: https://elcanoweb.adif.es
|
||||
Headers: Basic auth + X-CanalMovil headers
|
||||
```
|
||||
|
||||
#### Crear suscripción
|
||||
```
|
||||
POST /api/subscriptions
|
||||
Base: https://elcanoweb.adif.es
|
||||
Headers: Basic auth + X-CanalMovil headers
|
||||
```
|
||||
|
||||
#### Silenciar suscripción
|
||||
```
|
||||
PUT /api/subscriptions/{id}/mute
|
||||
Base: https://elcanoweb.adif.es
|
||||
Headers: Basic auth + X-CanalMovil headers
|
||||
```
|
||||
|
||||
## Tipos de Datos
|
||||
|
||||
### TrafficType (Tipos de tráfico)
|
||||
- `CERCANIAS` - Trenes de cercanías
|
||||
- `MEDIA_DISTANCIA` - Media distancia
|
||||
- `LARGA_DISTANCIA` - Larga distancia
|
||||
- `ALL` - Todos los tipos
|
||||
|
||||
### State (Estados)
|
||||
- `YES` - Sí
|
||||
- `NO` - No
|
||||
- `ALL` - Todos
|
||||
|
||||
### PageInfoDTO
|
||||
```json
|
||||
{
|
||||
"page": 0,
|
||||
"size": 20
|
||||
}
|
||||
```
|
||||
|
||||
## Notas de Seguridad
|
||||
|
||||
- La app usa certificate pinning con claves públicas específicas
|
||||
- Los tokens están hardcodeados en la aplicación
|
||||
- Las User-keys son diferentes para cada servicio (estaciones vs circulaciones)
|
||||
- El token de registro `b9034774-c6e4-4663-a1a8-74bf7102651b` está en el código
|
||||
|
||||
[CODE] 200
|
||||
[METHOD] POST
|
||||
[URL] https://circulacion.api.adif.es/portroyalmanager/secure/circulationpathdetails/onepaths/
|
||||
[URL] https://circulacion.api.adif.es/portroyalmanager/secure/circulationpaths/betweenstations/traffictype/
|
||||
[URL] https://circulacion.api.adif.es/portroyalmanager/secure/circulationpaths/departures/traffictype/
|
||||
[URL] https://estaciones.api.adif.es/portroyalmanager/secure/stationsobservations/
|
||||
240
README.md
240
README.md
@@ -1,2 +1,240 @@
|
||||
# adif-api-reverse-enginereeng
|
||||
# Ingeniería Reversa de la API de Adif (Elcano)
|
||||
|
||||
Este proyecto contiene la documentación y herramientas para interactuar con la API no documentada de Adif (sistema Elcano) obtenida mediante ingeniería reversa de la aplicación móvil oficial.
|
||||
|
||||
## Archivos
|
||||
|
||||
- `base.apk` - Aplicación móvil original de Adif
|
||||
- `API_DOCUMENTATION.md` - Documentación completa de la API descubierta
|
||||
- `adif_client.py` - Cliente Python para interactuar con la API
|
||||
- `decompiled/` - Código fuente descompilado de la APK (generado)
|
||||
- `apk_extracted/` - Contenido extraído de la APK (generado)
|
||||
|
||||
## Hallazgos Principales
|
||||
|
||||
### URLs Base
|
||||
- **Estaciones**: `https://estaciones.api.adif.es`
|
||||
- **Circulaciones**: `https://circulacion.api.adif.es`
|
||||
- **Avisa (Incidencias)**: `https://avisa.adif.es`
|
||||
|
||||
### Autenticación
|
||||
|
||||
La API usa **User-keys** en los headers HTTP en lugar de autenticación OAuth tradicional:
|
||||
|
||||
```http
|
||||
Content-Type: application/json;charset=utf-8
|
||||
User-key: f4ce9fbfa9d721e39b8984805901b5df # Para circulaciones
|
||||
User-key: 0d021447a2fd2ac64553674d5a0c1a6f # Para estaciones
|
||||
```
|
||||
|
||||
### Endpoints Principales
|
||||
|
||||
#### Circulaciones (Trenes)
|
||||
- `POST /portroyalmanager/secure/circulationpaths/departures/traffictype/` - Salidas
|
||||
- `POST /portroyalmanager/secure/circulationpaths/arrivals/traffictype/` - Llegadas
|
||||
- `POST /portroyalmanager/secure/circulationpaths/betweenstations/traffictype/` - Entre estaciones
|
||||
- `POST /portroyalmanager/secure/circulationpathdetails/onepaths/` - Detalles de ruta
|
||||
|
||||
#### Estaciones
|
||||
- `GET /portroyalmanager/secure/stations/allstations/reducedinfo/{token}/` - Todas las estaciones
|
||||
- `POST /portroyalmanager/secure/stations/onestation/` - Detalles de estación
|
||||
|
||||
## Uso del Cliente Python
|
||||
|
||||
### Instalación
|
||||
|
||||
```bash
|
||||
# Crear y activar entorno virtual
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate # En Linux/Mac
|
||||
# O en Windows: venv\Scripts\activate
|
||||
|
||||
# Instalar dependencias
|
||||
pip install requests
|
||||
```
|
||||
|
||||
### Ejemplo Básico
|
||||
|
||||
```python
|
||||
from adif_client import AdifClient, TrafficType, State
|
||||
|
||||
# Crear cliente
|
||||
client = AdifClient(debug=True)
|
||||
|
||||
# Obtener salidas de una estación
|
||||
departures = client.get_departures(
|
||||
station_code="10200", # Madrid Atocha
|
||||
traffic_type=TrafficType.CERCANIAS,
|
||||
size=10
|
||||
)
|
||||
|
||||
# Obtener trenes entre dos estaciones
|
||||
trains = client.get_between_stations(
|
||||
origin_station="10200", # Madrid Atocha
|
||||
destination_station="10302", # Madrid Chamartín
|
||||
traffic_type=TrafficType.ALL
|
||||
)
|
||||
|
||||
# Obtener detalles de una estación
|
||||
station = client.get_station_details("10200")
|
||||
```
|
||||
|
||||
### Ejecutar el ejemplo
|
||||
|
||||
```bash
|
||||
./venv/bin/python adif_client.py
|
||||
```
|
||||
|
||||
## Estructura de la Aplicación
|
||||
|
||||
La app está construida con:
|
||||
- **Kotlin** como lenguaje principal
|
||||
- **Retrofit** para las llamadas HTTP
|
||||
- **Hilt** para inyección de dependencias
|
||||
- **Coroutines** para operaciones asíncronas
|
||||
- **Firebase** para analytics
|
||||
|
||||
### Arquitectura
|
||||
|
||||
```
|
||||
com.adif.elcanomovil/
|
||||
├── serviceNetworking/ # Capa de red
|
||||
│ ├── circulations/ # Servicios de circulaciones
|
||||
│ ├── stations/ # Servicios de estaciones
|
||||
│ ├── compositions/ # Composiciones de trenes
|
||||
│ ├── avisa/ # Sistema de incidencias
|
||||
│ └── subscriptions/ # Suscripciones
|
||||
├── repositories/ # Repositorios (patrón Repository)
|
||||
├── domain/ # Lógica de negocio
|
||||
└── ui*/ # Capas de presentación
|
||||
```
|
||||
|
||||
## Información Técnica
|
||||
|
||||
### Estados (State Enum)
|
||||
- `YES` - Sí
|
||||
- `NOT` - No
|
||||
- `BOTH` - Ambos
|
||||
|
||||
**Nota**: En BuildConfig aparece como "ALL" pero en el código real es "BOTH"
|
||||
|
||||
### Tipos de Tráfico (TrafficType)
|
||||
- `CERCANIAS` - Trenes de cercanías
|
||||
- `MEDIA_DISTANCIA` - Media distancia
|
||||
- `LARGA_DISTANCIA` - Larga distancia
|
||||
- `ALL` - Todos los tipos
|
||||
|
||||
### PageInfo
|
||||
La paginación solo usa `pageNumber` (no incluye `size`):
|
||||
|
||||
```json
|
||||
{
|
||||
"page": {
|
||||
"pageNumber": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ⚠️ ACTUALIZACIÓN IMPORTANTE: Sistema de Autenticación
|
||||
|
||||
**Los tests iniciales fallaron porque la API usa un sistema de autenticación HMAC-SHA256 similar a AWS Signature V4.**
|
||||
|
||||
### El Problema Real
|
||||
|
||||
La API NO usa simples API keys. Cada petición requiere:
|
||||
|
||||
1. **Headers especiales**:
|
||||
- `X-Elcano-Host`
|
||||
- `X-Elcano-Client: AndroidElcanoApp`
|
||||
- `X-Elcano-Date` (timestamp ISO UTC)
|
||||
- `X-Elcano-UserId` (ID único)
|
||||
- `Authorization` con firma HMAC-SHA256
|
||||
|
||||
2. **Claves secretas** almacenadas en librería nativa (`libapi-keys.so`):
|
||||
- `accessKey` (método nativo)
|
||||
- `secretKey` (método nativo)
|
||||
|
||||
3. **Firma de cada petición** que incluye:
|
||||
- Método HTTP
|
||||
- Path y parámetros
|
||||
- Payload (body JSON)
|
||||
- Headers canónicos
|
||||
- Timestamp
|
||||
|
||||
### Cómo Obtener las Claves
|
||||
|
||||
**Método recomendado: Frida**
|
||||
|
||||
```bash
|
||||
# 1. Instalar Frida
|
||||
pip install frida-tools
|
||||
|
||||
# 2. Conectar dispositivo Android / iniciar emulador
|
||||
adb devices
|
||||
|
||||
# 3. Instalar la app
|
||||
adb install base.apk
|
||||
|
||||
# 4. Ejecutar el script de extracción
|
||||
frida -U -f com.adif.elcanomovil -l frida_extract_keys.js --no-pause
|
||||
|
||||
# 5. Interactuar con la app (ver trenes, etc.)
|
||||
# Las claves aparecerán en la consola
|
||||
```
|
||||
|
||||
Ver `AUTH_EXPLAINED.md` para detalles completos del sistema de autenticación.
|
||||
|
||||
## Limitaciones Conocidas
|
||||
|
||||
1. **⚠️ Sistema de autenticación complejo**: Requiere extracción de claves nativas (ver arriba)
|
||||
|
||||
2. **Certificate Pinning**: La app implementa certificate pinning (bypasseable con Frida)
|
||||
|
||||
3. **UserID dinámico**: Se genera por instalación, no es fijo
|
||||
|
||||
4. **Autenticación Avisa**: El sistema Avisa requiere OAuth2 con flujo de password adicional
|
||||
|
||||
## Códigos de Estación Comunes
|
||||
|
||||
- `10200` - Madrid Puerta de Atocha
|
||||
- `10302` - Madrid Chamartín-Clara Campoamor
|
||||
- `71801` - Barcelona Sants
|
||||
- `50000` - Valencia Nord
|
||||
- `11401` - Sevilla Santa Justa
|
||||
|
||||
## Herramientas Utilizadas
|
||||
|
||||
- **jadx** - Descompilador de Android APK a código Java
|
||||
- **unzip** - Para extraer contenido de la APK
|
||||
- **Python requests** - Cliente HTTP
|
||||
- **curl** - Pruebas de endpoints
|
||||
|
||||
## Descompilación
|
||||
|
||||
Para descompilar la APK manualmente:
|
||||
|
||||
```bash
|
||||
# Descargar jadx
|
||||
wget https://github.com/skylot/jadx/releases/download/v1.5.0/jadx-1.5.0.zip
|
||||
unzip jadx-1.5.0.zip -d jadx
|
||||
|
||||
# Descompilar
|
||||
./jadx/bin/jadx -d decompiled base.apk
|
||||
```
|
||||
|
||||
## Próximos Pasos
|
||||
|
||||
- [ ] Investigar el formato exacto de los objetos de petición
|
||||
- [ ] Obtener un token válido para el endpoint de estaciones
|
||||
- [ ] Implementar autenticación OAuth para Avisa
|
||||
- [ ] Documentar códigos de estación
|
||||
- [ ] Crear mappings de respuestas JSON
|
||||
- [ ] Implementar manejo de errores robusto
|
||||
|
||||
## Advertencia Legal
|
||||
|
||||
Este proyecto es solo para fines educativos y de investigación. La API de Adif es propiedad de ADIF y debe usarse respetando sus términos de servicio. No se debe abusar de la API ni usarla para fines comerciales sin autorización.
|
||||
|
||||
## Autor
|
||||
|
||||
Proyecto de ingeniería reversa educativa.
|
||||
|
||||
171
api_testing_scripts/adif_auth.py
Normal file
171
api_testing_scripts/adif_auth.py
Normal file
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Sistema de autenticación HMAC-SHA256 para API de Adif
|
||||
Basado en ElcanoAuth.java
|
||||
"""
|
||||
|
||||
import hmac
|
||||
import hashlib
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Dict
|
||||
|
||||
class AdifAuthenticator:
|
||||
"""Autenticador para la API de Adif usando HMAC-SHA256"""
|
||||
|
||||
def __init__(self, access_key: str, secret_key: str, user_id: str):
|
||||
self.access_key = access_key
|
||||
self.secret_key = secret_key
|
||||
self.user_id = user_id
|
||||
self.client = "AndroidElcanoApp"
|
||||
|
||||
def _format_payload(self, payload: str) -> str:
|
||||
"""Formatear payload (eliminar espacios, saltos de línea)"""
|
||||
return payload.replace(" ", "").replace("\n", "").replace("\r", "")
|
||||
|
||||
def _to_hex(self, data: str) -> str:
|
||||
"""Calcular SHA256 hash en hexadecimal"""
|
||||
return hashlib.sha256(data.encode('utf-8')).hexdigest()
|
||||
|
||||
def _hmac_sha256(self, key: bytes, message: str) -> bytes:
|
||||
"""Calcular HMAC-SHA256"""
|
||||
return hmac.new(key, message.encode('utf-8'), hashlib.sha256).digest()
|
||||
|
||||
def _get_signature_key(self, date_simple: str) -> bytes:
|
||||
"""Derivar clave de firma"""
|
||||
# kDate = HMAC-SHA256(secret_key, date)
|
||||
k_date = self._hmac_sha256(self.secret_key.encode('utf-8'), date_simple)
|
||||
|
||||
# kClient = HMAC-SHA256(kDate, client)
|
||||
k_client = self._hmac_sha256(k_date, self.client)
|
||||
|
||||
# kSigning = HMAC-SHA256(kClient, "elcano_request")
|
||||
k_signing = self._hmac_sha256(k_client, "elcano_request")
|
||||
|
||||
return k_signing
|
||||
|
||||
def _prepare_canonical_request(self, method: str, path: str, params: str,
|
||||
host: str, date: str, payload: str) -> tuple:
|
||||
"""Preparar canonical request"""
|
||||
# Headers canónicos (deben estar en orden)
|
||||
canonical_headers = (
|
||||
f"content-type:application/json;charset=utf-8\n"
|
||||
f"x-elcano-client:{self.client}\n"
|
||||
f"x-elcano-date:{date}\n"
|
||||
f"x-elcano-host:{host}\n"
|
||||
f"x-elcano-userid:{self.user_id}\n"
|
||||
)
|
||||
|
||||
signed_headers = "content-type;x-elcano-client;x-elcano-date;x-elcano-host;x-elcano-userid"
|
||||
|
||||
# Formatear payload
|
||||
formatted_payload = self._format_payload(payload)
|
||||
payload_hash = self._to_hex(formatted_payload)
|
||||
|
||||
# Canonical request
|
||||
canonical_request = (
|
||||
f"{method}\n"
|
||||
f"{path}\n"
|
||||
f"{params}\n"
|
||||
f"{canonical_headers}"
|
||||
f"{signed_headers}\n"
|
||||
f"{payload_hash}"
|
||||
)
|
||||
|
||||
return canonical_request, signed_headers
|
||||
|
||||
def _prepare_string_to_sign(self, canonical_request: str, date: str, date_simple: str) -> str:
|
||||
"""Preparar string to sign"""
|
||||
canonical_hash = self._to_hex(canonical_request)
|
||||
|
||||
string_to_sign = (
|
||||
f"HMAC-SHA256\n"
|
||||
f"{date}\n"
|
||||
f"{date_simple}/{self.client}/{self.user_id}/elcano_request\n"
|
||||
f"{canonical_hash}"
|
||||
)
|
||||
|
||||
return string_to_sign
|
||||
|
||||
def _calculate_signature(self, string_to_sign: str, date_simple: str) -> str:
|
||||
"""Calcular firma"""
|
||||
signing_key = self._get_signature_key(date_simple)
|
||||
signature = self._hmac_sha256(signing_key, string_to_sign)
|
||||
return signature.hex()
|
||||
|
||||
def sign_request(self, method: str, host: str, path: str,
|
||||
params: str = "", payload: str = "") -> Dict[str, str]:
|
||||
"""
|
||||
Firmar una petición HTTP
|
||||
|
||||
Args:
|
||||
method: Método HTTP (GET, POST, etc.)
|
||||
host: Host (ej: circulacion.api.adif.es)
|
||||
path: Path de la petición
|
||||
params: Query parameters (vacío si no hay)
|
||||
payload: Body JSON (vacío para GET)
|
||||
|
||||
Returns:
|
||||
Dict con todos los headers necesarios
|
||||
"""
|
||||
# Timestamps
|
||||
now = datetime.utcnow()
|
||||
date = now.strftime("%Y%m%dT%H%M%SZ")
|
||||
date_simple = now.strftime("%Y%m%d")
|
||||
|
||||
# Canonical request
|
||||
canonical_request, signed_headers = self._prepare_canonical_request(
|
||||
method, path, params, host, date, payload
|
||||
)
|
||||
|
||||
# String to sign
|
||||
string_to_sign = self._prepare_string_to_sign(canonical_request, date, date_simple)
|
||||
|
||||
# Signature
|
||||
signature = self._calculate_signature(string_to_sign, date_simple)
|
||||
|
||||
# Authorization header
|
||||
authorization = (
|
||||
f"HMAC-SHA256 "
|
||||
f"Credential={self.access_key}/{date_simple}/{self.client}/{self.user_id}/elcano_request,"
|
||||
f"SignedHeaders={signed_headers},"
|
||||
f"Signature={signature}"
|
||||
)
|
||||
|
||||
return {
|
||||
"X-Elcano-Host": host,
|
||||
"Content-type": "application/json;charset=utf-8",
|
||||
"X-Elcano-Client": self.client,
|
||||
"X-Elcano-Date": date,
|
||||
"X-Elcano-UserId": self.user_id,
|
||||
"Authorization": authorization
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test con las claves extraídas
|
||||
auth = AdifAuthenticator(
|
||||
access_key="and20210615",
|
||||
secret_key="Jthjtr946RTt",
|
||||
user_id="0c8c32dce47f8512"
|
||||
)
|
||||
|
||||
# Ejemplo de firma
|
||||
payload = json.dumps({
|
||||
"stationCode": "10200",
|
||||
"commercialService": "BOTH",
|
||||
"commercialStopType": "BOTH",
|
||||
"page": {"pageNumber": 0},
|
||||
"trafficType": "CERCANIAS"
|
||||
})
|
||||
|
||||
headers = auth.sign_request(
|
||||
method="POST",
|
||||
host="circulacion.api.adif.es",
|
||||
path="/portroyalmanager/secure/circulationpaths/departures/traffictype/",
|
||||
payload=payload
|
||||
)
|
||||
|
||||
print("Headers generados:")
|
||||
for key, value in headers.items():
|
||||
print(f"{key}: {value}")
|
||||
431
api_testing_scripts/adif_client.py
Executable file
431
api_testing_scripts/adif_client.py
Executable file
@@ -0,0 +1,431 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cliente Python para la API de Adif (Elcano)
|
||||
Obtenido mediante ingeniería reversa de la aplicación móvil
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from typing import Optional, Dict, Any, List
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class TrafficType(Enum):
|
||||
"""Tipos de tráfico ferroviario"""
|
||||
CERCANIAS = "CERCANIAS"
|
||||
MEDIA_DISTANCIA = "MEDIA_DISTANCIA"
|
||||
LARGA_DISTANCIA = "LARGA_DISTANCIA"
|
||||
ALL = "ALL"
|
||||
|
||||
|
||||
class State(Enum):
|
||||
"""Estados para filtros"""
|
||||
YES = "YES"
|
||||
NO = "NO"
|
||||
ALL = "ALL"
|
||||
|
||||
|
||||
class AdifClient:
|
||||
"""Cliente para interactuar con la API de Adif"""
|
||||
|
||||
# URLs base
|
||||
BASE_URL_STATIONS = "https://estaciones.api.adif.es"
|
||||
BASE_URL_CIRCULATION = "https://circulacion.api.adif.es"
|
||||
BASE_URL_ELCANOWEB = "https://elcanoweb.adif.es/api"
|
||||
BASE_URL_AVISA = "https://avisa.adif.es"
|
||||
|
||||
# User keys
|
||||
USER_KEY_CIRCULATIONS = "f4ce9fbfa9d721e39b8984805901b5df"
|
||||
USER_KEY_STATIONS = "0d021447a2fd2ac64553674d5a0c1a6f"
|
||||
|
||||
# Tokens
|
||||
REGISTRATION_TOKEN = "b9034774-c6e4-4663-a1a8-74bf7102651b"
|
||||
AVISA_BASIC_TOKEN = "YXZpc3RhX2NsaWVudF9hbmRyb2lkOjh5WzZKNyFmSjwhXypmYXE1NyNnOSohNElwa2MjWC1BTg=="
|
||||
SUBSCRIPTIONS_BASIC_TOKEN = "ZGVpbW9zOmRlaW1vc3R0"
|
||||
|
||||
def __init__(self, debug: bool = False):
|
||||
"""
|
||||
Inicializar el cliente
|
||||
|
||||
Args:
|
||||
debug: Si True, imprime información de depuración
|
||||
"""
|
||||
self.debug = debug
|
||||
self.session = requests.Session()
|
||||
|
||||
def _get_headers_stations(self) -> Dict[str, str]:
|
||||
"""Headers para endpoints de estaciones"""
|
||||
return {
|
||||
"Content-Type": "application/json;charset=utf-8",
|
||||
"User-key": self.USER_KEY_STATIONS
|
||||
}
|
||||
|
||||
def _get_headers_circulations(self) -> Dict[str, str]:
|
||||
"""Headers para endpoints de circulaciones"""
|
||||
return {
|
||||
"Content-Type": "application/json;charset=utf-8",
|
||||
"User-key": self.USER_KEY_CIRCULATIONS
|
||||
}
|
||||
|
||||
def _get_headers_avisa(self) -> Dict[str, str]:
|
||||
"""Headers para endpoints de Avisa"""
|
||||
return {
|
||||
"Content-Type": "application/json;charset=utf-8",
|
||||
"Authorization": f"Basic {self.AVISA_BASIC_TOKEN}"
|
||||
}
|
||||
|
||||
def _log(self, message: str):
|
||||
"""Log de depuración"""
|
||||
if self.debug:
|
||||
print(f"[DEBUG] {message}")
|
||||
|
||||
def _request(self, method: str, url: str, headers: Dict[str, str],
|
||||
data: Optional[Dict] = None, params: Optional[Dict] = None) -> Optional[Dict]:
|
||||
"""
|
||||
Realizar petición HTTP
|
||||
|
||||
Args:
|
||||
method: Método HTTP (GET, POST, etc.)
|
||||
url: URL completa
|
||||
headers: Headers HTTP
|
||||
data: Body JSON (opcional)
|
||||
params: Query parameters (opcional)
|
||||
|
||||
Returns:
|
||||
Respuesta JSON o None si hay error
|
||||
"""
|
||||
try:
|
||||
self._log(f"{method} {url}")
|
||||
if data:
|
||||
self._log(f"Body: {json.dumps(data, indent=2)}")
|
||||
|
||||
response = self.session.request(
|
||||
method=method,
|
||||
url=url,
|
||||
headers=headers,
|
||||
json=data,
|
||||
params=params,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
self._log(f"Status: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
self._log(f"Error: {response.text}")
|
||||
return {
|
||||
"error": True,
|
||||
"status_code": response.status_code,
|
||||
"message": response.text
|
||||
}
|
||||
except Exception as e:
|
||||
self._log(f"Exception: {str(e)}")
|
||||
return {"error": True, "message": str(e)}
|
||||
|
||||
# ==================== ESTACIONES ====================
|
||||
|
||||
def get_all_stations(self) -> Optional[Dict]:
|
||||
"""
|
||||
Obtener todas las estaciones
|
||||
|
||||
Returns:
|
||||
Listado de estaciones
|
||||
"""
|
||||
url = f"{self.BASE_URL_STATIONS}/portroyalmanager/secure/stations/allstations/reducedinfo/{self.REGISTRATION_TOKEN}/"
|
||||
return self._request("GET", url, self._get_headers_stations())
|
||||
|
||||
def get_station_details(self, station_code: str) -> Optional[Dict]:
|
||||
"""
|
||||
Obtener detalles de una estación
|
||||
|
||||
Args:
|
||||
station_code: Código de la estación
|
||||
|
||||
Returns:
|
||||
Detalles de la estación
|
||||
"""
|
||||
url = f"{self.BASE_URL_STATIONS}/portroyalmanager/secure/stations/onestation/"
|
||||
data = {"stationCode": station_code}
|
||||
return self._request("POST", url, self._get_headers_stations(), data=data)
|
||||
|
||||
def get_station_observations(self, station_code: str) -> Optional[Dict]:
|
||||
"""
|
||||
Obtener observaciones de una estación
|
||||
|
||||
Args:
|
||||
station_code: Código de la estación
|
||||
|
||||
Returns:
|
||||
Observaciones de la estación
|
||||
"""
|
||||
url = f"{self.BASE_URL_STATIONS}/portroyalmanager/secure/stationsobservations/"
|
||||
data = {"stationCode": station_code}
|
||||
return self._request("POST", url, self._get_headers_stations(), data=data)
|
||||
|
||||
# ==================== CIRCULACIONES ====================
|
||||
|
||||
def get_departures(self,
|
||||
station_code: str,
|
||||
traffic_type: TrafficType = TrafficType.ALL,
|
||||
commercial_service: State = State.ALL,
|
||||
commercial_stop_type: State = State.ALL,
|
||||
page: int = 0,
|
||||
size: int = 20,
|
||||
origin_station: Optional[str] = None,
|
||||
destination_station: Optional[str] = None) -> Optional[Dict]:
|
||||
"""
|
||||
Obtener salidas desde una estación
|
||||
|
||||
Args:
|
||||
station_code: Código de la estación
|
||||
traffic_type: Tipo de tráfico (CERCANIAS, MEDIA_DISTANCIA, etc.)
|
||||
commercial_service: Filtro de servicio comercial
|
||||
commercial_stop_type: Filtro de tipo de parada comercial
|
||||
page: Número de página
|
||||
size: Tamaño de página
|
||||
origin_station: Estación origen (opcional)
|
||||
destination_station: Estación destino (opcional)
|
||||
|
||||
Returns:
|
||||
Salidas de trenes
|
||||
"""
|
||||
url = f"{self.BASE_URL_CIRCULATION}/portroyalmanager/secure/circulationpaths/departures/traffictype/"
|
||||
data = {
|
||||
"commercialService": commercial_service.value,
|
||||
"commercialStopType": commercial_stop_type.value,
|
||||
"stationCode": station_code,
|
||||
"page": {
|
||||
"page": page,
|
||||
"size": size
|
||||
},
|
||||
"trafficType": traffic_type.value
|
||||
}
|
||||
|
||||
if origin_station:
|
||||
data["originStationCode"] = origin_station
|
||||
if destination_station:
|
||||
data["destinationStationCode"] = destination_station
|
||||
|
||||
return self._request("POST", url, self._get_headers_circulations(), data=data)
|
||||
|
||||
def get_arrivals(self,
|
||||
station_code: str,
|
||||
traffic_type: TrafficType = TrafficType.ALL,
|
||||
commercial_service: State = State.ALL,
|
||||
commercial_stop_type: State = State.ALL,
|
||||
page: int = 0,
|
||||
size: int = 20,
|
||||
origin_station: Optional[str] = None,
|
||||
destination_station: Optional[str] = None) -> Optional[Dict]:
|
||||
"""
|
||||
Obtener llegadas a una estación
|
||||
|
||||
Args:
|
||||
station_code: Código de la estación
|
||||
traffic_type: Tipo de tráfico
|
||||
commercial_service: Filtro de servicio comercial
|
||||
commercial_stop_type: Filtro de tipo de parada comercial
|
||||
page: Número de página
|
||||
size: Tamaño de página
|
||||
origin_station: Estación origen (opcional)
|
||||
destination_station: Estación destino (opcional)
|
||||
|
||||
Returns:
|
||||
Llegadas de trenes
|
||||
"""
|
||||
url = f"{self.BASE_URL_CIRCULATION}/portroyalmanager/secure/circulationpaths/arrivals/traffictype/"
|
||||
data = {
|
||||
"commercialService": commercial_service.value,
|
||||
"commercialStopType": commercial_stop_type.value,
|
||||
"stationCode": station_code,
|
||||
"page": {
|
||||
"page": page,
|
||||
"size": size
|
||||
},
|
||||
"trafficType": traffic_type.value
|
||||
}
|
||||
|
||||
if origin_station:
|
||||
data["originStationCode"] = origin_station
|
||||
if destination_station:
|
||||
data["destinationStationCode"] = destination_station
|
||||
|
||||
return self._request("POST", url, self._get_headers_circulations(), data=data)
|
||||
|
||||
def get_between_stations(self,
|
||||
origin_station: str,
|
||||
destination_station: str,
|
||||
traffic_type: TrafficType = TrafficType.ALL,
|
||||
commercial_service: State = State.ALL,
|
||||
commercial_stop_type: State = State.ALL,
|
||||
page: int = 0,
|
||||
size: int = 20) -> Optional[Dict]:
|
||||
"""
|
||||
Obtener trenes entre dos estaciones
|
||||
|
||||
Args:
|
||||
origin_station: Estación origen
|
||||
destination_station: Estación destino
|
||||
traffic_type: Tipo de tráfico
|
||||
commercial_service: Filtro de servicio comercial
|
||||
commercial_stop_type: Filtro de tipo de parada comercial
|
||||
page: Número de página
|
||||
size: Tamaño de página
|
||||
|
||||
Returns:
|
||||
Trenes entre estaciones
|
||||
"""
|
||||
url = f"{self.BASE_URL_CIRCULATION}/portroyalmanager/secure/circulationpaths/betweenstations/traffictype/"
|
||||
data = {
|
||||
"commercialService": commercial_service.value,
|
||||
"commercialStopType": commercial_stop_type.value,
|
||||
"originStationCode": origin_station,
|
||||
"destinationStationCode": destination_station,
|
||||
"page": {
|
||||
"page": page,
|
||||
"size": size
|
||||
},
|
||||
"trafficType": traffic_type.value
|
||||
}
|
||||
return self._request("POST", url, self._get_headers_circulations(), data=data)
|
||||
|
||||
def get_path_details(self,
|
||||
commercial_number: Optional[str] = None,
|
||||
origin_station: Optional[str] = None,
|
||||
destination_station: Optional[str] = None,
|
||||
launching_date: Optional[int] = None,
|
||||
all_control_points: bool = False) -> Optional[Dict]:
|
||||
"""
|
||||
Obtener detalles de una ruta/tren específico
|
||||
|
||||
Args:
|
||||
commercial_number: Número comercial del tren
|
||||
origin_station: Estación origen
|
||||
destination_station: Estación destino
|
||||
launching_date: Fecha de salida (timestamp en milisegundos)
|
||||
all_control_points: Si mostrar todos los puntos de control
|
||||
|
||||
Returns:
|
||||
Detalles de la ruta
|
||||
"""
|
||||
url = f"{self.BASE_URL_CIRCULATION}/portroyalmanager/secure/circulationpathdetails/onepaths/"
|
||||
data = {
|
||||
"allControlPoints": all_control_points
|
||||
}
|
||||
|
||||
if commercial_number:
|
||||
data["commercialNumber"] = commercial_number
|
||||
if origin_station:
|
||||
data["originStationCode"] = origin_station
|
||||
if destination_station:
|
||||
data["destinationStationCode"] = destination_station
|
||||
if launching_date:
|
||||
data["launchingDate"] = launching_date
|
||||
|
||||
return self._request("POST", url, self._get_headers_circulations(), data=data)
|
||||
|
||||
def get_composition(self,
|
||||
commercial_number: Optional[str] = None,
|
||||
origin_station: Optional[str] = None,
|
||||
destination_station: Optional[str] = None,
|
||||
launching_date: Optional[int] = None) -> Optional[Dict]:
|
||||
"""
|
||||
Obtener composición de un tren (vagones, etc.)
|
||||
|
||||
Args:
|
||||
commercial_number: Número comercial del tren
|
||||
origin_station: Estación origen
|
||||
destination_station: Estación destino
|
||||
launching_date: Fecha de salida (timestamp en milisegundos)
|
||||
|
||||
Returns:
|
||||
Composición del tren
|
||||
"""
|
||||
url = f"{self.BASE_URL_CIRCULATION}/portroyalmanager/secure/circulationpaths/compositions/path/"
|
||||
data = {}
|
||||
|
||||
if commercial_number:
|
||||
data["commercialNumber"] = commercial_number
|
||||
if origin_station:
|
||||
data["originStationCode"] = origin_station
|
||||
if destination_station:
|
||||
data["destinationStationCode"] = destination_station
|
||||
if launching_date:
|
||||
data["launchingDate"] = launching_date
|
||||
|
||||
return self._request("POST", url, self._get_headers_circulations(), data=data)
|
||||
|
||||
# ==================== AVISA ====================
|
||||
|
||||
def avisa_get_stations(self) -> Optional[Dict]:
|
||||
"""
|
||||
Obtener estaciones de Avisa
|
||||
|
||||
Returns:
|
||||
Estaciones de Avisa
|
||||
"""
|
||||
url = f"{self.BASE_URL_AVISA}/avisa-ws/api/v1/station"
|
||||
return self._request("GET", url, self._get_headers_avisa())
|
||||
|
||||
def avisa_get_categories(self) -> Optional[Dict]:
|
||||
"""
|
||||
Obtener categorías de estaciones
|
||||
|
||||
Returns:
|
||||
Categorías
|
||||
"""
|
||||
url = f"{self.BASE_URL_AVISA}/avisa-ws/api/v1/category"
|
||||
return self._request("GET", url, self._get_headers_avisa())
|
||||
|
||||
def avisa_get_incidences(self) -> Optional[Dict]:
|
||||
"""
|
||||
Obtener incidencias
|
||||
|
||||
Returns:
|
||||
Lista de incidencias
|
||||
"""
|
||||
url = f"{self.BASE_URL_AVISA}/avisa-ws/api/v1/incidence"
|
||||
return self._request("GET", url, self._get_headers_avisa())
|
||||
|
||||
|
||||
def main():
|
||||
"""Ejemplo de uso"""
|
||||
print("=== Cliente Adif API ===\n")
|
||||
|
||||
# Crear cliente con modo debug
|
||||
client = AdifClient(debug=True)
|
||||
|
||||
# Ejemplo: Obtener todas las estaciones
|
||||
print("\n1. Intentando obtener todas las estaciones...")
|
||||
stations = client.get_all_stations()
|
||||
if stations and not stations.get("error"):
|
||||
print(f"✓ Encontradas {len(stations.get('stations', []))} estaciones")
|
||||
else:
|
||||
print(f"✗ Error: {stations}")
|
||||
|
||||
# Ejemplo: Obtener salidas de Madrid Atocha (código: 10200)
|
||||
print("\n2. Intentando obtener salidas de Madrid Atocha...")
|
||||
departures = client.get_departures(
|
||||
station_code="10200",
|
||||
traffic_type=TrafficType.CERCANIAS,
|
||||
size=5
|
||||
)
|
||||
if departures and not departures.get("error"):
|
||||
print(f"✓ Obtenidas salidas")
|
||||
print(json.dumps(departures, indent=2, ensure_ascii=False)[:500] + "...")
|
||||
else:
|
||||
print(f"✗ Error: {departures}")
|
||||
|
||||
# Ejemplo: Obtener estaciones de Avisa
|
||||
print("\n3. Intentando obtener estaciones de Avisa...")
|
||||
avisa_stations = client.avisa_get_stations()
|
||||
if avisa_stations and not avisa_stations.get("error"):
|
||||
print(f"✓ Obtenidas estaciones de Avisa")
|
||||
else:
|
||||
print(f"✗ Error: {avisa_stations}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
175
api_testing_scripts/test_api_authenticated.py
Normal file
175
api_testing_scripts/test_api_authenticated.py
Normal file
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test de endpoints de Adif con autenticación HMAC-SHA256
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from adif_auth import AdifAuthenticator
|
||||
|
||||
# Claves extraídas con Frida
|
||||
ACCESS_KEY = "and20210615"
|
||||
SECRET_KEY = "Jthjtr946RTt"
|
||||
USER_ID = "0c8c32dce47f8512"
|
||||
|
||||
# Crear autenticador
|
||||
auth = AdifAuthenticator(ACCESS_KEY, SECRET_KEY, USER_ID)
|
||||
|
||||
def test_departures():
|
||||
"""Probar endpoint de salidas"""
|
||||
print("\n" + "="*70)
|
||||
print("TEST: Salidas de Madrid Atocha (Cercanías)")
|
||||
print("="*70)
|
||||
|
||||
host = "circulacion.api.adif.es"
|
||||
path = "/portroyalmanager/secure/circulationpaths/departures/traffictype/"
|
||||
url = f"https://{host}{path}"
|
||||
|
||||
payload = {
|
||||
"stationCode": "10200",
|
||||
"commercialService": "BOTH",
|
||||
"commercialStopType": "BOTH",
|
||||
"page": {"pageNumber": 0},
|
||||
"trafficType": "CERCANIAS"
|
||||
}
|
||||
|
||||
payload_str = json.dumps(payload)
|
||||
|
||||
# Firmar petición
|
||||
headers = auth.sign_request(
|
||||
method="POST",
|
||||
host=host,
|
||||
path=path,
|
||||
payload=payload_str
|
||||
)
|
||||
|
||||
print(f"\nURL: {url}")
|
||||
print(f"Payload: {payload_str}")
|
||||
print(f"\nHeaders:")
|
||||
for k, v in headers.items():
|
||||
if k == "Authorization":
|
||||
print(f" {k}: {v[:50]}...")
|
||||
else:
|
||||
print(f" {k}: {v}")
|
||||
|
||||
# Hacer petición
|
||||
try:
|
||||
response = requests.post(url, headers=headers, data=payload_str, timeout=10)
|
||||
print(f"\nStatus Code: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
print("✅ SUCCESS!")
|
||||
data = response.json()
|
||||
print(f"\nRespuesta (preview):")
|
||||
print(json.dumps(data, indent=2, ensure_ascii=False)[:1000])
|
||||
else:
|
||||
print(f"❌ ERROR")
|
||||
print(f"Response: {response.text[:500]}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ EXCEPTION: {e}")
|
||||
|
||||
|
||||
def test_station_details():
|
||||
"""Probar endpoint de detalles de estación"""
|
||||
print("\n" + "="*70)
|
||||
print("TEST: Detalles de estación Madrid Atocha")
|
||||
print("="*70)
|
||||
|
||||
host = "estaciones.api.adif.es"
|
||||
path = "/portroyalmanager/secure/stations/onestation/"
|
||||
url = f"https://{host}{path}"
|
||||
|
||||
payload = {"stationCode": "10200"}
|
||||
payload_str = json.dumps(payload)
|
||||
|
||||
headers = auth.sign_request(
|
||||
method="POST",
|
||||
host=host,
|
||||
path=path,
|
||||
payload=payload_str
|
||||
)
|
||||
|
||||
print(f"\nURL: {url}")
|
||||
print(f"Payload: {payload_str}")
|
||||
|
||||
try:
|
||||
response = requests.post(url, headers=headers, data=payload_str, timeout=10)
|
||||
print(f"\nStatus Code: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
print("✅ SUCCESS!")
|
||||
data = response.json()
|
||||
print(f"\nRespuesta (preview):")
|
||||
print(json.dumps(data, indent=2, ensure_ascii=False)[:1000])
|
||||
else:
|
||||
print(f"❌ ERROR")
|
||||
print(f"Response: {response.text[:500]}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ EXCEPTION: {e}")
|
||||
|
||||
|
||||
def test_arrivals():
|
||||
"""Probar endpoint de llegadas"""
|
||||
print("\n" + "="*70)
|
||||
print("TEST: Llegadas a Madrid Atocha (Todos los tipos)")
|
||||
print("="*70)
|
||||
|
||||
host = "circulacion.api.adif.es"
|
||||
path = "/portroyalmanager/secure/circulationpaths/arrivals/traffictype/"
|
||||
url = f"https://{host}{path}"
|
||||
|
||||
payload = {
|
||||
"stationCode": "10200",
|
||||
"commercialService": "BOTH",
|
||||
"commercialStopType": "BOTH",
|
||||
"page": {"pageNumber": 0},
|
||||
"trafficType": "ALL"
|
||||
}
|
||||
|
||||
payload_str = json.dumps(payload)
|
||||
|
||||
headers = auth.sign_request(
|
||||
method="POST",
|
||||
host=host,
|
||||
path=path,
|
||||
payload=payload_str
|
||||
)
|
||||
|
||||
print(f"\nURL: {url}")
|
||||
|
||||
try:
|
||||
response = requests.post(url, headers=headers, data=payload_str, timeout=10)
|
||||
print(f"\nStatus Code: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
print("✅ SUCCESS!")
|
||||
data = response.json()
|
||||
print(f"\nRespuesta (preview):")
|
||||
print(json.dumps(data, indent=2, ensure_ascii=False)[:800])
|
||||
else:
|
||||
print(f"❌ ERROR")
|
||||
print(f"Response: {response.text[:500]}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ EXCEPTION: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n" + "="*70)
|
||||
print("PRUEBAS DE API ADIF CON AUTENTICACIÓN HMAC-SHA256")
|
||||
print("="*70)
|
||||
print(f"\nUsando claves:")
|
||||
print(f" ACCESS_KEY: {ACCESS_KEY}")
|
||||
print(f" SECRET_KEY: {SECRET_KEY}")
|
||||
print(f" USER_ID: {USER_ID}")
|
||||
|
||||
# Ejecutar tests
|
||||
test_departures()
|
||||
test_station_details()
|
||||
test_arrivals()
|
||||
|
||||
print("\n" + "="*70)
|
||||
print("TESTS COMPLETADOS")
|
||||
print("="*70)
|
||||
199
api_testing_scripts/test_endpoints.py
Executable file
199
api_testing_scripts/test_endpoints.py
Executable file
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script para probar diferentes endpoints de la API de Adif
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# Headers descubiertos
|
||||
HEADERS_CIRCULATION = {
|
||||
"Content-Type": "application/json;charset=utf-8",
|
||||
"User-key": "f4ce9fbfa9d721e39b8984805901b5df"
|
||||
}
|
||||
|
||||
HEADERS_STATIONS = {
|
||||
"Content-Type": "application/json;charset=utf-8",
|
||||
"User-key": "0d021447a2fd2ac64553674d5a0c1a6f"
|
||||
}
|
||||
|
||||
HEADERS_AVISA = {
|
||||
"Content-Type": "application/json;charset=utf-8",
|
||||
"Authorization": "Basic YXZpc3RhX2NsaWVudF9hbmRyb2lkOjh5WzZKNyFmSjwhXypmYXE1NyNnOSohNElwa2MjWC1BTg=="
|
||||
}
|
||||
|
||||
# URLs base
|
||||
BASE_CIRCULATION = "https://circulacion.api.adif.es"
|
||||
BASE_STATIONS = "https://estaciones.api.adif.es"
|
||||
BASE_AVISA = "https://avisa.adif.es"
|
||||
|
||||
|
||||
def test_endpoint(name, method, url, headers, data=None):
|
||||
"""Probar un endpoint y mostrar resultado"""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"TEST: {name}")
|
||||
print(f"{'='*60}")
|
||||
print(f"Method: {method}")
|
||||
print(f"URL: {url}")
|
||||
print(f"Headers: {json.dumps(headers, indent=2)}")
|
||||
|
||||
if data:
|
||||
print(f"Body: {json.dumps(data, indent=2)}")
|
||||
|
||||
try:
|
||||
if method == "GET":
|
||||
response = requests.get(url, headers=headers, timeout=10)
|
||||
elif method == "POST":
|
||||
response = requests.post(url, headers=headers, json=data, timeout=10)
|
||||
else:
|
||||
print(f"❌ Método {method} no soportado")
|
||||
return
|
||||
|
||||
print(f"\nStatus: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
print("✅ SUCCESS")
|
||||
result = response.json()
|
||||
print(f"\nResponse Preview:")
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False)[:1000] + "...")
|
||||
else:
|
||||
print(f"❌ ERROR")
|
||||
print(f"Response: {response.text[:500]}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ EXCEPTION: {str(e)}")
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("PRUEBAS DE ENDPOINTS DE ADIF API")
|
||||
print("=" * 60)
|
||||
|
||||
# Test 1: Obtener salidas con diferentes formatos de body
|
||||
test_endpoint(
|
||||
"Salidas - Formato Simple",
|
||||
"POST",
|
||||
f"{BASE_CIRCULATION}/portroyalmanager/secure/circulationpaths/departures/traffictype/",
|
||||
HEADERS_CIRCULATION,
|
||||
{
|
||||
"stationCode": "10200",
|
||||
"commercialService": "BOTH",
|
||||
"commercialStopType": "BOTH",
|
||||
"page": {"pageNumber": 0},
|
||||
"trafficType": "CERCANIAS"
|
||||
}
|
||||
)
|
||||
|
||||
# Test 2: Probar con State YES
|
||||
test_endpoint(
|
||||
"Salidas - State YES",
|
||||
"POST",
|
||||
f"{BASE_CIRCULATION}/portroyalmanager/secure/circulationpaths/departures/traffictype/",
|
||||
HEADERS_CIRCULATION,
|
||||
{
|
||||
"stationCode": "10200",
|
||||
"commercialService": "YES",
|
||||
"commercialStopType": "YES",
|
||||
"page": {"pageNumber": 0},
|
||||
"trafficType": "ALL"
|
||||
}
|
||||
)
|
||||
|
||||
# Test 3: Detalles de una ruta específica
|
||||
test_endpoint(
|
||||
"Detalles de Ruta",
|
||||
"POST",
|
||||
f"{BASE_CIRCULATION}/portroyalmanager/secure/circulationpathdetails/onepaths/",
|
||||
HEADERS_CIRCULATION,
|
||||
{
|
||||
"commercialNumber": "C1",
|
||||
"allControlPoints": False
|
||||
}
|
||||
)
|
||||
|
||||
# Test 4: Entre estaciones
|
||||
test_endpoint(
|
||||
"Entre Estaciones",
|
||||
"POST",
|
||||
f"{BASE_CIRCULATION}/portroyalmanager/secure/circulationpaths/betweenstations/traffictype/",
|
||||
HEADERS_CIRCULATION,
|
||||
{
|
||||
"originStationCode": "10200",
|
||||
"destinationStationCode": "10302",
|
||||
"commercialService": "BOTH",
|
||||
"commercialStopType": "BOTH",
|
||||
"page": {"pageNumber": 0},
|
||||
"trafficType": "ALL"
|
||||
}
|
||||
)
|
||||
|
||||
# Test 5: Detalles de estación
|
||||
test_endpoint(
|
||||
"Detalles de Estación",
|
||||
"POST",
|
||||
f"{BASE_STATIONS}/portroyalmanager/secure/stations/onestation/",
|
||||
HEADERS_STATIONS,
|
||||
{
|
||||
"stationCode": "10200"
|
||||
}
|
||||
)
|
||||
|
||||
# Test 6: Observaciones de estación
|
||||
test_endpoint(
|
||||
"Observaciones de Estación",
|
||||
"POST",
|
||||
f"{BASE_STATIONS}/portroyalmanager/secure/stationsobservations/",
|
||||
HEADERS_STATIONS,
|
||||
{
|
||||
"stationCode": "10200"
|
||||
}
|
||||
)
|
||||
|
||||
# Test 7: Composición de tren
|
||||
test_endpoint(
|
||||
"Composición de Tren",
|
||||
"POST",
|
||||
f"{BASE_CIRCULATION}/portroyalmanager/secure/circulationpaths/compositions/path/",
|
||||
HEADERS_CIRCULATION,
|
||||
{
|
||||
"commercialNumber": "AVE 1001",
|
||||
"originStationCode": "10200",
|
||||
"destinationStationCode": "71801"
|
||||
}
|
||||
)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("RESUMEN")
|
||||
print("="*60)
|
||||
print("""
|
||||
Las pruebas han finalizado. Revisa los resultados arriba.
|
||||
|
||||
NOTAS:
|
||||
- Algunos endpoints pueden requerir códigos/números válidos
|
||||
- Los códigos de estación son numéricos (ej: 10200 para Madrid Atocha)
|
||||
- Los números comerciales varían según el tipo de tren
|
||||
- Algunos datos pueden no estar disponibles en tiempo real
|
||||
|
||||
CÓDIGOS DE ESTACIÓN COMUNES:
|
||||
- 10200: Madrid Puerta de Atocha
|
||||
- 10302: Madrid Chamartín
|
||||
- 71801: Barcelona Sants
|
||||
- 50000: Valencia Nord
|
||||
- 11401: Sevilla Santa Justa
|
||||
|
||||
TIPOS DE TRÁFICO:
|
||||
- CERCANIAS: Trenes de cercanías
|
||||
- MEDIA_DISTANCIA: Media distancia
|
||||
- LARGA_DISTANCIA: Larga distancia
|
||||
- ALL: Todos los tipos
|
||||
|
||||
ESTADOS:
|
||||
- YES: Solo servicios/paradas comerciales
|
||||
- NOT: Sin servicios/paradas comerciales
|
||||
- BOTH: Ambos tipos
|
||||
""")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
440
apk_decompiled/resources/AndroidManifest.xml
Normal file
440
apk_decompiled/resources/AndroidManifest.xml
Normal file
@@ -0,0 +1,440 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="72"
|
||||
android:versionName="2.1.0"
|
||||
android:compileSdkVersion="35"
|
||||
android:compileSdkVersionCodename="15"
|
||||
package="com.adif.elcanomovil"
|
||||
platformBuildVersionCode="35"
|
||||
platformBuildVersionName="15">
|
||||
<uses-sdk
|
||||
android:minSdkVersion="29"
|
||||
android:targetSdkVersion="35"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="https"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<data android:scheme="https"/>
|
||||
</intent>
|
||||
<package android:name="com.google.android.apps.maps"/>
|
||||
</queries>
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-feature
|
||||
android:glEsVersion="0x20000"
|
||||
android:required="true"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
||||
<uses-permission android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE"/>
|
||||
<permission
|
||||
android:name="com.adif.elcanomovil.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
|
||||
android:protectionLevel="signature"/>
|
||||
<uses-permission android:name="com.adif.elcanomovil.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"/>
|
||||
<application
|
||||
android:theme="@style/SplashScreenTheme"
|
||||
android:label="@string/app_name_label"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:name="com.adif.elcanomovil.ElcanoApplication"
|
||||
android:allowBackup="false"
|
||||
android:hardwareAccelerated="true"
|
||||
android:supportsRtl="true"
|
||||
android:extractNativeLibs="false"
|
||||
android:usesCleartextTraffic="false"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:appComponentFactory="androidx.core.app.CoreComponentFactory"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
<meta-data
|
||||
android:name="com.google.android.geo.API_KEY"
|
||||
android:value="AIzaSyDIzMtgIKRHGwmOFihX_--ftSeMLjwF3cY"/>
|
||||
<activity
|
||||
android:name="com.adif.elcanomovil.main.MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleInstance"
|
||||
android:windowSoftInputMode="adjustPan">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="adif.page.link"/>
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="adifpreproduccion.page.link"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="adifmovil"/>
|
||||
<data android:host="app"/>
|
||||
<data android:path="/home"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="adifmovil"/>
|
||||
<data android:host="app"/>
|
||||
<data android:path="/departures"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="adifmovil"/>
|
||||
<data android:host="app"/>
|
||||
<data android:path="/station"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="adifmovil"/>
|
||||
<data android:host="app"/>
|
||||
<data android:path="/moreAdif"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="adifmovil"/>
|
||||
<data android:host="app"/>
|
||||
<data android:path="/selectTrain"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="adifmovil"/>
|
||||
<data android:host="app"/>
|
||||
<data android:path="/favourites"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="adifmovil"/>
|
||||
<data android:host="app"/>
|
||||
<data android:path="/avisa"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="adifmovil"/>
|
||||
<data android:host="app"/>
|
||||
<data android:path="/avisaIncidenceDetails"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="adifmovil"/>
|
||||
<data android:host="app"/>
|
||||
<data android:path="/subscriptionDetails"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="adifmovil"/>
|
||||
<data android:host="app"/>
|
||||
<data android:path="/trainDetails"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="adifmovil"/>
|
||||
<data android:host="app"/>
|
||||
<data android:path="/favourites"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="adifmovil"/>
|
||||
<data android:host="app"/>
|
||||
<data android:path="/avisa"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="adifmovil"/>
|
||||
<data android:host="app"/>
|
||||
<data android:path="/avisaIncidenceDetails"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="adifmovil"/>
|
||||
<data android:host="app"/>
|
||||
<data android:path="/subscriptionDetails"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="adifmovil"/>
|
||||
<data android:host="app"/>
|
||||
<data android:path="/trainDetails"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:label="WizardActivity"
|
||||
android:name="com.adif.elcanomovil.uiMoreAdif.wizard.WizardActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_icon"
|
||||
android:resource="@drawable/ic_adif_logo_simple"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_color"
|
||||
android:resource="@color/colorPrimary"/>
|
||||
<meta-data
|
||||
android:name="com.adif.elcanomovil.notifications.AppFirebaseMessagingService"
|
||||
android:value="@string/default_notification_channel_id"/>
|
||||
<service
|
||||
android:name="com.adif.elcanomovil.notifications.AppFirebaseMessagingService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
<receiver
|
||||
android:name="com.adif.elcanomovil.widget.DeparturesWidget"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/departures_widget_info"/>
|
||||
</receiver>
|
||||
<activity
|
||||
android:name="com.adif.elcanomovil.widget.DeparturesWidgetConfigureActivity"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:exported="false"
|
||||
android:authorities="com.adif.elcanomovil.provider"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths"/>
|
||||
</provider>
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:exported="false"
|
||||
android:authorities="com.adif.elcanomovil.androidx-startup">
|
||||
<meta-data
|
||||
android:name="androidx.emoji2.text.EmojiCompatInitializer"
|
||||
android:value="androidx.startup"/>
|
||||
<meta-data
|
||||
android:name="androidx.lifecycle.ProcessLifecycleInitializer"
|
||||
android:value="androidx.startup"/>
|
||||
<meta-data
|
||||
android:name="androidx.profileinstaller.ProfileInstallerInitializer"
|
||||
android:value="androidx.startup"/>
|
||||
</provider>
|
||||
<uses-library
|
||||
android:name="org.apache.http.legacy"
|
||||
android:required="false"/>
|
||||
<service
|
||||
android:name="com.google.firebase.components.ComponentDiscoveryService"
|
||||
android:exported="false"
|
||||
android:directBootAware="true">
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.messaging.ktx.FirebaseMessagingLegacyRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.messaging.FirebaseMessagingKtxRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.messaging.FirebaseMessagingRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.dynamiclinks.ktx.FirebaseDynamicLinksLegacyRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.dynamiclinks.FirebaseDynamicLinksKtxRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.dynamiclinks.internal.FirebaseDynamicLinkRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.storage.ktx.FirebaseStorageKtxRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.storage.StorageRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.remoteconfig.ktx.FirebaseConfigLegacyRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.crashlytics.FirebaseCrashlyticsKtxRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.crashlytics.CrashlyticsRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.analytics.ktx.FirebaseAnalyticsKtxRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.remoteconfig.FirebaseRemoteConfigKtxRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.remoteconfig.RemoteConfigRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.sessions.FirebaseSessionsRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.analytics.connector.internal.AnalyticsConnectorRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.installations.FirebaseInstallationsKtxRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.installations.FirebaseInstallationsRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.ktx.FirebaseCommonLegacyRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.FirebaseCommonKtxRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.abt.component.AbtRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.components:com.google.firebase.datatransport.TransportRegistrar"
|
||||
android:value="com.google.firebase.components.ComponentRegistrar"/>
|
||||
</service>
|
||||
<receiver
|
||||
android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
|
||||
android:permission="com.google.android.c2dm.permission.SEND"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.c2dm.intent.RECEIVE"/>
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.cloudmessaging.FINISHED_AFTER_HANDLED"
|
||||
android:value="true"/>
|
||||
</receiver>
|
||||
<service
|
||||
android:name="com.google.firebase.messaging.FirebaseMessagingService"
|
||||
android:exported="false"
|
||||
android:directBootAware="true">
|
||||
<intent-filter android:priority="-500">
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
<activity
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||
android:name="com.google.android.gms.common.api.GoogleApiActivity"
|
||||
android:exported="false"/>
|
||||
<service
|
||||
android:name="com.google.firebase.sessions.SessionLifecycleService"
|
||||
android:enabled="true"
|
||||
android:exported="false"/>
|
||||
<provider
|
||||
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||
android:exported="false"
|
||||
android:authorities="com.adif.elcanomovil.firebaseinitprovider"
|
||||
android:initOrder="100"
|
||||
android:directBootAware="true"/>
|
||||
<receiver
|
||||
android:name="com.google.android.gms.measurement.AppMeasurementReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false"/>
|
||||
<service
|
||||
android:name="com.google.android.gms.measurement.AppMeasurementService"
|
||||
android:enabled="true"
|
||||
android:exported="false"/>
|
||||
<service
|
||||
android:name="com.google.android.gms.measurement.AppMeasurementJobService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:enabled="true"
|
||||
android:exported="false"/>
|
||||
<service
|
||||
android:name="androidx.room.MultiInstanceInvalidationService"
|
||||
android:exported="false"
|
||||
android:directBootAware="true"/>
|
||||
<uses-library
|
||||
android:name="androidx.window.extensions"
|
||||
android:required="false"/>
|
||||
<uses-library
|
||||
android:name="androidx.window.sidecar"
|
||||
android:required="false"/>
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
||||
android:value="@integer/google_play_services_version"/>
|
||||
<service
|
||||
android:name="androidx.core.widget.RemoteViewsCompatService"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS"/>
|
||||
<receiver
|
||||
android:name="androidx.profileinstaller.ProfileInstallReceiver"
|
||||
android:permission="android.permission.DUMP"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:directBootAware="false">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.profileinstaller.action.INSTALL_PROFILE"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="androidx.profileinstaller.action.SKIP_FILE"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="androidx.profileinstaller.action.SAVE_PROFILE"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="androidx.profileinstaller.action.BENCHMARK_OPERATION"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<service
|
||||
android:name="com.google.android.datatransport.runtime.backends.TransportBackendDiscovery"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="backend:com.google.android.datatransport.cct.CctBackendFactory"
|
||||
android:value="cct"/>
|
||||
</service>
|
||||
<service
|
||||
android:name="com.google.android.datatransport.runtime.scheduling.jobscheduling.JobInfoSchedulerService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:exported="false"/>
|
||||
<receiver
|
||||
android:name="com.google.android.datatransport.runtime.scheduling.jobscheduling.AlarmManagerSchedulerBroadcastReceiver"
|
||||
android:exported="false"/>
|
||||
</application>
|
||||
</manifest>
|
||||
BIN
apk_decompiled/resources/DebugProbesKt.bin
Normal file
BIN
apk_decompiled/resources/DebugProbesKt.bin
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
1.9.0
|
||||
@@ -0,0 +1 @@
|
||||
1.9.0
|
||||
@@ -0,0 +1 @@
|
||||
1.4.0
|
||||
@@ -0,0 +1 @@
|
||||
1.7.0
|
||||
@@ -0,0 +1 @@
|
||||
1.7.0
|
||||
@@ -0,0 +1 @@
|
||||
task ':arch:core:core-runtime:writeVersionFile' property 'version'
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
1.1.0
|
||||
@@ -0,0 +1 @@
|
||||
1.13.0
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
1.13.0
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
1.1.0
|
||||
@@ -0,0 +1 @@
|
||||
8.9.0
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
1.1.1
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
1.3.0
|
||||
@@ -0,0 +1 @@
|
||||
1.3.0
|
||||
@@ -0,0 +1 @@
|
||||
1.3.6
|
||||
@@ -0,0 +1 @@
|
||||
1.7.0
|
||||
@@ -0,0 +1 @@
|
||||
1.7.0
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
2.8.0
|
||||
@@ -0,0 +1 @@
|
||||
2.8.0
|
||||
@@ -0,0 +1 @@
|
||||
2.8.0
|
||||
@@ -0,0 +1 @@
|
||||
2.8.0
|
||||
@@ -0,0 +1 @@
|
||||
2.8.0
|
||||
@@ -0,0 +1 @@
|
||||
2.8.0
|
||||
@@ -0,0 +1 @@
|
||||
2.8.0
|
||||
@@ -0,0 +1 @@
|
||||
2.8.0
|
||||
@@ -0,0 +1 @@
|
||||
2.8.0
|
||||
@@ -0,0 +1 @@
|
||||
2.8.0
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
2.5.3
|
||||
@@ -0,0 +1 @@
|
||||
2.5.3
|
||||
@@ -0,0 +1 @@
|
||||
2.5.3
|
||||
@@ -0,0 +1 @@
|
||||
2.5.3
|
||||
@@ -0,0 +1 @@
|
||||
2.5.3
|
||||
@@ -0,0 +1 @@
|
||||
2.5.3
|
||||
@@ -0,0 +1 @@
|
||||
2.5.3
|
||||
@@ -0,0 +1 @@
|
||||
2.5.3
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
1.3.1
|
||||
@@ -0,0 +1 @@
|
||||
1.3.2
|
||||
@@ -0,0 +1 @@
|
||||
2.6.1
|
||||
@@ -0,0 +1 @@
|
||||
2.6.1
|
||||
@@ -0,0 +1 @@
|
||||
1.2.1
|
||||
@@ -0,0 +1 @@
|
||||
1.2.1
|
||||
@@ -0,0 +1 @@
|
||||
1.2.0
|
||||
@@ -0,0 +1 @@
|
||||
2.4.0
|
||||
@@ -0,0 +1 @@
|
||||
2.4.0
|
||||
@@ -0,0 +1 @@
|
||||
1.1.1
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
1.5.0
|
||||
@@ -0,0 +1 @@
|
||||
1.1.0
|
||||
@@ -0,0 +1 @@
|
||||
1.1.0
|
||||
@@ -0,0 +1 @@
|
||||
1.1.1
|
||||
@@ -0,0 +1 @@
|
||||
1.1.0-beta02
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
1.12.0
|
||||
@@ -0,0 +1 @@
|
||||
2.50
|
||||
@@ -0,0 +1 @@
|
||||
2.50
|
||||
@@ -0,0 +1 @@
|
||||
2.50
|
||||
@@ -0,0 +1 @@
|
||||
2.50
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
appMetadataVersion=1.1
|
||||
androidGradlePluginVersion=8.9.0
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user