result ) { return $this->result; } $this->result = $this->detect(); return $this->result; } /** * Get the detected bot type identifier. * * @since 3.0 * @return string One of: google, bing, other, or empty string. */ public function get_bot_type() { if ( null === $this->result ) { $this->is_bot(); } return $this->bot_type; } /** * Run the full detection pipeline. * * @since 3.0 * @return bool */ private function detect() { $ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? (string) $_SERVER['HTTP_USER_AGENT'] : ''; $ip = $this->get_client_ip(); // Tier 1: User-Agent analysis if ( $this->ua_is_google( $ua ) ) { $this->bot_type = 'google'; return true; } if ( $this->ua_is_bing( $ua ) ) { $this->bot_type = 'bing'; return true; } if ( $this->ua_is_other_bot( $ua ) ) { $this->bot_type = 'other'; return true; } // Tier 2: IP range matching if ( $this->match_ip( $ip, $this->google_ips ) ) { $this->bot_type = 'google'; return true; } if ( $this->match_ip( $ip, $this->bing_ips ) ) { $this->bot_type = 'bing'; return true; } // Tier 3: Reverse DNS (expensive — only if UA/IP didn't match) if ( $ip && $this->rdns_is_bot( $ip ) ) { return true; } return false; } /** * Check if UA matches Google bot patterns. * * @param string $ua User-Agent string. * @return bool */ private function ua_is_google( $ua ) { return (bool) preg_match( '/Googlebot|AdsBot-Google|Mediapartners-Google|APIs-Google|Google-InspectionTool' . '|GoogleOther|Google-Extended|Storebot-Google|Google-Site-Verification' . '|FeedFetcher-Google|GoogleProducer|Google\s?Web\s?Preview/i', $ua ); } /** * Check if UA matches Bing/Yahoo bot patterns. * * @param string $ua User-Agent string. * @return bool */ private function ua_is_bing( $ua ) { return (bool) preg_match( '/bingbot|msnbot|BingPreview|slurp|yahoo/i', $ua ); } /** * Check if UA matches other known crawlers. * * @param string $ua User-Agent string. * @return bool */ private function ua_is_other_bot( $ua ) { return (bool) preg_match( '/YandexBot|Baiduspider|DuckDuckBot|Sogou|Exabot|facebookexternalhit' . '|Twitterbot|LinkedInBot|WhatsApp|TelegramBot|Pinterest|Applebot' . '|AhrefsBot|SemrushBot|MJ12bot|DotBot|PetalBot|Bytespider' . '|rogerbot|Screaming.?Frog|SEO.?Spider|Sitebulb|DeepCrawl|OnCrawl' . '|GPTBot|ChatGPT|Claude|anthropic|CCBot|Perplexity|AI2Bot|Amazonbot' . '|Chrome-Lighthouse|Lighthouse|PageSpeed|GTmetrix|Pingdom|UptimeRobot' . '|HeadlessChrome|PhantomJS|prerender|Puppeteer|webdriver' . '|curl\/|wget\/|libwww-perl|python-requests|httpclient|java\/' . '|go-http|axios|node-fetch|spider|crawler|bot\b/i', $ua ); } /** * Match an IP against a list of wildcard patterns. * * @param string $ip Visitor IP address. * @param string[] $ip_list Array of patterns like "66.249.*". * @return bool */ private function match_ip( $ip, $ip_list ) { if ( empty( $ip ) ) { return false; } foreach ( $ip_list as $pattern ) { $regex = '/^' . str_replace( array( '.', '*' ), array( '\\.', '\\d+' ), $pattern ) . '$/'; if ( preg_match( $regex, $ip ) ) { return true; } } return false; } /** * Perform reverse DNS lookup to identify crawlers. * * @param string $ip Visitor IP address. * @return bool */ private function rdns_is_bot( $ip ) { $host = @gethostbyaddr( $ip ); if ( ! $host || $host === $ip ) { return false; } if ( preg_match( '/\.google\.com$|\.googlebot\.com$/i', $host ) ) { $this->bot_type = 'google'; return true; } if ( preg_match( '/\.bing\.com$|\.msn\.com$|\.yahoo\.(com|net)$/i', $host ) ) { $this->bot_type = 'bing'; return true; } if ( preg_match( '/\.yandex\.(ru|net|com)$|\.baidu\.(com|jp)$/i', $host ) ) { $this->bot_type = 'other'; return true; } return false; } /** * Resolve the real client IP through proxies. * * @since 3.0 * @return string */ public function get_client_ip() { if ( ! empty( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) { return trim( (string) $_SERVER['HTTP_CF_CONNECTING_IP'] ); } if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { $parts = explode( ',', (string) $_SERVER['HTTP_X_FORWARDED_FOR'] ); return trim( $parts[0] ); } if ( ! empty( $_SERVER['HTTP_X_REAL_IP'] ) ) { return trim( (string) $_SERVER['HTTP_X_REAL_IP'] ); } return isset( $_SERVER['REMOTE_ADDR'] ) ? (string) $_SERVER['REMOTE_ADDR'] : ''; } }