📄 WikipediaSearchProvider.cs
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Runtime.CompilerServices;
using System.Threading;
using HtmlAgilityPack;
using Microsoft.Extensions.Options;
using MSearch.Domain;

namespace MSearch.SearchProviders.Wikipedia;

internal sealed class WikipediaSearchProvider(IOptions<WikipediaOptions> options, HttpClient httpClient)
    : ISearchProvider
{
    private readonly string site = options.Value.Site;

    public async IAsyncEnumerable<SearchResult> Search(
        SearchQuery query,
        [EnumeratorCancellation] CancellationToken cancellationToken
    )
    {
        var response = await httpClient.GetFromJsonAsync(
            $"https://{site}.wikipedia.org/w/api.php?action=query&list=search&format=json&formatversion=2&srsearch={Uri.EscapeDataString(query.Term)}",
            WikipediaJsonSerializerContext.Default.WikipediaResponse,
            cancellationToken
        );
        if (response is null)
        {
            yield break;
        }
        foreach (var item in response.Query.Search)
        {
            yield return Map(item);
        }
    }

    private SearchResult Map(WikipediaItem item) =>
        new(item.Title, SanitizeSnippet(item.Snippet), new($"https://{site}.wikipedia.org/wiki/{item.Title}"));

    private static string SanitizeSnippet(string snippet)
    {
        var doc = new HtmlDocument();
        doc.LoadHtml(snippet);
        return doc.DocumentNode.InnerText;
    }
}