From 7c6e08c5c0e7c403bc34dc1eb0f42e28f0f46bdd Mon Sep 17 00:00:00 2001
From: Rafael Calafell Cladera <rafaelillopalma@hotmail.com>
Date: Tue, 29 Oct 2024 07:33:19 +0100
Subject: [PATCH 1/5] NEW! Add ListConfigs()

- Model
- Call
- Test
- Readme
---
 DelugeRPCClient.Net.Tests/ConfigTests.cs |  29 +++
 DelugeRPCClient.Net/DelugeClient.cs      |  13 +
 DelugeRPCClient.Net/Models/Config.cs     | 287 +++++++++++++++++++++++
 README.md                                |   7 +
 4 files changed, 336 insertions(+)
 create mode 100644 DelugeRPCClient.Net.Tests/ConfigTests.cs
 create mode 100644 DelugeRPCClient.Net/Models/Config.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/DelugeClient.cs b/DelugeRPCClient.Net/DelugeClient.cs
index 5fe09c8..2a923a6 100644
--- a/DelugeRPCClient.Net/DelugeClient.cs
+++ b/DelugeRPCClient.Net/DelugeClient.cs
@@ -151,6 +151,19 @@ public async Task<bool> ResumeTorrent(string hash)
 
         #endregion
 
+        #region Config
+
+        /// <summary>
+        /// List all existing labels
+        /// </summary>
+        /// <returns>list of labels</returns>
+        public async Task<Config> ListConfigs()
+        {
+            return await SendRequest<Config>("core.get_config");
+        }
+
+        #endregion
+
         #region Labels
 
         /// <summary>
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<int> 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<int> 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<string> 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<string> MoveCompletedPathsList { get; set; }
+
+        [JsonProperty(PropertyName = "download_location_paths_list")]
+        public List<string> 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/README.md b/README.md
index e6497b6..b9439eb 100644
--- a/README.md
+++ b/README.md
@@ -78,6 +78,13 @@ Resume Torrent
 bool resumeResult = await client.ResumeTorrent(torrentHash);
 ```
 
+## Configs
+
+#### List configs
+```C#
+List<Config> config = await client.ListConfigs();
+```
+
 ## Labels
 
 #### List existing labels

From 50f63d3c27ad3996fff7761d5f939f839ca8545d Mon Sep 17 00:00:00 2001
From: Rafael Calafell Cladera <rafaelillopalma@hotmail.com>
Date: Tue, 29 Oct 2024 07:41:43 +0100
Subject: [PATCH 2/5] NEW! Add RecheckTorrents()

- Call
- Test
- Readme
---
 DelugeRPCClient.Net.Tests/TorrentsTests.cs | 22 ++++++++++++++++++++++
 DelugeRPCClient.Net/DelugeClient.cs        | 10 ++++++++++
 README.md                                  |  5 +++++
 3 files changed, 37 insertions(+)

diff --git a/DelugeRPCClient.Net.Tests/TorrentsTests.cs b/DelugeRPCClient.Net.Tests/TorrentsTests.cs
index 9ae9696..7bedb2a 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;
@@ -104,5 +105,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<Torrent> 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/DelugeClient.cs b/DelugeRPCClient.Net/DelugeClient.cs
index 2a923a6..5285a2f 100644
--- a/DelugeRPCClient.Net/DelugeClient.cs
+++ b/DelugeRPCClient.Net/DelugeClient.cs
@@ -149,6 +149,16 @@ public async Task<bool> ResumeTorrent(string hash)
             return result == null;
         }
 
+        /// <summary>
+        /// Recheck torrents
+        /// </summary>
+        /// <param name="hash">Hash of the target torrents</param>
+        /// <returns>true if the action is successfull</returns>
+        public async Task<bool?> RecheckTorrents(List<string> hash)
+        {
+            return await SendRequest<bool?>("core.force_recheck", hash);
+        }
+
         #endregion
 
         #region Config
diff --git a/README.md b/README.md
index b9439eb..f83952e 100644
--- a/README.md
+++ b/README.md
@@ -78,6 +78,11 @@ Resume Torrent
 bool resumeResult = await client.ResumeTorrent(torrentHash);
 ```
 
+#### Recheck Torrents
+```C#
+bool recheckTorrentResult = await client.RecheckTorrents(List<string> torrentsHash);
+```
+
 ## Configs
 
 #### List configs

From f6e920cdaad19f373404a14a61d04b3f37b3577a Mon Sep 17 00:00:00 2001
From: Rafael Calafell Cladera <rafaelillopalma@hotmail.com>
Date: Tue, 29 Oct 2024 07:45:33 +0100
Subject: [PATCH 3/5] NEW! Add AddTorrentByUrl()

- DelugeResponse
- Call
- Test
- Constants
- Readme
---
 DelugeRPCClient.Net.Tests/Constants.cs     |  1 +
 DelugeRPCClient.Net.Tests/TorrentsTests.cs | 20 ++++++++++++++++
 DelugeRPCClient.Net/Core/DelugeResponse.cs |  7 ++++++
 DelugeRPCClient.Net/DelugeClient.cs        | 28 +++++++++++++++++++++-
 README.md                                  |  6 +++++
 5 files changed, 61 insertions(+), 1 deletion(-)

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 7bedb2a..8b0da9e 100644
--- a/DelugeRPCClient.Net.Tests/TorrentsTests.cs
+++ b/DelugeRPCClient.Net.Tests/TorrentsTests.cs
@@ -73,6 +73,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()
         {
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<T>
         [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 5285a2f..9daa1c6 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;
@@ -114,6 +115,31 @@ public async Task<Torrent> AddTorrentByFile(string file, TorrentOptions options
             return await GetTorrent(hash);
         }
 
+        /// <summary>
+        /// Add a new torrent by .torrent url
+        /// </summary>
+        /// <param name="file">url of the .torrent file</param>
+        /// <param name="options">Optional torrent options</param>
+        /// <returns>the torrent object</returns>
+        /// <exception cref="ArgumentException"></exception>
+        public async Task<Torrent> 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<string>(request);
+
+            request = CreateRequest("web.add_torrents", new object[] { new object[] { new { options = new object { }, path = pathTemp } } });
+            var resultAdd = await SendRequest<List<List<Object>>>(request);
+            return await GetTorrent(resultAdd.Select(item =>
+                new ResultEntry
+                {
+                    Success = item[0] is bool success && success,
+                    Hash = item[1] as string
+                }
+            ).FirstOrDefault().Hash);
+        }
+
         /// <summary>
         /// Remove a torrent from deluge
         /// </summary>
diff --git a/README.md b/README.md
index f83952e..fc962cf 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,12 @@ Add a torrent by .torrent file
 ```C#
 Torrent torrent = await client.AddTorrentByFile(torrentFilename);
 ```
+```
+
+Add a torrent by .torrent url
+```C#
+Torrent torrent = await client.AddTorrentByUrl(torrentUrl);
+```
 
 #### Remove Torrent
 ```C#

From d80ffb6b7f0977132f8dfa77bdfdf60583d02ee4 Mon Sep 17 00:00:00 2001
From: Rafael Calafell Cladera <rafaelillopalma@hotmail.com>
Date: Tue, 29 Oct 2024 19:19:44 +0100
Subject: [PATCH 4/5] NEW! Add AddTorrentByUrl()

- TorrentExtended
- Calls
- Test
- Readme
---
 DelugeRPCClient.Net.Tests/TorrentsTests.cs    |  19 +++
 DelugeRPCClient.Net/DelugeClient.cs           |  24 ++++
 DelugeRPCClient.Net/Models/TorrentExtended.cs | 118 ++++++++++++++++++
 README.md                                     |  11 +-
 4 files changed, 171 insertions(+), 1 deletion(-)
 create mode 100644 DelugeRPCClient.Net/Models/TorrentExtended.cs

diff --git a/DelugeRPCClient.Net.Tests/TorrentsTests.cs b/DelugeRPCClient.Net.Tests/TorrentsTests.cs
index 8b0da9e..7c65c28 100644
--- a/DelugeRPCClient.Net.Tests/TorrentsTests.cs
+++ b/DelugeRPCClient.Net.Tests/TorrentsTests.cs
@@ -33,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<TorrentExtended> 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()
         {
diff --git a/DelugeRPCClient.Net/DelugeClient.cs b/DelugeRPCClient.Net/DelugeClient.cs
index 9daa1c6..2d46ce7 100644
--- a/DelugeRPCClient.Net/DelugeClient.cs
+++ b/DelugeRPCClient.Net/DelugeClient.cs
@@ -69,6 +69,19 @@ public async Task<List<Torrent>> ListTorrents(Dictionary<string, string> filters
             return result.Values.ToList();
         }
 
+        /// <summary>
+        /// List all torrents with details optionnaly filtered
+        /// </summary>
+        /// <param name="filters">optional filters</param>
+        /// <returns>List of torrents</returns>
+        public async Task<List<TorrentExtended>> ListTorrentsExtended(Dictionary<string, string> filters = null)
+        {
+            filters = filters ?? new Dictionary<string, string>();
+            var keys = typeof(TorrentExtended).GetAllJsonPropertyFromType();
+            Dictionary<string, TorrentExtended> result = await SendRequest<Dictionary<string, TorrentExtended>>("core.get_torrents_status", filters, keys);
+            return result.Values.ToList();
+        }
+
         /// <summary>
         /// Get torrent informations by torrent hash
         /// </summary>
@@ -80,6 +93,17 @@ public async Task<Torrent> GetTorrent(string hash)
             return torrents.Count > 0 ? torrents[0] : null;
         }
 
+        /// <summary>
+        /// Get torrent informations with details by torrent hash
+        /// </summary>
+        /// <param name="hash">The requested torrent hash</param>
+        /// <returns>the torrent object</returns>
+        public async Task<TorrentExtended> GetTorrentExtended(string hash)
+        {
+            List<TorrentExtended> torrents = await ListTorrentsExtended(new Dictionary<string, string>() { { "hash", hash } });
+            return torrents.Count > 0 ? torrents[0] : null;
+        }
+
         /// <summary>
         /// Add a new torrent by magnet information
         /// </summary>
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 fc962cf..8b87e72 100644
--- a/README.md
+++ b/README.md
@@ -46,11 +46,21 @@ List torrents
 List<Torrent> torrents = await client.ListTorrents();
 ```
 
+List torrents extended
+```C#
+List<TorrentExtended> 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
@@ -62,7 +72,6 @@ Add a torrent by .torrent file
 ```C#
 Torrent torrent = await client.AddTorrentByFile(torrentFilename);
 ```
-```
 
 Add a torrent by .torrent url
 ```C#

From 22a457e2a049da0f89f2db16d3f74edde5442aa3 Mon Sep 17 00:00:00 2001
From: Rafael Calafell <48203251+rafacc87@users.noreply.github.com>
Date: Sun, 3 Nov 2024 23:18:40 +0100
Subject: [PATCH 5/5] Reduced timeout to 3 seconds

---
 DelugeRPCClient.Net/Core/CoreDelugeWebClient.cs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/DelugeRPCClient.Net/Core/CoreDelugeWebClient.cs b/DelugeRPCClient.Net/Core/CoreDelugeWebClient.cs
index 13ac59b..8d96f06 100644
--- a/DelugeRPCClient.Net/Core/CoreDelugeWebClient.cs
+++ b/DelugeRPCClient.Net/Core/CoreDelugeWebClient.cs
@@ -61,7 +61,9 @@ private async Task<String> PostJson(String json)
             StringContent content = new StringContent(json);
             content.Headers.ContentType = new MediaTypeWithQualityHeaderValue("application/json");
 
-            var responseMessage = await HttpClient.PostAsync(Url, content);
+            var httpClient = new HttpClient();
+            httpClient.Timeout = TimeSpan.FromMilliseconds(30);
+            var responseMessage = await httpClient.PostAsync(Url, content);
             responseMessage.EnsureSuccessStatusCode();
 
             var responseJson = await responseMessage.Content.ReadAsStringAsync();