about summary refs log tree commit diff
path: root/LibMatrix
diff options
context:
space:
mode:
authorEmma [it/its]@Rory& <root@rory.gay>2023-12-14 07:20:46 +0100
committerEmma [it/its]@Rory& <root@rory.gay>2023-12-14 07:20:46 +0100
commit5affd9f061e75f6575a2fe6715f9e8757cfe87e8 (patch)
tree13ea35ce981094a960746777a16dff8815c45e55 /LibMatrix
parentTemp state (diff)
downloadLibMatrix-5affd9f061e75f6575a2fe6715f9e8757cfe87e8.tar.xz
Cleanup
Diffstat (limited to '')
-rw-r--r--LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs (renamed from LibMatrix/EventTypes/Common/MjolnirShortcodeEventContent.cs)1
-rw-r--r--LibMatrix.EventTypes/Common/RoomEmotesEventContent.cs (renamed from LibMatrix/EventTypes/Common/RoomEmotesEventContent.cs)5
-rw-r--r--LibMatrix.EventTypes/EventContent.cs (renamed from LibMatrix/Interfaces/EventContent.cs)16
-rw-r--r--LibMatrix.EventTypes/LibMatrix.EventTypes.csproj9
-rw-r--r--LibMatrix.EventTypes/MatrixEventAttribute.cs (renamed from LibMatrix/EventTypes/MatrixEventAttribute.cs)2
-rw-r--r--LibMatrix.EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs (renamed from LibMatrix/EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs)3
-rw-r--r--LibMatrix.EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs (renamed from LibMatrix/EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs)1
-rw-r--r--LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs (renamed from LibMatrix/EventTypes/Spec/RoomMessageEventContent.cs)5
-rw-r--r--LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs)3
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomAliasEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAliasEventContent.cs)3
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomAvatarEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAvatarEventContent.cs)3
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs)1
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs)1
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs)1
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs)4
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs)3
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs52
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs)3
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs)1
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs)1
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs)11
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs)5
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomTopicEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/RoomInfo/RoomTopicEventContent.cs)1
-rw-r--r--LibMatrix.EventTypes/Spec/State/Space/SpaceChildEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/Space/SpaceChildEventContent.cs)1
-rw-r--r--LibMatrix.EventTypes/Spec/State/Space/SpaceParentEventContent.cs (renamed from LibMatrix/EventTypes/Spec/State/Space/SpaceParentEventContent.cs)1
-rw-r--r--LibMatrix.sln6
-rw-r--r--LibMatrix.sln.DotSettings.user1
-rw-r--r--LibMatrix/EventIdResponse.cs2
-rw-r--r--LibMatrix/EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs29
-rw-r--r--LibMatrix/EventTypes/UnknownStateEventContent.cs7
-rw-r--r--LibMatrix/Extensions/HttpClientExtensions.cs6
-rw-r--r--LibMatrix/Extensions/JsonElementExtensions.cs2
-rw-r--r--LibMatrix/Helpers/MessageFormatter.cs9
-rw-r--r--LibMatrix/Helpers/SyncHelper.cs12
-rw-r--r--LibMatrix/Helpers/SyncStateResolver.cs6
-rw-r--r--LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs13
-rw-r--r--LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs2
-rw-r--r--LibMatrix/Homeservers/RemoteHomeServer.cs14
-rw-r--r--LibMatrix/LibMatrix.csproj9
-rw-r--r--LibMatrix/MatrixException.cs4
-rw-r--r--LibMatrix/Responses/Admin/AdminRoomListingResult.cs6
-rw-r--r--LibMatrix/Responses/CreateRoomRequest.cs26
-rw-r--r--LibMatrix/Responses/CreationContentBaseType.cs10
-rw-r--r--LibMatrix/Responses/LoginResponse.cs2
-rw-r--r--LibMatrix/Responses/SyncResponse.cs10
-rw-r--r--LibMatrix/Responses/UserProfileResponse.cs1
-rw-r--r--LibMatrix/RoomTypes/GenericRoom.cs74
-rw-r--r--LibMatrix/RoomTypes/SpaceRoom.cs4
-rw-r--r--LibMatrix/Services/HomeserverProviderService.cs47
-rw-r--r--LibMatrix/Services/HomeserverResolverService.cs25
-rw-r--r--LibMatrix/StateEvent.cs39
-rw-r--r--LibMatrix/UserIdAndReason.cs2
-rw-r--r--LibMatrix/WhoAmIResponse.cs2
53 files changed, 269 insertions, 238 deletions
diff --git a/LibMatrix/EventTypes/Common/MjolnirShortcodeEventContent.cs b/LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs
index a09a393..7924a33 100644
--- a/LibMatrix/EventTypes/Common/MjolnirShortcodeEventContent.cs
+++ b/LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs
@@ -1,5 +1,4 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Common;
 
diff --git a/LibMatrix/EventTypes/Common/RoomEmotesEventContent.cs b/LibMatrix.EventTypes/Common/RoomEmotesEventContent.cs
index 8d05a2e..bfe480e 100644
--- a/LibMatrix/EventTypes/Common/RoomEmotesEventContent.cs
+++ b/LibMatrix.EventTypes/Common/RoomEmotesEventContent.cs
@@ -1,5 +1,4 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Common;
 
@@ -19,7 +18,5 @@ public class RoomEmotesEventContent : TimelineEventContent {
         public string? Url { get; set; }
     }
 
-    public class PackInfo {
-
-    }
+    public class PackInfo; // TODO: Implement this
 }
diff --git a/LibMatrix/Interfaces/EventContent.cs b/LibMatrix.EventTypes/EventContent.cs
index 76419a6..608550f 100644
--- a/LibMatrix/Interfaces/EventContent.cs
+++ b/LibMatrix.EventTypes/EventContent.cs
@@ -2,9 +2,11 @@ using System.Text.Json;
 using System.Text.Json.Nodes;
 using System.Text.Json.Serialization;
 
-namespace LibMatrix.Interfaces;
+namespace LibMatrix.EventTypes;
 
-public abstract class EventContent { }
+public abstract class EventContent;
+
+public class UnknownEventContent : TimelineEventContent;
 
 public abstract class TimelineEventContent : EventContent {
     [JsonPropertyName("m.relates_to")]
@@ -14,9 +16,9 @@ public abstract class TimelineEventContent : EventContent {
     public JsonObject? NewContent { get; set; }
 
     public TimelineEventContent SetReplaceRelation(string eventId) {
-        NewContent = JsonSerializer.SerializeToNode(this, GetType()).AsObject();
+        NewContent = JsonSerializer.SerializeToNode(this, GetType())!.AsObject();
         // NewContent = JsonSerializer.Deserialize(jsonText, GetType());
-        RelatesTo = new() {
+        RelatesTo = new MessageRelatesTo {
             RelationType = "m.replace",
             EventId = eventId
         };
@@ -39,10 +41,10 @@ public abstract class TimelineEventContent : EventContent {
 
         public class EventInReplyTo {
             [JsonPropertyName("event_id")]
-            public string EventId { get; set; }
+            public string? EventId { get; set; }
 
             [JsonPropertyName("rel_type")]
-            public string RelType { get; set; }
+            public string? RelType { get; set; }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj b/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj
new file mode 100644
index 0000000..3a63532
--- /dev/null
+++ b/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj
@@ -0,0 +1,9 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+    </PropertyGroup>
+
+</Project>
diff --git a/LibMatrix/EventTypes/MatrixEventAttribute.cs b/LibMatrix.EventTypes/MatrixEventAttribute.cs
index 92334d0..baa88ff 100644
--- a/LibMatrix/EventTypes/MatrixEventAttribute.cs
+++ b/LibMatrix.EventTypes/MatrixEventAttribute.cs
@@ -2,6 +2,6 @@ namespace LibMatrix.EventTypes;
 
 [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
 public class MatrixEventAttribute : Attribute {
-    public string EventName { get; set; }
+    public required string EventName { get; set; }
     public bool Legacy { get; set; }
 }
diff --git a/LibMatrix/EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs b/LibMatrix.EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs
index 8ffbca5..1e98e12 100644
--- a/LibMatrix/EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs
@@ -1,7 +1,6 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
-namespace LibMatrix.EventTypes.Spec.State;
+namespace LibMatrix.EventTypes.Spec.Ephemeral;
 
 [MatrixEvent(EventName = EventId)]
 public class PresenceEventContent : EventContent {
diff --git a/LibMatrix/EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs b/LibMatrix.EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs
index b947096..b62b448 100644
--- a/LibMatrix/EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs
@@ -1,5 +1,4 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Spec.State;
 
diff --git a/LibMatrix/EventTypes/Spec/RoomMessageEventContent.cs b/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs
index 944ed99..1e27bce 100644
--- a/LibMatrix/EventTypes/Spec/RoomMessageEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs
@@ -1,5 +1,4 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Spec;
 
@@ -7,9 +6,9 @@ namespace LibMatrix.EventTypes.Spec;
 public class RoomMessageEventContent : TimelineEventContent {
     public const string EventId = "m.room.message";
 
-    public RoomMessageEventContent(string? messageType = "m.notice", string? body = null) {
+    public RoomMessageEventContent(string messageType = "m.notice", string? body = null) {
         MessageType = messageType;
-        Body = body;
+        Body = body ?? "";
     }
 
     [JsonPropertyName("body")]
diff --git a/LibMatrix/EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
index 80d87d6..d3ab8cb 100644
--- a/LibMatrix/EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
@@ -1,7 +1,6 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
-namespace LibMatrix.EventTypes.Spec.State;
+namespace LibMatrix.EventTypes.Spec.State.Policy;
 
 //spec
 [MatrixEvent(EventName = EventId)] //spec
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAliasEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomAliasEventContent.cs
index 830386d..53b85b8 100644
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAliasEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomAliasEventContent.cs
@@ -1,7 +1,6 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
-namespace LibMatrix.EventTypes.Spec.State;
+namespace LibMatrix.EventTypes.Spec.State.RoomInfo;
 
 [MatrixEvent(EventName = EventId)]
 public class RoomAliasEventContent : TimelineEventContent {
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAvatarEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomAvatarEventContent.cs
index 9c208ba..d15e88e 100644
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAvatarEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomAvatarEventContent.cs
@@ -1,7 +1,6 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
-namespace LibMatrix.EventTypes.Spec.State;
+namespace LibMatrix.EventTypes.Spec.State.RoomInfo;
 
 [MatrixEvent(EventName = EventId)]
 public class RoomAvatarEventContent : TimelineEventContent {
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs
index 5ba253c..265775e 100644
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs
@@ -1,5 +1,4 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Spec.State;
 
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs
index 41145de..7d25dc7 100644
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs
@@ -1,5 +1,4 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Spec.State;
 
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs
index a3627f2..8e9e05f 100644
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs
@@ -1,5 +1,4 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Spec.State;
 
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs
index 5bad649..30f2def 100644
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs
@@ -1,13 +1,13 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Spec.State;
 
 [MatrixEvent(EventName = "m.room.guest_access")]
 public class RoomGuestAccessEventContent : TimelineEventContent {
     [JsonPropertyName("guest_access")]
-    public string GuestAccess { get; set; }
+    public required string GuestAccess { get; set; }
 
+    [JsonIgnore]
     public bool IsGuestAccessEnabled {
         get => GuestAccess == "can_join";
         set => GuestAccess = value ? "can_join" : "forbidden";
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
index 8f5c7f1..26d40e1 100644
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
@@ -1,10 +1,9 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Spec.State;
 
 [MatrixEvent(EventName = "m.room.history_visibility")]
 public class RoomHistoryVisibilityEventContent : TimelineEventContent {
     [JsonPropertyName("history_visibility")]
-    public string HistoryVisibility { get; set; }
+    public required string HistoryVisibility { get; set; }
 }
diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs
new file mode 100644
index 0000000..e300b5d
--- /dev/null
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs
@@ -0,0 +1,52 @@
+using System.Text.Json.Serialization;
+
+namespace LibMatrix.EventTypes.Spec.State;
+
+[MatrixEvent(EventName = "m.room.join_rules")]
+public class RoomJoinRulesEventContent : TimelineEventContent {
+    /// <summary>
+    /// one of ["public", "invite", "knock", "restricted", "knock_restricted"]
+    /// "private" is reserved without implementation!
+    /// </summary>
+    [JsonPropertyName("join_rule")]
+    public string JoinRuleValue { get; set; }
+    
+    [JsonIgnore]
+    public required JoinRules JoinRule {
+        get => JoinRuleValue switch {
+            "public" => JoinRules.Public,
+            "invite" => JoinRules.Invite,
+            "knock" => JoinRules.Knock,
+            "restricted" => JoinRules.Restricted,
+            "knock_restricted" => JoinRules.KnockRestricted,
+            _ => throw new ArgumentOutOfRangeException()
+        };
+        set => JoinRuleValue = value switch {
+            JoinRules.Public => "public",
+            JoinRules.Invite => "invite",
+            JoinRules.Knock => "knock",
+            JoinRules.Restricted => "restricted",
+            JoinRules.KnockRestricted => "knock_restricted",
+            _ => throw new ArgumentOutOfRangeException(nameof(value), value, null)
+        };
+    }
+
+    [JsonPropertyName("allow")]
+    public List<AllowEntry>? Allow { get; set; }
+
+    public class AllowEntry {
+        [JsonPropertyName("type")]
+        public required string Type { get; set; }
+
+        [JsonPropertyName("room_id")]
+        public required string RoomId { get; set; }
+    }
+
+    public enum JoinRules {
+        Public,
+        Invite,
+        Knock,
+        Restricted,
+        KnockRestricted
+    }
+}
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs
index 698315e..7e4f9b6 100644
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs
@@ -1,5 +1,4 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Spec.State;
 
@@ -11,7 +10,7 @@ public class RoomMemberEventContent : TimelineEventContent {
     public string? Reason { get; set; }
 
     [JsonPropertyName("membership")]
-    public string Membership { get; set; } = null!;
+    public required string Membership { get; set; }
 
     [JsonPropertyName("displayname")]
     public string? DisplayName { get; set; }
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs
index 9ad67eb..00a1e8f 100644
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs
@@ -1,5 +1,4 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Spec.State;
 
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs
index 11fe208..9bbcd90 100644
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs
@@ -1,5 +1,4 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Spec.State;
 
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs
index 08f8ad5..1a09ab8 100644
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs
@@ -1,5 +1,4 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Spec.State;
 
@@ -48,22 +47,22 @@ public class RoomPowerLevelEventContent : TimelineEventContent {
     }
 
     public bool IsUserAdmin(string userId) {
-        if (userId is null) throw new ArgumentNullException(nameof(userId));
+        ArgumentNullException.ThrowIfNull(userId);
         return Users.TryGetValue(userId, out var level) && level >= Events.Max(x => x.Value);
     }
 
     public bool UserHasTimelinePermission(string userId, string eventType) {
-        if (userId is null) throw new ArgumentNullException(nameof(userId));
+        ArgumentNullException.ThrowIfNull(userId);
         return Users.TryGetValue(userId, out var level) && level >= Events.GetValueOrDefault(eventType, EventsDefault ?? 0);
     }
 
     public bool UserHasStatePermission(string userId, string eventType) {
-        if (userId is null) throw new ArgumentNullException(nameof(userId));
+        ArgumentNullException.ThrowIfNull(userId);
         return Users.TryGetValue(userId, out var level) && level >= Events.GetValueOrDefault(eventType, StateDefault ?? 50);
     }
 
     public long GetUserPowerLevel(string userId) {
-        if (userId is null) throw new ArgumentNullException(nameof(userId));
+        ArgumentNullException.ThrowIfNull(userId);
         return Users.TryGetValue(userId, out var level) ? level : UsersDefault ?? UsersDefault ?? 0;
     }
 
@@ -72,7 +71,7 @@ public class RoomPowerLevelEventContent : TimelineEventContent {
     }
 
     public void SetUserPowerLevel(string userId, long powerLevel) {
-        if (userId is null) throw new ArgumentNullException(nameof(userId));
+        ArgumentNullException.ThrowIfNull(userId);
         Users ??= new();
         Users[userId] = powerLevel;
     }
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs
index cbd2241..75337f5 100644
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs
@@ -1,15 +1,14 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Spec.State;
 
 [MatrixEvent(EventName = "m.room.server_acl")]
 public class RoomServerACLEventContent : TimelineEventContent {
     [JsonPropertyName("allow")]
-    public List<string> Allow { get; set; } // = null!;
+    public List<string>? Allow { get; set; } // = null!;
 
     [JsonPropertyName("deny")]
-    public List<string> Deny { get; set; } // = null!;
+    public List<string>? Deny { get; set; } // = null!;
 
     [JsonPropertyName("allow_ip_literals")]
     public bool AllowIpLiterals { get; set; } // = false;
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomTopicEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomTopicEventContent.cs
index 866eecf..3121c39 100644
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomTopicEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomTopicEventContent.cs
@@ -1,5 +1,4 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Spec.State;
 
diff --git a/LibMatrix/EventTypes/Spec/State/Space/SpaceChildEventContent.cs b/LibMatrix.EventTypes/Spec/State/Space/SpaceChildEventContent.cs
index 82f4b7f..fb5c938 100644
--- a/LibMatrix/EventTypes/Spec/State/Space/SpaceChildEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/Space/SpaceChildEventContent.cs
@@ -1,5 +1,4 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Spec.State;
 
diff --git a/LibMatrix/EventTypes/Spec/State/Space/SpaceParentEventContent.cs b/LibMatrix.EventTypes/Spec/State/Space/SpaceParentEventContent.cs
index 887e91c..0c23298 100644
--- a/LibMatrix/EventTypes/Spec/State/Space/SpaceParentEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/Space/SpaceParentEventContent.cs
@@ -1,5 +1,4 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.EventTypes.Spec.State;
 
diff --git a/LibMatrix.sln b/LibMatrix.sln
index f94131b..f3eae7d 100644
--- a/LibMatrix.sln
+++ b/LibMatrix.sln
@@ -33,6 +33,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ArcaneLibs", "ArcaneLibs",
 EndProject

 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs", "ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj", "{13A797D1-7E13-4789-A167-8628B1641AC0}"

 EndProject

+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.EventTypes", "LibMatrix.EventTypes\LibMatrix.EventTypes.csproj", "{CD13665B-B964-4AB0-991B-12F067B16DA3}"

+EndProject

 Global

 	GlobalSection(SolutionConfigurationPlatforms) = preSolution

 		Debug|Any CPU = Debug|Any CPU

@@ -86,6 +88,10 @@ Global
 		{13A797D1-7E13-4789-A167-8628B1641AC0}.Debug|Any CPU.Build.0 = Debug|Any CPU

 		{13A797D1-7E13-4789-A167-8628B1641AC0}.Release|Any CPU.ActiveCfg = Release|Any CPU

 		{13A797D1-7E13-4789-A167-8628B1641AC0}.Release|Any CPU.Build.0 = Release|Any CPU

+		{CD13665B-B964-4AB0-991B-12F067B16DA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU

+		{CD13665B-B964-4AB0-991B-12F067B16DA3}.Debug|Any CPU.Build.0 = Debug|Any CPU

+		{CD13665B-B964-4AB0-991B-12F067B16DA3}.Release|Any CPU.ActiveCfg = Release|Any CPU

+		{CD13665B-B964-4AB0-991B-12F067B16DA3}.Release|Any CPU.Build.0 = Release|Any CPU

 	EndGlobalSection

 	GlobalSection(NestedProjects) = preSolution

 		{1B1B2197-61FB-416F-B6C8-845F2E5A0442} = {840309F0-435B-43A7-8471-8C2BE643889D}

diff --git a/LibMatrix.sln.DotSettings.user b/LibMatrix.sln.DotSettings.user
index f720bf5..e26043a 100644
--- a/LibMatrix.sln.DotSettings.user
+++ b/LibMatrix.sln.DotSettings.user
@@ -1,5 +1,6 @@
 <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
 	<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=_002Fhome_002FRory_002Fgit_002Fmatrix_002FMatrixRoomUtils_002FLibMatrix_002FArcaneLibs_002FArcaneLibs_002Fbin_002FDebug_002Fnet8_002E0_002FArcaneLibs_002Edll/@EntryIndexedValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/SweaWarningsMode/@EntryValue">ShowAndRun</s:String>
 	<s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue">&lt;AssemblyExplorer&gt;
   &lt;Assembly Path="/home/root@Rory/.cache/NuGetPackages/microsoft.extensions.hosting.abstractions/7.0.0/lib/net7.0/Microsoft.Extensions.Hosting.Abstractions.dll" /&gt;
 &lt;/AssemblyExplorer&gt;</s:String></wpf:ResourceDictionary>
\ No newline at end of file
diff --git a/LibMatrix/EventIdResponse.cs b/LibMatrix/EventIdResponse.cs
index 31a95b8..a7feeca 100644
--- a/LibMatrix/EventIdResponse.cs
+++ b/LibMatrix/EventIdResponse.cs
@@ -4,5 +4,5 @@ namespace LibMatrix;
 
 public class EventIdResponse {
     [JsonPropertyName("event_id")]
-    public string EventId { get; set; } = null!;
+    public required string EventId { get; set; }
 }
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs
deleted file mode 100644
index 2db9e60..0000000
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-[MatrixEvent(EventName = "m.room.join_rules")]
-public class RoomJoinRulesEventContent : TimelineEventContent {
-    private static string Public = "public";
-    private static string Invite = "invite";
-    private static string Knock = "knock";
-
-    /// <summary>
-    /// one of ["public", "invite", "knock", "restricted", "knock_restricted"]
-    /// "private" is reserved without implementation!
-    /// </summary>
-    [JsonPropertyName("join_rule")]
-    public string JoinRule { get; set; }
-
-    [JsonPropertyName("allow")]
-    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/EventTypes/UnknownStateEventContent.cs b/LibMatrix/EventTypes/UnknownStateEventContent.cs
deleted file mode 100644
index c47dc99..0000000
--- a/LibMatrix/EventTypes/UnknownStateEventContent.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes;
-
-public class UnknownEventContent : TimelineEventContent {
-
-}
diff --git a/LibMatrix/Extensions/HttpClientExtensions.cs b/LibMatrix/Extensions/HttpClientExtensions.cs
index 6f27f71..93e1441 100644
--- a/LibMatrix/Extensions/HttpClientExtensions.cs
+++ b/LibMatrix/Extensions/HttpClientExtensions.cs
@@ -116,8 +116,8 @@ public class MatrixHttpClient : HttpClient {
         return await response.Content.ReadAsStreamAsync(cancellationToken);
     }
 
-    public new async Task<HttpResponseMessage> PutAsJsonAsync<T>([StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, T value, JsonSerializerOptions? options = null,
-        CancellationToken cancellationToken = default) {
+    public async Task<HttpResponseMessage> PutAsJsonAsync<T>([StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, T value, JsonSerializerOptions? options = null,
+        CancellationToken cancellationToken = default) where T : notnull {
         options = GetJsonSerializerOptions(options);
         var request = new HttpRequestMessage(HttpMethod.Put, requestUri);
         request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
@@ -130,7 +130,7 @@ public class MatrixHttpClient : HttpClient {
     }
 
     public async Task<HttpResponseMessage> PostAsJsonAsync<T>([StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, T value, JsonSerializerOptions? options = null,
-        CancellationToken cancellationToken = default) {
+        CancellationToken cancellationToken = default) where T : notnull {
         options ??= new();
         options.Converters.Add(new JsonFloatStringConverter());
         options.Converters.Add(new JsonDoubleStringConverter());
diff --git a/LibMatrix/Extensions/JsonElementExtensions.cs b/LibMatrix/Extensions/JsonElementExtensions.cs
index 8c3884e..0bdf01c 100644
--- a/LibMatrix/Extensions/JsonElementExtensions.cs
+++ b/LibMatrix/Extensions/JsonElementExtensions.cs
@@ -37,7 +37,7 @@ public static class JsonElementExtensions {
 
                 if (field.Name == "content" && (objectType == typeof(StateEventResponse) || objectType == typeof(StateEvent))) {
                     unknownPropertyFound |= field.FindExtraJsonPropertyFieldsByValueKind(
-                        StateEvent.GetStateEventType(obj.GetProperty("type").GetString()),
+                        StateEvent.GetStateEventType(obj.GetProperty("type").GetString()!), // We expect type to always be present
                         mappedProperty.PropertyType);
                     continue;
                 }
diff --git a/LibMatrix/Helpers/MessageFormatter.cs b/LibMatrix/Helpers/MessageFormatter.cs
index 03efeec..f275b57 100644
--- a/LibMatrix/Helpers/MessageFormatter.cs
+++ b/LibMatrix/Helpers/MessageFormatter.cs
@@ -6,7 +6,7 @@ namespace LibMatrix.Helpers;
 public static class MessageFormatter {
     public static RoomMessageEventContent FormatError(string error) {
         return new RoomMessageEventContent(body: error, messageType: "m.text") {
-            FormattedBody = $"<font color=\"#FF0000\">{error}: {error}</font>",
+            FormattedBody = $"<font color=\"#FF0000\">{error}</font>",
             Format = "org.matrix.custom.html"
         };
     }
@@ -46,4 +46,11 @@ public static class MessageFormatter {
     public static RoomMessageEventContent ToMatrixMessage(this Exception e, string error) => FormatException(error, e);
 
     #endregion
+
+    public static RoomMessageEventContent FormatWarning(string warning) {
+        return new RoomMessageEventContent(body: warning, messageType: "m.text") {
+            FormattedBody = $"<font color=\"#FFFF00\">{warning}</font>",
+            Format = "org.matrix.custom.html"
+        };
+    }
 }
diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs
index 334c288..ba42735 100644
--- a/LibMatrix/Helpers/SyncHelper.cs
+++ b/LibMatrix/Helpers/SyncHelper.cs
@@ -38,12 +38,12 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg
         // Console.WriteLine("Calling: " + url);
         logger?.LogInformation("SyncHelper: Calling: {}", url);
         try {
-            var httpResp = await homeserver.ClientHttpClient.GetAsync(url, cancellationToken: cancellationToken ?? CancellationToken.None)!;
+            var httpResp = await homeserver.ClientHttpClient.GetAsync(url, cancellationToken: cancellationToken ?? CancellationToken.None);
             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);
+            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)!;
-            logger?.LogInformation("Deserialized sync response: {} bytes, {} elapsed, {} total", httpResp?.Content.Headers.ContentLength ?? -1, deserializeSw.Elapsed, sw.Elapsed);
+            var resp = await httpResp.Content.ReadFromJsonAsync<SyncResponse>(cancellationToken: cancellationToken ?? CancellationToken.None);
+            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)
                 await Task.Delay(timeToWait);
@@ -65,7 +65,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg
         while (!cancellationToken?.IsCancellationRequested ?? true) {
             var sync = await SyncAsync(cancellationToken);
             if (sync is null) continue;
-            if (!string.IsNullOrWhiteSpace(sync?.NextBatch)) Since = sync.NextBatch;
+            if (!string.IsNullOrWhiteSpace(sync.NextBatch)) Since = sync.NextBatch;
             yield return sync;
         }
     }
@@ -76,7 +76,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg
         var oldTimeout = Timeout;
         Timeout = 0;
         await foreach (var sync in EnumerateSyncAsync(cancellationToken)) {
-            if (sync?.ToJson(ignoreNull: true, indent: false).Length < 250) {
+            if (sync.ToJson(ignoreNull: true, indent: false).Length < 250) {
                 emptyInitialSyncCount++;
                 if (emptyInitialSyncCount > 5) {
                     IsInitialSync = false;
diff --git a/LibMatrix/Helpers/SyncStateResolver.cs b/LibMatrix/Helpers/SyncStateResolver.cs
index f40fa22..f380a1f 100644
--- a/LibMatrix/Helpers/SyncStateResolver.cs
+++ b/LibMatrix/Helpers/SyncStateResolver.cs
@@ -162,7 +162,11 @@ public class SyncStateResolver(AuthenticatedHomeserverGeneric homeserver, ILogge
         oldData.UnreadNotifications.HighlightCount = newData.UnreadNotifications?.HighlightCount ?? oldData.UnreadNotifications.HighlightCount;
         oldData.UnreadNotifications.NotificationCount = newData.UnreadNotifications?.NotificationCount ?? oldData.UnreadNotifications.NotificationCount;
 
-        oldData.Summary ??= new();
+        oldData.Summary ??= new() {
+            Heroes = newData.Summary?.Heroes ?? oldData.Summary.Heroes,
+            JoinedMemberCount = newData.Summary?.JoinedMemberCount ?? oldData.Summary.JoinedMemberCount,
+            InvitedMemberCount = newData.Summary?.InvitedMemberCount ?? oldData.Summary.InvitedMemberCount
+        };
         oldData.Summary.Heroes = newData.Summary?.Heroes ?? oldData.Summary.Heroes;
         oldData.Summary.JoinedMemberCount = newData.Summary?.JoinedMemberCount ?? oldData.Summary.JoinedMemberCount;
         oldData.Summary.InvitedMemberCount = newData.Summary?.InvitedMemberCount ?? oldData.Summary.InvitedMemberCount;
diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
index e85ecd2..cf85287 100644
--- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
+++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
 using System.Net.Http.Headers;
 using System.Net.Http.Json;
 using System.Text.Json;
@@ -50,9 +51,9 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke
     }
 
     public WhoAmIResponse WhoAmI { get; set; }
-    public string? UserId => WhoAmI?.UserId;
-    public string? UserLocalpart => UserId?.Split(":")[0][1..];
-    public string? ServerName => UserId?.Split(":", 2)[1];
+    public string UserId => WhoAmI.UserId;
+    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;
@@ -289,10 +290,10 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke
     }
 
     public async Task<RoomIdResponse> JoinRoomAsync(string roomId, List<string> homeservers = null, string? reason = null) {
-        var join_url = $"/_matrix/client/v3/join/{HttpUtility.UrlEncode(roomId)}";
-        Console.WriteLine($"Calling {join_url} with {homeservers?.Count ?? 0} via's...");
+        var joinUrl = $"/_matrix/client/v3/join/{HttpUtility.UrlEncode(roomId)}";
+        Console.WriteLine($"Calling {joinUrl} with {homeservers?.Count ?? 0} via's...");
         if (homeservers == null || homeservers.Count == 0) homeservers = new() { roomId.Split(':')[1] };
-        var fullJoinUrl = $"{join_url}?server_name=" + string.Join("&server_name=", homeservers);
+        var fullJoinUrl = $"{joinUrl}?server_name=" + string.Join("&server_name=", homeservers);
         var res = await ClientHttpClient.PostAsJsonAsync(fullJoinUrl, new {
             reason
         });
diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs
index 28ff775..6562686 100644
--- a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs
+++ b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs
@@ -20,7 +20,7 @@ public class AuthenticatedHomeserverSynapse : AuthenticatedHomeserverGeneric {
                 Console.WriteLine($"--- ADMIN Querying Room List with URL: {url} - Already have {i} items... ---");
 
                 res = await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<AdminRoomListingResult>(url);
-                totalRooms ??= res?.TotalRooms;
+                totalRooms ??= res.TotalRooms;
                 Console.WriteLine(res.ToJson(false));
                 foreach (var room in res.Rooms) {
                     if (localFilter is not null) {
diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs
index f8d61fd..a47b731 100644
--- a/LibMatrix/Homeservers/RemoteHomeServer.cs
+++ b/LibMatrix/Homeservers/RemoteHomeServer.cs
@@ -2,7 +2,6 @@ using System.Net.Http.Json;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 using ArcaneLibs.Extensions;
-using LibMatrix.EventTypes.Spec.State;
 using LibMatrix.Extensions;
 using LibMatrix.Responses;
 using LibMatrix.Services;
@@ -10,6 +9,7 @@ using LibMatrix.Services;
 namespace LibMatrix.Homeservers;
 
 public class RemoteHomeserver(string baseUrl) {
+    
     public static async Task<RemoteHomeserver> Create(string baseUrl, string? proxy = null) {
         var homeserver = new RemoteHomeserver(baseUrl);
         homeserver.WellKnownUris = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl);
@@ -32,9 +32,10 @@ public class RemoteHomeserver(string baseUrl) {
 
     private Dictionary<string, object> _profileCache { get; set; } = new();
     public string BaseUrl { get; } = baseUrl;
-    public MatrixHttpClient ClientHttpClient { get; set; }
-    public MatrixHttpClient ServerHttpClient { get; set; }
-    public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; }
+    
+    public MatrixHttpClient ClientHttpClient { get; set; } = null!;
+    public MatrixHttpClient ServerHttpClient { get; set; } = null!;
+    public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; } = null!;
 
     public async Task<UserProfileResponse> GetProfileAsync(string mxid) {
         if (mxid is null) throw new ArgumentNullException(nameof(mxid));
@@ -63,7 +64,7 @@ public class RemoteHomeserver(string baseUrl) {
     public async Task<AliasResult> ResolveRoomAliasAsync(string alias) {
         var resp = await ClientHttpClient.GetAsync($"/_matrix/client/v3/directory/room/{alias.Replace("#", "%23")}");
         var data = await resp.Content.ReadFromJsonAsync<AliasResult>();
-        var text = await resp.Content.ReadAsStringAsync();
+        //var text = await resp.Content.ReadAsStringAsync();
         if (!resp.IsSuccessStatusCode) Console.WriteLine("ResolveAlias: " + data.ToJson());
         return data;
     }
@@ -119,8 +120,9 @@ public class RemoteHomeserver(string baseUrl) {
 public class ServerVersionResponse {
 
     [JsonPropertyName("server")]
-    public ServerInfo Server { get; set; }
+    public required ServerInfo Server { get; set; }
 
+    // ReSharper disable once ClassNeverInstantiated.Global
     public class ServerInfo {
         [JsonPropertyName("name")]
         public string Name { get; set; }
diff --git a/LibMatrix/LibMatrix.csproj b/LibMatrix/LibMatrix.csproj
index afe06d7..07bd831 100644
--- a/LibMatrix/LibMatrix.csproj
+++ b/LibMatrix/LibMatrix.csproj
@@ -20,13 +20,18 @@
         <!-- This is dangerous, but eases development since locking the version will drift out of sync without noticing,
                 which causes build errors due to missing functions.
                 Using the NuGet version in development is annoying due to delays between pushing and being able to consume.
-                If you want to use a time-appropriate version of the library, recursively clone https://git.rory.gay/matrix/MatrixRoomUtils.git
+                If you want to use a time-appropriate version of the library, recursively clone https://cgit.rory.gay/matrix/MatrixRoomUtils.git
                 instead, since this will be locked by the MatrixRoomUtils project, which contains both LibMatrix and ArcaneLibs as a submodule. -->
         <PackageReference Condition="!Exists('..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj')" Include="ArcaneLibs" Version="*-preview*"/>
+        <ProjectReference Include="..\LibMatrix.EventTypes\LibMatrix.EventTypes.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <Folder Include="EventTypes\" />
     </ItemGroup>
     
     <Target Name="ArcaneLibsNugetWarning" AfterTargets="AfterBuild">
-        <Warning Text="ArcaneLibs is being referenced from NuGet, which is dangerous. Please read the warning in LibMatrix.csproj!" Condition="!Exists('..\..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj')"/>
+        <Warning Text="ArcaneLibs is being referenced from NuGet, which is dangerous. Please read the warning in LibMatrix.csproj!" Condition="!Exists('..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj')"/>
     </Target>
     
 </Project>
diff --git a/LibMatrix/MatrixException.cs b/LibMatrix/MatrixException.cs
index 863c6d4..10f0433 100644
--- a/LibMatrix/MatrixException.cs
+++ b/LibMatrix/MatrixException.cs
@@ -5,10 +5,10 @@ namespace LibMatrix;
 
 public class MatrixException : Exception {
     [JsonPropertyName("errcode")]
-    public string ErrorCode { get; set; }
+    public required string ErrorCode { get; set; }
 
     [JsonPropertyName("error")]
-    public string Error { get; set; }
+    public required string Error { get; set; }
 
     [JsonPropertyName("soft_logout")]
     public bool? SoftLogout { get; set; }
diff --git a/LibMatrix/Responses/Admin/AdminRoomListingResult.cs b/LibMatrix/Responses/Admin/AdminRoomListingResult.cs
index f035184..a90bc6f 100644
--- a/LibMatrix/Responses/Admin/AdminRoomListingResult.cs
+++ b/LibMatrix/Responses/Admin/AdminRoomListingResult.cs
@@ -20,7 +20,7 @@ public class AdminRoomListingResult {
 
     public class AdminRoomListingResultRoom {
         [JsonPropertyName("room_id")]
-        public string RoomId { get; set; }
+        public required string RoomId { get; set; }
 
         [JsonPropertyName("name")]
         public string? Name { get; set; }
@@ -35,10 +35,10 @@ public class AdminRoomListingResult {
         public int JoinedLocalMembers { get; set; }
 
         [JsonPropertyName("version")]
-        public string Version { get; set; }
+        public string? Version { get; set; }
 
         [JsonPropertyName("creator")]
-        public string Creator { get; set; }
+        public string? Creator { get; set; }
 
         [JsonPropertyName("encryption")]
         public string? Encryption { get; set; }
diff --git a/LibMatrix/Responses/CreateRoomRequest.cs b/LibMatrix/Responses/CreateRoomRequest.cs
index 85db517..f8d1d05 100644
--- a/LibMatrix/Responses/CreateRoomRequest.cs
+++ b/LibMatrix/Responses/CreateRoomRequest.cs
@@ -10,28 +10,28 @@ using LibMatrix.Interfaces;
 namespace LibMatrix.Responses;
 
 public class CreateRoomRequest {
-    [JsonIgnore] public CreationContentBaseType _creationContentBaseType;
+    [JsonIgnore] public CreationContentBaseType CreationContentBaseType;
 
-    public CreateRoomRequest() => _creationContentBaseType = new CreationContentBaseType(this);
+    public CreateRoomRequest() => CreationContentBaseType = new CreationContentBaseType(this);
 
     [JsonPropertyName("name")]
-    public string Name { get; set; } = null!;
+    public string? Name { get; set; }
 
     [JsonPropertyName("room_alias_name")]
-    public string RoomAliasName { get; set; } = null!;
+    public string? RoomAliasName { get; set; }
 
     //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!;
+    public List<StateEvent>? InitialState { get; set; }
 
     /// <summary>
     /// One of: ["public", "private"]
     /// </summary>
     [JsonPropertyName("visibility")]
-    public string Visibility { get; set; } = null!;
+    public string? Visibility { get; set; }
 
     [JsonPropertyName("power_level_content_override")]
     public RoomPowerLevelEventContent PowerLevelContentOverride { get; set; } = null!;
@@ -46,25 +46,25 @@ public class CreateRoomRequest {
     ///     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 eventType, string eventKey = ""] {
         get {
-            var stateEvent = InitialState.FirstOrDefault(x => x.Type == event_type && x.StateKey == event_key);
+            var stateEvent = InitialState.FirstOrDefault(x => x.Type == eventType && x.StateKey == eventKey);
             if (stateEvent == null) {
                 InitialState.Add(stateEvent = new StateEvent {
-                    Type = event_type,
-                    StateKey = event_key,
+                    Type = eventType,
+                    StateKey = eventKey,
                     TypedContent = (EventContent)Activator.CreateInstance(
                         StateEvent.KnownStateEventTypes.FirstOrDefault(x =>
                             x.GetCustomAttributes<MatrixEventAttribute>()?
-                                .Any(y => y.EventName == event_type) ?? false) ?? typeof(object)
-                    )
+                                .Any(y => y.EventName == eventType) ?? false) ?? typeof(object)
+                    )!
                 });
             }
 
             return stateEvent;
         }
         set {
-            var stateEvent = InitialState.FirstOrDefault(x => x.Type == event_type && x.StateKey == event_key);
+            var stateEvent = InitialState.FirstOrDefault(x => x.Type == eventType && x.StateKey == eventKey);
             if (stateEvent == null)
                 InitialState.Add(value);
             else
diff --git a/LibMatrix/Responses/CreationContentBaseType.cs b/LibMatrix/Responses/CreationContentBaseType.cs
index ba3ce5e..073bb60 100644
--- a/LibMatrix/Responses/CreationContentBaseType.cs
+++ b/LibMatrix/Responses/CreationContentBaseType.cs
@@ -3,16 +3,16 @@ using System.Text.Json.Serialization;
 namespace LibMatrix.Responses;
 
 public class CreationContentBaseType {
-    private readonly CreateRoomRequest createRoomRequest;
+    private readonly CreateRoomRequest _createRoomRequest;
 
-    public CreationContentBaseType(CreateRoomRequest createRoomRequest) => this.createRoomRequest = createRoomRequest;
+    public CreationContentBaseType(CreateRoomRequest createRoomRequest) => this._createRoomRequest = createRoomRequest;
 
     [JsonPropertyName("type")]
     public string Type {
-        get => (string)createRoomRequest.CreationContent["type"];
+        get => (string)_createRoomRequest.CreationContent["type"];
         set {
-            if (value is "null" or "") createRoomRequest.CreationContent.Remove("type");
-            else createRoomRequest.CreationContent["type"] = value;
+            if (value is "null" or "") _createRoomRequest.CreationContent.Remove("type");
+            else _createRoomRequest.CreationContent["type"] = value;
         }
     }
 }
diff --git a/LibMatrix/Responses/LoginResponse.cs b/LibMatrix/Responses/LoginResponse.cs
index 82004fc..c5d4e87 100644
--- a/LibMatrix/Responses/LoginResponse.cs
+++ b/LibMatrix/Responses/LoginResponse.cs
@@ -1,7 +1,5 @@
-using System.ComponentModel.DataAnnotations;
 using System.Text.Json.Serialization;
 using LibMatrix.Homeservers;
-using LibMatrix.Services;
 
 namespace LibMatrix.Responses;
 
diff --git a/LibMatrix/Responses/SyncResponse.cs b/LibMatrix/Responses/SyncResponse.cs
index d3130bb..42759ff 100644
--- a/LibMatrix/Responses/SyncResponse.cs
+++ b/LibMatrix/Responses/SyncResponse.cs
@@ -50,13 +50,13 @@ public class SyncResponse {
 
         public class LeftRoomDataStructure {
             [JsonPropertyName("account_data")]
-            public EventList AccountData { get; set; }
+            public EventList? AccountData { get; set; }
 
             [JsonPropertyName("timeline")]
             public JoinedRoomDataStructure.TimelineDataStructure? Timeline { get; set; }
 
             [JsonPropertyName("state")]
-            public EventList State { get; set; }
+            public EventList? State { get; set; }
         }
 
         public class JoinedRoomDataStructure {
@@ -99,13 +99,13 @@ public class SyncResponse {
 
             public class SummaryDataStructure {
                 [JsonPropertyName("m.heroes")]
-                public List<string> Heroes { get; set; }
+                public List<string>? Heroes { get; set; }
 
                 [JsonPropertyName("m.invited_member_count")]
-                public int InvitedMemberCount { get; set; }
+                public int? InvitedMemberCount { get; set; }
 
                 [JsonPropertyName("m.joined_member_count")]
-                public int JoinedMemberCount { get; set; }
+                public int? JoinedMemberCount { get; set; }
             }
         }
 
diff --git a/LibMatrix/Responses/UserProfileResponse.cs b/LibMatrix/Responses/UserProfileResponse.cs
index e56e87b..9972a26 100644
--- a/LibMatrix/Responses/UserProfileResponse.cs
+++ b/LibMatrix/Responses/UserProfileResponse.cs
@@ -1,5 +1,4 @@
 using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.Responses;
 
diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index 9804e78..d067f9f 100644
--- a/LibMatrix/RoomTypes/GenericRoom.cs
+++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -4,39 +4,39 @@ using System.Text.Json;
 using System.Text.Json.Serialization;
 using System.Web;
 using ArcaneLibs.Extensions;
+using LibMatrix.EventTypes;
 using LibMatrix.EventTypes.Spec;
 using LibMatrix.EventTypes.Spec.State;
-using LibMatrix.Extensions;
+using LibMatrix.EventTypes.Spec.State.RoomInfo;
 using LibMatrix.Homeservers;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix.RoomTypes;
 
 public class GenericRoom {
     internal readonly AuthenticatedHomeserverGeneric Homeserver;
-    internal readonly MatrixHttpClient _httpClient;
 
     public GenericRoom(AuthenticatedHomeserverGeneric homeserver, string roomId) {
         if (string.IsNullOrWhiteSpace(roomId))
             throw new ArgumentException("Room ID cannot be null or whitespace", nameof(roomId));
         Homeserver = homeserver;
-        _httpClient = homeserver.ClientHttpClient;
         RoomId = roomId;
-        if (GetType() != typeof(SpaceRoom))
+        // if (GetType() != typeof(SpaceRoom))
+        if (GetType() == typeof(GenericRoom)) {
             AsSpace = new SpaceRoom(homeserver, RoomId);
+        }
     }
 
     public string RoomId { get; set; }
 
     public async IAsyncEnumerable<StateEventResponse?> GetFullStateAsync() {
-        var result = _httpClient.GetAsyncEnumerableFromJsonAsync<StateEventResponse>($"/_matrix/client/v3/rooms/{RoomId}/state");
+        var result = Homeserver.ClientHttpClient.GetAsyncEnumerableFromJsonAsync<StateEventResponse>($"/_matrix/client/v3/rooms/{RoomId}/state");
         await foreach (var resp in result) {
             yield return resp;
         }
     }
 
     public async Task<List<StateEventResponse>> GetFullStateAsListAsync() {
-        return await _httpClient.GetFromJsonAsync<List<StateEventResponse>>($"/_matrix/client/v3/rooms/{RoomId}/state");
+        return await Homeserver.ClientHttpClient.GetFromJsonAsync<List<StateEventResponse>>($"/_matrix/client/v3/rooms/{RoomId}/state");
     }
 
     public async Task<T?> GetStateAsync<T>(string type, string stateKey = "") {
@@ -56,7 +56,7 @@ public class GenericRoom {
 
             return resp.Deserialize<T>();
 #else
-            var resp = await _httpClient.GetFromJsonAsync<T>(url);
+            var resp = await Homeserver.ClientHttpClient.GetFromJsonAsync<T>(url);
             return resp;
 #endif
         }
@@ -85,8 +85,8 @@ public class GenericRoom {
         if (!string.IsNullOrWhiteSpace(from)) url += $"&from={from}";
         if (limit is not null) url += $"&limit={limit}";
         if (!string.IsNullOrWhiteSpace(filter)) url += $"&filter={filter}";
-        var res = await _httpClient.GetFromJsonAsync<MessagesResponse>(url);
-        return res ?? new MessagesResponse();
+        var res = await Homeserver.ClientHttpClient.GetFromJsonAsync<MessagesResponse>(url);
+        return res;
     }
 
     /// <summary>
@@ -101,8 +101,8 @@ public class GenericRoom {
                 concat.Add(resp);
                 if (!includeState)
                     resp.State.Clear();
-                from = resp.End;
                 if (resp.End is null) break;
+                from = resp.End;
             }
 
             concat.Reverse();
@@ -131,12 +131,12 @@ public class GenericRoom {
                     resp.State.Clear();
 
                 limit -= resp.Chunk.Count + resp.State.Count;
-                from = resp.End;
                 yield return resp;
                 if (resp.End is null) {
                     Console.WriteLine("End is null");
                     yield break;
                 }
+                from = resp.End;
             }
         }
 
@@ -148,19 +148,19 @@ public class GenericRoom {
     public async Task<RoomIdResponse> JoinAsync(string[]? homeservers = null, string? reason = null, bool checkIfAlreadyMember = true) {
         if (checkIfAlreadyMember) {
             try {
-                var ce = await GetCreateEventAsync();
-                return new() {
+                _ = await GetCreateEventAsync();
+                return new RoomIdResponse {
                     RoomId = RoomId
                 };
             }
             catch { } //ignore
         }
 
-        var join_url = $"/_matrix/client/v3/join/{HttpUtility.UrlEncode(RoomId)}";
-        Console.WriteLine($"Calling {join_url} with {homeservers?.Length ?? 0} via's...");
+        var joinUrl = $"/_matrix/client/v3/join/{HttpUtility.UrlEncode(RoomId)}";
+        Console.WriteLine($"Calling {joinUrl} 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.PostAsJsonAsync(fullJoinUrl, new {
+        var fullJoinUrl = $"{joinUrl}?server_name=" + string.Join("&server_name=", homeservers);
+        var res = await Homeserver.ClientHttpClient.PostAsJsonAsync(fullJoinUrl, new {
             reason
         });
         return await res.Content.ReadFromJsonAsync<RoomIdResponse>() ?? throw new Exception("Failed to join room?");
@@ -168,9 +168,9 @@ public class GenericRoom {
 
     public async IAsyncEnumerable<StateEventResponse> GetMembersAsync(bool joinedOnly = true) {
         var sw = Stopwatch.StartNew();
-        var res = await _httpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/members");
+        var res = await Homeserver.ClientHttpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/members");
         Console.WriteLine($"Members call responded in {sw.GetElapsedAndRestart()}");
-        var resText = await res.Content.ReadAsStringAsync();
+        // var resText = await res.Content.ReadAsStringAsync();
         Console.WriteLine($"Members call response read in {sw.GetElapsedAndRestart()}");
         var result = await JsonSerializer.DeserializeAsync<ChunkedStateEventResponse>(await res.Content.ReadAsStreamAsync(), new JsonSerializerOptions() {
             TypeInfoResolver = ChunkedStateEventResponseSerializerContext.Default,
@@ -188,7 +188,7 @@ public class GenericRoom {
 
     #region Utility shortcuts
 
-    public async Task<EventIdResponse?> SendMessageEventAsync(RoomMessageEventContent content) =>
+    public async Task<EventIdResponse> SendMessageEventAsync(RoomMessageEventContent content) =>
         await SendTimelineEventAsync("m.room.message", content);
 
     public async Task<List<string>?> GetAliasesAsync() {
@@ -259,29 +259,29 @@ public class GenericRoom {
     #region Simple calls
 
     public async Task ForgetAsync() =>
-        await _httpClient.PostAsync($"/_matrix/client/v3/rooms/{RoomId}/forget", null);
+        await Homeserver.ClientHttpClient.PostAsync($"/_matrix/client/v3/rooms/{RoomId}/forget", null);
 
     public async Task LeaveAsync(string? reason = null) =>
-        await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/leave", new {
+        await Homeserver.ClientHttpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/leave", new {
             reason
         });
 
     public async Task KickAsync(string userId, string? reason = null) =>
-        await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/kick",
+        await Homeserver.ClientHttpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/kick",
             new UserIdAndReason { UserId = userId, Reason = reason });
 
     public async Task BanAsync(string userId, string? reason = null) =>
-        await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/ban",
+        await Homeserver.ClientHttpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/ban",
             new UserIdAndReason { UserId = userId, Reason = reason });
 
     public async Task UnbanAsync(string userId) =>
-        await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/unban",
+        await Homeserver.ClientHttpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/unban",
             new UserIdAndReason { UserId = userId });
 
     public async Task InviteUserAsync(string userId, string? reason = null, bool skipExisting = true) {
         if (skipExisting && await GetStateAsync<RoomMemberEventContent>("m.room.member", userId) is not null)
             return;
-        await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/invite", new UserIdAndReason(userId, reason));
+        await Homeserver.ClientHttpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/invite", new UserIdAndReason(userId, reason));
     }
 
     #endregion
@@ -289,19 +289,19 @@ public class GenericRoom {
     #region Events
 
     public async Task<EventIdResponse?> SendStateEventAsync(string eventType, object content) =>
-        await (await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType}", content))
+        await (await Homeserver.ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType}", content))
             .Content.ReadFromJsonAsync<EventIdResponse>();
 
     public async Task<EventIdResponse?> SendStateEventAsync(string eventType, string stateKey, object content) =>
-        await (await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType}/{stateKey}", content))
+        await (await Homeserver.ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType}/{stateKey}", content))
             .Content.ReadFromJsonAsync<EventIdResponse>();
 
-    public async Task<EventIdResponse?> SendTimelineEventAsync(string eventType, TimelineEventContent content) {
-        var res = await _httpClient.PutAsJsonAsync(
+    public async Task<EventIdResponse> SendTimelineEventAsync(string eventType, TimelineEventContent content) {
+        var res = await Homeserver.ClientHttpClient.PutAsJsonAsync(
             $"/_matrix/client/v3/rooms/{RoomId}/send/{eventType}/" + Guid.NewGuid(), content, new JsonSerializerOptions {
                 DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
             });
-        return await res.Content.ReadFromJsonAsync<EventIdResponse>();
+        return await res.Content.ReadFromJsonAsync<EventIdResponse>() ?? throw new Exception("Failed to send event");
     }
 
     public async Task<EventIdResponse?> SendFileAsync(string fileName, Stream fileStream, string messageType = "m.file", string contentType = "application/octet-stream") {
@@ -320,7 +320,7 @@ public class GenericRoom {
     }
 
     public async Task<T?> GetRoomAccountDataAsync<T>(string key) {
-        var res = await _httpClient.GetAsync($"/_matrix/client/v3/user/{Homeserver.UserId}/rooms/{RoomId}/account_data/{key}");
+        var res = await Homeserver.ClientHttpClient.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()}");
@@ -330,7 +330,7 @@ public class GenericRoom {
     }
 
     public async Task SetRoomAccountDataAsync(string key, object data) {
-        var res = await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/user/{Homeserver.UserId}/rooms/{RoomId}/account_data/{key}", data);
+        var res = await Homeserver.ClientHttpClient.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()}");
@@ -338,12 +338,12 @@ public class GenericRoom {
     }
 
     public async Task<T> GetEventAsync<T>(string eventId) {
-        return await _httpClient.GetFromJsonAsync<T>($"/_matrix/client/v3/rooms/{RoomId}/event/{eventId}");
+        return await Homeserver.ClientHttpClient.GetFromJsonAsync<T>($"/_matrix/client/v3/rooms/{RoomId}/event/{eventId}");
     }
 
     public async Task<EventIdResponse> RedactEventAsync(string eventToRedact, string reason) {
         var data = new { reason };
-        return (await (await _httpClient.PutAsJsonAsync(
+        return (await (await Homeserver.ClientHttpClient.PutAsJsonAsync(
             $"/_matrix/client/v3/rooms/{RoomId}/redact/{eventToRedact}/{Guid.NewGuid()}", data)).Content.ReadFromJsonAsync<EventIdResponse>())!;
     }
 
@@ -353,7 +353,7 @@ public class GenericRoom {
 
     public async Task<Dictionary<string, List<string>>> GetMembersByHomeserverAsync(bool joinedOnly = true) {
         if (Homeserver is AuthenticatedHomeserverMxApiExtended mxaeHomeserver)
-            return await Homeserver.ClientHttpClient.GetFromJsonAsync<Dictionary<string, List<string>>>(
+            return await mxaeHomeserver.ClientHttpClient.GetFromJsonAsync<Dictionary<string, List<string>>>(
                 $"/_matrix/client/v3/rooms/{RoomId}/members_by_homeserver?joined_only={joinedOnly}");
         Dictionary<string, List<string>> roomHomeservers = new();
         var members = GetMembersAsync();
diff --git a/LibMatrix/RoomTypes/SpaceRoom.cs b/LibMatrix/RoomTypes/SpaceRoom.cs
index da9fcf8..6ebd62f 100644
--- a/LibMatrix/RoomTypes/SpaceRoom.cs
+++ b/LibMatrix/RoomTypes/SpaceRoom.cs
@@ -4,10 +4,8 @@ using LibMatrix.Homeservers;
 namespace LibMatrix.RoomTypes;
 
 public class SpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomId) : GenericRoom(homeserver, roomId) {
-    private readonly GenericRoom _room;
-
     public async IAsyncEnumerable<GenericRoom> GetChildrenAsync(bool includeRemoved = false) {
-        var rooms = new List<GenericRoom>();
+        // var rooms = new List<GenericRoom>();
         var state = GetFullStateAsync();
         await foreach (var stateEvent in state) {
             if (stateEvent!.Type != "m.space.child") continue;
diff --git a/LibMatrix/Services/HomeserverProviderService.cs b/LibMatrix/Services/HomeserverProviderService.cs
index a42077a..983f469 100644
--- a/LibMatrix/Services/HomeserverProviderService.cs
+++ b/LibMatrix/Services/HomeserverProviderService.cs
@@ -6,20 +6,20 @@ using Microsoft.Extensions.Logging;
 
 namespace LibMatrix.Services;
 
-public class HomeserverProviderService(ILogger<HomeserverProviderService> logger, HomeserverResolverService homeserverResolverService) {
-    private static Dictionary<string, SemaphoreSlim> _authenticatedHomeserverSemaphore = new();
-    private static Dictionary<string, AuthenticatedHomeserverGeneric> _authenticatedHomeserverCache = new();
+public class HomeserverProviderService(ILogger<HomeserverProviderService> logger) {
+    private static readonly Dictionary<string, SemaphoreSlim> AuthenticatedHomeserverSemaphore = new();
+    private static readonly Dictionary<string, AuthenticatedHomeserverGeneric> AuthenticatedHomeserverCache = new();
 
-    private static Dictionary<string, SemaphoreSlim> _remoteHomeserverSemaphore = new();
-    private static Dictionary<string, RemoteHomeserver> _remoteHomeserverCache = new();
+    private static readonly Dictionary<string, SemaphoreSlim> RemoteHomeserverSemaphore = new();
+    private static readonly Dictionary<string, RemoteHomeserver> RemoteHomeserverCache = new();
 
     public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedWithToken(string homeserver, string accessToken, string? proxy = null) {
         var cacheKey = homeserver + accessToken + proxy;
-        var sem = _authenticatedHomeserverSemaphore.GetOrCreate(cacheKey, _ => new SemaphoreSlim(1, 1));
+        var sem = AuthenticatedHomeserverSemaphore.GetOrCreate(cacheKey, _ => new SemaphoreSlim(1, 1));
         await sem.WaitAsync();
         AuthenticatedHomeserverGeneric? hs;
-        lock (_authenticatedHomeserverCache) {
-            if (_authenticatedHomeserverCache.TryGetValue(cacheKey, out hs)) {
+        lock (AuthenticatedHomeserverCache) {
+            if (AuthenticatedHomeserverCache.TryGetValue(cacheKey, out hs)) {
                 sem.Release();
                 return hs;
             }
@@ -30,8 +30,8 @@ public class HomeserverProviderService(ILogger<HomeserverProviderService> logger
         var rhs = await RemoteHomeserver.Create(homeserver, proxy);
         var clientVersions = await rhs.GetClientVersionsAsync();
         if (proxy is not null)
-            Console.WriteLine($"Homeserver {homeserver} proxied via {proxy}...");
-        Console.WriteLine($"{homeserver}: " + clientVersions.ToJson());
+            logger.LogInformation($"Homeserver {homeserver} proxied via {proxy}...");
+        logger.LogInformation($"{homeserver}: " + clientVersions.ToJson());
 
         if (clientVersions.UnstableFeatures.TryGetValue("gay.rory.mxapiextensions.v0", out bool a) && a)
             hs = await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverMxApiExtended>(homeserver, accessToken, proxy);
@@ -43,18 +43,31 @@ public class HomeserverProviderService(ILogger<HomeserverProviderService> logger
                 hs = await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverGeneric>(homeserver, accessToken, proxy);
         }
 
-        lock (_authenticatedHomeserverCache)
-            _authenticatedHomeserverCache[cacheKey] = hs;
+        lock (AuthenticatedHomeserverCache)
+            AuthenticatedHomeserverCache[cacheKey] = hs;
         sem.Release();
 
         return hs;
     }
 
     public async Task<RemoteHomeserver> GetRemoteHomeserver(string homeserver, string? proxy = null) {
-        var hs = await RemoteHomeserver.Create(homeserver, proxy);
-        // hs._httpClient.Dispose();
-        // hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.ServerName) };
-        // hs._httpClient.Timeout = TimeSpan.FromSeconds(120);
+        var cacheKey = homeserver + proxy;
+        var sem = RemoteHomeserverSemaphore.GetOrCreate(cacheKey, _ => new SemaphoreSlim(1, 1));
+        await sem.WaitAsync();
+        RemoteHomeserver? hs;
+        lock (RemoteHomeserverCache) {
+            if (RemoteHomeserverCache.TryGetValue(cacheKey, out hs)) {
+                sem.Release();
+                return hs;
+            }
+        }
+
+        hs = await RemoteHomeserver.Create(homeserver, proxy);
+
+        lock (RemoteHomeserverCache)
+            RemoteHomeserverCache[cacheKey] = hs;
+        sem.Release();
+        
         return hs;
     }
 
@@ -68,4 +81,4 @@ public class HomeserverProviderService(ILogger<HomeserverProviderService> logger
         var data = await resp.Content.ReadFromJsonAsync<LoginResponse>();
         return data!;
     }
-}
+}
\ No newline at end of file
diff --git a/LibMatrix/Services/HomeserverResolverService.cs b/LibMatrix/Services/HomeserverResolverService.cs
index f9f92d6..9f937c5 100644
--- a/LibMatrix/Services/HomeserverResolverService.cs
+++ b/LibMatrix/Services/HomeserverResolverService.cs
@@ -11,15 +11,15 @@ public class HomeserverResolverService(ILogger<HomeserverResolverService>? logge
         Timeout = TimeSpan.FromMilliseconds(10000)
     };
 
-    private static readonly ConcurrentDictionary<string, WellKnownUris> _wellKnownCache = new();
-    private static readonly ConcurrentDictionary<string, SemaphoreSlim> _wellKnownSemaphores = new();
+    private static readonly ConcurrentDictionary<string, WellKnownUris> WellKnownCache = new();
+    private static readonly ConcurrentDictionary<string, SemaphoreSlim> WellKnownSemaphores = new();
 
     public async Task<WellKnownUris> ResolveHomeserverFromWellKnown(string homeserver) {
         if (homeserver is null) throw new ArgumentNullException(nameof(homeserver));
-        _wellKnownSemaphores.TryAdd(homeserver, new(1, 1));
-        await _wellKnownSemaphores[homeserver].WaitAsync();
-        if (_wellKnownCache.TryGetValue(homeserver, out var known)) {
-            _wellKnownSemaphores[homeserver].Release();
+        WellKnownSemaphores.TryAdd(homeserver, new(1, 1));
+        await WellKnownSemaphores[homeserver].WaitAsync();
+        if (WellKnownCache.TryGetValue(homeserver, out var known)) {
+            WellKnownSemaphores[homeserver].Release();
             return known;
         }
 
@@ -28,8 +28,8 @@ public class HomeserverResolverService(ILogger<HomeserverResolverService>? logge
             Client = await _tryResolveFromClientWellknown(homeserver),
             Server = await _tryResolveFromServerWellknown(homeserver)
         };
-        _wellKnownCache.TryAdd(homeserver, res);
-        _wellKnownSemaphores[homeserver].Release();
+        WellKnownCache.TryAdd(homeserver, res);
+        WellKnownSemaphores[homeserver].Release();
         return res;
     }
 
@@ -40,7 +40,9 @@ public class HomeserverResolverService(ILogger<HomeserverResolverService>? logge
             var hs = resp.GetProperty("m.homeserver").GetProperty("base_url").GetString();
             return hs;
         }
-        catch { }
+        catch {
+            // ignored
+        }
 
         logger?.LogInformation("No client well-known...");
         return null;
@@ -51,11 +53,14 @@ public class HomeserverResolverService(ILogger<HomeserverResolverService>? logge
         try {
             var resp = await _httpClient.GetFromJsonAsync<JsonElement>($"{homeserver}/.well-known/matrix/server");
             var hs = resp.GetProperty("m.server").GetString();
+            if(hs is null) throw new InvalidDataException("m.server is null");
             if (!hs.StartsWithAnyOf("http://", "https://"))
                 hs = $"https://{hs}";
             return hs;
         }
-        catch { }
+        catch {
+            // ignored
+        }
 
         // fallback: most servers host these on the same location
         var clientUrl = await _tryResolveFromClientWellknown(homeserver);
diff --git a/LibMatrix/StateEvent.cs b/LibMatrix/StateEvent.cs
index 6ca82f4..cfc7011 100644
--- a/LibMatrix/StateEvent.cs
+++ b/LibMatrix/StateEvent.cs
@@ -1,3 +1,5 @@
+using System.Collections.Frozen;
+using System.Collections.Immutable;
 using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using System.Text.Json;
@@ -7,31 +9,23 @@ using ArcaneLibs;
 using ArcaneLibs.Extensions;
 using LibMatrix.EventTypes;
 using LibMatrix.Extensions;
-using LibMatrix.Interfaces;
 
 namespace LibMatrix;
 
 public class StateEvent {
-    public static List<Type> KnownStateEventTypes { get; } = new ClassCollector<EventContent>().ResolveFromAllAccessibleAssemblies();
+    public static FrozenSet<Type> KnownStateEventTypes { get; } = new ClassCollector<EventContent>().ResolveFromAllAccessibleAssemblies().ToFrozenSet();
 
-    public static readonly Dictionary<string, Type> KnownStateEventTypesByName = KnownStateEventTypes.Aggregate(
+    public static FrozenDictionary<string, Type> KnownStateEventTypesByName { get; } = KnownStateEventTypes.Aggregate(
         new Dictionary<string, Type>(),
         (dict, type) => {
             var attrs = type.GetCustomAttributes<MatrixEventAttribute>();
             foreach (var attr in attrs) {
                 dict[attr.EventName] = type;
             }
-
             return dict;
-        });
-
-    public static Type GetStateEventType(string type) {
-        if (type == "m.receipt") {
-            return typeof(Dictionary<string, JsonObject>);
-        }
+        }).ToFrozenDictionary();
 
-        return KnownStateEventTypesByName.GetValueOrDefault(type) ?? typeof(UnknownEventContent);
-    }
+    public static Type GetStateEventType(string type) => KnownStateEventTypesByName.GetValueOrDefault(type) ?? typeof(UnknownEventContent);
 
     private static readonly JsonSerializerOptions TypedContentSerializerOptions = new JsonSerializerOptions() {
         Converters = {
@@ -80,13 +74,7 @@ public class StateEvent {
     [JsonPropertyName("content")]
     public JsonObject? RawContent {
         get => _rawContent;
-        set {
-            _rawContent = value;
-            // if (Type is not null && this is StateEventResponse stateEventResponse) {
-            //     if (File.Exists($"unknown_state_events/{Type}/{stateEventResponse.EventId}.json")) return;
-            //     var x = GetType.Name;
-            // }
-        }
+        set => _rawContent = value;
     }
 
     [JsonIgnore]
@@ -139,22 +127,22 @@ public class StateEvent {
 
 public class StateEventResponse : StateEvent {
     [JsonPropertyName("origin_server_ts")]
-    public ulong OriginServerTs { get; set; }
+    public ulong? OriginServerTs { get; set; }
 
     [JsonPropertyName("room_id")]
-    public string RoomId { get; set; }
+    public string? RoomId { get; set; }
 
     [JsonPropertyName("sender")]
-    public string Sender { get; set; }
+    public string? Sender { get; set; }
 
     [JsonPropertyName("unsigned")]
     public UnsignedData? Unsigned { get; set; }
 
     [JsonPropertyName("event_id")]
-    public string EventId { get; set; }
+    public string? EventId { get; set; }
 
     [JsonPropertyName("replaces_state")]
-    public new string ReplacesState { get; set; }
+    public new string? ReplacesState { get; set; }
 
     public class UnsignedData {
         [JsonPropertyName("age")]
@@ -179,8 +167,7 @@ public class StateEventResponse : StateEvent {
 
 [JsonSourceGenerationOptions(WriteIndented = true)]
 [JsonSerializable(typeof(ChunkedStateEventResponse))]
-internal partial class ChunkedStateEventResponseSerializerContext : JsonSerializerContext {
-}
+internal partial class ChunkedStateEventResponseSerializerContext : JsonSerializerContext;
 
 public class EventList {
     [JsonPropertyName("events")]
diff --git a/LibMatrix/UserIdAndReason.cs b/LibMatrix/UserIdAndReason.cs
index 09dc461..c76ecc7 100644
--- a/LibMatrix/UserIdAndReason.cs
+++ b/LibMatrix/UserIdAndReason.cs
@@ -2,7 +2,7 @@ using System.Text.Json.Serialization;
 
 namespace LibMatrix;
 
-internal class UserIdAndReason(string? userId = null, string? reason = null) {
+internal class UserIdAndReason(string userId = null!, string reason = null!) {
     [JsonPropertyName("user_id")]
     public string UserId { get; set; } = userId;
 
diff --git a/LibMatrix/WhoAmIResponse.cs b/LibMatrix/WhoAmIResponse.cs
index 4eb5f2e..e2ea118 100644
--- a/LibMatrix/WhoAmIResponse.cs
+++ b/LibMatrix/WhoAmIResponse.cs
@@ -4,7 +4,7 @@ namespace LibMatrix;
 
 public class WhoAmIResponse {
     [JsonPropertyName("user_id")]
-    public string? UserId { get; set; } = null!;
+    public required string UserId { get; set; }
 
     [JsonPropertyName("device_id")]
     public string? DeviceId { get; set; }