Torrent stuff
This commit is contained in:
286
Aberwyn/Data/RssProcessor.cs
Normal file
286
Aberwyn/Data/RssProcessor.cs
Normal file
@@ -0,0 +1,286 @@
|
||||
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;
|
||||
|
||||
public RssProcessor(ApplicationDbContext context, ILogger<RssProcessor> logger,
|
||||
HttpClient httpClient, HdTorrentsTrackerScraper trackerScraper)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_httpClient = httpClient;
|
||||
_trackerScraper = trackerScraper;
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
_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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user