about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ExampleBots/ModerationBot/Commands/DbgDumpAllStateTypesCommand.cs4
-rw-r--r--ExampleBots/ModerationBot/ModerationBot.cs6
-rw-r--r--ExampleBots/ModerationBot/PolicyEngine.cs8
-rw-r--r--ExampleBots/PluralContactBotPoC/Bot/Commands/CreateSystemCommand.cs2
-rw-r--r--LibMatrix/Extensions/HttpClientExtensions.cs21
-rw-r--r--LibMatrix/Helpers/SyncHelper.cs7
-rw-r--r--LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs38
-rw-r--r--LibMatrix/Homeservers/RemoteHomeServer.cs4
-rw-r--r--LibMatrix/Responses/SyncResponse.cs6
-rw-r--r--LibMatrix/RoomTypes/GenericRoom.cs6
-rw-r--r--LibMatrix/Services/HomeserverProviderService.cs38
-rw-r--r--LibMatrix/Services/ServiceInstaller.cs14
-rw-r--r--LibMatrix/StateEvent.cs5
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests.cs11
14 files changed, 93 insertions, 77 deletions
diff --git a/ExampleBots/ModerationBot/Commands/DbgDumpAllStateTypesCommand.cs b/ExampleBots/ModerationBot/Commands/DbgDumpAllStateTypesCommand.cs
index 3727877..7563cac 100644
--- a/ExampleBots/ModerationBot/Commands/DbgDumpAllStateTypesCommand.cs
+++ b/ExampleBots/ModerationBot/Commands/DbgDumpAllStateTypesCommand.cs
@@ -59,8 +59,8 @@ public class DbgDumpAllStateTypesCommand
         string raw = "Count | State type | Mapped type", html = "<table><tr><th>Count</th><th>State type</th><th>Mapped type</th></tr>";
         var groupedStates = states.GroupBy(x => x.Type).ToDictionary(x => x.Key, x => x.ToList()).OrderByDescending(x => x.Value.Count);
         foreach (var (type, stateGroup) in groupedStates) {
-            raw += $"{stateGroup.Count} | {type} | {stateGroup[0].GetType.Name}";
-            html += $"<tr><td>{stateGroup.Count}</td><td>{type}</td><td>{stateGroup[0].GetType.Name}</td></tr>";
+            raw += $"{stateGroup.Count} | {type} | {StateEvent.GetStateEventType(stateGroup[0].Type).Name}";
+            html += $"<tr><td>{stateGroup.Count}</td><td>{type}</td><td>{StateEvent.GetStateEventType(stateGroup[0].Type).Name}</td></tr>";
         }
 
         html += "</table>";
diff --git a/ExampleBots/ModerationBot/ModerationBot.cs b/ExampleBots/ModerationBot/ModerationBot.cs
index 1be7bd5..7c95229 100644
--- a/ExampleBots/ModerationBot/ModerationBot.cs
+++ b/ExampleBots/ModerationBot/ModerationBot.cs
@@ -70,7 +70,7 @@ public class ModerationBot(AuthenticatedHomeserverGeneric hs, ILogger<Moderation
 #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
         Task.Run(async () => {
             while (!cancellationToken.IsCancellationRequested) {
-                var controlRoomMembers = _controlRoom.GetMembersAsync();
+                var controlRoomMembers = _controlRoom.GetMembersEnumerableAsync();
                 var pls = await _controlRoom.GetPowerLevelsAsync();
                 await foreach (var member in controlRoomMembers) {
                     if ((member.TypedContent as RoomMemberEventContent)?
@@ -110,8 +110,8 @@ public class ModerationBot(AuthenticatedHomeserverGeneric hs, ILogger<Moderation
                     "Got timeline event in {}: {}", @event.RoomId, @event.ToJson(indent: true, ignoreNull: true));
 
                 if (@event != null && (
-                        @event.GetType.IsAssignableTo(typeof(BasePolicy))
-                        || @event.GetType.IsAssignableTo(typeof(PolicyRuleEventContent))
+                        @event.MappedType.IsAssignableTo(typeof(BasePolicy))
+                        || @event.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))
                     ))
                     await engine.ReloadActivePolicyListById(@event.RoomId);
 
diff --git a/ExampleBots/ModerationBot/PolicyEngine.cs b/ExampleBots/ModerationBot/PolicyEngine.cs
index 5af99ac..114b90d 100644
--- a/ExampleBots/ModerationBot/PolicyEngine.cs
+++ b/ExampleBots/ModerationBot/PolicyEngine.cs
@@ -85,8 +85,8 @@ public class PolicyEngine(AuthenticatedHomeserverGeneric hs, ILogger<ModerationB
         var stateEvents = room.GetFullStateAsync();
         await foreach (var stateEvent in stateEvents) {
             if (stateEvent != null && (
-                    stateEvent.GetType.IsAssignableTo(typeof(BasePolicy))
-                    || stateEvent.GetType.IsAssignableTo(typeof(PolicyRuleEventContent))
+                    stateEvent.MappedType.IsAssignableTo(typeof(BasePolicy))
+                    || stateEvent.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))
                 )) {
                 policyList.Policies.Add(stateEvent);
             }
@@ -253,8 +253,8 @@ public class PolicyEngine(AuthenticatedHomeserverGeneric hs, ILogger<ModerationB
         string raw = "Count | State type | Mapped type", html = "<table><tr><th>Count</th><th>State type</th><th>Mapped type</th></tr>";
         var groupedStates = states.GroupBy(x => x.Type).ToDictionary(x => x.Key, x => x.ToList()).OrderByDescending(x => x.Value.Count);
         foreach (var (type, stateGroup) in groupedStates) {
-            raw += $"{stateGroup.Count} | {type} | {stateGroup[0].GetType.Name}";
-            html += $"<tr><td>{stateGroup.Count}</td><td>{type}</td><td>{stateGroup[0].GetType.Name}</td></tr>";
+            raw += $"{stateGroup.Count} | {type} | {stateGroup[0].MappedType.Name}";
+            html += $"<tr><td>{stateGroup.Count}</td><td>{type}</td><td>{stateGroup[0].MappedType.Name}</td></tr>";
         }
 
         html += "</table>";
diff --git a/ExampleBots/PluralContactBotPoC/Bot/Commands/CreateSystemCommand.cs b/ExampleBots/PluralContactBotPoC/Bot/Commands/CreateSystemCommand.cs
index 6d2cb3a..0bb3265 100644
--- a/ExampleBots/PluralContactBotPoC/Bot/Commands/CreateSystemCommand.cs
+++ b/ExampleBots/PluralContactBotPoC/Bot/Commands/CreateSystemCommand.cs
@@ -35,7 +35,7 @@ public class CreateSystemCommand(IServiceProvider services, HomeserverProviderSe
                         Members = new(),
                     };
 
-                    var state = ctx.Room.GetMembersAsync();
+                    var state = ctx.Room.GetMembersEnumerableAsync();
                     await foreach (var member in state) {
                         sysData.Members.Add(member.StateKey);
                     }
diff --git a/LibMatrix/Extensions/HttpClientExtensions.cs b/LibMatrix/Extensions/HttpClientExtensions.cs
index 17cc31d..71bb0e5 100644
--- a/LibMatrix/Extensions/HttpClientExtensions.cs
+++ b/LibMatrix/Extensions/HttpClientExtensions.cs
@@ -37,7 +37,7 @@ public class MatrixHttpClient : HttpClient {
         return options;
     }
 
-    public override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
+    public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
         if (request.RequestUri is null) throw new NullReferenceException("RequestUri is null");
         if (!request.RequestUri.IsAbsoluteUri) request.RequestUri = new Uri(BaseAddress, request.RequestUri);
         // if (AssertedUserId is not null) request.RequestUri = request.RequestUri.AddQuery("user_id", AssertedUserId);
@@ -58,15 +58,18 @@ public class MatrixHttpClient : HttpClient {
         }
 
         HttpResponseMessage responseMessage;
-        try {
+        // try {
             responseMessage = await base.SendAsync(request, cancellationToken);
-        }
-        catch (Exception e) {
-            typeof(HttpRequestMessage).GetField("_sendStatus", BindingFlags.NonPublic | BindingFlags.Instance)
-                ?.SetValue(request, 0);
-            await Task.Delay(2500, cancellationToken);
-            return await SendAsync(request, cancellationToken);
-        }
+        // }
+        // catch (Exception e) {
+            // if (requestSettings is { Retries: 0 }) throw;
+            // typeof(HttpRequestMessage).GetField("_sendStatus", BindingFlags.NonPublic | BindingFlags.Instance)
+            // ?.SetValue(request, 0);
+            // await Task.Delay(requestSettings?.RetryDelay ?? 2500, cancellationToken);
+            // if(requestSettings is not null) requestSettings.Retries--;
+            // return await SendAsync(request, cancellationToken);
+            // throw;
+        // }
 
         if (responseMessage.IsSuccessStatusCode) return responseMessage;
 
diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs
index ba42735..0a11849 100644
--- a/LibMatrix/Helpers/SyncHelper.cs
+++ b/LibMatrix/Helpers/SyncHelper.cs
@@ -1,5 +1,7 @@
 using System.Diagnostics;
 using System.Net.Http.Json;
+using System.Text.Json;
+using System.Text.Json.Serialization;
 using ArcaneLibs.Extensions;
 using LibMatrix.Filters;
 using LibMatrix.Homeservers;
@@ -28,8 +30,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg
             Console.WriteLine("Homeserver for SyncHelper is not properly configured!");
             throw new ArgumentNullException(nameof(homeserver.ClientHttpClient), "Null passed as homeserver for SyncHelper!");
         }
-
-
+        
         var sw = Stopwatch.StartNew();
 
         var url = $"/_matrix/client/v3/sync?timeout={Timeout}&set_presence={SetPresence}&full_state={(FullState ? "true" : "false")}";
@@ -42,7 +43,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg
             if (httpResp is null) throw new NullReferenceException("Failed to send HTTP request");
             logger?.LogInformation("Got sync response: {} bytes, {} elapsed", httpResp.Content.Headers.ContentLength ?? -1, sw.Elapsed);
             var deserializeSw = Stopwatch.StartNew();
-            var resp = await httpResp.Content.ReadFromJsonAsync<SyncResponse>(cancellationToken: cancellationToken ?? CancellationToken.None);
+            var resp = await httpResp.Content.ReadFromJsonAsync<SyncResponse>(cancellationToken: cancellationToken ?? CancellationToken.None, jsonTypeInfo: SyncResponseSerializerContext.Default.SyncResponse);
             logger?.LogInformation("Deserialized sync response: {} bytes, {} elapsed, {} total", httpResp.Content.Headers.ContentLength ?? -1, deserializeSw.Elapsed, sw.Elapsed);
             var timeToWait = MinimumDelay.Subtract(sw.Elapsed);
             if (timeToWait.TotalMilliseconds > 0)
diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
index c1d6461..6fcd8e8 100644
--- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
+++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
@@ -16,37 +16,41 @@ using LibMatrix.Services;
 namespace LibMatrix.Homeservers;
 
 public class AuthenticatedHomeserverGeneric(string serverName, string accessToken) : RemoteHomeserver(serverName) {
-    public static async Task<T> Create<T>(string serverName, string accessToken, string? proxy = null) where T : AuthenticatedHomeserverGeneric {
-        var instance = Activator.CreateInstance(typeof(T), serverName, accessToken) as T
-                       ?? throw new InvalidOperationException($"Failed to create instance of {typeof(T).Name}");
-        HomeserverResolverService.WellKnownUris? urls = null;
-        if (proxy is null)
-            urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(serverName);
-
+    public static async Task<T> Create<T>(string serverName, string accessToken, string? proxy = null) where T : AuthenticatedHomeserverGeneric =>
+        await Create(typeof(T), serverName, accessToken, proxy) as T ?? throw new InvalidOperationException($"Failed to create instance of {typeof(T).Name}");
+    public static async Task<AuthenticatedHomeserverGeneric> Create(Type type, string serverName, string accessToken, string? proxy = null) {
+        if(!type.IsAssignableTo(typeof(AuthenticatedHomeserverGeneric))) throw new ArgumentException("Type must be a subclass of AuthenticatedHomeserverGeneric", nameof(type));
+        var instance = Activator.CreateInstance(type, serverName, accessToken) as AuthenticatedHomeserverGeneric
+                       ?? throw new InvalidOperationException($"Failed to create instance of {type.Name}");
+        
         instance.ClientHttpClient = new() {
-            BaseAddress = new Uri(proxy ?? urls?.Client
-                ?? throw new InvalidOperationException("Failed to resolve homeserver")),
             Timeout = TimeSpan.FromMinutes(15),
             DefaultRequestHeaders = {
                 Authorization = new AuthenticationHeaderValue("Bearer", accessToken)
             }
         };
         instance.ServerHttpClient = new() {
-            BaseAddress = new Uri(proxy ?? urls?.Server
-                ?? throw new InvalidOperationException("Failed to resolve homeserver")),
             Timeout = TimeSpan.FromMinutes(15),
             DefaultRequestHeaders = {
                 Authorization = new AuthenticationHeaderValue("Bearer", accessToken)
             }
         };
 
-        instance.WhoAmI = await instance.ClientHttpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami");
-
-        if (proxy is not null) {
+        if (string.IsNullOrWhiteSpace(proxy)) {
+            HomeserverResolverService.WellKnownUris? urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(serverName);
+            instance.ClientHttpClient.BaseAddress = new Uri(urls?.Client ?? throw new InvalidOperationException("Failed to resolve homeserver"));
+            instance.ServerHttpClient.BaseAddress = new Uri(urls?.Server ?? throw new InvalidOperationException("Failed to resolve homeserver"));
+        }
+        else {
+            instance.ClientHttpClient.BaseAddress = new Uri(proxy);
+            instance.ServerHttpClient.BaseAddress = new Uri(proxy);
             instance.ClientHttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", serverName);
             instance.ServerHttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", serverName);
         }
 
+        instance.WhoAmI = await instance.ClientHttpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami");
+
+
         return instance;
     }
 
@@ -55,12 +59,6 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke
     public string UserLocalpart => UserId.Split(":")[0][1..];
     public string ServerName => UserId.Split(":", 2)[1];
 
-    // public virtual async Task<WhoAmIResponse> WhoAmI() {
-    // if (_whoAmI is not null) return _whoAmI;
-    // _whoAmI = await _httpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami");
-    // return _whoAmI;
-    // }
-
     public string AccessToken { get; set; } = accessToken;
 
     public GenericRoom GetRoom(string roomId) {
diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs
index a47b731..a461d6e 100644
--- a/LibMatrix/Homeservers/RemoteHomeServer.cs
+++ b/LibMatrix/Homeservers/RemoteHomeServer.cs
@@ -37,9 +37,9 @@ public class RemoteHomeserver(string baseUrl) {
     public MatrixHttpClient ServerHttpClient { get; set; } = null!;
     public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; } = null!;
 
-    public async Task<UserProfileResponse> GetProfileAsync(string mxid) {
+    public async Task<UserProfileResponse> GetProfileAsync(string mxid, bool useCache = false) {
         if (mxid is null) throw new ArgumentNullException(nameof(mxid));
-        if (_profileCache.TryGetValue(mxid, out var value)) {
+        if (useCache && _profileCache.TryGetValue(mxid, out var value)) {
             if (value is SemaphoreSlim s) await s.WaitAsync();
             if (value is UserProfileResponse p) return p;
         }
diff --git a/LibMatrix/Responses/SyncResponse.cs b/LibMatrix/Responses/SyncResponse.cs
index 42759ff..49259d0 100644
--- a/LibMatrix/Responses/SyncResponse.cs
+++ b/LibMatrix/Responses/SyncResponse.cs
@@ -2,6 +2,10 @@ using System.Text.Json.Serialization;
 
 namespace LibMatrix.Responses;
 
+[JsonSourceGenerationOptions(WriteIndented = true)]
+[JsonSerializable(typeof(SyncResponse))]
+internal partial class SyncResponseSerializerContext : JsonSerializerContext { }
+
 public class SyncResponse {
     [JsonPropertyName("next_batch")]
     public string NextBatch { get; set; } = null!;
@@ -114,4 +118,4 @@ public class SyncResponse {
             public EventList? InviteState { get; set; }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index 9e2cb67..bfb7f43 100644
--- a/LibMatrix/RoomTypes/GenericRoom.cs
+++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -184,7 +184,7 @@ public class GenericRoom {
         else sw.Restart();
         foreach (var resp in result.Chunk) {
             if (resp?.Type != "m.room.member") continue;
-            if (joinedOnly && (resp.TypedContent as RoomMemberEventContent)?.Membership is not "join") continue;
+            if (joinedOnly && resp.RawContent?["membership"]?.GetValue<string>() != "join") continue;
             yield return resp;
         }
 
@@ -209,7 +209,7 @@ public class GenericRoom {
         var members = new List<StateEventResponse>();
         foreach (var resp in result.Chunk) {
             if (resp?.Type != "m.room.member") continue;
-            if (joinedOnly && (resp.TypedContent as RoomMemberEventContent)?.Membership is not "join") continue;
+            if (joinedOnly && resp.RawContent?["membership"]?.GetValue<string>() != "join") continue;
             members.Add(resp);
         }
 
@@ -267,7 +267,7 @@ public class GenericRoom {
                 var memberList = new List<string>();
                 int memberCount = 0;
                 await foreach (var member in members)
-                    memberList.Add((member.TypedContent is RoomMemberEventContent memberEvent ? memberEvent.DisplayName : "") ?? "");
+                    memberList.Add(member.RawContent?["displayname"]?.GetValue<string>() ?? "");
                 memberCount = memberList.Count;
                 memberList.RemoveAll(string.IsNullOrWhiteSpace);
                 memberList = memberList.OrderBy(x => x).ToList();
diff --git a/LibMatrix/Services/HomeserverProviderService.cs b/LibMatrix/Services/HomeserverProviderService.cs
index dc4acb1..577a706 100644
--- a/LibMatrix/Services/HomeserverProviderService.cs
+++ b/LibMatrix/Services/HomeserverProviderService.cs
@@ -25,22 +25,34 @@ public class HomeserverProviderService(ILogger<HomeserverProviderService> logger
             }
         }
 
-        // var domain = proxy ?? (await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver)).client;
-
         var rhs = await RemoteHomeserver.Create(homeserver, proxy);
-        var clientVersions = await rhs.GetClientVersionsAsync();
+        ClientVersionsResponse clientVersions = new();
+        try {
+            clientVersions = await rhs.GetClientVersionsAsync();
+        }
+        catch (Exception e) {
+            logger.LogError(e, "Failed to get client versions for {homeserver}", homeserver);
+        }
+
         if (proxy is not null)
-            logger.LogInformation($"Homeserver {homeserver} proxied via {proxy}...");
-        logger.LogInformation($"{homeserver}: " + clientVersions.ToJson());
+            logger.LogInformation("Homeserver {homeserver} proxied via {proxy}...", homeserver, proxy);
+        logger.LogInformation("{homeserver}: {clientVersions}", homeserver, clientVersions.ToJson());
 
-        if (clientVersions.UnstableFeatures.TryGetValue("gay.rory.mxapiextensions.v0", out bool a) && a)
-            hs = await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverMxApiExtended>(homeserver, accessToken, proxy);
-        else {
-            var serverVersion = await rhs.GetServerVersionAsync();
-            if (serverVersion is { Server.Name: "Synapse" })
-                hs = await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverSynapse>(homeserver, accessToken, proxy);
-            else
-                hs = await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverGeneric>(homeserver, accessToken, proxy);
+        try {
+            if (clientVersions.UnstableFeatures.TryGetValue("gay.rory.mxapiextensions.v0", out bool a) && a)
+                hs = await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverMxApiExtended>(homeserver, accessToken, proxy);
+            else {
+                var serverVersion = await rhs.GetServerVersionAsync();
+                if (serverVersion is { Server.Name: "Synapse" })
+                    hs = await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverSynapse>(homeserver, accessToken, proxy);
+                else
+                    hs = await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverGeneric>(homeserver, accessToken, proxy);
+            }
+        }
+        catch (Exception e) {
+            logger.LogError(e, "Failed to create authenticated homeserver for {homeserver}", homeserver);
+            sem.Release();
+            throw;
         }
 
         if(impersonatedMxid is not null)
diff --git a/LibMatrix/Services/ServiceInstaller.cs b/LibMatrix/Services/ServiceInstaller.cs
index b1c98e1..ad5bedc 100644
--- a/LibMatrix/Services/ServiceInstaller.cs
+++ b/LibMatrix/Services/ServiceInstaller.cs
@@ -6,20 +6,20 @@ public static class ServiceInstaller {
 
     public static IServiceCollection AddRoryLibMatrixServices(this IServiceCollection services, RoryLibMatrixConfiguration? config = null) {
         //Check required services
-        if (!services.Any(x => x.ServiceType == typeof(TieredStorageService)))
-            throw new Exception("[MRUCore/DI] No TieredStorageService has been registered!");
+        // if (!services.Any(x => x.ServiceType == typeof(TieredStorageService)))
+            // throw new Exception("[MRUCore/DI] No TieredStorageService has been registered!");
         //Add config
         services.AddSingleton(config ?? new RoryLibMatrixConfiguration());
 
         //Add services
         services.AddSingleton<HomeserverResolverService>();
 
-        if (services.First(x => x.ServiceType == typeof(TieredStorageService)).Lifetime == ServiceLifetime.Singleton) {
+        // if (services.First(x => x.ServiceType == typeof(TieredStorageService)).Lifetime == ServiceLifetime.Singleton) {
             services.AddSingleton<HomeserverProviderService>();
-        }
-        else {
-            services.AddScoped<HomeserverProviderService>();
-        }
+        // }
+        // else {
+            // services.AddScoped<HomeserverProviderService>();
+        // }
 
         // services.AddScoped<MatrixHttpClient>();
         return services;
diff --git a/LibMatrix/StateEvent.cs b/LibMatrix/StateEvent.cs
index ad7605a..4a0adbd 100644
--- a/LibMatrix/StateEvent.cs
+++ b/LibMatrix/StateEvent.cs
@@ -26,8 +26,11 @@ public class StateEvent {
         }).ToFrozenDictionary();
 
     public static Type GetStateEventType(string type) => KnownStateEventTypesByName.GetValueOrDefault(type) ?? typeof(UnknownEventContent);
+    
+    [JsonIgnore]
+    public Type MappedType => GetStateEventType(Type);
 
-    private static readonly JsonSerializerOptions TypedContentSerializerOptions = new JsonSerializerOptions() {
+    private static readonly JsonSerializerOptions TypedContentSerializerOptions = new() {
         Converters = {
             new JsonFloatStringConverter(),
             new JsonDoubleStringConverter(),
diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests.cs
index 913e044..bec8d18 100644
--- a/Tests/LibMatrix.Tests/Tests/RoomTests.cs
+++ b/Tests/LibMatrix.Tests/Tests/RoomTests.cs
@@ -52,7 +52,7 @@ public class RoomTests : TestBed<TestFixture> {
         var hs = await HomeserverAbstraction.GetHomeserver();
         var room = await RoomAbstraction.GetTestRoom(hs);
         Assert.NotNull(room);
-        var members = room.GetMembersAsync();
+        var members = room.GetMembersEnumerableAsync();
         Assert.NotNull(members);
         bool hitMembers = false;
         await foreach (var member in members) {
@@ -248,12 +248,7 @@ public class RoomTests : TestBed<TestFixture> {
         }
         await Task.WhenAll(tasks);
 
-        var states = room.GetMembersAsync(false);
-        var count = 0;
-        await foreach (var state in states) {
-            count++;
-        }
-        // Assert.Equal(++expectedCount, count);
-        Assert.Equal(16, count);
+        var states = await room.GetMembersListAsync(false);
+        Assert.Equal(16, states.Count);
     }
 }