424 lines
15 KiB
PHP
424 lines
15 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']
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
} |