/*---------------------------------------------------------------*/
|
/*
|
Titre : Ajouter des Tweets avec OAuth 2.0 avec rotation des tokens
|
|
URL : https://phpsources.net/code_s.php?id=1195
|
Auteur : KOogar
|
Date édition : 01 Mars 2026
|
Date mise a jour : 01 Mars 2026
|
|
Rapport de la maj:
|
- fonctionnement du code vérifié
|
*/
|
/*---------------------------------------------------------------*/?>
|
login.php (c'est le fichier auth.php pour twitter)
|
|
<?php
|
session_start();
|
|
// Génère un state aléatoire et sécurisé (anti-CSRF)
|
$state = bin2hex(random_bytes(16));
|
// ou openssl_random_pseudo_bytes(16) en hex
|
$_SESSION['oauth_state'] = $state;
|
|
// Optionnel : stocke aussi le code_verifier si tu génères PKCE ici
|
$code_verifier = bin2hex(random_bytes(32));
|
$code_challenge = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode(
|
hash('sha256', $code_verifier, true)));
|
$_SESSION['oauth_code_verifier'] = $code_verifier;
|
|
$params = [
|
'response_type' => 'code',
|
'client_id' => 'votre ic client',
|
'redirect_uri' => 'https://votre_site/confirmation.php',
|
'scope' =>
|
'users.read tweet.read tweet.write offline.access media.write',
|
'state' => $state,
|
'code_challenge' => $code_challenge,
|
'code_challenge_method' => 'S256'
|
];
|
|
$auth_url = 'https://x.com/i/oauth2/authorize?' . http_build_query($params);
|
|
echo "<a href='$auth_url'>Connecter avec X (Twitter)</a>";
|
?>
|
|
confirmation.php (c'est le fichier callback.php pour twitter)
|
|
<?php
|
session_start();
|
|
// Récupère les params de la redirection
|
$code = $_GET['code'] ?? null;
|
$received_state = $_GET['state'] ?? null;
|
|
if (!$code || !$received_state) {
|
die("Paramètres manquants dans la redirection.");
|
}
|
|
// Vérification CSRF stricte
|
if (!isset($_SESSION['oauth_state']) || !hash_equals($_SESSION['oauth_state'],
|
$received_state)) {
|
unset($_SESSION['oauth_state']); // Nettoie pour sécurité
|
die("Erreur CSRF : state ne correspond pas. Relance le flux.");
|
}
|
|
// State OK ? nettoie la session
|
unset($_SESSION['oauth_state']);
|
|
// Maintenant échange le code contre tokens
|
$client_id = 'votre id client';
|
$client_secret = 'votre code secret';
|
$redirect_uri = 'https://votre_site/confirmation.php';
|
|
$code_verifier = $_SESSION['oauth_code_verifier'] ?? ''; // Récup si stocké
|
unset($_SESSION['oauth_code_verifier']);
|
|
$post_data = http_build_query([
|
'code' => $code,
|
'grant_type' => 'authorization_code',
|
'client_id' => $client_id,
|
'redirect_uri' => $redirect_uri,
|
'code_verifier' => $code_verifier
|
]);
|
|
$ch = curl_init('https://api.x.com/2/oauth2/token');
|
curl_setopt_array($ch, [
|
CURLOPT_POST => true,
|
CURLOPT_RETURNTRANSFER => true,
|
CURLOPT_HTTPHEADER => [
|
'Content-Type: application/x-www-form-urlencoded',
|
'Authorization: Basic ' . base64_encode($client_id . ':' .
|
$client_secret)
|
],
|
CURLOPT_POSTFIELDS => $post_data,
|
]);
|
|
$response = curl_exec($ch);
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
curl_close($ch);
|
|
if ($http_code !== 200) {
|
echo "Échec échange code ? HTTP $http_code<br><pre>" . htmlspecialchars(
|
$response) . "</pre>";
|
exit;
|
}
|
|
$tokens = json_decode($response, true);
|
if (empty($tokens['access_token'])) {
|
die("Pas de token reçu.");
|
}
|
|
// Sauvegarde tes tokens (comme dans ton code original)
|
// Ex: saveTokens($tokens['access_token'], $tokens['refresh_token']);
|
|
echo "Succès ! Access token obtenu.<br>";
|
echo "<pre>" . print_r($tokens, true) . "</pre>";
|
// Redirige vers ton script principal ou affiche un message
|
?>
|
|
ajouter_tweet.php
|
|
<?php
|
session_start();
|
// Force l'encodage UTF-8 partout
|
header('Content-Type: text/html; charset=UTF-8');
|
mb_internal_encoding('UTF-8');
|
mb_http_output('UTF-8');
|
|
// ------------------------------------------------
|
// Fonctions utilitaires
|
// ------------------------------------------------
|
/**
|
* Rafraîchit l'access_token à partir du refresh_token
|
* @return string|false Nouveau access_token ou false en cas d'échec
|
*/
|
function refreshToken($refresh_token, $client_id, $client_secret) {
|
$ch = curl_init('https://api.x.com/2/oauth2/token');
|
curl_setopt_array($ch, [
|
CURLOPT_POST => true,
|
CURLOPT_RETURNTRANSFER => true,
|
CURLOPT_HTTPHEADER => [
|
'Content-Type: application/x-www-form-urlencoded',
|
'Authorization: Basic ' . base64_encode($client_id . ':' .
|
$client_secret)
|
],
|
CURLOPT_POSTFIELDS => http_build_query([
|
'refresh_token' => $refresh_token,
|
'grant_type' => 'refresh_token'
|
]),
|
CURLOPT_TIMEOUT => 30,
|
]);
|
$response = curl_exec($ch);
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
curl_close($ch);
|
if ($http_code !== 200) {
|
echo "Refresh échoué : $http_code - " . htmlspecialchars($response) .
|
"\n";
|
return false;
|
}
|
$data = json_decode($response, true);
|
if (!$data || !isset($data['access_token'])) {
|
echo "Réponse refresh invalide.\n";
|
return false;
|
}
|
// Sauvegarde les nouveaux tokens en session
|
$_SESSION['access_token'] = $data['access_token'];
|
if (isset($data['refresh_token'])) {
|
$_SESSION['refresh_token'] = $data['refresh_token'];
|
// IMPORTANT : rotation du refresh_token
|
echo "<pre>NOUVEAU refresh_token reçu et stocké en session : " .
|
substr($data['refresh_token'], 0, 30) . "...</pre>\n";
|
}
|
return $data['access_token'];
|
}
|
|
/**
|
* Sauvegarde les tokens dans un fichier JSON sécurisé
|
*/
|
function saveTokens($access_token, $refresh_token) {
|
$data = [
|
'access_token' => $access_token,
|
'refresh_token' => $refresh_token,
|
'updated_at' => time()
|
];
|
$file = __DIR__ . '/secure_tokens.json';
|
file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT));
|
chmod($file, 0600); // Protège le fichier
|
echo "<pre>Tokens sauvegardés dans secure_tokens.json (updated_at = " .
|
time() . ")</pre>\n";
|
}
|
|
/**
|
* Charge les tokens depuis le fichier JSON
|
*/
|
function loadTokens() {
|
$file = __DIR__ . '/secure_tokens.json';
|
echo "<pre>Chemin testé pour tokens : " . htmlspecialchars($file) .
|
"</pre>\n";
|
if (file_exists($file)) {
|
echo "<pre>Fichier existe !</pre>\n";
|
$content = file_get_contents($file);
|
echo "<pre>Contenu brut : " . htmlspecialchars($content) . "</pre>\n";
|
return json_decode($content, true);
|
} else {
|
echo "<pre>Fichier n'existe PAS ou inaccessible.</pre>\n";
|
}
|
return null;
|
}
|
|
/**
|
* Nettoie une chaîne pour éviter les problèmes d'UTF-8 malformé
|
*/
|
function fix_encoding($text) {
|
if (mb_check_encoding($text, 'UTF-8')) {
|
return $text;
|
}
|
// Tentative de reconversion depuis Windows-1252 (français courant)
|
return iconv('Windows-1252', 'UTF-8//IGNORE', $text);
|
}
|
|
// ------------------------------------------------
|
// Configuration
|
// ------------------------------------------------
|
$client_id = 'votre id';
|
$client_secret = 'votre id secret';
|
|
// Charge les tokens persistants (priorité au fichier)
|
$tokens = loadTokens();
|
$access_token = $tokens['access_token'] ?? $_SESSION['access_token'] ?? '';
|
$refresh_token = $tokens['refresh_token'] ?? $_SESSION['refresh_token'] ?? '';
|
|
echo "<pre>";
|
echo "Access token chargé : " . (empty($access_token) ? 'VIDE' : 'présent') .
|
"\n";
|
echo "Refresh token chargé : " . (empty($refresh_token) ? 'VIDE' : 'présent')
|
. "\n";
|
echo "Updated at : " . ($tokens['updated_at'] ?? 'non présent') . "\n";
|
echo "Temps écoulé depuis update : " . (time() - ($tokens['updated_at'] ?? 0))
|
. " secondes\n";
|
echo "</pre>";
|
|
// Debug refresh token avant utilisation
|
if (!empty($refresh_token)) {
|
echo "<pre>DEBUG - Refresh token utilisé pour refresh : " . substr(
|
$refresh_token, 0, 30) . "... (longueur totale : " . strlen($refresh_token) .
|
")</pre>\n";
|
}
|
|
// Si pas de token valide ou ancien ? rafraîchir
|
if (empty($access_token) || (isset($tokens['updated_at']) && time() - $tokens[
|
'updated_at'] > 6000)) {
|
if (!empty($refresh_token)) {
|
$new_token = refreshToken($refresh_token, $client_id, $client_secret);
|
if ($new_token) {
|
$access_token = $new_token;
|
// Met à jour les tokens persistants
|
saveTokens($access_token, $_SESSION['refresh_token'] ??
|
$refresh_token);
|
echo
|
"Token rafraîchi automatiquement ! Nouveau access_token : $new_token\n";
|
} else {
|
echo "Échec du refresh ? relancez le flux login manuellement.\n";
|
exit;
|
}
|
} else {
|
echo "Aucun refresh_token disponible ? relancez le flux login.\n";
|
exit;
|
}
|
}
|
|
// ------------------------------------------------
|
// Sélection image aléatoire
|
// ------------------------------------------------
|
$images = array(
|
'phpsources_img2.jpg',
|
'phpsources_img3.jpg',
|
'phpsources_img4.jpg',
|
'phpsources_img7.jpg',
|
'phpsources_img10.jpg',
|
'phpsources_img12.jpg'
|
);
|
$imageAleatoire1 = $images[array_rand($images)];
|
$image_path = __DIR__ . "/images_x/" . $imageAleatoire1;
|
|
echo "<pre>Image sélectionnée : " . htmlspecialchars($imageAleatoire1) . "\n";
|
echo "Chemin complet : " . htmlspecialchars($image_path) . "\n";
|
|
if (!file_exists($image_path)) {
|
die("Erreur : l'image n'existe pas ? " . htmlspecialchars($image_path));
|
}
|
|
// ------------------------------------------------
|
// Upload de l'image vers /2/media/upload
|
// ------------------------------------------------
|
$media_id = null;
|
|
if (file_exists($image_path)) {
|
echo "<pre>Début upload média... (media_category = tweet_image)</pre>\n";
|
|
$curl_media = curl_init('https://api.x.com/2/media/upload');
|
curl_setopt_array($curl_media, [
|
CURLOPT_POST => true,
|
CURLOPT_RETURNTRANSFER => true,
|
CURLOPT_HTTPHEADER => [
|
'Authorization: Bearer ' . $access_token,
|
],
|
CURLOPT_POSTFIELDS => [
|
'media' => new CURLFile(
|
$image_path,
|
mime_content_type($image_path) ?: 'image/jpeg',
|
basename($image_path)
|
),
|
'media_category' => 'tweet_image',
|
],
|
CURLOPT_TIMEOUT => 60,
|
]);
|
|
$media_response = curl_exec($curl_media);
|
$media_http_code = curl_getinfo($curl_media, CURLINFO_HTTP_CODE);
|
$media_err = curl_error($curl_media);
|
curl_close($curl_media);
|
|
if ($media_err) {
|
echo "<pre>Erreur cURL upload : $media_err</pre>\n";
|
} elseif ($media_http_code !== 200 && $media_http_code !== 201) {
|
echo "<pre>Erreur HTTP upload $media_http_code :<br>" . htmlspecialchars
|
($media_response) . "</pre>\n";
|
} else {
|
$media_data = json_decode($media_response, true);
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
echo "<pre>Erreur json_decode sur réponse upload : " .
|
json_last_error_msg() . "</pre>\n";
|
echo "<pre>Réponse brute : " . htmlspecialchars($media_response) .
|
"</pre>\n";
|
} else {
|
echo "<pre>Réponse upload média (structure complète) :\n" .
|
print_r($media_data, true) . "\n</pre>\n";
|
|
// Extraction robuste du media identifier
|
$media_id = null;
|
if (isset($media_data['data']['id'])) {
|
$media_id = $media_data['data']['id'];
|
echo "<pre>Media ID détecté via ['data']['id']</pre>\n";
|
} elseif (isset($media_data['data']['media_key'])) {
|
$media_id = $media_data['data']['media_key'];
|
echo
|
"<pre>Media KEY détecté via ['data']['media_key']</pre>\n";
|
} elseif (isset($media_data['media_id'])) {
|
$media_id = $media_data['media_id'];
|
echo
|
"<pre>Media ID détecté via ['media_id'] (ancien format)</pre>\n";
|
} elseif (isset($media_data['data']['media_id'])) {
|
$media_id = $media_data['data']['media_id'];
|
echo "<pre>Media ID détecté via ['data']['media_id']</pre>\n";
|
}
|
|
if ($media_id && is_string($media_id) && strlen($media_id) > 5) {
|
echo "<pre>Media ID obtenu : $media_id</pre>\n";
|
} else {
|
echo
|
"<pre>Impossible de trouver un identifiant média valide dans la" .
|
" réponse</pre>\n";
|
$media_id = null;
|
}
|
}
|
}
|
}
|
|
// ------------------------------------------------
|
// Texte du tweet
|
// ------------------------------------------------
|
$full_text = trim($_SESSION['tweeter_texte'] ?? '');
|
$full_text .= "\n";
|
$full_text .= trim('Downloader : https://phpsources.net/' . trim($_SESSION[
|
'tweeter_url'] ?? ''));
|
$full_text .= "\n";
|
$full_text .= ' #PHP #CodeGratuit #Developpement #WebDev';
|
|
// Ajout temporaire pour éviter duplicate pendant debug (à commenter après
|
// tests)
|
// $full_text .= "\nDebug " . date('H:i:s');
|
|
$full_text = trim($full_text);
|
|
// Debug
|
echo "<pre>";
|
echo "Texte à envoyer : " . htmlspecialchars($full_text) . "\n";
|
echo "Longueur : " . strlen($full_text) . "\n";
|
echo "</pre>";
|
|
// Nettoyage encoding
|
$clean_text = fix_encoding($full_text);
|
|
// ------------------------------------------------
|
// Préparation payload tweet (avec media si upload ok)
|
// ------------------------------------------------
|
$payload_array = ['text' => $clean_text];
|
|
if ($media_id) {
|
$payload_array['media'] = ['media_ids' => [$media_id]];
|
echo "<pre>Ajout du média au tweet : media_ids = [$media_id]</pre>\n";
|
} else {
|
echo
|
"<pre>Pas de média ajouté (upload échoué ou ID non trouvé)</pre>\n";
|
}
|
|
$payload = json_encode(
|
$payload_array,
|
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR
|
);
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
die("Erreur json_encode : " . json_last_error_msg() . "\nTexte nettoyé : "
|
. htmlspecialchars($clean_text));
|
}
|
|
echo "<pre>JSON exact envoyé :\n" . htmlspecialchars($payload) . "\n</pre>";
|
|
// ------------------------------------------------
|
// Envoi du tweet
|
// ------------------------------------------------
|
$curl = curl_init('https://api.x.com/2/tweets');
|
curl_setopt_array($curl, [
|
CURLOPT_POST => true,
|
CURLOPT_RETURNTRANSFER => true,
|
CURLOPT_HTTPHEADER => [
|
'Authorization: Bearer ' . $access_token,
|
'Content-Type: application/json; charset=utf-8'
|
],
|
CURLOPT_POSTFIELDS => $payload,
|
CURLOPT_TIMEOUT => 30,
|
CURLOPT_VERBOSE => true, // logs curl utiles pour debug
|
]);
|
|
$response = curl_exec($curl);
|
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
$err = curl_error($curl);
|
curl_close($curl);
|
|
if ($err) {
|
echo "cURL Error: " . $err;
|
} elseif ($http_code !== 201) {
|
echo "Erreur HTTP $http_code :<br><pre>" . htmlspecialchars($response) .
|
"</pre>";
|
} else {
|
$data = json_decode($response, true);
|
echo "Succès ! ID du tweet : " . ($data['data']['id'] ?? 'inconnu') . "\n";
|
if ($media_id) {
|
echo "Tweet posté AVEC IMAGE (media_id: $media_id)\n";
|
} else {
|
echo "Tweet posté SANS IMAGE\n";
|
}
|
}
|
| ?> |