Esenjin 87eeeafdde ajout du "mode tortue"
pour les instances vraiment trop stricts avec leur api ...
2025-03-21 20:20:12 +01:00

471 lines
16 KiB
PHP

<?php
/**
* FavMasToKey - Fonctions utilitaires
*/
// Empêcher l'accès direct au fichier
if (!defined('FAVMASTOKEY')) {
die('Accès direct interdit');
}
/**
* Valide un fichier JSON de favoris Mastodon
*
* @param string $file_path Chemin vers le fichier JSON
* @return array|bool Tableau contenant les données du fichier ou false en cas d'erreur
*/
function validate_mastodon_json($file_path) {
// Vérifier si le fichier existe
if (!file_exists($file_path)) {
return [
'success' => false,
'message' => 'Le fichier n\'existe pas.'
];
}
// Lire le contenu du fichier
$content = file_get_contents($file_path);
if (!$content) {
return [
'success' => false,
'message' => 'Impossible de lire le contenu du fichier.'
];
}
// Décoder le JSON
$json = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return [
'success' => false,
'message' => 'Le fichier n\'est pas un JSON valide: ' . json_last_error_msg()
];
}
// Vérifier la structure du fichier
if (!isset($json['@context']) || !isset($json['type']) || !isset($json['orderedItems'])) {
return [
'success' => false,
'message' => 'Le format du fichier JSON n\'est pas celui attendu pour un export de favoris Mastodon.'
];
}
// Vérifier que orderedItems est un tableau
if (!is_array($json['orderedItems'])) {
return [
'success' => false,
'message' => 'Le format des favoris dans le fichier est invalide.'
];
}
// Tout est OK
return [
'success' => true,
'data' => $json,
'count' => count($json['orderedItems'])
];
}
/**
* Extrait les identifiants de publications à partir des URLs Mastodon
*
* @param array $urls Tableau d'URLs Mastodon
* @return array Tableau d'identifiants extraits
*/
function extract_toot_ids($urls) {
$ids = [];
foreach ($urls as $url) {
// Format attendu: https://instance.tld/users/username/statuses/id
$parts = explode('/', $url);
// L'ID devrait être le dernier élément après "statuses"
$id = end($parts);
if (is_numeric($id)) {
$ids[] = [
'original_url' => $url,
'toot_id' => $id,
'instance' => parse_url($url, PHP_URL_HOST),
'username' => isset($parts[count($parts) - 3]) ? $parts[count($parts) - 3] : null
];
}
}
return $ids;
}
/**
* Effectue une requête cURL vers l'API Misskey
*
* @param string $instance Instance Misskey (ex: misskey.io)
* @param string $endpoint Point d'accès API (ex: /api/notes/favorites/create)
* @param array $data Données à envoyer
* @param string $token Token d'accès OAuth
* @return array Résultat de la requête
*/
function misskey_api_request($instance, $endpoint, $data, $token) {
global $config;
// Construire l'URL complète
$url = "https://{$instance}{$endpoint}";
// Ajouter le token d'accès aux données
$data['i'] = $token;
// Initialiser cURL
$ch = curl_init();
// Configurer la requête
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'User-Agent: FavMasToKey/' . $config['app_version']
],
CURLOPT_TIMEOUT => $config['timeout'],
CURLOPT_SSL_VERIFYPEER => true
]);
// Exécuter la requête
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
// Fermer la session cURL
curl_close($ch);
// Vérifier les erreurs
if ($error) {
return [
'success' => false,
'message' => 'Erreur cURL: ' . $error,
'http_code' => $http_code,
'error_code' => 'NETWORK_ERROR'
];
}
// Décoder la réponse
$response_data = json_decode($response, true);
// Analyse spécifique des erreurs
if ($http_code == 429) {
return [
'success' => false,
'message' => 'Limite de taux (rate limit) dépassée. Réduisez la fréquence des requêtes ou activez le mode lent.',
'http_code' => $http_code,
'error_code' => 'RATE_LIMIT_EXCEEDED',
'data' => $response_data
];
}
// Vérifier si la requête a réussi
if ($http_code >= 200 && $http_code < 300) {
return [
'success' => true,
'data' => $response_data,
'http_code' => $http_code
];
} else {
// Déterminer le type d'erreur
$error_code = 'API_ERROR';
$error_message = 'Erreur API Misskey';
if (isset($response_data['error'])) {
if (isset($response_data['error']['code'])) {
$error_code = $response_data['error']['code'];
}
if (isset($response_data['error']['message'])) {
$error_message = $response_data['error']['message'];
}
}
return [
'success' => false,
'message' => $error_message,
'http_code' => $http_code,
'error_code' => $error_code,
'data' => $response_data
];
}
}
/**
* Valide un jeton d'accès Misskey en effectuant une requête test
*
* @param string $instance Instance Misskey
* @param string $token Jeton d'accès à valider
* @return array Résultat de la validation
*/
function validate_misskey_token($instance, $token) {
// Test basique de connexion avec un simple ping
$ping_result = misskey_api_request($instance, '/api/ping', [], $token);
if (!$ping_result['success']) {
return $ping_result;
}
// Test de récupération d'informations du compte actuel (permission la plus basique)
$account_test = misskey_api_request($instance, '/api/i', [], $token);
if (!$account_test['success']) {
return [
'success' => false,
'message' => 'Le jeton est valide mais n\'a pas accès aux informations du compte. Assurez-vous d\'avoir accordé la permission "Afficher les informations du compte".',
'data' => $account_test['data'],
'error_code' => 'INSUFFICIENT_PERMISSIONS'
];
}
// Test de validation pour vérifier la permission d'ajouter aux favoris
// On n'a pas besoin d'ajouter un favori réel, juste de vérifier si on peut voir les favoris
$favorites_test = misskey_api_request($instance, '/api/i/favorites', ['limit' => 1], $token);
if (!$favorites_test['success']) {
return [
'success' => false,
'message' => 'Le jeton est valide mais n\'a pas accès aux favoris. Assurez-vous d\'avoir accordé les permissions "Afficher les favoris" et "Gérer les favoris".',
'data' => $favorites_test['data'],
'error_code' => 'INSUFFICIENT_PERMISSIONS'
];
}
// Tout est bon
return [
'success' => true,
'message' => 'Jeton valide avec toutes les permissions nécessaires',
'data' => [
'account' => isset($account_test['data']['username']) ? $account_test['data']['username'] : 'Compte validé',
'ping' => $ping_result['data']
]
];
}
/**
* Vérifie si une note est déjà dans les favoris
*
* @param string $instance Instance Misskey
* @param string $note_id ID de la note à vérifier
* @param string $token Token d'accès
* @return array Résultat de la vérification
*/
function check_if_favorited($instance, $note_id, $token) {
// Endpoint pour récupérer le statut de favori d'une note
$endpoint = '/api/notes/show';
// Données pour la requête
$data = [
'noteId' => $note_id
];
// Effectuer la requête
$result = misskey_api_request($instance, $endpoint, $data, $token);
if ($result['success'] && isset($result['data'])) {
// La note existe et nous avons une réponse
// Vérifier si l'utilisateur a déjà mis cette note en favori
if (isset($result['data']['isFavorited']) && $result['data']['isFavorited'] === true) {
return [
'success' => true,
'is_favorited' => true,
'message' => 'Cette note est déjà dans vos favoris'
];
} else {
return [
'success' => true,
'is_favorited' => false,
'message' => 'Cette note n\'est pas encore dans vos favoris'
];
}
}
// En cas d'erreur ou si la note n'existe pas
return [
'success' => false,
'is_favorited' => false,
'message' => isset($result['message']) ? $result['message'] : 'Impossible de vérifier le statut de favori',
'error_code' => isset($result['error_code']) ? $result['error_code'] : 'UNKNOWN_ERROR'
];
}
/**
* Recherche une note Mastodon sur le réseau fédéré de Misskey
* Utilise l'endpoint ap/show qui s'est avéré le plus fiable avec différentes instances
*
* @param string $instance Instance Misskey
* @param string $url URL de la publication Mastodon
* @param string $token Token d'accès
* @return array Résultat de la recherche
*/
function search_federated_note($instance, $url, $token) {
// Nettoyer l'URL (enlever les éventuels paramètres)
$cleanUrl = strtok($url, '?');
// Journal de débogage
error_log("Recherche fédérée pour: " . $cleanUrl);
// Méthode principale: Utiliser ap/show qui fonctionne avec la plupart des instances
$endpoint = '/api/ap/show';
$data = [
'uri' => $cleanUrl
];
// Effectuer la requête
$result = misskey_api_request($instance, $endpoint, $data, $token);
// Journal pour le format de la réponse
if ($result['success'] && isset($result['data'])) {
error_log("Format de réponse ap/show: " . json_encode(array_keys($result['data'])));
}
// Si la méthode principale a réussi et renvoie un ID, retourner le résultat
if ($result['success'] && isset($result['data'])) {
// Vérifier si l'ID existe directement
if (isset($result['data']['id'])) {
return $result;
}
// Certaines instances peuvent avoir l'ID dans 'note'
if (isset($result['data']['note']) && isset($result['data']['note']['id'])) {
// Remonter l'ID au niveau principal
$result['data']['id'] = $result['data']['note']['id'];
return $result;
}
// Pour les instances plus récentes qui utilisent un format différent
if (!empty($result['data'])) {
// Rechercher un champ qui pourrait contenir l'ID
foreach (['id', 'noteId', 'objectId', 'originalId'] as $possibleIdField) {
if (isset($result['data'][$possibleIdField])) {
$result['data']['id'] = $result['data'][$possibleIdField];
return $result;
}
}
// Si toujours pas d'ID, examiner la structure pour le trouver
foreach ($result['data'] as $key => $value) {
if (is_array($value) && isset($value['id'])) {
$result['data']['id'] = $value['id'];
return $result;
}
}
}
}
// Vérifier si c'est une erreur de rate limiting
if (!$result['success'] && isset($result['error_code']) && $result['error_code'] === 'RATE_LIMIT_EXCEEDED') {
return $result;
}
// Méthode de secours 1: Essayer notes/search-by-url (parfois utilisé dans les anciennes versions)
$fallback_result = misskey_api_request($instance, '/api/notes/search-by-url', ['url' => $cleanUrl], $token);
if ($fallback_result['success'] && isset($fallback_result['data'])) {
error_log("Format de réponse search-by-url: " . json_encode(array_keys($fallback_result['data'])));
// Vérifier si nous avons un résultat avec un ID
if (isset($fallback_result['data']['id'])) {
return $fallback_result;
}
// Si le résultat est un tableau (certaines instances renvoient un tableau)
if (is_array($fallback_result['data']) && !isset($fallback_result['data']['id'])) {
// Chercher le premier élément avec un ID
foreach ($fallback_result['data'] as $item) {
if (is_array($item) && isset($item['id'])) {
$fallback_result['data'] = $item; // Utiliser cet élément comme résultat
return $fallback_result;
}
}
}
}
// Vérifier à nouveau si c'est une erreur de rate limiting
if (!$fallback_result['success'] && isset($fallback_result['error_code']) && $fallback_result['error_code'] === 'RATE_LIMIT_EXCEEDED') {
return $fallback_result;
}
// Méthode de secours 2: Extraction et recherche par ID distant
$urlParts = parse_url($cleanUrl);
if (isset($urlParts['path'])) {
$pathParts = explode('/', trim($urlParts['path'], '/'));
if (count($pathParts) >= 4 && $pathParts[count($pathParts) - 2] === 'statuses') {
$statusId = end($pathParts);
$username = $pathParts[count($pathParts) - 3];
$acctDomain = isset($urlParts['host']) ? $urlParts['host'] : '';
if ($statusId && $username && $acctDomain) {
$remoteId = "https://{$acctDomain}/users/{$username}/statuses/{$statusId}";
// Essayer d'abord avec /api/notes/show
$remote_result = misskey_api_request($instance, '/api/notes/show', ['uri' => $remoteId], $token);
if ($remote_result['success'] && isset($remote_result['data']['id'])) {
return $remote_result;
}
// Vérifier si c'est une erreur de rate limiting
if (!$remote_result['success'] && isset($remote_result['error_code']) && $remote_result['error_code'] === 'RATE_LIMIT_EXCEEDED') {
return $remote_result;
}
// Dernier recours: essayer renotes/search
$renote_result = misskey_api_request($instance, '/api/notes/search', [
'query' => "@{$username}@{$acctDomain} {$statusId}",
'limit' => 10
], $token);
if ($renote_result['success'] && !empty($renote_result['data'])) {
// Parcourir les résultats pour trouver une correspondance
foreach ($renote_result['data'] as $note) {
if (isset($note['id'])) {
$renote_result['data'] = $note;
return $renote_result;
}
}
}
// Vérifier si c'est une erreur de rate limiting
if (!$renote_result['success'] && isset($renote_result['error_code']) && $renote_result['error_code'] === 'RATE_LIMIT_EXCEEDED') {
return $renote_result;
}
}
}
}
// Si aucune méthode n'a fonctionné, retourner une erreur
return [
'success' => false,
'message' => "Impossible de trouver la publication sur le réseau fédéré après plusieurs tentatives",
'error_code' => 'NOT_FOUND',
'http_code' => isset($result['http_code']) ? $result['http_code'] : 404
];
}
/**
* Ajoute une note aux favoris sur Misskey
*
* @param string $instance Instance Misskey
* @param string $note_id ID de la note à ajouter aux favoris
* @param string $token Token d'accès
* @return array Résultat de l'opération
*/
function add_to_favorites($instance, $note_id, $token) {
global $config;
// Endpoint pour ajouter aux favoris
$endpoint = $config['misskey_api_endpoint'];
// Données pour l'ajout aux favoris
$data = [
'noteId' => $note_id
];
// Effectuer la requête
$result = misskey_api_request($instance, $endpoint, $data, $token);
return $result;
}