'https://openai.com/gptbot.json', 'chatgpt-user' => 'https://openai.com/chatgpt-user.json', 'searchbot' => 'https://openai.com/searchbot.json', ); /** * IP ranges statiques Google — Googlebot + Special Crawlers (Google-Extended, etc.) * Source : https://developers.google.com/search/apis/ipranges/googlebot.json * https://developers.google.com/search/apis/ipranges/special-crawlers.json * Mis à jour : 2026-03-23 */ private $google_ranges = array( // Googlebot IPv4 '192.178.4.0/27','192.178.4.32/27','192.178.4.64/27','192.178.4.96/27', '192.178.4.128/27','192.178.4.160/27','192.178.4.192/27','192.178.4.224/27', '192.178.5.0/27','192.178.6.0/27','192.178.6.32/27','192.178.6.64/27', '192.178.6.96/27','192.178.6.128/27','192.178.6.160/27','192.178.6.192/27', '192.178.6.224/27','192.178.7.0/27','192.178.7.32/27','192.178.7.64/27', '192.178.7.96/27','192.178.7.128/27','192.178.7.160/27','192.178.7.192/27', '192.178.7.224/27', '34.100.182.96/28','34.101.50.144/28','34.118.254.0/28','34.118.66.0/28', '34.126.178.96/28','34.146.150.144/28','34.147.110.144/28','34.151.74.144/28', '34.152.50.64/28','34.154.114.144/28','34.155.98.32/28','34.165.18.176/28', '34.175.160.64/28','34.176.130.16/28','34.22.85.0/27','34.64.82.64/28', '34.65.242.112/28','34.80.50.80/28','34.88.194.0/28','34.89.10.80/28', '34.89.198.80/28','34.96.162.48/28','35.247.243.240/28', '66.249.64.0/27','66.249.64.32/27','66.249.64.64/27','66.249.64.96/27', '66.249.64.128/27','66.249.64.160/27','66.249.64.192/27','66.249.64.224/27', '66.249.65.0/27','66.249.65.32/27','66.249.65.64/27','66.249.65.96/27', '66.249.65.128/27','66.249.65.160/27','66.249.65.192/27','66.249.65.224/27', '66.249.66.0/27','66.249.66.32/27','66.249.66.64/27','66.249.66.96/27', '66.249.66.128/27','66.249.66.160/27','66.249.66.192/27','66.249.66.224/27', '66.249.67.0/27','66.249.67.32/27','66.249.67.64/27', '66.249.68.0/27','66.249.68.32/27','66.249.68.64/27','66.249.68.96/27', '66.249.68.128/27','66.249.68.160/27','66.249.68.192/27', '66.249.69.0/27','66.249.69.32/27','66.249.69.64/27','66.249.69.96/27', '66.249.69.128/27','66.249.69.160/27','66.249.69.192/27','66.249.69.224/27', '66.249.70.0/27','66.249.70.32/27','66.249.70.64/27','66.249.70.96/27', '66.249.70.128/27','66.249.70.160/27','66.249.70.192/27','66.249.70.224/27', '66.249.71.0/27','66.249.71.32/27','66.249.71.64/27','66.249.71.96/27', '66.249.71.128/27','66.249.71.160/27','66.249.71.192/27','66.249.71.224/27', '66.249.72.0/27','66.249.72.32/27','66.249.72.64/27','66.249.72.96/27', '66.249.72.128/27','66.249.72.160/27','66.249.72.192/27', '66.249.73.0/27','66.249.73.32/27','66.249.73.64/27','66.249.73.96/27', '66.249.73.128/27','66.249.73.160/27','66.249.73.192/27','66.249.73.224/27', '66.249.74.0/27','66.249.74.32/27','66.249.74.64/27','66.249.74.96/27', '66.249.74.128/27','66.249.74.160/27','66.249.74.192/27','66.249.74.224/27', '66.249.75.0/27','66.249.75.32/27','66.249.75.64/27','66.249.75.96/27', '66.249.75.128/27','66.249.75.160/27','66.249.75.192/27','66.249.75.224/27', '66.249.76.0/27','66.249.76.32/27','66.249.76.64/27','66.249.76.96/27', '66.249.76.128/27','66.249.76.160/27','66.249.76.192/27','66.249.76.224/27', '66.249.77.0/27','66.249.77.32/27','66.249.77.64/27','66.249.77.96/27', '66.249.77.128/27','66.249.77.160/27','66.249.77.192/27','66.249.77.224/27', '66.249.78.0/27','66.249.78.32/27','66.249.78.64/27','66.249.78.96/27', '66.249.78.128/27','66.249.78.160/27', '66.249.79.0/27','66.249.79.32/27','66.249.79.64/27','66.249.79.96/27', '66.249.79.128/27','66.249.79.160/27','66.249.79.192/27','66.249.79.224/27', // Special Crawlers IPv4 (Google-Extended, etc.) '108.177.2.0/27','108.177.2.32/27','108.177.2.64/27','108.177.2.96/27', '108.177.2.128/27','108.177.2.160/27','108.177.2.192/27','108.177.2.224/27', '192.178.16.0/27','192.178.16.32/27','192.178.16.64/27','192.178.16.96/27', '192.178.16.128/27','192.178.16.160/27','192.178.16.192/27','192.178.16.224/27', '192.178.17.0/27', '209.85.238.0/27','209.85.238.32/27','209.85.238.64/27','209.85.238.96/27', '209.85.238.128/27','209.85.238.160/27','209.85.238.192/27','209.85.238.224/27', '66.249.87.0/27','66.249.87.32/27','66.249.87.64/27','66.249.87.96/27', '66.249.87.128/27','66.249.87.160/27','66.249.87.192/27','66.249.87.224/27', '66.249.89.0/27','66.249.89.32/27','66.249.89.64/27','66.249.89.96/27', '66.249.89.128/27','66.249.89.160/27','66.249.89.192/27','66.249.89.224/27', '66.249.90.0/27','66.249.90.32/27','66.249.90.64/27','66.249.90.96/27', '66.249.90.128/27','66.249.90.160/27','66.249.90.192/27','66.249.90.224/27', '66.249.91.0/27','66.249.91.32/27','66.249.91.64/27','66.249.91.96/27', '66.249.91.128/27','66.249.91.160/27','66.249.91.192/27','66.249.91.224/27', '66.249.92.0/27','66.249.92.32/27','66.249.92.64/27','66.249.92.96/27', '66.249.92.128/27','66.249.92.160/27','66.249.92.192/27','66.249.92.224/27', '72.14.199.32/27','72.14.199.64/27','72.14.199.96/27','72.14.199.128/27', '72.14.199.160/27','72.14.199.192/27','72.14.199.224/27', '74.125.148.0/27','74.125.148.32/27','74.125.148.64/27','74.125.148.96/27', '74.125.148.128/27','74.125.148.160/27','74.125.148.192/27','74.125.148.224/27', '74.125.149.0/27','74.125.149.32/27','74.125.149.64/27','74.125.149.96/27', '74.125.149.128/27','74.125.149.160/27','74.125.149.192/27','74.125.149.224/27', '74.125.150.0/27','74.125.150.32/27','74.125.150.64/27','74.125.150.96/27', '74.125.150.128/27','74.125.150.160/27','74.125.150.192/27', '74.125.151.0/27','74.125.151.32/27','74.125.151.64/27','74.125.151.96/27', '74.125.151.128/27','74.125.151.160/27','74.125.151.192/27','74.125.151.224/27', '74.125.216.0/27','74.125.216.32/27','74.125.216.64/27','74.125.216.96/27', '74.125.216.128/27','74.125.216.160/27','74.125.216.192/27','74.125.216.224/27', '74.125.217.0/27','74.125.217.32/27','74.125.217.64/27','74.125.217.96/27', '74.125.217.128/27','74.125.217.160/27','74.125.217.192/27','74.125.217.224/27', '74.125.218.0/27','74.125.218.32/27','74.125.218.64/27','74.125.218.96/27', '74.125.218.128/27','74.125.218.160/27','74.125.218.192/27','74.125.218.224/27', '74.125.219.0/27','74.125.219.32/27','74.125.219.64/27','74.125.219.96/27', '74.125.219.128/27','74.125.219.160/27', ); /** * IP ranges statiques Anthropic (pas de JSON public disponible) * Source : https://docs.anthropic.com/en/api/ip-addresses */ private $anthropic_ranges = array( '160.79.104.0/21', ); /** * 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); } /** * Vérifier si une IP appartient à Anthropic */ public function is_anthropic_ip($ip) { if ($this->ip_in_ranges($ip, $this->anthropic_ranges)) { return array( 'is_anthropic' => true, 'bot_type' => 'claudebot', 'detected_by' => 'ip_range' ); } return array('is_anthropic' => false); } /** * Vérifier si une IP appartient à Google (Googlebot ou crawlers IA) */ public function is_google_ip($ip) { if ($this->ip_in_ranges($ip, $this->google_ranges)) { return array( 'is_google' => true, 'bot_type' => 'googlebot', 'detected_by' => 'ip_range' ); } return array('is_google' => false); } /** * Vérification globale toutes compagnies */ public function check_ip($ip) { $openai = $this->is_openai_ip($ip); if ($openai['is_openai']) { return array('matched' => true, 'family' => 'OpenAI', 'bot_type' => $openai['bot_type'], 'detected_by' => 'ip_range'); } $anthropic = $this->is_anthropic_ip($ip); if ($anthropic['is_anthropic']) { return array('matched' => true, 'family' => 'Anthropic', 'bot_type' => $anthropic['bot_type'], 'detected_by' => 'ip_range'); } $google = $this->is_google_ip($ip); if ($google['is_google']) { return array('matched' => true, 'family' => 'Google', 'bot_type' => $google['bot_type'], 'detected_by' => 'ip_range'); } return array('matched' => 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 (UA navigateur standard mais IP d'un acteur IA connu) */ public function detect_stealth_bot($user_agent, $ip) { if (!$this->is_standard_browser_ua($user_agent)) { return false; } $check = $this->check_ip($ip); if (!$check['matched']) { return false; } $names = array( 'OpenAI' => 'ChatGPT Atlas', 'Anthropic' => 'Claude Stealth', 'Google' => 'Google Stealth Crawler', ); return array( 'is_stealth' => true, 'suspected_bot' => $names[ $check['family'] ] ?? $check['family'] . ' Stealth Bot', 'family' => $check['family'], 'bot_type' => 'stealth_ai_bot', 'reason' => 'Standard browser UA with ' . $check['family'] . ' IP', 'confidence' => 'high', ); } /** * 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) { delete_transient('Almaweb_ip_ranges_' . $bot_type); } } /** * 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 ); } $stats['anthropic'] = array( 'cached' => true, 'count' => count($this->anthropic_ranges), 'expires_in' => -1, ); $stats['google'] = array( 'cached' => true, 'count' => count($this->google_ranges), 'expires_in' => -1, ); return $stats; } }