diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a1c1bb..90d0cfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/README.md b/README.md index ecf19ea..de85a03 100644 --- a/README.md +++ b/README.md @@ -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/) (referal links so I can get a few free premium days) diff --git a/client/src/app/add-new-torrent/add-new-torrent.component.html b/client/src/app/add-new-torrent/add-new-torrent.component.html index cf2fd43..6ec5165 100644 --- a/client/src/app/add-new-torrent/add-new-torrent.component.html +++ b/client/src/app/add-new-torrent/add-new-torrent.component.html @@ -53,7 +53,7 @@
- @@ -61,7 +61,7 @@

When a torrent is fully downloaded on the provider, perform this action.

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

diff --git a/client/src/app/navbar/navbar.component.html b/client/src/app/navbar/navbar.component.html index 12660d1..2d36b8b 100644 --- a/client/src/app/navbar/navbar.component.html +++ b/client/src/app/navbar/navbar.component.html @@ -55,7 +55,7 @@ Profile Logout - + diff --git a/client/src/app/navbar/navbar.component.ts b/client/src/app/navbar/navbar.component.ts index 7f62035..c47e2b9 100644 --- a/client/src/app/navbar/navbar.component.ts +++ b/client/src/app/navbar/navbar.component.ts @@ -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; } }); } diff --git a/client/src/app/setup/setup.component.html b/client/src/app/setup/setup.component.html index e9d3fef..aa7a9fd 100644 --- a/client/src/app/setup/setup.component.html +++ b/client/src/app/setup/setup.component.html @@ -51,6 +51,10 @@ Use this link to sign up to AllDebrid. +
+ Use this link to sign up to Premiumize.
(Referal links) @@ -60,6 +64,7 @@ @@ -79,6 +84,12 @@ https://alldebrid.com/apikeys/. +

+

+ You can find your API key here: + https://www.premiumize.me/account.

diff --git a/package.json b/package.json index a4b959c..2ca9fd3 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/server/RdtClient.Data/Enums/Provider.cs b/server/RdtClient.Data/Enums/Provider.cs index fd81c22..7422663 100644 --- a/server/RdtClient.Data/Enums/Provider.cs +++ b/server/RdtClient.Data/Enums/Provider.cs @@ -8,5 +8,8 @@ public enum Provider RealDebrid, [Description("AllDebrid")] - AllDebrid + AllDebrid, + + [Description("Premiumize")] + Premiumize } \ No newline at end of file diff --git a/server/RdtClient.Data/Models/Internal/DbSettings.cs b/server/RdtClient.Data/Models/Internal/DbSettings.cs index 604897a..75d5101 100644 --- a/server/RdtClient.Data/Models/Internal/DbSettings.cs +++ b/server/RdtClient.Data/Models/Internal/DbSettings.cs @@ -123,6 +123,7 @@ public class DbSettingsProvider [Description(@"The following 2 providers are supported: https://real-debrid.com https://alldebrid.com +https://www.premiumize.me/ 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: https://real-debrid.com/apitoken or -https://alldebrid.com/apikeys/.")] +https://alldebrid.com/apikeys/ +or +https://www.premiumize.me/account/")] 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")] diff --git a/server/RdtClient.Data/Models/TorrentClient/TorrentClientTorrent.cs b/server/RdtClient.Data/Models/TorrentClient/TorrentClientTorrent.cs index 64be22d..a2a6fa1 100644 --- a/server/RdtClient.Data/Models/TorrentClient/TorrentClientTorrent.cs +++ b/server/RdtClient.Data/Models/TorrentClient/TorrentClientTorrent.cs @@ -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? Files { get; set; } public List? Links { get; set; } public DateTimeOffset? Ended { get; set; } diff --git a/server/RdtClient.Service/DiConfig.cs b/server/RdtClient.Service/DiConfig.cs index 5233fcc..75c08e1 100644 --- a/server/RdtClient.Service/DiConfig.cs +++ b/server/RdtClient.Service/DiConfig.cs @@ -14,6 +14,7 @@ public static class DiConfig services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/server/RdtClient.Service/RdtClient.Service.csproj b/server/RdtClient.Service/RdtClient.Service.csproj index 1812f76..3a2bf20 100644 --- a/server/RdtClient.Service/RdtClient.Service.csproj +++ b/server/RdtClient.Service/RdtClient.Service.csproj @@ -9,11 +9,12 @@ - + - + - + + diff --git a/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs index efb9334..af0629c 100644 --- a/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs @@ -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 { - new TorrentClientAvailableFile + new() { Filename = "All files", Filesize = 0 @@ -168,18 +166,6 @@ public class AllDebridTorrentClient : ITorrentClient return Task.CompletedTask; } - public async Task 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 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 GetFiles(IList files, String parent) { var result = new List(); diff --git a/server/RdtClient.Service/Services/TorrentClients/PremiumizeTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/PremiumizeTorrentClient.cs new file mode 100644 index 0000000..8c39440 --- /dev/null +++ b/server/RdtClient.Service/Services/TorrentClients/PremiumizeTorrentClient.cs @@ -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 _logger; + private readonly IHttpClientFactory _httpClientFactory; + + public PremiumizeTorrentClient(ILogger 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(), + Links = new List + { + transfer.FolderId + }, + Ended = null, + Speed = 0, + Seeders = 0 + }; + } + + public async Task> GetTorrents() + { + var results = await GetClient().Transfers.ListAsync(); + return results.Select(Map).ToList(); + } + + public async Task 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 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 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> GetAvailableFiles(String hash) + { + var result = new List(); + return Task.FromResult>(result); + } + + public Task SelectFiles(Torrent torrent) + { + return Task.CompletedTask; + } + + public async Task Delete(String id) + { + await GetClient().Transfers.DeleteAsync(id); + } + + public Task Unrestrict(String link) + { + return Task.FromResult(link); + } + + public async Task 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?> 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(), + new List + { + transfer.FolderId + }); + + return new List + { + zip + }; + } + + private async Task 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); + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs index fb920ff..3e688d6 100644 --- a/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs @@ -214,13 +214,6 @@ public class RealDebridTorrentClient : ITorrentClient await GetClient().Torrents.SelectFilesAsync(torrent.RdId!, fileIds.ToArray()); } - public async Task 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 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) diff --git a/server/RdtClient.Service/Services/TorrentRunner.cs b/server/RdtClient.Service/Services/TorrentRunner.cs index 6a5b89f..0cfcc9d 100644 --- a/server/RdtClient.Service/Services/TorrentRunner.cs +++ b/server/RdtClient.Service/Services/TorrentRunner.cs @@ -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); diff --git a/server/RdtClient.Service/Services/Torrents.cs b/server/RdtClient.Service/Services/Torrents.cs index c5aad57..0a7786c 100644 --- a/server/RdtClient.Service/Services/Torrents.cs +++ b/server/RdtClient.Service/Services/Torrents.cs @@ -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") diff --git a/server/RdtClient.Service/Services/UnpackClient.cs b/server/RdtClient.Service/Services/UnpackClient.cs index d58c0e2..f24f592 100644 --- a/server/RdtClient.Service/Services/UnpackClient.cs +++ b/server/RdtClient.Service/Services/UnpackClient.cs @@ -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? _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) diff --git a/server/RdtClient.Web/RdtClient.Web.csproj b/server/RdtClient.Web/RdtClient.Web.csproj index a7c63f6..ff2bc06 100644 --- a/server/RdtClient.Web/RdtClient.Web.csproj +++ b/server/RdtClient.Web/RdtClient.Web.csproj @@ -4,7 +4,7 @@ net6.0 Exe 94c24cba-f03f-4453-a671-3640b517c573 - 2.0.21 + 2.0.22 enable enable latest