about summary refs log tree commit diff
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
parentTemp state (diff)
downloadLibMatrix-5affd9f061e75f6575a2fe6715f9e8757cfe87e8.tar.xz
Cleanup
m---------ArcaneLibs0
-rw-r--r--ExampleBots/LibMatrix.ExampleBot/Bot/Commands/CmdCommand.cs1
-rw-r--r--ExampleBots/LibMatrix.ExampleBot/Bot/FileStorageProvider.cs1
-rw-r--r--ExampleBots/LibMatrix.ExampleBot/Bot/Interfaces/CommandContext.cs1
-rw-r--r--ExampleBots/LibMatrix.ExampleBot/Bot/MRUBot.cs1
-rw-r--r--ExampleBots/LibMatrix.ExampleBot/Bot/StartupTasks/ServerRoomSizeCalulator.cs2
-rw-r--r--ExampleBots/LibMatrix.ExampleBot/Program.cs2
-rw-r--r--ExampleBots/ModerationBot/Commands/BanMediaCommand.cs2
-rw-r--r--ExampleBots/ModerationBot/Commands/DbgAllRoomsArePolicyListsCommand.cs5
-rw-r--r--ExampleBots/ModerationBot/Commands/DbgDumpActivePoliciesCommand.cs5
-rw-r--r--ExampleBots/ModerationBot/Commands/DbgDumpAllStateTypesCommand.cs4
-rw-r--r--ExampleBots/ModerationBot/Commands/JoinRoomCommand.cs5
-rw-r--r--ExampleBots/ModerationBot/Commands/JoinSpaceMembersCommand.cs4
-rw-r--r--ExampleBots/ModerationBot/FirstRunTasks.cs1
-rw-r--r--ExampleBots/ModerationBot/ModerationBot.cs19
-rw-r--r--ExampleBots/ModerationBot/PolicyEngine.cs15
-rw-r--r--ExampleBots/ModerationBot/PolicyList.cs1
-rw-r--r--ExampleBots/ModerationBot/StateEventTypes/Policies/BasePolicy.cs1
-rw-r--r--ExampleBots/ModerationBot/StateEventTypes/Policies/Implementations/MediaPolicyHomeserver.cs1
-rw-r--r--ExampleBots/ModerationBot/StateEventTypes/Policies/Implementations/MessagePolicyContainsText.cs1
-rw-r--r--ExampleBots/ModerationBot/StateEventTypes/Policies/Implementations/UnknownPolicy.cs3
-rw-r--r--ExampleBots/PluralContactBotPoC/Bot/AccountData/BotData.cs1
-rw-r--r--ExampleBots/PluralContactBotPoC/Bot/AccountData/SystemData.cs3
-rw-r--r--ExampleBots/PluralContactBotPoC/Program.cs2
-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
-rw-r--r--Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs3
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomEventTests.cs7
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests.cs1
-rw-r--r--Tests/LibMatrix.Tests/Tests/TestCleanup.cs6
-rw-r--r--Tests/TestDataGenerator/Bot/DataFetcher.cs27
-rw-r--r--Tests/TestDataGenerator/Bot/DataFetcherConfiguration.cs2
-rw-r--r--Tests/TestDataGenerator/Program.cs8
-rw-r--r--Utilities/LibMatrix.DebugDataValidationApi/Controllers/ValidationController.cs5
-rw-r--r--Utilities/LibMatrix.JsonSerializerContextGenerator/EventSerializerContexts.g.cs99
-rw-r--r--Utilities/LibMatrix.JsonSerializerContextGenerator/Program.cs28
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/AppServiceConfiguration.cs4
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/BotCommandInstaller.cs4
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/FileStorageProvider.cs4
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs6
91 files changed, 332 insertions, 460 deletions
diff --git a/ArcaneLibs b/ArcaneLibs
-Subproject 788516bf3253902b56dfe335c3d2166733969ba
+Subproject 28c0e5a7c7fdf306901ad20bb324efa697db222
diff --git a/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/CmdCommand.cs b/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/CmdCommand.cs
index ea42597..e690890 100644
--- a/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/CmdCommand.cs
+++ b/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/CmdCommand.cs
@@ -1,4 +1,3 @@
-using ArcaneLibs.Extensions;
 using ArcaneLibs.StringNormalisation;
 using LibMatrix.EventTypes.Spec;
 using LibMatrix.ExampleBot.Bot.Interfaces;
diff --git a/ExampleBots/LibMatrix.ExampleBot/Bot/FileStorageProvider.cs b/ExampleBots/LibMatrix.ExampleBot/Bot/FileStorageProvider.cs
index 935d53f..2c014de 100644
--- a/ExampleBots/LibMatrix.ExampleBot/Bot/FileStorageProvider.cs
+++ b/ExampleBots/LibMatrix.ExampleBot/Bot/FileStorageProvider.cs
@@ -1,6 +1,5 @@
 using System.Text.Json;
 using ArcaneLibs.Extensions;
-using LibMatrix.Extensions;
 using LibMatrix.Interfaces.Services;
 using Microsoft.Extensions.Logging;
 
diff --git a/ExampleBots/LibMatrix.ExampleBot/Bot/Interfaces/CommandContext.cs b/ExampleBots/LibMatrix.ExampleBot/Bot/Interfaces/CommandContext.cs
index 9b6ef7a..6dbb7f9 100644
--- a/ExampleBots/LibMatrix.ExampleBot/Bot/Interfaces/CommandContext.cs
+++ b/ExampleBots/LibMatrix.ExampleBot/Bot/Interfaces/CommandContext.cs
@@ -1,5 +1,4 @@
 using LibMatrix.EventTypes.Spec;
-using LibMatrix.Responses;
 using LibMatrix.RoomTypes;
 
 namespace LibMatrix.ExampleBot.Bot.Interfaces;
diff --git a/ExampleBots/LibMatrix.ExampleBot/Bot/MRUBot.cs b/ExampleBots/LibMatrix.ExampleBot/Bot/MRUBot.cs
index 8cf4f1f..8e6cd6a 100644
--- a/ExampleBots/LibMatrix.ExampleBot/Bot/MRUBot.cs
+++ b/ExampleBots/LibMatrix.ExampleBot/Bot/MRUBot.cs
@@ -3,7 +3,6 @@ using ArcaneLibs.Extensions;
 using LibMatrix.EventTypes.Spec;
 using LibMatrix.EventTypes.Spec.State;
 using LibMatrix.ExampleBot.Bot.Interfaces;
-using LibMatrix.Extensions;
 using LibMatrix.Helpers;
 using LibMatrix.Homeservers;
 using LibMatrix.Services;
diff --git a/ExampleBots/LibMatrix.ExampleBot/Bot/StartupTasks/ServerRoomSizeCalulator.cs b/ExampleBots/LibMatrix.ExampleBot/Bot/StartupTasks/ServerRoomSizeCalulator.cs
index 890db85..2f6e0b0 100644
--- a/ExampleBots/LibMatrix.ExampleBot/Bot/StartupTasks/ServerRoomSizeCalulator.cs
+++ b/ExampleBots/LibMatrix.ExampleBot/Bot/StartupTasks/ServerRoomSizeCalulator.cs
@@ -1,9 +1,7 @@
 using System.Diagnostics.CodeAnalysis;
-using ArcaneLibs.Extensions;
 using LibMatrix.ExampleBot.Bot.Interfaces;
 using LibMatrix.Homeservers;
 using LibMatrix.Services;
-using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
 
diff --git a/ExampleBots/LibMatrix.ExampleBot/Program.cs b/ExampleBots/LibMatrix.ExampleBot/Program.cs
index 6d8775e..25ce07d 100644
--- a/ExampleBots/LibMatrix.ExampleBot/Program.cs
+++ b/ExampleBots/LibMatrix.ExampleBot/Program.cs
@@ -3,8 +3,6 @@
 using ArcaneLibs;
 using LibMatrix.ExampleBot.Bot;
 using LibMatrix.ExampleBot.Bot.Interfaces;
-using LibMatrix.ExampleBot.Bot.StartupTasks;
-using LibMatrix.Extensions;
 using LibMatrix.Services;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
diff --git a/ExampleBots/ModerationBot/Commands/BanMediaCommand.cs b/ExampleBots/ModerationBot/Commands/BanMediaCommand.cs
index 9e49b22..535fd4f 100644
--- a/ExampleBots/ModerationBot/Commands/BanMediaCommand.cs
+++ b/ExampleBots/ModerationBot/Commands/BanMediaCommand.cs
@@ -1,4 +1,3 @@
-using System.Buffers.Text;
 using System.Security.Cryptography;
 using ArcaneLibs.Extensions;
 using LibMatrix;
@@ -7,7 +6,6 @@ using LibMatrix.Helpers;
 using LibMatrix.Services;
 using LibMatrix.Utilities.Bot.Interfaces;
 using ModerationBot.AccountData;
-using ModerationBot.StateEventTypes;
 using ModerationBot.StateEventTypes.Policies.Implementations;
 
 namespace ModerationBot.Commands;
diff --git a/ExampleBots/ModerationBot/Commands/DbgAllRoomsArePolicyListsCommand.cs b/ExampleBots/ModerationBot/Commands/DbgAllRoomsArePolicyListsCommand.cs
index 327a9a4..f578f53 100644
--- a/ExampleBots/ModerationBot/Commands/DbgAllRoomsArePolicyListsCommand.cs
+++ b/ExampleBots/ModerationBot/Commands/DbgAllRoomsArePolicyListsCommand.cs
@@ -1,14 +1,9 @@
-using System.Buffers.Text;
-using System.Security.Cryptography;
-using ArcaneLibs.Extensions;
-using LibMatrix;
 using LibMatrix.EventTypes.Spec;
 using LibMatrix.Helpers;
 using LibMatrix.RoomTypes;
 using LibMatrix.Services;
 using LibMatrix.Utilities.Bot.Interfaces;
 using ModerationBot.AccountData;
-using ModerationBot.StateEventTypes;
 
 namespace ModerationBot.Commands;
 
diff --git a/ExampleBots/ModerationBot/Commands/DbgDumpActivePoliciesCommand.cs b/ExampleBots/ModerationBot/Commands/DbgDumpActivePoliciesCommand.cs
index 35c95f8..285d792 100644
--- a/ExampleBots/ModerationBot/Commands/DbgDumpActivePoliciesCommand.cs
+++ b/ExampleBots/ModerationBot/Commands/DbgDumpActivePoliciesCommand.cs
@@ -1,14 +1,9 @@
-using System.Buffers.Text;
-using System.Security.Cryptography;
 using ArcaneLibs.Extensions;
-using LibMatrix;
 using LibMatrix.EventTypes.Spec;
-using LibMatrix.Helpers;
 using LibMatrix.RoomTypes;
 using LibMatrix.Services;
 using LibMatrix.Utilities.Bot.Interfaces;
 using ModerationBot.AccountData;
-using ModerationBot.StateEventTypes;
 
 namespace ModerationBot.Commands;
 
diff --git a/ExampleBots/ModerationBot/Commands/DbgDumpAllStateTypesCommand.cs b/ExampleBots/ModerationBot/Commands/DbgDumpAllStateTypesCommand.cs
index 0013065..3727877 100644
--- a/ExampleBots/ModerationBot/Commands/DbgDumpAllStateTypesCommand.cs
+++ b/ExampleBots/ModerationBot/Commands/DbgDumpAllStateTypesCommand.cs
@@ -1,14 +1,10 @@
-using System.Buffers.Text;
-using System.Security.Cryptography;
 using ArcaneLibs.Extensions;
 using LibMatrix;
 using LibMatrix.EventTypes.Spec;
-using LibMatrix.Helpers;
 using LibMatrix.RoomTypes;
 using LibMatrix.Services;
 using LibMatrix.Utilities.Bot.Interfaces;
 using ModerationBot.AccountData;
-using ModerationBot.StateEventTypes;
 
 namespace ModerationBot.Commands;
 
diff --git a/ExampleBots/ModerationBot/Commands/JoinRoomCommand.cs b/ExampleBots/ModerationBot/Commands/JoinRoomCommand.cs
index 7496a07..eb22a70 100644
--- a/ExampleBots/ModerationBot/Commands/JoinRoomCommand.cs
+++ b/ExampleBots/ModerationBot/Commands/JoinRoomCommand.cs
@@ -1,13 +1,8 @@
-using System.Buffers.Text;
-using System.Security.Cryptography;
-using ArcaneLibs.Extensions;
-using LibMatrix;
 using LibMatrix.EventTypes.Spec;
 using LibMatrix.Helpers;
 using LibMatrix.Services;
 using LibMatrix.Utilities.Bot.Interfaces;
 using ModerationBot.AccountData;
-using ModerationBot.StateEventTypes;
 
 namespace ModerationBot.Commands;
 
diff --git a/ExampleBots/ModerationBot/Commands/JoinSpaceMembersCommand.cs b/ExampleBots/ModerationBot/Commands/JoinSpaceMembersCommand.cs
index 6e64f6f..da77b05 100644
--- a/ExampleBots/ModerationBot/Commands/JoinSpaceMembersCommand.cs
+++ b/ExampleBots/ModerationBot/Commands/JoinSpaceMembersCommand.cs
@@ -1,14 +1,10 @@
-using System.Buffers.Text;
-using System.Security.Cryptography;
 using ArcaneLibs.Extensions;
-using LibMatrix;
 using LibMatrix.EventTypes.Spec;
 using LibMatrix.Helpers;
 using LibMatrix.RoomTypes;
 using LibMatrix.Services;
 using LibMatrix.Utilities.Bot.Interfaces;
 using ModerationBot.AccountData;
-using ModerationBot.StateEventTypes;
 
 namespace ModerationBot.Commands;
 
diff --git a/ExampleBots/ModerationBot/FirstRunTasks.cs b/ExampleBots/ModerationBot/FirstRunTasks.cs
index 83356bf..9dece1d 100644
--- a/ExampleBots/ModerationBot/FirstRunTasks.cs
+++ b/ExampleBots/ModerationBot/FirstRunTasks.cs
@@ -1,5 +1,4 @@
 using LibMatrix;
-using LibMatrix.EventTypes.Spec.State;
 using LibMatrix.Homeservers;
 using LibMatrix.Responses;
 using ModerationBot.AccountData;
diff --git a/ExampleBots/ModerationBot/ModerationBot.cs b/ExampleBots/ModerationBot/ModerationBot.cs
index 8a48b61..1be7bd5 100644
--- a/ExampleBots/ModerationBot/ModerationBot.cs
+++ b/ExampleBots/ModerationBot/ModerationBot.cs
@@ -1,32 +1,25 @@
-using System.Security.Cryptography;
-using System.Text.RegularExpressions;
 using ArcaneLibs.Extensions;
 using LibMatrix;
 using LibMatrix.EventTypes.Spec;
 using LibMatrix.EventTypes.Spec.State;
+using LibMatrix.EventTypes.Spec.State.Policy;
 using LibMatrix.Helpers;
 using LibMatrix.Homeservers;
-using LibMatrix.Responses;
 using LibMatrix.RoomTypes;
 using LibMatrix.Services;
-using LibMatrix.Utilities.Bot.Interfaces;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
 using ModerationBot.AccountData;
-using ModerationBot.StateEventTypes;
 using ModerationBot.StateEventTypes.Policies;
 
 namespace ModerationBot;
 
-public class ModerationBot(AuthenticatedHomeserverGeneric hs, ILogger<ModerationBot> logger, ModerationBotConfiguration configuration,
-    HomeserverResolverService hsResolver, PolicyEngine engine) : IHostedService {
-    private readonly IEnumerable<ICommand> _commands;
-
+public class ModerationBot(AuthenticatedHomeserverGeneric hs, ILogger<ModerationBot> logger, ModerationBotConfiguration configuration, PolicyEngine engine) : IHostedService {
     private Task _listenerTask;
 
     // private GenericRoom _policyRoom;
-    private GenericRoom _logRoom;
-    private GenericRoom _controlRoom;
+    private GenericRoom? _logRoom;
+    private GenericRoom? _controlRoom;
 
     /// <summary>Triggered when the application host is ready to start the service.</summary>
     /// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
@@ -63,6 +56,10 @@ public class ModerationBot(AuthenticatedHomeserverGeneric hs, ILogger<Moderation
         _controlRoom = hs.GetRoom(botData.ControlRoom);
         foreach (var configurationAdmin in configuration.Admins) {
             var pls = await _controlRoom.GetPowerLevelsAsync();
+            if (pls is null) {
+                await _logRoom?.SendMessageEventAsync(MessageFormatter.FormatWarning($"Control room has no m.room.power_levels?"));
+                continue;
+            }
             pls.SetUserPowerLevel(configurationAdmin, pls.GetUserPowerLevel(hs.UserId));
             await _controlRoom.SendStateEventAsync(RoomPowerLevelEventContent.EventId, pls);
         }
diff --git a/ExampleBots/ModerationBot/PolicyEngine.cs b/ExampleBots/ModerationBot/PolicyEngine.cs
index 8bfa448..5af99ac 100644
--- a/ExampleBots/ModerationBot/PolicyEngine.cs
+++ b/ExampleBots/ModerationBot/PolicyEngine.cs
@@ -5,15 +5,13 @@ using System.Text.RegularExpressions;
 using ArcaneLibs.Extensions;
 using LibMatrix;
 using LibMatrix.EventTypes.Spec;
-using LibMatrix.EventTypes.Spec.State;
+using LibMatrix.EventTypes.Spec.State.Policy;
 using LibMatrix.Helpers;
 using LibMatrix.Homeservers;
-using LibMatrix.Interfaces;
 using LibMatrix.RoomTypes;
 using LibMatrix.Services;
 using Microsoft.Extensions.Logging;
 using ModerationBot.AccountData;
-using ModerationBot.StateEventTypes;
 using ModerationBot.StateEventTypes.Policies;
 using ModerationBot.StateEventTypes.Policies.Implementations;
 
@@ -21,6 +19,7 @@ namespace ModerationBot;
 
 public class PolicyEngine(AuthenticatedHomeserverGeneric hs, ILogger<ModerationBot> logger, ModerationBotConfiguration configuration, HomeserverResolverService hsResolver) {
     private Dictionary<string, PolicyList> PolicyListAccountData { get; set; } = new();
+    // ReSharper disable once MemberCanBePrivate.Global
     public List<PolicyList> ActivePolicyLists { get; set; } = new();
     public List<BasePolicy> ActivePolicies { get; set; } = new();
     public Dictionary<string, List<BasePolicy>> ActivePoliciesByType { get; set; } = new();
@@ -47,8 +46,8 @@ public class PolicyEngine(AuthenticatedHomeserverGeneric hs, ILogger<ModerationB
             if (e is not { ErrorCode: "M_NOT_FOUND" }) throw;
         }
 
-        if (!PolicyListAccountData.ContainsKey(botData.DefaultPolicyRoom)) {
-            PolicyListAccountData.Add(botData.DefaultPolicyRoom, new PolicyList() {
+        if (!PolicyListAccountData.ContainsKey(botData.DefaultPolicyRoom!)) {
+            PolicyListAccountData.Add(botData.DefaultPolicyRoom!, new PolicyList() {
                 Trusted = true
             });
             await hs.SetAccountDataAsync("gay.rory.moderation_bot.policy_lists", PolicyListAccountData);
@@ -114,8 +113,8 @@ public class PolicyEngine(AuthenticatedHomeserverGeneric hs, ILogger<ModerationB
         foreach (var activePolicyList in ActivePolicyLists) {
             foreach (var policyEntry in activePolicyList.Policies) {
                 // TODO: implement rule translation
-                BasePolicy policy = policyEntry.TypedContent is BasePolicy ? policyEntry.TypedContent as BasePolicy : policyEntry.RawContent.Deserialize<UnknownPolicy>();
-                if (policy.Entity is null) continue;
+                var policy = policyEntry.TypedContent is BasePolicy ? policyEntry.TypedContent as BasePolicy : policyEntry.RawContent.Deserialize<UnknownPolicy>();
+                if (policy?.Entity is null) continue;
                 policy.PolicyList = activePolicyList;
                 policy.OriginalEvent = policyEntry;
                 activePolicies.Add(policy);
@@ -152,7 +151,7 @@ public class PolicyEngine(AuthenticatedHomeserverGeneric hs, ILogger<ModerationB
 
         if (@event.TypedContent is RoomMessageEventContent msgContent) {
             matchingPolicies.AddRange(await CheckMessageContent(@event));
-            if (msgContent.MessageType == "m.text" || msgContent.MessageType == "m.notice") ; //TODO: implement word etc. filters
+            // if (msgContent.MessageType == "m.text" || msgContent.MessageType == "m.notice") ; //TODO: implement word etc. filters
             if (msgContent.MessageType == "m.image" || msgContent.MessageType == "m.file" || msgContent.MessageType == "m.audio" || msgContent.MessageType == "m.video")
                 matchingPolicies.AddRange(await CheckMedia(@event));
         }
diff --git a/ExampleBots/ModerationBot/PolicyList.cs b/ExampleBots/ModerationBot/PolicyList.cs
index a3052bd..f291c7b 100644
--- a/ExampleBots/ModerationBot/PolicyList.cs
+++ b/ExampleBots/ModerationBot/PolicyList.cs
@@ -1,7 +1,6 @@
 using System.Text.Json.Serialization;
 using LibMatrix;
 using LibMatrix.RoomTypes;
-using ModerationBot.StateEventTypes;
 
 namespace ModerationBot;
 
diff --git a/ExampleBots/ModerationBot/StateEventTypes/Policies/BasePolicy.cs b/ExampleBots/ModerationBot/StateEventTypes/Policies/BasePolicy.cs
index 21b44b2..64b0448 100644
--- a/ExampleBots/ModerationBot/StateEventTypes/Policies/BasePolicy.cs
+++ b/ExampleBots/ModerationBot/StateEventTypes/Policies/BasePolicy.cs
@@ -1,6 +1,7 @@
 using System.ComponentModel.DataAnnotations;
 using System.Text.Json.Serialization;
 using LibMatrix;
+using LibMatrix.EventTypes;
 using LibMatrix.Interfaces;
 
 namespace ModerationBot.StateEventTypes.Policies;
diff --git a/ExampleBots/ModerationBot/StateEventTypes/Policies/Implementations/MediaPolicyHomeserver.cs b/ExampleBots/ModerationBot/StateEventTypes/Policies/Implementations/MediaPolicyHomeserver.cs
index 3dfd937..72c9a60 100644
--- a/ExampleBots/ModerationBot/StateEventTypes/Policies/Implementations/MediaPolicyHomeserver.cs
+++ b/ExampleBots/ModerationBot/StateEventTypes/Policies/Implementations/MediaPolicyHomeserver.cs
@@ -1,4 +1,3 @@
-using System.Text.Json.Serialization;
 using LibMatrix.EventTypes;
 
 namespace ModerationBot.StateEventTypes.Policies.Implementations;
diff --git a/ExampleBots/ModerationBot/StateEventTypes/Policies/Implementations/MessagePolicyContainsText.cs b/ExampleBots/ModerationBot/StateEventTypes/Policies/Implementations/MessagePolicyContainsText.cs
index daac162..fa21e2d 100644
--- a/ExampleBots/ModerationBot/StateEventTypes/Policies/Implementations/MessagePolicyContainsText.cs
+++ b/ExampleBots/ModerationBot/StateEventTypes/Policies/Implementations/MessagePolicyContainsText.cs
@@ -1,4 +1,3 @@
-using System.Text.Json.Serialization;
 using LibMatrix.EventTypes;
 
 namespace ModerationBot.StateEventTypes.Policies.Implementations;
diff --git a/ExampleBots/ModerationBot/StateEventTypes/Policies/Implementations/UnknownPolicy.cs b/ExampleBots/ModerationBot/StateEventTypes/Policies/Implementations/UnknownPolicy.cs
index 8dc8258..78860ca 100644
--- a/ExampleBots/ModerationBot/StateEventTypes/Policies/Implementations/UnknownPolicy.cs
+++ b/ExampleBots/ModerationBot/StateEventTypes/Policies/Implementations/UnknownPolicy.cs
@@ -1,6 +1,3 @@
-using System.Text.Json.Serialization;
-using LibMatrix.EventTypes;
-
 namespace ModerationBot.StateEventTypes.Policies.Implementations;
 
 /// <summary>
diff --git a/ExampleBots/PluralContactBotPoC/Bot/AccountData/BotData.cs b/ExampleBots/PluralContactBotPoC/Bot/AccountData/BotData.cs
index 5d11432..2adcbb3 100644
--- a/ExampleBots/PluralContactBotPoC/Bot/AccountData/BotData.cs
+++ b/ExampleBots/PluralContactBotPoC/Bot/AccountData/BotData.cs
@@ -1,6 +1,5 @@
 using System.Text.Json.Serialization;
 using LibMatrix.EventTypes;
-using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace PluralContactBotPoC.Bot.AccountData;
diff --git a/ExampleBots/PluralContactBotPoC/Bot/AccountData/SystemData.cs b/ExampleBots/PluralContactBotPoC/Bot/AccountData/SystemData.cs
index 42edd23..ad8ab1d 100644
--- a/ExampleBots/PluralContactBotPoC/Bot/AccountData/SystemData.cs
+++ b/ExampleBots/PluralContactBotPoC/Bot/AccountData/SystemData.cs
@@ -1,6 +1,5 @@
 using System.Text.Json.Serialization;
 using LibMatrix.EventTypes;
-using LibMatrix.Helpers;
 using LibMatrix.Interfaces;
 
 namespace PluralContactBotPoC.Bot.StateEventTypes;
@@ -8,7 +7,7 @@ namespace PluralContactBotPoC.Bot.StateEventTypes;
 [MatrixEvent(EventName = "gay.rory.plural_contact_bot.system_data")]
 public class SystemData : EventContent {
     [JsonPropertyName("control_room")]
-    public string ControlRoom { get; set; } = null!;
+    public required string ControlRoom { get; set; }
     [JsonPropertyName("system_members")]
     public List<string> Members { get; set; } = new();
     [JsonPropertyName("dm_space")]
diff --git a/ExampleBots/PluralContactBotPoC/Program.cs b/ExampleBots/PluralContactBotPoC/Program.cs
index f8d93c6..f65f2e9 100644
--- a/ExampleBots/PluralContactBotPoC/Program.cs
+++ b/ExampleBots/PluralContactBotPoC/Program.cs
@@ -1,13 +1,11 @@
 // See https://aka.ms/new-console-template for more information
 
 using System.Text.Json;
-using System.Text.Json.Serialization;
 using ArcaneLibs.Extensions;
 using LibMatrix.Services;
 using LibMatrix.Utilities.Bot;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
-using PluralContactBotPoC;
 using PluralContactBotPoC.Bot;
 
 Console.WriteLine("Hello, World!");
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; }
diff --git a/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs b/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs
index 76b8c8c..0cbcf75 100644
--- a/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs
+++ b/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs
@@ -1,5 +1,6 @@
 using ArcaneLibs.Extensions;
 using LibMatrix.EventTypes.Spec.State;
+using LibMatrix.EventTypes.Spec.State.RoomInfo;
 using LibMatrix.Homeservers;
 using LibMatrix.Responses;
 using LibMatrix.RoomTypes;
@@ -61,7 +62,7 @@ public static class RoomAbstraction {
             RoomAliasName = Guid.NewGuid().ToString(),
             InitialState = new()
         };
-        crq._creationContentBaseType.Type = "m.space";
+        crq.CreationContentBaseType.Type = "m.space";
 
 
         var createRoomTasks = Enumerable.Range(0, roomCount)
diff --git a/Tests/LibMatrix.Tests/Tests/RoomEventTests.cs b/Tests/LibMatrix.Tests/Tests/RoomEventTests.cs
index 6828087..060e6f2 100644
--- a/Tests/LibMatrix.Tests/Tests/RoomEventTests.cs
+++ b/Tests/LibMatrix.Tests/Tests/RoomEventTests.cs
@@ -1,6 +1,3 @@
-using System.Text;
-using ArcaneLibs.Extensions;
-using LibMatrix.EventTypes.Spec.State;
 using LibMatrix.Homeservers;
 using LibMatrix.Services;
 using LibMatrix.Tests.Abstractions;
@@ -88,8 +85,8 @@ public class RoomEventTests : TestBed<TestFixture> {
         Assert.NotNull(room);
         var rule = await room.GetJoinRuleAsync();
         Assert.NotNull(rule);
-        Assert.NotNull(rule.JoinRule);
-        Assert.NotEmpty(rule.JoinRule);
+        Assert.NotNull(rule.JoinRuleValue);
+        Assert.NotEmpty(rule.JoinRuleValue);
     }
 
     [Fact]
diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests.cs
index 3a11de9..913e044 100644
--- a/Tests/LibMatrix.Tests/Tests/RoomTests.cs
+++ b/Tests/LibMatrix.Tests/Tests/RoomTests.cs
@@ -1,5 +1,4 @@
 using System.Text;
-using ArcaneLibs.Extensions;
 using LibMatrix.EventTypes.Spec.State;
 using LibMatrix.Homeservers;
 using LibMatrix.Responses;
diff --git a/Tests/LibMatrix.Tests/Tests/TestCleanup.cs b/Tests/LibMatrix.Tests/Tests/TestCleanup.cs
index 8fb7443..d056345 100644
--- a/Tests/LibMatrix.Tests/Tests/TestCleanup.cs
+++ b/Tests/LibMatrix.Tests/Tests/TestCleanup.cs
@@ -1,9 +1,7 @@
 using System.Diagnostics;
-using ArcaneLibs.Extensions;
 using LibMatrix.Helpers;
 using LibMatrix.Services;
 using LibMatrix.Tests.Abstractions;
-using LibMatrix.Tests.DataTests;
 using LibMatrix.Tests.Fixtures;
 using Microsoft.Extensions.Logging;
 using Xunit.Abstractions;
@@ -12,14 +10,14 @@ using Xunit.Microsoft.DependencyInjection.Abstracts;
 namespace LibMatrix.Tests.Tests;
 
 public class TestCleanup : TestBed<TestFixture> {
-    private readonly TestFixture _fixture;
+    // private readonly TestFixture _fixture;
     private readonly HomeserverResolverService _resolver;
     private readonly Config _config;
     private readonly HomeserverProviderService _provider;
     private readonly ILogger<TestCleanup> _logger;
 
     public TestCleanup(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
-        _fixture = fixture;
+        // _fixture = fixture;
         _resolver = _fixture.GetService<HomeserverResolverService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverResolverService)}");
         _config = _fixture.GetService<Config>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}");
         _provider = _fixture.GetService<HomeserverProviderService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverProviderService)}");
diff --git a/Tests/TestDataGenerator/Bot/DataFetcher.cs b/Tests/TestDataGenerator/Bot/DataFetcher.cs
index b1f8402..5e8c1a1 100644
--- a/Tests/TestDataGenerator/Bot/DataFetcher.cs
+++ b/Tests/TestDataGenerator/Bot/DataFetcher.cs
@@ -1,25 +1,17 @@
-using System.Text;
-using System.Threading.Channels;
 using ArcaneLibs.Extensions;
 using LibMatrix.EventTypes.Spec;
-using LibMatrix.EventTypes.Spec.State;
-using LibMatrix.Helpers;
 using LibMatrix.Homeservers;
-using LibMatrix.Interfaces;
 using LibMatrix.RoomTypes;
-using LibMatrix.Services;
-using LibMatrix.Tests;
 using LibMatrix.Utilities.Bot;
-using LibMatrix.Utilities.Bot.Interfaces;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
 
-namespace PluralContactBotPoC.Bot;
+#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
 
-public class DataFetcher(AuthenticatedHomeserverGeneric hs, ILogger<DataFetcher> logger, LibMatrixBotConfiguration botConfiguration,
-    // DataFetcherConfiguration configuration,
-    HomeserverResolverService hsResolver) : IHostedService {
-    private Task _listenerTask;
+namespace TestDataGenerator.Bot;
+
+public class DataFetcher(AuthenticatedHomeserverGeneric hs, ILogger<DataFetcher> logger, LibMatrixBotConfiguration botConfiguration) : IHostedService {
+    private Task? _listenerTask;
 
     private GenericRoom? _logRoom;
 
@@ -32,7 +24,7 @@ public class DataFetcher(AuthenticatedHomeserverGeneric hs, ILogger<DataFetcher>
 
     private async Task Run(CancellationToken cancellationToken) {
         Directory.GetFiles("bot_data/cache").ToList().ForEach(File.Delete);
-        _logRoom = hs.GetRoom(botConfiguration.LogRoom);
+        _logRoom = hs.GetRoom(botConfiguration.LogRoom!);
 
         await _logRoom.SendMessageEventAsync(new RoomMessageEventContent(body: "Test data collector started!"));
         await _logRoom.SendMessageEventAsync(new RoomMessageEventContent(body: "Fetching rooms..."));
@@ -42,16 +34,14 @@ public class DataFetcher(AuthenticatedHomeserverGeneric hs, ILogger<DataFetcher>
 
         await _logRoom.SendMessageEventAsync(new RoomMessageEventContent(body: "Fetching room data..."));
 
-        Config cfg = new Config();
-
         var roomAliasTasks = rooms.Select(room => room.GetCanonicalAliasAsync()).ToAsyncEnumerable();
         List<Task<(string, string)>> aliasResolutionTasks = new();
         await foreach (var @event in roomAliasTasks) {
             if (@event?.Alias != null) {
                 await _logRoom.SendMessageEventAsync(new RoomMessageEventContent(body: $"Fetched room alias {(@event).Alias}!"));
-                aliasResolutionTasks.Add(Task<(string, string)>.Run(async () => {
+                aliasResolutionTasks.Add(Task.Run(async () => {
                     var alias = await hs.ResolveRoomAliasAsync(@event.Alias);
-                    return (@event.Alias, @alias.RoomId);
+                    return (@event.Alias, alias.RoomId);
                 }, cancellationToken));
             }
         }
@@ -65,5 +55,6 @@ public class DataFetcher(AuthenticatedHomeserverGeneric hs, ILogger<DataFetcher>
     /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
     public async Task StopAsync(CancellationToken cancellationToken) {
         logger.LogInformation("Shutting down bot!");
+        _listenerTask?.Dispose();
     }
 }
diff --git a/Tests/TestDataGenerator/Bot/DataFetcherConfiguration.cs b/Tests/TestDataGenerator/Bot/DataFetcherConfiguration.cs
index 06df0eb..a586d05 100644
--- a/Tests/TestDataGenerator/Bot/DataFetcherConfiguration.cs
+++ b/Tests/TestDataGenerator/Bot/DataFetcherConfiguration.cs
@@ -1,6 +1,6 @@
 using Microsoft.Extensions.Configuration;
 
-namespace PluralContactBotPoC.Bot;
+namespace TestDataGenerator.Bot;
 
 public class DataFetcherConfiguration {
     public DataFetcherConfiguration(IConfiguration config) => config.GetRequiredSection("DataFetcher").Bind(this);
diff --git a/Tests/TestDataGenerator/Program.cs b/Tests/TestDataGenerator/Program.cs
index 18ba61e..5d36215 100644
--- a/Tests/TestDataGenerator/Program.cs
+++ b/Tests/TestDataGenerator/Program.cs
@@ -1,19 +1,15 @@
 // See https://aka.ms/new-console-template for more information
 
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using ArcaneLibs.Extensions;
 using LibMatrix.Services;
 using LibMatrix.Utilities.Bot;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
-using PluralContactBotPoC;
-using PluralContactBotPoC.Bot;
+using TestDataGenerator.Bot;
 
 Console.WriteLine("Hello, World!");
 
 var host = Host.CreateDefaultBuilder(args).ConfigureServices((_, services) => {
-    services.AddScoped<TieredStorageService>(x =>
+    services.AddScoped<TieredStorageService>(_ =>
         new TieredStorageService(
             cacheStorageProvider: new FileStorageProvider("bot_data/cache/"),
             dataStorageProvider: new FileStorageProvider("bot_data/data/")
diff --git a/Utilities/LibMatrix.DebugDataValidationApi/Controllers/ValidationController.cs b/Utilities/LibMatrix.DebugDataValidationApi/Controllers/ValidationController.cs
index 81753d1..1b93614 100644
--- a/Utilities/LibMatrix.DebugDataValidationApi/Controllers/ValidationController.cs
+++ b/Utilities/LibMatrix.DebugDataValidationApi/Controllers/ValidationController.cs
@@ -7,16 +7,15 @@ namespace LibMatrix.DebugDataValidationApi.Controllers;
 [ApiController]
 [Route("/")]
 public class ValidationController(ILogger<ValidationController> logger) : ControllerBase {
-    private readonly ILogger<ValidationController> _logger = logger;
 
     [HttpPost("/validate/{type}")]
     public Task<bool> Get([FromRoute] string type, [FromBody] JsonElement content) {
         var t = Type.GetType(type);
         if (t is null) {
-            Console.WriteLine($"Type `{type}` does not exist!");
+            logger.LogWarning($"Type `{type}` does not exist!");
             throw new ArgumentException($"Unknown type {type}!");
         }
-        Console.WriteLine($"Validating {type}...");
+        logger.LogInformation($"Validating {type}...");
         return Task.FromResult(content.FindExtraJsonElementFields(t, "$"));
     }
 }
diff --git a/Utilities/LibMatrix.JsonSerializerContextGenerator/EventSerializerContexts.g.cs b/Utilities/LibMatrix.JsonSerializerContextGenerator/EventSerializerContexts.g.cs
deleted file mode 100644
index 1ca32dd..0000000
--- a/Utilities/LibMatrix.JsonSerializerContextGenerator/EventSerializerContexts.g.cs
+++ /dev/null
@@ -1,99 +0,0 @@
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.UnknownEventContent))]
-internal partial class UnknownEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.RoomMessageEventContent))]
-internal partial class RoomMessageEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.PresenceEventContent))]
-internal partial class PresenceEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomTypingEventContent))]
-internal partial class RoomTypingEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.ServerPolicyRuleEventContent))]
-internal partial class ServerPolicyRuleEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.UserPolicyRuleEventContent))]
-internal partial class UserPolicyRuleEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomPolicyRuleEventContent))]
-internal partial class RoomPolicyRuleEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomAliasEventContent))]
-internal partial class RoomAliasEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomAvatarEventContent))]
-internal partial class RoomAvatarEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomCanonicalAliasEventContent))]
-internal partial class RoomCanonicalAliasEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomCreateEventContent))]
-internal partial class RoomCreateEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomEncryptionEventContent))]
-internal partial class RoomEncryptionEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomGuestAccessEventContent))]
-internal partial class RoomGuestAccessEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomHistoryVisibilityEventContent))]
-internal partial class RoomHistoryVisibilityEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomJoinRulesEventContent))]
-internal partial class RoomJoinRulesEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomMemberEventContent))]
-internal partial class RoomMemberEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomNameEventContent))]
-internal partial class RoomNameEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomPinnedEventContent))]
-internal partial class RoomPinnedEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomPowerLevelEventContent))]
-internal partial class RoomPowerLevelEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomServerACLEventContent))]
-internal partial class RoomServerACLEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.RoomTopicEventContent))]
-internal partial class RoomTopicEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.SpaceChildEventContent))]
-internal partial class SpaceChildEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Spec.State.SpaceParentEventContent))]
-internal partial class SpaceParentEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Common.MjolnirShortcodeEventContent))]
-internal partial class MjolnirShortcodeEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
-
-[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-[System.Text.Json.Serialization.JsonSerializable(typeof(LibMatrix.EventTypes.Common.RoomEmotesEventContent))]
-internal partial class RoomEmotesEventContentSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
diff --git a/Utilities/LibMatrix.JsonSerializerContextGenerator/Program.cs b/Utilities/LibMatrix.JsonSerializerContextGenerator/Program.cs
index 76d6357..0c0277b 100644
--- a/Utilities/LibMatrix.JsonSerializerContextGenerator/Program.cs
+++ b/Utilities/LibMatrix.JsonSerializerContextGenerator/Program.cs
@@ -1,8 +1,6 @@
-using System.Reflection;
-using ArcaneLibs;
+using ArcaneLibs;
 using ArcaneLibs.Extensions.Streams;
-using LibMatrix;
-using LibMatrix.Interfaces;
+using LibMatrix.EventTypes;
 
 // string binary = args.Length > 1 ? args[0] : Console.ReadLine()!;
 
@@ -10,13 +8,25 @@ using LibMatrix.Interfaces;
 File.Delete("EventSerializerContexts.g.cs");
 var stream = File.OpenWrite("EventSerializerContexts.g.cs");
 var eventContentTypes = new ClassCollector<EventContent>().ResolveFromAllAccessibleAssemblies();
+
+stream.WriteString("using System.Text.Json.Serialization;\n");
+
+stream.WriteString(string.Join('\n', eventContentTypes.DistinctBy(x => x.Namespace)
+    .Select(x => $"using {x.Namespace};")));
+stream.WriteString("\n\nnamespace LibMatrix.Generated;\n\n[JsonSourceGenerationOptions(WriteIndented = true)]\n");
+
+// stream.WriteString(string.Join('\n', eventContentTypes//.DistinctBy(x => x.Namespace)
+//     .Select(x => $$"""
+//                   [JsonSourceGenerationOptions(WriteIndented = true)]
+//                   [JsonSerializable(typeof({{x.Name}}))]
+//                   internal partial class {{x.Name}}SerializerContext : JsonSerializerContext { }
+//
+//                   """)));
+
 stream.WriteString(string.Join('\n', eventContentTypes//.DistinctBy(x => x.Namespace)
-    .Select(x => $$"""
-                  [System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)]
-                  [System.Text.Json.Serialization.JsonSerializable(typeof({{x.FullName}}))]
-                  internal partial class {{x.Name}}SerializerContext : System.Text.Json.Serialization.JsonSerializerContext { }
+    .Select(x => $"[JsonSerializable(typeof({x.Name}))]")));
 
-                  """)));
+stream.WriteString("\ninternal partial class EventTypeSerializerContext : JsonSerializerContext { }");
 
 await stream.FlushAsync();
 stream.Close();
\ No newline at end of file
diff --git a/Utilities/LibMatrix.Utilities.Bot/AppServiceConfiguration.cs b/Utilities/LibMatrix.Utilities.Bot/AppServiceConfiguration.cs
index 99a789a..d8d1094 100644
--- a/Utilities/LibMatrix.Utilities.Bot/AppServiceConfiguration.cs
+++ b/Utilities/LibMatrix.Utilities.Bot/AppServiceConfiguration.cs
@@ -44,8 +44,8 @@ public class AppServiceConfiguration {
         else
             yaml += "protocols: []";
         yaml += "\n";
-        if (RateLimited is not null)
-            yaml += $"rate_limited: {RateLimited!.ToString().ToLower()}\n";
+        if (RateLimited.HasValue)
+            yaml += $"rate_limited: {RateLimited.Value.ToString().ToLower()}\n";
         else
             yaml += "rate_limited: false\n";
 
diff --git a/Utilities/LibMatrix.Utilities.Bot/BotCommandInstaller.cs b/Utilities/LibMatrix.Utilities.Bot/BotCommandInstaller.cs
index 91ec1e8..25a8d92 100644
--- a/Utilities/LibMatrix.Utilities.Bot/BotCommandInstaller.cs
+++ b/Utilities/LibMatrix.Utilities.Bot/BotCommandInstaller.cs
@@ -21,8 +21,8 @@ public static class BotCommandInstaller {
         services.AddSingleton<LibMatrixBotConfiguration>();
 
         services.AddScoped<AuthenticatedHomeserverGeneric>(x => {
-            var config = x.GetService<LibMatrixBotConfiguration>();
-            var hsProvider = x.GetService<HomeserverProviderService>();
+            var config = x.GetService<LibMatrixBotConfiguration>() ?? throw new Exception("No configuration found!");
+            var hsProvider = x.GetService<HomeserverProviderService>() ?? throw new Exception("No homeserver provider found!");
             var hs = hsProvider.GetAuthenticatedWithToken(config.Homeserver, config.AccessToken).Result;
 
             return hs;
diff --git a/Utilities/LibMatrix.Utilities.Bot/FileStorageProvider.cs b/Utilities/LibMatrix.Utilities.Bot/FileStorageProvider.cs
index 46032c7..b66fbf5 100644
--- a/Utilities/LibMatrix.Utilities.Bot/FileStorageProvider.cs
+++ b/Utilities/LibMatrix.Utilities.Bot/FileStorageProvider.cs
@@ -1,13 +1,10 @@
 using System.Text.Json;
 using ArcaneLibs.Extensions;
 using LibMatrix.Interfaces.Services;
-using Microsoft.Extensions.Logging;
 
 namespace LibMatrix.Utilities.Bot;
 
 public class FileStorageProvider : IStorageProvider {
-    private readonly ILogger<FileStorageProvider> _logger;
-
     public string TargetPath { get; }
 
     /// <summary>
@@ -15,7 +12,6 @@ public class FileStorageProvider : IStorageProvider {
     /// </summary>
     /// <param name="targetPath"></param>
     public FileStorageProvider(string targetPath) {
-        new Logger<FileStorageProvider>(new LoggerFactory()).LogInformation("test");
         Console.WriteLine($"Initialised FileStorageProvider with path {targetPath}");
         TargetPath = targetPath;
         if (!Directory.Exists(targetPath)) {
diff --git a/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs b/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs
index 2d04e37..94ea846 100644
--- a/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs
+++ b/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs
@@ -5,8 +5,8 @@ using LibMatrix.RoomTypes;
 namespace LibMatrix.Utilities.Bot.Interfaces;
 
 public class CommandContext {
-    public GenericRoom Room { get; set; }
-    public StateEventResponse MessageEvent { get; set; }
+    public required GenericRoom Room { get; set; }
+    public required StateEventResponse MessageEvent { get; set; }
 
     public string MessageContentWithoutReply =>
         (MessageEvent.TypedContent as RoomMessageEventContent)!
@@ -16,7 +16,7 @@ public class CommandContext {
 
     public string CommandName => MessageContentWithoutReply.Split(' ')[0][1..];
     public string[] Args => MessageContentWithoutReply.Split(' ')[1..];
-    public AuthenticatedHomeserverGeneric Homeserver { get; set; }
+    public required AuthenticatedHomeserverGeneric Homeserver { get; set; }
 
     public async Task<EventIdResponse> Reply(RoomMessageEventContent content) => await Room.SendMessageEventAsync(content);
 }