> */ public static function get_categories() { return array( 'major_ai' => array( 'label' => __( 'Major AI Assistants', 'ai-discovery-files' ), 'default_on' => true, ), 'google_ai' => array( 'label' => __( 'Google AI', 'ai-discovery-files' ), 'default_on' => true, ), 'apple_microsoft' => array( 'label' => __( 'Apple & Microsoft', 'ai-discovery-files' ), 'default_on' => true, ), 'meta' => array( 'label' => __( 'Meta', 'ai-discovery-files' ), 'default_on' => true, ), 'amazon' => array( 'label' => __( 'Amazon', 'ai-discovery-files' ), 'default_on' => true, ), 'ai_search' => array( 'label' => __( 'AI Search Engines', 'ai-discovery-files' ), 'default_on' => false, ), 'training' => array( 'label' => __( 'Training Crawlers', 'ai-discovery-files' ), 'default_on' => false, ), 'chinese_ai' => array( 'label' => __( 'Chinese AI', 'ai-discovery-files' ), 'default_on' => false, ), ); } /** * Return all known bots as an associative array keyed by lowercase bot slug. * * Each entry has: * - name (string) Human-readable bot name. * - ua (string) User-agent token used for detection (case-insensitive substring match). * - operator (string) Company or organisation that operates the bot. * - purpose (string) What the bot does. * - category (string) Category key from get_categories(). * * Applies the `aidf_crawler_registry` filter before returning, allowing * third-party code to add, remove, or modify entries. * * @return array> */ public static function get_bots() { $bots = array( // ---------------------------------------------------------------- // Major AI Assistants // ---------------------------------------------------------------- 'gptbot' => array( 'name' => 'GPTBot', 'ua' => 'GPTBot', 'operator' => 'OpenAI', 'purpose' => 'Training', 'category' => 'major_ai', ), 'chatgpt-user' => array( 'name' => 'ChatGPT-User', 'ua' => 'ChatGPT-User', 'operator' => 'OpenAI', 'purpose' => 'Browsing', 'category' => 'major_ai', ), 'oai-searchbot' => array( 'name' => 'OAI-SearchBot', 'ua' => 'OAI-SearchBot', 'operator' => 'OpenAI', 'purpose' => 'Search', 'category' => 'major_ai', ), 'claudebot' => array( 'name' => 'ClaudeBot', 'ua' => 'ClaudeBot', 'operator' => 'Anthropic', 'purpose' => 'Training', 'category' => 'major_ai', ), 'claude-user' => array( 'name' => 'Claude-User', 'ua' => 'Claude-User', 'operator' => 'Anthropic', 'purpose' => 'Browsing', 'category' => 'major_ai', ), 'claude-searchbot' => array( 'name' => 'Claude-SearchBot', 'ua' => 'Claude-SearchBot', 'operator' => 'Anthropic', 'purpose' => 'Search', 'category' => 'major_ai', ), 'perplexitybot' => array( 'name' => 'PerplexityBot', 'ua' => 'PerplexityBot', 'operator' => 'Perplexity', 'purpose' => 'Search', 'category' => 'major_ai', ), 'perplexity-user' => array( 'name' => 'Perplexity-User', 'ua' => 'Perplexity-User', 'operator' => 'Perplexity', 'purpose' => 'Browsing', 'category' => 'major_ai', ), 'grokbot' => array( 'name' => 'GrokBot', 'ua' => 'GrokBot', 'operator' => 'xAI', 'purpose' => 'Training', 'category' => 'major_ai', ), 'xai-grok' => array( 'name' => 'xAI-Grok', 'ua' => 'xAI-Grok', 'operator' => 'xAI', 'purpose' => 'Search', 'category' => 'major_ai', ), 'mistralai-user' => array( 'name' => 'MistralAI-User', 'ua' => 'MistralAI-User', 'operator' => 'Mistral AI', 'purpose' => 'Browsing', 'category' => 'major_ai', ), // ---------------------------------------------------------------- // Google AI // ---------------------------------------------------------------- 'googleother' => array( 'name' => 'GoogleOther', 'ua' => 'GoogleOther', 'operator' => 'Google', 'purpose' => 'R&D', 'category' => 'google_ai', ), 'google-cloudvertexbot' => array( 'name' => 'Google-CloudVertexBot', 'ua' => 'Google-CloudVertexBot', 'operator' => 'Google', 'purpose' => 'Vertex AI', 'category' => 'google_ai', ), 'gemini-deep-research' => array( 'name' => 'Gemini-Deep-Research', 'ua' => 'Gemini-Deep-Research', 'operator' => 'Google', 'purpose' => 'Deep Research', 'category' => 'google_ai', ), 'google-notebooklm' => array( 'name' => 'Google-NotebookLM', 'ua' => 'Google-NotebookLM', 'operator' => 'Google', 'purpose' => 'NotebookLM', 'category' => 'google_ai', ), // ---------------------------------------------------------------- // Apple & Microsoft // ---------------------------------------------------------------- 'applebot-extended' => array( 'name' => 'Applebot-Extended', 'ua' => 'Applebot-Extended', 'operator' => 'Apple', 'purpose' => 'Apple Intelligence', 'category' => 'apple_microsoft', ), 'applebot' => array( 'name' => 'Applebot', 'ua' => 'Applebot', 'operator' => 'Apple', 'purpose' => 'Siri / Spotlight', 'category' => 'apple_microsoft', ), 'bingbot' => array( 'name' => 'bingbot', 'ua' => 'bingbot', 'operator' => 'Microsoft', 'purpose' => 'Bing / Copilot', 'category' => 'apple_microsoft', ), 'azureai-searchbot' => array( 'name' => 'AzureAI-SearchBot', 'ua' => 'AzureAI-SearchBot', 'operator' => 'Microsoft', 'purpose' => 'Azure AI', 'category' => 'apple_microsoft', ), // ---------------------------------------------------------------- // Meta // ---------------------------------------------------------------- 'meta-externalagent' => array( 'name' => 'meta-externalagent', 'ua' => 'meta-externalagent', 'operator' => 'Meta', 'purpose' => 'Training', 'category' => 'meta', ), 'meta-externalfetcher' => array( 'name' => 'meta-externalfetcher', 'ua' => 'meta-externalfetcher', 'operator' => 'Meta', 'purpose' => 'Browsing', 'category' => 'meta', ), 'meta-webindexer' => array( 'name' => 'meta-webindexer', 'ua' => 'meta-webindexer', 'operator' => 'Meta', 'purpose' => 'Indexing', 'category' => 'meta', ), // ---------------------------------------------------------------- // Amazon // ---------------------------------------------------------------- 'amazonbot' => array( 'name' => 'Amazonbot', 'ua' => 'Amazonbot', 'operator' => 'Amazon', 'purpose' => 'Alexa / Search', 'category' => 'amazon', ), 'amzn-searchbot' => array( 'name' => 'Amzn-SearchBot', 'ua' => 'Amzn-SearchBot', 'operator' => 'Amazon', 'purpose' => 'AI Search', 'category' => 'amazon', ), 'amzn-user' => array( 'name' => 'Amzn-User', 'ua' => 'Amzn-User', 'operator' => 'Amazon', 'purpose' => 'Browsing', 'category' => 'amazon', ), // ---------------------------------------------------------------- // AI Search Engines // ---------------------------------------------------------------- 'duckassistbot' => array( 'name' => 'DuckAssistBot', 'ua' => 'DuckAssistBot', 'operator' => 'DuckDuckGo', 'purpose' => 'AI Answers', 'category' => 'ai_search', ), 'bravebot' => array( 'name' => 'Bravebot', 'ua' => 'Bravebot', 'operator' => 'Brave', 'purpose' => 'Search', 'category' => 'ai_search', ), 'youbot' => array( 'name' => 'YouBot', 'ua' => 'YouBot', 'operator' => 'You.com', 'purpose' => 'Search', 'category' => 'ai_search', ), 'andibot' => array( 'name' => 'Andibot', 'ua' => 'Andibot', 'operator' => 'Andi', 'purpose' => 'Search', 'category' => 'ai_search', ), 'phindbot' => array( 'name' => 'PhindBot', 'ua' => 'PhindBot', 'operator' => 'Phind', 'purpose' => 'Code Search', 'category' => 'ai_search', ), 'exabot' => array( 'name' => 'ExaBot', 'ua' => 'ExaBot', 'operator' => 'Exa', 'purpose' => 'Search', 'category' => 'ai_search', ), 'iaskbot' => array( 'name' => 'iAskBot', 'ua' => 'iAskBot', 'operator' => 'iAsk.AI', 'purpose' => 'Q&A Search', 'category' => 'ai_search', ), // ---------------------------------------------------------------- // Training Crawlers // ---------------------------------------------------------------- 'ccbot' => array( 'name' => 'CCBot', 'ua' => 'CCBot', 'operator' => 'Common Crawl', 'purpose' => 'Training data', 'category' => 'training', ), 'bytespider' => array( 'name' => 'Bytespider', 'ua' => 'Bytespider', 'operator' => 'ByteDance', 'purpose' => 'Training', 'category' => 'training', ), 'cohere-ai' => array( 'name' => 'cohere-ai', 'ua' => 'cohere-ai', 'operator' => 'Cohere', 'purpose' => 'Browsing', 'category' => 'training', ), 'cohere-training-data-crawler' => array( 'name' => 'cohere-training-data-crawler', 'ua' => 'cohere-training-data-crawler', 'operator' => 'Cohere', 'purpose' => 'Training', 'category' => 'training', ), 'diffbot' => array( 'name' => 'Diffbot', 'ua' => 'Diffbot', 'operator' => 'Diffbot', 'purpose' => 'Knowledge Graph', 'category' => 'training', ), 'ai2bot-dolma' => array( 'name' => 'Ai2Bot-Dolma', 'ua' => 'Ai2Bot-Dolma', 'operator' => 'Allen Institute', 'purpose' => 'Training', 'category' => 'training', ), 'ai2bot' => array( 'name' => 'AI2Bot', 'ua' => 'AI2Bot', 'operator' => 'Allen Institute', 'purpose' => 'Research', 'category' => 'training', ), // ---------------------------------------------------------------- // Chinese AI // ---------------------------------------------------------------- 'chatglm-spider' => array( 'name' => 'ChatGLM-Spider', 'ua' => 'ChatGLM-Spider', 'operator' => 'Zhipu AI', 'purpose' => 'Training', 'category' => 'chinese_ai', ), 'deepseekbot' => array( 'name' => 'DeepSeekBot', 'ua' => 'DeepSeekBot', 'operator' => 'DeepSeek', 'purpose' => 'Training', 'category' => 'chinese_ai', ), 'petalbot' => array( 'name' => 'PetalBot', 'ua' => 'PetalBot', 'operator' => 'Huawei', 'purpose' => 'Search', 'category' => 'chinese_ai', ), 'pangubot' => array( 'name' => 'PanguBot', 'ua' => 'PanguBot', 'operator' => 'Huawei', 'purpose' => 'Training', 'category' => 'chinese_ai', ), ); /** * Filters the complete bot registry. * * Third-party plugins may add, remove, or modify entries. Each entry * must contain 'name', 'ua', 'operator', 'purpose', and 'category' keys. * * @since 1.3.0 * * @param array> $bots Bot registry keyed by lowercase bot slug. */ return apply_filters( 'aidf_crawler_registry', $bots ); } /** * Return the bot keys that belong to default-ON categories. * * @return array */ public static function get_default_bot_keys() { $categories = self::get_categories(); $bots = self::get_bots(); $default_keys = array(); foreach ( $bots as $key => $bot ) { $cat = isset( $bot['category'] ) ? $bot['category'] : ''; if ( isset( $categories[ $cat ] ) && true === $categories[ $cat ]['default_on'] ) { $default_keys[] = $key; } } return $default_keys; } /** * Match a raw User-Agent string against the list of enabled bots. * * Performs a case-insensitive substring search using each bot's 'ua' token. * Returns the first matching bot key, or false if no match is found. * * @param string $ua_string Raw User-Agent header value. * @param array> $enabled_bots Subset of bots to check (same shape as get_bots()). * @return string|false Bot key on match, false otherwise. */ public static function match_user_agent( $ua_string, $enabled_bots ) { if ( empty( $ua_string ) || empty( $enabled_bots ) ) { return false; } // Sort by UA token length descending so longer, more-specific // tokens match before shorter ones (e.g. Applebot-Extended before Applebot). uasort( $enabled_bots, function ( $a, $b ) { return strlen( $b['ua'] ) - strlen( $a['ua'] ); } ); foreach ( $enabled_bots as $key => $bot ) { if ( false !== stripos( $ua_string, $bot['ua'] ) ) { return $key; } } return false; } /** * Return all bots grouped by their category key. * * @return array>> */ public static function get_bots_by_category() { $bots = self::get_bots(); $grouped = array(); foreach ( $bots as $key => $bot ) { $cat = isset( $bot['category'] ) ? $bot['category'] : 'uncategorised'; $grouped[ $cat ][ $key ] = $bot; } return $grouped; } }