#include "lichessboard.h" LichessBoard::LichessBoard() { streaming = false; lastHeartbeat = 0; onMoveReceived = nullptr; onGameStateChanged = nullptr; } void LichessBoard::setToken(String token) { apiToken = token; } void LichessBoard::setMoveCallback(MoveCallback callback) { onMoveReceived = callback; } void LichessBoard::setGameStateCallback(GameStateCallback callback) { onGameStateChanged = callback; } bool LichessBoard::streamGame(String gameId) { if (streaming) { stop(); } currentGameId = gameId; client.setInsecure(); // Pour HTTPS sans certificat Serial.println("Connexion à Lichess..."); if (!client.connect("lichess.org", 443)) { Serial.println("Échec connexion"); return false; } // Requête HTTP pour le stream String request = "GET /api/board/game/stream/" + gameId + " HTTP/1.1\r\n"; request += "Host: lichess.org\r\n"; if (apiToken.length() > 0) { request += "Authorization: Bearer " + apiToken + "\r\n"; } request += "Accept: application/x-ndjson\r\n"; request += "Connection: keep-alive\r\n"; request += "\r\n"; client.print(request); Serial.println("Requête envoyée, attente réponse..."); // Attendre les headers HTTP unsigned long timeout = millis(); while (client.connected() && !client.available()) { if (millis() - timeout > 5000) { Serial.println("Timeout headers"); client.stop(); return false; } delay(10); } // Lire les headers bool headersEnded = false; while (client.available() && !headersEnded) { String line = client.readStringUntil('\n'); if (line == "\r" || line.length() == 0) { headersEnded = true; } Serial.println("Header: " + line); } streaming = true; lastHeartbeat = millis(); Serial.println("Stream démarré!"); return true; } bool LichessBoard::streamMyGames() { if (apiToken.length() == 0) { Serial.println("Token requis pour streamMyGames"); return false; } if (streaming) { stop(); } client.setInsecure(); if (!client.connect("lichess.org", 443)) { Serial.println("Échec connexion"); return false; } String request = "GET /api/board/game/stream HTTP/1.1\r\n"; request += "Host: lichess.org\r\n"; request += "Authorization: Bearer " + apiToken + "\r\n"; request += "Accept: application/x-ndjson\r\n"; request += "Connection: keep-alive\r\n"; request += "\r\n"; client.print(request); // Skip headers while (client.connected()) { String line = client.readStringUntil('\n'); if (line == "\r" || line.length() == 0) break; } streaming = true; lastHeartbeat = millis(); return true; } bool LichessBoard::streamEvents() { if (apiToken.length() == 0) { Serial.println("Token requis"); return false; } if (streaming) { stop(); } client.setInsecure(); if (!client.connect("lichess.org", 443)) { Serial.println("Échec connexion"); return false; } String request = "GET /api/stream/event HTTP/1.1\r\n"; request += "Host: lichess.org\r\n"; request += "Authorization: Bearer " + apiToken + "\r\n"; request += "Accept: application/x-ndjson\r\n"; request += "Connection: keep-alive\r\n"; request += "\r\n"; client.print(request); // Skip headers while (client.connected()) { String line = client.readStringUntil('\n'); if (line == "\r" || line.length() == 0) break; } streaming = true; lastHeartbeat = millis(); return true; } void LichessBoard::parseLine(String line) { line.trim(); if (line.length() == 0) { // Heartbeat (ligne vide) lastHeartbeat = millis(); return; } Serial.println("JSON reçu: " + line); StaticJsonDocument<256> doc; DeserializationError error = deserializeJson(doc, line); if (error) { Serial.print("Erreur JSON: "); Serial.println(error.c_str()); return; } // Type d'événement const char *type = doc["type"]; if (strcmp(type, "gameFull") == 0) { // État complet de la partie au début Serial.println("=== Partie complète reçue ==="); const char *gameId = doc["id"]; const char *state = doc["state"]["status"]; Serial.print("Game ID: "); Serial.println(gameId); Serial.print("État: "); Serial.println(state); if (onGameStateChanged) { onGameStateChanged(String(state)); } // Récupérer tous les coups déjà joués const char *moves = doc["state"]["moves"]; if (moves && strlen(moves) > 0) { Serial.print("Coups joués: "); Serial.println(moves); } // FEN initial si disponible const char *initialFen = doc["initialFen"]; if (initialFen) { Serial.print("FEN: "); Serial.println(initialFen); } } else if (strcmp(type, "gameState") == 0) { // Mise à jour de l'état (nouveau coup) Serial.println("=== Nouveau coup ==="); const char *moves = doc["moves"]; const char *status = doc["status"]; Serial.print("Tous les coups: "); Serial.println(moves); Serial.print("État: "); Serial.println(status); if (onGameStateChanged) { onGameStateChanged(String(status)); } // Extraire le dernier coup if (moves && strlen(moves) > 0) { String allMoves = String(moves); int lastSpace = allMoves.lastIndexOf(' '); String lastMove = (lastSpace >= 0) ? allMoves.substring(lastSpace + 1) : allMoves; // Format UCI: e2e4 if (lastMove.length() >= 4) { String from = lastMove.substring(0, 2); String to = lastMove.substring(2, 4); Serial.print("Dernier coup: "); Serial.print(from); Serial.print(" -> "); Serial.println(to); if (onMoveReceived) { onMoveReceived(from, to, lastMove, ""); } } } } else if (strcmp(type, "gameStart") == 0) { // Nouvelle partie démarrée JsonObject game = doc["game"]; const char *gameId = game["id"]; Serial.print("Nouvelle partie: "); Serial.println(gameId); if (onGameStateChanged) { onGameStateChanged("started"); } } else if (strcmp(type, "gameFinish") == 0) { // Partie terminée JsonObject game = doc["game"]; const char *gameId = game["id"]; Serial.print("Partie terminée: "); Serial.println(gameId); if (onGameStateChanged) { onGameStateChanged("finished"); } } } void LichessBoard::loop() { if (!streaming) { return; } // Vérifier timeout if (millis() - lastHeartbeat > heartbeatTimeout) { Serial.println("Timeout stream!"); stop(); return; } // Lire les données disponibles while (client.available()) { String line = client.readStringUntil('\n'); parseLine(line); } // Vérifier connexion if (!client.connected()) { Serial.println("Connexion perdue"); stop(); } } void LichessBoard::stop() { if (client.connected()) { client.stop(); } streaming = false; currentGameId = ""; Serial.println("Stream arrêté"); } bool LichessBoard::isStreaming() { return streaming; } String LichessBoard::getCurrentGameId() { return currentGameId; } bool LichessBoard::getGameState(String gameId, String &fen, String &lastMove) { WiFiClientSecure httpClient; httpClient.setInsecure(); if (!httpClient.connect("lichess.org", 443)) { return false; } String request = "GET /game/export/" + gameId + "?moves=true&clocks=false HTTP/1.1\r\n"; request += "Host: lichess.org\r\n"; request += "Accept: application/json\r\n"; request += "Connection: close\r\n"; request += "\r\n"; httpClient.print(request); // Skip headers while (httpClient.connected()) { String line = httpClient.readStringUntil('\n'); if (line == "\r") break; } // Lire JSON String json = httpClient.readString(); httpClient.stop(); StaticJsonDocument<4096> doc; DeserializationError error = deserializeJson(doc, json); if (error) { return false; } const char *moves = doc["moves"]; if (moves) { lastMove = String(moves); // Extraire le dernier coup int lastSpace = lastMove.lastIndexOf(' '); if (lastSpace >= 0) { lastMove = lastMove.substring(lastSpace + 1); } } return true; }