304 lines
13 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|
|
} |