about summary refs log tree commit diff
path: root/LibMatrix
diff options
context:
space:
mode:
Diffstat (limited to 'LibMatrix')
-rw-r--r--LibMatrix/EventIdResponse.cs2
-rw-r--r--LibMatrix/EventTypes/Common/MjolnirShortcodeEventContent.cs10
-rw-r--r--LibMatrix/EventTypes/Common/RoomEmotesEventContent.cs25
-rw-r--r--LibMatrix/EventTypes/MatrixEventAttribute.cs7
-rw-r--r--LibMatrix/EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs22
-rw-r--r--LibMatrix/EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs12
-rw-r--r--LibMatrix/EventTypes/Spec/RoomMessageEventContent.cs47
-rw-r--r--LibMatrix/EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs75
-rw-r--r--LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAliasEventContent.cs12
-rw-r--r--LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAvatarEventContent.cs29
-rw-r--r--LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs15
-rw-r--r--LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs32
-rw-r--r--LibMatrix/EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs14
-rw-r--r--LibMatrix/EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs15
-rw-r--r--LibMatrix/EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs10
-rw-r--r--LibMatrix/EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs29
-rw-r--r--LibMatrix/EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs30
-rw-r--r--LibMatrix/EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs12
-rw-r--r--LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs10
-rw-r--r--LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs79
-rw-r--r--LibMatrix/EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs16
-rw-r--r--LibMatrix/EventTypes/Spec/State/RoomInfo/RoomTopicEventContent.cs11
-rw-r--r--LibMatrix/EventTypes/Spec/State/Space/SpaceChildEventContent.cs14
-rw-r--r--LibMatrix/EventTypes/Spec/State/Space/SpaceParentEventContent.cs13
-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/Interfaces/EventContent.cs48
-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
49 files changed, 173 insertions, 748 deletions
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/Common/MjolnirShortcodeEventContent.cs b/LibMatrix/EventTypes/Common/MjolnirShortcodeEventContent.cs
deleted file mode 100644
index a09a393..0000000
--- a/LibMatrix/EventTypes/Common/MjolnirShortcodeEventContent.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Common;
-
-[MatrixEvent(EventName = "org.matrix.mjolnir.shortcode")]
-public class MjolnirShortcodeEventContent : TimelineEventContent {
-    [JsonPropertyName("shortcode")]
-    public string? Shortcode { get; set; }
-}
diff --git a/LibMatrix/EventTypes/Common/RoomEmotesEventContent.cs b/LibMatrix/EventTypes/Common/RoomEmotesEventContent.cs
deleted file mode 100644
index 8d05a2e..0000000
--- a/LibMatrix/EventTypes/Common/RoomEmotesEventContent.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Common;
-
-[MatrixEvent(EventName = "im.ponies.room_emotes")]
-public class RoomEmotesEventContent : TimelineEventContent {
-    [JsonPropertyName("emoticons")]
-    public Dictionary<string, EmoticonData>? Emoticons { get; set; }
-
-    [JsonPropertyName("images")]
-    public Dictionary<string, EmoticonData>? Images { get; set; }
-
-    [JsonPropertyName("pack")]
-    public PackInfo? Pack { get; set; }
-
-    public class EmoticonData {
-        [JsonPropertyName("url")]
-        public string? Url { get; set; }
-    }
-
-    public class PackInfo {
-
-    }
-}
diff --git a/LibMatrix/EventTypes/MatrixEventAttribute.cs b/LibMatrix/EventTypes/MatrixEventAttribute.cs
deleted file mode 100644
index 92334d0..0000000
--- a/LibMatrix/EventTypes/MatrixEventAttribute.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace LibMatrix.EventTypes;
-
-[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
-public class MatrixEventAttribute : Attribute {
-    public 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
deleted file mode 100644
index 8ffbca5..0000000
--- a/LibMatrix/EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-[MatrixEvent(EventName = EventId)]
-public class PresenceEventContent : EventContent {
-    public const string EventId = "m.presence";
-
-    [JsonPropertyName("presence"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
-    public string? Presence { get; set; }
-    [JsonPropertyName("last_active_ago")]
-    public long LastActiveAgo { get; set; }
-    [JsonPropertyName("currently_active")]
-    public bool CurrentlyActive { get; set; }
-    [JsonPropertyName("status_msg"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
-    public string? StatusMessage { get; set; }
-    [JsonPropertyName("avatar_url"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
-    public string? AvatarUrl { get; set; }
-    [JsonPropertyName("displayname"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
-    public string? DisplayName { get; set; }
-}
diff --git a/LibMatrix/EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs b/LibMatrix/EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs
deleted file mode 100644
index b947096..0000000
--- a/LibMatrix/EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-[MatrixEvent(EventName = EventId)]
-public class RoomTypingEventContent : TimelineEventContent {
-    public const string EventId = "m.typing";
-
-    [JsonPropertyName("user_ids")]
-    public string[]? UserIds { get; set; }
-}
diff --git a/LibMatrix/EventTypes/Spec/RoomMessageEventContent.cs b/LibMatrix/EventTypes/Spec/RoomMessageEventContent.cs
deleted file mode 100644
index 944ed99..0000000
--- a/LibMatrix/EventTypes/Spec/RoomMessageEventContent.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec;
-
-[MatrixEvent(EventName = EventId)]
-public class RoomMessageEventContent : TimelineEventContent {
-    public const string EventId = "m.room.message";
-
-    public RoomMessageEventContent(string? messageType = "m.notice", string? body = null) {
-        MessageType = messageType;
-        Body = body;
-    }
-
-    [JsonPropertyName("body")]
-    public string Body { get; set; }
-
-    [JsonPropertyName("msgtype")]
-    public string MessageType { get; set; } = "m.notice";
-
-    [JsonPropertyName("formatted_body")]
-    public string? FormattedBody { get; set; }
-
-    [JsonPropertyName("format")]
-    public string? Format { get; set; }
-
-    /// <summary>
-    /// Media URI for this message, if any
-    /// </summary>
-    [JsonPropertyName("url")]
-    public string? Url { get; set; }
-
-    public string? FileName { get; set; }
-
-    [JsonPropertyName("info")]
-    public FileInfoStruct? FileInfo { get; set; }
-
-    public class FileInfoStruct {
-        [JsonPropertyName("mimetype")]
-        public string? MimeType { get; set; }
-        [JsonPropertyName("size")]
-        public long Size { get; set; }
-        [JsonPropertyName("thumbnail_url")]
-        public string? ThumbnailUrl { get; set; }
-    }
-
-}
diff --git a/LibMatrix/EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs b/LibMatrix/EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
deleted file mode 100644
index 80d87d6..0000000
--- a/LibMatrix/EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-//spec
-[MatrixEvent(EventName = EventId)] //spec
-[MatrixEvent(EventName = "m.room.rule.server")] //???
-[MatrixEvent(EventName = "org.matrix.mjolnir.rule.server")] //legacy
-public class ServerPolicyRuleEventContent : PolicyRuleEventContent {
-    public const string EventId = "m.policy.rule.server";
-}
-
-[MatrixEvent(EventName = EventId)] //spec
-[MatrixEvent(EventName = "m.room.rule.user")] //???
-[MatrixEvent(EventName = "org.matrix.mjolnir.rule.user")] //legacy
-public class UserPolicyRuleEventContent : PolicyRuleEventContent {
-    public const string EventId = "m.policy.rule.user";
-}
-
-[MatrixEvent(EventName = EventId)] //spec
-[MatrixEvent(EventName = "m.room.rule.room")] //???
-[MatrixEvent(EventName = "org.matrix.mjolnir.rule.room")] //legacy
-public class RoomPolicyRuleEventContent : PolicyRuleEventContent {
-    public const string EventId = "m.policy.rule.room";
-}
-
-public abstract class PolicyRuleEventContent : EventContent {
-    /// <summary>
-    ///     Entity this ban applies to, can use * and ? as globs.
-    ///     Policy is invalid if entity is null
-    /// </summary>
-    [JsonPropertyName("entity")]
-    public string? Entity { get; set; }
-
-    /// <summary>
-    ///     Reason this user is banned
-    /// </summary>
-    [JsonPropertyName("reason")]
-    public string? Reason { get; set; }
-
-    /// <summary>
-    ///     Suggested action to take
-    /// </summary>
-    [JsonPropertyName("recommendation")]
-    public string? Recommendation { get; set; }
-
-    /// <summary>
-    ///     Expiry time in milliseconds since the unix epoch, or null if the ban has no expiry.
-    /// </summary>
-    [JsonPropertyName("support.feline.policy.expiry.rev.2")] //stable prefix: expiry, msc pending
-    public long? Expiry { get; set; }
-
-    //utils
-    /// <summary>
-    ///     Readable expiry time, provided for easy interaction
-    /// </summary>
-    [JsonPropertyName("gay.rory.matrix_room_utils.readable_expiry_time_utc")]
-    public DateTime? ExpiryDateTime {
-        get => Expiry == null ? null : DateTimeOffset.FromUnixTimeMilliseconds(Expiry.Value).DateTime;
-        set => Expiry = ((DateTimeOffset)value).ToUnixTimeMilliseconds();
-    }
-}
-
-public static class PolicyRecommendationTypes {
-    /// <summary>
-    ///     Ban this user
-    /// </summary>
-    public static string Ban = "m.ban";
-
-    /// <summary>
-    ///     Mute this user
-    /// </summary>
-    public static string Mute = "support.feline.policy.recommendation_mute"; //stable prefix: m.mute, msc pending
-}
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAliasEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAliasEventContent.cs
deleted file mode 100644
index 830386d..0000000
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAliasEventContent.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-[MatrixEvent(EventName = EventId)]
-public class RoomAliasEventContent : TimelineEventContent {
-    public const string EventId = "m.room.alias";
-
-    [JsonPropertyName("aliases")]
-    public List<string>? Aliases { get; set; }
-}
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAvatarEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAvatarEventContent.cs
deleted file mode 100644
index 9c208ba..0000000
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAvatarEventContent.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-[MatrixEvent(EventName = EventId)]
-public class RoomAvatarEventContent : TimelineEventContent {
-    public const string EventId = "m.room.avatar";
-
-    [JsonPropertyName("url")]
-    public string? Url { get; set; }
-
-    [JsonPropertyName("info")]
-    public RoomAvatarInfo? Info { get; set; }
-
-    public class RoomAvatarInfo {
-        [JsonPropertyName("h")]
-        public int? Height { get; set; }
-
-        [JsonPropertyName("w")]
-        public int? Width { get; set; }
-
-        [JsonPropertyName("mimetype")]
-        public string? MimeType { get; set; }
-
-        [JsonPropertyName("size")]
-        public int? Size { get; set; }
-    }
-}
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs
deleted file mode 100644
index 5ba253c..0000000
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-[MatrixEvent(EventName = EventId)]
-public class RoomCanonicalAliasEventContent : TimelineEventContent {
-    public const string EventId = "m.room.canonical_alias";
-
-    [JsonPropertyName("alias")]
-    public string? Alias { get; set; }
-
-    [JsonPropertyName("alt_aliases")]
-    public string[]? AltAliases { get; set; }
-}
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs
deleted file mode 100644
index 41145de..0000000
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-[MatrixEvent(EventName = EventId)]
-public class RoomCreateEventContent : TimelineEventContent {
-    public const string EventId = "m.room.create";
-
-    [JsonPropertyName("room_version")]
-    public string? RoomVersion { get; set; }
-
-    [JsonPropertyName("creator")]
-    public string? Creator { get; set; }
-
-    [JsonPropertyName("m.federate")]
-    public bool? Federate { get; set; }
-
-    [JsonPropertyName("predecessor")]
-    public RoomCreatePredecessor? Predecessor { get; set; }
-
-    [JsonPropertyName("type")]
-    public string? Type { get; set; }
-
-    public class RoomCreatePredecessor {
-        [JsonPropertyName("room_id")]
-        public string? RoomId { get; set; }
-
-        [JsonPropertyName("event_id")]
-        public string? EventId { get; set; }
-    }
-}
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs
deleted file mode 100644
index a3627f2..0000000
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-[MatrixEvent(EventName = "m.room.encryption")]
-public class RoomEncryptionEventContent : TimelineEventContent {
-    [JsonPropertyName("algorithm")]
-    public string? Algorithm { get; set; }
-    [JsonPropertyName("rotation_period_ms")]
-    public ulong? RotationPeriodMs { get; set; }
-    [JsonPropertyName("rotation_period_msgs")]
-    public ulong? RotationPeriodMsgs { get; set; }
-}
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs
deleted file mode 100644
index 5bad649..0000000
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-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 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
deleted file mode 100644
index 8f5c7f1..0000000
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-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; }
-}
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/Spec/State/RoomInfo/RoomMemberEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs
deleted file mode 100644
index 698315e..0000000
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-[MatrixEvent(EventName = EventId)]
-public class RoomMemberEventContent : TimelineEventContent {
-    public const string EventId = "m.room.member";
-
-    [JsonPropertyName("reason")]
-    public string? Reason { get; set; }
-
-    [JsonPropertyName("membership")]
-    public string Membership { get; set; } = null!;
-
-    [JsonPropertyName("displayname")]
-    public string? DisplayName { get; set; }
-
-    [JsonPropertyName("is_direct")]
-    public bool? IsDirect { get; set; }
-
-    [JsonPropertyName("avatar_url")]
-    public string? AvatarUrl { get; set; }
-
-    [JsonPropertyName("kind")]
-    public string? Kind { get; set; }
-
-    [JsonPropertyName("join_authorised_via_users_server")]
-    public string? JoinAuthorisedViaUsersServer { get; set; }
-}
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs
deleted file mode 100644
index 9ad67eb..0000000
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-[MatrixEvent(EventName = EventId)]
-public class RoomNameEventContent : TimelineEventContent {
-    public const string EventId = "m.room.name";
-
-    [JsonPropertyName("name")]
-    public string? Name { get; set; }
-}
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs
deleted file mode 100644
index 11fe208..0000000
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-[MatrixEvent(EventName = "m.room.pinned_events")]
-public class RoomPinnedEventContent : TimelineEventContent {
-    [JsonPropertyName("pinned")]
-    public string[]? PinnedEvents { get; set; }
-}
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs
deleted file mode 100644
index 08f8ad5..0000000
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-[MatrixEvent(EventName = EventId)]
-public class RoomPowerLevelEventContent : TimelineEventContent {
-    public const string EventId = "m.room.power_levels";
-
-    [JsonPropertyName("ban")]
-    public long? Ban { get; set; } = 50;
-
-    [JsonPropertyName("events_default")]
-    public long? EventsDefault { get; set; } = 0;
-
-    [JsonPropertyName("invite")]
-    public long? Invite { get; set; } = 0;
-
-    [JsonPropertyName("kick")]
-    public long? Kick { get; set; } = 50;
-
-    [JsonPropertyName("notifications")]
-    public NotificationsPL? NotificationsPl { get; set; } // = null!;
-
-    [JsonPropertyName("redact")]
-    public long? Redact { get; set; } = 50;
-
-    [JsonPropertyName("state_default")]
-    public long? StateDefault { get; set; } = 50;
-
-    [JsonPropertyName("events")]
-    public Dictionary<string, long>? Events { get; set; } // = null!;
-
-    [JsonPropertyName("users")]
-    public Dictionary<string, long>? Users { get; set; } // = null!;
-
-    [JsonPropertyName("users_default")]
-    public long? UsersDefault { get; set; } = 0;
-
-    [Obsolete("Historical was a key related to MSC2716, a spec change on backfill that was dropped!", true)]
-    [JsonIgnore]
-    [JsonPropertyName("historical")]
-    public long Historical { get; set; } // = 50;
-
-    public class NotificationsPL {
-        [JsonPropertyName("room")]
-        public long Room { get; set; } = 50;
-    }
-
-    public bool IsUserAdmin(string userId) {
-        if (userId is null) throw new ArgumentNullException(nameof(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));
-        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));
-        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));
-        return Users.TryGetValue(userId, out var level) ? level : UsersDefault ?? UsersDefault ?? 0;
-    }
-
-    public long GetEventPowerLevel(string eventType) {
-        return Events.TryGetValue(eventType, out var level) ? level : EventsDefault ?? EventsDefault ?? 0;
-    }
-
-    public void SetUserPowerLevel(string userId, long powerLevel) {
-        if (userId is null) throw new ArgumentNullException(nameof(userId));
-        Users ??= new();
-        Users[userId] = powerLevel;
-    }
-}
diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs
deleted file mode 100644
index cbd2241..0000000
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-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!;
-
-    [JsonPropertyName("deny")]
-    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
deleted file mode 100644
index 866eecf..0000000
--- a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomTopicEventContent.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-[MatrixEvent(EventName = "m.room.topic")]
-[MatrixEvent(EventName = "org.matrix.msc3765.topic", Legacy = true)]
-public class RoomTopicEventContent : TimelineEventContent {
-    [JsonPropertyName("topic")]
-    public string? Topic { get; set; }
-}
diff --git a/LibMatrix/EventTypes/Spec/State/Space/SpaceChildEventContent.cs b/LibMatrix/EventTypes/Spec/State/Space/SpaceChildEventContent.cs
deleted file mode 100644
index 82f4b7f..0000000
--- a/LibMatrix/EventTypes/Spec/State/Space/SpaceChildEventContent.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-[MatrixEvent(EventName = "m.space.child")]
-public class SpaceChildEventContent : TimelineEventContent {
-    [JsonPropertyName("auto_join")]
-    public bool? AutoJoin { get; set; }
-    [JsonPropertyName("via")]
-    public List<string>? Via { get; set; }
-    [JsonPropertyName("suggested")]
-    public bool? Suggested { get; set; }
-}
diff --git a/LibMatrix/EventTypes/Spec/State/Space/SpaceParentEventContent.cs b/LibMatrix/EventTypes/Spec/State/Space/SpaceParentEventContent.cs
deleted file mode 100644
index 887e91c..0000000
--- a/LibMatrix/EventTypes/Spec/State/Space/SpaceParentEventContent.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Text.Json.Serialization;
-using LibMatrix.Interfaces;
-
-namespace LibMatrix.EventTypes.Spec.State;
-
-[MatrixEvent(EventName = "m.space.parent")]
-public class SpaceParentEventContent : TimelineEventContent {
-    [JsonPropertyName("via")]
-    public string[]? Via { get; set; }
-
-    [JsonPropertyName("canonical")]
-    public bool? Canonical { 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/Interfaces/EventContent.cs b/LibMatrix/Interfaces/EventContent.cs
deleted file mode 100644
index 76419a6..0000000
--- a/LibMatrix/Interfaces/EventContent.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System.Text.Json;
-using System.Text.Json.Nodes;
-using System.Text.Json.Serialization;
-
-namespace LibMatrix.Interfaces;
-
-public abstract class EventContent { }
-
-public abstract class TimelineEventContent : EventContent {
-    [JsonPropertyName("m.relates_to")]
-    public MessageRelatesTo? RelatesTo { get; set; }
-
-    [JsonPropertyName("m.new_content")]
-    public JsonObject? NewContent { get; set; }
-
-    public TimelineEventContent SetReplaceRelation(string eventId) {
-        NewContent = JsonSerializer.SerializeToNode(this, GetType()).AsObject();
-        // NewContent = JsonSerializer.Deserialize(jsonText, GetType());
-        RelatesTo = new() {
-            RelationType = "m.replace",
-            EventId = eventId
-        };
-        return this;
-    }
-
-    public T SetReplaceRelation<T>(string eventId) where T : TimelineEventContent {
-        return SetReplaceRelation(eventId) as T ?? throw new InvalidOperationException();
-    }
-
-    public class MessageRelatesTo {
-        [JsonPropertyName("m.in_reply_to")]
-        public EventInReplyTo? InReplyTo { get; set; }
-
-        [JsonPropertyName("event_id")]
-        public string? EventId { get; set; }
-
-        [JsonPropertyName("rel_type")]
-        public string? RelationType { get; set; }
-
-        public class EventInReplyTo {
-            [JsonPropertyName("event_id")]
-            public string EventId { get; set; }
-
-            [JsonPropertyName("rel_type")]
-            public string RelType { 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; }