using AllDebridNET; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using RdtClient.Data.Enums; using RdtClient.Data.Models.TorrentClient; using RdtClient.Service.Helpers; using System.Web; using File = AllDebridNET.File; using Torrent = RdtClient.Data.Models.Data.Torrent; namespace RdtClient.Service.Services.TorrentClients; public class AllDebridTorrentClient(ILogger logger, IHttpClientFactory httpClientFactory) : ITorrentClient { private AllDebridNETClient GetClient() { try { var apiKey = Settings.Get.Provider.ApiKey; if (String.IsNullOrWhiteSpace(apiKey)) { throw new("All-Debrid API Key not set in the settings"); } var httpClient = httpClientFactory.CreateClient(); httpClient.Timeout = TimeSpan.FromSeconds(10); var allDebridNetClient = new AllDebridNETClient("RealDebridClient", apiKey, httpClient); return allDebridNetClient; } catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException) { logger.LogError(ex, $"The connection to AllDebrid has timed out: {ex.Message}"); throw; } catch (TaskCanceledException ex) { logger.LogError(ex, $"The connection to AllDebrid has timed out: {ex.Message}"); throw; } } private static TorrentClientTorrent Map(Magnet torrent) { return new() { Id = torrent.Id.ToString(), Filename = torrent.Filename, OriginalFilename = torrent.Filename, Hash = torrent.Hash, Bytes = torrent.Size, OriginalBytes = torrent.Size, Host = null, Split = 0, Progress = (Int64) Math.Round(torrent.Downloaded * 100.0 / torrent.Size), Status = torrent.Status, StatusCode = torrent.StatusCode, Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate), Files = torrent.Links.Select((m, i) => new TorrentClientFile { Path = m.Filename, Bytes = m.Size, Id = i, Selected = true, }).ToList(), Links = torrent.Links.Select(m => m.LinkUrl.ToString()).ToList(), Ended = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.CompletionDate), Speed = torrent.DownloadSpeed, Seeders = torrent.Seeders }; } public async Task> GetTorrents() { var results = await GetClient().Magnet.StatusAllAsync(); return results.Select(Map).ToList(); } public async Task GetUser() { var user = await GetClient().User.GetAsync() ?? throw new("Unable to get user"); return new() { Username = user.Username, Expiration = user.PremiumUntil > 0 ? new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(user.PremiumUntil) : null }; } public async Task AddMagnet(String magnetLink) { var result = await GetClient().Magnet.UploadMagnetAsync(magnetLink); if (result?.Id == null) { throw new("Unable to add magnet link"); } var resultId = result.Id.ToString() ?? throw new($"Invalid responseID {result.Id}"); return resultId; } public async Task AddFile(Byte[] bytes) { var result = await GetClient().Magnet.UploadFileAsync(bytes); if (result?.Id == null) { throw new("Unable to add torrent file"); } var resultId = result.Id.ToString() ?? throw new($"Invalid responseID {result.Id}"); return resultId; } public Task> GetAvailableFiles(String hash) { return Task.FromResult>([]); } public Task SelectFiles(Torrent torrent) { return Task.CompletedTask; } public async Task Delete(String torrentId) { await GetClient().Magnet.DeleteAsync(torrentId); } public async Task Unrestrict(String link) { var result = await GetClient().Links.DownloadLinkAsync(link); if (result.Link == null) { throw new("Invalid result link"); } return result.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.StatusCode switch { 0 => TorrentStatus.Processing, // Processing In Queue. 1 => TorrentStatus.Downloading, // Processing Downloading. 2 => TorrentStatus.Downloading, // Processing Compressing / Moving. 3 => TorrentStatus.Uploading, // Processing Uploading. 4 => TorrentStatus.Finished, // Finished Ready. 5 => TorrentStatus.Error, // Error Upload fail. 6 => TorrentStatus.Error, // Error Internal error on unpacking. 7 => TorrentStatus.Error, // Error Not downloaded in 20 min. 8 => TorrentStatus.Error, // Error File too big. 9 => TorrentStatus.Error, // Error Internal error. 10 => TorrentStatus.Error, // Error Download took more than 72h. 11 => TorrentStatus.Error, // Error Deleted on the hoster website _ => TorrentStatus.Error }; } catch (AllDebridException ex) { if (ex.ErrorCode == "MAGNET_INVALID_ID") { torrent.RdStatusRaw = "deleted"; } else { throw; } } return torrent; } public async Task?> GetDownloadLinks(Torrent torrent) { if (torrent.RdId == null) { return null; } var magnet = await GetClient().Magnet.StatusAsync(torrent.RdId); if (magnet == null) { return null; } var links = magnet.Links; Log($"Getting download links", torrent); if (torrent.DownloadMinSize > 0) { var minFileSize = torrent.DownloadMinSize * 1024 * 1024; Log($"Determining which files are over {minFileSize} bytes", torrent); links = links.Where(m => m.Size > minFileSize) .ToList(); Log($"Found {links.Count} files that match the minimum file size criterea", torrent); } if (links.Count == 0) { Log($"Filtered all files out! Downloading ALL files instead!", torrent); links = magnet.Links; } Log($"Selecting links:"); foreach (var link in links) { var fileList = GetFiles(link.Files, ""); Log($"{link.Filename} ({link.Size}b) {link.LinkUrl}, contains files:{Environment.NewLine}{String.Join(Environment.NewLine, fileList)}"); } Log("", torrent); return links.Select(m => m.LinkUrl.ToString()).ToList(); } public Task GetFileName(Data.Models.Data.Download download) { if (String.IsNullOrWhiteSpace(download.Link)) { return Task.FromResult(null); } var uri = new Uri(download.Link); return Task.FromResult(HttpUtility.UrlDecode(uri.Segments.Last())); } private async Task GetInfo(String torrentId) { var result = await GetClient().Magnet.StatusAsync(torrentId) ?? throw new($"Unable to find magnet with ID {torrentId}"); return Map(result); } private static List GetFiles(IList files, String parent) { var result = new List(); foreach (var file in files) { if (!String.IsNullOrWhiteSpace(file.N)) { result.Add($"{parent}/{file.N}"); } if (file.E != null && file.E.Value.PurpleEArray != null && file.E.Value.PurpleEArray.Count > 0) { result.AddRange(GetFiles(file.E.Value.PurpleEArray, file.N)); } } return result; } private static List GetFiles(IList files, String parent) { var result = new List(); foreach (var file in files) { if (!String.IsNullOrWhiteSpace(file.N)) { result.Add($"{parent}/{file.N}"); } if (file.E != null && file.E.Count > 0) { result.AddRange(GetFiles(file.E, file.N)); } } return result; } private static List GetFiles(IList files, String parent) { var result = new List(); foreach (var file in files) { if (!String.IsNullOrWhiteSpace(file.N)) { result.Add($"{parent}/{file.N}"); } } return result; } private void Log(String message, Torrent? torrent = null) { if (torrent != null) { message = $"{message} {torrent.ToLog()}"; } logger.LogDebug(message); } }