Add Premiumize support.

This commit is contained in:
Roger Far 2023-03-11 19:11:38 -07:00
parent 3974189c5e
commit 998ddb05ac
19 changed files with 385 additions and 62 deletions

View file

@ -4,6 +4,10 @@ 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.22] - 2023-03-11
### Added
- Add support for Premiumize
## [2.0.21] - 2023-03-09
### Changed
- Fixed docker build by pinning the .NET version to LTS.

View file

@ -1,18 +1,20 @@
# Real-Debrid Torrent Client
This is a web interface to manage your torrents on Real-Debrid or AllDebrid. It supports the following features:
This is a web interface to manage your torrents on Real-Debrid, AllDebrid or Premiumize. It supports the following features:
- Add new torrents through magnets or files
- Download all files from Real-Debrid or AllDebrid to your local machine automatically
- 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 14 and .NET 6
- Built with Angular 15 and .NET 6
**You will need a Premium service at Real-Debrid or AllDebrid!**
**You will need a Premium service at Real-Debrid, AllDebrid or Premiumize!**
[Click here to sign up at Real-Debrid.](https://real-debrid.com/?id=1348683)
[Click here to sign up for Real-Debrid.](https://real-debrid.com/?id=1348683)
[Click here to sign up AllDebrid.](https://alldebrid.com/?uid=2v91l)
[Click here to sign up for AllDebrid.](https://alldebrid.com/?uid=2v91l)
[Click here to sign up for Premiumize.](https://www.premiumize.me/)
<sub>(referal links so I can get a few free premium days)</sub>

View file

@ -53,7 +53,7 @@
<div class="field">
<label class="label">Post Torrent Download Action</label>
<div class="control select is-fullwidth">
<select [(ngModel)]="downloadAction" [disabled]="provider === 'AllDebrid'">
<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>
@ -61,7 +61,7 @@
</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 will always download the full torrent.
This option is only available for RealDebrid. AllDebrid and Premiumize will always download the full torrent.
</p>
</div>

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" />
<div class="navbar-item">Version 2.0.21</div>
<div class="navbar-item">Version 2.0.22</div>
</div>
</div>
</div>

View file

@ -28,6 +28,9 @@ export class NavbarComponent implements OnInit {
case 'AllDebrid':
this.providerLink = 'https://alldebrid.com/?uid=2v91l&lang=en';
break;
case 'Premiumize':
this.providerLink = 'https://www.premiumize.me/';
break;
}
});
}

View file

@ -51,6 +51,10 @@
<a href="https://alldebrid.com/?uid=2v91l&lang=en" target="_blank" rel="noopener"
>Use this link to sign up to AllDebrid.</a
>
<br />
<a href="https://www.premiumize.me/" target="_blank" rel="noopener"
>Use this link to sign up to Premiumize.</a
>
<br />
<small>(Referal links)</small>
</div>
@ -60,6 +64,7 @@
<select [(ngModel)]="provider">
<option [value]="0">Real-Debrid</option>
<option [value]="1">AllDebrid</option>
<option [value]="2">Premiumize</option>
</select>
</div>
</div>
@ -79,6 +84,12 @@
<a href="https://alldebrid.com/apikeys/" target="_blank" rel="noopener"
>https://alldebrid.com/apikeys/</a
>.
</p>
<p class="help" *ngIf="provider === 'Premiumize'">
You can find your API key here:
<a href="https://www.premiumize.me/account" target="_blank" rel="noopener"
>https://www.premiumize.me/account</a
>.
</p>
</div>

View file

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

View file

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

View file

@ -123,6 +123,7 @@ public class DbSettingsProvider
[Description(@"The following 2 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>
At this point only 1 provider can be used at the time.")]
public Provider Provider { get; set; } = Provider.RealDebrid;
@ -130,15 +131,17 @@ At this point only 1 provider can be used at the time.")]
[Description(@"You can find your API key here:
<a href=""https://real-debrid.com/apitoken"" target=""_blank"" rel=""noopener"">https://real-debrid.com/apitoken</a>
or
<a href=""https://alldebrid.com/apikeys/"" target=""_blank"" rel=""noopener"">https://alldebrid.com/apikeys/</a>.")]
<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>")]
public String ApiKey { get; set; } = "";
[DisplayName("Automatically import and process torrents added to provider")]
[Description("When selected, import downloads that are not added through RealDebridClient but have been directly added to Real-Debrid or AllDebrid.")]
[Description("When selected, import downloads that are not added through RealDebridClient but have been directly added to Real-Debrid, AllDebrid or Premiumize.")]
public Boolean AutoImport { get; set; } = false;
[DisplayName("Automatically delete downloads removed from provider")]
[Description("When selected, cancel and delete downloads that have been removed from Real-Debrid or AllDebrid.")]
[Description("When selected, cancel and delete downloads that have been removed from Real-Debrid, AllDebrid or Premiumize.")]
public Boolean AutoDelete { get; set; } = false;
[DisplayName("Connection Timeout")]

View file

@ -4,16 +4,17 @@ public class TorrentClientTorrent
{
public String Id { get; set; } = default!;
public String Filename { get; set; } = default!;
public String? OriginalFilename { get; set; } = default!;
public String? OriginalFilename { get; set; }
public String Hash { get; set; } = default!;
public Int64 Bytes { get; set; }
public Int64 OriginalBytes { get; set; }
public String? Host { get; set; }
public Int64 Split { get; set; }
public Int64 Progress { get; set; }
public String? Status { get; set; } = default!;
public String? Status { get; set; }
public String? Message { get; set; }
public Int64 StatusCode { get; set; }
public DateTimeOffset Added { get; set; }
public DateTimeOffset? Added { get; set; }
public List<TorrentClientFile>? Files { get; set; }
public List<String>? Links { get; set; }
public DateTimeOffset? Ended { get; set; }

View file

@ -14,6 +14,7 @@ public static class DiConfig
services.AddScoped<AllDebridTorrentClient>();
services.AddScoped<Authentication>();
services.AddScoped<Downloads>();
services.AddScoped<PremiumizeTorrentClient>();
services.AddScoped<QBittorrent>();
services.AddScoped<RemoteService>();
services.AddScoped<RealDebridTorrentClient>();

View file

@ -9,11 +9,12 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="AllDebrid.NET" Version="1.0.10" />
<PackageReference Include="AllDebrid.NET" Version="1.0.12" />
<PackageReference Include="Aria2.NET" Version="1.0.5" />
<PackageReference Include="Downloader" Version="3.0.3" />
<PackageReference Include="Downloader" Version="3.0.4" />
<PackageReference Include="MonoTorrent" Version="2.0.7" />
<PackageReference Include="RD.NET" Version="2.1.3" />
<PackageReference Include="Premiumize.NET" Version="1.0.1" />
<PackageReference Include="RD.NET" Version="2.1.4" />
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="SharpCompress" Version="0.32.2" />

View file

@ -1,8 +1,6 @@
using AllDebridNET;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using RDNET;
using RDNET.Exceptions;
using RdtClient.Data.Enums;
using RdtClient.Data.Models.TorrentClient;
using RdtClient.Service.Helpers;
@ -36,19 +34,19 @@ public class AllDebridTorrentClient : ITorrentClient
var httpClient = _httpClientFactory.CreateClient();
httpClient.Timeout = TimeSpan.FromSeconds(10);
var allDebridNetClient = new AllDebridNETClient("RealDebridClient", apiKey);
var allDebridNetClient = new AllDebridNETClient("RealDebridClient", apiKey, httpClient);
return allDebridNetClient;
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
_logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}");
_logger.LogError(ex, $"The connection to AllDebrid has timed out: {ex.Message}");
throw;
}
catch (TaskCanceledException ex)
{
_logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}");
_logger.LogError(ex, $"The connection to AllDebrid has timed out: {ex.Message}");
throw;
}
@ -152,7 +150,7 @@ public class AllDebridTorrentClient : ITorrentClient
{
return new List<TorrentClientAvailableFile>
{
new TorrentClientAvailableFile
new()
{
Filename = "All files",
Filesize = 0
@ -168,18 +166,6 @@ public class AllDebridTorrentClient : ITorrentClient
return Task.CompletedTask;
}
public async Task<TorrentClientTorrent> GetInfo(String torrentId)
{
var result = await GetClient().Magnet.StatusAsync(torrentId);
if (result == null)
{
throw new Exception($"Unable to find magnet with ID {torrentId}");
}
return Map(result);
}
public async Task Delete(String torrentId)
{
await GetClient().Magnet.DeleteAsync(torrentId);
@ -260,17 +246,6 @@ public class AllDebridTorrentClient : ITorrentClient
throw;
}
}
catch (RealDebridException ex)
{
if (ex.ErrorCode == 7)
{
torrent.RdStatusRaw = "deleted";
}
else
{
throw;
}
}
return torrent;
}
@ -326,6 +301,18 @@ public class AllDebridTorrentClient : ITorrentClient
return links.Select(m => m.LinkUrl.ToString()).ToList();
}
private async Task<TorrentClientTorrent> GetInfo(String torrentId)
{
var result = await GetClient().Magnet.StatusAsync(torrentId);
if (result == null)
{
throw new Exception($"Unable to find magnet with ID {torrentId}");
}
return Map(result);
}
private static IEnumerable<String> GetFiles(IList<File> files, String parent)
{
var result = new List<String>();

View file

@ -0,0 +1,278 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using PremiumizeNET;
using RdtClient.Data.Enums;
using RdtClient.Data.Models.TorrentClient;
using RdtClient.Service.Helpers;
using Torrent = RdtClient.Data.Models.Data.Torrent;
namespace RdtClient.Service.Services.TorrentClients;
public class PremiumizeTorrentClient : ITorrentClient
{
private readonly ILogger<PremiumizeTorrentClient> _logger;
private readonly IHttpClientFactory _httpClientFactory;
public PremiumizeTorrentClient(ILogger<PremiumizeTorrentClient> logger, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
}
private PremiumizeNETClient GetClient()
{
try
{
var apiKey = Settings.Get.Provider.ApiKey;
if (String.IsNullOrWhiteSpace(apiKey))
{
throw new Exception("Premiumize API Key not set in the settings");
}
var httpClient = _httpClientFactory.CreateClient();
httpClient.Timeout = TimeSpan.FromSeconds(10);
var premiumizeNetClient = new PremiumizeNETClient(apiKey, httpClient);
return premiumizeNetClient;
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
_logger.LogError(ex, $"The connection to Premiumize has timed out: {ex.Message}");
throw;
}
catch (TaskCanceledException ex)
{
_logger.LogError(ex, $"The connection to Premiumize has timed out: {ex.Message}");
throw;
}
}
private static TorrentClientTorrent Map(Transfer transfer)
{
return new TorrentClientTorrent
{
Id = transfer.Id,
Filename = transfer.Name,
OriginalFilename = transfer.Name,
Hash = transfer.Src,
Bytes = 0,
OriginalBytes = 0,
Host = null,
Split = 0,
Progress = (Int64) ((transfer.Progress ?? 1.0) * 100.0),
Status = transfer.Status,
Message = transfer.Message,
StatusCode = 0,
Added = null,
Files = new List<TorrentClientFile>(),
Links = new List<String>
{
transfer.FolderId
},
Ended = null,
Speed = 0,
Seeders = 0
};
}
public async Task<IList<TorrentClientTorrent>> GetTorrents()
{
var results = await GetClient().Transfers.ListAsync();
return results.Select(Map).ToList();
}
public async Task<TorrentClientUser> GetUser()
{
var user = await GetClient().Account.InfoAsync();
if (user == null)
{
throw new Exception("Unable to get user");
}
return new TorrentClientUser
{
Username = user.CustomerId.ToString(),
Expiration = user.PremiumUntil > 0 ? new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(user.PremiumUntil.Value) : null
};
}
public async Task<String> AddMagnet(String magnetLink)
{
var result = await GetClient().Transfers.CreateAsync(magnetLink, "");
if (result?.Id == null)
{
throw new Exception("Unable to add magnet link");
}
var resultId = result.Id;
if (resultId == null)
{
throw new Exception($"Invalid responseID {result.Id}");
}
return resultId;
}
public async Task<String> AddFile(Byte[] bytes)
{
var result = await GetClient().Transfers.CreateAsync(bytes, "");
if (result?.Id == null)
{
throw new Exception("Unable to add torrent file");
}
var resultId = result.Id;
if (resultId == null)
{
throw new Exception($"Invalid responseID {result.Id}");
}
return resultId;
}
public Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
{
var result = new List<TorrentClientAvailableFile>();
return Task.FromResult<IList<TorrentClientAvailableFile>>(result);
}
public Task SelectFiles(Torrent torrent)
{
return Task.CompletedTask;
}
public async Task Delete(String id)
{
await GetClient().Transfers.DeleteAsync(id);
}
public Task<String> Unrestrict(String link)
{
return Task.FromResult(link);
}
public async Task<Torrent> UpdateData(Torrent torrent, TorrentClientTorrent? torrentClientTorrent)
{
try
{
if (torrent.RdId == null)
{
return torrent;
}
torrentClientTorrent ??= await GetInfo(torrent.RdId);
if (!String.IsNullOrWhiteSpace(torrentClientTorrent.Filename))
{
torrent.RdName = torrentClientTorrent.Filename;
}
if (torrentClientTorrent.Bytes > 0)
{
torrent.RdSize = torrentClientTorrent.Bytes;
}
if (torrentClientTorrent.Files != null)
{
torrent.RdFiles = JsonConvert.SerializeObject(torrentClientTorrent.Files);
}
torrent.RdHost = torrentClientTorrent.Host;
torrent.RdSplit = torrentClientTorrent.Split;
torrent.RdProgress = torrentClientTorrent.Progress;
torrent.RdAdded = torrentClientTorrent.Added;
torrent.RdEnded = torrentClientTorrent.Ended;
torrent.RdSpeed = torrentClientTorrent.Speed;
torrent.RdSeeders = torrentClientTorrent.Seeders;
torrent.RdStatusRaw = torrentClientTorrent.Status;
torrent.RdStatus = torrentClientTorrent.Status switch
{
"waiting" => TorrentStatus.Processing,
"queued" => TorrentStatus.Processing,
"running" => TorrentStatus.Downloading,
"seeding" => TorrentStatus.Uploading,
"finished" => TorrentStatus.Finished,
_ => TorrentStatus.Error
};
}
catch (PremiumizeException ex)
{
if (ex.Message == "MAGNET_INVALID_ID")
{
torrent.RdStatusRaw = "deleted";
}
else
{
throw;
}
}
return torrent;
}
public async Task<IList<String>?> GetDownloadLinks(Torrent torrent)
{
if (torrent.RdId == null)
{
return null;
}
var transfers = await GetClient().Transfers.ListAsync();
var transfer = transfers.FirstOrDefault(m => m.Id == torrent.RdId);
if (transfer == null)
{
return null;
}
if (String.IsNullOrWhiteSpace(transfer.FolderId))
{
return null;
}
var zip = await GetClient()
.Zip.Generate(new List<String>(),
new List<String>
{
transfer.FolderId
});
return new List<String>
{
zip
};
}
private async Task<TorrentClientTorrent> GetInfo(String id)
{
var results = await GetClient().Transfers.ListAsync();
var result = results.FirstOrDefault(m => m.Id == id);
if (result == null)
{
throw new Exception($"Unable to find transfer with ID {id}");
}
return Map(result);
}
private void Log(String message, Torrent? torrent = null)
{
if (torrent != null)
{
message = $"{message} {torrent.ToLog()}";
}
_logger.LogDebug(message);
}
}

View file

@ -214,13 +214,6 @@ public class RealDebridTorrentClient : ITorrentClient
await GetClient().Torrents.SelectFilesAsync(torrent.RdId!, fileIds.ToArray());
}
public async Task<TorrentClientTorrent> GetInfo(String torrentId)
{
var result = await GetClient().Torrents.GetInfoAsync(torrentId);
return Map(result);
}
public async Task Delete(String torrentId)
{
await GetClient().Torrents.DeleteAsync(torrentId);
@ -361,7 +354,7 @@ public class RealDebridTorrentClient : ITorrentClient
return null;
}
private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset)
{
if (_offset == null)
@ -372,6 +365,13 @@ public class RealDebridTorrentClient : ITorrentClient
return dateTimeOffset?.Subtract(_offset.Value).ToOffset(_offset.Value);
}
private async Task<TorrentClientTorrent> GetInfo(String torrentId)
{
var result = await GetClient().Torrents.GetInfoAsync(torrentId);
return Map(result);
}
private void Log(String message, Data.Models.Data.Torrent? torrent = null)
{
if (torrent != null)

View file

@ -413,7 +413,7 @@ public class TorrentRunner
var extension = Path.GetExtension(fileName);
if (extension != ".rar")
if (extension != ".rar" && extension != ".zip")
{
Log($"No need to unpack, setting it as unpacked", download, torrent);

View file

@ -32,6 +32,7 @@ public class Torrents
TorrentData torrentData,
Downloads downloads,
AllDebridTorrentClient allDebridTorrentClient,
PremiumizeTorrentClient premiumizeTorrentClient,
RealDebridTorrentClient realDebridTorrentClient)
{
_logger = logger;
@ -40,6 +41,7 @@ public class Torrents
_torrentClient = Settings.Get.Provider.Provider switch
{
Provider.Premiumize => premiumizeTorrentClient,
Provider.RealDebrid => realDebridTorrentClient,
Provider.AllDebrid => allDebridTorrentClient,
_ => throw new Exception("Invalid Provider")

View file

@ -2,6 +2,7 @@
using RdtClient.Service.Helpers;
using SharpCompress.Archives;
using SharpCompress.Archives.Rar;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
namespace RdtClient.Service.Services;
@ -21,7 +22,7 @@ public class UnpackClient
private Boolean _cancelled;
private RarArchiveEntry? _rarCurrentEntry;
private IArchiveEntry? _rarCurrentEntry;
private Dictionary<String, Int64>? _rarfileStatus;
public UnpackClient(Download download, String destinationPath)
@ -135,7 +136,17 @@ public class UnpackClient
{
await using Stream stream = File.OpenRead(filePath);
using var archive = RarArchive.Open(stream);
var extension = Path.GetExtension(filePath);
IArchive archive;
if (extension == ".zip")
{
archive = ZipArchive.Open(stream);
}
else
{
archive = RarArchive.Open(stream);
}
BytesTotal = archive.TotalSize;
@ -144,6 +155,8 @@ public class UnpackClient
.Select(m => m.Key)
.ToList();
archive.Dispose();
return entries;
}
@ -151,8 +164,20 @@ public class UnpackClient
{
var parts = ArchiveFactory.GetFileParts(filePath);
using var archive = RarArchive.Open(parts.Select(m => new FileInfo(m)));
var fi = parts.Select(m => new FileInfo(m));
var extension = Path.GetExtension(filePath);
IArchive archive;
if (extension == ".zip")
{
archive = ZipArchive.Open(fi);
}
else
{
archive = RarArchive.Open(fi);
}
if (archive.IsComplete)
{
BytesTotal = archive.TotalSize;
@ -181,6 +206,8 @@ public class UnpackClient
Overwrite = true
});
}
archive.Dispose();
}
private void ArchiveOnCompressedBytesRead(Object? sender, CompressedBytesReadEventArgs e)

View file

@ -4,7 +4,7 @@
<TargetFramework>net6.0</TargetFramework>
<OutputType>Exe</OutputType>
<UserSecretsId>94c24cba-f03f-4453-a671-3640b517c573</UserSecretsId>
<Version>2.0.21</Version>
<Version>2.0.22</Version>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>