86 lines
3.6 KiB
C#
86 lines
3.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using FinlyticCore.Dtos.News;
|
|
using FinlyticCore.Services;
|
|
using FinlyticNews.Adapters.Discovery;
|
|
using FinlyticNews.Util;
|
|
|
|
namespace FinlyticNews.Services;
|
|
|
|
public interface IArticleDiscoveryService
|
|
{
|
|
Task<List<DiscoveredArticle>?> DiscoverLinksAsync(string url, string adapterType, CancellationToken ct = default);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public class ArticleDiscoveryService : IArticleDiscoveryService
|
|
{
|
|
private readonly IEnumerable<ArticleDiscoveryAdapter> _adapters;
|
|
private readonly IFinlyticLogger<ArticleDiscoveryService> _finlyticLogger;
|
|
private readonly HttpClient _httpClient;
|
|
|
|
public ArticleDiscoveryService(
|
|
IEnumerable<ArticleDiscoveryAdapter> adapters,
|
|
IFinlyticLogger<ArticleDiscoveryService> finlyticLogger,
|
|
HttpClient httpClient)
|
|
{
|
|
_adapters = adapters;
|
|
_finlyticLogger = finlyticLogger;
|
|
_httpClient = httpClient;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<List<DiscoveredArticle>?> DiscoverLinksAsync(string url, string adapterType, CancellationToken ct = default)
|
|
{
|
|
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
|
|
{
|
|
await _finlyticLogger.LogWarningAsync(SettingKeys.NewsChannel, "[ArticleDiscoveryService] Invalid source URL passed for discovery: {Url}", url);
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
await _finlyticLogger.LogDebugAsync(SettingKeys.NewsChannel, "[ArticleDiscoveryService] Fetching content from discovery source: {Url}", uri);
|
|
var content = await _httpClient.GetStringAsync(uri, ct);
|
|
|
|
if (string.IsNullOrWhiteSpace(content)) return [];
|
|
|
|
var trimmedContent = content.TrimStart();
|
|
|
|
var adapter = _adapters.FirstOrDefault(a =>
|
|
a.Name.Equals(adapterType, StringComparison.OrdinalIgnoreCase) ||
|
|
uri.Host.Contains(a.Name, StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (adapter != null)
|
|
{
|
|
await _finlyticLogger.LogInfoAsync(SettingKeys.NewsChannel, "[ArticleDiscoveryService] Using specialized adapter {AdapterName} for source: {Url}", adapter.Name, url);
|
|
return adapter.ExtractUrls(content, url);
|
|
}
|
|
|
|
if (adapterType.Equals("rss", StringComparison.OrdinalIgnoreCase) ||
|
|
trimmedContent.StartsWith("<?xml", StringComparison.OrdinalIgnoreCase) ||
|
|
trimmedContent.StartsWith("<rss", StringComparison.OrdinalIgnoreCase) ||
|
|
trimmedContent.StartsWith("<feed", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
await _finlyticLogger.LogInfoAsync(SettingKeys.NewsChannel, "[ArticleDiscoveryService] Syndication (RSS) format detected for source: {Url}", url);
|
|
|
|
var rssAdapter = _adapters.FirstOrDefault(a => a.Name.Equals("rss", StringComparison.OrdinalIgnoreCase))
|
|
?? new RssDiscoveryAdapter();
|
|
|
|
return rssAdapter.ExtractUrls(content, url);
|
|
}
|
|
|
|
await _finlyticLogger.LogWarningAsync(SettingKeys.NewsChannel, "[ArticleDiscoveryService] No suitable discovery adapter found for type '{Type}' and URL: {Url}", adapterType, url);
|
|
return [];
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await _finlyticLogger.LogErrorAsync(SettingKeys.NewsChannel, ex, "[ArticleDiscoveryService] Failed to run article discovery on URL: {Url}", url);
|
|
return [];
|
|
}
|
|
}
|
|
} |