'https://openai.com/gptbot.json', 'chatgpt-user' => 'https://openai.com/chatgpt-user.json', 'searchbot' => 'https://openai.com/searchbot.json', ); /** * Cache des IP ranges */ private $cached_ranges = array(); /** * Durée du cache (24 heures) */ private $cache_duration = 86400; /** * Vérifier si une IP appartient à OpenAI */ public function is_openai_ip($ip) { foreach ($this->openai_ranges_urls as $bot_type => $url) { $ranges = $this->get_ip_ranges($bot_type, $url); if ($ranges && $this->ip_in_ranges($ip, $ranges)) { return array( 'is_openai' => true, 'bot_type' => $bot_type, 'detected_by' => 'ip_range' ); } } return array('is_openai' => false); } /** * Récupérer les IP ranges (avec cache) */ private function get_ip_ranges($bot_type, $url) { // Vérifier le cache WordPress $cache_key = 'Almaweb_ip_ranges_' . $bot_type; $cached = get_transient($cache_key); if ($cached !== false) { return $cached; } // Télécharger les nouvelles ranges $response = wp_remote_get($url, array( 'timeout' => 10, 'sslverify' => true )); if (is_wp_error($response)) { return false; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (!$data || !isset($data['prefixes'])) { return false; } $ranges = array(); foreach ($data['prefixes'] as $prefix) { if (isset($prefix['ipv4Prefix'])) { $ranges[] = $prefix['ipv4Prefix']; } if (isset($prefix['ipv6Prefix'])) { $ranges[] = $prefix['ipv6Prefix']; } } // Mettre en cache set_transient($cache_key, $ranges, $this->cache_duration); return $ranges; } /** * Vérifier si une IP est dans une liste de ranges CIDR */ private function ip_in_ranges($ip, $ranges) { $ip_long = ip2long($ip); if ($ip_long === false) { // Essayer IPv6 return $this->ipv6_in_ranges($ip, $ranges); } foreach ($ranges as $range) { if (strpos($range, '/') === false) { // IP simple if ($ip === $range) { return true; } continue; } // Range CIDR list($subnet, $mask) = explode('/', $range); $subnet_long = ip2long($subnet); if ($subnet_long === false) { continue; } $mask_long = -1 << (32 - (int)$mask); $subnet_masked = $subnet_long & $mask_long; $ip_masked = $ip_long & $mask_long; if ($subnet_masked === $ip_masked) { return true; } } return false; } /** * Vérifier IPv6 */ private function ipv6_in_ranges($ip, $ranges) { if (!function_exists('inet_pton')) { return false; } $ip_packed = @inet_pton($ip); if ($ip_packed === false) { return false; } foreach ($ranges as $range) { if (strpos($range, ':') === false) { continue; // Pas IPv6 } if (strpos($range, '/') === false) { // IP simple if ($ip === $range) { return true; } continue; } list($subnet, $mask) = explode('/', $range); $subnet_packed = @inet_pton($subnet); if ($subnet_packed === false) { continue; } $mask = (int)$mask; if ($this->ipv6_match($ip_packed, $subnet_packed, $mask)) { return true; } } return false; } /** * Comparer deux adresses IPv6 */ private function ipv6_match($ip_packed, $subnet_packed, $mask_bits) { $full_bytes = floor($mask_bits / 8); $remaining_bits = $mask_bits % 8; // Comparer les octets complets for ($i = 0; $i < $full_bytes; $i++) { if ($ip_packed[$i] !== $subnet_packed[$i]) { return false; } } // Comparer les bits restants if ($remaining_bits > 0) { $mask = ~((1 << (8 - $remaining_bits)) - 1); $ip_byte = ord($ip_packed[$full_bytes]); $subnet_byte = ord($subnet_packed[$full_bytes]); if (($ip_byte & $mask) !== ($subnet_byte & $mask)) { return false; } } return true; } /** * Détecter un bot invisible (Chrome normal mais IP OpenAI) */ public function detect_stealth_bot($user_agent, $ip) { // Vérifier si c'est un User-Agent Chrome/Safari standard if (!$this->is_standard_browser_ua($user_agent)) { return false; } // Vérifier si l'IP appartient à OpenAI $openai_check = $this->is_openai_ip($ip); if ($openai_check['is_openai']) { return array( 'is_stealth' => true, 'suspected_bot' => 'ChatGPT Atlas', 'bot_type' => 'stealth_ai_bot', 'reason' => 'Standard browser UA with OpenAI IP', 'confidence' => 'high' ); } return false; } /** * Vérifier si c'est un User-Agent de navigateur standard */ private function is_standard_browser_ua($user_agent) { $patterns = array( '/Mozilla\/5\.0.*Chrome\/\d+.*Safari\/\d+$/', '/Mozilla\/5\.0.*AppleWebKit.*Safari\/\d+$/', '/Mozilla\/5\.0.*Firefox\/\d+$/', ); foreach ($patterns as $pattern) { if (preg_match($pattern, $user_agent)) { // Vérifier qu'il n'y a pas de mot-clé bot if (stripos($user_agent, 'bot') === false && stripos($user_agent, 'crawler') === false && stripos($user_agent, 'spider') === false) { return true; } } } return false; } /** * Forcer le rafraîchissement du cache */ public function refresh_cache() { foreach ($this->openai_ranges_urls as $bot_type => $url) { $cache_key = 'Almaweb_ip_ranges_' . $bot_type; delete_transient($cache_key); } } /** * Obtenir les statistiques du cache */ public function get_cache_stats() { $stats = array(); foreach ($this->openai_ranges_urls as $bot_type => $url) { $cache_key = 'Almaweb_ip_ranges_' . $bot_type; $cached = get_transient($cache_key); $stats[$bot_type] = array( 'cached' => ($cached !== false), 'count' => $cached ? count($cached) : 0, 'expires_in' => $cached ? get_option('_transient_timeout_' . $cache_key) - time() : 0 ); } return $stats; } }