using Aberwyn.Data; using Aberwyn.Models; using Microsoft.EntityFrameworkCore; using System.Text.RegularExpressions; using System.Xml.Linq; namespace Aberwyn.Data { public class RssProcessor : IRssProcessor { private readonly ApplicationDbContext _context; private readonly ILogger _logger; private readonly HttpClient _httpClient; private readonly HdTorrentsTrackerScraper _trackerScraper; private readonly MovieMetadataService _movieMetadataService; public RssProcessor(ApplicationDbContext context, ILogger logger, HttpClient httpClient, HdTorrentsTrackerScraper trackerScraper, MovieMetadataService movieMetadataService) { _context = context; _logger = logger; _httpClient = httpClient; _trackerScraper = trackerScraper; _movieMetadataService = movieMetadataService; } public async Task ProcessRssFeeds() { var oneHourAgo = DateTime.UtcNow.AddHours(-1); var activeFeeds = await _context.RssFeeds .Where(f => f.IsActive && f.LastChecked <= oneHourAgo) .ToListAsync(); foreach (var feed in activeFeeds) { try { await ProcessSingleFeed(feed); } catch (Exception ex) { _logger.LogError(ex, "Error processing RSS feed {FeedName}", feed.Name); } } } private async Task ProcessSingleFeed(RssFeed feed) { var rssContent = await _httpClient.GetStringAsync(feed.Url); var rssDoc = XDocument.Parse(rssContent); var items = rssDoc.Descendants("item"); try { foreach (var item in items) { try { var torrentItem = ParseRssItem(item, feed); Console.WriteLine($"Trying to save: Title='{torrentItem.Title}', InfoHash='{torrentItem.InfoHash}', MovieName='{torrentItem.MovieName}'"); // Kolla om den redan finns var exists = await _context.TorrentItems .AnyAsync(t => t.InfoHash == torrentItem.InfoHash || (t.Title == torrentItem.Title && t.RssSource == feed.Name)); if (!exists) { if (!string.IsNullOrEmpty(torrentItem.InfoHash) && !string.IsNullOrEmpty(torrentItem.DownloadKey)) { var (seeders, leechers, completed) = await _trackerScraper.ScrapeHdTorrents( torrentItem.InfoHash, torrentItem.DownloadKey); torrentItem.Seeders = seeders; torrentItem.Leechers = leechers; torrentItem.Completed = completed; Console.WriteLine($"Scraped stats for {torrentItem.Title}: S:{seeders} L:{leechers} C:{completed}"); } var metadata = await _movieMetadataService.GetMovieAsync(torrentItem.MovieName, torrentItem.Year); if (metadata != null) { torrentItem.Metadata = metadata; var tmdbService = new TmdbService(); torrentItem.Metadata.Providers = await tmdbService.GetWatchProvidersByTitleAsync(torrentItem.Title, torrentItem.Year); } _context.TorrentItems.Add(torrentItem); var savedChanges = await _context.SaveChangesAsync(); Console.WriteLine($"SaveChanges returned: {savedChanges}"); // Kolla auto-download regler if (await ShouldAutoDownload(torrentItem)) { await ProcessTorrentDownload(torrentItem); } } else { var howLongAgo = DateTime.UtcNow.AddHours(-6); if (torrentItem.PublishDate >= howLongAgo) { var existing = await _context.TorrentItems .FirstOrDefaultAsync(t => t.InfoHash == torrentItem.InfoHash || (t.Title == torrentItem.Title && t.RssSource == feed.Name)); if (existing != null) { var (seeders, leechers, completed) = await _trackerScraper.ScrapeHdTorrents( existing.InfoHash, existing.DownloadKey); existing.Seeders = seeders; existing.Leechers = leechers; existing.Completed = completed; _context.TorrentItems.Update(existing); await _context.SaveChangesAsync(); Console.WriteLine($"Updated stats for {existing.Title}: S:{seeders} L:{leechers} C:{completed}"); } } } } catch (Exception itemEx) { Console.WriteLine($"Error processing individual item: {itemEx.Message}"); Console.WriteLine($"Stack trace: {itemEx.StackTrace}"); } } feed.LastChecked = DateTime.UtcNow; await _context.SaveChangesAsync(); } catch (Exception ex) { Console.WriteLine($"Error in ProcessSingleFeed: {ex.Message}"); Console.WriteLine($"Stack trace: {ex.StackTrace}"); } } private TorrentItem ParseRssItem(XElement item, RssFeed feed) { var title = item.Element("title")?.Value ?? "Unknown Title"; var description = item.Element("description")?.Value ?? ""; // Fix: Default till tom sträng var pubDate = DateTime.TryParse(item.Element("pubDate")?.Value, out var date) ? date : DateTime.UtcNow; var link = item.Element("link")?.Value ?? ""; var infoHash = ExtractInfoHashFromUrl(link); var downloadKey = ExtractParameterFromUrl(link, "key"); var token = ExtractParameterFromUrl(link, "token"); var (movieName, year) = ParseMovieNameAndYear(title); var magnetLink = ""; if (!string.IsNullOrEmpty(infoHash)) { magnetLink = $"magnet:?xt=urn:btih:{infoHash}&dn={Uri.EscapeDataString(title)}"; } return new TorrentItem { Title = title ?? "Unknown Title", // Garanterat inte null Description = description ?? string.Empty, // Garanterat inte null TorrentUrl = string.IsNullOrEmpty(link) ? null : link, MagnetLink = string.IsNullOrEmpty(magnetLink) ? null : magnetLink, InfoHash = string.IsNullOrEmpty(infoHash) ? null : infoHash, PublishDate = pubDate, RssSource = feed.Name ?? "Unknown", // Garanterat inte null Status = TorrentStatus.New, DownloadKey = string.IsNullOrEmpty(downloadKey) ? null : downloadKey, Token = string.IsNullOrEmpty(token) ? null : token, MovieName = string.IsNullOrEmpty(movieName) ? null : movieName, Year = year, Category = DetermineCategory(title), CreatedAt = DateTime.UtcNow }; } private (string movieName, int? year) ParseMovieNameAndYear(string title) { // Exempel titlar: // "Bring It on Fight to the Finish 2009 BluRay 1080p DDP 5.1 x264-hallowed" // "Deadpool & Wolverine 2024 Hybrid 1080p UHD BluRay DD+5.1 Atmos DV HDR10+ x265-HiDt" var movieName = ""; int? year = null; // Leta efter år (4 siffror mellan 1900-2099) var yearMatch = System.Text.RegularExpressions.Regex.Match(title, @"\b(19\d{2}|20\d{2})\b"); if (yearMatch.Success && int.TryParse(yearMatch.Groups[1].Value, out var parsedYear)) { year = parsedYear; // Ta allt före året som filmnamn var yearIndex = yearMatch.Index; movieName = title.Substring(0, yearIndex).Trim(); } else { // Om inget år hittas, ta första delen före vanliga release-keywords var keywordMatch = System.Text.RegularExpressions.Regex.Match(title, @"\b(BluRay|WEB-DL|WEBRip|HDTV|DVDRIP|REMUX|UHD|1080p|720p|480p|2160p)\b", RegexOptions.IgnoreCase); if (keywordMatch.Success) { movieName = title.Substring(0, keywordMatch.Index).Trim(); } else { // Fallback: ta första delen innan sista bindestreck var lastDashIndex = title.LastIndexOf('-'); if (lastDashIndex > 0) { movieName = title.Substring(0, lastDashIndex).Trim(); } else { movieName = title; } } } // Rensa bort vanliga suffix från filmnamnet movieName = System.Text.RegularExpressions.Regex.Replace(movieName, @"\b(REMASTERED|REPACK|PROPER|REAL|EXTENDED|DIRECTORS?\.?CUT|UNRATED)\b", "", RegexOptions.IgnoreCase).Trim(); return (movieName, year); } private string ExtractInfoHashFromUrl(string url) { if (string.IsNullOrEmpty(url)) return ""; var match = System.Text.RegularExpressions.Regex.Match(url, @"hash=([a-fA-F0-9]{40})"); return match.Success ? match.Groups[1].Value.ToUpper() : ""; } private string ExtractParameterFromUrl(string url, string paramName) { if (string.IsNullOrEmpty(url)) return ""; var match = System.Text.RegularExpressions.Regex.Match(url, $@"{paramName}=([^&]+)"); return match.Success ? Uri.UnescapeDataString(match.Groups[1].Value) : ""; } private string DetermineCategory(string title) { if (System.Text.RegularExpressions.Regex.IsMatch(title, @"\b(S\d{2}E\d{2}|Season|Episode)\b", RegexOptions.IgnoreCase)) return "TV"; else return "Movies"; } private async Task ShouldAutoDownload(TorrentItem item) { var rules = await _context.DownloadRules.Where(r => r.AutoDownload).ToListAsync(); return rules.Any(rule => (string.IsNullOrEmpty(rule.KeywordFilter) || item.Title.Contains(rule.KeywordFilter, StringComparison.OrdinalIgnoreCase)) && (string.IsNullOrEmpty(rule.CategoryFilter) || item.Category == rule.CategoryFilter) && item.Seeders >= rule.MinSeeders && (rule.MaxSize == 0 || item.Size <= rule.MaxSize)); } private async Task ProcessTorrentDownload(TorrentItem item) { try { if (!string.IsNullOrEmpty(item.MagnetLink)) { _logger.LogInformation("Starting magnet download for {Title}", item.Title); item.Status = TorrentStatus.Downloaded; } else if (!string.IsNullOrEmpty(item.TorrentUrl)) { var torrentData = await _httpClient.GetByteArrayAsync(item.TorrentUrl); item.Status = TorrentStatus.Downloaded; } await _context.SaveChangesAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error downloading torrent {Title}", item.Title); item.Status = TorrentStatus.Failed; await _context.SaveChangesAsync(); } } } }