SoftTraderBackend/ServiceHandler/NewsSentimentService.cs
2026-03-20 10:42:48 -04:00

102 lines
2.8 KiB
C#

using Backend.DatabaseHandler;
using Backend.Interface;
using System.Security.Cryptography;
using System.Text;
using System.Xml.Linq;
namespace Backend.ServiceHander
{
public sealed class NewsSentimentService
{
private readonly INewsSentimentStore _store;
private readonly FinBertScoringService _scorer;
private static readonly HttpClient Http = new()
{
DefaultRequestHeaders = { { "User-Agent", "SoftTrader/1.0" } }
};
public NewsSentimentService(INewsSentimentStore store, FinBertScoringService scorer)
{
_store = store;
_scorer = scorer;
}
public async Task<List<NewsSentimentRecord>> AnalyzeAsync(
string keyword,
CancellationToken cancellationToken = default)
{
keyword = keyword.Trim();
if (string.IsNullOrEmpty(keyword))
throw new ArgumentException("Keyword cannot be empty.");
var articles = await FetchRssAsync(keyword, cancellationToken);
var existingHashes = await _store.GetExistingHashesAsync(keyword, cancellationToken);
var newRecords = new List<NewsSentimentRecord>();
foreach (var (title, url, publishedAt) in articles)
{
string titleHash = ComputeHash(title);
if (existingHashes.Contains(titleHash))
continue;
var (score, label) = _scorer.Score(title);
newRecords.Add(new NewsSentimentRecord
{
Source = "google_rss",
Keyword = keyword,
PublishedAt = publishedAt,
Title = title,
Score = score,
SentimentLabel = label,
TitleHash = titleHash,
Url = url
});
}
if (newRecords.Count > 0)
await _store.InsertAsync(newRecords, cancellationToken);
return await _store.GetByKeywordAsync(keyword, cancellationToken);
}
private static async Task<List<(string Title, string Url, DateTimeOffset PublishedAt)>> FetchRssAsync(
string keyword,
CancellationToken cancellationToken)
{
string encodedKeyword = Uri.EscapeDataString(keyword);
string url = $"https://news.google.com/rss/search?q={encodedKeyword}&hl=en-US&gl=US&ceid=US:en";
string xml = await Http.GetStringAsync(url, cancellationToken);
var doc = XDocument.Parse(xml);
var items = new List<(string, string, DateTimeOffset)>();
foreach (var item in doc.Descendants("item"))
{
string? title = item.Element("title")?.Value;
string? link = item.Element("link")?.Value;
string? pubDate = item.Element("pubDate")?.Value;
if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(link))
continue;
DateTimeOffset published = DateTimeOffset.TryParse(pubDate, out var dt)
? dt
: DateTimeOffset.UtcNow;
items.Add((title, link, published));
}
return items;
}
private static string ComputeHash(string text)
{
byte[] bytes = SHA256.HashData(Encoding.UTF8.GetBytes(text));
return Convert.ToHexStringLower(bytes);
}
}
}