Merge remote-tracking branch 'rogerfar/master'

This commit is contained in:
YvesPa 2024-12-10 11:12:55 +01:00
commit f9335be163
24 changed files with 435 additions and 163 deletions

View file

@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.90] - 2024-12-06
### Changed
- Download individual files from Torbox instead of a zip file.
### Removed
- Removed ability to select instant files from AllDebrid.
## [2.0.89] - 2024-11-24
### Changed
- Disabled selecting of files as Real-Debrid was the only provider that supported that.
## [2.0.88] - 2024-11-24
### Changed
- Catch disabled instant availability endpoint from Real Debrid.
## [2.0.87] - 2024-11-18
### Added
- Torbox support.
- qBittorrent API authentication when no authentication is used.
### Changed
- .NET version changed to .NET 9.
- Changed download limit to be split by active torrents.
## [2.0.86] - 2024-09-03
### Changed
- Add potential fix for BASE_PATH.

View file

@ -21,7 +21,7 @@ RUN \
RUN ls -FCla /appclient/root
# Stage 2 - Build the backend
FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim-amd64 AS dotnet-build-env
FROM mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim-amd64 AS dotnet-build-env
ARG TARGETPLATFORM
ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64}
ARG BUILDPLATFORM
@ -66,14 +66,14 @@ RUN \
RUN \
if [ "$TARGETPLATFORM" = "linux/arm/v7" ] ; then \
wget https://download.visualstudio.microsoft.com/download/pr/c3bf3103-efdb-42e0-af55-bbf861a4215b/dc22eda8877933b8c6569e3823f18d21/aspnetcore-runtime-8.0.0-linux-musl-arm64.tar.gz && \
tar zxf aspnetcore-runtime-8.0.0-linux-musl-arm64.tar.gz -C /usr/share/dotnet ; \
wget https://download.visualstudio.microsoft.com/download/pr/59a041e1-921e-405e-8092-95333f80f9ca/63e83e3feb70e05ca05ed5db3c579be2/aspnetcore-runtime-9.0.0-linux-musl-arm.tar.gz && \
tar zxf aspnetcore-runtime-9.0.0-linux-musl-arm.tar.gz -C /usr/share/dotnet ; \
elif [ "$TARGETPLATFORM" = "linux/arm64" ] ; then \
wget https://download.visualstudio.microsoft.com/download/pr/c3bf3103-efdb-42e0-af55-bbf861a4215b/dc22eda8877933b8c6569e3823f18d21/aspnetcore-runtime-8.0.0-linux-musl-arm64.tar.gz && \
tar zxf aspnetcore-runtime-8.0.0-linux-musl-arm64.tar.gz -C /usr/share/dotnet ; \
wget https://download.visualstudio.microsoft.com/download/pr/e137f557-83cb-4f55-b1c8-e5f59ccd3cba/b8ba6f2ab96d0961757b71b00c201f31/aspnetcore-runtime-9.0.0-linux-musl-arm64.tar.gz && \
tar zxf aspnetcore-runtime-9.0.0-linux-musl-arm64.tar.gz -C /usr/share/dotnet ; \
else \
wget https://download.visualstudio.microsoft.com/download/pr/7aa33fc7-07fe-48c2-8e44-a4bfb4928535/3b96ec50970eee414895ef3a5b188bcd/aspnetcore-runtime-8.0.0-linux-musl-x64.tar.gz && \
tar zxf aspnetcore-runtime-8.0.0-linux-musl-x64.tar.gz -C /usr/share/dotnet ; \
wget https://download.visualstudio.microsoft.com/download/pr/86d7a513-fe71-4f37-b9ec-fdcf5566cce8/e72574fc82d7496c73a61f411d967d8e/aspnetcore-runtime-9.0.0-linux-musl-x64.tar.gz && \
tar zxf aspnetcore-runtime-9.0.0-linux-musl-x64.tar.gz -C /usr/share/dotnet ; \
fi
RUN \

View file

@ -6,7 +6,7 @@ This is a web interface to manage your torrents on Real-Debrid, AllDebrid or Pre
- Download all files from Real-Debrid, AllDebrid or Premiumize to your local machine automatically
- Unpack all files when finished downloading
- Implements a fake qBittorrent API so you can hook up other applications like Sonarr, Radarr or Couchpotato.
- Built with Angular 15 and .NET 8
- Built with Angular 15 and .NET 9
**You will need a Premium service at Real-Debrid, AllDebrid or Premiumize!**
@ -30,7 +30,7 @@ Instead of running in Docker you can install it as a service in Windows or Linux
## Windows Service
1. Make sure you have the ASP.NET Core Runtime 8 installed: [https://dotnet.microsoft.com/download/dotnet/8.0](https://dotnet.microsoft.com/download/dotnet/8.0)
1. Make sure you have the **ASP.NET Core Runtime 9.0.0** installed: [https://dotnet.microsoft.com/download/dotnet/9.0](https://dotnet.microsoft.com/download/dotnet/9.0)
2. Get the latest zip file from the Releases page and extract it to your host.
3. Open the `appsettings.json` file and replace the `LogLevel` `Path` to a path on your host.
4. In `appsettings.json` replace the `Database` `Path` to a path on your host.
@ -52,7 +52,7 @@ Instead of running in Docker you can install it as a service in Linux.
```rm packages-microsoft-prod.deb```
```sudo apt-get update && sudo apt-get install -y dotnet-sdk-8.0```
```sudo apt-get update && sudo apt-get install -y dotnet-sdk-9.0```
2. Get latest archive from [releases](https://github.com/rogerfar/rdt-client/releases):
```wget <zip_url>```
@ -188,7 +188,7 @@ By default the application runs in the root of your hosted address (i.e. https:/
- NodeJS
- NPM
- Angular CLI
- .NET 8
- .NET 9
- Visual Studio 2022
- (optional) Resharper

View file

@ -72,35 +72,6 @@
</p>
</div>
<div class="field">
<label class="label">Post Torrent Download Action</label>
<div class="control select is-fullwidth">
<select [(ngModel)]="downloadAction" [disabled]="provider === 'AllDebrid' || provider === 'Premiumize'">
<option [ngValue]="0">Download all files</option>
<option [ngValue]="1">Download all available files</option>
<option [ngValue]="2">Manually pick files</option>
</select>
</div>
<p class="help">When a torrent is fully downloaded on the provider, perform this action.</p>
<p class="help" *ngIf="provider === 'AllDebrid'">
This option is only available for RealDebrid. AllDebrid and Premiumize will always download the full torrent.
</p>
</div>
<div
class="notification is-success is-light"
*ngIf="provider === 'AllDebrid' && availableFiles && availableFiles.length > 0"
>
This torrent is available for immediate download.
</div>
<div
class="notification is-warning is-light"
*ngIf="provider === 'AllDebrid' && availableFiles && availableFiles.length === 0"
>
This torrent is not available for immediate download.
</div>
<div class="field">
<label class="label">Minimum file size to download</label>
<div class="control">
@ -221,39 +192,6 @@
</p>
</div>
</div>
<div fxFlex *ngIf="provider === 'RealDebrid'">
<div class="field">
<label class="label">Available files</label>
<p class="help">
These files are available for immediate download from your debrid provider. <br />
It is possible that there are more files in the torrent, which are not shown here.<br />
</p>
<div class="scroll-container">
<div class="field" *ngIf="downloadAction === 2">
<label class="checkbox is-fullwidth-label">
<input type="checkbox" [checked]="allSelected" (change)="downloadFileCheckedAll()" />
Select all
</label>
</div>
<div class="field" *ngIf="downloadAction === 2 && availableFiles !== null">
<label class="checkbox is-fullwidth-label" *ngFor="let file of availableFiles">
<input
type="checkbox"
[checked]="downloadFiles[file.filename]"
(change)="downloadFileChecked(file.filename)"
/>
{{ file.filename }} ({{ file.filesize | filesize }})
</label>
</div>
<div class="field" *ngIf="downloadAction !== 2 && availableFiles !== null">
<label class="is-fullwidth-label is-block" *ngFor="let file of availableFiles">
<span [ngClass]="{ 'strike-through': isRegexExcluded(file) }">{{ file.filename }}</span>
<span *ngIf="file.filesize > 0">({{ file.filesize | filesize }})</span>
</label>
</div>
</div>
</div>
</div>
</div>
<div class="field">

View file

@ -55,7 +55,7 @@
<a class="navbar-item" routerLink="profile"> Profile </a>
<a class="navbar-item" (click)="logout()"> Logout </a>
<hr class="navbar-divider" />
<a href="https://github.com/rogerfar/rdt-client" target="_blank" class="navbar-item">Version 2.0.86</a>
<a href="https://github.com/rogerfar/rdt-client" target="_blank" class="navbar-item">Version 2.0.90</a>
</div>
</div>
</div>

View file

@ -15,7 +15,11 @@ export class NavbarComponent implements OnInit {
public profile: Profile;
public providerLink: string;
constructor(private settingsService: SettingsService, private authService: AuthService, private router: Router) {}
constructor(
private settingsService: SettingsService,
private authService: AuthService,
private router: Router,
) {}
ngOnInit(): void {
this.settingsService.getProfile().subscribe((result) => {
@ -31,6 +35,9 @@ export class NavbarComponent implements OnInit {
case 'Premiumize':
this.providerLink = 'https://www.premiumize.me/';
break;
case 'TorBox':
this.providerLink = 'https://torbox.app/';
break;
}
});
}
@ -40,7 +47,7 @@ export class NavbarComponent implements OnInit {
() => {
this.router.navigate(['/login']);
},
(err) => {}
(err) => {},
);
}
}

View file

@ -42,7 +42,7 @@
<div class="notification is-primary">
To be able to use the Real-Debrid Client you need a premium subscription to download torrents.
<br /><br />
Not premium yet? You have the choice of 2 providers:
Not premium yet? You have the choice of 4 providers:
<br />
<a href="https://real-debrid.com/?id=1348683" target="_blank" rel="noopener"
>Use this link to sign up to Real-Debrid.</a
@ -55,6 +55,8 @@
<a href="https://www.premiumize.me/" target="_blank" rel="noopener"
>Use this link to sign up to Premiumize.</a
>
<a href="https://torbox.app/" target="_blank" rel="noopener"
>Use this link to sign up to TorBox.</a>
<br />
<small>(Referal links)</small>
</div>
@ -65,6 +67,7 @@
<option [ngValue]="0">Real-Debrid</option>
<option [ngValue]="1">AllDebrid</option>
<option [ngValue]="2">Premiumize</option>
<option [ngValue]="3">TorBox</option>
</select>
</div>
</div>
@ -91,6 +94,10 @@
>https://www.premiumize.me/account</a
>.
</p>
<p class="help" *ngIf="provider === 3">
You can find your API key here:
<a href="https://torbox.app/settings" target="_blank" rel="noopener">https://torbox.app/settings</a>.
</p>
</div>
<div class="field">

View file

@ -1,6 +1,6 @@
{
"name": "rdt-client",
"version": "2.0.86",
"version": "2.0.90",
"description": "This is a web interface to manage your torrents on Real-Debrid.",
"main": "index.js",
"dependencies": {

View file

@ -51,12 +51,12 @@ public class TorrentData(DataContext dataContext)
public async Task<Torrent?> GetByHash(String hash)
{
#pragma warning disable CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string comparisons
hash = hash.ToLower();
var dbTorrent = await dataContext.Torrents
.AsNoTracking()
.Include(m => m.Downloads)
.FirstOrDefaultAsync(m => m.Hash.ToLower() == hash.ToLower());
#pragma warning restore CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string comparisons
.FirstOrDefaultAsync(m => m.Hash == hash);
if (dbTorrent == null)
{

View file

@ -11,5 +11,8 @@ public enum Provider
AllDebrid,
[Description("Premiumize")]
Premiumize
Premiumize,
[Description("TorBox")]
TorBox
}

View file

@ -155,10 +155,11 @@ http://127.0.0.1:6800/jsonrpc.")]
public class DbSettingsProvider
{
[DisplayName("Provider")]
[Description(@"The following 3 providers are supported:
[Description(@"The following 4 providers are supported:
<a href=""https://real-debrid.com/?id=1348683"" target=""_blank"" rel=""noopener"">https://real-debrid.com</a>
<a href=""https://alldebrid.com/?uid=2v91l&lang=en"" target=""_blank"" rel=""noopener"">https://alldebrid.com</a>
<a href=""https://www.premiumize.me/"" target=""_blank"" rel=""noopener"">https://www.premiumize.me/</a>
<a href=""https://torbox.app/"" target=""_blank"" rel=""noopener"">https://torbox.app/</a>
At this point only 1 provider can be used at the time.")]
public Provider Provider { get; set; } = Provider.RealDebrid;
@ -168,7 +169,9 @@ At this point only 1 provider can be used at the time.")]
or
<a href=""""https://alldebrid.com/apikeys/"""" target=""""_blank"""" rel=""""noopener"""">https://alldebrid.com/apikeys/</a>
or
<a href=""https://www.premiumize.me/account/"" target=""_blank"" rel=""noopener"">https://www.premiumize.me/account/</a>")]
<a href=""https://www.premiumize.me/account/"" target=""_blank"" rel=""noopener"">https://www.premiumize.me/account/</a>
or
<a href=""""https://torbox.app/settings/"""" target=""""_blank"""" rel=""""noopener"""">https://torbox.app/settings/</a>")]
public String ApiKey { get; set; } = "";
[DisplayName("Automatically import and process torrents added to provider")]

View file

@ -1,22 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8">
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Serilog" Version="4.0.1" />
<PackageReference Include="Serilog" Version="4.2.0" />
</ItemGroup>
</Project>

View file

@ -24,6 +24,7 @@ public static class DiConfig
services.AddScoped<RemoteService>();
services.AddScoped<RealDebridTorrentClient>();
services.AddScoped<Settings>();
services.AddScoped<TorBoxTorrentClient>();
services.AddScoped<Torrents>();
services.AddScoped<TorrentRunner>();

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
@ -11,17 +11,18 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="AllDebrid.NET" Version="1.0.12" />
<PackageReference Include="Aria2.NET" Version="1.0.5" />
<PackageReference Include="Downloader" Version="3.1.2" />
<PackageReference Include="Downloader" Version="3.3.1" />
<PackageReference Include="Downloader.NET" Version="1.0.12" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.8.0" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0" />
<PackageReference Include="MonoTorrent" Version="3.0.2" />
<PackageReference Include="Polly" Version="8.4.1" />
<PackageReference Include="Polly" Version="8.5.0" />
<PackageReference Include="Premiumize.NET" Version="1.0.4" />
<PackageReference Include="RD.NET" Version="2.1.6" />
<PackageReference Include="Serilog" Version="4.0.1" />
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="SharpCompress" Version="0.38.0" />
<PackageReference Include="TorBox.NET" Version="1.2.1" />
</ItemGroup>
<ItemGroup>

View file

@ -26,7 +26,7 @@ public class InternalDownloader : IDownloader
_uri = uri;
_filePath = filePath;
_downloadConfiguration = new();
SetSettings();
@ -131,14 +131,14 @@ public class InternalDownloader : IDownloader
{
settingDownloadParallelCount = 1;
}
var settingDownloadMaxSpeed = Settings.Get.DownloadClient.MaxSpeed;
if (settingDownloadMaxSpeed <= 0)
{
settingDownloadMaxSpeed = 0;
}
settingDownloadMaxSpeed /= Math.Max(TorrentRunner.ActiveDownloadClients.Count, 1);
settingDownloadMaxSpeed = settingDownloadMaxSpeed * 1024 * 1024;
var settingDownloadTimeout = Settings.Get.DownloadClient.Timeout;

View file

@ -118,23 +118,9 @@ public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IHtt
return resultId;
}
public async Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
public Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
{
var isAvailable = await GetClient().Magnet.InstantAvailabilityAsync(hash);
if (isAvailable)
{
return
[
new()
{
Filename = "All files",
Filesize = 0
}
];
}
return [];
return Task.FromResult<IList<TorrentClientAvailableFile>>([]);
}
public Task SelectFiles(Torrent torrent)

View file

@ -117,8 +117,7 @@ public class PremiumizeTorrentClient(ILogger<PremiumizeTorrentClient> logger, IH
public Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
{
var result = new List<TorrentClientAvailableFile>();
return Task.FromResult<IList<TorrentClientAvailableFile>>(result);
return Task.FromResult<IList<TorrentClientAvailableFile>>([]);
}
public Task SelectFiles(Torrent torrent)

View file

@ -137,49 +137,27 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
return result.Id;
}
public async Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
public Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
{
var result = await GetClient().Torrents.GetAvailableFiles(hash);
var files = result.SelectMany(m => m.Value).SelectMany(m => m.Value).SelectMany(m => m.Values);
var groups = files.Where(m => m.Filename != null).GroupBy(m => $"{m.Filename}-{m.Filesize}");
var torrentClientAvailableFiles = groups.Select(m => new TorrentClientAvailableFile
{
Filename = m.First().Filename!,
Filesize = m.First().Filesize
}).ToList();
return torrentClientAvailableFiles;
return Task.FromResult<IList<TorrentClientAvailableFile>>([]);
}
public async Task SelectFiles(Data.Models.Data.Torrent torrent)
{
var files = torrent.Files;
IList<TorrentClientFile> files;
Log("Seleting files", torrent);
if (torrent.DownloadAction == TorrentDownloadAction.DownloadAvailableFiles)
{
Log($"Determining which files are already available on RealDebrid", torrent);
var availableFiles = await GetAvailableFiles(torrent.Hash);
Log($"Found {files.Count}/{torrent.Files.Count} available files on RealDebrid", torrent);
files = torrent.Files.Where(m => availableFiles.Any(f => m.Path.EndsWith(f.Filename))).ToList();
}
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadAll)
{
Log("Selecting all files", torrent);
files = [.. torrent.Files];
}
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadManual)
if (torrent.DownloadAction == TorrentDownloadAction.DownloadManual)
{
Log("Selecting manual selected files", torrent);
files = torrent.Files.Where(m => torrent.ManualFiles.Any(f => m.Path.EndsWith(f))).ToList();
}
else
{
Log("Selecting all files", torrent);
files = [.. torrent.Files];
}
Log($"Selecting {files.Count}/{torrent.Files.Count} files", torrent);

View file

@ -0,0 +1,319 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using TorBoxNET;
using RdtClient.Data.Enums;
using RdtClient.Data.Models.TorrentClient;
namespace RdtClient.Service.Services.TorrentClients;
public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClientFactory httpClientFactory) : ITorrentClient
{
private TimeSpan? _offset;
private TorBoxNetClient GetClient()
{
try
{
var apiKey = Settings.Get.Provider.ApiKey;
if (String.IsNullOrWhiteSpace(apiKey))
{
throw new("TorBox API Key not set in the settings");
}
var httpClient = httpClientFactory.CreateClient();
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
var torBoxNetClient = new TorBoxNetClient(null, httpClient, 5);
torBoxNetClient.UseApiAuthentication(apiKey);
// Get the server time to fix up the timezones on results
if (_offset == null)
{
var serverTime = DateTimeOffset.UtcNow;
_offset = serverTime.Offset;
}
return torBoxNetClient;
}
catch (AggregateException ae)
{
foreach (var inner in ae.InnerExceptions)
{
logger.LogError(inner, $"The connection to RealDebrid has failed: {inner.Message}");
}
throw;
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}");
throw;
}
catch (TaskCanceledException ex)
{
logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}");
throw;
}
}
private TorrentClientTorrent Map(TorrentInfoResult torrent)
{
return new()
{
Id = torrent.Hash,
Filename = torrent.Name,
OriginalFilename = torrent.Name,
Hash = torrent.Hash,
Bytes = torrent.Size,
OriginalBytes = torrent.Size,
Host = torrent.DownloadPresent.ToString(),
Split = 0,
Progress = (Int64)((torrent.Progress) * 100.0),
Status = torrent.DownloadState,
Added = ChangeTimeZone(torrent.CreatedAt)!.Value,
Files = (torrent.Files ?? []).Select(m => new TorrentClientFile
{
Path = String.Join("/", m.Name.Split('/').Skip(1)),
Bytes = m.Size,
Id = m.Id,
Selected = true
}).ToList(),
Links = [],
Ended = ChangeTimeZone(torrent.UpdatedAt),
Speed = torrent.DownloadSpeed,
Seeders = torrent.Seeds,
};
}
public async Task<IList<TorrentClientTorrent>> GetTorrents()
{
var torrents = new List<TorrentInfoResult>();
var currentTorrents = await GetClient().Torrents.GetCurrentAsync(true);
if (currentTorrents != null)
{
torrents.AddRange(currentTorrents);
}
var queuedTorrents = await GetClient().Torrents.GetQueuedAsync(true);
if (queuedTorrents != null)
{
torrents.AddRange(queuedTorrents);
}
return torrents.Select(Map).ToList();
}
public async Task<TorrentClientUser> GetUser()
{
var user = await GetClient().User.GetAsync(false);
return new()
{
Username = user.Data!.Email,
Expiration = user.Data!.Plan != 0 ? user.Data!.PremiumExpiresAt!.Value : null
};
}
public async Task<String> AddMagnet(String magnetLink)
{
// var seeding = Settings.Get.Integrations.Default.FinishedAction;
// Line is not working right now, will disable seeding and fix in december when I have time again.
// var seed = (seeding == TorrentFinishedAction.RemoveAllTorrents || seeding == TorrentFinishedAction.RemoveRealDebrid) ? 3 : 2;
var seed = 3;
var result = await GetClient().Torrents.AddMagnetAsync(magnetLink, seed, false);
if (result.Error == "ACTIVE_LIMIT")
{
var magnetLinkInfo = MonoTorrent.MagnetLink.Parse(magnetLink);
return magnetLinkInfo.InfoHashes.V1!.ToHex().ToLowerInvariant();
}
else
{
return result.Data!.Hash!;
}
}
public async Task<String> AddFile(Byte[] bytes)
{
// Line is not working right now, will disable seeding and fix in december when I have time again.
// var seed = (seeding == TorrentFinishedAction.RemoveAllTorrents || seeding == TorrentFinishedAction.RemoveRealDebrid) ? 3 : 2;
const Int32 seed = 3;
var result = await GetClient().Torrents.AddFileAsync(bytes, seed);
if (result.Error == "ACTIVE_LIMIT")
{
using var stream = new MemoryStream(bytes);
var torrent = await MonoTorrent.Torrent.LoadAsync(stream);
return torrent.InfoHashes.V1!.ToHex().ToLowerInvariant();
}
return result.Data!.Hash!;
}
public async Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
{
var availability = await GetClient().Torrents.GetAvailabilityAsync(hash, listFiles: true);
if (availability.Data != null)
{
return (availability.Data[0]?.Files ?? []).Select(file => new TorrentClientAvailableFile
{
Filename = file.Name,
Filesize = file.Size
})
.ToList();
}
return [];
}
public Task SelectFiles(Data.Models.Data.Torrent torrent)
{
return Task.CompletedTask;
}
public async Task Delete(String torrentId)
{
await GetClient().Torrents.ControlAsync(torrentId, "delete");
}
public async Task<String> Unrestrict(String link)
{
var segments = link.Split('/');
var zipped = segments[5] == "zip";
var fileId = zipped ? "0" : segments[5];
var result = await GetClient().Torrents.RequestDownloadAsync(Convert.ToInt32(segments[4]), Convert.ToInt32(fileId), zipped);
if (result.Error != null)
{
throw new("Unrestrict returned an invalid download");
}
return result.Data!;
}
public async Task<Data.Models.Data.Torrent> UpdateData(Data.Models.Data.Torrent torrent, TorrentClientTorrent? torrentClientTorrent)
{
try
{
if (torrent.RdId == null)
{
return torrent;
}
var rdTorrent = await GetInfo(torrent.Hash) ?? throw new($"Resource not found");
if (!String.IsNullOrWhiteSpace(rdTorrent.Filename))
{
torrent.RdName = rdTorrent.Filename;
}
if (!String.IsNullOrWhiteSpace(rdTorrent.OriginalFilename))
{
torrent.RdName = rdTorrent.OriginalFilename;
}
if (rdTorrent.Bytes > 0)
{
torrent.RdSize = rdTorrent.Bytes;
}
else if (rdTorrent.OriginalBytes > 0)
{
torrent.RdSize = rdTorrent.OriginalBytes;
}
if (rdTorrent.Files != null)
{
torrent.RdFiles = JsonConvert.SerializeObject(rdTorrent.Files);
}
torrent.RdHost = rdTorrent.Host;
torrent.RdSplit = rdTorrent.Split;
torrent.RdProgress = rdTorrent.Progress;
torrent.RdAdded = rdTorrent.Added;
torrent.RdEnded = rdTorrent.Ended;
torrent.RdSpeed = rdTorrent.Speed;
torrent.RdSeeders = rdTorrent.Seeders;
torrent.RdStatusRaw = rdTorrent.Status;
if (rdTorrent.Host == "True")
{
torrent.RdStatus = TorrentStatus.Finished;
}
else
{
torrent.RdStatus = rdTorrent.Status switch
{
"queued" => TorrentStatus.Processing,
"metaDL" => TorrentStatus.Processing,
"checking" => TorrentStatus.Processing,
"checkingResumeData" => TorrentStatus.Processing,
"paused" => TorrentStatus.Downloading,
"downloading" => TorrentStatus.Downloading,
"completed" => TorrentStatus.Downloading,
"uploading" => TorrentStatus.Downloading,
"uploading (no peers)" => TorrentStatus.Downloading,
"stalled" => TorrentStatus.Downloading,
"stalled (no seeds)" => TorrentStatus.Downloading,
"processing" => TorrentStatus.Downloading,
"cached" => TorrentStatus.Finished,
_ => TorrentStatus.Error
};
}
}
catch (Exception ex)
{
if (ex.Message == "Resource not found")
{
torrent.RdStatusRaw = "deleted";
}
else
{
throw;
}
}
return torrent;
}
public async Task<IList<String>?> GetDownloadLinks(Data.Models.Data.Torrent torrent)
{
var files = new List<String>();
var torrentId = await GetClient().Torrents.GetHashInfoAsync(torrent.Hash, skipCache: true);
foreach (var file in torrent.Files)
{
var newFile = $"https://torbox.app/fakedl/{torrentId?.Id}/{file.Id}";
files.Add(newFile);
}
return files;
}
private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset)
{
if (_offset == null)
{
return dateTimeOffset;
}
return dateTimeOffset?.Subtract(_offset.Value).ToOffset(_offset.Value);
}
private async Task<TorrentClientTorrent> GetInfo(String torrentHash)
{
var result = await GetClient().Torrents.GetHashInfoAsync(torrentHash, skipCache: true);
return Map(result!);
}
}

View file

@ -22,7 +22,8 @@ public class Torrents(
Downloads downloads,
AllDebridTorrentClient allDebridTorrentClient,
PremiumizeTorrentClient premiumizeTorrentClient,
RealDebridTorrentClient realDebridTorrentClient)
RealDebridTorrentClient realDebridTorrentClient,
TorBoxTorrentClient torBoxTorrentClient)
{
private static readonly SemaphoreSlim RealDebridUpdateLock = new(1, 1);
@ -40,6 +41,7 @@ public class Torrents(
Provider.Premiumize => premiumizeTorrentClient,
Provider.RealDebrid => realDebridTorrentClient,
Provider.AllDebrid => allDebridTorrentClient,
Provider.TorBox => torBoxTorrentClient,
_ => throw new("Invalid Provider")
};
}

View file

@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using RdtClient.Data.Enums;
using RdtClient.Data.Models.QBittorrent;
using RdtClient.Service.Services;
@ -21,6 +22,11 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
{
logger.LogDebug($"Auth login");
if (Settings.Get.General.AuthenticationType == AuthenticationType.None)
{
return Ok("Ok.");
}
if (String.IsNullOrWhiteSpace(request.UserName) || String.IsNullOrEmpty(request.Password))
{
return Ok("Fails.");

View file

@ -1,5 +1,4 @@
using System.Diagnostics;
using System.Net;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Hosting.WindowsServices;
@ -136,8 +135,6 @@ builder.Host.UseWindowsService();
RdtClient.Data.DiConfig.Config(builder.Services, appSettings);
builder.Services.RegisterRdtServices();
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
try
{
// Build the app

View file

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<UserSecretsId>94c24cba-f03f-4453-a671-3640b517c573</UserSecretsId>
<Version>2.0.86</Version>
<Version>2.0.90</Version>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
@ -29,17 +29,17 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8">
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="8.0.8" />
<PackageReference Include="Serilog" Version="4.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="9.0.0" />
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
</ItemGroup>

View file

@ -199,10 +199,12 @@
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=NAMESPACE_005FALIAS/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FRESOURCE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EPsiFeatures_002EVisualStudio_002EBackend_002EDaemon_002EHighlightingSettingsMigrator/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Housekeeping/VsHighlighting/SuppressVsSquiggles/@EntryValue">True</s:Boolean>
</wpf:ResourceDictionary>