'OpenAI (training)', 'ClaudeBot' => 'Anthropic (training)', 'Google-Extended' => 'Google (training)', 'Meta-ExternalAgent' => 'Meta (training)', 'Bytespider' => 'ByteDance (training)', 'CCBot' => 'Common Crawl (training)', 'Applebot-Extended' => 'Apple Intelligence (training)', 'Amazonbot' => 'Amazon (training)', 'Diffbot' => 'Diffbot (training)', 'Omgilibot' => 'Omgili (training)', 'FacebookBot' => 'Meta (training)', 'ImagesiftBot' => 'Imagesift (training)', 'Cohere-ai' => 'Cohere (training)', ]; /** * AI retrieval/search bots (real-time answers, search results). * Default: ALLOWED (opt-out to block). */ public const RETRIEVAL_BOTS = [ 'OAI-SearchBot' => 'OpenAI Search', 'ChatGPT-User' => 'ChatGPT (browsing)', 'Claude-SearchBot' => 'Anthropic Search', 'Claude-User' => 'Claude (browsing)', 'PerplexityBot' => 'Perplexity AI', 'Perplexity-User' => 'Perplexity (browsing)', 'GoogleOther' => 'Google AI', ]; public function __construct() { $settings = AI_Ready_WP::get_settings(); if ( ! empty( $settings['enable_robots_ai'] ) ) { add_filter( 'robots_txt', [ $this, 'modify_robots_txt' ], 100, 2 ); } } /** * Add AI bot directives to WordPress virtual robots.txt. */ public function modify_robots_txt( string $output, bool $public ): string { if ( ! $public ) { return $output; } $settings = AI_Ready_WP::get_settings(); // Determine which bots to block $block_training = ! empty( $settings['robots_block_training'] ); $block_retrieval = ! empty( $settings['robots_block_retrieval'] ); $lines = []; $lines[] = ''; $lines[] = '# AI Bot Directives (managed by AI-Ready WP)'; // Training bots if ( $block_training ) { $allowed_training = self::get_allowed_list( $settings, 'robots_allow_training' ); foreach ( self::TRAINING_BOTS as $bot => $label ) { if ( in_array( $bot, $allowed_training, true ) ) { continue; // Explicitly allowed } $lines[] = 'User-agent: ' . $bot; $lines[] = 'Disallow: /'; $lines[] = ''; } } // Retrieval bots (only block if explicitly set) if ( $block_retrieval ) { $allowed_retrieval = self::get_allowed_list( $settings, 'robots_allow_retrieval' ); foreach ( self::RETRIEVAL_BOTS as $bot => $label ) { if ( in_array( $bot, $allowed_retrieval, true ) ) { continue; } $lines[] = 'User-agent: ' . $bot; $lines[] = 'Disallow: /'; $lines[] = ''; } } // AI discovery pointers (always add) $lines[] = '# AI Discovery Files'; $lines[] = 'Sitemap: ' . home_url( '/sitemap.xml' ); // Without a working filesystem we cannot tell which files exist, so // robots.txt simply does not advertise them rather than fataling. $fs = AI_Ready_WP::filesystem(); if ( null !== $fs && $fs->exists( AIREWP_ROOT_PATH . '.well-known/agents.json' ) ) { $lines[] = '# agents.json: ' . home_url( '/.well-known/agents.json' ); } if ( null !== $fs && $fs->exists( AIREWP_ROOT_PATH . 'llms.txt' ) ) { $lines[] = '# llms.txt: ' . home_url( '/llms.txt' ); } return $output . implode( "\n", $lines ) . "\n"; } /** * Get the list of explicitly allowed bots from a comma-separated setting. */ private static function get_allowed_list( array $settings, string $key ): array { $value = $settings[ $key ] ?? ''; if ( empty( $value ) ) { return []; } return array_map( 'trim', explode( ',', $value ) ); } /** * Get all known AI bot user-agent strings for analytics matching. */ public static function get_all_bot_agents(): array { return array_merge( array_keys( self::TRAINING_BOTS ), array_keys( self::RETRIEVAL_BOTS ) ); } }