about summary refs log tree commit diff
path: root/MatrixRoomUtils.Core
diff options
context:
space:
mode:
Diffstat (limited to 'MatrixRoomUtils.Core')
-rw-r--r--MatrixRoomUtils.Core/AuthenticatedHomeServer.cs83
-rw-r--r--MatrixRoomUtils.Core/Authentication/MatrixAuth.cs32
-rw-r--r--MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs10
-rw-r--r--MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs37
-rw-r--r--MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs12
-rw-r--r--MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs13
-rw-r--r--MatrixRoomUtils.Core/Extensions/StringExtensions.cs4
-rw-r--r--MatrixRoomUtils.Core/Interfaces/IHomeServer.cs86
-rw-r--r--MatrixRoomUtils.Core/MatrixException.cs57
-rw-r--r--MatrixRoomUtils.Core/RatelimitedHttpClient.cs7
-rw-r--r--MatrixRoomUtils.Core/RemoteHomeServer.cs35
-rw-r--r--MatrixRoomUtils.Core/Responses/Admin/AdminRoomListingResult.cs6
-rw-r--r--MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs234
-rw-r--r--MatrixRoomUtils.Core/Responses/LoginResponse.cs19
-rw-r--r--MatrixRoomUtils.Core/Responses/ProfileResponse.cs4
-rw-r--r--MatrixRoomUtils.Core/Responses/StateEventResponse.cs24
-rw-r--r--MatrixRoomUtils.Core/Room.cs112
-rw-r--r--MatrixRoomUtils.Core/RuntimeCache.cs119
-rw-r--r--MatrixRoomUtils.Core/StateEvent.cs46
-rw-r--r--MatrixRoomUtils.Core/StateEventStruct.cs3
-rw-r--r--MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs31
21 files changed, 453 insertions, 521 deletions
diff --git a/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
index 368aa20..ee6be72 100644
--- a/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
+++ b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
@@ -9,63 +9,47 @@ using MatrixRoomUtils.Core.Responses.Admin;
 
 namespace MatrixRoomUtils.Core;
 
-public class AuthenticatedHomeServer : IHomeServer
-{
-    public string UserId { get; set; }
-    public string AccessToken { get; set; }
+public class AuthenticatedHomeServer : IHomeServer {
     public readonly HomeserverAdminApi Admin;
 
-    public AuthenticatedHomeServer(string userId, string accessToken, string canonicalHomeServerDomain)
-    {
+    public AuthenticatedHomeServer(string userId, string accessToken, string canonicalHomeServerDomain) {
         UserId = userId;
         AccessToken = accessToken;
         HomeServerDomain = canonicalHomeServerDomain;
         Admin = new HomeserverAdminApi(this);
-        _httpClient = new HttpClient();
+        _httpClient = new MatrixHttpClient();
     }
 
-    public async Task<AuthenticatedHomeServer> Configure()
-    {
+    public string UserId { get; set; }
+    public string AccessToken { get; set; }
+
+    public async Task<AuthenticatedHomeServer> Configure() {
         FullHomeServerDomain = await ResolveHomeserverFromWellKnown(HomeServerDomain);
         _httpClient.Dispose();
-        _httpClient = new HttpClient { BaseAddress = new Uri(FullHomeServerDomain) };
+        _httpClient = new MatrixHttpClient { BaseAddress = new Uri(FullHomeServerDomain) };
         _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
         Console.WriteLine("[AHS] Finished setting up http client");
 
         return this;
     }
 
-    public async Task<Room> GetRoom(string roomId)
-    {
-        return new Room(_httpClient, roomId);
-    }
+    public async Task<Room> GetRoom(string roomId) => new Room(_httpClient, roomId);
 
-    public async Task<List<Room>> GetJoinedRooms()
-    {
+    public async Task<List<Room>> GetJoinedRooms() {
         var rooms = new List<Room>();
         var roomQuery = await _httpClient.GetAsync("/_matrix/client/v3/joined_rooms");
-        if (!roomQuery.IsSuccessStatusCode)
-        {
-            Console.WriteLine($"Failed to get rooms: {await roomQuery.Content.ReadAsStringAsync()}");
-            throw new InvalidDataException($"Failed to get rooms: {await roomQuery.Content.ReadAsStringAsync()}");
-        }
 
         var roomsJson = await roomQuery.Content.ReadFromJsonAsync<JsonElement>();
-        foreach (var room in roomsJson.GetProperty("joined_rooms").EnumerateArray())
-        {
-            rooms.Add(new Room(_httpClient, room.GetString()));
-        }
+        foreach (var room in roomsJson.GetProperty("joined_rooms").EnumerateArray()) rooms.Add(new Room(_httpClient, room.GetString()));
 
         Console.WriteLine($"Fetched {rooms.Count} rooms");
 
         return rooms;
     }
 
-    public async Task<string> UploadFile(string fileName, Stream fileStream, string contentType = "application/octet-stream")
-    {
+    public async Task<string> UploadFile(string fileName, Stream fileStream, string contentType = "application/octet-stream") {
         var res = await _httpClient.PostAsync($"/_matrix/media/r0/upload?filename={fileName}", new StreamContent(fileStream));
-        if (!res.IsSuccessStatusCode)
-        {
+        if (!res.IsSuccessStatusCode) {
             Console.WriteLine($"Failed to upload file: {await res.Content.ReadAsStringAsync()}");
             throw new InvalidDataException($"Failed to upload file: {await res.Content.ReadAsStringAsync()}");
         }
@@ -74,11 +58,9 @@ public class AuthenticatedHomeServer : IHomeServer
         return resJson.GetProperty("content_uri").GetString()!;
     }
 
-    public async Task<Room> CreateRoom(CreateRoomRequest creationEvent)
-    {
+    public async Task<Room> CreateRoom(CreateRoomRequest creationEvent) {
         var res = await _httpClient.PostAsJsonAsync("/_matrix/client/r0/createRoom", creationEvent);
-        if (!res.IsSuccessStatusCode)
-        {
+        if (!res.IsSuccessStatusCode) {
             Console.WriteLine($"Failed to create room: {await res.Content.ReadAsStringAsync()}");
             throw new InvalidDataException($"Failed to create room: {await res.Content.ReadAsStringAsync()}");
         }
@@ -86,47 +68,34 @@ public class AuthenticatedHomeServer : IHomeServer
         return await GetRoom((await res.Content.ReadFromJsonAsync<JsonObject>())!["room_id"]!.ToString());
     }
 
-    public class HomeserverAdminApi
-    {
+    public class HomeserverAdminApi {
         private readonly AuthenticatedHomeServer _authenticatedHomeServer;
 
-        public HomeserverAdminApi(AuthenticatedHomeServer authenticatedHomeServer)
-        {
-            _authenticatedHomeServer = authenticatedHomeServer;
-        }
+        public HomeserverAdminApi(AuthenticatedHomeServer authenticatedHomeServer) => _authenticatedHomeServer = authenticatedHomeServer;
 
-        public async IAsyncEnumerable<AdminRoomListingResult.AdminRoomListingResultRoom> SearchRoomsAsync(int limit = int.MaxValue, string orderBy = "name", string dir = "f", string? searchTerm = null, string? contentSearch = null)
-        {
+        public async IAsyncEnumerable<AdminRoomListingResult.AdminRoomListingResultRoom> SearchRoomsAsync(int limit = int.MaxValue, string orderBy = "name", string dir = "f", string? searchTerm = null, string? contentSearch = null) {
             AdminRoomListingResult? res = null;
-            int i = 0;
+            var i = 0;
             int? totalRooms = null;
-            do
-            {
+            do {
                 var url = $"/_synapse/admin/v1/rooms?limit={Math.Min(limit, 100)}&dir={dir}&order_by={orderBy}";
-                if (!string.IsNullOrEmpty(searchTerm))
-                {
-                    url += $"&search_term={searchTerm}";
-                }
+                if (!string.IsNullOrEmpty(searchTerm)) url += $"&search_term={searchTerm}";
+
+                if (res?.NextBatch != null) url += $"&from={res.NextBatch}";
 
-                if (res?.NextBatch != null)
-                {
-                    url += $"&from={res.NextBatch}";
-                }
                 Console.WriteLine($"--- ADMIN Querying Room List with URL: {url} - Already have {i} items... ---");
 
                 res = await _authenticatedHomeServer._httpClient.GetFromJsonAsync<AdminRoomListingResult>(url);
                 totalRooms ??= res?.TotalRooms;
-                Console.WriteLine(res.ToJson(indent:false));
-                foreach (var room in res.Rooms)
-                {
+                Console.WriteLine(res.ToJson(false));
+                foreach (var room in res.Rooms) {
                     if (contentSearch != null && !string.IsNullOrEmpty(contentSearch) &&
                         !(
                             room.Name?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true ||
                             room.CanonicalAlias?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true ||
                             room.Creator?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true
                         )
-                       )
-                    {
+                       ) {
                         totalRooms--;
                         continue;
                     }
diff --git a/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs b/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs
index b4b8d19..0f9eb58 100644
--- a/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs
+++ b/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs
@@ -5,35 +5,31 @@ using MatrixRoomUtils.Core.Responses;
 
 namespace MatrixRoomUtils.Core.Authentication;
 
-public class MatrixAuth
-{
-    public static async Task<LoginResponse> Login(string homeserver, string username, string password)
-    {
+public class MatrixAuth {
+    public static async Task<LoginResponse> Login(string homeserver, string username, string password) {
         Console.WriteLine($"Logging in to {homeserver} as {username}...");
         homeserver = (await new RemoteHomeServer(homeserver).Configure()).FullHomeServerDomain;
         var hc = new HttpClient();
-        var payload = new
-        {
+        var payload = new {
             type = "m.login.password",
-            identifier = new
-            {
+            identifier = new {
                 type = "m.id.user",
                 user = username
             },
-            password = password,
+            password,
             initial_device_display_name = "Rory&::MatrixRoomUtils"
         };
         Console.WriteLine($"Sending login request to {homeserver}...");
         var resp = await hc.PostAsJsonAsync($"{homeserver}/_matrix/client/r0/login", payload);
         Console.WriteLine($"Login: {resp.StatusCode}");
         var data = await resp.Content.ReadFromJsonAsync<JsonElement>();
-        if (!resp.IsSuccessStatusCode) Console.WriteLine("Login: " + data.ToString());
-        if (data.TryGetProperty("retry_after_ms", out var retryAfter))
-        {
+        if (!resp.IsSuccessStatusCode) Console.WriteLine("Login: " + data);
+        if (data.TryGetProperty("retry_after_ms", out var retryAfter)) {
             Console.WriteLine($"Login: Waiting {retryAfter.GetInt32()}ms before retrying");
             await Task.Delay(retryAfter.GetInt32());
             return await Login(homeserver, username, password);
         }
+
         Console.WriteLine($"Login: {data.ToJson()}");
         return data.Deserialize<LoginResponse>();
         //var token = data.GetProperty("access_token").GetString();
@@ -41,20 +37,16 @@ public class MatrixAuth
     }
 
     [Obsolete("Migrate to IHomeServer instance")]
-    public static async Task<ProfileResponse> GetProfile(string homeserver, string mxid) => 
-        await (await new RemoteHomeServer(homeserver).Configure()).GetProfile(mxid);
+    public static async Task<ProfileResponse> GetProfile(string homeserver, string mxid) => await (await new RemoteHomeServer(homeserver).Configure()).GetProfile(mxid);
 
-    private static async Task<bool> CheckSuccessStatus(string url)
-    {
+    private static async Task<bool> CheckSuccessStatus(string url) {
         //cors causes failure, try to catch
-        try
-        {
+        try {
             using var hc = new HttpClient();
             var resp = await hc.GetAsync(url);
             return resp.IsSuccessStatusCode;
         }
-        catch (Exception e)
-        {
+        catch (Exception e) {
             Console.WriteLine($"Failed to check success status: {e.Message}");
             return false;
         }
diff --git a/MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs b/MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs
index cce71dd..c51baec 100644
--- a/MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs
@@ -1,15 +1,13 @@
 namespace MatrixRoomUtils.Core.Extensions;
 
-public static class DictionaryExtensions
-{
-    public static bool ChangeKey<TKey, TValue>(this IDictionary<TKey, TValue> dict, 
-        TKey oldKey, TKey newKey)
-    {
+public static class DictionaryExtensions {
+    public static bool ChangeKey<TKey, TValue>(this IDictionary<TKey, TValue> dict,
+        TKey oldKey, TKey newKey) {
         TValue value;
         if (!dict.Remove(oldKey, out value))
             return false;
 
-        dict[newKey] = value;  // or dict.Add(newKey, value) depending on ur comfort
+        dict[newKey] = value; // or dict.Add(newKey, value) depending on ur comfort
         return true;
     }
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
index 8eb0226..47b3121 100644
--- a/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
@@ -1,19 +1,40 @@
+using System.Text.Json;
+
 namespace MatrixRoomUtils.Core.Extensions;
 
-public static class HttpClientExtensions
-{
-    public static async Task<bool> CheckSuccessStatus(this HttpClient hc, string url)
-    {
+public static class HttpClientExtensions {
+    public static async Task<bool> CheckSuccessStatus(this HttpClient hc, string url) {
         //cors causes failure, try to catch
-        try
-        {
+        try {
             var resp = await hc.GetAsync(url);
             return resp.IsSuccessStatusCode;
         }
-        catch (Exception e)
-        {
+        catch (Exception e) {
             Console.WriteLine($"Failed to check success status: {e.Message}");
             return false;
         }
     }
+}
+
+public class MatrixHttpClient : HttpClient {
+    public override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
+        var a = await base.SendAsync(request, cancellationToken);
+        if (!a.IsSuccessStatusCode) {
+            Console.WriteLine($"Failed to send request: {a.StatusCode}");
+            var content = await a.Content.ReadAsStringAsync(cancellationToken);
+            if (content.StartsWith('{')) {
+                var ex = JsonSerializer.Deserialize<MatrixException>(content);
+                if (ex?.RetryAfterMs != null) {
+                    await Task.Delay(ex.RetryAfterMs.Value, cancellationToken);
+                    return await SendAsync(request, cancellationToken);
+                }
+
+                throw ex!;
+            }
+
+            throw new InvalidDataException("Encountered invalid data:\n" + content);
+        }
+
+        return a;
+    }
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs b/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs
index 725c832..b007136 100644
--- a/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs
@@ -5,18 +5,16 @@ using System.Text.Json.Serialization;
 
 namespace MatrixRoomUtils.Core.Extensions;
 
-public static class JsonElementExtensions
-{
-    public static void FindExtraJsonFields([DisallowNull] this JsonElement? res, Type t)
-    {
+public static class JsonElementExtensions {
+    public static void FindExtraJsonFields([DisallowNull] this JsonElement? res, Type t) {
         var props = t.GetProperties();
         var unknownPropertyFound = false;
-        foreach (var field in res.Value.EnumerateObject())
-        {
+        foreach (var field in res.Value.EnumerateObject()) {
             if (props.Any(x => x.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name == field.Name)) continue;
             Console.WriteLine($"[!!] Unknown property {field.Name} in {t.Name}!");
             unknownPropertyFound = true;
         }
-        if(unknownPropertyFound) Console.WriteLine(res.Value.ToJson());
+
+        if (unknownPropertyFound) Console.WriteLine(res.Value.ToJson());
     }
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs b/MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs
index 5aa9645..812c81c 100644
--- a/MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs
@@ -1,15 +1,14 @@
+using System.Text.Encodings.Web;
 using System.Text.Json;
 
 namespace MatrixRoomUtils.Core.Extensions;
 
-public static class ObjectExtensions
-{
-    public static string ToJson(this object obj, bool indent = true, bool ignoreNull = false, bool unsafeContent = false)
-    {
+public static class ObjectExtensions {
+    public static string ToJson(this object obj, bool indent = true, bool ignoreNull = false, bool unsafeContent = false) {
         var jso = new JsonSerializerOptions();
-        if(indent) jso.WriteIndented = true;
-        if(ignoreNull) jso.IgnoreNullValues = true;
-        if(unsafeContent) jso.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
+        if (indent) jso.WriteIndented = true;
+        if (ignoreNull) jso.IgnoreNullValues = true;
+        if (unsafeContent) jso.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
         return JsonSerializer.Serialize(obj, jso);
     }
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Extensions/StringExtensions.cs b/MatrixRoomUtils.Core/Extensions/StringExtensions.cs
index 8fadc6d..b81d59f 100644
--- a/MatrixRoomUtils.Core/Extensions/StringExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/StringExtensions.cs
@@ -1,7 +1,6 @@
 namespace MatrixRoomUtils.Core.Extensions;
 
-public static class StringExtensions
-{
+public static class StringExtensions {
     // public static async Task<string> GetMediaUrl(this string MxcUrl)
     // {
     //     //MxcUrl: mxc://rory.gay/ocRVanZoUTCcifcVNwXgbtTg
@@ -11,5 +10,4 @@ public static class StringExtensions
     //     var mediaId = MxcUrl.Split('/')[3];
     //     return $"{(await new RemoteHomeServer(server).Configure()).FullHomeServerDomain}/_matrix/media/v3/download/{server}/{mediaId}";
     // }
-    
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
index 3ae1355..9a9ba7a 100644
--- a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
+++ b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
@@ -5,35 +5,32 @@ using MatrixRoomUtils.Core.Responses;
 
 namespace MatrixRoomUtils.Core.Interfaces;
 
-public class IHomeServer
-{
-    private Dictionary<string, ProfileResponse?> _profileCache = new();
+public class IHomeServer {
+    private readonly Dictionary<string, ProfileResponse?> _profileCache = new();
     public string HomeServerDomain { get; set; }
     public string FullHomeServerDomain { get; set; }
 
-    private protected HttpClient _httpClient { get; set; } = new();
+    private protected MatrixHttpClient _httpClient { get; set; } = new();
 
-    public async Task<string> ResolveHomeserverFromWellKnown(string homeserver)
-    {
+    public async Task<string> ResolveHomeserverFromWellKnown(string homeserver) {
         var res = await _resolveHomeserverFromWellKnown(homeserver);
-        if(!res.StartsWith("http")) res = "https://" + res;
-        if(res.EndsWith(":443")) res = res.Substring(0, res.Length - 4);
+        if (!res.StartsWith("http")) res = "https://" + res;
+        if (res.EndsWith(":443")) res = res.Substring(0, res.Length - 4);
         return res;
     }
-    private async Task<string> _resolveHomeserverFromWellKnown(string homeserver)
-    {
-        if (RuntimeCache.HomeserverResolutionCache.Count == 0)
-        {
+
+    private async Task<string> _resolveHomeserverFromWellKnown(string homeserver) {
+        if (RuntimeCache.HomeserverResolutionCache.Count == 0) {
             Console.WriteLine("No cached homeservers, resolving...");
             await Task.Delay(Random.Shared.Next(1000, 5000));
-        } 
-        if (RuntimeCache.HomeserverResolutionCache.ContainsKey(homeserver))
-        {
-            if (RuntimeCache.HomeserverResolutionCache[homeserver].ResolutionTime < DateTime.Now.AddHours(1))
-            {
+        }
+
+        if (RuntimeCache.HomeserverResolutionCache.ContainsKey(homeserver)) {
+            if (RuntimeCache.HomeserverResolutionCache[homeserver].ResolutionTime < DateTime.Now.AddHours(1)) {
                 Console.WriteLine($"Found cached homeserver: {RuntimeCache.HomeserverResolutionCache[homeserver].Result}");
                 return RuntimeCache.HomeserverResolutionCache[homeserver].Result;
             }
+
             Console.WriteLine($"Cached homeserver expired, removing: {RuntimeCache.HomeserverResolutionCache[homeserver].Result}");
             RuntimeCache.HomeserverResolutionCache.Remove(homeserver);
         }
@@ -42,29 +39,25 @@ public class IHomeServer
         string result = null;
         Console.WriteLine($"Resolving homeserver: {homeserver}");
         if (!homeserver.StartsWith("http")) homeserver = "https://" + homeserver;
-        if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/client"))
-        {
-            Console.WriteLine($"Got successful response for client well-known...");
+        if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/client")) {
+            Console.WriteLine("Got successful response for client well-known...");
             var resp = await _httpClient.GetFromJsonAsync<JsonElement>($"{homeserver}/.well-known/matrix/client");
             Console.WriteLine($"Response: {resp.ToString()}");
             var hs = resp.GetProperty("m.homeserver").GetProperty("base_url").GetString();
             result = hs;
         }
-        else
-        {
-            Console.WriteLine($"No client well-known...");
-            if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/server"))
-            {
+        else {
+            Console.WriteLine("No client well-known...");
+            if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/server")) {
                 var resp = await _httpClient.GetFromJsonAsync<JsonElement>($"{homeserver}/.well-known/matrix/server");
                 var hs = resp.GetProperty("m.server").GetString();
                 result = hs;
             }
-            else
-            {
-                Console.WriteLine($"No server well-known...");
-                if (await _httpClient.CheckSuccessStatus($"{homeserver}/_matrix/client/versions")) result = homeserver;
-                else
-                {
+            else {
+                Console.WriteLine("No server well-known...");
+                if (await _httpClient.CheckSuccessStatus($"{homeserver}/_matrix/client/versions"))
+                    result = homeserver;
+                else {
                     Console.WriteLine("No homeserver on shortname...");
                     if (await _httpClient.CheckSuccessStatus($"{homeserver.Replace("//", "//matrix.")}/_matrix/client/versions")) result = homeserver.Replace("//", "//matrix.");
                     else Console.WriteLine($"Failed to resolve homeserver, not on {homeserver}, nor do client or server well-knowns exist!");
@@ -72,30 +65,27 @@ public class IHomeServer
             }
         }
 
-        if (result != null)
-        {
+        if (result != null) {
             Console.WriteLine($"Resolved homeserver: {homeserver} -> {result}");
-            RuntimeCache.HomeserverResolutionCache.TryAdd(homeserver, new()
-            {
+            RuntimeCache.HomeserverResolutionCache.TryAdd(homeserver, new HomeServerResolutionResult {
                 Result = result,
                 ResolutionTime = DateTime.Now
             });
             return result;
         }
+
         throw new InvalidDataException($"Failed to resolve homeserver, not on {homeserver}, nor do client or server well-knowns exist!");
     }
-    public async Task<ProfileResponse> GetProfile(string mxid, bool debounce = false, bool cache = true)
-    {
-        if (cache)
-        {
-            if(debounce) await Task.Delay(Random.Shared.Next(100, 500));
-            if (_profileCache.ContainsKey(mxid))
-            {
-                while (_profileCache[mxid] == null)
-                {
+
+    public async Task<ProfileResponse> GetProfile(string mxid, bool debounce = false, bool cache = true) {
+        if (cache) {
+            if (debounce) await Task.Delay(Random.Shared.Next(100, 500));
+            if (_profileCache.ContainsKey(mxid)) {
+                while (_profileCache[mxid] == null) {
                     Console.WriteLine($"Waiting for profile cache for {mxid}, currently {_profileCache[mxid]?.ToJson() ?? "null"} within {_profileCache.Count} profiles...");
                     await Task.Delay(Random.Shared.Next(50, 500));
                 }
+
                 return _profileCache[mxid];
             }
         }
@@ -103,13 +93,11 @@ public class IHomeServer
         _profileCache.Add(mxid, null);
         var resp = await _httpClient.GetAsync($"/_matrix/client/r0/profile/{mxid}");
         var data = await resp.Content.ReadFromJsonAsync<JsonElement>();
-        if(!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data.ToString());
+        if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data);
         var profile = data.Deserialize<ProfileResponse>();
         _profileCache[mxid] = profile;
         return profile;
     }
-    public string? ResolveMediaUri(string mxc)
-    {
-        return mxc.Replace("mxc://", $"{FullHomeServerDomain}/_matrix/media/r0/download/");
-    }
+
+    public string? ResolveMediaUri(string mxc) => mxc.Replace("mxc://", $"{FullHomeServerDomain}/_matrix/media/r0/download/");
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/MatrixException.cs b/MatrixRoomUtils.Core/MatrixException.cs
new file mode 100644
index 0000000..3df70e1
--- /dev/null
+++ b/MatrixRoomUtils.Core/MatrixException.cs
@@ -0,0 +1,57 @@
+using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Extensions;
+
+namespace MatrixRoomUtils.Core;
+
+public class MatrixException : Exception {
+    [JsonPropertyName("errcode")]
+    public string ErrorCode { get; set; }
+
+    [JsonPropertyName("error")]
+    public string Error { get; set; }
+
+    [JsonPropertyName("soft_logout")]
+    public bool? SoftLogout { get; set; }
+
+    [JsonPropertyName("retry_after_ms")]
+    public int? RetryAfterMs { get; set; }
+
+    public override string Message =>
+        ErrorCode switch {
+            // common
+            "M_FORBIDDEN" => "You do not have permission to perform this action: " + Error,
+            "M_UNKNOWN_TOKEN" => "The access token specified was not recognised: " + Error + (SoftLogout == true ? " (soft logout)" : ""),
+            "M_MISSING_TOKEN" => "No access token was specified: " + Error,
+            "M_BAD_JSON" => "Request contained valid JSON, but it was malformed in some way: " + Error,
+            "M_NOT_JSON" => "Request did not contain valid JSON: " + Error,
+            "M_NOT_FOUND" => "The requested resource was not found: " + Error,
+            "M_LIMIT_EXCEEDED" => "Too many requests have been sent in a short period of time. Wait a while then try again: " + Error,
+            "M_UNRECOGNISED" => "The server did not recognise the request: " + Error,
+            "M_UNKOWN" => "The server encountered an unexpected error: " + Error,
+            // endpoint specific
+            "M_UNAUTHORIZED" => "The request did not contain valid authentication information for the target of the request: " + Error,
+            "M_USER_DEACTIVATED" => "The user ID associated with the request has been deactivated: " + Error,
+            "M_USER_IN_USE" => "The user ID associated with the request is already in use: " + Error,
+            "M_INVALID_USERNAME" => "The requested user ID is not valid: " + Error,
+            "M_ROOM_IN_USE" => "The room alias requested is already taken: " + Error,
+            "M_INVALID_ROOM_STATE" => "The room associated with the request is not in a valid state to perform the request: " + Error,
+            "M_THREEPID_IN_USE" => "The threepid requested is already associated with a user ID on this server: " + Error,
+            "M_THREEPID_NOT_FOUND" => "The threepid requested is not associated with any user ID: " + Error,
+            "M_THREEPID_AUTH_FAILED" => "The provided threepid and/or token was invalid: " + Error,
+            "M_THREEPID_DENIED" => "The homeserver does not permit the third party identifier in question: " + Error,
+            "M_SERVER_NOT_TRUSTED" => "The homeserver does not trust the identity server: " + Error,
+            "M_UNSUPPORTED_ROOM_VERSION" => "The room version is not supported: " + Error,
+            "M_INCOMPATIBLE_ROOM_VERSION" => "The room version is incompatible: " + Error,
+            "M_BAD_STATE" => "The request was invalid because the state was invalid: " + Error,
+            "M_GUEST_ACCESS_FORBIDDEN" => "Guest access is forbidden: " + Error,
+            "M_CAPTCHA_NEEDED" => "Captcha needed: " + Error,
+            "M_CAPTCHA_INVALID" => "Captcha invalid: " + Error,
+            "M_MISSING_PARAM" => "Missing parameter: " + Error,
+            "M_INVALID_PARAM" => "Invalid parameter: " + Error,
+            "M_TOO_LARGE" => "The request or entity was too large: " + Error,
+            "M_EXCLUSIVE" => "The resource being requested is reserved by an application service, or the application service making the request has not created the resource: " + Error,
+            "M_RESOURCE_LIMIT_EXCEEDED" => "Exceeded resource limit: " + Error,
+            "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM" => "Cannot leave server notice room: " + Error,
+            _ => "Unknown error: " + new { ErrorCode, Error, SoftLogout, RetryAfterMs }.ToJson()
+        };
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/RatelimitedHttpClient.cs b/MatrixRoomUtils.Core/RatelimitedHttpClient.cs
deleted file mode 100644
index 61eab07..0000000
--- a/MatrixRoomUtils.Core/RatelimitedHttpClient.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace MatrixRoomUtils.Core;
-
-public class RatelimitedHttpClient : HttpClient
-{
-    
-    
-}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/RemoteHomeServer.cs b/MatrixRoomUtils.Core/RemoteHomeServer.cs
index 942f873..3f50d2e 100644
--- a/MatrixRoomUtils.Core/RemoteHomeServer.cs
+++ b/MatrixRoomUtils.Core/RemoteHomeServer.cs
@@ -1,50 +1,39 @@
 using System.Net.Http.Json;
 using System.Text.Json;
+using MatrixRoomUtils.Core.Extensions;
 using MatrixRoomUtils.Core.Interfaces;
 
 namespace MatrixRoomUtils.Core;
 
-public class RemoteHomeServer : IHomeServer
-{
-
-
-    public RemoteHomeServer(string canonicalHomeServerDomain)
-    {
+public class RemoteHomeServer : IHomeServer {
+    public RemoteHomeServer(string canonicalHomeServerDomain) {
         HomeServerDomain = canonicalHomeServerDomain;
-        _httpClient = new HttpClient();
+        _httpClient = new MatrixHttpClient();
         _httpClient.Timeout = TimeSpan.FromSeconds(5);
     }
-    public async Task<RemoteHomeServer> Configure()
-    {
+
+    public async Task<RemoteHomeServer> Configure() {
         FullHomeServerDomain = await ResolveHomeserverFromWellKnown(HomeServerDomain);
         _httpClient.Dispose();
-        _httpClient = new HttpClient { BaseAddress = new Uri(FullHomeServerDomain) };
+        _httpClient = new MatrixHttpClient { BaseAddress = new Uri(FullHomeServerDomain) };
         _httpClient.Timeout = TimeSpan.FromSeconds(5);
         Console.WriteLine("[RHS] Finished setting up http client");
 
         return this;
     }
-    
-    public async Task<Room> GetRoom(string roomId)
-    {
-        return new Room(_httpClient, roomId);
-    }
 
-    public async Task<List<Room>> GetJoinedRooms()
-    {
+    public async Task<Room> GetRoom(string roomId) => new Room(_httpClient, roomId);
+
+    public async Task<List<Room>> GetJoinedRooms() {
         var rooms = new List<Room>();
         var roomQuery = await _httpClient.GetAsync("/_matrix/client/v3/joined_rooms");
-        if (!roomQuery.IsSuccessStatusCode)
-        {
+        if (!roomQuery.IsSuccessStatusCode) {
             Console.WriteLine($"Failed to get rooms: {await roomQuery.Content.ReadAsStringAsync()}");
             throw new InvalidDataException($"Failed to get rooms: {await roomQuery.Content.ReadAsStringAsync()}");
         }
 
         var roomsJson = await roomQuery.Content.ReadFromJsonAsync<JsonElement>();
-        foreach (var room in roomsJson.GetProperty("joined_rooms").EnumerateArray())
-        {
-            rooms.Add(new Room(_httpClient, room.GetString()));
-        }
+        foreach (var room in roomsJson.GetProperty("joined_rooms").EnumerateArray()) rooms.Add(new Room(_httpClient, room.GetString()));
 
         return rooms;
     }
diff --git a/MatrixRoomUtils.Core/Responses/Admin/AdminRoomListingResult.cs b/MatrixRoomUtils.Core/Responses/Admin/AdminRoomListingResult.cs
index 8ec0e4f..37bb3ba 100644
--- a/MatrixRoomUtils.Core/Responses/Admin/AdminRoomListingResult.cs
+++ b/MatrixRoomUtils.Core/Responses/Admin/AdminRoomListingResult.cs
@@ -2,8 +2,7 @@ using System.Text.Json.Serialization;
 
 namespace MatrixRoomUtils.Core.Responses.Admin;
 
-public class AdminRoomListingResult
-{
+public class AdminRoomListingResult {
     [JsonPropertyName("offset")]
     public int Offset { get; set; }
 
@@ -19,8 +18,7 @@ public class AdminRoomListingResult
     [JsonPropertyName("rooms")]
     public List<AdminRoomListingResultRoom> Rooms { get; set; } = new();
 
-    public class AdminRoomListingResultRoom
-    {
+    public class AdminRoomListingResultRoom {
         [JsonPropertyName("room_id")]
         public string RoomId { get; set; }
 
diff --git a/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs b/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
index 5df99f7..da7d569 100644
--- a/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
+++ b/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
@@ -6,58 +6,56 @@ using MatrixRoomUtils.Core.Extensions;
 
 namespace MatrixRoomUtils.Core.Responses;
 
-public class CreateRoomRequest
-{
-    [JsonPropertyName("name")] public string Name { get; set; } = null!;
+public class CreateRoomRequest {
+    [JsonIgnore] public CreationContentBaseType _creationContentBaseType;
+
+    public CreateRoomRequest() => _creationContentBaseType = new CreationContentBaseType(this);
+
+    [JsonPropertyName("name")]
+    public string Name { get; set; } = null!;
 
-    [JsonPropertyName("room_alias_name")] public string RoomAliasName { get; set; } = null!;
+    [JsonPropertyName("room_alias_name")]
+    public string RoomAliasName { get; set; } = null!;
 
     //we dont want to use this, we want more control
     // [JsonPropertyName("preset")]
     // public string Preset { get; set; } = null!;
-    [JsonPropertyName("initial_state")] public List<StateEvent> InitialState { get; set; } = null!;
-    [JsonPropertyName("visibility")] public string Visibility { get; set; } = null!;
+    [JsonPropertyName("initial_state")]
+    public List<StateEvent> InitialState { get; set; } = null!;
+
+    [JsonPropertyName("visibility")]
+    public string Visibility { get; set; } = null!;
 
     [JsonPropertyName("power_level_content_override")]
     public PowerLevelEvent PowerLevelContentOverride { get; set; } = null!;
 
-    [JsonPropertyName("creation_content")] public JsonObject CreationContent { get; set; } = new();
+    [JsonPropertyName("creation_content")]
+    public JsonObject CreationContent { get; set; } = new();
 
     /// <summary>
-    /// For use only when you can't use the CreationContent property
+    ///     For use only when you can't use the CreationContent property
     /// </summary>
 
-    public StateEvent this[string event_type, string event_key = ""]
-    {
+    public StateEvent this[string event_type, string event_key = ""] {
         get => InitialState.First(x => x.Type == event_type && x.StateKey == event_key);
-        set
-        {
+        set {
             var stateEvent = InitialState.FirstOrDefault(x => x.Type == event_type && x.StateKey == event_key);
             if (stateEvent == null)
-            {
                 InitialState.Add(value);
-            }
             else
-            {
                 InitialState[InitialState.IndexOf(stateEvent)] = value;
-            }
         }
     }
 
     //extra properties
     [JsonIgnore]
-    public string HistoryVisibility
-    {
-        get
-        {
+    public string HistoryVisibility {
+        get {
             var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.history_visibility");
-            if (stateEvent == null)
-            {
-                InitialState.Add(new StateEvent()
-                {
+            if (stateEvent == null) {
+                InitialState.Add(new StateEvent {
                     Type = "m.room.history_visibility",
-                    Content = new JsonObject()
-                    {
+                    Content = new JsonObject {
                         ["history_visibility"] = "shared"
                     }
                 });
@@ -66,22 +64,16 @@ public class CreateRoomRequest
 
             return stateEvent.ContentAsJsonNode["history_visibility"].GetValue<string>();
         }
-        set
-        {
+        set {
             var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.history_visibility");
             if (stateEvent == null)
-            {
-                InitialState.Add(new StateEvent()
-                {
+                InitialState.Add(new StateEvent {
                     Type = "m.room.history_visibility",
-                    Content = new JsonObject()
-                    {
+                    Content = new JsonObject {
                         ["history_visibility"] = value
                     }
                 });
-            }
-            else
-            {
+            else {
                 var v = stateEvent.ContentAsJsonNode;
                 v["history_visibility"] = value;
                 stateEvent.ContentAsJsonNode = v;
@@ -90,18 +82,13 @@ public class CreateRoomRequest
     }
 
     [JsonIgnore]
-    public string RoomIcon
-    {
-        get
-        {
+    public string RoomIcon {
+        get {
             var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.avatar");
-            if (stateEvent == null)
-            {
-                InitialState.Add(new StateEvent()
-                {
+            if (stateEvent == null) {
+                InitialState.Add(new StateEvent {
                     Type = "m.room.avatar",
-                    Content = new JsonObject()
-                    {
+                    Content = new JsonObject {
                         ["url"] = ""
                     }
                 });
@@ -110,22 +97,16 @@ public class CreateRoomRequest
 
             return stateEvent.ContentAsJsonNode["url"].GetValue<string>();
         }
-        set
-        {
+        set {
             var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.avatar");
             if (stateEvent == null)
-            {
-                InitialState.Add(new StateEvent()
-                {
+                InitialState.Add(new StateEvent {
                     Type = "m.room.avatar",
-                    Content = new JsonObject()
-                    {
+                    Content = new JsonObject {
                         ["url"] = value
                     }
                 });
-            }
-            else
-            {
+            else {
                 var v = stateEvent.ContentAsJsonNode;
                 v["url"] = value;
                 stateEvent.ContentAsJsonNode = v;
@@ -177,64 +158,50 @@ public class CreateRoomRequest
     //     }
     // }
 
-    public ServerACL ServerACLs
-    {
-        get
-        {
+    public ServerACL ServerACLs {
+        get {
             var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.server_acl");
-            if (stateEvent == null)
-            {
-                InitialState.Add(new StateEvent()
-                {
+            if (stateEvent == null) {
+                InitialState.Add(new StateEvent {
                     Type = "m.room.server_acl",
-                    Content = new JsonObject()
-                    {
-                        ["allow"] = new JsonArray()
-                        {
+                    Content = new JsonObject {
+                        ["allow"] = new JsonArray {
                             "*"
                         },
                         ["deny"] = new JsonArray()
                     }
                 });
-                return new ServerACL()
-                {
-                    Allow = new List<string>()
-                    {
+                return new ServerACL {
+                    Allow = new List<string> {
                         "*"
                     },
                     Deny = new List<string>(),
                     AllowIpLiterals = true
                 };
             }
-            return new ServerACL()
-            {
-                Allow = JsonSerializer.Deserialize<List<string>>(stateEvent.ContentAsJsonNode["allow"]),
-                Deny = JsonSerializer.Deserialize<List<string>>(stateEvent.ContentAsJsonNode["deny"]),
+
+            return new ServerACL {
+                Allow = stateEvent.ContentAsJsonNode["allow"].Deserialize<List<string>>(),
+                Deny = stateEvent.ContentAsJsonNode["deny"].Deserialize<List<string>>(),
                 AllowIpLiterals = true
             };
         }
-        set
-        {
+        set {
             Console.WriteLine($"Setting server acl to {value.ToJson()}");
             var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.server_acl");
             if (stateEvent == null)
-            {
-                InitialState.Add(new StateEvent()
-                {
+                InitialState.Add(new StateEvent {
                     Type = "m.room.server_acl",
-                    Content = new JsonObject()
-                    {
-                        ["allow"] = JsonArray.Parse(JsonSerializer.Serialize(value.Allow)),
-                        ["deny"] = JsonArray.Parse(JsonSerializer.Serialize(value.Deny))
-                        ["allow_ip_literals"] = value.AllowIpLiterals
+                    Content = new JsonObject {
+                        ["allow"] = JsonNode.Parse(JsonSerializer.Serialize(value.Allow)),
+                        ["deny"] = JsonNode.Parse(JsonSerializer.Serialize(value.Deny))
+                            ["allow_ip_literals"] = value.AllowIpLiterals
                     }
                 });
-            }
-            else
-            {
+            else {
                 var v = stateEvent.ContentAsJsonNode;
-                v["allow"] = JsonArray.Parse(JsonSerializer.Serialize(value.Allow));
-                v["deny"] = JsonArray.Parse(JsonSerializer.Serialize(value.Deny));
+                v["allow"] = JsonNode.Parse(JsonSerializer.Serialize(value.Allow));
+                v["deny"] = JsonNode.Parse(JsonSerializer.Serialize(value.Deny));
                 v["allow_ip_literals"] = value.AllowIpLiterals;
                 stateEvent.ContentAsJsonNode = v;
                 Console.WriteLine($"v={v.ToJson()}");
@@ -243,14 +210,7 @@ public class CreateRoomRequest
         }
     }
 
-
-    [JsonIgnore] public CreationContentBaseType _creationContentBaseType;
-
-    public CreateRoomRequest() => _creationContentBaseType = new(this);
-
-
-    public Dictionary<string, string> Validate()
-    {
+    public Dictionary<string, string> Validate() {
         Dictionary<string, string> errors = new();
         if (!Regex.IsMatch(RoomAliasName, @"[a-zA-Z0-9_\-]+$"))
             errors.Add("room_alias_name", "Room alias name must only contain letters, numbers, underscores, and hyphens.");
@@ -259,49 +219,65 @@ public class CreateRoomRequest
     }
 }
 
-public class CreationContentBaseType
-{
+public class CreationContentBaseType {
     private readonly CreateRoomRequest createRoomRequest;
 
-    public CreationContentBaseType(CreateRoomRequest createRoomRequest)
-    {
-        this.createRoomRequest = createRoomRequest;
-    }
+    public CreationContentBaseType(CreateRoomRequest createRoomRequest) => this.createRoomRequest = createRoomRequest;
 
     [JsonPropertyName("type")]
-    public string Type
-    {
+    public string Type {
         get => (string)createRoomRequest.CreationContent["type"];
-        set
-        {
+        set {
             if (value is "null" or "") createRoomRequest.CreationContent.Remove("type");
             else createRoomRequest.CreationContent["type"] = value;
         }
     }
 }
 
-public class PowerLevelEvent
-{
-    [JsonPropertyName("ban")] public int Ban { get; set; } // = 50;
-    [JsonPropertyName("events_default")] public int EventsDefault { get; set; } // = 0;
-    [JsonPropertyName("events")] public Dictionary<string, int> Events { get; set; } // = null!;
-    [JsonPropertyName("invite")] public int Invite { get; set; } // = 50;
-    [JsonPropertyName("kick")] public int Kick { get; set; } // = 50;
-    [JsonPropertyName("notifications")] public NotificationsPL NotificationsPl { get; set; } // = null!;
-    [JsonPropertyName("redact")] public int Redact { get; set; } // = 50;
-    [JsonPropertyName("state_default")] public int StateDefault { get; set; } // = 50;
-    [JsonPropertyName("users")] public Dictionary<string, int> Users { get; set; } // = null!;
-    [JsonPropertyName("users_default")] public int UsersDefault { get; set; } // = 0;
+public class PowerLevelEvent {
+    [JsonPropertyName("ban")]
+    public int Ban { get; set; } // = 50;
+
+    [JsonPropertyName("events_default")]
+    public int EventsDefault { get; set; } // = 0;
+
+    [JsonPropertyName("events")]
+    public Dictionary<string, int> Events { get; set; } // = null!;
+
+    [JsonPropertyName("invite")]
+    public int Invite { get; set; } // = 50;
+
+    [JsonPropertyName("kick")]
+    public int Kick { get; set; } // = 50;
+
+    [JsonPropertyName("notifications")]
+    public NotificationsPL NotificationsPl { get; set; } // = null!;
+
+    [JsonPropertyName("redact")]
+    public int Redact { get; set; } // = 50;
+
+    [JsonPropertyName("state_default")]
+    public int StateDefault { get; set; } // = 50;
+
+    [JsonPropertyName("users")]
+    public Dictionary<string, int> Users { get; set; } // = null!;
+
+    [JsonPropertyName("users_default")]
+    public int UsersDefault { get; set; } // = 0;
 }
 
-public class NotificationsPL
-{
-    [JsonPropertyName("room")] public int Room { get; set; } = 50;
+public class NotificationsPL {
+    [JsonPropertyName("room")]
+    public int Room { get; set; } = 50;
 }
 
-public class ServerACL
-{
-    [JsonPropertyName("allow")] public List<string> Allow { get; set; } // = null!;
-    [JsonPropertyName("deny")] public List<string> Deny { get; set; } // = null!;
-    [JsonPropertyName("allow_ip_literals")] public bool AllowIpLiterals { get; set; } // = false;
+public class ServerACL {
+    [JsonPropertyName("allow")]
+    public List<string> Allow { get; set; } // = null!;
+
+    [JsonPropertyName("deny")]
+    public List<string> Deny { get; set; } // = null!;
+
+    [JsonPropertyName("allow_ip_literals")]
+    public bool AllowIpLiterals { get; set; } // = false;
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Responses/LoginResponse.cs b/MatrixRoomUtils.Core/Responses/LoginResponse.cs
index 34b42d1..3259e44 100644
--- a/MatrixRoomUtils.Core/Responses/LoginResponse.cs
+++ b/MatrixRoomUtils.Core/Responses/LoginResponse.cs
@@ -4,27 +4,26 @@ using System.Text.Json.Serialization;
 
 namespace MatrixRoomUtils.Core.Responses;
 
-public class LoginResponse
-{
+public class LoginResponse {
     [JsonPropertyName("access_token")]
     public string AccessToken { get; set; }
+
     [JsonPropertyName("device_id")]
     public string DeviceId { get; set; }
+
     [JsonPropertyName("home_server")]
     public string HomeServer { get; set; }
+
     [JsonPropertyName("user_id")]
     public string UserId { get; set; }
-    
-    public async Task<ProfileResponse> GetProfile()
-    {
+
+    public async Task<ProfileResponse> GetProfile() {
         var hc = new HttpClient();
         var resp = await hc.GetAsync($"{HomeServer}/_matrix/client/r0/profile/{UserId}");
         var data = await resp.Content.ReadFromJsonAsync<JsonElement>();
-        if(!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data.ToString());
+        if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data);
         return data.Deserialize<ProfileResponse>();
     }
-    public async Task<string> GetCanonicalHomeserverUrl()
-    {
-        return (await new RemoteHomeServer(HomeServer).Configure()).FullHomeServerDomain;
-    }
+
+    public async Task<string> GetCanonicalHomeserverUrl() => (await new RemoteHomeServer(HomeServer).Configure()).FullHomeServerDomain;
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Responses/ProfileResponse.cs b/MatrixRoomUtils.Core/Responses/ProfileResponse.cs
index 2c0b679..db72386 100644
--- a/MatrixRoomUtils.Core/Responses/ProfileResponse.cs
+++ b/MatrixRoomUtils.Core/Responses/ProfileResponse.cs
@@ -2,10 +2,10 @@ using System.Text.Json.Serialization;
 
 namespace MatrixRoomUtils.Core.Responses;
 
-public class ProfileResponse
-{
+public class ProfileResponse {
     [JsonPropertyName("avatar_url")]
     public string? AvatarUrl { get; set; } = "";
+
     [JsonPropertyName("displayname")]
     public string? DisplayName { get; set; } = "";
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Responses/StateEventResponse.cs b/MatrixRoomUtils.Core/Responses/StateEventResponse.cs
index 670c121..36f0a36 100644
--- a/MatrixRoomUtils.Core/Responses/StateEventResponse.cs
+++ b/MatrixRoomUtils.Core/Responses/StateEventResponse.cs
@@ -2,43 +2,47 @@ using System.Text.Json.Serialization;
 
 namespace MatrixRoomUtils.Core;
 
-public class StateEventResponse : StateEvent
-{
+public class StateEventResponse : StateEvent {
     [JsonPropertyName("origin_server_ts")]
     public ulong OriginServerTs { get; set; }
+
     [JsonPropertyName("room_id")]
     public string RoomId { get; set; }
+
     [JsonPropertyName("sender")]
     public string Sender { get; set; }
+
     [JsonPropertyName("unsigned")]
     public UnsignedData? Unsigned { get; set; }
+
     [JsonPropertyName("event_id")]
     public string EventId { get; set; }
+
     [JsonPropertyName("user_id")]
     public string UserId { get; set; }
+
     [JsonPropertyName("replaces_state")]
     public string ReplacesState { get; set; }
+
     [JsonPropertyName("prev_content")]
     public dynamic PrevContent { get; set; }
-    
-    
-    public class UnsignedData
-    {
+
+    public class UnsignedData {
         [JsonPropertyName("age")]
         public ulong Age { get; set; }
+
         [JsonPropertyName("prev_content")]
         public dynamic? PrevContent { get; set; }
+
         [JsonPropertyName("redacted_because")]
         public dynamic? RedactedBecause { get; set; }
+
         [JsonPropertyName("transaction_id")]
         public string? TransactionId { get; set; }
-        
     }
 }
 
-public class StateEventResponse<T> : StateEventResponse where T : class
-{
-    
+public class StateEventResponse<T> : StateEventResponse where T : class {
     [JsonPropertyName("content")]
     public T Content { get; set; }
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Room.cs b/MatrixRoomUtils.Core/Room.cs
index f228271..a867c0c 100644
--- a/MatrixRoomUtils.Core/Room.cs
+++ b/MatrixRoomUtils.Core/Room.cs
@@ -1,6 +1,4 @@
-using System.Diagnostics.CodeAnalysis;
 using System.Net.Http.Json;
-using System.Reflection;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 using System.Web;
@@ -8,26 +6,23 @@ using MatrixRoomUtils.Core.Extensions;
 
 namespace MatrixRoomUtils.Core;
 
-public class Room
-{
+public class Room {
     private readonly HttpClient _httpClient;
-    public string RoomId { get; set; }
 
-    public Room(HttpClient httpClient, string roomId)
-    {
+    public Room(HttpClient httpClient, string roomId) {
         _httpClient = httpClient;
         RoomId = roomId;
     }
 
-    public async Task<JsonElement?> GetStateAsync(string type, string stateKey = "", bool logOnFailure = true)
-    {
+    public string RoomId { get; set; }
+
+    public async Task<JsonElement?> GetStateAsync(string type, string stateKey = "", bool logOnFailure = true) {
         var url = $"/_matrix/client/v3/rooms/{RoomId}/state";
         if (!string.IsNullOrEmpty(type)) url += $"/{type}";
         if (!string.IsNullOrEmpty(stateKey)) url += $"/{stateKey}";
 
         var res = await _httpClient.GetAsync(url);
-        if (!res.IsSuccessStatusCode)
-        {
+        if (!res.IsSuccessStatusCode) {
             if (logOnFailure) Console.WriteLine($"{RoomId}/{stateKey}/{type} - got status: {res.StatusCode}");
             return null;
         }
@@ -36,20 +31,17 @@ public class Room
         return result;
     }
 
-    public async Task<T?> GetStateAsync<T>(string type, string stateKey = "", bool logOnFailure = false)
-    {
+    public async Task<T?> GetStateAsync<T>(string type, string stateKey = "", bool logOnFailure = false) {
         var res = await GetStateAsync(type, stateKey, logOnFailure);
         if (res == null) return default;
         return res.Value.Deserialize<T>();
     }
-    
-    public async Task<MessagesResponse> GetMessagesAsync(string from = "", int limit = 10, string dir = "b", string filter = "")
-    {
+
+    public async Task<MessagesResponse> GetMessagesAsync(string from = "", int limit = 10, string dir = "b", string filter = "") {
         var url = $"/_matrix/client/r0/rooms/{RoomId}/messages?from={from}&limit={limit}&dir={dir}";
         if (!string.IsNullOrEmpty(filter)) url += $"&filter={filter}";
         var res = await _httpClient.GetAsync(url);
-        if (!res.IsSuccessStatusCode)
-        {
+        if (!res.IsSuccessStatusCode) {
             Console.WriteLine($"Failed to get messages for {RoomId} - got status: {res.StatusCode}");
             throw new Exception($"Failed to get messages for {RoomId} - got status: {res.StatusCode}");
         }
@@ -58,11 +50,9 @@ public class Room
         return result ?? new MessagesResponse();
     }
 
-    public async Task<string> GetNameAsync()
-    {
+    public async Task<string> GetNameAsync() {
         var res = await GetStateAsync("m.room.name");
-        if (!res.HasValue)
-        {
+        if (!res.HasValue) {
             Console.WriteLine($"Room {RoomId} has no name!");
             return RoomId;
         }
@@ -72,22 +62,19 @@ public class Room
         return resn;
     }
 
-    public async Task JoinAsync(string[]? homeservers = null)
-    {
-        string join_url = $"/_matrix/client/r0/join/{HttpUtility.UrlEncode(RoomId)}";
+    public async Task JoinAsync(string[]? homeservers = null) {
+        var join_url = $"/_matrix/client/r0/join/{HttpUtility.UrlEncode(RoomId)}";
         Console.WriteLine($"Calling {join_url} with {homeservers?.Length ?? 0} via's...");
         if (homeservers == null || homeservers.Length == 0) homeservers = new[] { RoomId.Split(':')[1] };
         var fullJoinUrl = $"{join_url}?server_name=" + string.Join("&server_name=", homeservers);
         var res = await _httpClient.PostAsync(fullJoinUrl, null);
     }
 
-    public async Task<List<string>> GetMembersAsync(bool joinedOnly = true)
-    {
+    public async Task<List<string>> GetMembersAsync(bool joinedOnly = true) {
         var res = await GetStateAsync("");
         if (!res.HasValue) return new List<string>();
         var members = new List<string>();
-        foreach (var member in res.Value.EnumerateArray())
-        {
+        foreach (var member in res.Value.EnumerateArray()) {
             if (member.GetProperty("type").GetString() != "m.room.member") continue;
             if (joinedOnly && member.GetProperty("content").GetProperty("membership").GetString() != "join") continue;
             var memberId = member.GetProperty("state_key").GetString();
@@ -97,63 +84,52 @@ public class Room
         return members;
     }
 
-    public async Task<List<string>> GetAliasesAsync()
-    {
+    public async Task<List<string>> GetAliasesAsync() {
         var res = await GetStateAsync("m.room.aliases");
         if (!res.HasValue) return new List<string>();
         var aliases = new List<string>();
-        foreach (var alias in res.Value.GetProperty("aliases").EnumerateArray())
-        {
-            aliases.Add(alias.GetString() ?? "");
-        }
+        foreach (var alias in res.Value.GetProperty("aliases").EnumerateArray()) aliases.Add(alias.GetString() ?? "");
 
         return aliases;
     }
 
-    public async Task<string> GetCanonicalAliasAsync()
-    {
+    public async Task<string> GetCanonicalAliasAsync() {
         var res = await GetStateAsync("m.room.canonical_alias");
         if (!res.HasValue) return "";
         return res.Value.GetProperty("alias").GetString() ?? "";
     }
 
-    public async Task<string> GetTopicAsync()
-    {
+    public async Task<string> GetTopicAsync() {
         var res = await GetStateAsync("m.room.topic");
         if (!res.HasValue) return "";
         return res.Value.GetProperty("topic").GetString() ?? "";
     }
 
-    public async Task<string> GetAvatarUrlAsync()
-    {
+    public async Task<string> GetAvatarUrlAsync() {
         var res = await GetStateAsync("m.room.avatar");
         if (!res.HasValue) return "";
         return res.Value.GetProperty("url").GetString() ?? "";
     }
 
-    public async Task<JoinRules> GetJoinRuleAsync()
-    {
+    public async Task<JoinRules> GetJoinRuleAsync() {
         var res = await GetStateAsync("m.room.join_rules");
         if (!res.HasValue) return new JoinRules();
         return res.Value.Deserialize<JoinRules>() ?? new JoinRules();
     }
 
-    public async Task<string> GetHistoryVisibilityAsync()
-    {
+    public async Task<string> GetHistoryVisibilityAsync() {
         var res = await GetStateAsync("m.room.history_visibility");
         if (!res.HasValue) return "";
         return res.Value.GetProperty("history_visibility").GetString() ?? "";
     }
 
-    public async Task<string> GetGuestAccessAsync()
-    {
+    public async Task<string> GetGuestAccessAsync() {
         var res = await GetStateAsync("m.room.guest_access");
         if (!res.HasValue) return "";
         return res.Value.GetProperty("guest_access").GetString() ?? "";
     }
 
-    public async Task<CreateEvent> GetCreateEventAsync()
-    {
+    public async Task<CreateEvent> GetCreateEventAsync() {
         var res = await GetStateAsync("m.room.create");
         if (!res.HasValue) return new CreateEvent();
 
@@ -163,33 +139,45 @@ public class Room
     }
 }
 
-public class MessagesResponse
-{
+public class MessagesResponse {
     [JsonPropertyName("start")]
     public string Start { get; set; }
+
     [JsonPropertyName("end")]
     public string? End { get; set; }
+
     [JsonPropertyName("chunk")]
     public List<StateEventResponse> Chunk { get; set; } = new();
+
     [JsonPropertyName("state")]
     public List<StateEventResponse> State { get; set; } = new();
 }
 
-public class CreateEvent
-{
-    [JsonPropertyName("creator")] public string Creator { get; set; }
-    [JsonPropertyName("room_version")] public string RoomVersion { get; set; }
-    [JsonPropertyName("type")] public string? Type { get; set; }
-    [JsonPropertyName("predecessor")] public object? Predecessor { get; set; }
-    [JsonPropertyName("m.federate")] public bool Federate { get; set; }
+public class CreateEvent {
+    [JsonPropertyName("creator")]
+    public string Creator { get; set; }
+
+    [JsonPropertyName("room_version")]
+    public string RoomVersion { get; set; }
+
+    [JsonPropertyName("type")]
+    public string? Type { get; set; }
+
+    [JsonPropertyName("predecessor")]
+    public object? Predecessor { get; set; }
+
+    [JsonPropertyName("m.federate")]
+    public bool Federate { get; set; }
 }
 
-public class JoinRules
-{
+public class JoinRules {
     private const string Public = "public";
     private const string Invite = "invite";
     private const string Knock = "knock";
 
-    [JsonPropertyName("join_rule")] public string JoinRule { get; set; }
-    [JsonPropertyName("allow")] public List<string> Allow { get; set; }
+    [JsonPropertyName("join_rule")]
+    public string JoinRule { get; set; }
+
+    [JsonPropertyName("allow")]
+    public List<string> Allow { get; set; }
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/RuntimeCache.cs b/MatrixRoomUtils.Core/RuntimeCache.cs
index 4f73341..a2fcf40 100644
--- a/MatrixRoomUtils.Core/RuntimeCache.cs
+++ b/MatrixRoomUtils.Core/RuntimeCache.cs
@@ -3,9 +3,21 @@ using MatrixRoomUtils.Core.Responses;
 
 namespace MatrixRoomUtils.Core;
 
-public class RuntimeCache
-{
+public class RuntimeCache {
     public static bool WasLoaded = false;
+
+    static RuntimeCache() =>
+        Task.Run(async () => {
+            while (true) {
+                await Task.Delay(1000);
+                foreach (var (key, value) in GenericResponseCache)
+                    if (value.Cache.Any())
+                        SaveObject("rory.matrixroomutils.generic_cache:" + key, value);
+                    else
+                        RemoveObject("rory.matrixroomutils.generic_cache:" + key);
+            }
+        });
+
     public static string? LastUsedToken { get; set; }
     public static AuthenticatedHomeServer CurrentHomeServer { get; set; }
     public static Dictionary<string, UserInfo> LoginSessions { get; set; } = new();
@@ -18,110 +30,69 @@ public class RuntimeCache
     public static Action Save { get; set; } = () => { Console.WriteLine("RuntimeCache.Save() was called, but no callback was set!"); };
     public static Action<string, object> SaveObject { get; set; } = (key, value) => { Console.WriteLine($"RuntimeCache.SaveObject({key}, {value}) was called, but no callback was set!"); };
     public static Action<string> RemoveObject { get; set; } = key => { Console.WriteLine($"RuntimeCache.RemoveObject({key}) was called, but no callback was set!"); };
-
-    static RuntimeCache()
-    {
-        Task.Run(async () =>
-        {
-            while(true)
-            {
-                await Task.Delay(1000);
-                foreach (var (key, value) in GenericResponseCache)
-                {
-                    if (value.Cache.Any())
-                        SaveObject("rory.matrixroomutils.generic_cache:" + key, value);
-                    else
-                    {
-                        RemoveObject("rory.matrixroomutils.generic_cache:" + key);
-                    }
-                }
-            }
-        });
-    }
 }
 
-public class UserInfo
-{
+public class UserInfo {
     public ProfileResponse Profile { get; set; } = new();
     public LoginResponse LoginResponse { get; set; }
 
-    public string AccessToken
-    {
-        get => LoginResponse.AccessToken;
-    }
+    public string AccessToken => LoginResponse.AccessToken;
 }
 
-public class HomeServerResolutionResult
-{
+public class HomeServerResolutionResult {
     public string Result { get; set; }
     public DateTime ResolutionTime { get; set; }
 }
 
-public class ObjectCache<T> where T : class
-{
+public class ObjectCache<T> where T : class {
+    public ObjectCache() =>
+        //expiry timer
+        Task.Run(async () => {
+            while (Cache.Any()) {
+                await Task.Delay(1000);
+                foreach (var x in Cache.Where(x => x.Value.ExpiryTime < DateTime.Now).OrderBy(x => x.Value.ExpiryTime).Take(15).ToList())
+                    // Console.WriteLine($"Removing {x.Key} from cache");
+                    Cache.Remove(x.Key);
+                //RuntimeCache.SaveObject("rory.matrixroomutils.generic_cache:" + Name, this);
+            }
+        });
+
     public Dictionary<string, GenericResult<T>> Cache { get; set; } = new();
     public string Name { get; set; } = null!;
 
-    public GenericResult<T> this[string key]
-    {
-        get
-        {
-            if (Cache.ContainsKey(key))
-            {
+    public GenericResult<T> this[string key] {
+        get {
+            if (Cache.ContainsKey(key)) {
                 // Console.WriteLine($"cache.get({key}): hit");
                 // Console.WriteLine($"Found item in cache: {key} - {Cache[key].Result.ToJson(indent: false)}");
                 if (Cache[key].ExpiryTime < DateTime.Now)
-                    Console.WriteLine($"WARNING: item {key} in cache {Name} expired at {Cache[key].ExpiryTime}:\n{Cache[key].Result.ToJson(indent: false)}");
+                    Console.WriteLine($"WARNING: item {key} in cache {Name} expired at {Cache[key].ExpiryTime}:\n{Cache[key].Result.ToJson(false)}");
                 return Cache[key];
             }
 
             Console.WriteLine($"cache.get({key}): miss");
             return null;
         }
-        set
-        {
-            Cache[key] = value;
-            // Console.WriteLine($"set({key}) = {Cache[key].Result.ToJson(indent:false)}");
-            // Console.WriteLine($"new_state: {this.ToJson(indent:false)}");
-            // Console.WriteLine($"New item in cache: {key} - {Cache[key].Result.ToJson(indent: false)}");
-            // Console.Error.WriteLine("Full cache: " + Cache.ToJson());
-        }
-    }
-
-    public ObjectCache()
-    {
-        //expiry timer
-        Task.Run(async () =>
-        {
-            while (Cache.Any())
-            {
-                await Task.Delay(1000);
-                foreach (var x in Cache.Where(x => x.Value.ExpiryTime < DateTime.Now).OrderBy(x => x.Value.ExpiryTime).Take(15).ToList())
-                {
-                    // Console.WriteLine($"Removing {x.Key} from cache");
-                    Cache.Remove(x.Key);
-                }
-                //RuntimeCache.SaveObject("rory.matrixroomutils.generic_cache:" + Name, this);
-            }
-        });
+        set => Cache[key] = value;
+        // Console.WriteLine($"set({key}) = {Cache[key].Result.ToJson(indent:false)}");
+        // Console.WriteLine($"new_state: {this.ToJson(indent:false)}");
+        // Console.WriteLine($"New item in cache: {key} - {Cache[key].Result.ToJson(indent: false)}");
+        // Console.Error.WriteLine("Full cache: " + Cache.ToJson());
     }
 
     public bool ContainsKey(string key) => Cache.ContainsKey(key);
 }
 
-public class GenericResult<T>
-{
-    public T? Result { get; set; }
-    public DateTime? ExpiryTime { get; set; } = DateTime.Now;
-
-    public GenericResult()
-    {
+public class GenericResult<T> {
+    public GenericResult() {
         //expiry timer
     }
 
-    public GenericResult(T? result, DateTime? expiryTime = null) : this()
-    {
+    public GenericResult(T? result, DateTime? expiryTime = null) : this() {
         Result = result;
         ExpiryTime = expiryTime;
     }
+
+    public T? Result { get; set; }
+    public DateTime? ExpiryTime { get; set; } = DateTime.Now;
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEvent.cs b/MatrixRoomUtils.Core/StateEvent.cs
index 6321fb6..a8c1fac 100644
--- a/MatrixRoomUtils.Core/StateEvent.cs
+++ b/MatrixRoomUtils.Core/StateEvent.cs
@@ -4,52 +4,48 @@ using System.Text.Json.Serialization;
 
 namespace MatrixRoomUtils.Core;
 
-public class StateEvent
-{
-    [JsonPropertyName("content")] public dynamic Content { get; set; } = new { };
+public class StateEvent {
+    [JsonPropertyName("content")]
+    public dynamic Content { get; set; } = new { };
+
+    [JsonPropertyName("state_key")]
+    public string StateKey { get; set; } = "";
 
-    [JsonPropertyName("state_key")] public string StateKey { get; set; } = "";
-    [JsonPropertyName("type")] public string Type { get; set; }
-    [JsonPropertyName("replaces_state")] public string? ReplacesState { get; set; }
+    [JsonPropertyName("type")]
+    public string Type { get; set; }
+
+    [JsonPropertyName("replaces_state")]
+    public string? ReplacesState { get; set; }
 
     //extra properties
     [JsonIgnore]
-    public JsonNode ContentAsJsonNode
-    {
+    public JsonNode ContentAsJsonNode {
         get => JsonSerializer.SerializeToNode(Content);
         set => Content = value;
     }
 
-    public StateEvent<T> As<T>() where T : class
-    {
-        return (StateEvent<T>)this;
-    }
-
-    public string dtype
-    {
-        get
-        {
-            string res = GetType().Name switch
-            {
+    public string dtype {
+        get {
+            var res = GetType().Name switch {
                 "StateEvent`1" => $"StateEvent<{Content.GetType().Name}>",
                 _ => GetType().Name
             };
             return res;
         }
     }
+
+    public StateEvent<T> As<T>() where T : class => (StateEvent<T>)this;
 }
 
-public class StateEvent<T> : StateEvent where T : class
-{
-    public StateEvent()
-    {
+public class StateEvent<T> : StateEvent where T : class {
+    public StateEvent() {
         //import base content if not an empty object
-        if (base.Content.GetType() == typeof(T))
-        {
+        if (base.Content.GetType() == typeof(T)) {
             Console.WriteLine($"StateEvent<{typeof(T)}> created with base content of type {base.Content.GetType()}. Importing base content.");
             Content = base.Content;
         }
     }
+
     [JsonPropertyName("content")]
     public new T Content { get; set; }
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEventStruct.cs b/MatrixRoomUtils.Core/StateEventStruct.cs
index bfda594..cd301ac 100644
--- a/MatrixRoomUtils.Core/StateEventStruct.cs
+++ b/MatrixRoomUtils.Core/StateEventStruct.cs
@@ -1,7 +1,6 @@
 namespace MatrixRoomUtils.Core;
 
-public struct StateEventStruct
-{
+public struct StateEventStruct {
     public object content { get; set; }
     public long origin_server_ts { get; set; }
     public string sender { get; set; }
diff --git a/MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs
index a927ace..6f6d082 100644
--- a/MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs
+++ b/MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs
@@ -2,51 +2,50 @@ using System.Text.Json.Serialization;
 
 namespace MatrixRoomUtils.Core.StateEventTypes;
 
-public class PolicyRuleStateEventData
-{
+public class PolicyRuleStateEventData {
     /// <summary>
-    /// Entity this ban applies to, can use * and ? as globs.
+    ///     Entity this ban applies to, can use * and ? as globs.
     /// </summary>
     [JsonPropertyName("entity")]
     public string Entity { get; set; }
+
     /// <summary>
-    /// Reason this user is banned
+    ///     Reason this user is banned
     /// </summary>
     [JsonPropertyName("reason")]
     public string? Reason { get; set; }
+
     /// <summary>
-    /// Suggested action to take
+    ///     Suggested action to take
     /// </summary>
     [JsonPropertyName("recommendation")]
     public string? Recommendation { get; set; }
 
     /// <summary>
-    /// Expiry time in milliseconds since the unix epoch, or null if the ban has no expiry.
+    ///     Expiry time in milliseconds since the unix epoch, or null if the ban has no expiry.
     /// </summary>
     [JsonPropertyName("support.feline.policy.expiry.rev.2")] //stable prefix: expiry, msc pending
     public long? Expiry { get; set; }
-    
-    
+
     //utils
     /// <summary>
-    /// Readable expiry time, provided for easy interaction
+    ///     Readable expiry time, provided for easy interaction
     /// </summary>
     [JsonPropertyName("gay.rory.matrix_room_utils.readable_expiry_time_utc")]
-    public DateTime? ExpiryDateTime
-    {
+    public DateTime? ExpiryDateTime {
         get => Expiry == null ? null : DateTimeOffset.FromUnixTimeMilliseconds(Expiry.Value).DateTime;
-        set => Expiry = ((DateTimeOffset) value).ToUnixTimeMilliseconds();
+        set => Expiry = ((DateTimeOffset)value).ToUnixTimeMilliseconds();
     }
 }
 
-public static class PolicyRecommendationTypes
-{
+public static class PolicyRecommendationTypes {
     /// <summary>
-    /// Ban this user
+    ///     Ban this user
     /// </summary>
     public static string Ban = "m.ban";
+
     /// <summary>
-    /// Mute this user
+    ///     Mute this user
     /// </summary>
     public static string Mute = "support.feline.policy.recommendation_mute"; //stable prefix: m.mute, msc pending
 }
\ No newline at end of file