<?php /** * FavMasToKey - Traitement des favoris (version améliorée) */ // Définir la constante pour inclure les fichiers define('FAVMASTOKEY', true); // Forcer les en-têtes JSON dès le début pour éviter tout conflit header('Content-Type: application/json'); // Activer la capture d'erreurs set_error_handler(function($errno, $errstr, $errfile, $errline) { echo json_encode([ 'success' => false, 'message' => "Erreur PHP: $errstr (ligne $errline dans $errfile)", ]); exit; }); try { // Inclure les fichiers requis require_once 'includes/config.php'; require_once 'includes/functions.php'; // Vérifier que la requête est en POST if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['success' => false, 'message' => 'Méthode non autorisée']); exit; } // Vérifier que l'utilisateur est authentifié if (!isset($_SESSION['misskey_token']) || empty($_SESSION['misskey_token'])) { http_response_code(401); echo json_encode(['success' => false, 'message' => 'Non authentifié']); exit; } // Récupérer l'instance Misskey $misskey_instance = isset($_SESSION['misskey_instance']) ? $_SESSION['misskey_instance'] : ''; if (empty($misskey_instance)) { http_response_code(400); echo json_encode(['success' => false, 'message' => 'Instance Misskey non définie']); exit; } // Récupérer le token d'accès $token = $_SESSION['misskey_token']; // Récupérer les données envoyées $input_data = file_get_contents('php://input'); $input = json_decode($input_data, true); if (!$input || !isset($input['batch']) || !is_array($input['batch'])) { http_response_code(400); echo json_encode([ 'success' => false, 'message' => 'Données invalides', 'debug' => [ 'received' => $input_data ? substr($input_data, 0, 200) . '...' : 'Aucune donnée reçue' ] ]); exit; } // Journaliser les paramètres reçus $receivedParams = [ 'tortoiseMode' => isset($input['tortoiseMode']) ? $input['tortoiseMode'] : 'non défini', 'slowMode' => isset($input['slowMode']) ? $input['slowMode'] : 'non défini', 'delaySeconds' => isset($input['delaySeconds']) ? $input['delaySeconds'] : 'non défini', 'useCachedIds' => isset($input['useCachedIds']) ? $input['useCachedIds'] : false, 'hasBatchData' => isset($input['batchData']) ? true : false ]; error_log("Paramètres reçus dans process.php: " . json_encode($receivedParams)); // Récupérer le lot à traiter $batch = $input['batch']; $currentIndex = isset($input['currentIndex']) ? (int)$input['currentIndex'] : 0; $totalItems = isset($input['totalItems']) ? (int)$input['totalItems'] : count($batch); $slowMode = isset($input['slowMode']) ? (bool)$input['slowMode'] : false; $tortoiseMode = isset($input['tortoiseMode']) ? (bool)$input['tortoiseMode'] : false; $delaySeconds = isset($input['delaySeconds']) ? (int)$input['delaySeconds'] : 30; $useCachedIds = isset($input['useCachedIds']) ? (bool)$input['useCachedIds'] : false; // AMÉLIORATION 3: Récupérer les données du cache si fournies $batchData = isset($input['batchData']) ? $input['batchData'] : null; error_log("Paramètres après conversion: slowMode=" . ($slowMode ? 'true' : 'false') . ", tortoiseMode=" . ($tortoiseMode ? 'true' : 'false') . ", delaySeconds=" . $delaySeconds . ", useCachedIds=" . ($useCachedIds ? 'true' : 'false')); // Ajuster le délai entre les requêtes selon le mode et la valeur personnalisée if ($tortoiseMode) { $delayMs = $delaySeconds * 1000; // Convertir en millisecondes (délai tortue - 60-300 secondes) error_log("Utilisation du mode tortue avec délai: " . $delayMs . "ms"); } else if ($slowMode) { $delayMs = $delaySeconds * 1000; // Convertir en millisecondes (délai lent - 10-60 secondes) error_log("Utilisation du mode lent avec délai: " . $delayMs . "ms"); } else { $delayMs = $config['delay_between_requests']; // Délai normal (3000ms par défaut) error_log("Utilisation du mode normal avec délai: " . $delayMs . "ms"); } // Résultats du traitement $results = []; // Traiter chaque URL du lot foreach ($batch as $index => $url) { // Extraire les informations de l'URL $urlParts = parse_url($url); // Vérifier que l'URL est valide if (!$urlParts || !isset($urlParts['host']) || !isset($urlParts['path'])) { $results[$index] = [ 'status' => 'error', 'message' => "URL invalide: $url", 'error_type' => 'invalid_url' ]; continue; } // Ajouter un log pour déboguer error_log("Traitement de l'URL: " . $url . " sur l'instance: " . $misskey_instance); // AMÉLIORATION 3: Vérifier si nous avons un ID en cache pour cette URL $cachedMisskeyId = null; if ($useCachedIds && $batchData) { // Vérifier si batchData est un tableau indexé ou associatif if (isset($batchData[$index])) { // Format indexé if ($batchData[$index]['url'] === $url && $batchData[$index]['cached'] === true) { $cachedMisskeyId = $batchData[$index]['cachedId']; error_log("ID trouvé en cache pour $url: $cachedMisskeyId"); } } else { // Format non-indexé, parcourir et chercher l'URL foreach ($batchData as $item) { if (isset($item['url']) && $item['url'] === $url && isset($item['cached']) && $item['cached'] === true && isset($item['cachedId'])) { $cachedMisskeyId = $item['cachedId']; error_log("ID trouvé en cache pour $url: $cachedMisskeyId (recherche)"); break; } } } } // AMÉLIORATION 2: Si nous avons un ID en cache, nous pouvons sauter directement à la vérification du statut de favori $noteId = null; $searchNeeded = true; if ($cachedMisskeyId) { $noteId = $cachedMisskeyId; $searchNeeded = false; error_log("Utilisation de l'ID en cache, recherche fédérée évitée pour: $url"); } // Rechercher la note sur Misskey à partir de l'URL si nécessaire if ($searchNeeded) { $searchResult = search_federated_note($misskey_instance, $url, $token); // Vérifier si la recherche a réussi ET si les données contiennent un ID valide if ($searchResult['success'] && isset($searchResult['data']) && !empty($searchResult['data'])) { // Vérifier et extraire l'ID de manière sécurisée if (isset($searchResult['data']['id']) && !empty($searchResult['data']['id'])) { $noteId = $searchResult['data']['id']; // AMÉLIORATION 3: Sauvegarder le résultat dans la réponse pour mise à jour du cache côté client $results[$index]['misskey_id'] = $noteId; } else { // L'ID est manquant ou vide dans la réponse $dataKeys = is_array($searchResult['data']) ? array_keys($searchResult['data']) : ['non_array']; $results[$index] = [ 'status' => 'error', 'message' => "Note trouvée mais sans ID valide: $url", 'error_type' => 'invalid_response', 'details' => "Clés disponibles: " . implode(', ', $dataKeys) ]; // Log pour déboguer error_log("Structure de données reçue: " . json_encode($searchResult['data'])); continue; } } else { // Note non trouvée ou erreur de recherche $errorMessage = isset($searchResult['message']) ? $searchResult['message'] : "Publication introuvable"; $errorCode = isset($searchResult['error_code']) ? $searchResult['error_code'] : ''; $httpCode = isset($searchResult['http_code']) ? $searchResult['http_code'] : ''; // Déterminer le type d'erreur $errorType = 'not_found'; if (strpos($errorMessage, 'rate limit') !== false || $httpCode == 429 || (isset($searchResult['data']['error']) && (isset($searchResult['data']['error']['code']) && $searchResult['data']['error']['code'] == 'RATE_LIMIT_EXCEEDED'))) { $errorType = 'rate_limit'; if ($tortoiseMode) { $errorMessage = "Limite d'API atteinte malgré le mode tortue. Essayez d'augmenter le délai ou attendez quelques minutes."; } else if ($slowMode) { $errorMessage = "Limite d'API atteinte. Activez le mode tortue ou attendez quelques minutes."; } else { $errorMessage = "Limite d'API atteinte. Activez le mode ultra-lent ou le mode tortue."; } } elseif ($httpCode >= 500) { $errorType = 'server_error'; $errorMessage = "L'instance Misskey rencontre des problèmes. Statut HTTP: $httpCode"; } elseif ($httpCode == 401 || $httpCode == 403) { $errorType = 'auth_error'; $errorMessage = "Problème d'authentification ou de permission. Vérifiez votre token."; } elseif ($httpCode == 0) { $errorType = 'network_error'; $errorMessage = "Problème de connexion réseau. Vérifiez votre connectivité."; } $results[$index] = [ 'status' => $errorType == 'rate_limit' ? 'warning' : 'error', 'message' => "Publication non trouvée sur le réseau fédéré: $url ($errorMessage)", 'error_type' => $errorType, 'details' => $httpCode ? "HTTP: $httpCode" : "" ]; // Ajouter des infos de debug if (isset($searchResult['data']) && is_array($searchResult['data'])) { error_log("Données reçues pour URL $url: " . json_encode($searchResult['data'])); } continue; } } // AMÉLIORATION 2: Vérifier d'abord si la note est déjà favorite pour économiser une requête API $favoriteCheckResult = check_if_favorited($misskey_instance, $noteId, $token); if ($favoriteCheckResult['success'] && $favoriteCheckResult['is_favorited']) { // La note est déjà dans les favoris $results[$index] = [ 'status' => 'info', 'message' => "Déjà dans vos favoris: $url", 'error_type' => 'already_favorited', 'misskey_id' => $noteId // AMÉLIORATION 3: Inclure l'ID même si déjà favori ]; // AMÉLIORATION 2: Ne pas attendre le délai complet pour les notes déjà favorites error_log("Note déjà favorite, aucun délai nécessaire: $url"); continue; } else { // La note n'est pas encore dans les favoris ou la vérification a échoué // Dans le cas d'échec, nous essayons quand même d'ajouter la note $favoriteResult = add_to_favorites($misskey_instance, $noteId, $token); if ($favoriteResult['success']) { $results[$index] = [ 'status' => 'success', 'message' => "Ajouté aux favoris: $url", 'misskey_id' => $noteId // AMÉLIORATION 3: Inclure l'ID pour mise à jour du cache ]; } else { // Vérifier si c'est une erreur de "déjà ajouté aux favoris" $errorMessage = isset($favoriteResult['data']['error']['message']) ? $favoriteResult['data']['error']['message'] : (isset($favoriteResult['message']) ? $favoriteResult['message'] : 'Erreur inconnue'); // Extraire le code d'erreur si disponible $errorCode = isset($favoriteResult['data']['error']['code']) ? $favoriteResult['data']['error']['code'] : ''; // Déterminer le type d'erreur $errorType = 'api_error'; if (strpos($errorMessage, 'already') !== false) { $errorType = 'already_favorited'; } elseif (strpos($errorMessage, 'rate limit') !== false || strpos($errorMessage, 'limit exceeded') !== false || $errorCode == 'RATE_LIMIT_EXCEEDED') { $errorType = 'rate_limit'; } elseif (strpos($errorMessage, 'permission') !== false || $errorCode == 'NO_PERMISSION') { $errorType = 'permission_denied'; } if ($errorType === 'already_favorited') { $results[$index] = [ 'status' => 'info', 'message' => "Déjà dans vos favoris: $url", 'error_type' => $errorType, 'misskey_id' => $noteId // AMÉLIORATION 3: Inclure l'ID même si déjà favori ]; } else if ($errorType === 'rate_limit') { $message = "Limite d'API atteinte pour: $url. "; if ($tortoiseMode) { $message .= "Essayez d'augmenter le délai ou attendez quelques minutes."; } else if ($slowMode) { $message .= "Activez le mode tortue."; } else { $message .= "Activez le mode ultra-lent ou le mode tortue."; } $results[$index] = [ 'status' => 'warning', 'message' => $message, 'error_type' => $errorType, 'details' => $errorMessage, 'misskey_id' => $noteId // AMÉLIORATION 3: Inclure l'ID pour mise à jour du cache ]; } else { $results[$index] = [ 'status' => 'error', 'message' => "Erreur lors de l'ajout aux favoris: $errorMessage", 'error_type' => $errorType, 'details' => $errorCode ? "Code: $errorCode" : "", 'misskey_id' => $noteId // AMÉLIORATION 3: Inclure l'ID pour mise à jour du cache ]; } } } // AMÉLIORATION 2: Pas besoin de pause pour les notes déjà dans les favoris ou si nous avons utilisé l'ID en cache // Nous vérifions si l'opération actuelle nécessite un délai $requiresDelay = true; // Pas de délai nécessaire si c'est déjà en favoris if ($results[$index]['status'] === 'info' && $results[$index]['error_type'] === 'already_favorited') { $requiresDelay = false; } // Pas de délai nécessaire si nous avons utilisé un ID en cache et l'opération a réussi if ($cachedMisskeyId && ($results[$index]['status'] === 'success' || ($results[$index]['status'] === 'info' && $results[$index]['error_type'] === 'already_favorited'))) { $requiresDelay = false; } // Pour les notes qui requièrent vraiment un délai (opérations lourdes d'API) if ($requiresDelay) { // En mode tortue ou lent, appliquer un délai réduit pour les notes où nous avons fait moins de requêtes API if (($tortoiseMode || $slowMode) && !$searchNeeded) { // Si nous avons sauté la recherche fédérée, utiliser seulement 10% du délai complet $reducedDelay = floor($delayMs * 0.1); error_log("Pause réduite de {$reducedDelay}ms pour note avec ID connu: $url"); usleep($reducedDelay * 1000); } else { // Délai complet pour les requêtes complètes qui nécessitent une recherche fédérée error_log("Pause complète de {$delayMs}ms après traitement complet de: $url"); usleep($delayMs * 1000); } } else { error_log("Aucune pause nécessaire pour la note: $url"); } } // AMÉLIORATION 2: Vérifier si toutes les notes sont déjà favorites $allAlreadyFavorited = true; foreach ($results as $result) { if (!($result['status'] === 'info' && $result['error_type'] === 'already_favorited')) { $allAlreadyFavorited = false; break; } } // Si au moins une erreur de rate limit a été détectée, suggérer le mode approprié $hasRateLimitErrors = false; foreach ($results as $result) { if (isset($result['error_type']) && $result['error_type'] === 'rate_limit') { $hasRateLimitErrors = true; break; } } // Déterminer les suggestions à faire à l'utilisateur $suggestions = [ 'use_slow_mode' => $hasRateLimitErrors && !$slowMode && !$tortoiseMode, 'use_tortoise_mode' => $hasRateLimitErrors && $slowMode && !$tortoiseMode, 'increase_delay' => $hasRateLimitErrors && ($slowMode || $tortoiseMode) && $delaySeconds < ($tortoiseMode ? 300 : 60), 'all_already_favorited' => $allAlreadyFavorited // AMÉLIORATION 2: Indiquer si toutes les notes sont déjà favorites ]; // Renvoyer les résultats echo json_encode([ 'success' => true, 'results' => $results, 'progress' => [ 'current' => $currentIndex + count($batch), 'total' => $totalItems, 'percentage' => round((($currentIndex + count($batch)) / $totalItems) * 100, 2) ], 'suggestions' => $suggestions, 'config' => [ 'slow_mode' => $slowMode, 'tortoise_mode' => $tortoiseMode, 'delay_seconds' => $delaySeconds, 'all_already_favorited' => $allAlreadyFavorited // AMÉLIORATION 2: Indiquer dans la config si tout est déjà traité ] ]); } catch (Exception $e) { // Capturer toutes les exceptions et renvoyer un message d'erreur formaté en JSON echo json_encode([ 'success' => false, 'message' => 'Exception: ' . $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine() ]); }