From 3c6218955fdc9acc3c2f641b44bf4f88f8cffd47 Mon Sep 17 00:00:00 2001 From: Rafael Calafell <48203251+rafacc87@users.noreply.github.com> Date: Tue, 29 Oct 2024 23:14:31 +0100 Subject: [PATCH] New calls (#2) * NEW! Add ListConfigs() - Model - Call - Test - Readme * NEW! Add RecheckTorrents() - Call - Test - Readme * NEW! Add AddTorrentByUrl() - DelugeResponse - Call - Test - Constants - Readme * NEW! Add AddTorrentByUrl() - TorrentExtended - Calls - Test - Readme --------- Co-authored-by: Rafael Calafell Cladera --- DelugeRPCClient.Net.Tests/ConfigTests.cs | 29 ++ DelugeRPCClient.Net.Tests/Constants.cs | 1 + DelugeRPCClient.Net.Tests/TorrentsTests.cs | 61 ++++ DelugeRPCClient.Net/Core/DelugeResponse.cs | 7 + DelugeRPCClient.Net/DelugeClient.cs | 75 ++++- DelugeRPCClient.Net/Models/Config.cs | 287 ++++++++++++++++++ DelugeRPCClient.Net/Models/TorrentExtended.cs | 118 +++++++ README.md | 27 ++ 8 files changed, 604 insertions(+), 1 deletion(-) create mode 100644 DelugeRPCClient.Net.Tests/ConfigTests.cs create mode 100644 DelugeRPCClient.Net/Models/Config.cs create mode 100644 DelugeRPCClient.Net/Models/TorrentExtended.cs diff --git a/DelugeRPCClient.Net.Tests/ConfigTests.cs b/DelugeRPCClient.Net.Tests/ConfigTests.cs new file mode 100644 index 0000000..3b3ef30 --- /dev/null +++ b/DelugeRPCClient.Net.Tests/ConfigTests.cs @@ -0,0 +1,29 @@ +using DelugeRPCClient.Net.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace DelugeRPCClient.Net.Tests +{ + [TestClass] + public class ConfigTests + { + [TestMethod] + public async Task ListConfigs() + { + DelugeClient client = new DelugeClient(url: Constants.DelugeUrl, password: Constants.DelugePassword); + + bool loginResult = await client.Login(); + Assert.IsTrue(loginResult); + + Config configs = await client.ListConfigs(); + Assert.IsNotNull(configs); + + bool logoutResult = await client.Logout(); + Assert.IsTrue(logoutResult); + } + } +} diff --git a/DelugeRPCClient.Net.Tests/Constants.cs b/DelugeRPCClient.Net.Tests/Constants.cs index 4752aa4..a411622 100644 --- a/DelugeRPCClient.Net.Tests/Constants.cs +++ b/DelugeRPCClient.Net.Tests/Constants.cs @@ -11,5 +11,6 @@ internal class Constants internal const string TestLabelName = "testlabel"; internal const string TorrentMagnet = "magnet:?xt=urn:btih:30987c19cf0eae3cf47766f387c621fa78a58ab9&dn=debian-9.2.1-amd64-netinst.iso"; internal const string TestTorrentFilename = "test.torrent"; + internal const string TestTorrentUrl = "https://archive.org/download/die_siedler_2_151/die_siedler_2_151_archive.torrent"; } } diff --git a/DelugeRPCClient.Net.Tests/TorrentsTests.cs b/DelugeRPCClient.Net.Tests/TorrentsTests.cs index 9ae9696..7c65c28 100644 --- a/DelugeRPCClient.Net.Tests/TorrentsTests.cs +++ b/DelugeRPCClient.Net.Tests/TorrentsTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using System.Text; using System.Threading; @@ -32,6 +33,25 @@ public async Task ListAndGetTorrent() Assert.IsTrue(logoutResult); } + [TestMethod] + public async Task ListAndGetTorrentExtended() + { + DelugeClient client = new DelugeClient(url: Constants.DelugeUrl, password: Constants.DelugePassword); + + bool loginResult = await client.Login(); + Assert.IsTrue(loginResult); + + List torrents = await client.ListTorrentsExtended(); + Assert.IsNotNull(torrents); + Assert.AreNotEqual(0, torrents.Count); + + TorrentExtended torrent = await client.GetTorrentExtended(torrents[0].Hash); + Assert.IsNotNull(torrent); + + bool logoutResult = await client.Logout(); + Assert.IsTrue(logoutResult); + } + [TestMethod] public async Task AddRemoveTorrentByMagnet() { @@ -72,6 +92,26 @@ public async Task AddRemoveTorrentByFile() Assert.IsTrue(logoutResult); } + [TestMethod] + public async Task AddRemoveTorrentByUrl() + { + DelugeClient client = new DelugeClient(url: Constants.DelugeUrl, password: Constants.DelugePassword); + + bool loginResult = await client.Login(); + Assert.IsTrue(loginResult); + + Torrent torrent = await client.AddTorrentByUrl(Constants.TestTorrentUrl); + Assert.IsNotNull(torrent); + + Thread.Sleep(1000); + + bool removeTorrentResult = await client.RemoveTorrent(torrent.Hash); + Assert.IsTrue(removeTorrentResult); + + bool logoutResult = await client.Logout(); + Assert.IsTrue(logoutResult); + } + [TestMethod] public async Task PauseResumeTorrent() { @@ -104,5 +144,26 @@ public async Task PauseResumeTorrent() bool logoutResult = await client.Logout(); Assert.IsTrue(logoutResult); } + + [TestMethod] + public async Task RecheckTorrents() + { + DelugeClient client = new DelugeClient(url: Constants.DelugeUrl, password: Constants.DelugePassword); + + bool loginResult = await client.Login(); + Assert.IsTrue(loginResult); + + List torrents = await client.ListTorrents(); + Assert.IsNotNull(torrents); + Assert.AreNotEqual(0, torrents.Count); + + Torrent torrent = torrents[0]; + + bool? recheckResult = await client.RecheckTorrents(torrent.Hash.Split(",").ToList()); + Assert.IsNull(recheckResult); + + bool logoutResult = await client.Logout(); + Assert.IsTrue(logoutResult); + } } } diff --git a/DelugeRPCClient.Net/Core/DelugeResponse.cs b/DelugeRPCClient.Net/Core/DelugeResponse.cs index a1f6975..5282b96 100644 --- a/DelugeRPCClient.Net/Core/DelugeResponse.cs +++ b/DelugeRPCClient.Net/Core/DelugeResponse.cs @@ -16,4 +16,11 @@ internal class DelugeResponsee [JsonProperty(PropertyName = "error")] public DelugeError Error { get; set; } } + + internal class ResultEntry + { + public bool Success { get; set; } + public string Hash { get; set; } + + } } diff --git a/DelugeRPCClient.Net/DelugeClient.cs b/DelugeRPCClient.Net/DelugeClient.cs index 5fe09c8..2d46ce7 100644 --- a/DelugeRPCClient.Net/DelugeClient.cs +++ b/DelugeRPCClient.Net/DelugeClient.cs @@ -1,4 +1,5 @@ -using DelugeRPCClient.Net.Models; +using DelugeRPCClient.Net.Core; +using DelugeRPCClient.Net.Models; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -68,6 +69,19 @@ public async Task> ListTorrents(Dictionary filters return result.Values.ToList(); } + /// + /// List all torrents with details optionnaly filtered + /// + /// optional filters + /// List of torrents + public async Task> ListTorrentsExtended(Dictionary filters = null) + { + filters = filters ?? new Dictionary(); + var keys = typeof(TorrentExtended).GetAllJsonPropertyFromType(); + Dictionary result = await SendRequest>("core.get_torrents_status", filters, keys); + return result.Values.ToList(); + } + /// /// Get torrent informations by torrent hash /// @@ -79,6 +93,17 @@ public async Task GetTorrent(string hash) return torrents.Count > 0 ? torrents[0] : null; } + /// + /// Get torrent informations with details by torrent hash + /// + /// The requested torrent hash + /// the torrent object + public async Task GetTorrentExtended(string hash) + { + List torrents = await ListTorrentsExtended(new Dictionary() { { "hash", hash } }); + return torrents.Count > 0 ? torrents[0] : null; + } + /// /// Add a new torrent by magnet information /// @@ -114,6 +139,31 @@ public async Task AddTorrentByFile(string file, TorrentOptions options return await GetTorrent(hash); } + /// + /// Add a new torrent by .torrent url + /// + /// url of the .torrent file + /// Optional torrent options + /// the torrent object + /// + public async Task AddTorrentByUrl(string url, TorrentOptions options = null) + { + if (String.IsNullOrWhiteSpace(url)) throw new ArgumentException(nameof(url)); + var request = CreateRequest("web.download_torrent_from_url", url, options); + request.NullValueHandling = NullValueHandling.Ignore; + string pathTemp = await SendRequest(request); + + request = CreateRequest("web.add_torrents", new object[] { new object[] { new { options = new object { }, path = pathTemp } } }); + var resultAdd = await SendRequest>>(request); + return await GetTorrent(resultAdd.Select(item => + new ResultEntry + { + Success = item[0] is bool success && success, + Hash = item[1] as string + } + ).FirstOrDefault().Hash); + } + /// /// Remove a torrent from deluge /// @@ -149,6 +199,29 @@ public async Task ResumeTorrent(string hash) return result == null; } + /// + /// Recheck torrents + /// + /// Hash of the target torrents + /// true if the action is successfull + public async Task RecheckTorrents(List hash) + { + return await SendRequest("core.force_recheck", hash); + } + + #endregion + + #region Config + + /// + /// List all existing labels + /// + /// list of labels + public async Task ListConfigs() + { + return await SendRequest("core.get_config"); + } + #endregion #region Labels diff --git a/DelugeRPCClient.Net/Models/Config.cs b/DelugeRPCClient.Net/Models/Config.cs new file mode 100644 index 0000000..e95a34e --- /dev/null +++ b/DelugeRPCClient.Net/Models/Config.cs @@ -0,0 +1,287 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace DelugeRPCClient.Net.Models +{ + public class Config + { + [JsonProperty(PropertyName = "send_info")] + public bool SendInfo { get; set; } + + [JsonProperty(PropertyName = "info_sent")] + public double InfoSent { get; set; } + + [JsonProperty(PropertyName = "daemon_port")] + public int DaemonPort { get; set; } + + [JsonProperty(PropertyName = "allow_remote")] + public bool AllowRemote { get; set; } + + [JsonProperty(PropertyName = "pre_allocate_storage")] + public bool PreAllocateStorage { get; set; } + + [JsonProperty(PropertyName = "download_location")] + public string DownloadLocation { get; set; } + + [JsonProperty(PropertyName = "listen_ports")] + public List ListenPorts { get; set; } + + [JsonProperty(PropertyName = "listen_interface")] + public string ListenInterface { get; set; } + + [JsonProperty(PropertyName = "outgoing_interface")] + public string OutgoingInterface { get; set; } + + [JsonProperty(PropertyName = "random_port")] + public bool RandomPort { get; set; } + + [JsonProperty(PropertyName = "listen_random_port")] + public int ListenRandomPort { get; set; } + + [JsonProperty(PropertyName = "listen_use_sys_port")] + public bool ListenUseSysPort { get; set; } + + [JsonProperty(PropertyName = "listen_reuse_port")] + public bool ListenReusePort { get; set; } + + [JsonProperty(PropertyName = "outgoing_ports")] + public List OutgoingPorts { get; set; } + + [JsonProperty(PropertyName = "random_outgoing_ports")] + public bool RandomOutgoingPorts { get; set; } + + [JsonProperty(PropertyName = "copy_torrent_file")] + public bool CopyTorrentFile { get; set; } + + [JsonProperty(PropertyName = "del_copy_torrent_file")] + public bool DelCopyTorrentFile { get; set; } + + [JsonProperty(PropertyName = "torrentfiles_location")] + public string TorrentFilesLocation { get; set; } + + [JsonProperty(PropertyName = "plugins_location")] + public string PluginsLocation { get; set; } + + [JsonProperty(PropertyName = "prioritize_first_last_pieces")] + public bool PrioritizeFirstLastPieces { get; set; } + + [JsonProperty(PropertyName = "sequential_download")] + public bool SequentialDownload { get; set; } + + [JsonProperty(PropertyName = "dht")] + public bool DHT { get; set; } + + [JsonProperty(PropertyName = "upnp")] + public bool UPnP { get; set; } + + [JsonProperty(PropertyName = "natpmp")] + public bool NATPMP { get; set; } + + [JsonProperty(PropertyName = "utpex")] + public bool UTPEx { get; set; } + + [JsonProperty(PropertyName = "lsd")] + public bool LSD { get; set; } + + [JsonProperty(PropertyName = "enc_in_policy")] + public int EncInPolicy { get; set; } + + [JsonProperty(PropertyName = "enc_out_policy")] + public int EncOutPolicy { get; set; } + + [JsonProperty(PropertyName = "enc_level")] + public int EncLevel { get; set; } + + [JsonProperty(PropertyName = "max_connections_global")] + public int MaxConnectionsGlobal { get; set; } + + [JsonProperty(PropertyName = "max_upload_speed")] + public double MaxUploadSpeed { get; set; } + + [JsonProperty(PropertyName = "max_download_speed")] + public double MaxDownloadSpeed { get; set; } + + [JsonProperty(PropertyName = "max_upload_slots_global")] + public int MaxUploadSlotsGlobal { get; set; } + + [JsonProperty(PropertyName = "max_half_open_connections")] + public int MaxHalfOpenConnections { get; set; } + + [JsonProperty(PropertyName = "max_connections_per_second")] + public int MaxConnectionsPerSecond { get; set; } + + [JsonProperty(PropertyName = "ignore_limits_on_local_network")] + public bool IgnoreLimitsOnLocalNetwork { get; set; } + + [JsonProperty(PropertyName = "max_connections_per_torrent")] + public int MaxConnectionsPerTorrent { get; set; } + + [JsonProperty(PropertyName = "max_upload_slots_per_torrent")] + public int MaxUploadSlotsPerTorrent { get; set; } + + [JsonProperty(PropertyName = "max_upload_speed_per_torrent")] + public double MaxUploadSpeedPerTorrent { get; set; } + + [JsonProperty(PropertyName = "max_download_speed_per_torrent")] + public double MaxDownloadSpeedPerTorrent { get; set; } + + [JsonProperty(PropertyName = "enabled_plugins")] + public List EnabledPlugins { get; set; } + + [JsonProperty(PropertyName = "add_paused")] + public bool AddPaused { get; set; } + + [JsonProperty(PropertyName = "max_active_seeding")] + public int MaxActiveSeeding { get; set; } + + [JsonProperty(PropertyName = "max_active_downloading")] + public int MaxActiveDownloading { get; set; } + + [JsonProperty(PropertyName = "max_active_limit")] + public int MaxActiveLimit { get; set; } + + [JsonProperty(PropertyName = "dont_count_slow_torrents")] + public bool DontCountSlowTorrents { get; set; } + + [JsonProperty(PropertyName = "queue_new_to_top")] + public bool QueueNewToTop { get; set; } + + [JsonProperty(PropertyName = "stop_seed_at_ratio")] + public bool StopSeedAtRatio { get; set; } + + [JsonProperty(PropertyName = "remove_seed_at_ratio")] + public bool RemoveSeedAtRatio { get; set; } + + [JsonProperty(PropertyName = "stop_seed_ratio")] + public double StopSeedRatio { get; set; } + + [JsonProperty(PropertyName = "share_ratio_limit")] + public double ShareRatioLimit { get; set; } + + [JsonProperty(PropertyName = "seed_time_ratio_limit")] + public double SeedTimeRatioLimit { get; set; } + + [JsonProperty(PropertyName = "seed_time_limit")] + public int SeedTimeLimit { get; set; } + + [JsonProperty(PropertyName = "auto_managed")] + public bool AutoManaged { get; set; } + + [JsonProperty(PropertyName = "move_completed")] + public bool MoveCompleted { get; set; } + + [JsonProperty(PropertyName = "move_completed_path")] + public string MoveCompletedPath { get; set; } + + [JsonProperty(PropertyName = "move_completed_paths_list")] + public List MoveCompletedPathsList { get; set; } + + [JsonProperty(PropertyName = "download_location_paths_list")] + public List DownloadLocationPathsList { get; set; } + + [JsonProperty(PropertyName = "path_chooser_show_chooser_button_on_localhost")] + public bool PathChooserShowChooserButtonOnLocalhost { get; set; } + + [JsonProperty(PropertyName = "path_chooser_auto_complete_enabled")] + public bool PathChooserAutoCompleteEnabled { get; set; } + + [JsonProperty(PropertyName = "path_chooser_accelerator_string")] + public string PathChooserAcceleratorString { get; set; } + + [JsonProperty(PropertyName = "path_chooser_max_popup_rows")] + public int PathChooserMaxPopupRows { get; set; } + + [JsonProperty(PropertyName = "path_chooser_show_hidden_files")] + public bool PathChooserShowHiddenFiles { get; set; } + + [JsonProperty(PropertyName = "new_release_check")] + public bool NewReleaseCheck { get; set; } + + [JsonProperty(PropertyName = "proxy")] + public ProxyConfig Proxy { get; set; } + + [JsonProperty(PropertyName = "peer_tos")] + public string PeerTos { get; set; } + + [JsonProperty(PropertyName = "rate_limit_ip_overhead")] + public bool RateLimitIpOverhead { get; set; } + + [JsonProperty(PropertyName = "geoip_db_location")] + public string GeoIpDbLocation { get; set; } + + [JsonProperty(PropertyName = "cache_size")] + public int CacheSize { get; set; } + + [JsonProperty(PropertyName = "cache_expiry")] + public int CacheExpiry { get; set; } + + [JsonProperty(PropertyName = "auto_manage_prefer_seeds")] + public bool AutoManagePreferSeeds { get; set; } + + [JsonProperty(PropertyName = "shared")] + public bool Shared { get; set; } + + [JsonProperty(PropertyName = "super_seeding")] + public bool SuperSeeding { get; set; } + + public bool ExistProperty(string propertyName) + { + PropertyInfo property = typeof(Config).GetProperty(propertyName); + + if (property != null) + { + return true; + } + + return false; + } + + public string GetPropertyValue(string propertyName) + { + PropertyInfo property = typeof(Config).GetProperty(propertyName); + + if (property != null) + { + return property.GetValue(this)?.ToString(); + } + + return null; + } + } + + public class ProxyConfig + { + [JsonProperty(PropertyName = "type")] + public int Type { get; set; } + + [JsonProperty(PropertyName = "hostname")] + public string Hostname { get; set; } + + [JsonProperty(PropertyName = "username")] + public string Username { get; set; } + + [JsonProperty(PropertyName = "password")] + public string Password { get; set; } + + [JsonProperty(PropertyName = "port")] + public int Port { get; set; } + + [JsonProperty(PropertyName = "proxy_hostnames")] + public bool ProxyHostnames { get; set; } + + [JsonProperty(PropertyName = "proxy_peer_connections")] + public bool ProxyPeerConnections { get; set; } + + [JsonProperty(PropertyName = "proxy_tracker_connections")] + public bool ProxyTrackerConnections { get; set; } + + [JsonProperty(PropertyName = "force_proxy")] + public bool ForceProxy { get; set; } + + [JsonProperty(PropertyName = "anonymous_mode")] + public bool AnonymousMode { get; set; } + } +} \ No newline at end of file diff --git a/DelugeRPCClient.Net/Models/TorrentExtended.cs b/DelugeRPCClient.Net/Models/TorrentExtended.cs new file mode 100644 index 0000000..4cdf591 --- /dev/null +++ b/DelugeRPCClient.Net/Models/TorrentExtended.cs @@ -0,0 +1,118 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace DelugeRPCClient.Net.Models +{ + public class TorrentExtended : Torrent + { + [JsonProperty(PropertyName = "total_done")] + public long TotalDone { get; set; } + + [JsonProperty(PropertyName = "total_payload_download")] + public long TotalPayloadDownload { get; set; } + + [JsonProperty(PropertyName = "total_uploaded")] + public long TotalUploaded { get; set; } + + [JsonProperty(PropertyName = "next_announce")] + public int NextAnnounce { get; set; } + + [JsonProperty(PropertyName = "tracker_status")] + public string TrackerStatus { get; set; } + + [JsonProperty(PropertyName = "num_pieces")] + public int NumPieces { get; set; } + + [JsonProperty(PropertyName = "piece_length")] + public long PieceLength { get; set; } + + [JsonProperty(PropertyName = "is_auto_managed")] + public bool IsAutoManaged { get; set; } + + [JsonProperty(PropertyName = "active_time")] + public long ActiveTime { get; set; } + + [JsonProperty(PropertyName = "seeding_time")] + public long SeedingTime { get; set; } + + [JsonProperty(PropertyName = "time_since_transfer")] + public long TimeSinceTransfer { get; set; } + + [JsonProperty(PropertyName = "seed_rank")] + public int SeedRank { get; set; } + + [JsonProperty(PropertyName = "last_seen_complete")] + public long LastSeenComplete { get; set; } + + [JsonProperty(PropertyName = "completed_time")] + public long CompletedTime { get; set; } + + [JsonProperty(PropertyName = "owner")] + public string Owner { get; set; } + + [JsonProperty(PropertyName = "public")] + public bool Public { get; set; } + + [JsonProperty(PropertyName = "shared")] + public bool Shared { get; set; } + + [JsonProperty(PropertyName = "queue")] + public int Queue { get; set; } + + [JsonProperty(PropertyName = "total_wanted")] + public long TotalWanted { get; set; } + + [JsonProperty(PropertyName = "state")] + public string State { get; set; } + + [JsonProperty(PropertyName = "progress")] + public float Progress { get; set; } + + [JsonProperty(PropertyName = "num_seeds")] + public int NumSeeds { get; set; } + + [JsonProperty(PropertyName = "total_seeds")] + public int TotalSeeds { get; set; } + + [JsonProperty(PropertyName = "num_peers")] + public int NumPeers { get; set; } + + [JsonProperty(PropertyName = "total_peers")] + public int TotalPeers { get; set; } + + [JsonProperty(PropertyName = "download_payload_rate")] + public long DownloadPayloadRate { get; set; } + + [JsonProperty(PropertyName = "upload_payload_rate")] + public long UploadPayloadRate { get; set; } + + [JsonProperty(PropertyName = "eta")] + public long Eta { get; set; } + + [JsonProperty(PropertyName = "distributed_copies")] + public float DistributedCopies { get; set; } + + [JsonProperty(PropertyName = "time_added")] + public int TimeAdded { get; set; } + + [JsonProperty(PropertyName = "tracker_host")] + public string TrackerHost { get; set; } + + [JsonProperty(PropertyName = "download_location")] + public string DownloadLocation { get; set; } + + [JsonProperty(PropertyName = "total_remaining")] + public long TotalRemaining { get; set; } + + [JsonProperty(PropertyName = "max_download_speed")] + public long MaxDownloadSpeed { get; set; } + + [JsonProperty(PropertyName = "max_upload_speed")] + public long MaxUploadSpeed { get; set; } + + [JsonProperty(PropertyName = "seeds_peers_ratio")] + public float SeedsPeersRatio { get; set; } + } +} diff --git a/README.md b/README.md index e6497b6..8b87e72 100644 --- a/README.md +++ b/README.md @@ -46,11 +46,21 @@ List torrents List torrents = await client.ListTorrents(); ``` +List torrents extended +```C# +List torrents = await client.ListTorrentsExtended(); +``` + Get torrent by hash ```C# Torrent torrent = await client.GetTorrent(torrentHash); ``` +Get torrent extended by hash +```C# +TorrentExtended torrent = await client.GetTorrentExtended(torrentHash); +``` + #### Add Torrent Add a torrent by magnet uri @@ -63,6 +73,11 @@ Add a torrent by .torrent file Torrent torrent = await client.AddTorrentByFile(torrentFilename); ``` +Add a torrent by .torrent url +```C# +Torrent torrent = await client.AddTorrentByUrl(torrentUrl); +``` + #### Remove Torrent ```C# bool removeTorrentResult = await client.RemoveTorrent(torrentHash); @@ -78,6 +93,18 @@ Resume Torrent bool resumeResult = await client.ResumeTorrent(torrentHash); ``` +#### Recheck Torrents +```C# +bool recheckTorrentResult = await client.RecheckTorrents(List torrentsHash); +``` + +## Configs + +#### List configs +```C# +List config = await client.ListConfigs(); +``` + ## Labels #### List existing labels