Merge pull request #680 from Wald764/pr-main-repo
🎉 add support of DebridLinkFr
This commit is contained in:
commit
7b4facd46f
10 changed files with 373 additions and 7 deletions
|
|
@ -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/)
|
||||
|
||||
<sub>(referal links so I can get a few free premium days)</sub>
|
||||
|
||||
## Docker Setup
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 4 providers:
|
||||
Not premium yet? You have the choice of 5 providers:
|
||||
<br />
|
||||
<a href="https://real-debrid.com/?id=1348683" target="_blank" rel="noopener"
|
||||
>Use this link to sign up to Real-Debrid.</a
|
||||
|
|
@ -59,6 +59,9 @@
|
|||
<a href="https://torbox.app/" target="_blank" rel="noopener"
|
||||
>Use this link to sign up to TorBox.</a>
|
||||
<br />
|
||||
<a href="https://debrid-link.com/" target="_blank" rel="noopener"
|
||||
>Use this link to sign up to DebridLink.</a
|
||||
>
|
||||
<small>(Referal links)</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
|
|
@ -69,6 +72,7 @@
|
|||
<option [ngValue]="1">AllDebrid</option>
|
||||
<option [ngValue]="2">Premiumize</option>
|
||||
<option [ngValue]="3">TorBox</option>
|
||||
<option [ngValue]="4">DebridLink</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -99,6 +103,12 @@
|
|||
You can find your API key here:
|
||||
<a href="https://torbox.app/settings" target="_blank" rel="noopener">https://torbox.app/settings</a>.
|
||||
</p>
|
||||
<p class="help" *ngIf="provider === 4">
|
||||
You can find your API key here:
|
||||
<a href="https://debrid-link.com/webapp/apikey" target="_blank" rel="noopener"
|
||||
>https://debrid-link.com/webapp/apikey</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
|
|
|
|||
|
|
@ -14,5 +14,8 @@ public enum Provider
|
|||
Premiumize,
|
||||
|
||||
[Description("TorBox")]
|
||||
TorBox
|
||||
TorBox,
|
||||
|
||||
[Description("DebridLink")]
|
||||
DebridLink
|
||||
}
|
||||
|
|
@ -159,6 +159,7 @@ public class DbSettingsProvider
|
|||
<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://debrid-link.com/"" target=""_blank"" rel=""noopener"">https://debrid-link.com/</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;
|
||||
|
|
@ -171,7 +172,9 @@ or
|
|||
or
|
||||
<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>")]
|
||||
<a href=""https://torbox.app/settings/"" target=""_blank"" rel=""noopener"">https://torbox.app/settings/</a>
|
||||
or
|
||||
<a href=""https://debrid-link.com/webapp/apikey"" target=""_blank"" rel=""noopener"">https://debrid-link.com/webapp/apikey</a>")]
|
||||
public String ApiKey { get; set; } = "";
|
||||
|
||||
[DisplayName("Automatically import and process torrents added to provider")]
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ public static class DiConfig
|
|||
services.AddScoped<TorBoxTorrentClient>();
|
||||
services.AddScoped<Torrents>();
|
||||
services.AddScoped<TorrentRunner>();
|
||||
services.AddScoped<DebridLinkClient>();
|
||||
|
||||
services.AddSingleton<IAuthorizationHandler, AuthSettingHandler>();
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="AllDebrid.NET" Version="1.0.16" />
|
||||
<PackageReference Include="Aria2.NET" Version="1.0.5" />
|
||||
<PackageReference Include="DebridLinkFr.NET" Version="1.0.3" />
|
||||
<PackageReference Include="Downloader" Version="3.3.3" />
|
||||
<PackageReference Include="Downloader.NET" Version="1.0.13" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.2" />
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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<DebridLinkClient> _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public DebridLinkClient(ILogger<DebridLinkClient> 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<TorrentFile>()).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<IList<TorrentClientTorrent>> GetTorrents()
|
||||
{
|
||||
var page = 0;
|
||||
var results = new List<Torrent>();
|
||||
|
||||
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<TorrentClientUser> 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<String> AddMagnet(String magnetLink)
|
||||
{
|
||||
var result = await GetClient().Seedbox.AddTorrentAsync(magnetLink);
|
||||
|
||||
return result.Id ?? "";
|
||||
}
|
||||
|
||||
public async Task<String> AddFile(Byte[] bytes)
|
||||
{
|
||||
var result = await GetClient().Seedbox.AddTorrentByFileAsync(bytes);
|
||||
|
||||
return result.Id ?? "";
|
||||
}
|
||||
|
||||
public Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
{
|
||||
return Task.FromResult<IList<TorrentClientAvailableFile>>([]);
|
||||
}
|
||||
|
||||
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<String> Unrestrict(String link)
|
||||
{
|
||||
return link;
|
||||
}
|
||||
|
||||
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.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<IList<String>?> 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<TorrentClientTorrent> 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<string> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in a new issue