Test
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AngularJS.Core" Version="1.8.2" />
|
<PackageReference Include="AngularJS.Core" Version="1.8.2" />
|
||||||
|
<PackageReference Include="BencodeNET" Version="5.0.0" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.67" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.67" />
|
||||||
<PackageReference Include="Lib.Net.Http.WebPush" Version="3.3.1" />
|
<PackageReference Include="Lib.Net.Http.WebPush" Version="3.3.1" />
|
||||||
|
|
||||||
|
|||||||
99
Aberwyn/Controllers/TorrentController.cs
Normal file
99
Aberwyn/Controllers/TorrentController.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
public class TorrentController : Controller
|
||||||
|
{
|
||||||
|
private readonly ITorrentService _torrentService;
|
||||||
|
private readonly ILogger<TorrentController> _logger;
|
||||||
|
|
||||||
|
public TorrentController(ITorrentService torrentService, ILogger<TorrentController> logger)
|
||||||
|
{
|
||||||
|
_torrentService = torrentService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View(new TorrentUploadViewModel());
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Upload(TorrentUploadViewModel model)
|
||||||
|
{
|
||||||
|
if (model.TorrentFile == null || model.TorrentFile.Length == 0)
|
||||||
|
{
|
||||||
|
ModelState.AddModelError("TorrentFile", "Vänligen välj en torrent-fil");
|
||||||
|
return View("Index", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!model.TorrentFile.FileName.EndsWith(".torrent", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ModelState.AddModelError("TorrentFile", "Endast .torrent filer är tillåtna");
|
||||||
|
return View("Index", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.TorrentFile.Length > 10 * 1024 * 1024) // 10MB limit
|
||||||
|
{
|
||||||
|
ModelState.AddModelError("TorrentFile", "Filen är för stor (max 10MB)");
|
||||||
|
return View("Index", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
model.ShowResults = true;
|
||||||
|
|
||||||
|
return View("Index", model);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Fel vid uppladdning av torrent");
|
||||||
|
ModelState.AddModelError("", "Ett oväntat fel inträffade");
|
||||||
|
return View("Index", model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> RefreshStats(string infoHash, string scrapeUrl)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var torrentInfo = new TorrentInfo
|
||||||
|
{
|
||||||
|
InfoHash = infoHash,
|
||||||
|
ScrapeUrl = scrapeUrl,
|
||||||
|
InfoHashBytes = Convert.FromHexString(infoHash.Replace("%", ""))
|
||||||
|
};
|
||||||
|
|
||||||
|
var updatedInfo = await _torrentService.FetchTrackerStatsAsync(torrentInfo);
|
||||||
|
|
||||||
|
return Json(new
|
||||||
|
{
|
||||||
|
success = updatedInfo.HasTrackerData,
|
||||||
|
seeders = updatedInfo.Seeders,
|
||||||
|
leechers = updatedInfo.Leechers,
|
||||||
|
completed = updatedInfo.Completed,
|
||||||
|
error = updatedInfo.ErrorMessage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Fel vid uppdatering av tracker-stats");
|
||||||
|
return Json(new { success = false, error = "Fel vid uppdatering" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Aberwyn/Models/TorrentInfo.cs
Normal file
29
Aberwyn/Models/TorrentInfo.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
public class TorrentInfo
|
||||||
|
{
|
||||||
|
public string FileName { get; set; }
|
||||||
|
public string AnnounceUrl { get; set; }
|
||||||
|
public string ScrapeUrl { get; set; }
|
||||||
|
public string InfoHash { get; set; }
|
||||||
|
public byte[] InfoHashBytes { get; set; }
|
||||||
|
public long Size { get; set; }
|
||||||
|
public int Seeders { get; set; } = 0;
|
||||||
|
public int Leechers { get; set; } = 0;
|
||||||
|
public int Completed { get; set; } = 0;
|
||||||
|
public bool HasTrackerData { get; set; } = false;
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
}
|
||||||
|
public class TrackerInfo
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public bool SupportsScraping { get; set; }
|
||||||
|
public bool RequiresAuth { get; set; }
|
||||||
|
public bool IsPrivate { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TorrentUploadViewModel
|
||||||
|
{
|
||||||
|
public IFormFile TorrentFile { get; set; }
|
||||||
|
public TorrentInfo TorrentInfo { get; set; }
|
||||||
|
public bool ShowResults { get; set; } = false;
|
||||||
|
}
|
||||||
@@ -64,6 +64,8 @@ catch (Exception ex)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
builder.Services.AddHttpClient<ITorrentService, TorrentService>();
|
||||||
|
builder.Services.AddScoped<ITorrentService, TorrentService>();
|
||||||
|
|
||||||
// Add services to the container
|
// Add services to the container
|
||||||
builder.Services.AddControllersWithViews()
|
builder.Services.AddControllersWithViews()
|
||||||
|
|||||||
225
Aberwyn/Views/Torrent/Index.cshtml
Normal file
225
Aberwyn/Views/Torrent/Index.cshtml
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
@model TorrentUploadViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Torrent Analyzer";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="mb-0">
|
||||||
|
<i class="fas fa-download"></i> Torrent Analyzer
|
||||||
|
</h3>
|
||||||
|
<p class="text-muted mb-0">Ladda upp en torrent-fil för att se seeders/leechers</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
@if (!ViewData.ModelState.IsValid)
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
@foreach (var error in ViewData.ModelState.Values.SelectMany(v => v.Errors))
|
||||||
|
{
|
||||||
|
<div>@error.ErrorMessage</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<form asp-action="Upload" method="post" enctype="multipart/form-data" class="mb-4">
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="torrentFile" class="form-label">Välj torrent-fil</label>
|
||||||
|
<input type="file"
|
||||||
|
class="form-control"
|
||||||
|
asp-for="TorrentFile"
|
||||||
|
accept=".torrent"
|
||||||
|
required>
|
||||||
|
<div class="form-text">Endast .torrent filer, max 10MB</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-upload"></i> Analysera Torrent
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@if (Model.ShowResults && Model.TorrentInfo != null)
|
||||||
|
{
|
||||||
|
<hr>
|
||||||
|
<div class="torrent-results">
|
||||||
|
<h4 class="mb-3">
|
||||||
|
<i class="fas fa-info-circle"></i> Torrent Information
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-3"><strong>Filnamn:</strong></div>
|
||||||
|
<div class="col-sm-9">@Model.TorrentInfo.FileName</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-3"><strong>Storlek:</strong></div>
|
||||||
|
<div class="col-sm-9">@FormatFileSize(Model.TorrentInfo.Size)</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-3"><strong>Announce URL:</strong></div>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<small class="text-muted font-monospace">@Model.TorrentInfo.AnnounceUrl</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-3"><strong>Info Hash:</strong></div>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<small class="text-muted font-monospace">@Model.TorrentInfo.InfoHash</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(Model.TorrentInfo.ErrorMessage))
|
||||||
|
{
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
<strong>Tracker-info:</strong> @Model.TorrentInfo.ErrorMessage
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (Model.TorrentInfo.HasTrackerData)
|
||||||
|
{
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-chart-bar"></i> Tracker Statistik
|
||||||
|
</h5>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-sm btn-outline-primary"
|
||||||
|
onclick="refreshStats()"
|
||||||
|
id="refreshBtn">
|
||||||
|
<i class="fas fa-sync-alt"></i> Uppdatera
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row text-center">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="stat-box p-3">
|
||||||
|
<h3 class="text-success mb-1" id="seeders">@Model.TorrentInfo.Seeders</h3>
|
||||||
|
<small class="text-muted">Seeders</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="stat-box p-3">
|
||||||
|
<h3 class="text-warning mb-1" id="leechers">@Model.TorrentInfo.Leechers</h3>
|
||||||
|
<small class="text-muted">Leechers</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="stat-box p-3">
|
||||||
|
<h3 class="text-info mb-1" id="completed">@Model.TorrentInfo.Completed</h3>
|
||||||
|
<small class="text-muted">Completed</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
Tracker-statistik kunde inte hämtas. Detta kan bero på att trackern inte stöder scraping eller kräver autentisering.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
string FormatFileSize(long bytes)
|
||||||
|
{
|
||||||
|
if (bytes == 0) return "Okänd storlek";
|
||||||
|
|
||||||
|
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
|
||||||
|
int order = 0;
|
||||||
|
double size = bytes;
|
||||||
|
|
||||||
|
while (size >= 1024 && order < sizes.Length - 1)
|
||||||
|
{
|
||||||
|
order++;
|
||||||
|
size = size / 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{size:0.##} {sizes[order]}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.stat-box {
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: white;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.torrent-results {
|
||||||
|
animation: fadeIn 0.5s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(20px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-monospace {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function refreshStats() {
|
||||||
|
const refreshBtn = document.getElementById('refreshBtn');
|
||||||
|
const seedersEl = document.getElementById('seeders');
|
||||||
|
const leechersEl = document.getElementById('leechers');
|
||||||
|
const completedEl = document.getElementById('completed');
|
||||||
|
|
||||||
|
if (!seedersEl) return; // Ingen tracker data att uppdatera
|
||||||
|
|
||||||
|
refreshBtn.disabled = true;
|
||||||
|
refreshBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Uppdaterar...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('@Url.Action("RefreshStats", "Torrent")', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
infoHash: '@Model.TorrentInfo?.InfoHash',
|
||||||
|
scrapeUrl: '@Model.TorrentInfo?.ScrapeUrl'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
seedersEl.textContent = data.seeders;
|
||||||
|
leechersEl.textContent = data.leechers;
|
||||||
|
completedEl.textContent = data.completed;
|
||||||
|
|
||||||
|
// Visa success animation
|
||||||
|
[seedersEl, leechersEl, completedEl].forEach(el => {
|
||||||
|
el.style.transform = 'scale(1.1)';
|
||||||
|
setTimeout(() => el.style.transform = 'scale(1)', 200);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alert('Fel vid uppdatering: ' + (data.error || 'Okänt fel'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fel:', error);
|
||||||
|
alert('Nätverksfel vid uppdatering');
|
||||||
|
} finally {
|
||||||
|
refreshBtn.disabled = false;
|
||||||
|
refreshBtn.innerHTML = '<i class="fas fa-sync-alt"></i> Uppdatera';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user