API REST para automação de login e extração de dados de voo do DJI AG SmartFarm.
A API usa X-API-KEY para autenticação. Todos os endpoints (exceto /health) requerem o header:
X-API-KEY: sua_chave_secreta
O DJI AG usa WebAssembly para gerar assinaturas de requisição, impossibilitando requisições HTTP diretas. Esta API usa Playwright para automação de browser com contexto persistente.
- ✅ Login automático no DJI Account via Playwright
- ✅ Sessão persistente (mantém login entre execuções)
- ✅ Listagem de records de voo
- ✅ Detalhes de record individual
- ✅ Extração de dados GPS/telemetria
- ✅ Exportação GeoJSON
- ✅ Anti-detecção de automação
- ✅ Pronto para Docker/VPS
- Python 3.10+
- FastAPI - Framework web REST
- Playwright - Automação de browser
- Pydantic - Validação de dados
- Uvicorn - Servidor ASGI
O DJI AG SmartFarm usa WebAssembly (WASM) para gerar assinaturas criptográficas nas requisições. Cada chamada à API do DJI requer um x-signature gerado dinamicamente pelo código WASM rodando no browser. Isso torna impossível fazer requisições HTTP diretas, pois:
- O algoritmo de assinatura está ofuscado dentro do binário WASM
- A assinatura depende de estado interno do browser (cookies, timestamps, etc.)
- Tentativas de engenharia reversa violariam termos de uso
Solução: Usar Playwright para automatizar um browser real que executa o WASM normalmente.
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ API REST │────▶│ Playwright │────▶│ DJI SmartFarm │
│ (FastAPI) │ │ (Chromium) │ │ (WASM + Auth) │
└─────────────┘ └──────────────────┘ └─────────────────┘
│ │ │
│ POST /auth/login │ │
│───────────────────▶│ Navega para │
│ │ djiag.com/records │
│ │───────────────────────▶│
│ │ │
│ │ Redirect p/ login │
│ │◀───────────────────────│
│ │ │
│ │ Preenche email/senha │
│ │───────────────────────▶│
│ │ │
│ │ Cookies de sessão │
│ │◀───────────────────────│
│ { success: true }│ │
│◀───────────────────│ │
O diretório browser_profile/ armazena o contexto persistente do Chromium:
browser_profile/
├── Default/
│ ├── Cookies # Cookies de sessão DJI
│ ├── Local Storage/ # Tokens e dados locais
│ ├── Session Storage/ # Dados de sessão
│ ├── Login Data # Credenciais salvas (criptografadas)
│ └── Preferences # Configurações do browser
├── Local State # Estado geral do Chromium
└── ...
Vantagens do contexto persistente:
- ✅ Mantém sessão entre reinicializações da API
- ✅ Evita login repetido (cookies válidos são reutilizados)
- ✅ Preserva configurações anti-CAPTCHA
- ✅ Reduz suspeita de automação (browser "tem histórico")
Importante para deploy:
- O
browser_profile/local (Windows/Mac) pode ser copiado para o servidor - Evita necessidade de resolver CAPTCHA em ambiente headless
- Deve ser tratado como dado sensível (contém cookies de autenticação)
Se o servidor apresentar CAPTCHA no login (comum em IPs novos):
# 1. No Windows, compactar o profile autenticado
Compress-Archive -Path "browser_profile\*" -DestinationPath "browser_profile.zip"
# 2. Enviar para o servidor
scp browser_profile.zip user@servidor:/opt/djiag-api/
# 3. No servidor, extrair para o volume Docker
docker compose down
unzip browser_profile.zip -d /var/lib/docker/volumes/djiag-api_djiag-browser/_data/
docker compose up -dO serviço inclui técnicas para evitar detecção como bot:
# Remove flag de automação do navigator
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
# Argumentos do Chromium
--disable-blink-features=AutomationControlled
--ignore-default-args=['--enable-automation']Playwright requer que todas as operações sejam executadas na mesma thread onde o browser foi inicializado. A API usa uma arquitetura especial:
┌─────────────────┐ ┌─────────────────────┐
│ FastAPI │ │ PlaywrightThread │
│ (async/await) │────▶│ (thread dedicada) │
│ │ │ │
│ - Recebe HTTP │ │ - Controla browser │
│ - Valida API │ │ - Executa ações │
│ - Retorna JSON │ │ - Mantém contexto │
└─────────────────┘ └─────────────────────┘
Isso garante:
- Compatibilidade com FastAPI assíncrono
- Estabilidade do browser (sem race conditions)
- Reutilização da mesma instância do browser
git clone <seu-repositorio>
cd djiag-api
# Criar ambiente virtual
python -m venv venv
# Windows
venv\Scripts\activate
# Linux/Mac
source venv/bin/activate
# Instalar dependências
pip install -r requirements.txt
# Instalar browser do Playwright
playwright install chromiumcp .env.example .envEdite o .env:
# Credenciais DJI (obrigatório)
DJI_USERNAME=seu_email@exemplo.com
DJI_PASSWORD=sua_senha
# Segurança API (obrigatório)
API_KEY=sua_chave_secreta
# Configurações
API_HOST=0.0.0.0
API_PORT=8000
API_PREFIX=/api
BROWSER_HEADLESS=falsepython -m src.main# Copiar e configurar .env
cp .env.example .env
nano .env
# Iniciar
docker compose up -d --build
# Ver logs
docker compose logs -fVeja DEPLOY.md para instruções completas de deploy em VPS.
| Método | Endpoint | Auth | Descrição |
|---|---|---|---|
| GET | /api/health |
❌ | Health check |
| POST | /api/auth/login |
✅ | Login no DJI AG |
| GET | /api/auth/status |
✅ | Status da autenticação |
| GET | /api/records |
✅ | Listar records |
| GET | /api/records/{id} |
✅ | Detalhes de um record |
| GET | /api/records/{id}/flight-data |
✅ | Dados de voo (GPS/telemetria) |
| GET | /api/records/{id}/geojson |
✅ | GeoJSON (resposta JSON) |
| GET | /api/records/{id}/geojson/download |
✅ | GeoJSON (download arquivo) |
Swagger UI: http://localhost:8000/api/docs
# Health check (sem autenticação)
curl http://localhost:8000/api/health
# Login
curl -X POST http://localhost:8000/api/auth/login \
-H "X-API-KEY: sua_api_key"
# Listar records
curl http://localhost:8000/api/records \
-H "X-API-KEY: sua_api_key"
# Obter GeoJSON
curl http://localhost:8000/api/records/ABC123/geojson \
-H "X-API-KEY: sua_api_key"
# Download GeoJSON como arquivo
curl -O http://localhost:8000/api/records/ABC123/geojson/download \
-H "X-API-KEY: sua_api_key"import requests
BASE_URL = "http://localhost:8000/api"
HEADERS = {"X-API-KEY": "sua_api_key"}
# Login
response = requests.post(f"{BASE_URL}/auth/login", headers=HEADERS)
print(response.json())
# Listar records
response = requests.get(f"{BASE_URL}/records", headers=HEADERS)
records = response.json()
# Obter GeoJSON de um record
record_id = records["items"][0]["id"]
response = requests.get(f"{BASE_URL}/records/{record_id}/geojson", headers=HEADERS)
geojson = response.json()$headers = @{ "X-API-KEY" = "sua_api_key" }
# Login
Invoke-RestMethod -Uri "http://localhost:8000/api/auth/login" -Method POST -Headers $headers
# Listar records
Invoke-RestMethod -Uri "http://localhost:8000/api/records" -Headers $headersdjiag-api/
├── src/
│ ├── application/ # Casos de uso
│ ├── domain/ # Entidades e interfaces
│ ├── infrastructure/ # Implementações (browser, config)
│ │ ├── config/
│ │ ├── repositories/
│ │ └── services/
│ ├── presentation/ # API (rotas, dependencies)
│ │ └── routes/
│ └── main.py
├── prototipo/ # Scripts de desenvolvimento
├── downloads/ # Downloads salvos
├── browser_profile/ # Sessão persistente do browser
├── docker-compose.yml
├── Dockerfile
├── requirements.txt
├── .env.example
├── DEPLOY.md
└── README.md
- Verifique credenciais no
.env - Se aparecer CAPTCHA, complete manualmente (browser abrirá)
- Configure
BROWSER_HEADLESS=falsepara ver o browser
- Use o endpoint
/geojson/downloadpara arquivos grandes - O download retorna arquivo ao invés de renderizar no Swagger
- Verifique se
shm_size: 2gbestá no docker-compose - Playwright precisa de memória compartilhada
- Verifique se Playwright está instalado:
playwright install chromium - No Docker, sempre use
BROWSER_HEADLESS=true
Este projeto é para uso pessoal e educacional. Respeite os Termos de Serviço do DJI.