Torrent changes again!
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -8,20 +8,54 @@ public class TorrentController : Controller
|
||||
private readonly DelugeClient _deluge;
|
||||
private readonly MovieMetadataService _movieMetadataService;
|
||||
|
||||
public TorrentController(ITorrentService torrentService, ILogger<TorrentController> logger, DelugeClient delugeClient, MovieMetadataService movieMetadataService)
|
||||
public TorrentController(
|
||||
ITorrentService torrentService,
|
||||
ILogger<TorrentController> logger,
|
||||
DelugeClient delugeClient,
|
||||
MovieMetadataService movieMetadataService)
|
||||
{
|
||||
_torrentService = torrentService;
|
||||
_logger = logger;
|
||||
_deluge = delugeClient;
|
||||
_movieMetadataService = movieMetadataService;
|
||||
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Index()
|
||||
public async Task<IActionResult> Index(int page = 1, string sort = "date", string range = "all")
|
||||
{
|
||||
var torrents = await _torrentService.GetRecentTorrentsAsync(50);
|
||||
return View(torrents); // skicka lista av TorrentItem
|
||||
var pageSize = 20;
|
||||
var torrents = await _torrentService.GetRecentTorrentsAsync(500);
|
||||
|
||||
// filtrera på tidsintervall
|
||||
torrents = range switch
|
||||
{
|
||||
"day" => torrents.Where(t => t.PublishDate > DateTime.UtcNow.AddDays(-1)).ToList(),
|
||||
"week" => torrents.Where(t => t.PublishDate > DateTime.UtcNow.AddDays(-7)).ToList(),
|
||||
"month" => torrents.Where(t => t.PublishDate > DateTime.UtcNow.AddMonths(-1)).ToList(),
|
||||
_ => torrents
|
||||
};
|
||||
|
||||
// sortera
|
||||
torrents = sort switch
|
||||
{
|
||||
"seeders" => torrents.OrderByDescending(t => t.Seeders).ToList(),
|
||||
"leechers" => torrents.OrderByDescending(t => t.Leechers).ToList(),
|
||||
"title" => torrents.OrderBy(t => t.MovieName).ToList(),
|
||||
_ => torrents.OrderByDescending(t => t.PublishDate).ToList(),
|
||||
};
|
||||
|
||||
var pagedItems = torrents.Skip((page - 1) * pageSize).Take(pageSize).ToList();
|
||||
|
||||
var vm = new TorrentListViewModel
|
||||
{
|
||||
Items = pagedItems,
|
||||
CurrentPage = page,
|
||||
TotalPages = (int)Math.Ceiling(torrents.Count / (double)pageSize),
|
||||
CurrentSort = sort,
|
||||
CurrentRange = range
|
||||
};
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
@@ -41,7 +75,6 @@ public class TorrentController : Controller
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Upload(TorrentUploadViewModel model)
|
||||
@@ -66,16 +99,14 @@ public class TorrentController : Controller
|
||||
|
||||
try
|
||||
{
|
||||
// Parsa torrent-filen
|
||||
var torrentInfo = await _torrentService.ParseTorrentAsync(model.TorrentFile);
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(torrentInfo.ErrorMessage))
|
||||
{
|
||||
ModelState.AddModelError("", torrentInfo.ErrorMessage);
|
||||
return View("Index", model);
|
||||
}
|
||||
|
||||
// Försök hämta tracker-statistik
|
||||
torrentInfo = await _torrentService.FetchTrackerStatsAsync(torrentInfo);
|
||||
|
||||
model.TorrentInfo = torrentInfo;
|
||||
@@ -105,7 +136,7 @@ public class TorrentController : Controller
|
||||
};
|
||||
|
||||
var updatedInfo = await _torrentService.FetchTrackerStatsAsync(torrentInfo);
|
||||
|
||||
|
||||
return Json(new
|
||||
{
|
||||
success = updatedInfo.HasTrackerData,
|
||||
@@ -121,19 +152,4 @@ public class TorrentController : Controller
|
||||
return Json(new { success = false, error = "Fel vid uppdatering" });
|
||||
}
|
||||
}
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> List()
|
||||
{
|
||||
try
|
||||
{
|
||||
var torrents = await _torrentService.GetRecentTorrentsAsync(50);
|
||||
return View(torrents);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Fel vid hämtning av torrent-lista");
|
||||
return View(new List<TorrentItem>());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,18 @@ public class MovieMetadata
|
||||
public string? ImdbID { get; set; }
|
||||
public string? Providers { get; set; }
|
||||
}
|
||||
|
||||
public class TorrentListViewModel
|
||||
{
|
||||
public List<TorrentItem> Items { get; set; } = new();
|
||||
public int CurrentPage { get; set; }
|
||||
public int TotalPages { get; set; }
|
||||
public string CurrentSort { get; set; } = "date";
|
||||
public string CurrentRange { get; set; } = "all";
|
||||
public string CurrentPeriod { get; set; } = "all"; // day/week/month/all
|
||||
|
||||
}
|
||||
|
||||
public class JustWatchResponse
|
||||
{
|
||||
public Data data { get; set; }
|
||||
|
||||
@@ -1,64 +1,141 @@
|
||||
@model IEnumerable<TorrentItem>
|
||||
@model TorrentListViewModel
|
||||
<link rel="stylesheet" href="~/css/torrent.css" />
|
||||
|
||||
<!-- Periodval -->
|
||||
<div class="torrent-period">
|
||||
<button class="@(Model.CurrentRange == "day" ? "active" : "")" onclick="location.href='?range=day'">Dag</button>
|
||||
<button class="@(Model.CurrentRange == "week" ? "active" : "")" onclick="location.href='?range=week'">Vecka</button>
|
||||
<button class="@(Model.CurrentRange == "month" ? "active" : "")" onclick="location.href='?range=month'">Månad</button>
|
||||
<button class="@(Model.CurrentRange == "all" ? "active" : "")" onclick="location.href='?range=all'">All time</button>
|
||||
</div>
|
||||
|
||||
<!-- Torrentlista med versioner -->
|
||||
<div class="torrent-list">
|
||||
<div class="torrent-header">
|
||||
<div class="col-title">Film</div>
|
||||
<div class="col-center">Seeders</div>
|
||||
<div class="col-center">Leechers</div>
|
||||
<div class="col-right">Datum</div>
|
||||
<div class="col-right">Åtgärd</div>
|
||||
<div onclick="sortBy('title')" class="@(Model.CurrentSort == "title" ? "active" : "")">Titel</div>
|
||||
<div onclick="sortBy('date')" class="@(Model.CurrentSort == "date" ? "active" : "")">Tid / Datum</div>
|
||||
<div onclick="sortBy('seeders')" class="@(Model.CurrentSort == "seeders" ? "active" : "")">Seeders</div>
|
||||
<div onclick="sortBy('leechers')" class="@(Model.CurrentSort == "leechers" ? "active" : "")">Leechers</div>
|
||||
<div>Åtgärd</div>
|
||||
</div>
|
||||
|
||||
@foreach (var t in Model)
|
||||
@foreach (var group in Model.Items
|
||||
.GroupBy(t => new { t.MovieName, t.Metadata?.Year })
|
||||
.Select(g => new
|
||||
{
|
||||
Title = g.Key.MovieName,
|
||||
Year = g.Key.Year,
|
||||
Versions = g
|
||||
.OrderByDescending(t => t.Title.Contains("Fix") || t.Title.Contains("Repack"))
|
||||
.ThenByDescending(t => t.Seeders)
|
||||
.ToList()
|
||||
}))
|
||||
{
|
||||
<div class="torrent-row">
|
||||
var showBadge = group.Versions.Count > 1;
|
||||
var main = group.Versions.First();
|
||||
|
||||
<!-- Huvudrad -->
|
||||
<div class="torrent-row torrent-group-title @(group.Versions.Count == 1 ? "last-row" : "")">
|
||||
<div class="col-title">
|
||||
<img src="@t.Metadata?.Poster" alt="@t.Title" class="poster" onclick="showLightbox(this.src)" />
|
||||
<a href="@main.Metadata?.Poster" class="glightbox">
|
||||
<img src="@main.Metadata?.Poster" alt="@main.Title" class="poster" />
|
||||
</a>
|
||||
<div class="title-info">
|
||||
@if (!string.IsNullOrEmpty(t.Metadata?.Title))
|
||||
@if (!string.IsNullOrEmpty(main.Metadata?.Title))
|
||||
{
|
||||
<strong>@t.MovieName (@t.Metadata?.Year)</strong>
|
||||
<strong>@group.Title (@group.Year)</strong>
|
||||
} else
|
||||
{
|
||||
<strong>@t.Title</strong>
|
||||
<strong>@main.Title</strong>
|
||||
}
|
||||
|
||||
|
||||
<div class="meta">
|
||||
@if (!string.IsNullOrEmpty(t.Metadata?.Genre))
|
||||
@if (!string.IsNullOrEmpty(main.Metadata?.Genre))
|
||||
{
|
||||
<span class="genre">@t.Metadata.Genre</span>
|
||||
<span class="genre">@main.Metadata.Genre</span>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(t.Metadata?.ImdbRating))
|
||||
@if (!string.IsNullOrEmpty(main.Metadata?.ImdbID))
|
||||
{
|
||||
<span class="imdb">⭐ @t.Metadata.ImdbRating</span>
|
||||
<a class="imdb" href="https://www.imdb.com/title/@main.Metadata.ImdbID" target="_blank">
|
||||
⭐ @main.Metadata.ImdbRating
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</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-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>
|
||||
<div class="col-date">
|
||||
<div class="time">@main.PublishDate.ToString("HH:mm")</div>
|
||||
<div class="date">@main.PublishDate.ToString("yyyy-MM-dd")</div>
|
||||
</div>
|
||||
<div class="col-center @(main.Seeders > 40 ? "highlight-green" : "")">@main.Seeders</div>
|
||||
<div class="col-center highlight-red">@main.Leechers</div>
|
||||
<div class="col-action">
|
||||
<form asp-controller="Torrent" asp-action="Add" method="post" onsubmit="return confirmDownload('@main.Title')">
|
||||
<input type="hidden" name="torrentUrl" value="@main.TorrentUrl" />
|
||||
<button type="submit" class="btn-add btn-small">➕ Ladda ner</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Versioner -->
|
||||
@if (group.Versions.Count > 1)
|
||||
{
|
||||
var lastVersion = group.Versions.Last();
|
||||
@foreach (var t in group.Versions.Skip(0))
|
||||
{
|
||||
var isLast = t == lastVersion;
|
||||
<div class="torrent-row torrent-version @(isLast ? "last-version" : "")" title="@t.Title">
|
||||
<div class="col-title">
|
||||
<strong>@t.Title</strong>
|
||||
</div>
|
||||
<div>
|
||||
@t.PublishDate.ToString("HH:mm yyyy-MM-dd")
|
||||
</div>
|
||||
<div class="@(t.Seeders > 40 ? "highlight-green" : "")">
|
||||
@t.Seeders
|
||||
</div>
|
||||
<div class="highlight-red">
|
||||
@t.Leechers
|
||||
</div>
|
||||
<div class="col-action">
|
||||
<form asp-controller="Torrent" asp-action="Add" method="post" onsubmit="return confirmDownload('@t.Title')">
|
||||
<input type="hidden" name="torrentUrl" value="@t.TorrentUrl" />
|
||||
<button type="submit" class="btn-add btn-small">➕</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Lightbox för poster -->
|
||||
<div id="posterLightbox" onclick="this.style.display='none'">
|
||||
<img id="lightboxImg" />
|
||||
<!-- Pagination -->
|
||||
<div class="pagination">
|
||||
@for (int i = 1; i <= Model.TotalPages; i++)
|
||||
{
|
||||
if (i == Model.CurrentPage)
|
||||
{
|
||||
<span class="current">@i</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a asp-route-page="@i" asp-route-sort="@Model.CurrentSort" asp-route-range="@Model.CurrentRange">@i</a>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/glightbox/dist/css/glightbox.min.css" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/glightbox/dist/js/glightbox.min.js"></script>
|
||||
<script>
|
||||
function showLightbox(src) {
|
||||
const lb = document.getElementById('posterLightbox');
|
||||
const img = document.getElementById('lightboxImg');
|
||||
img.src = src;
|
||||
lb.style.display = 'flex';
|
||||
const lightbox = GLightbox({
|
||||
selector: '.glightbox'
|
||||
});
|
||||
function sortBy(field){
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('sort', field);
|
||||
window.location = url;
|
||||
}
|
||||
function confirmDownload(title) {
|
||||
return confirm(`Vill du ladda ner "${title}"?`);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,70 +1,111 @@
|
||||
/* ==========================================================
|
||||
TORRENT LIST STYLING
|
||||
========================================================== */
|
||||
|
||||
.torrent-list {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 10px 16px;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
|
||||
margin: 16px 0;
|
||||
.torrent-list {
|
||||
max-width: 900px;
|
||||
margin: 16px auto;
|
||||
font-size: 14px;
|
||||
color: #1F2C3C;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.torrent-header, .torrent-row {
|
||||
display: grid;
|
||||
grid-template-columns: 4fr 1fr 1fr 2fr 1fr;
|
||||
grid-template-columns: 4fr 2fr 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.torrent-header {
|
||||
font-weight: 600;
|
||||
border-bottom: 2px solid #ccc;
|
||||
margin-bottom: 4px;
|
||||
border-bottom: 2px solid #333;
|
||||
color: #223344;
|
||||
}
|
||||
|
||||
.torrent-row {
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.torrent-row:hover {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.col-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.col-date {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.col-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.col-right {
|
||||
text-align: right;
|
||||
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;
|
||||
}
|
||||
|
||||
.poster {
|
||||
width: 28px;
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Versioner kompakt */
|
||||
.torrent-version {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px; /* Mellanslag mellan titel, datum, seeders, leechers och knapp */
|
||||
padding: 2px 36px;
|
||||
font-size: 11px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
|
||||
.torrent-version .col-title {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
white-space: wrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.torrent-version .col-date {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.torrent-version .col-center {
|
||||
flex: 0.6;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.torrent-version .col-action {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.torrent-version:hover {
|
||||
background-color: rgba(240, 240, 240, 0.3);
|
||||
}
|
||||
|
||||
/* Huvudrad fortfarande grid */
|
||||
.torrent-row.torrent-group-title {
|
||||
display: grid;
|
||||
grid-template-columns: 4fr 2fr 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
border-bottom: none;
|
||||
|
||||
padding: 6px 0;
|
||||
}
|
||||
.torrent-version.last-version {
|
||||
border-bottom: 1px solid #ccc; /* Streck under sista versionen */
|
||||
}
|
||||
.torrent-row:hover {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
.torrent-row.last-row {
|
||||
border-bottom: 1px solid #ccc; /* streck under sista raden i gruppen */
|
||||
}
|
||||
/* Highlight */
|
||||
.highlight-green {
|
||||
color: #00cc66;
|
||||
font-weight: bold;
|
||||
@@ -73,82 +114,75 @@
|
||||
.highlight-red {
|
||||
color: #cc3333;
|
||||
}
|
||||
.torrent-row form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn-add {
|
||||
background-color: #3399ff;
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 3px 8px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-add:hover {
|
||||
background-color: #2389e0;
|
||||
}
|
||||
|
||||
.poster {
|
||||
width: 25px;
|
||||
height: 35px;
|
||||
object-fit: cover;
|
||||
cursor: pointer;
|
||||
margin-right: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
.title-info {
|
||||
/* Periodval */
|
||||
.torrent-period {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.title-info strong {
|
||||
font-size: 14px;
|
||||
margin-bottom: 2px;
|
||||
.torrent-period button {
|
||||
background: #eee;
|
||||
border: none;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.meta {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
.torrent-period button.active {
|
||||
background: #3399ff;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Pagination */
|
||||
.pagination {
|
||||
text-align: center;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.genre {
|
||||
margin-right: 8px;
|
||||
color: #888;
|
||||
}
|
||||
.pagination a, .pagination span {
|
||||
display: inline-block;
|
||||
margin: 0 4px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
background: #eee;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.pagination .current {
|
||||
background: #3399ff;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pagination a:hover {
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
.imdb {
|
||||
font-size: 12px;
|
||||
color: #5a4800;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
/* 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;
|
||||
}
|
||||
.imdb:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
Reference in New Issue
Block a user