namespace WordPressStaticExporter.Services; public sealed class RobotsService { private readonly HttpClient _http = new(); private readonly List _disallow = []; private bool _loaded; public async Task LoadAsync(Uri siteUrl, CancellationToken cancellationToken) { if (_loaded) return; _loaded = true; try { var robots = new Uri(siteUrl, "/robots.txt"); var text = await _http.GetStringAsync(robots, cancellationToken); var applies = false; foreach (var raw in text.Split('\n')) { var line = raw.Split('#')[0].Trim(); if (line.StartsWith("User-agent:", StringComparison.OrdinalIgnoreCase)) { var agent = line["User-agent:".Length..].Trim(); applies = agent == "*" || agent.Contains("WordPressStaticExporter", StringComparison.OrdinalIgnoreCase); } else if (applies && line.StartsWith("Disallow:", StringComparison.OrdinalIgnoreCase)) { var path = line["Disallow:".Length..].Trim(); if (!string.IsNullOrWhiteSpace(path)) _disallow.Add(path); } } } catch { // Missing or unreachable robots.txt does not block the export. } } public bool IsAllowed(Uri uri) { var path = uri.AbsolutePath; return !_disallow.Any(rule => rule == "/" || path.StartsWith(rule, StringComparison.OrdinalIgnoreCase)); } }