Files
Aberwyn/Aberwyn/Data/RssProcessor.cs
Elias Jansson 64aa9cf716
All checks were successful
continuous-integration/drone/push Build is passing
Budget improvements and list!
2025-09-11 22:42:56 +02:00

304 lines
13 KiB
C#

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<RssProcessor> _logger;
private readonly HttpClient _httpClient;
private readonly HdTorrentsTrackerScraper _trackerScraper;
private readonly MovieMetadataService _movieMetadataService;
public RssProcessor(ApplicationDbContext context, ILogger<RssProcessor> logger,
HttpClient httpClient, HdTorrentsTrackerScraper trackerScraper, MovieMetadataService movieMetadataService)
{
_context = context;
_logger = logger;
_httpClient = httpClient;
_trackerScraper = trackerScraper;
_movieMetadataService = movieMetadataService;
}
public async Task ProcessRssFeeds()
{
var debug = false;
var oneHourAgo = DateTime.UtcNow.AddHours(-1);
if (debug)
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.MovieName, 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<bool> 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();
}
}
}
}