From 59c47bfe1a31634a667af88778f8e11923320ac4 Mon Sep 17 00:00:00 2001 From: Wald764 Date: Sun, 22 Oct 2023 00:14:52 +0200 Subject: [PATCH] :tada: add support of DebridLinkFr --- client/src/app/navbar/navbar.component.ts | 3 + client/src/app/setup/setup.component.html | 12 +- server/RdtClient.Data/Enums/Provider.cs | 3 + .../Models/Internal/DbSettings.cs | 5 +- server/RdtClient.Service/DiConfig.cs | 1 + .../RdtClient.Service.csproj | 1 + .../DebridLinkFrTorrentClient.cs | 317 ++++++++++++++++++ server/RdtClient.Service/Services/Torrents.cs | 10 +- 8 files changed, 346 insertions(+), 6 deletions(-) create mode 100644 server/RdtClient.Service/Services/TorrentClients/DebridLinkFrTorrentClient.cs diff --git a/client/src/app/navbar/navbar.component.ts b/client/src/app/navbar/navbar.component.ts index 95c8067..a07406d 100644 --- a/client/src/app/navbar/navbar.component.ts +++ b/client/src/app/navbar/navbar.component.ts @@ -35,6 +35,9 @@ export class NavbarComponent implements OnInit { case 'Premiumize': this.providerLink = 'https://www.premiumize.me/'; break; + case 'DebridLinkFr': + this.providerLink = 'https://debrid-link.fr/'; + break; case 'TorBox': this.providerLink = 'https://torbox.app/'; break; diff --git a/client/src/app/setup/setup.component.html b/client/src/app/setup/setup.component.html index 28c81e3..fda6219 100644 --- a/client/src/app/setup/setup.component.html +++ b/client/src/app/setup/setup.component.html @@ -56,6 +56,9 @@ >Use this link to sign up to Premiumize.
+ Use this link to sign up to DebridLinkFr. Use this link to sign up to TorBox.
@@ -68,7 +71,8 @@ - + + @@ -99,6 +103,12 @@ You can find your API key here: https://torbox.app/settings.

+

+ You can find your API key here: + https://debrid-link.com/webapp/apikey. +

diff --git a/server/RdtClient.Data/Enums/Provider.cs b/server/RdtClient.Data/Enums/Provider.cs index ff372dc..00e3da4 100644 --- a/server/RdtClient.Data/Enums/Provider.cs +++ b/server/RdtClient.Data/Enums/Provider.cs @@ -13,6 +13,9 @@ public enum Provider [Description("Premiumize")] Premiumize, + [Description("DebridLinkFr")] + DebridLinkFr, + [Description("TorBox")] TorBox } \ 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 dba1ff3..e90e258 100644 --- a/server/RdtClient.Data/Models/Internal/DbSettings.cs +++ b/server/RdtClient.Data/Models/Internal/DbSettings.cs @@ -159,6 +159,7 @@ public class DbSettingsProvider https://real-debrid.com https://alldebrid.com https://www.premiumize.me/ +https://debrid-link.com/ https://torbox.app/ At this point only 1 provider can be used at the time.")] public Provider Provider { get; set; } = Provider.RealDebrid; @@ -171,7 +172,9 @@ or or https://www.premiumize.me/account/ or -https://torbox.app/settings/")] +https://torbox.app/settings/ +or +https://debrid-link.com/webapp/apikey")] public String ApiKey { get; set; } = ""; [DisplayName("Automatically import and process torrents added to provider")] diff --git a/server/RdtClient.Service/DiConfig.cs b/server/RdtClient.Service/DiConfig.cs index 695443f..e61c71a 100644 --- a/server/RdtClient.Service/DiConfig.cs +++ b/server/RdtClient.Service/DiConfig.cs @@ -27,6 +27,7 @@ public static class DiConfig services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddSingleton(); diff --git a/server/RdtClient.Service/RdtClient.Service.csproj b/server/RdtClient.Service/RdtClient.Service.csproj index 90ad249..a3a60b5 100644 --- a/server/RdtClient.Service/RdtClient.Service.csproj +++ b/server/RdtClient.Service/RdtClient.Service.csproj @@ -11,6 +11,7 @@ + diff --git a/server/RdtClient.Service/Services/TorrentClients/DebridLinkFrTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/DebridLinkFrTorrentClient.cs new file mode 100644 index 0000000..ea7927c --- /dev/null +++ b/server/RdtClient.Service/Services/TorrentClients/DebridLinkFrTorrentClient.cs @@ -0,0 +1,317 @@ +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using DebridLinkFrNET; +using RdtClient.Data.Enums; +using RdtClient.Data.Models.TorrentClient; +using RdtClient.Service.Helpers; +using DebridLinkFrNET.Models; + +namespace RdtClient.Service.Services.TorrentClients; + +public class DebridLinkFrClient : ITorrentClient +{ + private readonly ILogger _logger; + private readonly IHttpClientFactory _httpClientFactory; + + public DebridLinkFrClient(ILogger logger, IHttpClientFactory httpClientFactory) + { + _logger = logger; + _httpClientFactory = httpClientFactory; + } + + private DebridLinkFrNETClient GetClient() + { + try + { + var apiKey = Settings.Get.Provider.ApiKey; + + if (String.IsNullOrWhiteSpace(apiKey)) + { + throw new Exception("Real-Debrid API Key not set in the settings"); + } + + var httpClient = _httpClientFactory.CreateClient(); + httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout); + + var debridLinkFrClient = new DebridLinkFrNETClient(apiKey, httpClient); + + return debridLinkFrClient; + } + catch (AggregateException ae) + { + foreach (var inner in ae.InnerExceptions) + { + _logger.LogError(inner, $"The connection to DebridLinkFr has failed: {inner.Message}"); + } + + throw; + } + catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException) + { + _logger.LogError(ex, $"The connection to DebridLinkFr has timed out: {ex.Message}"); + + throw; + } + catch (TaskCanceledException ex) + { + _logger.LogError(ex, $"The connection to DebridLinkFr has timed out: {ex.Message}"); + + throw; + } + } + + private TorrentClientTorrent Map(Torrent torrent) + { + return new TorrentClientTorrent + { + Id = torrent.Id ?? "", + Filename = torrent.Name ?? "", + OriginalFilename = torrent.Name ?? "", + Hash = torrent.HashString ?? "", + Bytes = torrent.TotalSize, + OriginalBytes = 0, + Host = torrent.ServerId ?? "", + Split = 0, + Progress = torrent.DownloadPercent, + Status = torrent.Status.ToString(), + Added = DateTimeOffset.FromUnixTimeSeconds(torrent.Created), + Files = (torrent.Files ?? new List()).Select((m, i) => new TorrentClientFile + { + Path = m.Name ?? "", + Bytes = m.Size, + Id = i, + Selected = true + }).ToList(), + Links = torrent.Files?.Select(m => m.DownloadUrl.ToString()).ToList(), + Ended = null, + Speed = torrent.UploadSpeed, + Seeders = torrent.PeersConnected, + }; + } + + public async Task> GetTorrents() + { + var page = 0; + var results = new List(); + + while (true) + { + var pagedResults = await GetClient().Seedbox.ListAsync(null,page, 50); + + results.AddRange(pagedResults); + + if (pagedResults.Count == 0) + { + break; + } + + page += 1; + } + + return results.Select(Map).ToList(); + } + + public async Task GetUser() + { + var user = await GetClient().Account.Infos(); + + return new TorrentClientUser + { + Username = user.Username, + Expiration = user.PremiumLeft > 0 ? DateTimeOffset.Now.AddSeconds(user.PremiumLeft) : null + }; + } + + public async Task AddMagnet(String magnetLink) + { + var result = await GetClient().Seedbox.AddTorrentAsync(magnetLink); + + return result.Id ?? ""; + } + + public async Task AddFile(Byte[] bytes) + { + var result = await GetClient().Seedbox.AddTorrentByFileAsync(bytes); + + return result.Id ?? ""; + } + + public async Task> GetAvailableFiles(String hash) + { + var result = await GetClient().Seedbox.CachedAsync(hash); + + var files = result.First().Value.Files ?? new List(); + + var groups = files.Where(m => m.Name != null).GroupBy(m => $"{m.Name}-{m.Size}"); + + var torrentClientAvailableFiles = groups.Select(m => new TorrentClientAvailableFile + { + Filename = m.First().Name!, + Filesize = m.First().Size + }).ToList(); + + return torrentClientAvailableFiles; + } + + public Task SelectFiles(Data.Models.Data.Torrent torrent) + { + return Task.CompletedTask; + } + + public async Task Delete(String torrentId) + { + await GetClient().Seedbox.DeleteAsync(torrentId); + } + + public async Task Unrestrict(String link) + { + return link; + } + + public async Task UpdateData(Data.Models.Data.Torrent torrent, TorrentClientTorrent? torrentClientTorrent) + { + try + { + if (torrent.RdId == null) + { + return torrent; + } + + var rdTorrent = await GetInfo(torrent.RdId); + + if (rdTorrent == null) + { + throw new Exception($"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; + /** + * + * 0 Torrent is stopped + * 1 Torrent is queued to verify local data + * 2 Torrent is verifying local data + * 3 Torrent is queued to download + * 4 Torrent is downloading + * 5 Torrent is queued to seed + * 6 Torrent is seeding + * 100 Torrent is stored + */ + torrent.RdStatus = rdTorrent.Status switch + { + "100" => TorrentStatus.Finished, + "1" => TorrentStatus.Processing, + "2" => TorrentStatus.Processing, + "3" => TorrentStatus.Processing, + "4" => TorrentStatus.Downloading, + "5" => TorrentStatus.Finished, + "6" => TorrentStatus.Finished, + _ => TorrentStatus.Error + }; + } + catch (Exception ex) + { + if (ex.Message == "Resource not found") + { + torrent.RdStatusRaw = "deleted"; + } + else + { + throw; + } + } + + return torrent; + } + + public async Task?> GetDownloadLinks(Data.Models.Data.Torrent torrent) + { + if (torrent.RdId == null) + { + return null; + } + + var rdTorrent = await GetInfo(torrent.RdId); + + if (rdTorrent.Links == null) + { + return null; + } + + var downloadLinks = rdTorrent.Links.Where(m => !String.IsNullOrWhiteSpace(m)).ToList(); + + Log($"Found {downloadLinks.Count} links", torrent); + + foreach (var link in downloadLinks) + { + Log($"{link}", torrent); + } + + // Check if all the links are set that have been selected + if (torrent.Files.Count(m => m.Selected) == downloadLinks.Count) + { + return downloadLinks; + } + + // Check if all all the links are set for manual selection + if (torrent.ManualFiles.Count == downloadLinks.Count) + { + return downloadLinks; + } + + // If there is only 1 link, delay for 1 minute to see if more links pop up. + if (downloadLinks.Count == 1 && torrent.RdEnded.HasValue && DateTime.UtcNow > torrent.RdEnded.Value.ToUniversalTime().AddMinutes(1)) + { + return downloadLinks; + } + + return null; + } + + private async Task GetInfo(String torrentId) + { + var result = await GetClient().Seedbox.ListAsync(torrentId); + + return Map(result.First()); + } + + private void Log(String message, Data.Models.Data.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/Torrents.cs b/server/RdtClient.Service/Services/Torrents.cs index 97d93bc..877314f 100644 --- a/server/RdtClient.Service/Services/Torrents.cs +++ b/server/RdtClient.Service/Services/Torrents.cs @@ -23,6 +23,7 @@ public class Torrents( AllDebridTorrentClient allDebridTorrentClient, PremiumizeTorrentClient premiumizeTorrentClient, RealDebridTorrentClient realDebridTorrentClient, + DebridLinkFrClient debridLinkFrClient, TorBoxTorrentClient torBoxTorrentClient) { private static readonly SemaphoreSlim RealDebridUpdateLock = new(1, 1); @@ -41,6 +42,7 @@ public class Torrents( Provider.Premiumize => premiumizeTorrentClient, Provider.RealDebrid => realDebridTorrentClient, Provider.AllDebrid => allDebridTorrentClient, + Provider.DebridLinkFr => debridLinkFrClient, Provider.TorBox => torBoxTorrentClient, _ => throw new("Invalid Provider") }; @@ -575,7 +577,7 @@ public class Torrents( await torrentData.UpdateComplete(download.TorrentId, null, null, false); } - + public async Task UpdateComplete(Guid torrentId, String? error, DateTimeOffset datetime, Boolean retry) { await torrentData.UpdateComplete(torrentId, error, datetime, retry); @@ -657,7 +659,7 @@ public class Torrents( Torrent torrent) { await RealDebridUpdateLock.WaitAsync(); - + try { var existingTorrent = await torrentData.GetByHash(infoHash); @@ -732,7 +734,7 @@ public class Torrents( var outputSb = new StringBuilder(); using var process = new Process(); - + process.StartInfo.FileName = fileName; process.StartInfo.Arguments = arguments; process.StartInfo.CreateNoWindow = true; @@ -758,7 +760,7 @@ public class Torrents( errorSb.AppendLine(data.Data.Trim()); }; - + process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine();