using System.Diagnostics; using DebridLinkFrNET; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using RdtClient.Data.Enums; using RdtClient.Data.Models.Data; using RdtClient.Data.Models.DebridClient; using RdtClient.Data.Models.Internal; using RdtClient.Service.Helpers; using Download = RdtClient.Data.Models.Data.Download; using Torrent = DebridLinkFrNET.Models.Torrent; namespace RdtClient.Service.Services.DebridClients; public class DebridLinkClient(ILogger logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter, ISettings settings) : IDebridClient { public async Task> GetDownloads() { 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() { Username = user.Username, Expiration = user.PremiumLeft > 0 ? DateTimeOffset.Now.AddSeconds(user.PremiumLeft) : null }; } public async Task AddTorrentMagnet(String magnetLink) { try { var result = await GetClient().Seedbox.AddTorrentAsync(magnetLink); return result.Id ?? ""; } catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) || ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase)) { throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2)); } } public async Task AddTorrentFile(Byte[] bytes) { try { var result = await GetClient().Seedbox.AddTorrentByFileAsync(bytes); return result.Id ?? ""; } catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) || ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase)) { throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2)); } } public Task AddNzbLink(String nzbLink) { throw new NotSupportedException(); } public Task AddNzbFile(Byte[] bytes, String? name) { throw new NotSupportedException(); } public Task> GetAvailableFiles(String hash) { return Task.FromResult>([]); } /// public Task SelectFiles(Data.Models.Data.Torrent torrent) { return Task.FromResult(torrent.Files.Count); } public async Task Delete(Data.Models.Data.Torrent torrent) { await GetClient().Seedbox.DeleteAsync(torrent.RdId!); } public Task Unrestrict(Data.Models.Data.Torrent torrent, String link) { return Task.FromResult(link); } public async Task UpdateData(Data.Models.Data.Torrent torrent, DebridClientTorrent? torrentClientTorrent) { try { if (torrent.RdId == null) { return torrent; } var rdTorrent = await GetInfo(torrent.RdId) ?? throw new($"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 = Provider.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?> GetDownloadInfos(Data.Models.Data.Torrent torrent) { if (torrent.RdId == null) { return null; } var rdTorrent = await GetInfo(torrent.RdId); if (rdTorrent.Links == null) { return null; } return rdTorrent.Files? .Where(m => fileFilter.IsDownloadable(torrent, m.Path, m.Bytes) && m.DownloadLink != null) .Select(m => new DownloadInfo { RestrictedLink = m.DownloadLink!, FileName = Path.GetFileName(m.Path) }) .ToList(); } /// public Task GetFileName(Download download) { // FileName is set in GetDownlaadInfos Debug.Assert(download.FileName != null); return Task.FromResult(download.FileName); } private DebridLinkFrNETClient GetClient() { try { var apiKey = settings.Current.Provider.ApiKey; if (String.IsNullOrWhiteSpace(apiKey)) { throw new("DebridLink API Key not set in the settings"); } var httpClient = httpClientFactory.CreateClient(); httpClient.Timeout = TimeSpan.FromSeconds(settings.Current.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 DebridClientTorrent Map(Torrent torrent) { return new() { 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 ?? []).Select((m, i) => new DebridClientFile { Path = m.Name ?? "", Bytes = m.Size, Id = i, Selected = true, DownloadLink = m.DownloadUrl }) .ToList(), Links = torrent.Files?.Select(m => m.DownloadUrl.ToString()).ToList(), Ended = null, Speed = torrent.UploadSpeed, Seeders = torrent.PeersConnected }; } 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 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); } }