diff --git a/README.md b/README.md
index 22eaffc..2cc71c8 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,8 @@ This is a web interface to manage your torrents on Real-Debrid, AllDebrid or Pre
[Click here to sign up for Premiumize.](https://www.premiumize.me/)
+[Click here to sign up for DebridLink.](https://debrid-link.fr/)
+
(referal links so I can get a few free premium days)
## Docker Setup
diff --git a/client/src/app/navbar/navbar.component.ts b/client/src/app/navbar/navbar.component.ts
index 95c8067..7550699 100644
--- a/client/src/app/navbar/navbar.component.ts
+++ b/client/src/app/navbar/navbar.component.ts
@@ -38,6 +38,9 @@ export class NavbarComponent implements OnInit {
case 'TorBox':
this.providerLink = 'https://torbox.app/';
break;
+ case 'DebridLink':
+ this.providerLink = 'https://debrid-link.com/';
+ break;
}
});
}
diff --git a/client/src/app/setup/setup.component.html b/client/src/app/setup/setup.component.html
index 28c81e3..a096260 100644
--- a/client/src/app/setup/setup.component.html
+++ b/client/src/app/setup/setup.component.html
@@ -42,7 +42,7 @@
@@ -69,6 +72,7 @@
AllDebrid
Premiumize
TorBox
+ DebridLink
@@ -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..4c6084b 100644
--- a/server/RdtClient.Data/Enums/Provider.cs
+++ b/server/RdtClient.Data/Enums/Provider.cs
@@ -14,5 +14,8 @@ public enum Provider
Premiumize,
[Description("TorBox")]
- TorBox
+ TorBox,
+
+ [Description("DebridLink")]
+ DebridLink
}
\ 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..71de2b4 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 8f2c179..a3835aa 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/DownloadClient.cs b/server/RdtClient.Service/Services/DownloadClient.cs
index 07cdbec..0ad6df4 100644
--- a/server/RdtClient.Service/Services/DownloadClient.cs
+++ b/server/RdtClient.Service/Services/DownloadClient.cs
@@ -46,6 +46,11 @@ public class DownloadClient(Download download, Torrent torrent, String destinati
downloadPath = AllDebridTorrentClient.GetSymlinkPath(torrent, download);
}
+ if (torrent.ClientKind == Torrent.TorrentClientKind.DebridLink && Type == Data.Enums.DownloadClient.Symlink)
+ {
+ downloadPath = DebridLinkClient.GetSymlinkPath(torrent, download);
+ }
+
if (filePath == null || downloadPath == null)
{
throw new("Invalid download path");
diff --git a/server/RdtClient.Service/Services/TorrentClients/DebridLinkTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/DebridLinkTorrentClient.cs
new file mode 100644
index 0000000..edbc911
--- /dev/null
+++ b/server/RdtClient.Service/Services/TorrentClients/DebridLinkTorrentClient.cs
@@ -0,0 +1,336 @@
+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;
+using System.Web;
+using Download = RdtClient.Data.Models.Data.Download;
+
+namespace RdtClient.Service.Services.TorrentClients;
+
+public class DebridLinkClient : ITorrentClient
+{
+ private readonly ILogger _logger;
+ private readonly IHttpClientFactory _httpClientFactory;
+
+ public DebridLinkClient(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("DebridLink API Key not set in the settings");
+ }
+
+ var httpClient = _httpClientFactory.CreateClient();
+ httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
+
+ var DebridLinkClient = new DebridLinkFrNETClient(apiKey, httpClient);
+
+ return DebridLinkClient;
+ }
+ catch (AggregateException ae)
+ {
+ foreach (var inner in ae.InnerExceptions)
+ {
+ _logger.LogError(inner, $"The connection to DebridLink has failed: {inner.Message}");
+ }
+
+ throw;
+ }
+ catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
+ {
+ _logger.LogError(ex, $"The connection to DebridLink has timed out: {ex.Message}");
+
+ throw;
+ }
+ catch (TaskCanceledException ex)
+ {
+ _logger.LogError(ex, $"The connection to DebridLink 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 Task> GetAvailableFiles(String hash)
+ {
+ return Task.FromResult>([]);
+ }
+
+ 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.ClientKind = Data.Models.Data.Torrent.TorrentClientKind.DebridLink;
+ 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);
+ }
+
+ public Task GetFileName(string downloadUrl)
+ {
+ if (String.IsNullOrWhiteSpace(downloadUrl))
+ {
+ return Task.FromResult("");
+ }
+
+ var uri = new Uri(downloadUrl);
+
+ return Task.FromResult(HttpUtility.UrlDecode(uri.Segments.Last()));
+ }
+
+ public static String? GetSymlinkPath(Data.Models.Data.Torrent torrent, Download download)
+ {
+ if (torrent.RdName == null || download.FileName == null)
+ {
+ return null;
+ }
+
+ // Single file torrents always have that file at `/mnt-root/Seedbox/filename.ext`
+ if (torrent.Files?.Count == 1)
+ {
+ return download.FileName;
+ }
+
+ return Path.Combine(torrent.RdName, download.FileName);
+ }
+}
\ No newline at end of file
diff --git a/server/RdtClient.Service/Services/Torrents.cs b/server/RdtClient.Service/Services/Torrents.cs
index 97d93bc..0edb19f 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,
+ DebridLinkClient debridLinkClient,
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.DebridLink => debridLinkClient,
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();