about summary refs log tree commit diff
path: root/LibMatrix
diff options
context:
space:
mode:
Diffstat (limited to 'LibMatrix')
-rw-r--r--LibMatrix/Extensions/HttpClientExtensions.cs5
-rw-r--r--LibMatrix/Extensions/JsonElementExtensions.cs3
-rw-r--r--LibMatrix/Filters/SyncFilter.cs1
-rw-r--r--LibMatrix/Helpers/HomeserverWeightEstimation.cs58
-rw-r--r--LibMatrix/Helpers/MatrixEventAttribute.cs9
-rw-r--r--LibMatrix/Helpers/SyncHelper.cs15
-rw-r--r--LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs89
-rw-r--r--LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs14
-rw-r--r--LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs (renamed from LibMatrix/AuthenticatedHomeServer.cs)89
-rw-r--r--LibMatrix/Homeservers/RemoteHomeServer.cs46
-rw-r--r--LibMatrix/Interfaces/IHomeServer.cs29
-rw-r--r--LibMatrix/Interfaces/IStateEventType.cs4
-rw-r--r--LibMatrix/Interfaces/Services/IStorageProvider.cs5
-rw-r--r--LibMatrix/LibMatrix.csproj4
-rw-r--r--LibMatrix/MatrixException.cs2
-rw-r--r--LibMatrix/MessagesResponse.cs1
-rw-r--r--LibMatrix/RemoteHomeServer.cs13
-rw-r--r--LibMatrix/Responses/Admin/AdminRoomListingResult.cs1
-rw-r--r--LibMatrix/Responses/ClientVersionsResponse.cs12
-rw-r--r--LibMatrix/Responses/CreateRoomRequest.cs4
-rw-r--r--LibMatrix/Responses/LoginResponse.cs21
-rw-r--r--LibMatrix/RoomTypes/GenericRoom.cs34
-rw-r--r--LibMatrix/RoomTypes/SpaceRoom.cs12
-rw-r--r--LibMatrix/Services/HomeserverProviderService.cs74
-rw-r--r--LibMatrix/Services/HomeserverResolverService.cs10
-rw-r--r--LibMatrix/Services/ServiceInstaller.cs2
-rw-r--r--LibMatrix/StateEvent.cs8
-rw-r--r--LibMatrix/StateEventTypes/Common/MjolnirShortcodeEventData.cs1
-rw-r--r--LibMatrix/StateEventTypes/Common/RoomEmotesEventData.cs2
-rw-r--r--LibMatrix/StateEventTypes/Spec/CanonicalAliasEventData.cs1
-rw-r--r--LibMatrix/StateEventTypes/Spec/GuestAccessEventData.cs1
-rw-r--r--LibMatrix/StateEventTypes/Spec/HistoryVisibilityEventData.cs1
-rw-r--r--LibMatrix/StateEventTypes/Spec/JoinRulesEventData.cs12
-rw-r--r--LibMatrix/StateEventTypes/Spec/PolicyRuleStateEventData.cs2
-rw-r--r--LibMatrix/StateEventTypes/Spec/PresenceStateEventData.cs5
-rw-r--r--LibMatrix/StateEventTypes/Spec/ProfileResponseEventData.cs4
-rw-r--r--LibMatrix/StateEventTypes/Spec/RoomAliasEventData.cs2
-rw-r--r--LibMatrix/StateEventTypes/Spec/RoomAvatarEventData.cs1
-rw-r--r--LibMatrix/StateEventTypes/Spec/RoomCreateEventData.cs1
-rw-r--r--LibMatrix/StateEventTypes/Spec/RoomEncryptionEventData.cs1
-rw-r--r--LibMatrix/StateEventTypes/Spec/RoomMemberEventData.cs1
-rw-r--r--LibMatrix/StateEventTypes/Spec/RoomMessageEventData.cs1
-rw-r--r--LibMatrix/StateEventTypes/Spec/RoomNameEventData.cs1
-rw-r--r--LibMatrix/StateEventTypes/Spec/RoomPinnedEventData.cs1
-rw-r--r--LibMatrix/StateEventTypes/Spec/RoomPowerLevelEventData.cs26
-rw-r--r--LibMatrix/StateEventTypes/Spec/RoomTopicEventData.cs1
-rw-r--r--LibMatrix/StateEventTypes/Spec/RoomTypingEventData.cs1
-rw-r--r--LibMatrix/StateEventTypes/Spec/ServerACLEventData.cs2
-rw-r--r--LibMatrix/StateEventTypes/Spec/SpaceChildEventData.cs1
-rw-r--r--LibMatrix/StateEventTypes/Spec/SpaceParentEventData.cs1
-rw-r--r--LibMatrix/WhoAmIResponse.cs13
51 files changed, 457 insertions, 191 deletions
diff --git a/LibMatrix/Extensions/HttpClientExtensions.cs b/LibMatrix/Extensions/HttpClientExtensions.cs
index d4017ed..4d81b6e 100644
--- a/LibMatrix/Extensions/HttpClientExtensions.cs
+++ b/LibMatrix/Extensions/HttpClientExtensions.cs
@@ -1,7 +1,12 @@
+using System;
 using System.Diagnostics;
+using System.IO;
+using System.Net.Http;
 using System.Net.Http.Headers;
 using System.Reflection;
 using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
 
 namespace LibMatrix.Extensions;
 
diff --git a/LibMatrix/Extensions/JsonElementExtensions.cs b/LibMatrix/Extensions/JsonElementExtensions.cs
index f39f300..99fa72d 100644
--- a/LibMatrix/Extensions/JsonElementExtensions.cs
+++ b/LibMatrix/Extensions/JsonElementExtensions.cs
@@ -1,3 +1,6 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
 using System.Reflection;
 using System.Text.Json;
 using System.Text.Json.Nodes;
diff --git a/LibMatrix/Filters/SyncFilter.cs b/LibMatrix/Filters/SyncFilter.cs
index c907f6b..e281346 100644
--- a/LibMatrix/Filters/SyncFilter.cs
+++ b/LibMatrix/Filters/SyncFilter.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
 using System.Text.Json.Serialization;
 
 namespace LibMatrix.Filters;
diff --git a/LibMatrix/Helpers/HomeserverWeightEstimation.cs b/LibMatrix/Helpers/HomeserverWeightEstimation.cs
new file mode 100644
index 0000000..8f1bf3a
--- /dev/null
+++ b/LibMatrix/Helpers/HomeserverWeightEstimation.cs
@@ -0,0 +1,58 @@
+namespace LibMatrix.Helpers;
+
+public class HomeserverWeightEstimation {
+    public static Dictionary<string, int> EstimatedSize = new() {
+        { "matrix.org", 84387 },
+        { "anontier.nl", 44809 },
+        { "nixos.org", 8195 },
+        { "the-apothecary.club", 6983 },
+        { "waifuhunter.club", 3953 },
+        { "neko.dev", 2666 },
+        { "nerdsin.space", 2647 },
+        { "feline.support", 2633 },
+        { "gitter.im", 2584 },
+        { "midov.pl", 2219 },
+        { "no.lgbtqia.zone", 2083 },
+        { "nheko.im", 1883 },
+        { "fachschaften.org", 1849 },
+        { "pixelthefox.net", 1478 },
+        { "arcticfoxes.net", 981 },
+        { "pixie.town", 817 },
+        { "privacyguides.org", 809 },
+        { "rory.gay", 653 },
+        { "artemislena.eu", 599 },
+        { "alchemi.dev", 445 },
+        { "jameskitt616.one", 390 },
+        { "hackint.org", 382 },
+        { "pikaviestin.fi", 368 },
+        { "matrix.nomagic.uk", 337 },
+        { "thearcanebrony.net", 178 },
+        { "fairydust.space", 176 },
+        { "grin.hu", 176 },
+        { "envs.net", 165 },
+        { "tastytea.de", 143 },
+        { "koneko.chat", 121 },
+        { "vscape.tk", 115 },
+        { "funklause.de", 112 },
+        { "seirdy.one", 107 },
+        { "pcg.life", 72 },
+        { "draupnir.midnightthoughts.space", 22 },
+        { "tchncs.de", 19 },
+        { "catgirl.cloud", 16 },
+        { "possum.city", 16 },
+        { "tu-dresden.de", 9 },
+        { "fosscord.com", 9 },
+        { "nightshade.fun", 8 },
+        { "matrix.eclipse.org", 8 },
+        { "masfloss.net", 8 },
+        { "e2e.zone", 8 },
+        { "hyteck.de", 8 }
+    };
+
+    public static Dictionary<string, int> LargeRooms = new() {
+        { "!ehXvUhWNASUkSLvAGP:matrix.org", 21957 },
+        { "!fRRqjOaQcUbKOfCjvc:anontier.nl", 19117 },
+        { "!OGEhHVWSdvArJzumhm:matrix.org", 101457 },
+        { "!YTvKGNlinIzlkMTVRl:matrix.org", 30164 }
+    };
+}
diff --git a/LibMatrix/Helpers/MatrixEventAttribute.cs b/LibMatrix/Helpers/MatrixEventAttribute.cs
new file mode 100644
index 0000000..7556019
--- /dev/null
+++ b/LibMatrix/Helpers/MatrixEventAttribute.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace LibMatrix.Helpers;
+
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+public class MatrixEventAttribute : Attribute {
+    public string EventName { get; set; }
+    public bool Legacy { get; set; }
+}
diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs
index b957c0c..de4f3d4 100644
--- a/LibMatrix/Helpers/SyncHelper.cs
+++ b/LibMatrix/Helpers/SyncHelper.cs
@@ -1,19 +1,26 @@
+using System;
+using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
+using System.Linq;
 using System.Net.Http.Json;
 using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+using ArcaneLibs.Extensions;
 using LibMatrix.Extensions;
 using LibMatrix.Filters;
+using LibMatrix.Homeservers;
 using LibMatrix.Responses;
 using LibMatrix.Services;
 
 namespace LibMatrix.Helpers;
 
 public class SyncHelper {
-    private readonly AuthenticatedHomeServer _homeServer;
+    private readonly AuthenticatedHomeserverGeneric _homeserver;
     private readonly TieredStorageService _storageService;
 
-    public SyncHelper(AuthenticatedHomeServer homeServer, TieredStorageService storageService) {
-        _homeServer = homeServer;
+    public SyncHelper(AuthenticatedHomeserverGeneric homeserver, TieredStorageService storageService) {
+        _homeserver = homeserver;
         _storageService = storageService;
     }
 
@@ -33,7 +40,7 @@ public class SyncHelper {
         // else url += "&full_state=true";
         Console.WriteLine("Calling: " + url);
         try {
-            var req = await _homeServer._httpClient.GetAsync(url, cancellationToken: cancellationToken ?? CancellationToken.None);
+            var req = await _homeserver._httpClient.GetAsync(url, cancellationToken: cancellationToken ?? CancellationToken.None);
 
             // var res = await JsonSerializer.DeserializeAsync<SyncResult>(await req.Content.ReadAsStreamAsync());
 
diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
new file mode 100644
index 0000000..ecac4e4
--- /dev/null
+++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Json;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using System.Threading.Tasks;
+using LibMatrix.Extensions;
+using LibMatrix.Helpers;
+using LibMatrix.Responses;
+using LibMatrix.RoomTypes;
+using LibMatrix.Services;
+
+namespace LibMatrix.Homeservers;
+
+public class AuthenticatedHomeserverGeneric : RemoteHomeServer {
+    public AuthenticatedHomeserverGeneric(TieredStorageService storage, string canonicalHomeServerDomain, string accessToken) : base(canonicalHomeServerDomain) {
+        Storage = storage;
+        AccessToken = accessToken.Trim();
+        HomeServerDomain = canonicalHomeServerDomain.Trim();
+        SyncHelper = new SyncHelper(this, storage);
+        _httpClient = new MatrixHttpClient();
+    }
+
+    public TieredStorageService Storage { get; set; }
+    public SyncHelper SyncHelper { get; init; }
+    public WhoAmIResponse WhoAmI { get; set; } = null!;
+    public string UserId => WhoAmI.UserId;
+    public string AccessToken { get; set; }
+
+
+    public Task<GenericRoom> GetRoom(string roomId) => Task.FromResult<GenericRoom>(new(this, roomId));
+
+    public async Task<List<GenericRoom>> GetJoinedRooms() {
+        var roomQuery = await _httpClient.GetAsync("/_matrix/client/v3/joined_rooms");
+
+        var roomsJson = await roomQuery.Content.ReadFromJsonAsync<JsonElement>();
+        var rooms = roomsJson.GetProperty("joined_rooms").EnumerateArray().Select(room => new GenericRoom(this, room.GetString()!)).ToList();
+
+        Console.WriteLine($"Fetched {rooms.Count} rooms");
+
+        return rooms;
+    }
+
+    public async Task<string> UploadFile(string fileName, Stream fileStream, string contentType = "application/octet-stream") {
+        var res = await _httpClient.PostAsync($"/_matrix/media/v3/upload?filename={fileName}", new StreamContent(fileStream));
+        if (!res.IsSuccessStatusCode) {
+            Console.WriteLine($"Failed to upload file: {await res.Content.ReadAsStringAsync()}");
+            throw new InvalidDataException($"Failed to upload file: {await res.Content.ReadAsStringAsync()}");
+        }
+
+        var resJson = await res.Content.ReadFromJsonAsync<JsonElement>();
+        return resJson.GetProperty("content_uri").GetString()!;
+    }
+
+    public async Task<GenericRoom> CreateRoom(CreateRoomRequest creationEvent) {
+        var res = await _httpClient.PostAsJsonAsync("/_matrix/client/v3/createRoom", creationEvent);
+        if (!res.IsSuccessStatusCode) {
+            Console.WriteLine($"Failed to create room: {await res.Content.ReadAsStringAsync()}");
+            throw new InvalidDataException($"Failed to create room: {await res.Content.ReadAsStringAsync()}");
+        }
+
+        return await GetRoom((await res.Content.ReadFromJsonAsync<JsonObject>())!["room_id"]!.ToString());
+    }
+
+#region Account Data
+
+    public async Task<T> GetAccountData<T>(string key) {
+        var res = await _httpClient.GetAsync($"/_matrix/client/v3/user/{UserId}/account_data/{key}");
+        if (!res.IsSuccessStatusCode) {
+            Console.WriteLine($"Failed to get account data: {await res.Content.ReadAsStringAsync()}");
+            throw new InvalidDataException($"Failed to get account data: {await res.Content.ReadAsStringAsync()}");
+        }
+
+        return await res.Content.ReadFromJsonAsync<T>();
+    }
+
+    public async Task SetAccountData(string key, object data) {
+        var res = await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/user/{UserId}/account_data/{key}", data);
+        if (!res.IsSuccessStatusCode) {
+            Console.WriteLine($"Failed to set account data: {await res.Content.ReadAsStringAsync()}");
+            throw new InvalidDataException($"Failed to set account data: {await res.Content.ReadAsStringAsync()}");
+        }
+    }
+
+#endregion
+}
diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs
new file mode 100644
index 0000000..8ffcfaf
--- /dev/null
+++ b/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs
@@ -0,0 +1,14 @@
+using LibMatrix.Extensions;
+using LibMatrix.Helpers;
+using LibMatrix.Services;
+
+namespace LibMatrix.Homeservers;
+
+public class AuthenticatedHomeserverMxApiExtended : AuthenticatedHomeserverGeneric {
+    public AuthenticatedHomeserverMxApiExtended(TieredStorageService storage, string canonicalHomeServerDomain, string accessToken) : base(storage, canonicalHomeServerDomain, accessToken) {
+        AccessToken = accessToken.Trim();
+        HomeServerDomain = canonicalHomeServerDomain.Trim();
+        SyncHelper = new SyncHelper(this, storage);
+        _httpClient = new MatrixHttpClient();
+    }
+}
diff --git a/LibMatrix/AuthenticatedHomeServer.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs
index a99dc27..3b0bc10 100644
--- a/LibMatrix/AuthenticatedHomeServer.cs
+++ b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs
@@ -1,75 +1,18 @@
-using System.Net.Http.Json;
-using System.Text.Json;
-using System.Text.Json.Nodes;
-using System.Text.Json.Serialization;
-using LibMatrix.Extensions;
+using System;
+using System.Collections.Generic;
+using ArcaneLibs.Extensions;
 using LibMatrix.Filters;
-using LibMatrix.Helpers;
-using LibMatrix.Interfaces;
-using LibMatrix.Responses;
 using LibMatrix.Responses.Admin;
-using LibMatrix.RoomTypes;
 using LibMatrix.Services;
 
-namespace LibMatrix;
+namespace LibMatrix.Homeservers;
 
-public class AuthenticatedHomeServer : IHomeServer {
-    private readonly TieredStorageService _storage;
-    public readonly HomeserverAdminApi Admin;
-    public readonly SyncHelper SyncHelper;
+public class AuthenticatedHomeserverSynapse : AuthenticatedHomeserverGeneric {
+    public readonly SynapseAdminApi Admin;
+    public class SynapseAdminApi {
+        private readonly AuthenticatedHomeserverGeneric _authenticatedHomeserver;
 
-    public AuthenticatedHomeServer(TieredStorageService storage, string canonicalHomeServerDomain, string accessToken) {
-        _storage = storage;
-        AccessToken = accessToken.Trim();
-        HomeServerDomain = canonicalHomeServerDomain.Trim();
-        Admin = new HomeserverAdminApi(this);
-        SyncHelper = new SyncHelper(this, storage);
-        _httpClient = new MatrixHttpClient();
-    }
-
-    public WhoAmIResponse WhoAmI { get; set; } = null!;
-    public string UserId => WhoAmI.UserId;
-    public string AccessToken { get; set; }
-
-
-    public Task<GenericRoom> GetRoom(string roomId) => Task.FromResult<GenericRoom>(new(this, roomId));
-
-    public async Task<List<GenericRoom>> GetJoinedRooms() {
-        var roomQuery = await _httpClient.GetAsync("/_matrix/client/v3/joined_rooms");
-
-        var roomsJson = await roomQuery.Content.ReadFromJsonAsync<JsonElement>();
-        var rooms = roomsJson.GetProperty("joined_rooms").EnumerateArray().Select(room => new GenericRoom(this, room.GetString()!)).ToList();
-
-        Console.WriteLine($"Fetched {rooms.Count} rooms");
-
-        return rooms;
-    }
-
-    public async Task<string> UploadFile(string fileName, Stream fileStream, string contentType = "application/octet-stream") {
-        var res = await _httpClient.PostAsync($"/_matrix/media/v3/upload?filename={fileName}", new StreamContent(fileStream));
-        if (!res.IsSuccessStatusCode) {
-            Console.WriteLine($"Failed to upload file: {await res.Content.ReadAsStringAsync()}");
-            throw new InvalidDataException($"Failed to upload file: {await res.Content.ReadAsStringAsync()}");
-        }
-
-        var resJson = await res.Content.ReadFromJsonAsync<JsonElement>();
-        return resJson.GetProperty("content_uri").GetString()!;
-    }
-
-    public async Task<GenericRoom> CreateRoom(CreateRoomRequest creationEvent) {
-        var res = await _httpClient.PostAsJsonAsync("/_matrix/client/v3/createRoom", creationEvent);
-        if (!res.IsSuccessStatusCode) {
-            Console.WriteLine($"Failed to create room: {await res.Content.ReadAsStringAsync()}");
-            throw new InvalidDataException($"Failed to create room: {await res.Content.ReadAsStringAsync()}");
-        }
-
-        return await GetRoom((await res.Content.ReadFromJsonAsync<JsonObject>())!["room_id"]!.ToString());
-    }
-
-    public class HomeserverAdminApi {
-        private readonly AuthenticatedHomeServer _authenticatedHomeServer;
-
-        public HomeserverAdminApi(AuthenticatedHomeServer authenticatedHomeServer) => _authenticatedHomeServer = authenticatedHomeServer;
+        public SynapseAdminApi(AuthenticatedHomeserverGeneric authenticatedHomeserver) => _authenticatedHomeserver = authenticatedHomeserver;
 
         public async IAsyncEnumerable<AdminRoomListingResult.AdminRoomListingResultRoom> SearchRoomsAsync(int limit = int.MaxValue, string orderBy = "name", string dir = "f", string? searchTerm = null, LocalRoomQueryFilter? localFilter = null) {
             AdminRoomListingResult? res = null;
@@ -83,7 +26,7 @@ public class AuthenticatedHomeServer : IHomeServer {
 
                 Console.WriteLine($"--- ADMIN Querying Room List with URL: {url} - Already have {i} items... ---");
 
-                res = await _authenticatedHomeServer._httpClient.GetFromJsonAsync<AdminRoomListingResult>(url);
+                res = await _authenticatedHomeserver._httpClient.GetFromJsonAsync<AdminRoomListingResult>(url);
                 totalRooms ??= res?.TotalRooms;
                 Console.WriteLine(res.ToJson(false));
                 foreach (var room in res.Rooms) {
@@ -160,14 +103,8 @@ public class AuthenticatedHomeServer : IHomeServer {
             } while (i < Math.Min(limit, totalRooms ?? limit));
         }
     }
-}
 
-public class WhoAmIResponse {
-    [JsonPropertyName("user_id")]
-    public string UserId { get; set; } = null!;
-
-    [JsonPropertyName("device_id")]
-    public string? DeviceId { get; set; }
-    [JsonPropertyName("is_guest")]
-    public bool? IsGuest { get; set; }
+    public AuthenticatedHomeserverSynapse(TieredStorageService storage, string canonicalHomeServerDomain, string accessToken) : base(storage, canonicalHomeServerDomain, accessToken) {
+        Admin = new(this);
+    }
 }
diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs
new file mode 100644
index 0000000..fc31f4f
--- /dev/null
+++ b/LibMatrix/Homeservers/RemoteHomeServer.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using LibMatrix.Extensions;
+using LibMatrix.Responses;
+using LibMatrix.StateEventTypes.Spec;
+
+namespace LibMatrix.Homeservers;
+
+public class RemoteHomeServer {
+    public RemoteHomeServer(string canonicalHomeServerDomain) {
+        HomeServerDomain = canonicalHomeServerDomain;
+        _httpClient = new MatrixHttpClient();
+        _httpClient.Timeout = TimeSpan.FromSeconds(5);
+    }
+
+    private Dictionary<string, object> _profileCache { get; set; } = new();
+    public string HomeServerDomain { get; set; }
+    public string FullHomeServerDomain { get; set; }
+    public MatrixHttpClient _httpClient { get; set; }
+
+    public async Task<ProfileResponseEventData> GetProfile(string mxid) {
+        if(mxid is null) throw new ArgumentNullException(nameof(mxid));
+        if (_profileCache.TryGetValue(mxid, out var value)) {
+            if (value is SemaphoreSlim s) await s.WaitAsync();
+            if (value is ProfileResponseEventData p) return p;
+        }
+        _profileCache[mxid] = new SemaphoreSlim(1);
+
+        var resp = await _httpClient.GetAsync($"/_matrix/client/v3/profile/{mxid}");
+        var data = await resp.Content.ReadFromJsonAsync<ProfileResponseEventData>();
+        if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data);
+        _profileCache[mxid] = data;
+
+        return data;
+    }
+
+    public async Task<ClientVersionsResponse> GetClientVersions() {
+        var resp = await _httpClient.GetAsync($"/_matrix/client/versions");
+        var data = await resp.Content.ReadFromJsonAsync<ClientVersionsResponse>();
+        if (!resp.IsSuccessStatusCode) Console.WriteLine("ClientVersions: " + data);
+        return data;
+    }
+}
diff --git a/LibMatrix/Interfaces/IHomeServer.cs b/LibMatrix/Interfaces/IHomeServer.cs
deleted file mode 100644
index 5e7e374..0000000
--- a/LibMatrix/Interfaces/IHomeServer.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using System.Net.Http.Json;
-using LibMatrix.Extensions;
-using LibMatrix.StateEventTypes.Spec;
-
-namespace LibMatrix.Interfaces;
-
-public class IHomeServer {
-    private readonly Dictionary<string, object> _profileCache = new();
-    public string HomeServerDomain { get; set; }
-    public string FullHomeServerDomain { get; set; }
-
-    public MatrixHttpClient _httpClient { get; set; } = new();
-
-    public async Task<ProfileResponseEventData> GetProfile(string mxid) {
-        if(mxid is null) throw new ArgumentNullException(nameof(mxid));
-        if (_profileCache.ContainsKey(mxid)) {
-            if (_profileCache[mxid] is SemaphoreSlim s) await s.WaitAsync();
-            if (_profileCache[mxid] is ProfileResponseEventData p) return p;
-        }
-        _profileCache[mxid] = new SemaphoreSlim(1);
-
-        var resp = await _httpClient.GetAsync($"/_matrix/client/v3/profile/{mxid}");
-        var data = await resp.Content.ReadFromJsonAsync<ProfileResponseEventData>();
-        if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data);
-        _profileCache[mxid] = data;
-
-        return data;
-    }
-}
diff --git a/LibMatrix/Interfaces/IStateEventType.cs b/LibMatrix/Interfaces/IStateEventType.cs
index d80f22d..13a0d05 100644
--- a/LibMatrix/Interfaces/IStateEventType.cs
+++ b/LibMatrix/Interfaces/IStateEventType.cs
@@ -1,5 +1,3 @@
 namespace LibMatrix.Interfaces;
 
-public interface IStateEventType {
-
-}
+public interface IStateEventType { }
diff --git a/LibMatrix/Interfaces/Services/IStorageProvider.cs b/LibMatrix/Interfaces/Services/IStorageProvider.cs
index 519d8ed..e07e136 100644
--- a/LibMatrix/Interfaces/Services/IStorageProvider.cs
+++ b/LibMatrix/Interfaces/Services/IStorageProvider.cs
@@ -1,3 +1,8 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+
 namespace LibMatrix.Interfaces.Services;
 
 public interface IStorageProvider {
diff --git a/LibMatrix/LibMatrix.csproj b/LibMatrix/LibMatrix.csproj
index 3571eab..8ae57cc 100644
--- a/LibMatrix/LibMatrix.csproj
+++ b/LibMatrix/LibMatrix.csproj
@@ -11,4 +11,8 @@
         <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
     </ItemGroup>
 
+    <ItemGroup>
+      <ProjectReference Include="..\..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" />
+    </ItemGroup>
+
 </Project>
diff --git a/LibMatrix/MatrixException.cs b/LibMatrix/MatrixException.cs
index 99bacb5..1b38a6e 100644
--- a/LibMatrix/MatrixException.cs
+++ b/LibMatrix/MatrixException.cs
@@ -1,4 +1,6 @@
+using System;
 using System.Text.Json.Serialization;
+using ArcaneLibs.Extensions;
 using LibMatrix.Extensions;
 
 namespace LibMatrix;
diff --git a/LibMatrix/MessagesResponse.cs b/LibMatrix/MessagesResponse.cs
index f09d136..d7bb54a 100644
--- a/LibMatrix/MessagesResponse.cs
+++ b/LibMatrix/MessagesResponse.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
 using System.Text.Json.Serialization;
 using LibMatrix.Responses;
 
diff --git a/LibMatrix/RemoteHomeServer.cs b/LibMatrix/RemoteHomeServer.cs
deleted file mode 100644
index 81ef9a7..0000000
--- a/LibMatrix/RemoteHomeServer.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using LibMatrix.Extensions;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix;
-
-public class RemoteHomeServer : IHomeServer {
-    public RemoteHomeServer(string canonicalHomeServerDomain) {
-        HomeServerDomain = canonicalHomeServerDomain;
-        _httpClient = new MatrixHttpClient();
-        _httpClient.Timeout = TimeSpan.FromSeconds(5);
-    }
-
-}
diff --git a/LibMatrix/Responses/Admin/AdminRoomListingResult.cs b/LibMatrix/Responses/Admin/AdminRoomListingResult.cs
index f035184..bbc23e6 100644
--- a/LibMatrix/Responses/Admin/AdminRoomListingResult.cs
+++ b/LibMatrix/Responses/Admin/AdminRoomListingResult.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
 using System.Text.Json.Serialization;
 
 namespace LibMatrix.Responses.Admin;
diff --git a/LibMatrix/Responses/ClientVersionsResponse.cs b/LibMatrix/Responses/ClientVersionsResponse.cs
new file mode 100644
index 0000000..7fac565
--- /dev/null
+++ b/LibMatrix/Responses/ClientVersionsResponse.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LibMatrix.Responses;
+
+public class ClientVersionsResponse {
+    [JsonPropertyName("versions")]
+    public List<string> Versions { get; set; } = new();
+
+    [JsonPropertyName("unstable_features")]
+    public Dictionary<string, bool> UnstableFeatures { get; set; } = new();
+}
diff --git a/LibMatrix/Responses/CreateRoomRequest.cs b/LibMatrix/Responses/CreateRoomRequest.cs
index d59e6fd..c1c1697 100644
--- a/LibMatrix/Responses/CreateRoomRequest.cs
+++ b/LibMatrix/Responses/CreateRoomRequest.cs
@@ -1,8 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
 using System.Reflection;
 using System.Text.Json.Nodes;
 using System.Text.Json.Serialization;
 using System.Text.RegularExpressions;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.StateEventTypes.Spec;
 
 namespace LibMatrix.Responses;
diff --git a/LibMatrix/Responses/LoginResponse.cs b/LibMatrix/Responses/LoginResponse.cs
index 2800a9c..175f337 100644
--- a/LibMatrix/Responses/LoginResponse.cs
+++ b/LibMatrix/Responses/LoginResponse.cs
@@ -15,3 +15,24 @@ public class LoginResponse {
     [JsonPropertyName("user_id")]
     public string UserId { get; set; }
 }
+public class LoginRequest {
+    [JsonPropertyName("type")]
+    public string Type { get; set; } = "m.login.password";
+
+    [JsonPropertyName("identifier")]
+    public LoginIdentifier Identifier { get; set; } = new();
+
+    [JsonPropertyName("password")]
+    public string Password { get; set; } = "";
+
+    [JsonPropertyName("initial_device_display_name")]
+    public string InitialDeviceDisplayName { get; set; } = "Rory&::LibMatrix";
+
+    public class LoginIdentifier {
+        [JsonPropertyName("type")]
+        public string Type { get; set; } = "m.id.user";
+
+        [JsonPropertyName("user")]
+        public string User { get; set; } = "";
+    }
+}
diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index df1eb52..3ba965b 100644
--- a/LibMatrix/RoomTypes/GenericRoom.cs
+++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -1,22 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net.Http;
 using System.Net.Http.Json;
 using System.Text.Json;
+using System.Threading.Tasks;
 using System.Web;
 using LibMatrix.Extensions;
+using LibMatrix.Homeservers;
 using LibMatrix.Responses;
 using LibMatrix.StateEventTypes.Spec;
 
 namespace LibMatrix.RoomTypes;
 
 public class GenericRoom {
-    internal readonly AuthenticatedHomeServer _homeServer;
+    internal readonly AuthenticatedHomeserverGeneric Homeserver;
     internal readonly MatrixHttpClient _httpClient;
 
-    public GenericRoom(AuthenticatedHomeServer homeServer, string roomId) {
-        _homeServer = homeServer;
-        _httpClient = homeServer._httpClient;
+    public GenericRoom(AuthenticatedHomeserverGeneric homeserver, string roomId) {
+        Homeserver = homeserver;
+        _httpClient = homeserver._httpClient;
         RoomId = roomId;
         if (GetType() != typeof(SpaceRoom))
-            AsSpace = new SpaceRoom(homeServer, RoomId);
+            AsSpace = new SpaceRoom(homeserver, RoomId);
     }
 
     public string RoomId { get; set; }
@@ -182,5 +188,23 @@ public class GenericRoom {
         return res;
     }
 
+    public async Task<T> GetRoomAccountData<T>(string key) {
+        var res = await _httpClient.GetAsync($"/_matrix/client/v3/user/{Homeserver.UserId}/rooms/{RoomId}/account_data/{key}");
+        if (!res.IsSuccessStatusCode) {
+            Console.WriteLine($"Failed to get room account data: {await res.Content.ReadAsStringAsync()}");
+            throw new InvalidDataException($"Failed to get room account data: {await res.Content.ReadAsStringAsync()}");
+        }
+
+        return await res.Content.ReadFromJsonAsync<T>();
+    }
+
+    public async Task SetRoomAccountData(string key, object data) {
+        var res = await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/user/{Homeserver.UserId}/rooms/{RoomId}/account_data/{key}", data);
+        if (!res.IsSuccessStatusCode) {
+            Console.WriteLine($"Failed to set room account data: {await res.Content.ReadAsStringAsync()}");
+            throw new InvalidDataException($"Failed to set room account data: {await res.Content.ReadAsStringAsync()}");
+        }
+    }
+
     public readonly SpaceRoom AsSpace;
 }
diff --git a/LibMatrix/RoomTypes/SpaceRoom.cs b/LibMatrix/RoomTypes/SpaceRoom.cs
index 5393ee7..017a123 100644
--- a/LibMatrix/RoomTypes/SpaceRoom.cs
+++ b/LibMatrix/RoomTypes/SpaceRoom.cs
@@ -1,13 +1,17 @@
+using System.Collections.Generic;
+using System.Threading;
+using ArcaneLibs.Extensions;
 using LibMatrix.Extensions;
+using LibMatrix.Homeservers;
 
 namespace LibMatrix.RoomTypes;
 
 public class SpaceRoom : GenericRoom {
-    private new readonly AuthenticatedHomeServer _homeServer;
+    private new readonly AuthenticatedHomeserverGeneric _homeserver;
     private readonly GenericRoom _room;
 
-    public SpaceRoom(AuthenticatedHomeServer homeServer, string roomId) : base(homeServer, roomId) {
-        _homeServer = homeServer;
+    public SpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomId) : base(homeserver, roomId) {
+        _homeserver = homeserver;
     }
 
     private static SemaphoreSlim _semaphore = new(1, 1);
@@ -18,7 +22,7 @@ public class SpaceRoom : GenericRoom {
         await foreach (var stateEvent in state) {
             if (stateEvent.Type != "m.space.child") continue;
             if (stateEvent.RawContent.ToJson() != "{}" || includeRemoved)
-                yield return await _homeServer.GetRoom(stateEvent.StateKey);
+                yield return await _homeserver.GetRoom(stateEvent.StateKey);
         }
         _semaphore.Release();
     }
diff --git a/LibMatrix/Services/HomeserverProviderService.cs b/LibMatrix/Services/HomeserverProviderService.cs
index 366f0ca..776c7eb 100644
--- a/LibMatrix/Services/HomeserverProviderService.cs
+++ b/LibMatrix/Services/HomeserverProviderService.cs
@@ -1,7 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
 using System.Net.Http.Headers;
 using System.Net.Http.Json;
 using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+using ArcaneLibs.Extensions;
 using LibMatrix.Extensions;
+using LibMatrix.Homeservers;
 using LibMatrix.Responses;
 using Microsoft.Extensions.Logging;
 
@@ -22,46 +29,53 @@ public class HomeserverProviderService {
     }
 
     private static Dictionary<string, SemaphoreSlim> _authenticatedHomeserverSemaphore = new();
-    private static Dictionary<string, AuthenticatedHomeServer> _authenticatedHomeServerCache = new();
+    private static Dictionary<string, AuthenticatedHomeserverGeneric> _authenticatedHomeServerCache = new();
 
-    public async Task<AuthenticatedHomeServer> GetAuthenticatedWithToken(string homeserver, string accessToken,
-        string? overrideFullDomain = null) {
-        var sem = _authenticatedHomeserverSemaphore.GetOrCreate(homeserver+accessToken, _ => new SemaphoreSlim(1, 1));
+    public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedWithToken(string homeserver, string accessToken,
+        string? proxy = null) {
+        var sem = _authenticatedHomeserverSemaphore.GetOrCreate(homeserver + accessToken, _ => new SemaphoreSlim(1, 1));
         await sem.WaitAsync();
-        if (_authenticatedHomeServerCache.ContainsKey(homeserver+accessToken)) {
+        if (_authenticatedHomeServerCache.ContainsKey(homeserver + accessToken)) {
             sem.Release();
-            return _authenticatedHomeServerCache[homeserver+accessToken];
+            return _authenticatedHomeServerCache[homeserver + accessToken];
         }
 
-        var hs = new AuthenticatedHomeServer(_tieredStorageService, homeserver, accessToken);
-        hs.FullHomeServerDomain = overrideFullDomain ??
-                                  await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver);
-        hs._httpClient.Dispose();
-        hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.FullHomeServerDomain) };
-        hs._httpClient.Timeout = TimeSpan.FromSeconds(120);
+        var domain = proxy ?? await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver);
+        var hc = new MatrixHttpClient { BaseAddress = new Uri(domain) };
+
+        AuthenticatedHomeserverGeneric hs;
+        if (true) {
+            hs = new AuthenticatedHomeserverMxApiExtended(_tieredStorageService, homeserver, accessToken);
+        }
+        else {
+            hs = new AuthenticatedHomeserverGeneric(_tieredStorageService, homeserver, accessToken);
+        }
+
+        hs.FullHomeServerDomain = domain;
+        hs._httpClient = hc;
+        hs._httpClient.Timeout = TimeSpan.FromMinutes(15);
         hs._httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
 
         hs.WhoAmI = (await hs._httpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami"))!;
 
-        _authenticatedHomeServerCache[homeserver+accessToken] = hs;
+        lock(_authenticatedHomeServerCache)
+            _authenticatedHomeServerCache[homeserver + accessToken] = hs;
         sem.Release();
 
         return hs;
     }
 
-    public async Task<RemoteHomeServer> GetRemoteHomeserver(string homeserver, string? overrideFullDomain = null) {
+    public async Task<RemoteHomeServer> GetRemoteHomeserver(string homeserver, string? proxy = null) {
         var hs = new RemoteHomeServer(homeserver);
-        hs.FullHomeServerDomain = overrideFullDomain ??
-                                  await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver);
+        hs.FullHomeServerDomain = proxy ?? await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver);
         hs._httpClient.Dispose();
         hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.FullHomeServerDomain) };
         hs._httpClient.Timeout = TimeSpan.FromSeconds(120);
         return hs;
     }
 
-    public async Task<LoginResponse> Login(string homeserver, string user, string password,
-        string? overrideFullDomain = null) {
-        var hs = await GetRemoteHomeserver(homeserver, overrideFullDomain);
+    public async Task<LoginResponse> Login(string homeserver, string user, string password, string? proxy = null) {
+        var hs = await GetRemoteHomeserver(homeserver, proxy);
         var payload = new LoginRequest {
             Identifier = new LoginRequest.LoginIdentifier { User = user },
             Password = password
@@ -70,26 +84,4 @@ public class HomeserverProviderService {
         var data = await resp.Content.ReadFromJsonAsync<LoginResponse>();
         return data!;
     }
-
-    private class LoginRequest {
-        [JsonPropertyName("type")]
-        public string Type { get; set; } = "m.login.password";
-
-        [JsonPropertyName("identifier")]
-        public LoginIdentifier Identifier { get; set; } = new();
-
-        [JsonPropertyName("password")]
-        public string Password { get; set; } = "";
-
-        [JsonPropertyName("initial_device_display_name")]
-        public string InitialDeviceDisplayName { get; set; } = "Rory&::LibMatrix";
-
-        public class LoginIdentifier {
-            [JsonPropertyName("type")]
-            public string Type { get; set; } = "m.id.user";
-
-            [JsonPropertyName("user")]
-            public string User { get; set; } = "";
-        }
-    }
 }
diff --git a/LibMatrix/Services/HomeserverResolverService.cs b/LibMatrix/Services/HomeserverResolverService.cs
index 78c5c37..dcd0fe9 100644
--- a/LibMatrix/Services/HomeserverResolverService.cs
+++ b/LibMatrix/Services/HomeserverResolverService.cs
@@ -1,4 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
 using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using ArcaneLibs.Extensions;
 using LibMatrix.Extensions;
 using Microsoft.Extensions.Logging;
 
@@ -26,9 +32,9 @@ public class HomeserverResolverService {
         if (homeserver is null) throw new ArgumentNullException(nameof(homeserver));
         var sem = _wellKnownSemaphores.GetOrCreate(homeserver, _ => new SemaphoreSlim(1, 1));
         await sem.WaitAsync();
-        if (_wellKnownCache.ContainsKey(homeserver)) {
+        if (_wellKnownCache.TryGetValue(homeserver, out var known)) {
             sem.Release();
-            return _wellKnownCache[homeserver];
+            return known;
         }
 
         string? result = null;
diff --git a/LibMatrix/Services/ServiceInstaller.cs b/LibMatrix/Services/ServiceInstaller.cs
index b1c98e1..9c4cdb9 100644
--- a/LibMatrix/Services/ServiceInstaller.cs
+++ b/LibMatrix/Services/ServiceInstaller.cs
@@ -1,3 +1,5 @@
+using System;
+using System.Linq;
 using Microsoft.Extensions.DependencyInjection;
 
 namespace LibMatrix.Services;
diff --git a/LibMatrix/StateEvent.cs b/LibMatrix/StateEvent.cs
index a2f951b..26dc39a 100644
--- a/LibMatrix/StateEvent.cs
+++ b/LibMatrix/StateEvent.cs
@@ -1,8 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
 using System.Reflection;
 using System.Text.Json;
 using System.Text.Json.Nodes;
 using System.Text.Json.Serialization;
+using ArcaneLibs;
+using ArcaneLibs.Extensions;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix;
@@ -103,6 +109,7 @@ public class StateEvent {
     }
 
     //debug
+    [JsonIgnore]
     public string dtype {
         get {
             var res = GetType().Name switch {
@@ -113,5 +120,6 @@ public class StateEvent {
         }
     }
 
+    [JsonIgnore]
     public string cdtype => TypedContent.GetType().Name;
 }
diff --git a/LibMatrix/StateEventTypes/Common/MjolnirShortcodeEventData.cs b/LibMatrix/StateEventTypes/Common/MjolnirShortcodeEventData.cs
index 808a0db..c3fee34 100644
--- a/LibMatrix/StateEventTypes/Common/MjolnirShortcodeEventData.cs
+++ b/LibMatrix/StateEventTypes/Common/MjolnirShortcodeEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Common;
diff --git a/LibMatrix/StateEventTypes/Common/RoomEmotesEventData.cs b/LibMatrix/StateEventTypes/Common/RoomEmotesEventData.cs
index af1c09e..754a9dc 100644
--- a/LibMatrix/StateEventTypes/Common/RoomEmotesEventData.cs
+++ b/LibMatrix/StateEventTypes/Common/RoomEmotesEventData.cs
@@ -1,5 +1,7 @@
+using System.Collections.Generic;
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Common;
diff --git a/LibMatrix/StateEventTypes/Spec/CanonicalAliasEventData.cs b/LibMatrix/StateEventTypes/Spec/CanonicalAliasEventData.cs
index 36cc90e..384ca43 100644
--- a/LibMatrix/StateEventTypes/Spec/CanonicalAliasEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/CanonicalAliasEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/GuestAccessEventData.cs b/LibMatrix/StateEventTypes/Spec/GuestAccessEventData.cs
index b6ddd93..eadba67 100644
--- a/LibMatrix/StateEventTypes/Spec/GuestAccessEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/GuestAccessEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/HistoryVisibilityEventData.cs b/LibMatrix/StateEventTypes/Spec/HistoryVisibilityEventData.cs
index 8836fc0..1c73346 100644
--- a/LibMatrix/StateEventTypes/Spec/HistoryVisibilityEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/HistoryVisibilityEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/JoinRulesEventData.cs b/LibMatrix/StateEventTypes/Spec/JoinRulesEventData.cs
index 0393395..d3da559 100644
--- a/LibMatrix/StateEventTypes/Spec/JoinRulesEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/JoinRulesEventData.cs
@@ -1,5 +1,7 @@
+using System.Collections.Generic;
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
@@ -14,5 +16,13 @@ public class JoinRulesEventData : IStateEventType {
     public string JoinRule { get; set; }
 
     [JsonPropertyName("allow")]
-    public List<string> Allow { get; set; }
+    public List<AllowEntry> Allow { get; set; }
+
+    public class AllowEntry {
+        [JsonPropertyName("type")]
+        public string Type { get; set; }
+
+        [JsonPropertyName("room_id")]
+        public string RoomId { get; set; }
+    }
 }
diff --git a/LibMatrix/StateEventTypes/Spec/PolicyRuleStateEventData.cs b/LibMatrix/StateEventTypes/Spec/PolicyRuleStateEventData.cs
index 963864f..c0aed9e 100644
--- a/LibMatrix/StateEventTypes/Spec/PolicyRuleStateEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/PolicyRuleStateEventData.cs
@@ -1,5 +1,7 @@
+using System;
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/PresenceStateEventData.cs b/LibMatrix/StateEventTypes/Spec/PresenceStateEventData.cs
index fa75a88..c5a95ae 100644
--- a/LibMatrix/StateEventTypes/Spec/PresenceStateEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/PresenceStateEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
@@ -14,4 +15,8 @@ public class PresenceStateEventData : IStateEventType {
     public bool CurrentlyActive { get; set; }
     [JsonPropertyName("status_msg")]
     public string StatusMessage { get; set; }
+    [JsonPropertyName("avatar_url")]
+    public string AvatarUrl { get; set; }
+    [JsonPropertyName("displayname")]
+    public string DisplayName { get; set; }
 }
diff --git a/LibMatrix/StateEventTypes/Spec/ProfileResponseEventData.cs b/LibMatrix/StateEventTypes/Spec/ProfileResponseEventData.cs
index d2340f5..14449e0 100644
--- a/LibMatrix/StateEventTypes/Spec/ProfileResponseEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/ProfileResponseEventData.cs
@@ -5,8 +5,8 @@ namespace LibMatrix.StateEventTypes.Spec;
 
 public class ProfileResponseEventData : IStateEventType {
     [JsonPropertyName("avatar_url")]
-    public string? AvatarUrl { get; set; } = "";
+    public string? AvatarUrl { get; set; }
 
     [JsonPropertyName("displayname")]
-    public string? DisplayName { get; set; } = "";
+    public string? DisplayName { get; set; }
 }
diff --git a/LibMatrix/StateEventTypes/Spec/RoomAliasEventData.cs b/LibMatrix/StateEventTypes/Spec/RoomAliasEventData.cs
index 8d921b2..df80a08 100644
--- a/LibMatrix/StateEventTypes/Spec/RoomAliasEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/RoomAliasEventData.cs
@@ -1,5 +1,7 @@
+using System.Collections.Generic;
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/RoomAvatarEventData.cs b/LibMatrix/StateEventTypes/Spec/RoomAvatarEventData.cs
index cbe41dd..4d3fabf 100644
--- a/LibMatrix/StateEventTypes/Spec/RoomAvatarEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/RoomAvatarEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/RoomCreateEventData.cs b/LibMatrix/StateEventTypes/Spec/RoomCreateEventData.cs
index b96c31e..0b1bd5c 100644
--- a/LibMatrix/StateEventTypes/Spec/RoomCreateEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/RoomCreateEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/RoomEncryptionEventData.cs b/LibMatrix/StateEventTypes/Spec/RoomEncryptionEventData.cs
index e16716e..126117d 100644
--- a/LibMatrix/StateEventTypes/Spec/RoomEncryptionEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/RoomEncryptionEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/RoomMemberEventData.cs b/LibMatrix/StateEventTypes/Spec/RoomMemberEventData.cs
index 623c43c..7c181ae 100644
--- a/LibMatrix/StateEventTypes/Spec/RoomMemberEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/RoomMemberEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/RoomMessageEventData.cs b/LibMatrix/StateEventTypes/Spec/RoomMessageEventData.cs
index 14dd67a..5d65237 100644
--- a/LibMatrix/StateEventTypes/Spec/RoomMessageEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/RoomMessageEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/RoomNameEventData.cs b/LibMatrix/StateEventTypes/Spec/RoomNameEventData.cs
index 9d13513..2245793 100644
--- a/LibMatrix/StateEventTypes/Spec/RoomNameEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/RoomNameEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/RoomPinnedEventData.cs b/LibMatrix/StateEventTypes/Spec/RoomPinnedEventData.cs
index c7d29fa..10ef3f5 100644
--- a/LibMatrix/StateEventTypes/Spec/RoomPinnedEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/RoomPinnedEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/RoomPowerLevelEventData.cs b/LibMatrix/StateEventTypes/Spec/RoomPowerLevelEventData.cs
index c5dda78..3c985f6 100644
--- a/LibMatrix/StateEventTypes/Spec/RoomPowerLevelEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/RoomPowerLevelEventData.cs
@@ -1,5 +1,9 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
@@ -7,43 +11,43 @@ namespace LibMatrix.StateEventTypes.Spec;
 [MatrixEvent(EventName = "m.room.power_levels")]
 public class RoomPowerLevelEventData : IStateEventType {
     [JsonPropertyName("ban")]
-    public int Ban { get; set; } // = 50;
+    public long Ban { get; set; } // = 50;
 
     [JsonPropertyName("events_default")]
-    public int EventsDefault { get; set; } // = 0;
+    public long EventsDefault { get; set; } // = 0;
 
     [JsonPropertyName("events")]
-    public Dictionary<string, int> Events { get; set; } // = null!;
+    public Dictionary<string, long> Events { get; set; } // = null!;
 
     [JsonPropertyName("invite")]
-    public int Invite { get; set; } // = 50;
+    public long Invite { get; set; } // = 50;
 
     [JsonPropertyName("kick")]
-    public int Kick { get; set; } // = 50;
+    public long Kick { get; set; } // = 50;
 
     [JsonPropertyName("notifications")]
     public NotificationsPL NotificationsPl { get; set; } // = null!;
 
     [JsonPropertyName("redact")]
-    public int Redact { get; set; } // = 50;
+    public long Redact { get; set; } // = 50;
 
     [JsonPropertyName("state_default")]
-    public int StateDefault { get; set; } // = 50;
+    public long StateDefault { get; set; } // = 50;
 
     [JsonPropertyName("users")]
-    public Dictionary<string, int> Users { get; set; } // = null!;
+    public Dictionary<string, long> Users { get; set; } // = null!;
 
     [JsonPropertyName("users_default")]
-    public int UsersDefault { get; set; } // = 0;
+    public long UsersDefault { get; set; } // = 0;
 
     [Obsolete("Historical was a key related to MSC2716, a spec change on backfill that was dropped!", true)]
     [JsonIgnore]
     [JsonPropertyName("historical")]
-    public int Historical { get; set; } // = 50;
+    public long Historical { get; set; } // = 50;
 
     public class NotificationsPL {
         [JsonPropertyName("room")]
-        public int Room { get; set; } = 50;
+        public long Room { get; set; } = 50;
     }
 
     public bool IsUserAdmin(string userId) {
diff --git a/LibMatrix/StateEventTypes/Spec/RoomTopicEventData.cs b/LibMatrix/StateEventTypes/Spec/RoomTopicEventData.cs
index 0fd0df6..eaf9e8c 100644
--- a/LibMatrix/StateEventTypes/Spec/RoomTopicEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/RoomTopicEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/RoomTypingEventData.cs b/LibMatrix/StateEventTypes/Spec/RoomTypingEventData.cs
index 857338c..cebb238 100644
--- a/LibMatrix/StateEventTypes/Spec/RoomTypingEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/RoomTypingEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/ServerACLEventData.cs b/LibMatrix/StateEventTypes/Spec/ServerACLEventData.cs
index 68bbe6b..a258707 100644
--- a/LibMatrix/StateEventTypes/Spec/ServerACLEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/ServerACLEventData.cs
@@ -1,5 +1,7 @@
+using System.Collections.Generic;
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/SpaceChildEventData.cs b/LibMatrix/StateEventTypes/Spec/SpaceChildEventData.cs
index a55e941..5ccab88 100644
--- a/LibMatrix/StateEventTypes/Spec/SpaceChildEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/SpaceChildEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/StateEventTypes/Spec/SpaceParentEventData.cs b/LibMatrix/StateEventTypes/Spec/SpaceParentEventData.cs
index 7dc7f4c..6477290 100644
--- a/LibMatrix/StateEventTypes/Spec/SpaceParentEventData.cs
+++ b/LibMatrix/StateEventTypes/Spec/SpaceParentEventData.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix.Extensions;
+using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace LibMatrix.StateEventTypes.Spec;
diff --git a/LibMatrix/WhoAmIResponse.cs b/LibMatrix/WhoAmIResponse.cs
new file mode 100644
index 0000000..f461c9b
--- /dev/null
+++ b/LibMatrix/WhoAmIResponse.cs
@@ -0,0 +1,13 @@
+using System.Text.Json.Serialization;
+
+namespace LibMatrix;
+
+public class WhoAmIResponse {
+    [JsonPropertyName("user_id")]
+    public string UserId { get; set; } = null!;
+
+    [JsonPropertyName("device_id")]
+    public string? DeviceId { get; set; }
+    [JsonPropertyName("is_guest")]
+    public bool? IsGuest { get; set; }
+}
\ No newline at end of file