Librairie Go headless pour suivre en temps réel l'état d'une commande Uber Eats.
🚀 ubertracker est une librairie Go sans interface, sans BDD intégrée, conçue pour être embarquée dans n'importe quel projet. Elle interroge l'API privée d'Uber Eats à intervalle adaptatif et expose les mises à jour via un channel Go.
📦 Ce qu'elle fournit :
- 🔄 Statut temps réel : phase de la commande (
PREPARING,COURIER_ASSIGNED,EN_ROUTE,COMPLETED,CANCELLED) - ⏱️ ETA dynamique : minutes restantes extraites des
backgroundFeedCards - 👤 Infos livreur : nom, prénom, téléphone
- 🔢 Code PIN : code de vérification à la livraison
- ♻️ Reprise automatique : relance les suivis interrompus après redémarrage
- 💉 100% injectable :
OrderStore,FetchFn,SendEmbedFnsont des interfaces/fonctions — zéro dépendance externe imposée
"Branche-toi, reçois les updates, fais ce que tu veux avec."
Uber Eats ne proposant pas d'API publique pour le suivi des commandes, la logique de cette librairie repose sur une analyse minutieuse du trafic réseau (Reverse Engineering) :
- Interception (Charles Proxy) : Utilisation de Charles Proxy avec une configuration SSL Proxying spécifique (installation du certificat racine sur l'appareil) pour déchiffrer le trafic HTTPS en clair entre l'application client et les serveurs d'Uber.
- Isolement de l'Endpoint : Identification de l'endpoint privé responsable du rafraîchissement de la carte et du statut, et extraction du format de la requête (méthode, headers critiques, et structure du payload contenant l'UUID de la commande).
- Tests & Validation : Rejeu des requêtes isolées sur un client API web (type Postman/Hoppscotch) pour tester les limites du serveur. Cela a permis d'identifier les cookies strictement nécessaires, de comprendre la rotation des tokens, et d'isoler les clés JSON vitales (comme les
backgroundFeedCardspour l'ETA) avant d'écrire la moindre ligne de code Go en dur.
go get github.com/superselle/ubertracker@latest// Votre type implémente l'interface tracker.OrderStore
type MyStore struct{ db *sql.DB }
func (s *MyStore) GetSnapshot(ctx context.Context, uuid string) (status string, progress int, text string, rawJSON string, err error) { ... }
func (s *MyStore) SaveOrder(ctx context.Context, order tracker.TrackedOrder) error { ... }
func (s *MyStore) GetMessageID(ctx context.Context, uuid string) (string, error) { ... }
func (s *MyStore) GetPendingOrders(ctx context.Context) (map[string]int, error) { ... }
func (s *MyStore) ListResumableOrders(ctx context.Context) ([]tracker.ResumableOrder, error) { ... }import "github.com/superselle/ubertracker/tracker"
store := &MyStore{db: db}
manager := tracker.NewManager(store)
// Reprendre les commandes actives après redémarrage
manager.ResumeActiveOrders()
// Démarrer le suivi d'une commande
manager.StartTracking(tracker.OrderIdentity{
UUID: "abc-123-uuid",
ChannelID: "1234567890",
GuildID: "9876543210",
ClientID: "user_42",
CuistotID: "user_99",
})
// Consommer les mises à jour dans une goroutine séparée
go func() {
for update := range manager.UpdateChannel {
fmt.Printf("[%s] %s — ETA %d min\n", update.LastStatus, update.LastText, update.ETAMinutes)
// → Envoyer sur Discord, Telegram, Slack, webhook, etc.
}
}()
// Arrêt propre
defer manager.Shutdown()// Par défaut, FetchUberJSON est utilisée (nécessite cookies Uber valides)
// Tu peux injecter ta propre fonction pour les tests ou un proxy custom :
myFetch := func(ctx context.Context, uuid string) ([]byte, error) {
return os.ReadFile("testdata/order_active.json")
}
manager := tracker.NewManager(store, myFetch)ubertracker/
├── tracker/
│ ├── interfaces.go ← OrderStore, ResumableOrder (contrats)
│ ├── models.go ← TrackedOrder, Response, Order, MapEntity…
│ ├── config.go ← GlobalCookies (runtime)
│ ├── api.go ← FetchUberJSON (appel HTTP Uber)
│ ├── browser.go ← GetFreshCookies (chromedp)
│ ├── parser.go ← MergeOrderData, ExtractETAFromOrder, detectPhase
│ ├── worker.go ← Reconcile, StartOrderWorker, AdaptiveInterval
│ ├── manager.go ← Manager (goroutine pool + UpdateChannel)
│ └── testdata/ ← Fixtures JSON (7 scénarios)
└── tests/
├── testutil/ ← Factories, MockStore, MockFetch
└── tracker/ ← 57 tests black box
Flux d'une mise à jour :
cookie Uber
│
▼
┌─────────────┐ JSON parsé ┌─────────────┐ TrackedOrder ┌──────────────────┐
│ FetchFn │ ─────────────► │ Reconcile │ ───────────────► │ UpdateChannel │
│ (HTTP) │ │ (worker) │ │ → consommateur │
└─────────────┘ └─────────────┘ └──────────────────┘
│
ShouldEmit ? (delta)
│
OrderStore.SaveOrder
| Méthode | Description |
|---|---|
NewManager(store, fetchFn?) |
Crée le manager (fetchFn optionnel) |
StartTracking(OrderIdentity) bool |
Démarre un worker. false si déjà actif |
StopTracking(uuid string) |
Arrête un worker spécifique |
ResumeActiveOrders() |
Recharge les commandes non terminées depuis le store |
Shutdown() |
Arrêt propre de tous les workers, ferme UpdateChannel |
UpdateChannel <-chan TrackedOrder |
Canal de réception des mises à jour |
| Champ | Type | Description |
|---|---|---|
LastStatus |
string |
Phase Uber (PREPARING, EN_ROUTE, COMPLETED…) |
LastProgress |
int |
Barre de progression 1-5 |
LastText |
string |
Message lisible ("Le livreur arrive dans 5 min") |
ETAMinutes |
int |
Minutes restantes (-1 = inconnu) |
FullJSONData |
string |
JSON brut complet de la commande |
MessageID |
string |
ID du message Discord associé |
UUID / GuildID / ChannelID |
string |
Identifiants de routage |
result, err := tracker.Reconcile(ctx, store, uuid, resp)
// result.ShouldEmit → true si l'état a changé depuis le snapshot
// result.Phase → phase de la commande
// result.Eta → ETA extrait
// result.FinalJSON → JSON à persister# Tests complets (57 tests, 0 failures)
go test ./... -count=1
# Avec couverture (64.4% sur tracker/)
go test ./tests/tracker/ -count=1 "-coverpkg=github.com/superselle/ubertracker/tracker"
# Race detector
go test ./... -count=1 -raceVoir exemples-utilisation.md pour des intégrations complètes : Discord • Telegram • Slack • IFTTT/Zapier • Home Assistant • Grafana • CLI
MIT — voir LICENSE