More torrent and omdb
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Elias Jansson
2025-08-20 00:39:19 +02:00
parent 146c557c25
commit 6b19f08d6b
12 changed files with 1703 additions and 9 deletions

View File

@@ -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)

View File

@@ -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);
}

View 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;
}
}
}

View 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;
}
}
}
}

View File

@@ -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)
{

File diff suppressed because it is too large Load Diff

View File

@@ -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");
}
}
}

View File

@@ -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")

View File

@@ -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

View File

@@ -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()

View File

@@ -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>

View File

@@ -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;
}