This commit is contained in:
@@ -1,14 +1,20 @@
|
||||
using Aberwyn.Data;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
public class TorrentController : Controller
|
||||
{
|
||||
private readonly ITorrentService _torrentService;
|
||||
private readonly ILogger<TorrentController> _logger;
|
||||
private readonly DelugeClient _deluge;
|
||||
private readonly MovieMetadataService _movieMetadataService;
|
||||
|
||||
public TorrentController(ITorrentService torrentService, ILogger<TorrentController> logger)
|
||||
public TorrentController(ITorrentService torrentService, ILogger<TorrentController> logger, DelugeClient delugeClient, MovieMetadataService movieMetadataService)
|
||||
{
|
||||
_torrentService = torrentService;
|
||||
_logger = logger;
|
||||
_deluge = delugeClient;
|
||||
_movieMetadataService = movieMetadataService;
|
||||
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@@ -18,6 +24,24 @@ public class TorrentController : Controller
|
||||
return View(torrents); // skicka lista av TorrentItem
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Add(string torrentUrl)
|
||||
{
|
||||
if (await _deluge.LoginAsync("deluge1"))
|
||||
{
|
||||
var success = await _deluge.AddTorrentUrlAsync(torrentUrl);
|
||||
if (success)
|
||||
{
|
||||
TempData["Message"] = "Torrent tillagd i Deluge!";
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
}
|
||||
|
||||
TempData["Error"] = "Misslyckades att lägga till torrent.";
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Upload(TorrentUploadViewModel model)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Aberwyn.Models;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace Aberwyn.Data
|
||||
{
|
||||
@@ -34,6 +35,8 @@ namespace Aberwyn.Data
|
||||
DisplayOrder = 1
|
||||
}
|
||||
);
|
||||
builder.Entity<TorrentItem>()
|
||||
.OwnsOne(t => t.Metadata);
|
||||
}
|
||||
|
||||
|
||||
|
||||
70
Aberwyn/Data/DelugeClient.cs
Normal file
70
Aberwyn/Data/DelugeClient.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using MySqlX.XDevAPI;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Aberwyn.Data
|
||||
{
|
||||
public class DelugeClient
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly string _url;
|
||||
private string _sessionId;
|
||||
|
||||
public DelugeClient(HttpClient httpClient, string baseUrl = "http://192.168.1.3:8112/json")
|
||||
{
|
||||
_http = httpClient;
|
||||
_url = baseUrl;
|
||||
}
|
||||
|
||||
public async Task<bool> LoginAsync(string password)
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
method = "auth.login",
|
||||
@params = new object[] { password },
|
||||
id = 1
|
||||
};
|
||||
|
||||
var response = await _http.PostAsJsonAsync(_url, payload);
|
||||
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
|
||||
|
||||
// spara sessioncookie för framtida requests
|
||||
if (response.Headers.TryGetValues("Set-Cookie", out var cookies))
|
||||
{
|
||||
_sessionId = cookies.FirstOrDefault()?.Split(';')[0];
|
||||
if (!_http.DefaultRequestHeaders.Contains("Cookie"))
|
||||
_http.DefaultRequestHeaders.Add("Cookie", _sessionId);
|
||||
}
|
||||
|
||||
return json.GetProperty("result").GetBoolean();
|
||||
}
|
||||
|
||||
public async Task<bool> AddMagnetAsync(string magnetLink)
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
method = "core.add_torrent_url",
|
||||
@params = new object[] { magnetLink, new { } },
|
||||
id = 2
|
||||
};
|
||||
|
||||
var response = await _http.PostAsJsonAsync(_url, payload);
|
||||
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
|
||||
return json.GetProperty("result").ValueKind != JsonValueKind.Null;
|
||||
}
|
||||
public async Task<bool> AddTorrentUrlAsync(string torrentUrl)
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
method = "core.add_torrent_url",
|
||||
@params = new object[] { torrentUrl, new { } },
|
||||
id = 3
|
||||
};
|
||||
|
||||
var response = await _http.PostAsJsonAsync(_url, payload);
|
||||
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
|
||||
|
||||
return json.GetProperty("result").ValueKind != JsonValueKind.Null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
50
Aberwyn/Data/MovieMetadataService.cs
Normal file
50
Aberwyn/Data/MovieMetadataService.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
namespace Aberwyn.Data
|
||||
{
|
||||
public class MovieMetadataService
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly string _apiKey = "6a666b45";
|
||||
|
||||
public MovieMetadataService(HttpClient httpClient)
|
||||
{
|
||||
_http = httpClient;
|
||||
}
|
||||
|
||||
public async Task<MovieMetadata?> GetMovieAsync(string title, int? year = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
MovieMetadata? metadata = null;
|
||||
|
||||
// Först försök med titel + år
|
||||
if (year.HasValue)
|
||||
{
|
||||
var urlWithYear = $"https://www.omdbapi.com/?t={Uri.EscapeDataString(title)}&y={year.Value}&apikey={_apiKey}";
|
||||
metadata = await _http.GetFromJsonAsync<MovieMetadata>(urlWithYear);
|
||||
}
|
||||
|
||||
// Om inget hittas, försök bara med titel
|
||||
if (metadata == null || string.IsNullOrEmpty(metadata.Title))
|
||||
{
|
||||
var urlTitleOnly = $"https://www.omdbapi.com/?t={Uri.EscapeDataString(title)}&apikey={_apiKey}";
|
||||
metadata = await _http.GetFromJsonAsync<MovieMetadata>(urlTitleOnly);
|
||||
}
|
||||
|
||||
// Returnera metadata om något hittades
|
||||
return (metadata != null && !string.IsNullOrEmpty(metadata.Title)) ? metadata : null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -12,14 +12,16 @@ namespace Aberwyn.Data
|
||||
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)
|
||||
HttpClient httpClient, HdTorrentsTrackerScraper trackerScraper, MovieMetadataService movieMetadataService)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_httpClient = httpClient;
|
||||
_trackerScraper = trackerScraper;
|
||||
_movieMetadataService = movieMetadataService;
|
||||
}
|
||||
|
||||
public async Task ProcessRssFeeds()
|
||||
@@ -77,6 +79,13 @@ namespace Aberwyn.Data
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
_context.TorrentItems.Add(torrentItem);
|
||||
var savedChanges = await _context.SaveChangesAsync();
|
||||
Console.WriteLine($"SaveChanges returned: {savedChanges}");
|
||||
@@ -87,6 +96,7 @@ namespace Aberwyn.Data
|
||||
}
|
||||
} else
|
||||
{
|
||||
|
||||
var howLongAgo = DateTime.UtcNow.AddHours(-6);
|
||||
if (torrentItem.PublishDate >= howLongAgo)
|
||||
{
|
||||
|
||||
1237
Aberwyn/Migrations/20250819221425_AddMovieMetadataToTorrentItem.Designer.cs
generated
Normal file
1237
Aberwyn/Migrations/20250819221425_AddMovieMetadataToTorrentItem.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,125 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddMovieMetadataToTorrentItem : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Actors",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Director",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Genre",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_ImdbID",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_ImdbRating",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Plot",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Poster",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Runtime",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Title",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Year",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Actors",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Director",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Genre",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_ImdbID",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_ImdbRating",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Plot",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Poster",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Runtime",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Title",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Year",
|
||||
table: "TorrentItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1143,6 +1143,55 @@ namespace Aberwyn.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TorrentItem", b =>
|
||||
{
|
||||
b.OwnsOne("MovieMetadata", "Metadata", b1 =>
|
||||
{
|
||||
b1.Property<int>("TorrentItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b1.Property<string>("Actors")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("Director")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("Genre")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("ImdbID")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("ImdbRating")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("Plot")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("Poster")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("Runtime")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("Year")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.HasKey("TorrentItemId");
|
||||
|
||||
b1.ToTable("TorrentItems");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("TorrentItemId");
|
||||
});
|
||||
|
||||
b.Navigation("Metadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("Preferences")
|
||||
|
||||
@@ -13,6 +13,7 @@ public class TorrentInfo
|
||||
public int Completed { get; set; } = 0;
|
||||
public bool HasTrackerData { get; set; } = false;
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
}
|
||||
public class TrackerInfo
|
||||
{
|
||||
@@ -65,6 +66,23 @@ public class TorrentItem
|
||||
public int? Year { get; set; }
|
||||
public string? DownloadKey { get; set; }
|
||||
public string? Token { get; set; }
|
||||
|
||||
public MovieMetadata? Metadata { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class MovieMetadata
|
||||
{
|
||||
public string Title { get; set; } = string.Empty; // required
|
||||
public string? Year { get; set; }
|
||||
public string? Poster { get; set; }
|
||||
public string? ImdbRating { get; set; }
|
||||
public string? Genre { get; set; }
|
||||
public string? Plot { get; set; }
|
||||
public string? Director { get; set; }
|
||||
public string? Actors { get; set; }
|
||||
public string? Runtime { get; set; }
|
||||
public string? ImdbID { get; set; }
|
||||
}
|
||||
|
||||
public enum TorrentStatus
|
||||
|
||||
@@ -59,7 +59,7 @@ try
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Fel vid läsning av setup.json: {ex.Message}");
|
||||
Console.WriteLine($"Fel vid läsning av setup.json: {ex.Message}");
|
||||
setup = new SetupSettings { IsConfigured = false };
|
||||
}
|
||||
|
||||
@@ -71,8 +71,9 @@ builder.Services.AddScoped<IRssProcessor, RssProcessor>();
|
||||
builder.Services.AddHostedService<TorrentRssService>();
|
||||
|
||||
builder.Services.AddHttpClient<HdTorrentsTrackerScraper>();
|
||||
builder.Services.AddScoped<HdTorrentsTrackerScraper>();
|
||||
|
||||
builder.Services.AddScoped<HdTorrentsTrackerScraper>();
|
||||
builder.Services.AddHttpClient<DelugeClient>();
|
||||
builder.Services.AddHttpClient<MovieMetadataService>();
|
||||
|
||||
// Add services to the container
|
||||
builder.Services.AddControllersWithViews()
|
||||
|
||||
@@ -8,16 +8,46 @@
|
||||
<div class="col-center">Leechers</div>
|
||||
<div class="col-center">Completed</div>
|
||||
<div class="col-right">Datum</div>
|
||||
<div class="col-right">Åtgärd</div>
|
||||
</div>
|
||||
|
||||
@foreach (var t in Model)
|
||||
{
|
||||
<div class="torrent-row">
|
||||
<div class="col-title">@t.Title</div>
|
||||
<div class="col-title">
|
||||
<img src="@t.Metadata?.Poster" alt="@t.Title" class="poster" onclick="showLightbox(this.src)" />
|
||||
<div class="title-info">
|
||||
<strong>@t.Title</strong>
|
||||
@if (!string.IsNullOrEmpty(t.Metadata?.ImdbRating))
|
||||
{
|
||||
<span class="imdb">⭐ @t.Metadata.ImdbRating</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-center @(t.Seeders > 40 ? "highlight-green" : "")">@t.Seeders</div>
|
||||
<div class="col-center highlight-red">@t.Leechers</div>
|
||||
<div class="col-center">@t.Completed</div>
|
||||
<div class="col-right">@t.PublishDate.ToString("yyyy-MM-dd HH:mm")</div>
|
||||
<div class="col-right col-action">
|
||||
<form asp-controller="Torrent" asp-action="Add" method="post">
|
||||
<input type="hidden" name="torrentUrl" value="@t.TorrentUrl" />
|
||||
<button type="submit" class="btn-add btn-small">➕ Lägg till</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Lightbox för poster -->
|
||||
<div id="posterLightbox" onclick="this.style.display='none'">
|
||||
<img id="lightboxImg" />
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function showLightbox(src) {
|
||||
const lb = document.getElementById('posterLightbox');
|
||||
const img = document.getElementById('lightboxImg');
|
||||
img.src = src;
|
||||
lb.style.display = 'flex';
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
.torrent-header, .torrent-row {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 1fr 1fr 1fr 2fr;
|
||||
grid-template-columns: 3fr 1fr 1fr 1fr 2fr 1fr;
|
||||
align-items: center;
|
||||
padding: 6px 0;
|
||||
}
|
||||
@@ -36,9 +36,9 @@
|
||||
}
|
||||
|
||||
.col-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.col-center {
|
||||
@@ -50,7 +50,22 @@
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
.title-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.imdb {
|
||||
font-size: 0.9em;
|
||||
color: rgba(0,0,0,0.15);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* Knapp alltid längst till höger */
|
||||
.col-action {
|
||||
text-align: right;
|
||||
}
|
||||
.highlight-green {
|
||||
color: #00cc66;
|
||||
font-weight: bold;
|
||||
@@ -59,3 +74,65 @@
|
||||
.highlight-red {
|
||||
color: #cc3333;
|
||||
}
|
||||
.torrent-row form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.btn-add {
|
||||
background-color: #3399ff;
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-add:hover {
|
||||
background-color: #2389e0;
|
||||
}
|
||||
|
||||
.poster {
|
||||
width: 40px;
|
||||
height: 60px;
|
||||
object-fit: cover;
|
||||
cursor: pointer;
|
||||
margin-right: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.title-info {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.imdb {
|
||||
font-size: 0.9em;
|
||||
color: #ffcc00;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
/* Lightbox */
|
||||
#posterLightbox {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.8);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
#posterLightbox img {
|
||||
max-width: 80%;
|
||||
max-height: 80%;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* Justera knappstorlek */
|
||||
.btn-small {
|
||||
padding: 4px 8px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user