From 34e4e9b1a917f76063fa8bf3db5a9f9d93fb7fdd Mon Sep 17 00:00:00 2001
From: flashwave <me@flash.moe>
Date: Sat, 26 Apr 2025 23:15:54 +0000
Subject: [PATCH] Switched to file namespace declarations.

---
 .editorconfig                                 |   2 +-
 SharpChat.Flashii/FlashiiAuthResult.cs        |  40 +-
 SharpChat.Flashii/FlashiiBanInfo.cs           |  22 +-
 SharpChat.Flashii/FlashiiClient.cs            | 386 +++++-----
 SharpChat.Flashii/FlashiiIPAddressBanInfo.cs  |  12 +-
 SharpChat.Flashii/FlashiiRawBanInfo.cs        |  40 +-
 SharpChat.Flashii/FlashiiUserBanInfo.cs       |  16 +-
 SharpChat.SockChat/S2CPacket.cs               |   8 +-
 .../S2CPackets/AuthFailS2CPacket.cs           |  74 +-
 .../S2CPackets/AuthSuccessS2CPacket.cs        |  68 +-
 .../S2CPackets/BanListS2CPacket.cs            |  42 +-
 .../S2CPackets/ChannelCreateS2CPacket.cs      |  32 +-
 .../S2CPackets/ChannelDeleteS2CPacket.cs      |  20 +-
 .../S2CPackets/ChannelUpdateS2CPacket.cs      |  38 +-
 .../S2CPackets/ChatMessageAddS2CPacket.cs     |  72 +-
 .../S2CPackets/ChatMessageDeleteS2CPacket.cs  |  16 +-
 .../S2CPackets/CommandResponseS2CPacket.cs    | 150 ++--
 .../S2CPackets/ContextChannelsS2CPacket.cs    |  32 +-
 .../S2CPackets/ContextClearS2CPacket.cs       |  30 +-
 .../S2CPackets/ContextUsersS2CPacket.cs       |  56 +-
 .../S2CPackets/ForceDisconnectS2CPacket.cs    |  32 +-
 .../S2CPackets/PongS2CPacket.cs               |  10 +-
 .../UserChannelForceJoinS2CPacket.cs          |  16 +-
 .../S2CPackets/UserChannelJoinS2CPacket.cs    |  62 +-
 .../S2CPackets/UserChannelLeaveS2CPacket.cs   |  20 +-
 .../S2CPackets/UserConnectS2CPacket.cs        |  68 +-
 .../S2CPackets/UserDisconnectS2CPacket.cs     |  88 +--
 .../S2CPackets/UserUpdateS2CPacket.cs         |  56 +-
 SharpChat/C2SPacketHandler.cs                 |  10 +-
 SharpChat/C2SPacketHandlerContext.cs          |  30 +-
 .../C2SPacketHandlers/AuthC2SPacketHandler.cs | 170 ++---
 .../C2SPacketHandlers/PingC2SPacketHandler.cs |  50 +-
 .../SendMessageC2SPacketHandler.cs            | 128 ++--
 SharpChat/Channel.cs                          |  64 +-
 SharpChat/ClientCommand.cs                    |  10 +-
 SharpChat/ClientCommandContext.cs             |  84 +--
 SharpChat/ClientCommands/AFKClientCommand.cs  |  52 +-
 .../ClientCommands/ActionClientCommand.cs     |  52 +-
 .../ClientCommands/BanListClientCommand.cs    |  44 +-
 .../ClientCommands/BroadcastClientCommand.cs  |  54 +-
 .../CreateChannelClientCommand.cs             | 108 +--
 .../DeleteChannelClientCommand.cs             |  58 +-
 .../DeleteMessageClientCommand.cs             |  63 +-
 .../JoinChannelClientCommand.cs               |  34 +-
 .../ClientCommands/KickBanClientCommand.cs    | 116 +--
 SharpChat/ClientCommands/NickClientCommand.cs | 104 +--
 .../PardonAddressClientCommand.cs             |  62 +-
 .../ClientCommands/PardonUserClientCommand.cs |  76 +-
 .../PasswordChannelClientCommand.cs           |  38 +-
 .../RankChannelClientCommand.cs               |  42 +-
 .../RemoteAddressClientCommand.cs             |  44 +-
 .../ShutdownRestartClientCommand.cs           |  44 +-
 .../ClientCommands/WhisperClientCommand.cs    |  72 +-
 SharpChat/ClientCommands/WhoClientCommand.cs  | 100 +--
 SharpChat/Connection.cs                       | 120 +--
 SharpChat/Context.cs                          | 712 +++++++++---------
 SharpChat/EventStorage/EventStorage.cs        |  38 +-
 SharpChat/EventStorage/MariaDBEventStorage.cs | 226 +++---
 .../MariaDBEventStorage_Database.cs           | 152 ++--
 .../MariaDBEventStorage_Migrations.cs         | 154 ++--
 SharpChat/EventStorage/StoredEventFlags.cs    |  18 +-
 SharpChat/EventStorage/StoredEventInfo.cs     |  40 +-
 SharpChat/EventStorage/VirtualEventStorage.cs | 114 +--
 SharpChat/Events/ChatEvent.cs                 |   6 +-
 SharpChat/Events/MessageCreateEvent.cs        |  60 +-
 SharpChat/Program.cs                          | 222 +++---
 SharpChat/SharpChatWebSocketServer.cs         | 272 +++----
 .../S2CPackets/ContextMessageS2CPacket.cs     | 190 ++---
 SharpChat/SockChatServer.cs                   | 378 +++++-----
 SharpChat/User.cs                             | 114 +--
 SharpChat/UserStatus.cs                       |  12 +-
 SharpChatCommon/Auth/AuthClient.cs            |  10 +-
 SharpChatCommon/Auth/AuthFailedException.cs   |   6 +-
 SharpChatCommon/Auth/AuthResult.cs            |  16 +-
 SharpChatCommon/Bans/BanInfo.cs               |  16 +-
 SharpChatCommon/Bans/BanKind.cs               |  10 +-
 SharpChatCommon/Bans/BansClient.cs            |  30 +-
 SharpChatCommon/Bans/IPAddressBanInfo.cs      |   8 +-
 SharpChatCommon/Bans/UserBanInfo.cs           |  12 +-
 SharpChatCommon/ColourInheritable.cs          |  22 +-
 SharpChatCommon/ColourRgb.cs                  |  16 +-
 SharpChatCommon/Configuration/CachedValue.cs  |  54 +-
 SharpChatCommon/Configuration/Config.cs       |  48 +-
 .../Configuration/ConfigExceptions.cs         |  14 +-
 SharpChatCommon/Configuration/ScopedConfig.cs |  50 +-
 SharpChatCommon/Configuration/StreamConfig.cs | 190 ++---
 SharpChatCommon/Logger.cs                     |  48 +-
 SharpChatCommon/RNG.cs                        | 124 +--
 SharpChatCommon/RateLimiter.cs                |  56 +-
 SharpChatCommon/SharpInfo.cs                  |  48 +-
 SharpChatCommon/Snowflake/RandomSnowflake.cs  |  18 +-
 .../Snowflake/SnowflakeGenerator.cs           |  54 +-
 SharpChatCommon/UserPermissions.cs            |  46 +-
 93 files changed, 3470 insertions(+), 3471 deletions(-)

diff --git a/.editorconfig b/.editorconfig
index 4aa2717..0209688 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -15,7 +15,7 @@ csharp_indent_labels = one_less_than_current
 csharp_using_directive_placement = outside_namespace:silent
 csharp_prefer_simple_using_statement = true:suggestion
 csharp_prefer_braces = true:silent
-csharp_style_namespace_declarations = block_scoped:silent
+csharp_style_namespace_declarations = file_scoped:silent
 csharp_style_prefer_method_group_conversion = true:silent
 csharp_style_prefer_top_level_statements = true:silent
 csharp_style_prefer_primary_constructors = true:suggestion
diff --git a/SharpChat.Flashii/FlashiiAuthResult.cs b/SharpChat.Flashii/FlashiiAuthResult.cs
index 8bcc823..86a2645 100644
--- a/SharpChat.Flashii/FlashiiAuthResult.cs
+++ b/SharpChat.Flashii/FlashiiAuthResult.cs
@@ -1,31 +1,31 @@
 using SharpChat.Auth;
 using System.Text.Json.Serialization;
 
-namespace SharpChat.Flashii {
-    public class FlashiiAuthResult : AuthResult {
-        public string UserId => UserIdRaw.ToString();
-        public string UserName => UserNameRaw ?? string.Empty;
-        public ColourInheritable UserColour => ColourInheritable.FromMisuzu(UserColourRaw);
+namespace SharpChat.Flashii;
 
-        [JsonPropertyName("success")]
-        public bool Success { get; init; }
+public class FlashiiAuthResult : AuthResult {
+    public string UserId => UserIdRaw.ToString();
+    public string UserName => UserNameRaw ?? string.Empty;
+    public ColourInheritable UserColour => ColourInheritable.FromMisuzu(UserColourRaw);
 
-        [JsonPropertyName("reason")]
-        public string? Reason { get; init; }
+    [JsonPropertyName("success")]
+    public bool Success { get; init; }
 
-        [JsonPropertyName("user_id")]
-        public long UserIdRaw { get; init; }
+    [JsonPropertyName("reason")]
+    public string? Reason { get; init; }
 
-        [JsonPropertyName("username")]
-        public string? UserNameRaw { get; init; }
+    [JsonPropertyName("user_id")]
+    public long UserIdRaw { get; init; }
 
-        [JsonPropertyName("colour_raw")]
-        public int UserColourRaw { get; init; }
+    [JsonPropertyName("username")]
+    public string? UserNameRaw { get; init; }
 
-        [JsonPropertyName("hierarchy")]
-        public int UserRank { get; init; }
+    [JsonPropertyName("colour_raw")]
+    public int UserColourRaw { get; init; }
 
-        [JsonPropertyName("perms")]
-        public UserPermissions UserPermissions { get; init; }
-    }
+    [JsonPropertyName("hierarchy")]
+    public int UserRank { get; init; }
+
+    [JsonPropertyName("perms")]
+    public UserPermissions UserPermissions { get; init; }
 }
diff --git a/SharpChat.Flashii/FlashiiBanInfo.cs b/SharpChat.Flashii/FlashiiBanInfo.cs
index ed6906c..51b3472 100644
--- a/SharpChat.Flashii/FlashiiBanInfo.cs
+++ b/SharpChat.Flashii/FlashiiBanInfo.cs
@@ -1,13 +1,13 @@
-using SharpChat.Bans;
+using SharpChat.Bans;
 
-namespace SharpChat.Flashii {
-    public abstract class FlashiiBanInfo(
-        BanKind kind,
-        FlashiiRawBanInfo rawBanInfo
-    ) : BanInfo {
-        public BanKind Kind { get; } = kind;
-        public bool IsPermanent { get; } = rawBanInfo.IsPermanent;
-        public DateTimeOffset ExpiresAt { get; } = rawBanInfo.ExpiresAt;
-        public abstract override string ToString();
-    }
+namespace SharpChat.Flashii;
+
+public abstract class FlashiiBanInfo(
+    BanKind kind,
+    FlashiiRawBanInfo rawBanInfo
+) : BanInfo {
+    public BanKind Kind { get; } = kind;
+    public bool IsPermanent { get; } = rawBanInfo.IsPermanent;
+    public DateTimeOffset ExpiresAt { get; } = rawBanInfo.ExpiresAt;
+    public abstract override string ToString();
 }
diff --git a/SharpChat.Flashii/FlashiiClient.cs b/SharpChat.Flashii/FlashiiClient.cs
index a749532..b4cd820 100644
--- a/SharpChat.Flashii/FlashiiClient.cs
+++ b/SharpChat.Flashii/FlashiiClient.cs
@@ -6,235 +6,235 @@ using System.Security.Cryptography;
 using System.Text;
 using System.Text.Json;
 
-namespace SharpChat.Flashii {
-    public class FlashiiClient(HttpClient httpClient, Config config) : AuthClient, BansClient {
-        private const string DEFAULT_BASE_URL = "https://flashii.net/_sockchat";
-        private readonly CachedValue<string> BaseURL = config.ReadCached("url", DEFAULT_BASE_URL);
+namespace SharpChat.Flashii;
 
-        private const string DEFAULT_SECRET_KEY = "woomy";
-        private readonly CachedValue<string> SecretKey = config.ReadCached("secret", DEFAULT_SECRET_KEY);
+public class FlashiiClient(HttpClient httpClient, Config config) : AuthClient, BansClient {
+    private const string DEFAULT_BASE_URL = "https://flashii.net/_sockchat";
+    private readonly CachedValue<string> BaseURL = config.ReadCached("url", DEFAULT_BASE_URL);
 
-        private string CreateStringSignature(string str) {
-            return CreateBufferSignature(Encoding.UTF8.GetBytes(str));
-        }
+    private const string DEFAULT_SECRET_KEY = "woomy";
+    private readonly CachedValue<string> SecretKey = config.ReadCached("secret", DEFAULT_SECRET_KEY);
 
-        private string CreateBufferSignature(byte[] bytes) {
-            using HMACSHA256 algo = new(Encoding.UTF8.GetBytes(SecretKey!));
-            return string.Concat(algo.ComputeHash(bytes).Select(c => c.ToString("x2")));
-        }
+    private string CreateStringSignature(string str) {
+        return CreateBufferSignature(Encoding.UTF8.GetBytes(str));
+    }
 
-        private const string AUTH_VERIFY_URL = "{0}/verify";
-        private const string AUTH_VERIFY_SIG = "verify#{0}#{1}#{2}";
+    private string CreateBufferSignature(byte[] bytes) {
+        using HMACSHA256 algo = new(Encoding.UTF8.GetBytes(SecretKey!));
+        return string.Concat(algo.ComputeHash(bytes).Select(c => c.ToString("x2")));
+    }
 
-        public async Task<AuthResult> AuthVerifyAsync(IPAddress remoteAddr, string scheme, string token) {
+    private const string AUTH_VERIFY_URL = "{0}/verify";
+    private const string AUTH_VERIFY_SIG = "verify#{0}#{1}#{2}";
+
+    public async Task<AuthResult> AuthVerifyAsync(IPAddress remoteAddr, string scheme, string token) {
+        string remoteAddrStr = remoteAddr.ToString();
+
+        HttpRequestMessage request = new(HttpMethod.Post, string.Format(AUTH_VERIFY_URL, BaseURL)) {
+            Content = new FormUrlEncodedContent(new Dictionary<string, string> {
+                { "method", scheme },
+                { "token", token },
+                { "ipaddr", remoteAddrStr },
+            }),
+            Headers = {
+                { "X-SharpChat-Signature", CreateStringSignature(string.Format(AUTH_VERIFY_SIG, scheme, token, remoteAddrStr)) },
+            },
+        };
+
+        using HttpResponseMessage response = await httpClient.SendAsync(request);
+        response.EnsureSuccessStatusCode();
+
+        using Stream stream = await response.Content.ReadAsStreamAsync();
+        FlashiiAuthResult? authResult = await JsonSerializer.DeserializeAsync<FlashiiAuthResult>(stream);
+        if(authResult?.Success != true)
+            throw new AuthFailedException(authResult?.Reason ?? "none");
+
+        return authResult;
+    }
+
+    private const string AUTH_BUMP_USERS_ONLINE_URL = "{0}/bump";
+
+    public async Task AuthBumpUsersOnlineAsync(IEnumerable<(IPAddress remoteAddr, string userId)> entries) {
+        if(!entries.Any())
+            return;
+
+        string now = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
+        StringBuilder sb = new();
+        sb.AppendFormat("bump#{0}", now);
+
+        Dictionary<string, string> formData = new() {
+            { "t", now },
+        };
+
+        foreach(var (remoteAddr, userId) in entries) {
             string remoteAddrStr = remoteAddr.ToString();
-
-            HttpRequestMessage request = new(HttpMethod.Post, string.Format(AUTH_VERIFY_URL, BaseURL)) {
-                Content = new FormUrlEncodedContent(new Dictionary<string, string> {
-                    { "method", scheme },
-                    { "token", token },
-                    { "ipaddr", remoteAddrStr },
-                }),
-                Headers = {
-                    { "X-SharpChat-Signature", CreateStringSignature(string.Format(AUTH_VERIFY_SIG, scheme, token, remoteAddrStr)) },
-                },
-            };
-
-            using HttpResponseMessage response = await httpClient.SendAsync(request);
-            response.EnsureSuccessStatusCode();
-
-            using Stream stream = await response.Content.ReadAsStreamAsync();
-            FlashiiAuthResult? authResult = await JsonSerializer.DeserializeAsync<FlashiiAuthResult>(stream);
-            if(authResult?.Success != true)
-                throw new AuthFailedException(authResult?.Reason ?? "none");
-
-            return authResult;
+            sb.AppendFormat("#{0}:{1}", userId, remoteAddrStr);
+            formData.Add(string.Format("u[{0}]", userId), remoteAddrStr);
         }
 
-        private const string AUTH_BUMP_USERS_ONLINE_URL = "{0}/bump";
+        HttpRequestMessage request = new(HttpMethod.Post, string.Format(AUTH_BUMP_USERS_ONLINE_URL, BaseURL)) {
+            Headers = {
+                { "X-SharpChat-Signature", CreateStringSignature(sb.ToString()) }
+            },
+            Content = new FormUrlEncodedContent(formData),
+        };
 
-        public async Task AuthBumpUsersOnlineAsync(IEnumerable<(IPAddress remoteAddr, string userId)> entries) {
-            if(!entries.Any())
-                return;
+        using HttpResponseMessage response = await httpClient.SendAsync(request);
+        response.EnsureSuccessStatusCode();
+    }
 
-            string now = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
-            StringBuilder sb = new();
-            sb.AppendFormat("bump#{0}", now);
+    private const string BANS_CREATE_URL = "{0}/bans/create";
+    private const string BANS_CREATE_SIG = "create#{0}#{1}#{2}#{3}#{4}#{5}#{6}#{7}";
 
-            Dictionary<string, string> formData = new() {
+    public async Task BanCreateAsync(
+        BanKind kind,
+        TimeSpan duration,
+        IPAddress remoteAddr,
+        string? userId = null,
+        string? reason = null,
+        IPAddress? issuerRemoteAddr = null,
+        string? issuerUserId = null
+    ) {
+        if(duration <= TimeSpan.Zero || kind != BanKind.User)
+            return;
+
+        issuerUserId ??= string.Empty;
+        userId ??= string.Empty;
+        reason ??= string.Empty;
+        issuerRemoteAddr ??= IPAddress.IPv6None;
+
+        string isPerma = duration == TimeSpan.MaxValue ? "1" : "0";
+        string durationStr = duration == TimeSpan.MaxValue ? "-1" : duration.TotalSeconds.ToString();
+        string remoteAddrStr = remoteAddr.ToString();
+        string issuerRemoteAddrStr = issuerRemoteAddr.ToString();
+
+        string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
+        string sig = string.Format(
+            BANS_CREATE_SIG,
+            now, userId, remoteAddrStr,
+            issuerUserId, issuerRemoteAddrStr,
+            durationStr, isPerma, reason
+        );
+
+        HttpRequestMessage request = new(HttpMethod.Post, string.Format(BANS_CREATE_URL, BaseURL)) {
+            Headers = {
+                { "X-SharpChat-Signature", CreateStringSignature(sig) },
+            },
+            Content = new FormUrlEncodedContent(new Dictionary<string, string> {
                 { "t", now },
-            };
+                { "ui", userId },
+                { "ua", remoteAddrStr },
+                { "mi", issuerUserId },
+                { "ma", issuerRemoteAddrStr },
+                { "d", durationStr },
+                { "p", isPerma },
+                { "r", reason },
+            }),
+        };
 
-            foreach(var (remoteAddr, userId) in entries) {
-                string remoteAddrStr = remoteAddr.ToString();
-                sb.AppendFormat("#{0}:{1}", userId, remoteAddrStr);
-                formData.Add(string.Format("u[{0}]", userId), remoteAddrStr);
-            }
+        using HttpResponseMessage response = await httpClient.SendAsync(request);
 
-            HttpRequestMessage request = new(HttpMethod.Post, string.Format(AUTH_BUMP_USERS_ONLINE_URL, BaseURL)) {
-                Headers = {
-                    { "X-SharpChat-Signature", CreateStringSignature(sb.ToString()) }
-                },
-                Content = new FormUrlEncodedContent(formData),
-            };
+        response.EnsureSuccessStatusCode();
+    }
 
-            using HttpResponseMessage response = await httpClient.SendAsync(request);
-            response.EnsureSuccessStatusCode();
-        }
+    private const string BANS_REVOKE_URL = "{0}/bans/revoke?t={1}&s={2}&x={3}";
+    private const string BANS_REVOKE_SIG = "revoke#{0}#{1}#{2}";
 
-        private const string BANS_CREATE_URL = "{0}/bans/create";
-        private const string BANS_CREATE_SIG = "create#{0}#{1}#{2}#{3}#{4}#{5}#{6}#{7}";
+    public async Task<bool> BanRevokeAsync(BanInfo info) {
+        string type;
+        string target;
 
-        public async Task BanCreateAsync(
-            BanKind kind,
-            TimeSpan duration,
-            IPAddress remoteAddr,
-            string? userId = null,
-            string? reason = null,
-            IPAddress? issuerRemoteAddr = null,
-            string? issuerUserId = null
-        ) {
-            if(duration <= TimeSpan.Zero || kind != BanKind.User)
-                return;
+        if(info is UserBanInfo ubi) {
+            if(info.Kind != BanKind.User)
+                throw new ArgumentException("info argument is an instance of UserBanInfo but Kind was not set to BanKind.User", nameof(info));
 
-            issuerUserId ??= string.Empty;
-            userId ??= string.Empty;
-            reason ??= string.Empty;
-            issuerRemoteAddr ??= IPAddress.IPv6None;
+            type = "user";
+            target = ubi.UserId;
+        } else if(info is IPAddressBanInfo iabi) {
+            if(info.Kind != BanKind.IPAddress)
+                throw new ArgumentException("info argument is an instance of IPAddressBanInfo but Kind was not set to BanKind.IPAddress", nameof(info));
 
-            string isPerma = duration == TimeSpan.MaxValue ? "1" : "0";
-            string durationStr = duration == TimeSpan.MaxValue ? "-1" : duration.TotalSeconds.ToString();
-            string remoteAddrStr = remoteAddr.ToString();
-            string issuerRemoteAddrStr = issuerRemoteAddr.ToString();
+            type = "addr";
+            target = iabi.Address.ToString();
+        } else throw new ArgumentException("info argument is set to unsupported implementation", nameof(info));
 
-            string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
-            string sig = string.Format(
-                BANS_CREATE_SIG,
-                now, userId, remoteAddrStr,
-                issuerUserId, issuerRemoteAddrStr,
-                durationStr, isPerma, reason
-            );
+        string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
+        string url = string.Format(BANS_REVOKE_URL, BaseURL, Uri.EscapeDataString(type), Uri.EscapeDataString(target), Uri.EscapeDataString(now));
+        string sig = string.Format(BANS_REVOKE_SIG, now, type, target);
 
-            HttpRequestMessage request = new(HttpMethod.Post, string.Format(BANS_CREATE_URL, BaseURL)) {
-                Headers = {
-                    { "X-SharpChat-Signature", CreateStringSignature(sig) },
-                },
-                Content = new FormUrlEncodedContent(new Dictionary<string, string> {
-                    { "t", now },
-                    { "ui", userId },
-                    { "ua", remoteAddrStr },
-                    { "mi", issuerUserId },
-                    { "ma", issuerRemoteAddrStr },
-                    { "d", durationStr },
-                    { "p", isPerma },
-                    { "r", reason },
-                }),
-            };
+        HttpRequestMessage request = new(HttpMethod.Delete, url) {
+            Headers = {
+                { "X-SharpChat-Signature", CreateStringSignature(sig) },
+            },
+        };
 
-            using HttpResponseMessage response = await httpClient.SendAsync(request);
+        using HttpResponseMessage response = await httpClient.SendAsync(request);
+        if(response.StatusCode == HttpStatusCode.NotFound)
+            return false;
 
-            response.EnsureSuccessStatusCode();
-        }
+        response.EnsureSuccessStatusCode();
 
-        private const string BANS_REVOKE_URL = "{0}/bans/revoke?t={1}&s={2}&x={3}";
-        private const string BANS_REVOKE_SIG = "revoke#{0}#{1}#{2}";
+        return response.StatusCode == HttpStatusCode.NoContent;
+    }
 
-        public async Task<bool> BanRevokeAsync(BanInfo info) {
-            string type;
-            string target;
+    private const string BANS_CHECK_URL = "{0}/bans/check?u={1}&a={2}&x={3}&n={4}";
+    private const string BANS_CHECK_SIG = "check#{0}#{1}#{2}#{3}";
 
-            if(info is UserBanInfo ubi) {
-                if(info.Kind != BanKind.User)
-                    throw new ArgumentException("info argument is an instance of UserBanInfo but Kind was not set to BanKind.User", nameof(info));
+    public async Task<BanInfo?> BanGetAsync(string? userIdOrName = null, IPAddress? remoteAddr = null) {
+        userIdOrName ??= "0";
+        remoteAddr ??= IPAddress.None;
 
-                type = "user";
-                target = ubi.UserId;
-            } else if(info is IPAddressBanInfo iabi) {
-                if(info.Kind != BanKind.IPAddress)
-                    throw new ArgumentException("info argument is an instance of IPAddressBanInfo but Kind was not set to BanKind.IPAddress", nameof(info));
+        string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
+        bool usingUserName = string.IsNullOrEmpty(userIdOrName) || userIdOrName.Any(c => c is < '0' or > '9');
+        string remoteAddrStr = remoteAddr.ToString();
+        string usingUserNameStr = usingUserName ? "1" : "0";
+        string url = string.Format(BANS_CHECK_URL, BaseURL, Uri.EscapeDataString(userIdOrName), Uri.EscapeDataString(remoteAddrStr), Uri.EscapeDataString(now), Uri.EscapeDataString(usingUserNameStr));
+        string sig = string.Format(BANS_CHECK_SIG, now, userIdOrName, remoteAddrStr, usingUserNameStr);
 
-                type = "addr";
-                target = iabi.Address.ToString();
-            } else throw new ArgumentException("info argument is set to unsupported implementation", nameof(info));
+        HttpRequestMessage request = new(HttpMethod.Get, url) {
+            Headers = {
+                { "X-SharpChat-Signature", CreateStringSignature(sig) },
+            },
+        };
 
-            string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
-            string url = string.Format(BANS_REVOKE_URL, BaseURL, Uri.EscapeDataString(type), Uri.EscapeDataString(target), Uri.EscapeDataString(now));
-            string sig = string.Format(BANS_REVOKE_SIG, now, type, target);
+        using HttpResponseMessage response = await httpClient.SendAsync(request);
+        response.EnsureSuccessStatusCode();
 
-            HttpRequestMessage request = new(HttpMethod.Delete, url) {
-                Headers = {
-                    { "X-SharpChat-Signature", CreateStringSignature(sig) },
-                },
-            };
+        using Stream stream = await response.Content.ReadAsStreamAsync();
+        FlashiiRawBanInfo? rawBanInfo = await JsonSerializer.DeserializeAsync<FlashiiRawBanInfo>(stream);
+        if(rawBanInfo?.IsBanned != true || rawBanInfo.HasExpired)
+            return null;
 
-            using HttpResponseMessage response = await httpClient.SendAsync(request);
-            if(response.StatusCode == HttpStatusCode.NotFound)
-                return false;
+        return rawBanInfo.RemoteAddress is null or "::"
+            ? new FlashiiUserBanInfo(rawBanInfo)
+            : new FlashiiIPAddressBanInfo(rawBanInfo);
+    }
 
-            response.EnsureSuccessStatusCode();
+    private const string BANS_LIST_URL = "{0}/bans/list?x={1}";
+    private const string BANS_LIST_SIG = "list#{0}";
 
-            return response.StatusCode == HttpStatusCode.NoContent;
-        }
+    public async Task<BanInfo[]> BanGetListAsync() {
+        string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
+        string url = string.Format(BANS_LIST_URL, BaseURL, Uri.EscapeDataString(now));
+        string sig = string.Format(BANS_LIST_SIG, now);
 
-        private const string BANS_CHECK_URL = "{0}/bans/check?u={1}&a={2}&x={3}&n={4}";
-        private const string BANS_CHECK_SIG = "check#{0}#{1}#{2}#{3}";
+        HttpRequestMessage request = new(HttpMethod.Get, url) {
+            Headers = {
+                { "X-SharpChat-Signature", CreateStringSignature(sig) },
+            },
+        };
 
-        public async Task<BanInfo?> BanGetAsync(string? userIdOrName = null, IPAddress? remoteAddr = null) {
-            userIdOrName ??= "0";
-            remoteAddr ??= IPAddress.None;
+        using HttpResponseMessage response = await httpClient.SendAsync(request);
+        response.EnsureSuccessStatusCode();
 
-            string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
-            bool usingUserName = string.IsNullOrEmpty(userIdOrName) || userIdOrName.Any(c => c is < '0' or > '9');
-            string remoteAddrStr = remoteAddr.ToString();
-            string usingUserNameStr = usingUserName ? "1" : "0";
-            string url = string.Format(BANS_CHECK_URL, BaseURL, Uri.EscapeDataString(userIdOrName), Uri.EscapeDataString(remoteAddrStr), Uri.EscapeDataString(now), Uri.EscapeDataString(usingUserNameStr));
-            string sig = string.Format(BANS_CHECK_SIG, now, userIdOrName, remoteAddrStr, usingUserNameStr);
+        using Stream stream = await response.Content.ReadAsStreamAsync();
+        FlashiiRawBanInfo[]? list = await JsonSerializer.DeserializeAsync<FlashiiRawBanInfo[]>(stream);
+        if(list is null || list.Length < 1)
+            return [];
 
-            HttpRequestMessage request = new(HttpMethod.Get, url) {
-                Headers = {
-                    { "X-SharpChat-Signature", CreateStringSignature(sig) },
-                },
-            };
-
-            using HttpResponseMessage response = await httpClient.SendAsync(request);
-            response.EnsureSuccessStatusCode();
-
-            using Stream stream = await response.Content.ReadAsStreamAsync();
-            FlashiiRawBanInfo? rawBanInfo = await JsonSerializer.DeserializeAsync<FlashiiRawBanInfo>(stream);
-            if(rawBanInfo?.IsBanned != true || rawBanInfo.HasExpired)
-                return null;
-
-            return rawBanInfo.RemoteAddress is null or "::"
-                ? new FlashiiUserBanInfo(rawBanInfo)
-                : new FlashiiIPAddressBanInfo(rawBanInfo);
-        }
-
-        private const string BANS_LIST_URL = "{0}/bans/list?x={1}";
-        private const string BANS_LIST_SIG = "list#{0}";
-
-        public async Task<BanInfo[]> BanGetListAsync() {
-            string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
-            string url = string.Format(BANS_LIST_URL, BaseURL, Uri.EscapeDataString(now));
-            string sig = string.Format(BANS_LIST_SIG, now);
-
-            HttpRequestMessage request = new(HttpMethod.Get, url) {
-                Headers = {
-                    { "X-SharpChat-Signature", CreateStringSignature(sig) },
-                },
-            };
-
-            using HttpResponseMessage response = await httpClient.SendAsync(request);
-            response.EnsureSuccessStatusCode();
-
-            using Stream stream = await response.Content.ReadAsStreamAsync();
-            FlashiiRawBanInfo[]? list = await JsonSerializer.DeserializeAsync<FlashiiRawBanInfo[]>(stream);
-            if(list is null || list.Length < 1)
-                return [];
-
-            return [.. list.Where(b => b?.IsBanned == true && !b.HasExpired).Select(b => {
-                return (BanInfo)(b.RemoteAddress is null or "::"
-                    ? new FlashiiUserBanInfo(b) : new FlashiiIPAddressBanInfo(b));
-            })];
-        }
+        return [.. list.Where(b => b?.IsBanned == true && !b.HasExpired).Select(b => {
+            return (BanInfo)(b.RemoteAddress is null or "::"
+                ? new FlashiiUserBanInfo(b) : new FlashiiIPAddressBanInfo(b));
+        })];
     }
 }
diff --git a/SharpChat.Flashii/FlashiiIPAddressBanInfo.cs b/SharpChat.Flashii/FlashiiIPAddressBanInfo.cs
index 7bd6fc5..6c85b03 100644
--- a/SharpChat.Flashii/FlashiiIPAddressBanInfo.cs
+++ b/SharpChat.Flashii/FlashiiIPAddressBanInfo.cs
@@ -1,9 +1,9 @@
-using SharpChat.Bans;
+using SharpChat.Bans;
 using System.Net;
 
-namespace SharpChat.Flashii {
-    public class FlashiiIPAddressBanInfo(FlashiiRawBanInfo rawBanInfo) : FlashiiBanInfo(BanKind.IPAddress, rawBanInfo), IPAddressBanInfo {
-        public IPAddress Address { get; } = IPAddress.TryParse(rawBanInfo.RemoteAddress, out IPAddress? addr) && addr is not null ? addr : IPAddress.IPv6None;
-        public override string ToString() => Address.ToString();
-    }
+namespace SharpChat.Flashii;
+
+public class FlashiiIPAddressBanInfo(FlashiiRawBanInfo rawBanInfo) : FlashiiBanInfo(BanKind.IPAddress, rawBanInfo), IPAddressBanInfo {
+    public IPAddress Address { get; } = IPAddress.TryParse(rawBanInfo.RemoteAddress, out IPAddress? addr) && addr is not null ? addr : IPAddress.IPv6None;
+    public override string ToString() => Address.ToString();
 }
diff --git a/SharpChat.Flashii/FlashiiRawBanInfo.cs b/SharpChat.Flashii/FlashiiRawBanInfo.cs
index 3263fde..5993341 100644
--- a/SharpChat.Flashii/FlashiiRawBanInfo.cs
+++ b/SharpChat.Flashii/FlashiiRawBanInfo.cs
@@ -1,29 +1,29 @@
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
 
-namespace SharpChat.Flashii {
-    public class FlashiiRawBanInfo {
-        [JsonPropertyName("is_ban")]
-        public bool IsBanned { get; set; }
+namespace SharpChat.Flashii;
 
-        [JsonPropertyName("user_id")]
-        public string? UserId { get; set; }
+public class FlashiiRawBanInfo {
+    [JsonPropertyName("is_ban")]
+    public bool IsBanned { get; set; }
 
-        [JsonPropertyName("user_name")]
-        public string? UserName { get; set; }
+    [JsonPropertyName("user_id")]
+    public string? UserId { get; set; }
 
-        [JsonPropertyName("user_colour")]
-        public int UserColourRaw { get; set; }
-        public ColourInheritable UserColour => ColourInheritable.FromMisuzu(UserColourRaw);
+    [JsonPropertyName("user_name")]
+    public string? UserName { get; set; }
 
-        [JsonPropertyName("ip_addr")]
-        public string? RemoteAddress { get; set; }
+    [JsonPropertyName("user_colour")]
+    public int UserColourRaw { get; set; }
+    public ColourInheritable UserColour => ColourInheritable.FromMisuzu(UserColourRaw);
 
-        [JsonPropertyName("is_perma")]
-        public bool IsPermanent { get; set; }
+    [JsonPropertyName("ip_addr")]
+    public string? RemoteAddress { get; set; }
 
-        [JsonPropertyName("expires")]
-        public DateTimeOffset ExpiresAt { get; set; }
+    [JsonPropertyName("is_perma")]
+    public bool IsPermanent { get; set; }
 
-        public bool HasExpired => !IsPermanent && DateTimeOffset.UtcNow >= ExpiresAt;
-    }
+    [JsonPropertyName("expires")]
+    public DateTimeOffset ExpiresAt { get; set; }
+
+    public bool HasExpired => !IsPermanent && DateTimeOffset.UtcNow >= ExpiresAt;
 }
diff --git a/SharpChat.Flashii/FlashiiUserBanInfo.cs b/SharpChat.Flashii/FlashiiUserBanInfo.cs
index 6124547..ffe7ea9 100644
--- a/SharpChat.Flashii/FlashiiUserBanInfo.cs
+++ b/SharpChat.Flashii/FlashiiUserBanInfo.cs
@@ -1,10 +1,10 @@
-using SharpChat.Bans;
+using SharpChat.Bans;
 
-namespace SharpChat.Flashii {
-    public class FlashiiUserBanInfo(FlashiiRawBanInfo rawBanInfo) : FlashiiBanInfo(BanKind.User, rawBanInfo), UserBanInfo {
-        public string UserId { get; } = rawBanInfo.UserId ?? string.Empty;
-        public string UserName { get; } = rawBanInfo.UserName ?? $"({rawBanInfo.UserId ?? string.Empty})";
-        public ColourInheritable UserColour { get; } = ColourInheritable.FromMisuzu(rawBanInfo.UserColourRaw);
-        public override string ToString() => UserName;
-    }
+namespace SharpChat.Flashii;
+
+public class FlashiiUserBanInfo(FlashiiRawBanInfo rawBanInfo) : FlashiiBanInfo(BanKind.User, rawBanInfo), UserBanInfo {
+    public string UserId { get; } = rawBanInfo.UserId ?? string.Empty;
+    public string UserName { get; } = rawBanInfo.UserName ?? $"({rawBanInfo.UserId ?? string.Empty})";
+    public ColourInheritable UserColour { get; } = ColourInheritable.FromMisuzu(rawBanInfo.UserColourRaw);
+    public override string ToString() => UserName;
 }
diff --git a/SharpChat.SockChat/S2CPacket.cs b/SharpChat.SockChat/S2CPacket.cs
index f43f048..240d56b 100644
--- a/SharpChat.SockChat/S2CPacket.cs
+++ b/SharpChat.SockChat/S2CPacket.cs
@@ -1,5 +1,5 @@
-namespace SharpChat.SockChat {
-    public interface S2CPacket {
-        string Pack();
-    }
+namespace SharpChat.SockChat;
+
+public interface S2CPacket {
+    string Pack();
 }
diff --git a/SharpChat.SockChat/S2CPackets/AuthFailS2CPacket.cs b/SharpChat.SockChat/S2CPackets/AuthFailS2CPacket.cs
index c6e9533..e9f0f89 100644
--- a/SharpChat.SockChat/S2CPackets/AuthFailS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/AuthFailS2CPacket.cs
@@ -1,43 +1,43 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class AuthFailS2CPacket(
-        AuthFailS2CPacket.Reason reason,
-        DateTimeOffset? expiresAt = null
-    ) : S2CPacket {
-        public enum Reason {
-            AuthInvalid,
-            MaxSessions,
-            Banned,
-            Exception,
+namespace SharpChat.SockChat.S2CPackets;
+
+public class AuthFailS2CPacket(
+    AuthFailS2CPacket.Reason reason,
+    DateTimeOffset? expiresAt = null
+) : S2CPacket {
+    public enum Reason {
+        AuthInvalid,
+        MaxSessions,
+        Banned,
+        Exception,
+    }
+
+    public string Pack() {
+        StringBuilder sb = new();
+
+        sb.Append("1\tn\t");
+
+        switch(reason) {
+            case Reason.AuthInvalid:
+            default:
+                sb.Append("authfail");
+                break;
+            case Reason.Exception:
+                sb.Append("userfail");
+                break;
+            case Reason.MaxSessions:
+                sb.Append("sockfail");
+                break;
+            case Reason.Banned:
+                sb.Append("joinfail\t");
+                if(expiresAt is null || expiresAt == DateTimeOffset.MaxValue)
+                    sb.Append("-1");
+                else
+                    sb.Append(expiresAt.Value.ToUnixTimeSeconds());
+                break;
         }
 
-        public string Pack() {
-            StringBuilder sb = new();
-
-            sb.Append("1\tn\t");
-
-            switch(reason) {
-                case Reason.AuthInvalid:
-                default:
-                    sb.Append("authfail");
-                    break;
-                case Reason.Exception:
-                    sb.Append("userfail");
-                    break;
-                case Reason.MaxSessions:
-                    sb.Append("sockfail");
-                    break;
-                case Reason.Banned:
-                    sb.Append("joinfail\t");
-                    if(expiresAt is null || expiresAt == DateTimeOffset.MaxValue)
-                        sb.Append("-1");
-                    else
-                        sb.Append(expiresAt.Value.ToUnixTimeSeconds());
-                    break;
-            }
-
-            return sb.ToString();
-        }
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/AuthSuccessS2CPacket.cs b/SharpChat.SockChat/S2CPackets/AuthSuccessS2CPacket.cs
index 44bf7ce..caad0f8 100644
--- a/SharpChat.SockChat/S2CPackets/AuthSuccessS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/AuthSuccessS2CPacket.cs
@@ -1,40 +1,40 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class AuthSuccessS2CPacket(
-        string userId,
-        string userName,
-        ColourInheritable userColour,
-        int userRank,
-        UserPermissions userPerms,
-        string channelName,
-        int maxMsgLength
-    ) : S2CPacket {
-        public string Pack() {
-            StringBuilder sb = new();
+namespace SharpChat.SockChat.S2CPackets;
 
-            sb.Append("1\ty\t");
-            sb.Append(userId);
-            sb.Append('\t');
-            sb.Append(userName);
-            sb.Append('\t');
-            sb.Append(userColour);
-            sb.Append('\t');
-            sb.Append(userRank);
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.CreateChannel) ? (userPerms.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
-            sb.Append('\t');
-            sb.Append(channelName);
-            sb.Append('\t');
-            sb.Append(maxMsgLength);
+public class AuthSuccessS2CPacket(
+    string userId,
+    string userName,
+    ColourInheritable userColour,
+    int userRank,
+    UserPermissions userPerms,
+    string channelName,
+    int maxMsgLength
+) : S2CPacket {
+    public string Pack() {
+        StringBuilder sb = new();
 
-            return sb.ToString();
-        }
+        sb.Append("1\ty\t");
+        sb.Append(userId);
+        sb.Append('\t');
+        sb.Append(userName);
+        sb.Append('\t');
+        sb.Append(userColour);
+        sb.Append('\t');
+        sb.Append(userRank);
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.CreateChannel) ? (userPerms.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
+        sb.Append('\t');
+        sb.Append(channelName);
+        sb.Append('\t');
+        sb.Append(maxMsgLength);
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/BanListS2CPacket.cs b/SharpChat.SockChat/S2CPackets/BanListS2CPacket.cs
index fb4fc6d..8f35c84 100644
--- a/SharpChat.SockChat/S2CPackets/BanListS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/BanListS2CPacket.cs
@@ -1,29 +1,29 @@
 using SharpChat.Bans;
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class BanListS2CPacket(
-        long msgId,
-        IEnumerable<BanListS2CPacket.Entry> entries
-    ) : S2CPacket {
-        public record Entry(BanKind type, string value);
+namespace SharpChat.SockChat.S2CPackets;
 
-        public string Pack() {
-            StringBuilder sb = new();
+public class BanListS2CPacket(
+    long msgId,
+    IEnumerable<BanListS2CPacket.Entry> entries
+) : S2CPacket {
+    public record Entry(BanKind type, string value);
 
-            sb.Append("2\t");
-            sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds());
-            sb.Append("\t-1\t0\fbanlist\f");
-            sb.Append(string.Join(", ", entries.Select(entry => string.Format(
-                @"<a href=""javascript:void(0);"" onclick=""Chat.SendMessageWrapper('{0} '+ this.innerHTML);"">{1}</a>",
-                entry.type == BanKind.IPAddress ? "/unbanip" : "/unban",
-                entry.value
-            ))));
-            sb.Append('\t');
-            sb.Append(msgId);
-            sb.Append("\t10010");
+    public string Pack() {
+        StringBuilder sb = new();
 
-            return sb.ToString();
-        }
+        sb.Append("2\t");
+        sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds());
+        sb.Append("\t-1\t0\fbanlist\f");
+        sb.Append(string.Join(", ", entries.Select(entry => string.Format(
+            @"<a href=""javascript:void(0);"" onclick=""Chat.SendMessageWrapper('{0} '+ this.innerHTML);"">{1}</a>",
+            entry.type == BanKind.IPAddress ? "/unbanip" : "/unban",
+            entry.value
+        ))));
+        sb.Append('\t');
+        sb.Append(msgId);
+        sb.Append("\t10010");
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/ChannelCreateS2CPacket.cs b/SharpChat.SockChat/S2CPackets/ChannelCreateS2CPacket.cs
index 3618ed5..498d5e3 100644
--- a/SharpChat.SockChat/S2CPackets/ChannelCreateS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/ChannelCreateS2CPacket.cs
@@ -1,22 +1,22 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class ChannelCreateS2CPacket(
-        string name,
-        bool hasPassword,
-        bool isTemporary
-    ) : S2CPacket {
-        public string Pack() {
-            StringBuilder sb = new();
+namespace SharpChat.SockChat.S2CPackets;
 
-            sb.Append("4\t0\t");
-            sb.Append(name);
-            sb.Append('\t');
-            sb.Append(hasPassword ? '1' : '0');
-            sb.Append('\t');
-            sb.Append(isTemporary ? '1' : '0');
+public class ChannelCreateS2CPacket(
+    string name,
+    bool hasPassword,
+    bool isTemporary
+) : S2CPacket {
+    public string Pack() {
+        StringBuilder sb = new();
 
-            return sb.ToString();
-        }
+        sb.Append("4\t0\t");
+        sb.Append(name);
+        sb.Append('\t');
+        sb.Append(hasPassword ? '1' : '0');
+        sb.Append('\t');
+        sb.Append(isTemporary ? '1' : '0');
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/ChannelDeleteS2CPacket.cs b/SharpChat.SockChat/S2CPackets/ChannelDeleteS2CPacket.cs
index 9bcaa1b..b185d87 100644
--- a/SharpChat.SockChat/S2CPackets/ChannelDeleteS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/ChannelDeleteS2CPacket.cs
@@ -1,16 +1,16 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class ChannelDeleteS2CPacket(
-        string channelName
-    ) : S2CPacket {
-        public string Pack() {
-            StringBuilder sb = new();
+namespace SharpChat.SockChat.S2CPackets;
 
-            sb.Append("4\t2\t");
-            sb.Append(channelName);
+public class ChannelDeleteS2CPacket(
+    string channelName
+) : S2CPacket {
+    public string Pack() {
+        StringBuilder sb = new();
 
-            return sb.ToString();
-        }
+        sb.Append("4\t2\t");
+        sb.Append(channelName);
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/ChannelUpdateS2CPacket.cs b/SharpChat.SockChat/S2CPackets/ChannelUpdateS2CPacket.cs
index 4e7a6ed..25547fb 100644
--- a/SharpChat.SockChat/S2CPackets/ChannelUpdateS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/ChannelUpdateS2CPacket.cs
@@ -1,25 +1,25 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class ChannelUpdateS2CPacket(
-        string previousName,
-        string newName,
-        bool hasPassword,
-        bool isTemporary
-    ) : S2CPacket {
-        public string Pack() {
-            StringBuilder sb = new();
+namespace SharpChat.SockChat.S2CPackets;
 
-            sb.Append("4\t1\t");
-            sb.Append(previousName);
-            sb.Append('\t');
-            sb.Append(newName);
-            sb.Append('\t');
-            sb.Append(hasPassword ? '1' : '0');
-            sb.Append('\t');
-            sb.Append(isTemporary ? '1' : '0');
+public class ChannelUpdateS2CPacket(
+    string previousName,
+    string newName,
+    bool hasPassword,
+    bool isTemporary
+) : S2CPacket {
+    public string Pack() {
+        StringBuilder sb = new();
 
-            return sb.ToString();
-        }
+        sb.Append("4\t1\t");
+        sb.Append(previousName);
+        sb.Append('\t');
+        sb.Append(newName);
+        sb.Append('\t');
+        sb.Append(hasPassword ? '1' : '0');
+        sb.Append('\t');
+        sb.Append(isTemporary ? '1' : '0');
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/ChatMessageAddS2CPacket.cs b/SharpChat.SockChat/S2CPackets/ChatMessageAddS2CPacket.cs
index 4a1952d..09fbb56 100644
--- a/SharpChat.SockChat/S2CPackets/ChatMessageAddS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/ChatMessageAddS2CPacket.cs
@@ -1,48 +1,48 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class ChatMessageAddS2CPacket(
-        long msgId,
-        DateTimeOffset created,
-        string userId,
-        string text,
-        bool isAction,
-        bool isPrivate
-    ) : S2CPacket {
-        public string Pack() {
-            StringBuilder sb = new();
+namespace SharpChat.SockChat.S2CPackets;
 
-            sb.Append("2\t");
+public class ChatMessageAddS2CPacket(
+    long msgId,
+    DateTimeOffset created,
+    string userId,
+    string text,
+    bool isAction,
+    bool isPrivate
+) : S2CPacket {
+    public string Pack() {
+        StringBuilder sb = new();
 
-            sb.Append(created.ToUnixTimeSeconds());
-            sb.Append('\t');
+        sb.Append("2\t");
 
-            sb.Append(userId);
-            sb.Append('\t');
+        sb.Append(created.ToUnixTimeSeconds());
+        sb.Append('\t');
 
-            if(isAction)
-                sb.Append("<i>");
+        sb.Append(userId);
+        sb.Append('\t');
 
-            sb.Append(
-                text.Replace("<", "&lt;")
-                    .Replace(">", "&gt;")
-                    .Replace("\n", " <br/> ")
-                    .Replace("\t", "    ")
-            );
+        if(isAction)
+            sb.Append("<i>");
 
-            if(isAction)
-                sb.Append("</i>");
+        sb.Append(
+            text.Replace("<", "&lt;")
+                .Replace(">", "&gt;")
+                .Replace("\n", " <br/> ")
+                .Replace("\t", "    ")
+        );
 
-            sb.Append('\t');
-            sb.Append(msgId);
-            sb.AppendFormat(
-                "\t1{0}0{1}{2}",
-                isAction ? '1' : '0',
-                isAction ? '0' : '1',
-                isPrivate ? '1' : '0'
-            );
+        if(isAction)
+            sb.Append("</i>");
 
-            return sb.ToString();
-        }
+        sb.Append('\t');
+        sb.Append(msgId);
+        sb.AppendFormat(
+            "\t1{0}0{1}{2}",
+            isAction ? '1' : '0',
+            isAction ? '0' : '1',
+            isPrivate ? '1' : '0'
+        );
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/ChatMessageDeleteS2CPacket.cs b/SharpChat.SockChat/S2CPackets/ChatMessageDeleteS2CPacket.cs
index 79a01a2..caf2573 100644
--- a/SharpChat.SockChat/S2CPackets/ChatMessageDeleteS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/ChatMessageDeleteS2CPacket.cs
@@ -1,14 +1,14 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class ChatMessageDeleteS2CPacket(long eventId) : S2CPacket {
-        public string Pack() {
-            StringBuilder sb = new();
+namespace SharpChat.SockChat.S2CPackets;
 
-            sb.Append("6\t");
-            sb.Append(eventId);
+public class ChatMessageDeleteS2CPacket(long eventId) : S2CPacket {
+    public string Pack() {
+        StringBuilder sb = new();
 
-            return sb.ToString();
-        }
+        sb.Append("6\t");
+        sb.Append(eventId);
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/CommandResponseS2CPacket.cs b/SharpChat.SockChat/S2CPackets/CommandResponseS2CPacket.cs
index bc671f1..fa72bb7 100644
--- a/SharpChat.SockChat/S2CPackets/CommandResponseS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/CommandResponseS2CPacket.cs
@@ -1,85 +1,85 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class CommandResponseS2CPacket(
-        long msgId,
-        string stringId,
-        bool isError = true,
-        params object[] args
-    ) : S2CPacket {
-        public string Pack() {
-            StringBuilder sb = new();
+namespace SharpChat.SockChat.S2CPackets;
 
-            if(stringId == LCR.WELCOME) {
-                sb.Append("7\t1\t");
-                sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds());
-                sb.Append("\t-1\tChatBot\tinherit\t\t");
-            } else {
-                sb.Append("2\t");
-                sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds());
-                sb.Append("\t-1\t");
+public class CommandResponseS2CPacket(
+    long msgId,
+    string stringId,
+    bool isError = true,
+    params object[] args
+) : S2CPacket {
+    public string Pack() {
+        StringBuilder sb = new();
+
+        if(stringId == LCR.WELCOME) {
+            sb.Append("7\t1\t");
+            sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds());
+            sb.Append("\t-1\tChatBot\tinherit\t\t");
+        } else {
+            sb.Append("2\t");
+            sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds());
+            sb.Append("\t-1\t");
+        }
+
+        sb.Append(isError ? '1' : '0');
+        sb.Append('\f');
+        sb.Append(stringId == LCR.WELCOME ? LCR.BROADCAST : stringId);
+
+        if(args.Length > 0)
+            foreach(object arg in args) {
+                sb.Append('\f');
+                sb.Append(arg);
             }
 
-            sb.Append(isError ? '1' : '0');
-            sb.Append('\f');
-            sb.Append(stringId == LCR.WELCOME ? LCR.BROADCAST : stringId);
+        sb.Append('\t');
 
-            if(args.Length > 0)
-                foreach(object arg in args) {
-                    sb.Append('\f');
-                    sb.Append(arg);
-                }
+        if(stringId == LCR.WELCOME) {
+            sb.Append(stringId);
+            sb.Append("\t0");
+        } else
+            sb.Append(msgId);
 
-            sb.Append('\t');
+        sb.Append("\t10010");
+        /*sb.AppendFormat(
+            "\t1{0}0{1}{2}",
+            Flags.HasFlag(ChatMessageFlags.Action) ? '1' : '0',
+            Flags.HasFlag(ChatMessageFlags.Action) ? '0' : '1',
+            Flags.HasFlag(ChatMessageFlags.Private) ? '1' : '0'
+        );*/
 
-            if(stringId == LCR.WELCOME) {
-                sb.Append(stringId);
-                sb.Append("\t0");
-            } else
-                sb.Append(msgId);
-
-            sb.Append("\t10010");
-            /*sb.AppendFormat(
-                "\t1{0}0{1}{2}",
-                Flags.HasFlag(ChatMessageFlags.Action) ? '1' : '0',
-                Flags.HasFlag(ChatMessageFlags.Action) ? '0' : '1',
-                Flags.HasFlag(ChatMessageFlags.Private) ? '1' : '0'
-            );*/
-
-            return sb.ToString();
-        }
-    }
-
-    // Abbreviated class name because otherwise shit gets wide
-    public static class LCR {
-        public const string GENERIC_ERROR = "generr";
-        public const string COMMAND_NOT_FOUND = "nocmd";
-        public const string COMMAND_NOT_ALLOWED = "cmdna";
-        public const string COMMAND_FORMAT_ERROR = "cmderr";
-        public const string WELCOME = "welcome";
-        public const string BROADCAST = "say";
-        public const string IP_ADDRESS = "ipaddr";
-        public const string USER_NOT_FOUND = "usernf";
-        public const string NAME_IN_USE = "nameinuse";
-        public const string CHANNEL_INSUFFICIENT_HIERARCHY = "ipchan";
-        public const string CHANNEL_INVALID_PASSWORD = "ipwchan";
-        public const string CHANNEL_NOT_FOUND = "nochan";
-        public const string CHANNEL_ALREADY_EXISTS = "nischan";
-        public const string CHANNEL_NAME_INVALID = "inchan";
-        public const string CHANNEL_CREATED = "crchan";
-        public const string CHANNEL_DELETE_FAILED = "ndchan";
-        public const string CHANNEL_DELETED = "delchan";
-        public const string CHANNEL_PASSWORD_CHANGED = "cpwdchan";
-        public const string CHANNEL_HIERARCHY_CHANGED = "cprivchan";
-        public const string USERS_LISTING_ERROR = "whoerr";
-        public const string USERS_LISTING_CHANNEL = "whochan";
-        public const string USERS_LISTING_SERVER = "who";
-        public const string INSUFFICIENT_HIERARCHY = "rankerr";
-        public const string MESSAGE_DELETE_ERROR = "delerr";
-        public const string KICK_NOT_ALLOWED = "kickna";
-        public const string USER_NOT_BANNED = "notban";
-        public const string USER_UNBANNED = "unban";
-        public const string FLOOD_WARN = "flwarn";
-        public const string NICKNAME_CHANGE = "nick";
+        return sb.ToString();
     }
 }
+
+// Abbreviated class name because otherwise shit gets wide
+public static class LCR {
+    public const string GENERIC_ERROR = "generr";
+    public const string COMMAND_NOT_FOUND = "nocmd";
+    public const string COMMAND_NOT_ALLOWED = "cmdna";
+    public const string COMMAND_FORMAT_ERROR = "cmderr";
+    public const string WELCOME = "welcome";
+    public const string BROADCAST = "say";
+    public const string IP_ADDRESS = "ipaddr";
+    public const string USER_NOT_FOUND = "usernf";
+    public const string NAME_IN_USE = "nameinuse";
+    public const string CHANNEL_INSUFFICIENT_HIERARCHY = "ipchan";
+    public const string CHANNEL_INVALID_PASSWORD = "ipwchan";
+    public const string CHANNEL_NOT_FOUND = "nochan";
+    public const string CHANNEL_ALREADY_EXISTS = "nischan";
+    public const string CHANNEL_NAME_INVALID = "inchan";
+    public const string CHANNEL_CREATED = "crchan";
+    public const string CHANNEL_DELETE_FAILED = "ndchan";
+    public const string CHANNEL_DELETED = "delchan";
+    public const string CHANNEL_PASSWORD_CHANGED = "cpwdchan";
+    public const string CHANNEL_HIERARCHY_CHANGED = "cprivchan";
+    public const string USERS_LISTING_ERROR = "whoerr";
+    public const string USERS_LISTING_CHANNEL = "whochan";
+    public const string USERS_LISTING_SERVER = "who";
+    public const string INSUFFICIENT_HIERARCHY = "rankerr";
+    public const string MESSAGE_DELETE_ERROR = "delerr";
+    public const string KICK_NOT_ALLOWED = "kickna";
+    public const string USER_NOT_BANNED = "notban";
+    public const string USER_UNBANNED = "unban";
+    public const string FLOOD_WARN = "flwarn";
+    public const string NICKNAME_CHANGE = "nick";
+}
diff --git a/SharpChat.SockChat/S2CPackets/ContextChannelsS2CPacket.cs b/SharpChat.SockChat/S2CPackets/ContextChannelsS2CPacket.cs
index 294da5d..245bf40 100644
--- a/SharpChat.SockChat/S2CPackets/ContextChannelsS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/ContextChannelsS2CPacket.cs
@@ -1,25 +1,25 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class ContextChannelsS2CPacket(IEnumerable<ContextChannelsS2CPacket.Entry> entries) : S2CPacket {
-        public record Entry(string name, bool hasPassword, bool isTemporary);
+namespace SharpChat.SockChat.S2CPackets;
 
-        public string Pack() {
-            StringBuilder sb = new();
+public class ContextChannelsS2CPacket(IEnumerable<ContextChannelsS2CPacket.Entry> entries) : S2CPacket {
+    public record Entry(string name, bool hasPassword, bool isTemporary);
 
-            sb.Append("7\t2\t");
-            sb.Append(entries.Count());
+    public string Pack() {
+        StringBuilder sb = new();
 
-            foreach(Entry entry in entries) {
-                sb.Append('\t');
-                sb.Append(entry.name);
-                sb.Append('\t');
-                sb.Append(entry.hasPassword ? '1' : '0');
-                sb.Append('\t');
-                sb.Append(entry.isTemporary ? '1' : '0');
-            }
+        sb.Append("7\t2\t");
+        sb.Append(entries.Count());
 
-            return sb.ToString();
+        foreach(Entry entry in entries) {
+            sb.Append('\t');
+            sb.Append(entry.name);
+            sb.Append('\t');
+            sb.Append(entry.hasPassword ? '1' : '0');
+            sb.Append('\t');
+            sb.Append(entry.isTemporary ? '1' : '0');
         }
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/ContextClearS2CPacket.cs b/SharpChat.SockChat/S2CPackets/ContextClearS2CPacket.cs
index 1dfb7f3..a60b931 100644
--- a/SharpChat.SockChat/S2CPackets/ContextClearS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/ContextClearS2CPacket.cs
@@ -1,22 +1,22 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class ContextClearS2CPacket(ContextClearS2CPacket.Mode mode) : S2CPacket {
-        public enum Mode {
-            Messages = 0,
-            Users = 1,
-            Channels = 2,
-            MessagesUsers = 3,
-            MessagesUsersChannels = 4,
-        }
+namespace SharpChat.SockChat.S2CPackets;
 
-        public string Pack() {
-            StringBuilder sb = new();
+public class ContextClearS2CPacket(ContextClearS2CPacket.Mode mode) : S2CPacket {
+    public enum Mode {
+        Messages = 0,
+        Users = 1,
+        Channels = 2,
+        MessagesUsers = 3,
+        MessagesUsersChannels = 4,
+    }
 
-            sb.Append("8\t");
-            sb.Append((int)mode);
+    public string Pack() {
+        StringBuilder sb = new();
 
-            return sb.ToString();
-        }
+        sb.Append("8\t");
+        sb.Append((int)mode);
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/ContextUsersS2CPacket.cs b/SharpChat.SockChat/S2CPackets/ContextUsersS2CPacket.cs
index 0390c41..90e723a 100644
--- a/SharpChat.SockChat/S2CPackets/ContextUsersS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/ContextUsersS2CPacket.cs
@@ -1,37 +1,37 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class ContextUsersS2CPacket(IEnumerable<ContextUsersS2CPacket.Entry> entries) : S2CPacket {
-        public record Entry(string id, string name, ColourInheritable colour, int rank, UserPermissions perms, bool visible);
+namespace SharpChat.SockChat.S2CPackets;
 
-        public string Pack() {
-            StringBuilder sb = new();
+public class ContextUsersS2CPacket(IEnumerable<ContextUsersS2CPacket.Entry> entries) : S2CPacket {
+    public record Entry(string id, string name, ColourInheritable colour, int rank, UserPermissions perms, bool visible);
 
-            sb.Append("7\t0\t");
-            sb.Append(entries.Count());
+    public string Pack() {
+        StringBuilder sb = new();
 
-            foreach(Entry entry in entries) {
-                sb.Append('\t');
-                sb.Append(entry.id);
-                sb.Append('\t');
-                sb.Append(entry.name);
-                sb.Append('\t');
-                sb.Append(entry.colour);
-                sb.Append('\t');
-                sb.Append(entry.rank);
-                sb.Append(' ');
-                sb.Append(entry.perms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
-                sb.Append(' ');
-                sb.Append(entry.perms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
-                sb.Append(' ');
-                sb.Append(entry.perms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
-                sb.Append(' ');
-                sb.Append(entry.perms.HasFlag(UserPermissions.CreateChannel) ? (entry.perms.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
-                sb.Append('\t');
-                sb.Append(entry.visible ? '1' : '0');
-            }
+        sb.Append("7\t0\t");
+        sb.Append(entries.Count());
 
-            return sb.ToString();
+        foreach(Entry entry in entries) {
+            sb.Append('\t');
+            sb.Append(entry.id);
+            sb.Append('\t');
+            sb.Append(entry.name);
+            sb.Append('\t');
+            sb.Append(entry.colour);
+            sb.Append('\t');
+            sb.Append(entry.rank);
+            sb.Append(' ');
+            sb.Append(entry.perms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
+            sb.Append(' ');
+            sb.Append(entry.perms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
+            sb.Append(' ');
+            sb.Append(entry.perms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
+            sb.Append(' ');
+            sb.Append(entry.perms.HasFlag(UserPermissions.CreateChannel) ? (entry.perms.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
+            sb.Append('\t');
+            sb.Append(entry.visible ? '1' : '0');
         }
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/ForceDisconnectS2CPacket.cs b/SharpChat.SockChat/S2CPackets/ForceDisconnectS2CPacket.cs
index a714c9b..1b7f9a9 100644
--- a/SharpChat.SockChat/S2CPackets/ForceDisconnectS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/ForceDisconnectS2CPacket.cs
@@ -1,22 +1,22 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class ForceDisconnectS2CPacket(DateTimeOffset? expires = null) : S2CPacket {
-        public string Pack() {
-            StringBuilder sb = new();
+namespace SharpChat.SockChat.S2CPackets;
 
-            sb.Append("9\t");
+public class ForceDisconnectS2CPacket(DateTimeOffset? expires = null) : S2CPacket {
+    public string Pack() {
+        StringBuilder sb = new();
 
-            if(expires.HasValue && expires.Value > DateTimeOffset.UtcNow) {
-                sb.Append("1\t");
-                if(expires.Value < DateTimeOffset.MaxValue)
-                    sb.Append(expires.Value.ToUnixTimeSeconds());
-                else
-                    sb.Append("-1");
-            } else
-                sb.Append('0');
-            
-            return sb.ToString();
-        }
+        sb.Append("9\t");
+
+        if(expires.HasValue && expires.Value > DateTimeOffset.UtcNow) {
+            sb.Append("1\t");
+            if(expires.Value < DateTimeOffset.MaxValue)
+                sb.Append(expires.Value.ToUnixTimeSeconds());
+            else
+                sb.Append("-1");
+        } else
+            sb.Append('0');
+        
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/PongS2CPacket.cs b/SharpChat.SockChat/S2CPackets/PongS2CPacket.cs
index 08b667a..082d674 100644
--- a/SharpChat.SockChat/S2CPackets/PongS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/PongS2CPacket.cs
@@ -1,7 +1,7 @@
-namespace SharpChat.SockChat.S2CPackets {
-    public class PongS2CPacket : S2CPacket {
-        public string Pack() {
-            return "0\tpong";
-        }
+namespace SharpChat.SockChat.S2CPackets;
+
+public class PongS2CPacket : S2CPacket {
+    public string Pack() {
+        return "0\tpong";
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/UserChannelForceJoinS2CPacket.cs b/SharpChat.SockChat/S2CPackets/UserChannelForceJoinS2CPacket.cs
index 18d9507..b9a628c 100644
--- a/SharpChat.SockChat/S2CPackets/UserChannelForceJoinS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/UserChannelForceJoinS2CPacket.cs
@@ -1,14 +1,14 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class UserChannelForceJoinS2CPacket(string channelName) : S2CPacket {
-        public string Pack() {
-            StringBuilder sb = new();
+namespace SharpChat.SockChat.S2CPackets;
 
-            sb.Append("5\t2\t");
-            sb.Append(channelName);
+public class UserChannelForceJoinS2CPacket(string channelName) : S2CPacket {
+    public string Pack() {
+        StringBuilder sb = new();
 
-            return sb.ToString();
-        }
+        sb.Append("5\t2\t");
+        sb.Append(channelName);
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/UserChannelJoinS2CPacket.cs b/SharpChat.SockChat/S2CPackets/UserChannelJoinS2CPacket.cs
index 7bc00ac..081a9de 100644
--- a/SharpChat.SockChat/S2CPackets/UserChannelJoinS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/UserChannelJoinS2CPacket.cs
@@ -1,37 +1,37 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class UserChannelJoinS2CPacket(
-        long msgId,
-        string userId,
-        string userName,
-        ColourInheritable userColour,
-        int userRank,
-        UserPermissions userPerms
-    ) : S2CPacket {
-        public string Pack() {
-            StringBuilder sb = new();
+namespace SharpChat.SockChat.S2CPackets;
 
-            sb.Append("5\t0\t");
-            sb.Append(userId);
-            sb.Append('\t');
-            sb.Append(userName);
-            sb.Append('\t');
-            sb.Append(userColour);
-            sb.Append('\t');
-            sb.Append(userRank);
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.CreateChannel) ? (userPerms.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
-            sb.Append('\t');
-            sb.Append(msgId);
+public class UserChannelJoinS2CPacket(
+    long msgId,
+    string userId,
+    string userName,
+    ColourInheritable userColour,
+    int userRank,
+    UserPermissions userPerms
+) : S2CPacket {
+    public string Pack() {
+        StringBuilder sb = new();
 
-            return sb.ToString();
-        }
+        sb.Append("5\t0\t");
+        sb.Append(userId);
+        sb.Append('\t');
+        sb.Append(userName);
+        sb.Append('\t');
+        sb.Append(userColour);
+        sb.Append('\t');
+        sb.Append(userRank);
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.CreateChannel) ? (userPerms.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
+        sb.Append('\t');
+        sb.Append(msgId);
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/UserChannelLeaveS2CPacket.cs b/SharpChat.SockChat/S2CPackets/UserChannelLeaveS2CPacket.cs
index 36af72c..1910b9e 100644
--- a/SharpChat.SockChat/S2CPackets/UserChannelLeaveS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/UserChannelLeaveS2CPacket.cs
@@ -1,16 +1,16 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class UserChannelLeaveS2CPacket(long msgId, string userId) : S2CPacket {
-        public string Pack() {
-            StringBuilder sb = new();
+namespace SharpChat.SockChat.S2CPackets;
 
-            sb.Append("5\t1\t");
-            sb.Append(userId);
-            sb.Append('\t');
-            sb.Append(msgId);
+public class UserChannelLeaveS2CPacket(long msgId, string userId) : S2CPacket {
+    public string Pack() {
+        StringBuilder sb = new();
 
-            return sb.ToString();
-        }
+        sb.Append("5\t1\t");
+        sb.Append(userId);
+        sb.Append('\t');
+        sb.Append(msgId);
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/UserConnectS2CPacket.cs b/SharpChat.SockChat/S2CPackets/UserConnectS2CPacket.cs
index 468e1aa..c495663 100644
--- a/SharpChat.SockChat/S2CPackets/UserConnectS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/UserConnectS2CPacket.cs
@@ -1,40 +1,40 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class UserConnectS2CPacket(
-        long msgId,
-        DateTimeOffset joined,
-        string userId,
-        string userName,
-        ColourInheritable userColour,
-        int userRank,
-        UserPermissions userPerms
-    ) : S2CPacket {
-        public string Pack() {
-            StringBuilder sb = new();
+namespace SharpChat.SockChat.S2CPackets;
 
-            sb.Append("1\t");
-            sb.Append(joined.ToUnixTimeSeconds());
-            sb.Append('\t');
-            sb.Append(userId);
-            sb.Append('\t');
-            sb.Append(userName);
-            sb.Append('\t');
-            sb.Append(userColour);
-            sb.Append('\t');
-            sb.Append(userRank);
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.CreateChannel) ? (userPerms.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
-            sb.Append('\t');
-            sb.Append(msgId);
+public class UserConnectS2CPacket(
+    long msgId,
+    DateTimeOffset joined,
+    string userId,
+    string userName,
+    ColourInheritable userColour,
+    int userRank,
+    UserPermissions userPerms
+) : S2CPacket {
+    public string Pack() {
+        StringBuilder sb = new();
 
-            return sb.ToString();
-        }
+        sb.Append("1\t");
+        sb.Append(joined.ToUnixTimeSeconds());
+        sb.Append('\t');
+        sb.Append(userId);
+        sb.Append('\t');
+        sb.Append(userName);
+        sb.Append('\t');
+        sb.Append(userColour);
+        sb.Append('\t');
+        sb.Append(userRank);
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.CreateChannel) ? (userPerms.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
+        sb.Append('\t');
+        sb.Append(msgId);
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/UserDisconnectS2CPacket.cs b/SharpChat.SockChat/S2CPackets/UserDisconnectS2CPacket.cs
index 18d4a69..cd6f0a9 100644
--- a/SharpChat.SockChat/S2CPackets/UserDisconnectS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/UserDisconnectS2CPacket.cs
@@ -1,51 +1,51 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class UserDisconnectS2CPacket(
-        long msgId,
-        DateTimeOffset disconnected,
-        string userId,
-        string userName,
-        UserDisconnectS2CPacket.Reason reason
-    ) : S2CPacket {
-        public enum Reason {
-            Leave,
-            TimeOut,
-            Kicked,
-            Flood,
+namespace SharpChat.SockChat.S2CPackets;
+
+public class UserDisconnectS2CPacket(
+    long msgId,
+    DateTimeOffset disconnected,
+    string userId,
+    string userName,
+    UserDisconnectS2CPacket.Reason reason
+) : S2CPacket {
+    public enum Reason {
+        Leave,
+        TimeOut,
+        Kicked,
+        Flood,
+    }
+
+    public string Pack() {
+        StringBuilder sb = new();
+
+        sb.Append("3\t");
+        sb.Append(userId);
+        sb.Append('\t');
+        sb.Append(userName);
+        sb.Append('\t');
+
+        switch(reason) {
+            case Reason.Leave:
+            default:
+                sb.Append("leave");
+                break;
+            case Reason.TimeOut:
+                sb.Append("timeout");
+                break;
+            case Reason.Kicked:
+                sb.Append("kick");
+                break;
+            case Reason.Flood:
+                sb.Append("flood");
+                break;
         }
 
-        public string Pack() {
-            StringBuilder sb = new();
+        sb.Append('\t');
+        sb.Append(disconnected.ToUnixTimeSeconds());
+        sb.Append('\t');
+        sb.Append(msgId);
 
-            sb.Append("3\t");
-            sb.Append(userId);
-            sb.Append('\t');
-            sb.Append(userName);
-            sb.Append('\t');
-
-            switch(reason) {
-                case Reason.Leave:
-                default:
-                    sb.Append("leave");
-                    break;
-                case Reason.TimeOut:
-                    sb.Append("timeout");
-                    break;
-                case Reason.Kicked:
-                    sb.Append("kick");
-                    break;
-                case Reason.Flood:
-                    sb.Append("flood");
-                    break;
-            }
-
-            sb.Append('\t');
-            sb.Append(disconnected.ToUnixTimeSeconds());
-            sb.Append('\t');
-            sb.Append(msgId);
-
-            return sb.ToString();
-        }
+        return sb.ToString();
     }
 }
diff --git a/SharpChat.SockChat/S2CPackets/UserUpdateS2CPacket.cs b/SharpChat.SockChat/S2CPackets/UserUpdateS2CPacket.cs
index 1e22325..2dc6591 100644
--- a/SharpChat.SockChat/S2CPackets/UserUpdateS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/UserUpdateS2CPacket.cs
@@ -1,34 +1,34 @@
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class UserUpdateS2CPacket(
-        string userId,
-        string userName,
-        ColourInheritable userColour,
-        int userRank,
-        UserPermissions userPerms
-    ) : S2CPacket {
-        public string Pack() {
-            StringBuilder sb = new();
+namespace SharpChat.SockChat.S2CPackets;
 
-            sb.Append("10\t");
-            sb.Append(userId);
-            sb.Append('\t');
-            sb.Append(userName);
-            sb.Append('\t');
-            sb.Append(userColour);
-            sb.Append('\t');
-            sb.Append(userRank);
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
-            sb.Append(' ');
-            sb.Append(userPerms.HasFlag(UserPermissions.CreateChannel) ? (userPerms.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
+public class UserUpdateS2CPacket(
+    string userId,
+    string userName,
+    ColourInheritable userColour,
+    int userRank,
+    UserPermissions userPerms
+) : S2CPacket {
+    public string Pack() {
+        StringBuilder sb = new();
 
-            return sb.ToString();
-        }
+        sb.Append("10\t");
+        sb.Append(userId);
+        sb.Append('\t');
+        sb.Append(userName);
+        sb.Append('\t');
+        sb.Append(userColour);
+        sb.Append('\t');
+        sb.Append(userRank);
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
+        sb.Append(' ');
+        sb.Append(userPerms.HasFlag(UserPermissions.CreateChannel) ? (userPerms.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat/C2SPacketHandler.cs b/SharpChat/C2SPacketHandler.cs
index d90cfc6..6c6cd27 100644
--- a/SharpChat/C2SPacketHandler.cs
+++ b/SharpChat/C2SPacketHandler.cs
@@ -1,6 +1,6 @@
-namespace SharpChat {
-    public interface C2SPacketHandler {
-        bool IsMatch(C2SPacketHandlerContext ctx);
-        Task Handle(C2SPacketHandlerContext ctx);
-    }
+namespace SharpChat; 
+
+public interface C2SPacketHandler {
+    bool IsMatch(C2SPacketHandlerContext ctx);
+    Task Handle(C2SPacketHandlerContext ctx);
 }
diff --git a/SharpChat/C2SPacketHandlerContext.cs b/SharpChat/C2SPacketHandlerContext.cs
index c5eacd8..feb008f 100644
--- a/SharpChat/C2SPacketHandlerContext.cs
+++ b/SharpChat/C2SPacketHandlerContext.cs
@@ -1,19 +1,19 @@
-namespace SharpChat {
-    public class C2SPacketHandlerContext(
-        string text,
-        Context chat,
-        Connection connection
-    ) {
-        public string Text { get; } = text ?? throw new ArgumentNullException(nameof(text));
-        public Context Chat { get; } = chat ?? throw new ArgumentNullException(nameof(chat));
-        public Connection Connection { get; } = connection ?? throw new ArgumentNullException(nameof(connection));
+namespace SharpChat;
 
-        public bool CheckPacketId(string packetId) {
-            return Text == packetId || Text.StartsWith(packetId + '\t');
-        }
+public class C2SPacketHandlerContext(
+    string text,
+    Context chat,
+    Connection connection
+) {
+    public string Text { get; } = text ?? throw new ArgumentNullException(nameof(text));
+    public Context Chat { get; } = chat ?? throw new ArgumentNullException(nameof(chat));
+    public Connection Connection { get; } = connection ?? throw new ArgumentNullException(nameof(connection));
 
-        public string[] SplitText(int expect) {
-            return Text.Split('\t', expect + 1);
-        }
+    public bool CheckPacketId(string packetId) {
+        return Text == packetId || Text.StartsWith(packetId + '\t');
+    }
+
+    public string[] SplitText(int expect) {
+        return Text.Split('\t', expect + 1);
     }
 }
diff --git a/SharpChat/C2SPacketHandlers/AuthC2SPacketHandler.cs b/SharpChat/C2SPacketHandlers/AuthC2SPacketHandler.cs
index 998f8f8..4ec1479 100644
--- a/SharpChat/C2SPacketHandlers/AuthC2SPacketHandler.cs
+++ b/SharpChat/C2SPacketHandlers/AuthC2SPacketHandler.cs
@@ -3,110 +3,110 @@ using SharpChat.Bans;
 using SharpChat.Configuration;
 using SharpChat.SockChat.S2CPackets;
 
-namespace SharpChat.C2SPacketHandlers {
-    public class AuthC2SPacketHandler(
-        AuthClient authClient,
-        BansClient bansClient,
-        Channel defaultChannel,
-        CachedValue<int> maxMsgLength,
-        CachedValue<int> maxConns
-    ) : C2SPacketHandler {
-        private readonly Channel DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel));
-        private readonly CachedValue<int> MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength));
-        private readonly CachedValue<int> MaxConnections = maxConns ?? throw new ArgumentNullException(nameof(maxConns));
+namespace SharpChat.C2SPacketHandlers;
 
-        public bool IsMatch(C2SPacketHandlerContext ctx) {
-            return ctx.CheckPacketId("1");
+public class AuthC2SPacketHandler(
+    AuthClient authClient,
+    BansClient bansClient,
+    Channel defaultChannel,
+    CachedValue<int> maxMsgLength,
+    CachedValue<int> maxConns
+) : C2SPacketHandler {
+    private readonly Channel DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel));
+    private readonly CachedValue<int> MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength));
+    private readonly CachedValue<int> MaxConnections = maxConns ?? throw new ArgumentNullException(nameof(maxConns));
+
+    public bool IsMatch(C2SPacketHandlerContext ctx) {
+        return ctx.CheckPacketId("1");
+    }
+
+    public async Task Handle(C2SPacketHandlerContext ctx) {
+        string[] args = ctx.SplitText(3);
+
+        string? authMethod = args.ElementAtOrDefault(1);
+        string? authToken = args.ElementAtOrDefault(2);
+
+        if(string.IsNullOrWhiteSpace(authMethod) || string.IsNullOrWhiteSpace(authToken)) {
+            await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid));
+            ctx.Connection.Dispose();
+            return;
         }
 
-        public async Task Handle(C2SPacketHandlerContext ctx) {
-            string[] args = ctx.SplitText(3);
+        if(authMethod.All(c => c is >= '0' and <= '9') && authToken.Contains(':')) {
+            string[] tokenParts = authToken.Split(':', 2);
+            authMethod = tokenParts[0];
+            authToken = tokenParts[1];
+        }
 
-            string? authMethod = args.ElementAtOrDefault(1);
-            string? authToken = args.ElementAtOrDefault(2);
+        try {
+            AuthResult authResult = await authClient.AuthVerifyAsync(
+                ctx.Connection.RemoteAddress,
+                authMethod,
+                authToken
+            );
 
-            if(string.IsNullOrWhiteSpace(authMethod) || string.IsNullOrWhiteSpace(authToken)) {
-                await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid));
+            BanInfo? banInfo = await bansClient.BanGetAsync(authResult.UserId, ctx.Connection.RemoteAddress);
+            if(banInfo is not null) {
+                Logger.Write($"<{ctx.Connection.Id}> User is banned.");
+                await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.Banned, banInfo.IsPermanent ? DateTimeOffset.MaxValue : banInfo.ExpiresAt));
                 ctx.Connection.Dispose();
                 return;
             }
 
-            if(authMethod.All(c => c is >= '0' and <= '9') && authToken.Contains(':')) {
-                string[] tokenParts = authToken.Split(':', 2);
-                authMethod = tokenParts[0];
-                authToken = tokenParts[1];
-            }
-
+            await ctx.Chat.ContextAccess.WaitAsync();
             try {
-                AuthResult authResult = await authClient.AuthVerifyAsync(
-                    ctx.Connection.RemoteAddress,
-                    authMethod,
-                    authToken
-                );
+                User? user = ctx.Chat.Users.FirstOrDefault(u => u.UserId == authResult.UserId);
 
-                BanInfo? banInfo = await bansClient.BanGetAsync(authResult.UserId, ctx.Connection.RemoteAddress);
-                if(banInfo is not null) {
-                    Logger.Write($"<{ctx.Connection.Id}> User is banned.");
-                    await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.Banned, banInfo.IsPermanent ? DateTimeOffset.MaxValue : banInfo.ExpiresAt));
+                if(user == null)
+                    user = new User(
+                        authResult.UserId,
+                        authResult.UserName ?? $"({authResult.UserId})",
+                        authResult.UserColour,
+                        authResult.UserRank,
+                        authResult.UserPermissions
+                    );
+                else
+                    await ctx.Chat.UpdateUser(
+                        user,
+                        userName: authResult.UserName ?? $"({authResult.UserId})",
+                        colour: authResult.UserColour,
+                        rank: authResult.UserRank,
+                        perms: authResult.UserPermissions
+                    );
+
+                // Enforce a maximum amount of connections per user
+                if(ctx.Chat.Connections.Count(conn => conn.User == user) >= MaxConnections) {
+                    await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.MaxSessions));
                     ctx.Connection.Dispose();
                     return;
                 }
 
-                await ctx.Chat.ContextAccess.WaitAsync();
-                try {
-                    User? user = ctx.Chat.Users.FirstOrDefault(u => u.UserId == authResult.UserId);
+                ctx.Connection.BumpPing();
+                ctx.Connection.User = user;
+                await ctx.Connection.Send(new CommandResponseS2CPacket(0, LCR.WELCOME, false, $"Welcome to Flashii Chat, {user.UserName}!"));
 
-                    if(user == null)
-                        user = new User(
-                            authResult.UserId,
-                            authResult.UserName ?? $"({authResult.UserId})",
-                            authResult.UserColour,
-                            authResult.UserRank,
-                            authResult.UserPermissions
-                        );
-                    else
-                        await ctx.Chat.UpdateUser(
-                            user,
-                            userName: authResult.UserName ?? $"({authResult.UserId})",
-                            colour: authResult.UserColour,
-                            rank: authResult.UserRank,
-                            perms: authResult.UserPermissions
-                        );
+                if(File.Exists("welcome.txt")) {
+                    IEnumerable<string> lines = File.ReadAllLines("welcome.txt").Where(x => !string.IsNullOrWhiteSpace(x));
+                    string? line = lines.ElementAtOrDefault(RNG.Next(lines.Count()));
 
-                    // Enforce a maximum amount of connections per user
-                    if(ctx.Chat.Connections.Count(conn => conn.User == user) >= MaxConnections) {
-                        await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.MaxSessions));
-                        ctx.Connection.Dispose();
-                        return;
-                    }
-
-                    ctx.Connection.BumpPing();
-                    ctx.Connection.User = user;
-                    await ctx.Connection.Send(new CommandResponseS2CPacket(0, LCR.WELCOME, false, $"Welcome to Flashii Chat, {user.UserName}!"));
-
-                    if(File.Exists("welcome.txt")) {
-                        IEnumerable<string> lines = File.ReadAllLines("welcome.txt").Where(x => !string.IsNullOrWhiteSpace(x));
-                        string? line = lines.ElementAtOrDefault(RNG.Next(lines.Count()));
-
-                        if(!string.IsNullOrWhiteSpace(line))
-                            await ctx.Connection.Send(new CommandResponseS2CPacket(0, LCR.WELCOME, false, line));
-                    }
-
-                    await ctx.Chat.HandleJoin(user, DefaultChannel, ctx.Connection, MaxMessageLength);
-                } finally {
-                    ctx.Chat.ContextAccess.Release();
+                    if(!string.IsNullOrWhiteSpace(line))
+                        await ctx.Connection.Send(new CommandResponseS2CPacket(0, LCR.WELCOME, false, line));
                 }
-            } catch(AuthFailedException ex) {
-                Logger.Write($"<{ctx.Connection.Id}> Failed to authenticate: {ex}");
-                await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid));
-                ctx.Connection.Dispose();
-                throw;
-            } catch(Exception ex) {
-                Logger.Write($"<{ctx.Connection.Id}> Failed to authenticate: {ex}");
-                await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.Exception));
-                ctx.Connection.Dispose();
-                throw;
+
+                await ctx.Chat.HandleJoin(user, DefaultChannel, ctx.Connection, MaxMessageLength);
+            } finally {
+                ctx.Chat.ContextAccess.Release();
             }
+        } catch(AuthFailedException ex) {
+            Logger.Write($"<{ctx.Connection.Id}> Failed to authenticate: {ex}");
+            await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid));
+            ctx.Connection.Dispose();
+            throw;
+        } catch(Exception ex) {
+            Logger.Write($"<{ctx.Connection.Id}> Failed to authenticate: {ex}");
+            await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.Exception));
+            ctx.Connection.Dispose();
+            throw;
         }
     }
 }
diff --git a/SharpChat/C2SPacketHandlers/PingC2SPacketHandler.cs b/SharpChat/C2SPacketHandlers/PingC2SPacketHandler.cs
index 8739a57..c0e658a 100644
--- a/SharpChat/C2SPacketHandlers/PingC2SPacketHandler.cs
+++ b/SharpChat/C2SPacketHandlers/PingC2SPacketHandler.cs
@@ -2,39 +2,39 @@ using SharpChat.Auth;
 using SharpChat.SockChat.S2CPackets;
 using System.Net;
 
-namespace SharpChat.C2SPacketHandlers {
-    public class PingC2SPacketHandler(AuthClient authClient) : C2SPacketHandler {
-        private readonly TimeSpan BumpInterval = TimeSpan.FromMinutes(1);
-        private DateTimeOffset LastBump = DateTimeOffset.MinValue;
+namespace SharpChat.C2SPacketHandlers;
 
-        public bool IsMatch(C2SPacketHandlerContext ctx) {
-            return ctx.CheckPacketId("0");
-        }
+public class PingC2SPacketHandler(AuthClient authClient) : C2SPacketHandler {
+    private readonly TimeSpan BumpInterval = TimeSpan.FromMinutes(1);
+    private DateTimeOffset LastBump = DateTimeOffset.MinValue;
 
-        public async Task Handle(C2SPacketHandlerContext ctx) {
-            string[] parts = ctx.SplitText(2);
+    public bool IsMatch(C2SPacketHandlerContext ctx) {
+        return ctx.CheckPacketId("0");
+    }
 
-            if(!int.TryParse(parts.FirstOrDefault(), out int pTime))
-                return;
+    public async Task Handle(C2SPacketHandlerContext ctx) {
+        string[] parts = ctx.SplitText(2);
 
-            ctx.Connection.BumpPing();
-            await ctx.Connection.Send(new PongS2CPacket());
+        if(!int.TryParse(parts.FirstOrDefault(), out int pTime))
+            return;
 
-            ctx.Chat.ContextAccess.Wait();
-            try {
-                if(LastBump < DateTimeOffset.UtcNow - BumpInterval) {
-                    (IPAddress, string)[] bumpList = [.. ctx.Chat.Users
-                        .Where(u => u.Status == UserStatus.Online && ctx.Chat.Connections.Any(c => c.User == u))
-                        .Select(u => (ctx.Chat.GetRemoteAddresses(u).FirstOrDefault() ?? IPAddress.None, u.UserId))];
+        ctx.Connection.BumpPing();
+        await ctx.Connection.Send(new PongS2CPacket());
 
-                    if(bumpList.Length > 0)
-                        await authClient.AuthBumpUsersOnlineAsync(bumpList);
+        ctx.Chat.ContextAccess.Wait();
+        try {
+            if(LastBump < DateTimeOffset.UtcNow - BumpInterval) {
+                (IPAddress, string)[] bumpList = [.. ctx.Chat.Users
+                    .Where(u => u.Status == UserStatus.Online && ctx.Chat.Connections.Any(c => c.User == u))
+                    .Select(u => (ctx.Chat.GetRemoteAddresses(u).FirstOrDefault() ?? IPAddress.None, u.UserId))];
 
-                    LastBump = DateTimeOffset.UtcNow;
-                }
-            } finally {
-                ctx.Chat.ContextAccess.Release();
+                if(bumpList.Length > 0)
+                    await authClient.AuthBumpUsersOnlineAsync(bumpList);
+
+                LastBump = DateTimeOffset.UtcNow;
             }
+        } finally {
+            ctx.Chat.ContextAccess.Release();
         }
     }
 }
diff --git a/SharpChat/C2SPacketHandlers/SendMessageC2SPacketHandler.cs b/SharpChat/C2SPacketHandlers/SendMessageC2SPacketHandler.cs
index a21d3b5..59930b8 100644
--- a/SharpChat/C2SPacketHandlers/SendMessageC2SPacketHandler.cs
+++ b/SharpChat/C2SPacketHandlers/SendMessageC2SPacketHandler.cs
@@ -4,86 +4,86 @@ using SharpChat.Snowflake;
 using System.Globalization;
 using System.Text;
 
-namespace SharpChat.C2SPacketHandlers {
-    public class SendMessageC2SPacketHandler(
-        RandomSnowflake randomSnowflake,
-        CachedValue<int> maxMsgLength
-    ) : C2SPacketHandler {
-        private readonly CachedValue<int> MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength));
+namespace SharpChat.C2SPacketHandlers;
 
-        private List<ClientCommand> Commands { get; } = [];
+public class SendMessageC2SPacketHandler(
+    RandomSnowflake randomSnowflake,
+    CachedValue<int> maxMsgLength
+) : C2SPacketHandler {
+    private readonly CachedValue<int> MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength));
 
-        public void AddCommand(ClientCommand command) {
-            Commands.Add(command ?? throw new ArgumentNullException(nameof(command)));
-        }
+    private List<ClientCommand> Commands { get; } = [];
 
-        public void AddCommands(IEnumerable<ClientCommand> commands) {
-            Commands.AddRange(commands ?? throw new ArgumentNullException(nameof(commands)));
-        }
+    public void AddCommand(ClientCommand command) {
+        Commands.Add(command ?? throw new ArgumentNullException(nameof(command)));
+    }
 
-        public bool IsMatch(C2SPacketHandlerContext ctx) {
-            return ctx.CheckPacketId("2");
-        }
+    public void AddCommands(IEnumerable<ClientCommand> commands) {
+        Commands.AddRange(commands ?? throw new ArgumentNullException(nameof(commands)));
+    }
 
-        public async Task Handle(C2SPacketHandlerContext ctx) {
-            string[] args = ctx.SplitText(3);
+    public bool IsMatch(C2SPacketHandlerContext ctx) {
+        return ctx.CheckPacketId("2");
+    }
 
-            User? user = ctx.Connection.User;
-            string? messageText = args.ElementAtOrDefault(2);
+    public async Task Handle(C2SPacketHandlerContext ctx) {
+        string[] args = ctx.SplitText(3);
 
-            if(user == null || !user.Can(UserPermissions.SendMessage) || string.IsNullOrWhiteSpace(messageText))
+        User? user = ctx.Connection.User;
+        string? messageText = args.ElementAtOrDefault(2);
+
+        if(user == null || !user.Can(UserPermissions.SendMessage) || string.IsNullOrWhiteSpace(messageText))
+            return;
+
+        // Extra validation step, not necessary at all but enforces proper formatting in SCv1.
+        if(!long.TryParse(args[1], out long mUserId) || user.UserId != mUserId.ToString())
+            return;
+
+        ctx.Chat.ContextAccess.Wait();
+        try {
+            if(!ctx.Chat.UserLastChannel.TryGetValue(user.UserId, out Channel? channel)
+                && (channel is null || !ctx.Chat.IsInChannel(user, channel)))
                 return;
 
-            // Extra validation step, not necessary at all but enforces proper formatting in SCv1.
-            if(!long.TryParse(args[1], out long mUserId) || user.UserId != mUserId.ToString())
-                return;
+            if(user.Status != UserStatus.Online)
+                await ctx.Chat.UpdateUser(user, status: UserStatus.Online);
 
-            ctx.Chat.ContextAccess.Wait();
-            try {
-                if(!ctx.Chat.UserLastChannel.TryGetValue(user.UserId, out Channel? channel)
-                    && (channel is null || !ctx.Chat.IsInChannel(user, channel)))
-                    return;
+            int maxMsgLength = MaxMessageLength;
+            StringInfo messageTextInfo = new(messageText);
+            if(Encoding.UTF8.GetByteCount(messageText) > (maxMsgLength * 10)
+                || messageTextInfo.LengthInTextElements > maxMsgLength)
+                messageText = messageTextInfo.SubstringByTextElements(0, Math.Min(messageTextInfo.LengthInTextElements, maxMsgLength));
 
-                if(user.Status != UserStatus.Online)
-                    await ctx.Chat.UpdateUser(user, status: UserStatus.Online);
-
-                int maxMsgLength = MaxMessageLength;
-                StringInfo messageTextInfo = new(messageText);
-                if(Encoding.UTF8.GetByteCount(messageText) > (maxMsgLength * 10)
-                    || messageTextInfo.LengthInTextElements > maxMsgLength)
-                    messageText = messageTextInfo.SubstringByTextElements(0, Math.Min(messageTextInfo.LengthInTextElements, maxMsgLength));
-
-                messageText = messageText.Trim();
+            messageText = messageText.Trim();
 
 #if DEBUG
-                Logger.Write($"<{ctx.Connection.Id} {user.UserName}> {messageText}");
+            Logger.Write($"<{ctx.Connection.Id} {user.UserName}> {messageText}");
 #endif
 
-                if(messageText.StartsWith('/')) {
-                    ClientCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel);
-                    foreach(ClientCommand cmd in Commands)
-                        if(cmd.IsMatch(context)) {
-                            await cmd.Dispatch(context);
-                            return;
-                        }
-                }
-
-                await ctx.Chat.DispatchEvent(new MessageCreateEvent(
-                    randomSnowflake.Next(),
-                    channel.Name,
-                    user.UserId,
-                    user.UserName,
-                    user.Colour,
-                    user.Rank,
-                    user.NickName,
-                    user.Permissions,
-                    DateTimeOffset.Now,
-                    messageText,
-                    false, false, false
-                ));
-            } finally {
-                ctx.Chat.ContextAccess.Release();
+            if(messageText.StartsWith('/')) {
+                ClientCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel);
+                foreach(ClientCommand cmd in Commands)
+                    if(cmd.IsMatch(context)) {
+                        await cmd.Dispatch(context);
+                        return;
+                    }
             }
+
+            await ctx.Chat.DispatchEvent(new MessageCreateEvent(
+                randomSnowflake.Next(),
+                channel.Name,
+                user.UserId,
+                user.UserName,
+                user.Colour,
+                user.Rank,
+                user.NickName,
+                user.Permissions,
+                DateTimeOffset.Now,
+                messageText,
+                false, false, false
+            ));
+        } finally {
+            ctx.Chat.ContextAccess.Release();
         }
     }
 }
diff --git a/SharpChat/Channel.cs b/SharpChat/Channel.cs
index c23edfe..4cc0361 100644
--- a/SharpChat/Channel.cs
+++ b/SharpChat/Channel.cs
@@ -1,40 +1,40 @@
-namespace SharpChat {
-    public class Channel(
-        string name,
-        string password = "",
-        bool isTemporary = false,
-        int rank = 0,
-        string ownerId = ""
-    ) {
-        public string Name { get; } = name ?? throw new ArgumentNullException(nameof(name));
-        public string Password { get; set; } = password ?? string.Empty;
-        public bool IsTemporary { get; set; } = isTemporary;
-        public int Rank { get; set; } = rank;
-        public string OwnerId { get; set; } = ownerId;
+namespace SharpChat;
 
-        public bool HasPassword
-            => !string.IsNullOrWhiteSpace(Password);
+public class Channel(
+    string name,
+    string password = "",
+    bool isTemporary = false,
+    int rank = 0,
+    string ownerId = ""
+) {
+    public string Name { get; } = name ?? throw new ArgumentNullException(nameof(name));
+    public string Password { get; set; } = password ?? string.Empty;
+    public bool IsTemporary { get; set; } = isTemporary;
+    public int Rank { get; set; } = rank;
+    public string OwnerId { get; set; } = ownerId;
 
-        public bool NameEquals(string name) {
-            return string.Equals(name, Name, StringComparison.InvariantCultureIgnoreCase);
-        }
+    public bool HasPassword
+        => !string.IsNullOrWhiteSpace(Password);
 
-        public bool IsOwner(User user) {
-            return string.IsNullOrEmpty(OwnerId)
-                && user != null
-                && OwnerId == user.UserId;
-        }
+    public bool NameEquals(string name) {
+        return string.Equals(name, Name, StringComparison.InvariantCultureIgnoreCase);
+    }
 
-        public override int GetHashCode() {
-            return Name.GetHashCode();
-        }
+    public bool IsOwner(User user) {
+        return string.IsNullOrEmpty(OwnerId)
+            && user != null
+            && OwnerId == user.UserId;
+    }
 
-        public static bool CheckName(string name) {
-            return !string.IsNullOrWhiteSpace(name) && name.All(CheckNameChar);
-        }
+    public override int GetHashCode() {
+        return Name.GetHashCode();
+    }
 
-        public static bool CheckNameChar(char c) {
-            return char.IsLetter(c) || char.IsNumber(c) || c == '-' || c == '_';
-        }
+    public static bool CheckName(string name) {
+        return !string.IsNullOrWhiteSpace(name) && name.All(CheckNameChar);
+    }
+
+    public static bool CheckNameChar(char c) {
+        return char.IsLetter(c) || char.IsNumber(c) || c == '-' || c == '_';
     }
 }
diff --git a/SharpChat/ClientCommand.cs b/SharpChat/ClientCommand.cs
index 63b071b..fdcf95f 100644
--- a/SharpChat/ClientCommand.cs
+++ b/SharpChat/ClientCommand.cs
@@ -1,6 +1,6 @@
-namespace SharpChat {
-    public interface ClientCommand {
-        bool IsMatch(ClientCommandContext ctx);
-        Task Dispatch(ClientCommandContext ctx);
-    }
+namespace SharpChat;
+
+public interface ClientCommand {
+    bool IsMatch(ClientCommandContext ctx);
+    Task Dispatch(ClientCommandContext ctx);
 }
diff --git a/SharpChat/ClientCommandContext.cs b/SharpChat/ClientCommandContext.cs
index 3f51212..d53a9ba 100644
--- a/SharpChat/ClientCommandContext.cs
+++ b/SharpChat/ClientCommandContext.cs
@@ -1,49 +1,49 @@
-namespace SharpChat {
-    public class ClientCommandContext {
-        public string Name { get; }
-        public string[] Args { get; }
-        public Context Chat { get; }
-        public User User { get; }
-        public Connection Connection { get; }
-        public Channel Channel { get; }
+namespace SharpChat;
 
-        public ClientCommandContext(
-            string text,
-            Context chat,
-            User user,
-            Connection connection,
-            Channel channel
-        ) {
-            ArgumentNullException.ThrowIfNull(text);
+public class ClientCommandContext {
+    public string Name { get; }
+    public string[] Args { get; }
+    public Context Chat { get; }
+    public User User { get; }
+    public Connection Connection { get; }
+    public Channel Channel { get; }
 
-            Chat = chat ?? throw new ArgumentNullException(nameof(chat));
-            User = user ?? throw new ArgumentNullException(nameof(user));
-            Connection = connection ?? throw new ArgumentNullException(nameof(connection));
-            Channel = channel ?? throw new ArgumentNullException(nameof(channel));
+    public ClientCommandContext(
+        string text,
+        Context chat,
+        User user,
+        Connection connection,
+        Channel channel
+    ) {
+        ArgumentNullException.ThrowIfNull(text);
 
-            string[] parts = text[1..].Split(' ');
-            Name = parts.First().Replace(".", string.Empty);
-            Args = [.. parts.Skip(1)];
-        }
+        Chat = chat ?? throw new ArgumentNullException(nameof(chat));
+        User = user ?? throw new ArgumentNullException(nameof(user));
+        Connection = connection ?? throw new ArgumentNullException(nameof(connection));
+        Channel = channel ?? throw new ArgumentNullException(nameof(channel));
 
-        public ClientCommandContext(
-            string name,
-            string[] args,
-            Context chat,
-            User user,
-            Connection connection,
-            Channel channel
-        ) {
-            Name = name ?? throw new ArgumentNullException(nameof(name));
-            Args = args ?? throw new ArgumentNullException(nameof(args));
-            Chat = chat ?? throw new ArgumentNullException(nameof(chat));
-            User = user ?? throw new ArgumentNullException(nameof(user));
-            Connection = connection ?? throw new ArgumentNullException(nameof(connection));
-            Channel = channel ?? throw new ArgumentNullException(nameof(channel));
-        }
+        string[] parts = text[1..].Split(' ');
+        Name = parts.First().Replace(".", string.Empty);
+        Args = [.. parts.Skip(1)];
+    }
 
-        public bool NameEquals(string name) {
-            return Name.Equals(name, StringComparison.InvariantCultureIgnoreCase);
-        }
+    public ClientCommandContext(
+        string name,
+        string[] args,
+        Context chat,
+        User user,
+        Connection connection,
+        Channel channel
+    ) {
+        Name = name ?? throw new ArgumentNullException(nameof(name));
+        Args = args ?? throw new ArgumentNullException(nameof(args));
+        Chat = chat ?? throw new ArgumentNullException(nameof(chat));
+        User = user ?? throw new ArgumentNullException(nameof(user));
+        Connection = connection ?? throw new ArgumentNullException(nameof(connection));
+        Channel = channel ?? throw new ArgumentNullException(nameof(channel));
+    }
+
+    public bool NameEquals(string name) {
+        return Name.Equals(name, StringComparison.InvariantCultureIgnoreCase);
     }
 }
diff --git a/SharpChat/ClientCommands/AFKClientCommand.cs b/SharpChat/ClientCommands/AFKClientCommand.cs
index bdacf1e..4718d1a 100644
--- a/SharpChat/ClientCommands/AFKClientCommand.cs
+++ b/SharpChat/ClientCommands/AFKClientCommand.cs
@@ -1,34 +1,34 @@
 using System.Globalization;
 using System.Text;
 
-namespace SharpChat.ClientCommands {
-    public class AFKClientCommand : ClientCommand {
-        private const string DEFAULT = "AFK";
-        public const int MAX_GRAPHEMES = 5;
-        public const int MAX_BYTES = MAX_GRAPHEMES * 10;
+namespace SharpChat.ClientCommands;
 
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("afk");
+public class AFKClientCommand : ClientCommand {
+    private const string DEFAULT = "AFK";
+    public const int MAX_GRAPHEMES = 5;
+    public const int MAX_BYTES = MAX_GRAPHEMES * 10;
+
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("afk");
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        string? statusText = ctx.Args.FirstOrDefault();
+        if(string.IsNullOrWhiteSpace(statusText))
+            statusText = DEFAULT;
+        else {
+            statusText = statusText.Trim();
+
+            StringInfo sti = new(statusText);
+            if(Encoding.UTF8.GetByteCount(statusText) > MAX_BYTES
+                || sti.LengthInTextElements > MAX_GRAPHEMES)
+                statusText = sti.SubstringByTextElements(0, Math.Min(sti.LengthInTextElements, MAX_GRAPHEMES)).Trim();
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            string? statusText = ctx.Args.FirstOrDefault();
-            if(string.IsNullOrWhiteSpace(statusText))
-                statusText = DEFAULT;
-            else {
-                statusText = statusText.Trim();
-
-                StringInfo sti = new(statusText);
-                if(Encoding.UTF8.GetByteCount(statusText) > MAX_BYTES
-                    || sti.LengthInTextElements > MAX_GRAPHEMES)
-                    statusText = sti.SubstringByTextElements(0, Math.Min(sti.LengthInTextElements, MAX_GRAPHEMES)).Trim();
-            }
-
-            await ctx.Chat.UpdateUser(
-                ctx.User,
-                status: UserStatus.Away,
-                statusText: statusText
-            );
-        }
+        await ctx.Chat.UpdateUser(
+            ctx.User,
+            status: UserStatus.Away,
+            statusText: statusText
+        );
     }
 }
diff --git a/SharpChat/ClientCommands/ActionClientCommand.cs b/SharpChat/ClientCommands/ActionClientCommand.cs
index dae5643..baf6294 100644
--- a/SharpChat/ClientCommands/ActionClientCommand.cs
+++ b/SharpChat/ClientCommands/ActionClientCommand.cs
@@ -1,33 +1,33 @@
 using SharpChat.Events;
 
-namespace SharpChat.ClientCommands {
-    public class ActionClientCommand : ClientCommand {
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("action")
-                || ctx.NameEquals("me");
-        }
+namespace SharpChat.ClientCommands;
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            if(ctx.Args.Length < 1)
-                return;
+public class ActionClientCommand : ClientCommand {
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("action")
+            || ctx.NameEquals("me");
+    }
 
-            string actionStr = string.Join(' ', ctx.Args);
-            if(string.IsNullOrWhiteSpace(actionStr))
-                return;
+    public async Task Dispatch(ClientCommandContext ctx) {
+        if(ctx.Args.Length < 1)
+            return;
 
-            await ctx.Chat.DispatchEvent(new MessageCreateEvent(
-                ctx.Chat.RandomSnowflake.Next(),
-                ctx.Channel.Name,
-                ctx.User.UserId,
-                ctx.User.UserName,
-                ctx.User.Colour,
-                ctx.User.Rank,
-                ctx.User.NickName,
-                ctx.User.Permissions,
-                DateTimeOffset.Now,
-                actionStr,
-                false, true, false
-            ));
-        }
+        string actionStr = string.Join(' ', ctx.Args);
+        if(string.IsNullOrWhiteSpace(actionStr))
+            return;
+
+        await ctx.Chat.DispatchEvent(new MessageCreateEvent(
+            ctx.Chat.RandomSnowflake.Next(),
+            ctx.Channel.Name,
+            ctx.User.UserId,
+            ctx.User.UserName,
+            ctx.User.Colour,
+            ctx.User.Rank,
+            ctx.User.NickName,
+            ctx.User.Permissions,
+            DateTimeOffset.Now,
+            actionStr,
+            false, true, false
+        ));
     }
 }
diff --git a/SharpChat/ClientCommands/BanListClientCommand.cs b/SharpChat/ClientCommands/BanListClientCommand.cs
index 02b2162..7907ee3 100644
--- a/SharpChat/ClientCommands/BanListClientCommand.cs
+++ b/SharpChat/ClientCommands/BanListClientCommand.cs
@@ -1,30 +1,30 @@
 using SharpChat.Bans;
 using SharpChat.SockChat.S2CPackets;
 
-namespace SharpChat.ClientCommands {
-    public class BanListClientCommand(BansClient bansClient) : ClientCommand {
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("bans")
-                || ctx.NameEquals("banned");
+namespace SharpChat.ClientCommands;
+
+public class BanListClientCommand(BansClient bansClient) : ClientCommand {
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("bans")
+            || ctx.NameEquals("banned");
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        long msgId = ctx.Chat.RandomSnowflake.Next();
+
+        if(!ctx.User.Can(UserPermissions.BanUser | UserPermissions.KickUser)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
+            return;
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            long msgId = ctx.Chat.RandomSnowflake.Next();
-
-            if(!ctx.User.Can(UserPermissions.BanUser | UserPermissions.KickUser)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
-                return;
-            }
-
-            try {
-                BanInfo[] banInfos = await bansClient.BanGetListAsync();
-                await ctx.Chat.SendTo(ctx.User, new BanListS2CPacket(
-                    msgId,
-                    banInfos.Select(bi => new BanListS2CPacket.Entry(bi.Kind, bi.ToString()))
-                ));
-            } catch(Exception) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.GENERIC_ERROR, true));
-            }
+        try {
+            BanInfo[] banInfos = await bansClient.BanGetListAsync();
+            await ctx.Chat.SendTo(ctx.User, new BanListS2CPacket(
+                msgId,
+                banInfos.Select(bi => new BanListS2CPacket.Entry(bi.Kind, bi.ToString()))
+            ));
+        } catch(Exception) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.GENERIC_ERROR, true));
         }
     }
 }
diff --git a/SharpChat/ClientCommands/BroadcastClientCommand.cs b/SharpChat/ClientCommands/BroadcastClientCommand.cs
index 6fa95c5..878e9b3 100644
--- a/SharpChat/ClientCommands/BroadcastClientCommand.cs
+++ b/SharpChat/ClientCommands/BroadcastClientCommand.cs
@@ -1,34 +1,34 @@
 using SharpChat.Events;
 using SharpChat.SockChat.S2CPackets;
 
-namespace SharpChat.ClientCommands {
-    public class BroadcastClientCommand : ClientCommand {
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("say")
-                || ctx.NameEquals("broadcast");
+namespace SharpChat.ClientCommands;
+
+public class BroadcastClientCommand : ClientCommand {
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("say")
+            || ctx.NameEquals("broadcast");
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        long msgId = ctx.Chat.RandomSnowflake.Next();
+
+        if(!ctx.User.Can(UserPermissions.Broadcast)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
+            return;
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            long msgId = ctx.Chat.RandomSnowflake.Next();
-
-            if(!ctx.User.Can(UserPermissions.Broadcast)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
-                return;
-            }
-
-            await ctx.Chat.DispatchEvent(new MessageCreateEvent(
-                msgId,
-                string.Empty,
-                ctx.User.UserId,
-                ctx.User.UserName,
-                ctx.User.Colour,
-                ctx.User.Rank,
-                ctx.User.NickName,
-                ctx.User.Permissions,
-                DateTimeOffset.Now,
-                string.Join(' ', ctx.Args),
-                false, false, true
-            ));
-        }
+        await ctx.Chat.DispatchEvent(new MessageCreateEvent(
+            msgId,
+            string.Empty,
+            ctx.User.UserId,
+            ctx.User.UserName,
+            ctx.User.Colour,
+            ctx.User.Rank,
+            ctx.User.NickName,
+            ctx.User.Permissions,
+            DateTimeOffset.Now,
+            string.Join(' ', ctx.Args),
+            false, false, true
+        ));
     }
 }
diff --git a/SharpChat/ClientCommands/CreateChannelClientCommand.cs b/SharpChat/ClientCommands/CreateChannelClientCommand.cs
index cfa9858..00cf727 100644
--- a/SharpChat/ClientCommands/CreateChannelClientCommand.cs
+++ b/SharpChat/ClientCommands/CreateChannelClientCommand.cs
@@ -1,62 +1,62 @@
 using SharpChat.SockChat.S2CPackets;
 
-namespace SharpChat.ClientCommands {
-    public class CreateChannelClientCommand : ClientCommand {
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("create");
+namespace SharpChat.ClientCommands;
+
+public class CreateChannelClientCommand : ClientCommand {
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("create");
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        long msgId = ctx.Chat.RandomSnowflake.Next();
+
+        if(!ctx.User.Can(UserPermissions.CreateChannel)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
+            return;
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            long msgId = ctx.Chat.RandomSnowflake.Next();
+        string firstArg = ctx.Args.First();
 
-            if(!ctx.User.Can(UserPermissions.CreateChannel)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
-                return;
-            }
-
-            string firstArg = ctx.Args.First();
-
-            bool createChanHasHierarchy;
-            if(ctx.Args.Length < 1 || (createChanHasHierarchy = firstArg.All(char.IsDigit) && ctx.Args.Length < 2)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
-                return;
-            }
-
-            int createChanHierarchy = 0;
-            if(createChanHasHierarchy)
-                if(!int.TryParse(firstArg, out createChanHierarchy))
-                    createChanHierarchy = 0;
-
-            if(createChanHierarchy > ctx.User.Rank) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.INSUFFICIENT_HIERARCHY));
-                return;
-            }
-
-            string createChanName = string.Join('_', ctx.Args.Skip(createChanHasHierarchy ? 1 : 0));
-
-            if(!Channel.CheckName(createChanName)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NAME_INVALID));
-                return;
-            }
-
-            if(ctx.Chat.Channels.Any(c => c.NameEquals(createChanName))) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_ALREADY_EXISTS, true, createChanName));
-                return;
-            }
-
-            Channel createChan = new(
-                createChanName,
-                isTemporary: !ctx.User.Can(UserPermissions.SetChannelPermanent),
-                rank: createChanHierarchy,
-                ownerId: ctx.User.UserId
-            );
-
-            ctx.Chat.Channels.Add(createChan);
-            foreach(User ccu in ctx.Chat.Users.Where(u => u.Rank >= ctx.Channel.Rank))
-                await ctx.Chat.SendTo(ccu, new ChannelCreateS2CPacket(createChan.Name, createChan.HasPassword, createChan.IsTemporary));
-
-            await ctx.Chat.SwitchChannel(ctx.User, createChan, createChan.Password);
-            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_CREATED, false, createChan.Name));
+        bool createChanHasHierarchy;
+        if(ctx.Args.Length < 1 || (createChanHasHierarchy = firstArg.All(char.IsDigit) && ctx.Args.Length < 2)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
+            return;
         }
+
+        int createChanHierarchy = 0;
+        if(createChanHasHierarchy)
+            if(!int.TryParse(firstArg, out createChanHierarchy))
+                createChanHierarchy = 0;
+
+        if(createChanHierarchy > ctx.User.Rank) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.INSUFFICIENT_HIERARCHY));
+            return;
+        }
+
+        string createChanName = string.Join('_', ctx.Args.Skip(createChanHasHierarchy ? 1 : 0));
+
+        if(!Channel.CheckName(createChanName)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NAME_INVALID));
+            return;
+        }
+
+        if(ctx.Chat.Channels.Any(c => c.NameEquals(createChanName))) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_ALREADY_EXISTS, true, createChanName));
+            return;
+        }
+
+        Channel createChan = new(
+            createChanName,
+            isTemporary: !ctx.User.Can(UserPermissions.SetChannelPermanent),
+            rank: createChanHierarchy,
+            ownerId: ctx.User.UserId
+        );
+
+        ctx.Chat.Channels.Add(createChan);
+        foreach(User ccu in ctx.Chat.Users.Where(u => u.Rank >= ctx.Channel.Rank))
+            await ctx.Chat.SendTo(ccu, new ChannelCreateS2CPacket(createChan.Name, createChan.HasPassword, createChan.IsTemporary));
+
+        await ctx.Chat.SwitchChannel(ctx.User, createChan, createChan.Password);
+        await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_CREATED, false, createChan.Name));
     }
 }
diff --git a/SharpChat/ClientCommands/DeleteChannelClientCommand.cs b/SharpChat/ClientCommands/DeleteChannelClientCommand.cs
index a819374..bd8cf08 100644
--- a/SharpChat/ClientCommands/DeleteChannelClientCommand.cs
+++ b/SharpChat/ClientCommands/DeleteChannelClientCommand.cs
@@ -1,37 +1,37 @@
 using SharpChat.SockChat.S2CPackets;
 
-namespace SharpChat.ClientCommands {
-    public class DeleteChannelClientCommand : ClientCommand {
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("delchan") || (
-                ctx.NameEquals("delete")
-                && ctx.Args.FirstOrDefault()?.All(char.IsDigit) == false
-            );
+namespace SharpChat.ClientCommands;
+
+public class DeleteChannelClientCommand : ClientCommand {
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("delchan") || (
+            ctx.NameEquals("delete")
+            && ctx.Args.FirstOrDefault()?.All(char.IsDigit) == false
+        );
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        long msgId = ctx.Chat.RandomSnowflake.Next();
+
+        if(ctx.Args.Length < 1 || string.IsNullOrWhiteSpace(ctx.Args.FirstOrDefault())) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
+            return;
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            long msgId = ctx.Chat.RandomSnowflake.Next();
+        string delChanName = string.Join('_', ctx.Args);
+        Channel? delChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(delChanName));
 
-            if(ctx.Args.Length < 1 || string.IsNullOrWhiteSpace(ctx.Args.FirstOrDefault())) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
-                return;
-            }
-
-            string delChanName = string.Join('_', ctx.Args);
-            Channel? delChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(delChanName));
-
-            if(delChan == null) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, delChanName));
-                return;
-            }
-
-            if(!ctx.User.Can(UserPermissions.DeleteChannel) && delChan.IsOwner(ctx.User)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_DELETE_FAILED, true, delChan.Name));
-                return;
-            }
-
-            await ctx.Chat.RemoveChannel(delChan);
-            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_DELETED, false, delChan.Name));
+        if(delChan == null) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, delChanName));
+            return;
         }
+
+        if(!ctx.User.Can(UserPermissions.DeleteChannel) && delChan.IsOwner(ctx.User)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_DELETE_FAILED, true, delChan.Name));
+            return;
+        }
+
+        await ctx.Chat.RemoveChannel(delChan);
+        await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_DELETED, false, delChan.Name));
     }
 }
diff --git a/SharpChat/ClientCommands/DeleteMessageClientCommand.cs b/SharpChat/ClientCommands/DeleteMessageClientCommand.cs
index 2960b2c..ab14ac6 100644
--- a/SharpChat/ClientCommands/DeleteMessageClientCommand.cs
+++ b/SharpChat/ClientCommands/DeleteMessageClientCommand.cs
@@ -1,41 +1,40 @@
 using SharpChat.EventStorage;
 using SharpChat.SockChat.S2CPackets;
 
-namespace SharpChat.ClientCommands
-{
-    public class DeleteMessageClientCommand : ClientCommand {
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("delmsg") || (
-                ctx.NameEquals("delete")
-                && ctx.Args.FirstOrDefault()?.All(char.IsDigit) == true
-            );
+namespace SharpChat.ClientCommands;
+
+public class DeleteMessageClientCommand : ClientCommand {
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("delmsg") || (
+            ctx.NameEquals("delete")
+            && ctx.Args.FirstOrDefault()?.All(char.IsDigit) == true
+        );
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        long msgId = ctx.Chat.RandomSnowflake.Next();
+        bool deleteAnyMessage = ctx.User.Can(UserPermissions.DeleteAnyMessage);
+
+        if(!deleteAnyMessage && !ctx.User.Can(UserPermissions.DeleteOwnMessage)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
+            return;
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            long msgId = ctx.Chat.RandomSnowflake.Next();
-            bool deleteAnyMessage = ctx.User.Can(UserPermissions.DeleteAnyMessage);
+        string? firstArg = ctx.Args.FirstOrDefault();
 
-            if(!deleteAnyMessage && !ctx.User.Can(UserPermissions.DeleteOwnMessage)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
-                return;
-            }
-
-            string? firstArg = ctx.Args.FirstOrDefault();
-
-            if(string.IsNullOrWhiteSpace(firstArg) || !firstArg.All(char.IsDigit) || !long.TryParse(firstArg, out long delSeqId)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
-                return;
-            }
-
-            StoredEventInfo? delMsg = ctx.Chat.Events.GetEvent(delSeqId);
-
-            if(delMsg?.Sender is null || delMsg.Sender.Rank > ctx.User.Rank || (!deleteAnyMessage && delMsg.Sender.UserId != ctx.User.UserId)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.MESSAGE_DELETE_ERROR));
-                return;
-            }
-
-            ctx.Chat.Events.RemoveEvent(delMsg);
-            await ctx.Chat.Send(new ChatMessageDeleteS2CPacket(delMsg.Id));
+        if(string.IsNullOrWhiteSpace(firstArg) || !firstArg.All(char.IsDigit) || !long.TryParse(firstArg, out long delSeqId)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
+            return;
         }
+
+        StoredEventInfo? delMsg = ctx.Chat.Events.GetEvent(delSeqId);
+
+        if(delMsg?.Sender is null || delMsg.Sender.Rank > ctx.User.Rank || (!deleteAnyMessage && delMsg.Sender.UserId != ctx.User.UserId)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.MESSAGE_DELETE_ERROR));
+            return;
+        }
+
+        ctx.Chat.Events.RemoveEvent(delMsg);
+        await ctx.Chat.Send(new ChatMessageDeleteS2CPacket(delMsg.Id));
     }
 }
diff --git a/SharpChat/ClientCommands/JoinChannelClientCommand.cs b/SharpChat/ClientCommands/JoinChannelClientCommand.cs
index 20eb5b1..d2b9364 100644
--- a/SharpChat/ClientCommands/JoinChannelClientCommand.cs
+++ b/SharpChat/ClientCommands/JoinChannelClientCommand.cs
@@ -1,23 +1,23 @@
 using SharpChat.SockChat.S2CPackets;
 
-namespace SharpChat.ClientCommands {
-    public class JoinChannelClientCommand : ClientCommand {
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("join");
+namespace SharpChat.ClientCommands;
+
+public class JoinChannelClientCommand : ClientCommand {
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("join");
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        long msgId = ctx.Chat.RandomSnowflake.Next();
+        string joinChanStr = ctx.Args.FirstOrDefault() ?? "Channel";
+        Channel? joinChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(joinChanStr));
+
+        if(joinChan is null) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, joinChanStr));
+            await ctx.Chat.ForceChannel(ctx.User);
+            return;
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            long msgId = ctx.Chat.RandomSnowflake.Next();
-            string joinChanStr = ctx.Args.FirstOrDefault() ?? "Channel";
-            Channel? joinChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(joinChanStr));
-
-            if(joinChan is null) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, joinChanStr));
-                await ctx.Chat.ForceChannel(ctx.User);
-                return;
-            }
-
-            await ctx.Chat.SwitchChannel(ctx.User, joinChan, string.Join(' ', ctx.Args.Skip(1)));
-        }
+        await ctx.Chat.SwitchChannel(ctx.User, joinChan, string.Join(' ', ctx.Args.Skip(1)));
     }
 }
diff --git a/SharpChat/ClientCommands/KickBanClientCommand.cs b/SharpChat/ClientCommands/KickBanClientCommand.cs
index d1b710e..45e0257 100644
--- a/SharpChat/ClientCommands/KickBanClientCommand.cs
+++ b/SharpChat/ClientCommands/KickBanClientCommand.cs
@@ -2,72 +2,72 @@ using SharpChat.Bans;
 using SharpChat.SockChat.S2CPackets;
 using System.Net;
 
-namespace SharpChat.ClientCommands {
-    public class KickBanClientCommand(BansClient bansClient) : ClientCommand {
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("kick")
-                || ctx.NameEquals("ban");
+namespace SharpChat.ClientCommands;
+
+public class KickBanClientCommand(BansClient bansClient) : ClientCommand {
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("kick")
+            || ctx.NameEquals("ban");
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        bool isBanning = ctx.NameEquals("ban");
+        long msgId = ctx.Chat.RandomSnowflake.Next();
+
+        if(!ctx.User.Can(isBanning ? UserPermissions.BanUser : UserPermissions.KickUser)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
+            return;
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            bool isBanning = ctx.NameEquals("ban");
-            long msgId = ctx.Chat.RandomSnowflake.Next();
+        string? banUserTarget = ctx.Args.ElementAtOrDefault(0);
+        string? banDurationStr = ctx.Args.ElementAtOrDefault(1);
+        int banReasonIndex = 1;
+        User? banUser = null;
 
-            if(!ctx.User.Can(isBanning ? UserPermissions.BanUser : UserPermissions.KickUser)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
+        if(banUserTarget == null || (banUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(banUserTarget))) == null) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, banUserTarget ?? "User"));
+            return;
+        }
+
+        if(banUser.Rank >= ctx.User.Rank && banUser != ctx.User) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.KICK_NOT_ALLOWED, true, banUser.LegacyName));
+            return;
+        }
+
+        TimeSpan duration = isBanning ? TimeSpan.MaxValue : TimeSpan.Zero;
+        if(!string.IsNullOrWhiteSpace(banDurationStr) && double.TryParse(banDurationStr, out double durationSeconds)) {
+            if(durationSeconds < 0) {
+                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
                 return;
             }
 
-            string? banUserTarget = ctx.Args.ElementAtOrDefault(0);
-            string? banDurationStr = ctx.Args.ElementAtOrDefault(1);
-            int banReasonIndex = 1;
-            User? banUser = null;
-
-            if(banUserTarget == null || (banUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(banUserTarget))) == null) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, banUserTarget ?? "User"));
-                return;
-            }
-
-            if(banUser.Rank >= ctx.User.Rank && banUser != ctx.User) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.KICK_NOT_ALLOWED, true, banUser.LegacyName));
-                return;
-            }
-
-            TimeSpan duration = isBanning ? TimeSpan.MaxValue : TimeSpan.Zero;
-            if(!string.IsNullOrWhiteSpace(banDurationStr) && double.TryParse(banDurationStr, out double durationSeconds)) {
-                if(durationSeconds < 0) {
-                    await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
-                    return;
-                }
-
-                duration = TimeSpan.FromSeconds(durationSeconds);
-                ++banReasonIndex;
-            }
-
-            if(duration <= TimeSpan.Zero) {
-                await ctx.Chat.BanUser(banUser, duration);
-                return;
-            }
-
-            string banReason = string.Join(' ', ctx.Args.Skip(banReasonIndex));
-
-            BanInfo? banInfo = await bansClient.BanGetAsync(banUser.UserId);
-            if(banInfo is not null) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.KICK_NOT_ALLOWED, true, banUser.LegacyName));
-                return;
-            }
-
-            await bansClient.BanCreateAsync(
-                BanKind.User,
-                duration,
-                ctx.Chat.GetRemoteAddresses(banUser).FirstOrDefault() ?? IPAddress.None,
-                banUser.UserId,
-                banReason,
-                ctx.Connection.RemoteAddress,
-                ctx.User.UserId
-            );
+            duration = TimeSpan.FromSeconds(durationSeconds);
+            ++banReasonIndex;
+        }
 
+        if(duration <= TimeSpan.Zero) {
             await ctx.Chat.BanUser(banUser, duration);
+            return;
         }
+
+        string banReason = string.Join(' ', ctx.Args.Skip(banReasonIndex));
+
+        BanInfo? banInfo = await bansClient.BanGetAsync(banUser.UserId);
+        if(banInfo is not null) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.KICK_NOT_ALLOWED, true, banUser.LegacyName));
+            return;
+        }
+
+        await bansClient.BanCreateAsync(
+            BanKind.User,
+            duration,
+            ctx.Chat.GetRemoteAddresses(banUser).FirstOrDefault() ?? IPAddress.None,
+            banUser.UserId,
+            banReason,
+            ctx.Connection.RemoteAddress,
+            ctx.User.UserId
+        );
+
+        await ctx.Chat.BanUser(banUser, duration);
     }
 }
diff --git a/SharpChat/ClientCommands/NickClientCommand.cs b/SharpChat/ClientCommands/NickClientCommand.cs
index 5c7e207..0d20d73 100644
--- a/SharpChat/ClientCommands/NickClientCommand.cs
+++ b/SharpChat/ClientCommands/NickClientCommand.cs
@@ -2,62 +2,62 @@ using SharpChat.SockChat.S2CPackets;
 using System.Globalization;
 using System.Text;
 
-namespace SharpChat.ClientCommands {
-    public class NickClientCommand : ClientCommand {
-        private const int MAX_GRAPHEMES = 16;
-        private const int MAX_BYTES = MAX_GRAPHEMES * 10;
+namespace SharpChat.ClientCommands;
 
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("nick");
+public class NickClientCommand : ClientCommand {
+    private const int MAX_GRAPHEMES = 16;
+    private const int MAX_BYTES = MAX_GRAPHEMES * 10;
+
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("nick");
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        long msgId = ctx.Chat.RandomSnowflake.Next();
+        bool setOthersNick = ctx.User.Can(UserPermissions.SetOthersNickname);
+
+        if(!setOthersNick && !ctx.User.Can(UserPermissions.SetOwnNickname)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
+            return;
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            long msgId = ctx.Chat.RandomSnowflake.Next();
-            bool setOthersNick = ctx.User.Can(UserPermissions.SetOthersNickname);
+        User? targetUser = null;
+        int offset = 0;
 
-            if(!setOthersNick && !ctx.User.Can(UserPermissions.SetOwnNickname)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
-                return;
-            }
-
-            User? targetUser = null;
-            int offset = 0;
-
-            if(setOthersNick && long.TryParse(ctx.Args.FirstOrDefault(), out long targetUserId) && targetUserId > 0) {
-                targetUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == targetUserId.ToString());
-                ++offset;
-            }
-
-            targetUser ??= ctx.User;
-
-            if(ctx.Args.Length < offset) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
-                return;
-            }
-
-            string nickStr = string.Join('_', ctx.Args.Skip(offset))
-                .Replace("\n", string.Empty).Replace("\r", string.Empty)
-                .Replace("\f", string.Empty).Replace("\t", string.Empty)
-                .Replace(' ', '_').Trim();
-
-            if(nickStr == targetUser.UserName)
-                nickStr = string.Empty;
-            else if(string.IsNullOrEmpty(nickStr))
-                nickStr = string.Empty;
-            else {
-                StringInfo nsi = new(nickStr);
-                if(Encoding.UTF8.GetByteCount(nickStr) > MAX_BYTES
-                    || nsi.LengthInTextElements > MAX_GRAPHEMES)
-                    nickStr = nsi.SubstringByTextElements(0, Math.Min(nsi.LengthInTextElements, MAX_GRAPHEMES)).Trim();
-            }
-
-            if(!string.IsNullOrWhiteSpace(nickStr) && ctx.Chat.Users.Any(u => u.NameEquals(nickStr))) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.NAME_IN_USE, true, nickStr));
-                return;
-            }
-
-            string? previousName = targetUser.UserId == ctx.User.UserId ? (targetUser.NickName ?? targetUser.UserName) : null;
-            await ctx.Chat.UpdateUser(targetUser, nickName: nickStr, silent: previousName == null);
+        if(setOthersNick && long.TryParse(ctx.Args.FirstOrDefault(), out long targetUserId) && targetUserId > 0) {
+            targetUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == targetUserId.ToString());
+            ++offset;
         }
+
+        targetUser ??= ctx.User;
+
+        if(ctx.Args.Length < offset) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
+            return;
+        }
+
+        string nickStr = string.Join('_', ctx.Args.Skip(offset))
+            .Replace("\n", string.Empty).Replace("\r", string.Empty)
+            .Replace("\f", string.Empty).Replace("\t", string.Empty)
+            .Replace(' ', '_').Trim();
+
+        if(nickStr == targetUser.UserName)
+            nickStr = string.Empty;
+        else if(string.IsNullOrEmpty(nickStr))
+            nickStr = string.Empty;
+        else {
+            StringInfo nsi = new(nickStr);
+            if(Encoding.UTF8.GetByteCount(nickStr) > MAX_BYTES
+                || nsi.LengthInTextElements > MAX_GRAPHEMES)
+                nickStr = nsi.SubstringByTextElements(0, Math.Min(nsi.LengthInTextElements, MAX_GRAPHEMES)).Trim();
+        }
+
+        if(!string.IsNullOrWhiteSpace(nickStr) && ctx.Chat.Users.Any(u => u.NameEquals(nickStr))) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.NAME_IN_USE, true, nickStr));
+            return;
+        }
+
+        string? previousName = targetUser.UserId == ctx.User.UserId ? (targetUser.NickName ?? targetUser.UserName) : null;
+        await ctx.Chat.UpdateUser(targetUser, nickName: nickStr, silent: previousName == null);
     }
 }
diff --git a/SharpChat/ClientCommands/PardonAddressClientCommand.cs b/SharpChat/ClientCommands/PardonAddressClientCommand.cs
index 54dfbd8..b410b21 100644
--- a/SharpChat/ClientCommands/PardonAddressClientCommand.cs
+++ b/SharpChat/ClientCommands/PardonAddressClientCommand.cs
@@ -2,39 +2,39 @@ using SharpChat.Bans;
 using SharpChat.SockChat.S2CPackets;
 using System.Net;
 
-namespace SharpChat.ClientCommands {
-    public class PardonAddressClientCommand(BansClient bansClient) : ClientCommand {
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("pardonip")
-                || ctx.NameEquals("unbanip");
+namespace SharpChat.ClientCommands;
+
+public class PardonAddressClientCommand(BansClient bansClient) : ClientCommand {
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("pardonip")
+            || ctx.NameEquals("unbanip");
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        long msgId = ctx.Chat.RandomSnowflake.Next();
+
+        if(!ctx.User.Can(UserPermissions.BanUser | UserPermissions.KickUser)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
+            return;
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            long msgId = ctx.Chat.RandomSnowflake.Next();
-
-            if(!ctx.User.Can(UserPermissions.BanUser | UserPermissions.KickUser)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
-                return;
-            }
-
-            string? unbanAddrTarget = ctx.Args.FirstOrDefault();
-            if(string.IsNullOrWhiteSpace(unbanAddrTarget) || !IPAddress.TryParse(unbanAddrTarget, out IPAddress? unbanAddr)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
-                return;
-            }
-
-            unbanAddrTarget = unbanAddr.ToString();
-
-            BanInfo? banInfo = await bansClient.BanGetAsync(remoteAddr: unbanAddr);
-            if(banInfo?.Kind != BanKind.IPAddress) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_BANNED, true, unbanAddrTarget));
-                return;
-            }
-
-            if(await bansClient.BanRevokeAsync(banInfo))
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_UNBANNED, false, unbanAddrTarget));
-            else
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_BANNED, true, unbanAddrTarget));
+        string? unbanAddrTarget = ctx.Args.FirstOrDefault();
+        if(string.IsNullOrWhiteSpace(unbanAddrTarget) || !IPAddress.TryParse(unbanAddrTarget, out IPAddress? unbanAddr)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
+            return;
         }
+
+        unbanAddrTarget = unbanAddr.ToString();
+
+        BanInfo? banInfo = await bansClient.BanGetAsync(remoteAddr: unbanAddr);
+        if(banInfo?.Kind != BanKind.IPAddress) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_BANNED, true, unbanAddrTarget));
+            return;
+        }
+
+        if(await bansClient.BanRevokeAsync(banInfo))
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_UNBANNED, false, unbanAddrTarget));
+        else
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_BANNED, true, unbanAddrTarget));
     }
 }
diff --git a/SharpChat/ClientCommands/PardonUserClientCommand.cs b/SharpChat/ClientCommands/PardonUserClientCommand.cs
index 5bd6bb7..d489f44 100644
--- a/SharpChat/ClientCommands/PardonUserClientCommand.cs
+++ b/SharpChat/ClientCommands/PardonUserClientCommand.cs
@@ -1,46 +1,46 @@
 using SharpChat.Bans;
 using SharpChat.SockChat.S2CPackets;
 
-namespace SharpChat.ClientCommands {
-    public class PardonUserClientCommand(BansClient bansClient) : ClientCommand {
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("pardon")
-                || ctx.NameEquals("unban");
+namespace SharpChat.ClientCommands;
+
+public class PardonUserClientCommand(BansClient bansClient) : ClientCommand {
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("pardon")
+            || ctx.NameEquals("unban");
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        long msgId = ctx.Chat.RandomSnowflake.Next();
+
+        if(!ctx.User.Can(UserPermissions.BanUser | UserPermissions.KickUser)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
+            return;
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            long msgId = ctx.Chat.RandomSnowflake.Next();
-
-            if(!ctx.User.Can(UserPermissions.BanUser | UserPermissions.KickUser)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
-                return;
-            }
-
-            string? unbanUserTarget = ctx.Args.FirstOrDefault();
-            if(string.IsNullOrWhiteSpace(unbanUserTarget)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
-                return;
-            }
-
-            string unbanUserDisplay = unbanUserTarget;
-            User? unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(unbanUserTarget));
-            if(unbanUser == null && long.TryParse(unbanUserTarget, out long unbanUserId))
-                unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == unbanUserId.ToString());
-            if(unbanUser != null) {
-                unbanUserTarget = unbanUser.UserId;
-                unbanUserDisplay = unbanUser.UserName;
-            }
-
-            BanInfo? banInfo = await bansClient.BanGetAsync(unbanUserTarget);
-            if(banInfo?.Kind != BanKind.User) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_BANNED, true, unbanUserDisplay));
-                return;
-            }
-
-            if(await bansClient.BanRevokeAsync(banInfo))
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_UNBANNED, false, unbanUserDisplay));
-            else
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_BANNED, true, unbanUserDisplay));
+        string? unbanUserTarget = ctx.Args.FirstOrDefault();
+        if(string.IsNullOrWhiteSpace(unbanUserTarget)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
+            return;
         }
+
+        string unbanUserDisplay = unbanUserTarget;
+        User? unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(unbanUserTarget));
+        if(unbanUser == null && long.TryParse(unbanUserTarget, out long unbanUserId))
+            unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == unbanUserId.ToString());
+        if(unbanUser != null) {
+            unbanUserTarget = unbanUser.UserId;
+            unbanUserDisplay = unbanUser.UserName;
+        }
+
+        BanInfo? banInfo = await bansClient.BanGetAsync(unbanUserTarget);
+        if(banInfo?.Kind != BanKind.User) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_BANNED, true, unbanUserDisplay));
+            return;
+        }
+
+        if(await bansClient.BanRevokeAsync(banInfo))
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_UNBANNED, false, unbanUserDisplay));
+        else
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_BANNED, true, unbanUserDisplay));
     }
 }
diff --git a/SharpChat/ClientCommands/PasswordChannelClientCommand.cs b/SharpChat/ClientCommands/PasswordChannelClientCommand.cs
index a8bc0e6..3fd48d3 100644
--- a/SharpChat/ClientCommands/PasswordChannelClientCommand.cs
+++ b/SharpChat/ClientCommands/PasswordChannelClientCommand.cs
@@ -1,27 +1,27 @@
 using SharpChat.SockChat.S2CPackets;
 
-namespace SharpChat.ClientCommands {
-    public class PasswordChannelClientCommand : ClientCommand {
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("pwd")
-                || ctx.NameEquals("password");
+namespace SharpChat.ClientCommands;
+
+public class PasswordChannelClientCommand : ClientCommand {
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("pwd")
+            || ctx.NameEquals("password");
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        long msgId = ctx.Chat.RandomSnowflake.Next();
+
+        if(!ctx.User.Can(UserPermissions.SetChannelPassword) || ctx.Channel.IsOwner(ctx.User)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
+            return;
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            long msgId = ctx.Chat.RandomSnowflake.Next();
+        string chanPass = string.Join(' ', ctx.Args).Trim();
 
-            if(!ctx.User.Can(UserPermissions.SetChannelPassword) || ctx.Channel.IsOwner(ctx.User)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
-                return;
-            }
+        if(string.IsNullOrWhiteSpace(chanPass))
+            chanPass = string.Empty;
 
-            string chanPass = string.Join(' ', ctx.Args).Trim();
-
-            if(string.IsNullOrWhiteSpace(chanPass))
-                chanPass = string.Empty;
-
-            await ctx.Chat.UpdateChannel(ctx.Channel, password: chanPass);
-            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_PASSWORD_CHANGED, false));
-        }
+        await ctx.Chat.UpdateChannel(ctx.Channel, password: chanPass);
+        await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_PASSWORD_CHANGED, false));
     }
 }
diff --git a/SharpChat/ClientCommands/RankChannelClientCommand.cs b/SharpChat/ClientCommands/RankChannelClientCommand.cs
index dc7da06..34f8683 100644
--- a/SharpChat/ClientCommands/RankChannelClientCommand.cs
+++ b/SharpChat/ClientCommands/RankChannelClientCommand.cs
@@ -1,28 +1,28 @@
 using SharpChat.SockChat.S2CPackets;
 
-namespace SharpChat.ClientCommands {
-    public class RankChannelClientCommand : ClientCommand {
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("rank")
-                || ctx.NameEquals("privilege")
-                || ctx.NameEquals("priv");
+namespace SharpChat.ClientCommands;
+
+public class RankChannelClientCommand : ClientCommand {
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("rank")
+            || ctx.NameEquals("privilege")
+            || ctx.NameEquals("priv");
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        long msgId = ctx.Chat.RandomSnowflake.Next();
+
+        if(!ctx.User.Can(UserPermissions.SetChannelHierarchy) || ctx.Channel.IsOwner(ctx.User)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
+            return;
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            long msgId = ctx.Chat.RandomSnowflake.Next();
-
-            if(!ctx.User.Can(UserPermissions.SetChannelHierarchy) || ctx.Channel.IsOwner(ctx.User)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
-                return;
-            }
-
-            if(ctx.Args.Length < 1 || !int.TryParse(ctx.Args.First(), out int chanHierarchy) || chanHierarchy > ctx.User.Rank) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.INSUFFICIENT_HIERARCHY));
-                return;
-            }
-
-            await ctx.Chat.UpdateChannel(ctx.Channel, hierarchy: chanHierarchy);
-            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_HIERARCHY_CHANGED, false));
+        if(ctx.Args.Length < 1 || !int.TryParse(ctx.Args.First(), out int chanHierarchy) || chanHierarchy > ctx.User.Rank) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.INSUFFICIENT_HIERARCHY));
+            return;
         }
+
+        await ctx.Chat.UpdateChannel(ctx.Channel, hierarchy: chanHierarchy);
+        await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_HIERARCHY_CHANGED, false));
     }
 }
diff --git a/SharpChat/ClientCommands/RemoteAddressClientCommand.cs b/SharpChat/ClientCommands/RemoteAddressClientCommand.cs
index 509ff92..adc7765 100644
--- a/SharpChat/ClientCommands/RemoteAddressClientCommand.cs
+++ b/SharpChat/ClientCommands/RemoteAddressClientCommand.cs
@@ -1,31 +1,31 @@
 using SharpChat.SockChat.S2CPackets;
 using System.Net;
 
-namespace SharpChat.ClientCommands {
-    public class RemoteAddressClientCommand : ClientCommand {
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("ip")
-                || ctx.NameEquals("whois");
+namespace SharpChat.ClientCommands;
+
+public class RemoteAddressClientCommand : ClientCommand {
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("ip")
+            || ctx.NameEquals("whois");
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        long msgId = ctx.Chat.RandomSnowflake.Next();
+
+        if(!ctx.User.Can(UserPermissions.SeeIPAddress)) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, "/ip"));
+            return;
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            long msgId = ctx.Chat.RandomSnowflake.Next();
+        string? ipUserStr = ctx.Args.FirstOrDefault();
+        User? ipUser = null;
 
-            if(!ctx.User.Can(UserPermissions.SeeIPAddress)) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, "/ip"));
-                return;
-            }
-
-            string? ipUserStr = ctx.Args.FirstOrDefault();
-            User? ipUser = null;
-
-            if(string.IsNullOrWhiteSpace(ipUserStr) || (ipUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(ipUserStr))) == null) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, ipUserStr ?? "User"));
-                return;
-            }
-
-            foreach(IPAddress ip in ctx.Chat.GetRemoteAddresses(ipUser))
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.IP_ADDRESS, false, ipUser.UserName, ip));
+        if(string.IsNullOrWhiteSpace(ipUserStr) || (ipUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(ipUserStr))) == null) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, ipUserStr ?? "User"));
+            return;
         }
+
+        foreach(IPAddress ip in ctx.Chat.GetRemoteAddresses(ipUser))
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.IP_ADDRESS, false, ipUser.UserName, ip));
     }
 }
diff --git a/SharpChat/ClientCommands/ShutdownRestartClientCommand.cs b/SharpChat/ClientCommands/ShutdownRestartClientCommand.cs
index b0ac76a..63468f2 100644
--- a/SharpChat/ClientCommands/ShutdownRestartClientCommand.cs
+++ b/SharpChat/ClientCommands/ShutdownRestartClientCommand.cs
@@ -1,31 +1,31 @@
 using SharpChat.SockChat.S2CPackets;
 
-namespace SharpChat.ClientCommands {
-    public class ShutdownRestartClientCommand(ManualResetEvent waitHandle, Func<bool> shutdownCheck) : ClientCommand {
-        private readonly ManualResetEvent WaitHandle = waitHandle ?? throw new ArgumentNullException(nameof(waitHandle));
-        private readonly Func<bool> ShutdownCheck = shutdownCheck ?? throw new ArgumentNullException(nameof(shutdownCheck));
+namespace SharpChat.ClientCommands;
 
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("shutdown")
-                || ctx.NameEquals("restart");
+public class ShutdownRestartClientCommand(ManualResetEvent waitHandle, Func<bool> shutdownCheck) : ClientCommand {
+    private readonly ManualResetEvent WaitHandle = waitHandle ?? throw new ArgumentNullException(nameof(waitHandle));
+    private readonly Func<bool> ShutdownCheck = shutdownCheck ?? throw new ArgumentNullException(nameof(shutdownCheck));
+
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("shutdown")
+            || ctx.NameEquals("restart");
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        if(!ctx.User.UserId.Equals("1")) {
+            long msgId = ctx.Chat.RandomSnowflake.Next();
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
+            return;
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            if(!ctx.User.UserId.Equals("1")) {
-                long msgId = ctx.Chat.RandomSnowflake.Next();
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
-                return;
-            }
+        if(!ShutdownCheck())
+            return;
 
-            if(!ShutdownCheck())
-                return;
+        if(ctx.NameEquals("restart"))
+            foreach(Connection conn in ctx.Chat.Connections)
+                conn.PrepareForRestart();
 
-            if(ctx.NameEquals("restart"))
-                foreach(Connection conn in ctx.Chat.Connections)
-                    conn.PrepareForRestart();
-
-            await ctx.Chat.Update();
-            WaitHandle?.Set();
-        }
+        await ctx.Chat.Update();
+        WaitHandle?.Set();
     }
 }
diff --git a/SharpChat/ClientCommands/WhisperClientCommand.cs b/SharpChat/ClientCommands/WhisperClientCommand.cs
index 78038b9..bff5937 100644
--- a/SharpChat/ClientCommands/WhisperClientCommand.cs
+++ b/SharpChat/ClientCommands/WhisperClientCommand.cs
@@ -1,45 +1,45 @@
 using SharpChat.Events;
 using SharpChat.SockChat.S2CPackets;
 
-namespace SharpChat.ClientCommands {
-    public class WhisperClientCommand : ClientCommand {
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("whisper")
-                || ctx.NameEquals("msg");
+namespace SharpChat.ClientCommands;
+
+public class WhisperClientCommand : ClientCommand {
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("whisper")
+            || ctx.NameEquals("msg");
+    }
+
+    public async Task Dispatch(ClientCommandContext ctx) {
+        long msgId = ctx.Chat.RandomSnowflake.Next();
+
+        if(ctx.Args.Length < 2) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
+            return;
         }
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            long msgId = ctx.Chat.RandomSnowflake.Next();
+        string whisperUserStr = ctx.Args.FirstOrDefault() ?? string.Empty;
+        User? whisperUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(whisperUserStr));
 
-            if(ctx.Args.Length < 2) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
-                return;
-            }
-
-            string whisperUserStr = ctx.Args.FirstOrDefault() ?? string.Empty;
-            User? whisperUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(whisperUserStr));
-
-            if(whisperUser == null) {
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, whisperUserStr));
-                return;
-            }
-
-            if(whisperUser == ctx.User)
-                return;
-
-            await ctx.Chat.DispatchEvent(new MessageCreateEvent(
-                msgId,
-                User.GetDMChannelName(ctx.User, whisperUser),
-                ctx.User.UserId,
-                ctx.User.UserName,
-                ctx.User.Colour,
-                ctx.User.Rank,
-                ctx.User.NickName,
-                ctx.User.Permissions,
-                DateTimeOffset.Now,
-                string.Join(' ', ctx.Args.Skip(1)),
-                true, false, false
-            ));
+        if(whisperUser == null) {
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, whisperUserStr));
+            return;
         }
+
+        if(whisperUser == ctx.User)
+            return;
+
+        await ctx.Chat.DispatchEvent(new MessageCreateEvent(
+            msgId,
+            User.GetDMChannelName(ctx.User, whisperUser),
+            ctx.User.UserId,
+            ctx.User.UserName,
+            ctx.User.Colour,
+            ctx.User.Rank,
+            ctx.User.NickName,
+            ctx.User.Permissions,
+            DateTimeOffset.Now,
+            string.Join(' ', ctx.Args.Skip(1)),
+            true, false, false
+        ));
     }
 }
diff --git a/SharpChat/ClientCommands/WhoClientCommand.cs b/SharpChat/ClientCommands/WhoClientCommand.cs
index 398cfc6..74b07b5 100644
--- a/SharpChat/ClientCommands/WhoClientCommand.cs
+++ b/SharpChat/ClientCommands/WhoClientCommand.cs
@@ -1,62 +1,62 @@
 using SharpChat.SockChat.S2CPackets;
 using System.Text;
 
-namespace SharpChat.ClientCommands {
-    public class WhoClientCommand : ClientCommand {
-        public bool IsMatch(ClientCommandContext ctx) {
-            return ctx.NameEquals("who");
-        }
+namespace SharpChat.ClientCommands;
 
-        public async Task Dispatch(ClientCommandContext ctx) {
-            long msgId = ctx.Chat.RandomSnowflake.Next();
-            StringBuilder whoChanSB = new();
-            string? whoChanStr = ctx.Args.FirstOrDefault();
+public class WhoClientCommand : ClientCommand {
+    public bool IsMatch(ClientCommandContext ctx) {
+        return ctx.NameEquals("who");
+    }
 
-            if(string.IsNullOrEmpty(whoChanStr)) {
-                foreach(User whoUser in ctx.Chat.Users) {
-                    whoChanSB.Append(@"<a href=""javascript:void(0);"" onclick=""UI.InsertChatText(this.innerHTML);""");
+    public async Task Dispatch(ClientCommandContext ctx) {
+        long msgId = ctx.Chat.RandomSnowflake.Next();
+        StringBuilder whoChanSB = new();
+        string? whoChanStr = ctx.Args.FirstOrDefault();
 
-                    if(whoUser == ctx.User)
-                        whoChanSB.Append(@" style=""font-weight: bold;""");
+        if(string.IsNullOrEmpty(whoChanStr)) {
+            foreach(User whoUser in ctx.Chat.Users) {
+                whoChanSB.Append(@"<a href=""javascript:void(0);"" onclick=""UI.InsertChatText(this.innerHTML);""");
 
-                    whoChanSB.Append('>');
-                    whoChanSB.Append(whoUser.LegacyName);
-                    whoChanSB.Append("</a>, ");
-                }
+                if(whoUser == ctx.User)
+                    whoChanSB.Append(@" style=""font-weight: bold;""");
 
-                if(whoChanSB.Length > 2)
-                    whoChanSB.Length -= 2;
-
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USERS_LISTING_SERVER, false, whoChanSB));
-            } else {
-                Channel? whoChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(whoChanStr));
-
-                if(whoChan is null) {
-                    await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, whoChanStr));
-                    return;
-                }
-
-                if(whoChan.Rank > ctx.User.Rank || (whoChan.HasPassword && !ctx.User.Can(UserPermissions.JoinAnyChannel))) {
-                    await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USERS_LISTING_ERROR, true, whoChanStr));
-                    return;
-                }
-
-                foreach(User whoUser in ctx.Chat.GetChannelUsers(whoChan)) {
-                    whoChanSB.Append(@"<a href=""javascript:void(0);"" onclick=""UI.InsertChatText(this.innerHTML);""");
-
-                    if(whoUser == ctx.User)
-                        whoChanSB.Append(@" style=""font-weight: bold;""");
-
-                    whoChanSB.Append('>');
-                    whoChanSB.Append(whoUser.LegacyName);
-                    whoChanSB.Append("</a>, ");
-                }
-
-                if(whoChanSB.Length > 2)
-                    whoChanSB.Length -= 2;
-
-                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USERS_LISTING_CHANNEL, false, whoChan.Name, whoChanSB));
+                whoChanSB.Append('>');
+                whoChanSB.Append(whoUser.LegacyName);
+                whoChanSB.Append("</a>, ");
             }
+
+            if(whoChanSB.Length > 2)
+                whoChanSB.Length -= 2;
+
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USERS_LISTING_SERVER, false, whoChanSB));
+        } else {
+            Channel? whoChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(whoChanStr));
+
+            if(whoChan is null) {
+                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, whoChanStr));
+                return;
+            }
+
+            if(whoChan.Rank > ctx.User.Rank || (whoChan.HasPassword && !ctx.User.Can(UserPermissions.JoinAnyChannel))) {
+                await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USERS_LISTING_ERROR, true, whoChanStr));
+                return;
+            }
+
+            foreach(User whoUser in ctx.Chat.GetChannelUsers(whoChan)) {
+                whoChanSB.Append(@"<a href=""javascript:void(0);"" onclick=""UI.InsertChatText(this.innerHTML);""");
+
+                if(whoUser == ctx.User)
+                    whoChanSB.Append(@" style=""font-weight: bold;""");
+
+                whoChanSB.Append('>');
+                whoChanSB.Append(whoUser.LegacyName);
+                whoChanSB.Append("</a>, ");
+            }
+
+            if(whoChanSB.Length > 2)
+                whoChanSB.Length -= 2;
+
+            await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USERS_LISTING_CHANNEL, false, whoChan.Name, whoChanSB));
         }
     }
 }
diff --git a/SharpChat/Connection.cs b/SharpChat/Connection.cs
index 3d127e3..ccd3d27 100644
--- a/SharpChat/Connection.cs
+++ b/SharpChat/Connection.cs
@@ -2,89 +2,89 @@ using Fleck;
 using SharpChat.SockChat;
 using System.Net;
 
-namespace SharpChat {
-    public class Connection : IDisposable {
-        public const int ID_LENGTH = 20;
+namespace SharpChat;
+
+public class Connection : IDisposable {
+    public const int ID_LENGTH = 20;
 
 #if DEBUG
-        public static TimeSpan SessionTimeOut { get; } = TimeSpan.FromMinutes(1);
+    public static TimeSpan SessionTimeOut { get; } = TimeSpan.FromMinutes(1);
 #else
-        public static TimeSpan SessionTimeOut { get; } = TimeSpan.FromMinutes(5);
+    public static TimeSpan SessionTimeOut { get; } = TimeSpan.FromMinutes(5);
 #endif
 
-        public IWebSocketConnection Socket { get; }
+    public IWebSocketConnection Socket { get; }
 
-        public string Id { get; }
-        public bool IsDisposed { get; private set; }
-        public DateTimeOffset LastPing { get; set; } = DateTimeOffset.Now;
-        public User? User { get; set; }
+    public string Id { get; }
+    public bool IsDisposed { get; private set; }
+    public DateTimeOffset LastPing { get; set; } = DateTimeOffset.Now;
+    public User? User { get; set; }
 
-        private int CloseCode { get; set; } = 1000;
+    private int CloseCode { get; set; } = 1000;
 
-        public IPAddress RemoteAddress { get; }
-        public ushort RemotePort { get; }
+    public IPAddress RemoteAddress { get; }
+    public ushort RemotePort { get; }
 
-        public bool IsAlive => !IsDisposed && !HasTimedOut;
+    public bool IsAlive => !IsDisposed && !HasTimedOut;
 
-        public Connection(IWebSocketConnection sock) {
-            Socket = sock;
-            Id = RNG.SecureRandomString(ID_LENGTH);
+    public Connection(IWebSocketConnection sock) {
+        Socket = sock;
+        Id = RNG.SecureRandomString(ID_LENGTH);
 
-            if(!IPAddress.TryParse(sock.ConnectionInfo.ClientIpAddress, out IPAddress? addr))
-                throw new Exception("Unable to parse remote address?????");
+        if(!IPAddress.TryParse(sock.ConnectionInfo.ClientIpAddress, out IPAddress? addr))
+            throw new Exception("Unable to parse remote address?????");
 
-            if(IPAddress.IsLoopback(addr)
-                && sock.ConnectionInfo.Headers.TryGetValue("X-Real-IP", out string? addrStr)
-                && IPAddress.TryParse(addrStr, out IPAddress? realAddr))
-                addr = realAddr;
+        if(IPAddress.IsLoopback(addr)
+            && sock.ConnectionInfo.Headers.TryGetValue("X-Real-IP", out string? addrStr)
+            && IPAddress.TryParse(addrStr, out IPAddress? realAddr))
+            addr = realAddr;
 
-            RemoteAddress = addr;
-            RemotePort = (ushort)sock.ConnectionInfo.ClientPort;
-        }
+        RemoteAddress = addr;
+        RemotePort = (ushort)sock.ConnectionInfo.ClientPort;
+    }
 
-        public async Task Send(S2CPacket packet) {
-            if(!Socket.IsAvailable)
-                return;
+    public async Task Send(S2CPacket packet) {
+        if(!Socket.IsAvailable)
+            return;
 
-            string data = packet.Pack();
-            if(!string.IsNullOrWhiteSpace(data))
-                await Socket.Send(data);
-        }
+        string data = packet.Pack();
+        if(!string.IsNullOrWhiteSpace(data))
+            await Socket.Send(data);
+    }
 
-        public void BumpPing() {
-            LastPing = DateTimeOffset.Now;
-        }
+    public void BumpPing() {
+        LastPing = DateTimeOffset.Now;
+    }
 
-        public bool HasTimedOut
-            => DateTimeOffset.Now - LastPing > SessionTimeOut;
+    public bool HasTimedOut
+        => DateTimeOffset.Now - LastPing > SessionTimeOut;
 
-        public void PrepareForRestart() {
-            CloseCode = 1012;
-        }
+    public void PrepareForRestart() {
+        CloseCode = 1012;
+    }
 
-        ~Connection() {
-            DoDispose();
-        }
+    ~Connection() {
+        DoDispose();
+    }
 
-        public void Dispose() {
-            DoDispose();
-            GC.SuppressFinalize(this);
-        }
+    public void Dispose() {
+        DoDispose();
+        GC.SuppressFinalize(this);
+    }
 
-        private void DoDispose() {
-            if(IsDisposed)
-                return;
+    private void DoDispose() {
+        if(IsDisposed)
+            return;
 
-            IsDisposed = true;
-            Socket.Close(CloseCode);
-        }
+        IsDisposed = true;
+        Socket.Close(CloseCode);
+    }
 
-        public override string ToString() {
-            return Id;
-        }
+    public override string ToString() {
+        return Id;
+    }
 
-        public override int GetHashCode() {
-            return Id.GetHashCode();
-        }
+    public override int GetHashCode() {
+        return Id.GetHashCode();
     }
 }
diff --git a/SharpChat/Context.cs b/SharpChat/Context.cs
index 6e47527..a095cf9 100644
--- a/SharpChat/Context.cs
+++ b/SharpChat/Context.cs
@@ -5,409 +5,409 @@ using SharpChat.SockChat;
 using SharpChat.SockChat.S2CPackets;
 using System.Net;
 
-namespace SharpChat {
-    public class Context {
-        public record ChannelUserAssoc(string UserId, string ChannelName);
+namespace SharpChat;
 
-        public readonly SemaphoreSlim ContextAccess = new(1, 1);
+public class Context {
+    public record ChannelUserAssoc(string UserId, string ChannelName);
 
-        public SnowflakeGenerator SnowflakeGenerator { get; } = new();
-        public RandomSnowflake RandomSnowflake { get; }
+    public readonly SemaphoreSlim ContextAccess = new(1, 1);
 
-        public HashSet<Channel> Channels { get; } = [];
-        public HashSet<Connection> Connections { get; } = [];
-        public HashSet<User> Users { get; } = [];
-        public EventStorage.EventStorage Events { get; }
-        public HashSet<ChannelUserAssoc> ChannelUsers { get; } = [];
-        public Dictionary<string, RateLimiter> UserRateLimiters { get; } = [];
-        public Dictionary<string, Channel> UserLastChannel { get; } = [];
+    public SnowflakeGenerator SnowflakeGenerator { get; } = new();
+    public RandomSnowflake RandomSnowflake { get; }
 
-        public Context(EventStorage.EventStorage evtStore) {
-            Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore));
-            RandomSnowflake = new(SnowflakeGenerator);
-        }
+    public HashSet<Channel> Channels { get; } = [];
+    public HashSet<Connection> Connections { get; } = [];
+    public HashSet<User> Users { get; } = [];
+    public EventStorage.EventStorage Events { get; }
+    public HashSet<ChannelUserAssoc> ChannelUsers { get; } = [];
+    public Dictionary<string, RateLimiter> UserRateLimiters { get; } = [];
+    public Dictionary<string, Channel> UserLastChannel { get; } = [];
 
-        public async Task DispatchEvent(ChatEvent eventInfo) {
-            if(eventInfo is MessageCreateEvent mce) {
-                if(mce.IsBroadcast) {
-                    await Send(new CommandResponseS2CPacket(RandomSnowflake.Next(), LCR.BROADCAST, false, mce.MessageText));
-                } else if(mce.IsPrivate) {
-                    // The channel name returned by GetDMChannelName should not be exposed to the user, instead @<Target User> should be displayed
-                    // e.g. nook sees @Arysil and Arysil sees @nook
+    public Context(EventStorage.EventStorage evtStore) {
+        Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore));
+        RandomSnowflake = new(SnowflakeGenerator);
+    }
 
-                    // this entire routine is garbage, channels should probably in the db
-                    if(!mce.ChannelName.StartsWith('@'))
-                        return;
+    public async Task DispatchEvent(ChatEvent eventInfo) {
+        if(eventInfo is MessageCreateEvent mce) {
+            if(mce.IsBroadcast) {
+                await Send(new CommandResponseS2CPacket(RandomSnowflake.Next(), LCR.BROADCAST, false, mce.MessageText));
+            } else if(mce.IsPrivate) {
+                // The channel name returned by GetDMChannelName should not be exposed to the user, instead @<Target User> should be displayed
+                // e.g. nook sees @Arysil and Arysil sees @nook
 
-                    IEnumerable<string> uids = mce.ChannelName[1..].Split('-', 3).Select(u => (long.TryParse(u, out long up) ? up : -1).ToString());
-                    if(uids.Count() != 2)
-                        return;
+                // this entire routine is garbage, channels should probably in the db
+                if(!mce.ChannelName.StartsWith('@'))
+                    return;
 
-                    IEnumerable<User> users = Users.Where(u => uids.Any(uid => uid == u.UserId));
-                    User? target = users.FirstOrDefault(u => u.UserId != mce.SenderId);
-                    if(target == null)
-                        return;
+                IEnumerable<string> uids = mce.ChannelName[1..].Split('-', 3).Select(u => (long.TryParse(u, out long up) ? up : -1).ToString());
+                if(uids.Count() != 2)
+                    return;
 
-                    foreach(User user in users)
-                        await SendTo(user, new ChatMessageAddS2CPacket(
-                            mce.MessageId,
-                            DateTimeOffset.Now,
-                            mce.SenderId,
-                            mce.SenderId == user.UserId ? $"{target.LegacyName} {mce.MessageText}" : mce.MessageText,
-                            mce.IsAction,
-                            true
-                        ));
-                } else {
-                    Channel? channel = Channels.FirstOrDefault(c => c.NameEquals(mce.ChannelName));
-                    if(channel is not null)
-                        await SendTo(channel, new ChatMessageAddS2CPacket(
-                            mce.MessageId,
-                            DateTimeOffset.Now,
-                            mce.SenderId,
-                            mce.MessageText,
-                            mce.IsAction,
-                            false
-                        ));
-                }
+                IEnumerable<User> users = Users.Where(u => uids.Any(uid => uid == u.UserId));
+                User? target = users.FirstOrDefault(u => u.UserId != mce.SenderId);
+                if(target == null)
+                    return;
 
-                Events.AddEvent(
-                    mce.MessageId, "msg:add",
-                    mce.ChannelName,
-                    mce.SenderId, mce.SenderName, mce.SenderColour, mce.SenderRank, mce.SenderNickName, mce.SenderPerms,
-                    new { text = mce.MessageText },
-                    (mce.IsBroadcast ? StoredEventFlags.Broadcast : 0)
-                    | (mce.IsAction ? StoredEventFlags.Action : 0)
-                    | (mce.IsPrivate ? StoredEventFlags.Private : 0)
-                );
-                return;
-            }
-        }
-
-        public async Task Update() {
-            foreach(Connection conn in Connections)
-                if(!conn.IsDisposed && conn.HasTimedOut) {
-                    conn.Dispose();
-                    Logger.Write($"Nuked connection {conn.Id} associated with {conn.User}.");
-                }
-
-            Connections.RemoveWhere(conn => conn.IsDisposed);
-
-            foreach(User user in Users)
-                if(!Connections.Any(conn => conn.User == user)) {
-                    Logger.Write($"Timing out {user} (no more connections).");
-                    await HandleDisconnect(user, UserDisconnectS2CPacket.Reason.TimeOut);
-                }
-        }
-
-        public async Task SafeUpdate() {
-            ContextAccess.Wait();
-            try {
-                await Update();
-            } finally {
-                ContextAccess.Release();
-            }
-        }
-
-        public bool IsInChannel(User user, Channel channel) {
-            return ChannelUsers.Contains(new ChannelUserAssoc(user.UserId, channel.Name));
-        }
-
-        public string[] GetUserChannelNames(User user) {
-            return [.. ChannelUsers.Where(cu => cu.UserId == user.UserId).Select(cu => cu.ChannelName)];
-        }
-
-        public Channel[] GetUserChannels(User user) {
-            string[] names = GetUserChannelNames(user);
-            return [.. Channels.Where(c => names.Any(n => c.NameEquals(n)))];
-        }
-
-        public string[] GetChannelUserIds(Channel channel) {
-            return [.. ChannelUsers.Where(cu => channel.NameEquals(cu.ChannelName)).Select(cu => cu.UserId)];
-        }
-
-        public User[] GetChannelUsers(Channel channel) {
-            string[] ids = GetChannelUserIds(channel);
-            return [.. Users.Where(u => ids.Contains(u.UserId))];
-        }
-
-        public async Task UpdateUser(
-            User user,
-            string? userName = null,
-            string? nickName = null,
-            ColourInheritable? colour = null,
-            UserStatus? status = null,
-            string? statusText = null,
-            int? rank = null,
-            UserPermissions? perms = null,
-            bool silent = false
-        ) {
-            ArgumentNullException.ThrowIfNull(user);
-
-            bool hasChanged = false;
-            string previousName = string.Empty;
-
-            if(userName != null && !user.UserName.Equals(userName)) {
-                user.UserName = userName;
-                hasChanged = true;
-            }
-
-            if(nickName != null && !user.NickName.Equals(nickName)) {
-                if(!silent)
-                    previousName = user.LegacyName;
-
-                user.NickName = nickName;
-                hasChanged = true;
-            }
-
-            if(colour.HasValue && user.Colour != colour.Value) {
-                user.Colour = colour.Value;
-                hasChanged = true;
-            }
-
-            if(status.HasValue && user.Status != status.Value) {
-                user.Status = status.Value;
-                hasChanged = true;
-            }
-
-            if(statusText != null && !user.StatusText.Equals(statusText)) {
-                user.StatusText = statusText;
-                hasChanged = true;
-            }
-
-            if(rank != null && user.Rank != rank) {
-                user.Rank = (int)rank;
-                hasChanged = true;
-            }
-
-            if(perms.HasValue && user.Permissions != perms) {
-                user.Permissions = perms.Value;
-                hasChanged = true;
-            }
-
-            if(hasChanged) {
-                if(!string.IsNullOrWhiteSpace(previousName))
-                    await SendToUserChannels(user, new CommandResponseS2CPacket(RandomSnowflake.Next(), LCR.NICKNAME_CHANGE, false, previousName, user.LegacyNameWithStatus));
-
-                await SendToUserChannels(user, new UserUpdateS2CPacket(user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions));
-            }
-        }
-
-        public async Task BanUser(User user, TimeSpan duration, UserDisconnectS2CPacket.Reason reason = UserDisconnectS2CPacket.Reason.Kicked) {
-            if(duration > TimeSpan.Zero) {
-                DateTimeOffset expires = duration >= TimeSpan.MaxValue ? DateTimeOffset.MaxValue : DateTimeOffset.Now + duration;
-                await SendTo(user, new ForceDisconnectS2CPacket(expires));
-            } else
-                await SendTo(user, new ForceDisconnectS2CPacket());
-
-            foreach(Connection conn in Connections)
-                if(conn.User == user)
-                    conn.Dispose();
-            Connections.RemoveWhere(conn => conn.IsDisposed);
-
-            await HandleDisconnect(user, reason);
-        }
-
-        public async Task HandleJoin(User user, Channel chan, Connection conn, int maxMsgLength) {
-            if(!IsInChannel(user, chan)) {
-                long msgId = RandomSnowflake.Next();
-                await SendTo(chan, new UserConnectS2CPacket(msgId, DateTimeOffset.Now, user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions));
-                Events.AddEvent(msgId, "user:connect", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
-            }
-
-            await conn.Send(new AuthSuccessS2CPacket(
-                user.UserId,
-                user.LegacyNameWithStatus,
-                user.Colour,
-                user.Rank,
-                user.Permissions,
-                chan.Name,
-                maxMsgLength
-            ));
-            await conn.Send(new ContextUsersS2CPacket(
-                GetChannelUsers(chan).Except([user]).OrderByDescending(u => u.Rank)
-                    .Select(u => new ContextUsersS2CPacket.Entry(
-                        u.UserId,
-                        u.LegacyNameWithStatus,
-                        u.Colour,
-                        u.Rank,
-                        u.Permissions,
+                foreach(User user in users)
+                    await SendTo(user, new ChatMessageAddS2CPacket(
+                        mce.MessageId,
+                        DateTimeOffset.Now,
+                        mce.SenderId,
+                        mce.SenderId == user.UserId ? $"{target.LegacyName} {mce.MessageText}" : mce.MessageText,
+                        mce.IsAction,
                         true
-                    ))
-            ));
-
-            foreach(StoredEventInfo msg in Events.GetChannelEventLog(chan.Name))
-                await conn.Send(new ContextMessageS2CPacket(msg));
-
-            await conn.Send(new ContextChannelsS2CPacket(
-                Channels.Where(c => c.Rank <= user.Rank)
-                    .Select(c => new ContextChannelsS2CPacket.Entry(c.Name, c.HasPassword, c.IsTemporary))
-            ));
-
-            Users.Add(user);
-
-            ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name));
-            UserLastChannel[user.UserId] = chan;
-        }
-
-        public async Task HandleDisconnect(User user, UserDisconnectS2CPacket.Reason reason = UserDisconnectS2CPacket.Reason.Leave) {
-            await UpdateUser(user, status: UserStatus.Offline);
-            Users.Remove(user);
-            UserLastChannel.Remove(user.UserId);
-
-            Channel[] channels = GetUserChannels(user);
-
-            foreach(Channel chan in channels) {
-                ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name));
-
-                long msgId = RandomSnowflake.Next();
-                await SendTo(chan, new UserDisconnectS2CPacket(msgId, DateTimeOffset.Now, user.UserId, user.LegacyNameWithStatus, reason));
-                Events.AddEvent(msgId, "user:disconnect", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, new { reason = (int)reason }, StoredEventFlags.Log);
-
-                if(chan.IsTemporary && chan.IsOwner(user))
-                    await RemoveChannel(chan);
+                    ));
+            } else {
+                Channel? channel = Channels.FirstOrDefault(c => c.NameEquals(mce.ChannelName));
+                if(channel is not null)
+                    await SendTo(channel, new ChatMessageAddS2CPacket(
+                        mce.MessageId,
+                        DateTimeOffset.Now,
+                        mce.SenderId,
+                        mce.MessageText,
+                        mce.IsAction,
+                        false
+                    ));
             }
+
+            Events.AddEvent(
+                mce.MessageId, "msg:add",
+                mce.ChannelName,
+                mce.SenderId, mce.SenderName, mce.SenderColour, mce.SenderRank, mce.SenderNickName, mce.SenderPerms,
+                new { text = mce.MessageText },
+                (mce.IsBroadcast ? StoredEventFlags.Broadcast : 0)
+                | (mce.IsAction ? StoredEventFlags.Action : 0)
+                | (mce.IsPrivate ? StoredEventFlags.Private : 0)
+            );
+            return;
+        }
+    }
+
+    public async Task Update() {
+        foreach(Connection conn in Connections)
+            if(!conn.IsDisposed && conn.HasTimedOut) {
+                conn.Dispose();
+                Logger.Write($"Nuked connection {conn.Id} associated with {conn.User}.");
+            }
+
+        Connections.RemoveWhere(conn => conn.IsDisposed);
+
+        foreach(User user in Users)
+            if(!Connections.Any(conn => conn.User == user)) {
+                Logger.Write($"Timing out {user} (no more connections).");
+                await HandleDisconnect(user, UserDisconnectS2CPacket.Reason.TimeOut);
+            }
+    }
+
+    public async Task SafeUpdate() {
+        ContextAccess.Wait();
+        try {
+            await Update();
+        } finally {
+            ContextAccess.Release();
+        }
+    }
+
+    public bool IsInChannel(User user, Channel channel) {
+        return ChannelUsers.Contains(new ChannelUserAssoc(user.UserId, channel.Name));
+    }
+
+    public string[] GetUserChannelNames(User user) {
+        return [.. ChannelUsers.Where(cu => cu.UserId == user.UserId).Select(cu => cu.ChannelName)];
+    }
+
+    public Channel[] GetUserChannels(User user) {
+        string[] names = GetUserChannelNames(user);
+        return [.. Channels.Where(c => names.Any(n => c.NameEquals(n)))];
+    }
+
+    public string[] GetChannelUserIds(Channel channel) {
+        return [.. ChannelUsers.Where(cu => channel.NameEquals(cu.ChannelName)).Select(cu => cu.UserId)];
+    }
+
+    public User[] GetChannelUsers(Channel channel) {
+        string[] ids = GetChannelUserIds(channel);
+        return [.. Users.Where(u => ids.Contains(u.UserId))];
+    }
+
+    public async Task UpdateUser(
+        User user,
+        string? userName = null,
+        string? nickName = null,
+        ColourInheritable? colour = null,
+        UserStatus? status = null,
+        string? statusText = null,
+        int? rank = null,
+        UserPermissions? perms = null,
+        bool silent = false
+    ) {
+        ArgumentNullException.ThrowIfNull(user);
+
+        bool hasChanged = false;
+        string previousName = string.Empty;
+
+        if(userName != null && !user.UserName.Equals(userName)) {
+            user.UserName = userName;
+            hasChanged = true;
         }
 
-        public async Task SwitchChannel(User user, Channel chan, string password) {
-            if(UserLastChannel.TryGetValue(user.UserId, out Channel? ulc) && chan == ulc) {
+        if(nickName != null && !user.NickName.Equals(nickName)) {
+            if(!silent)
+                previousName = user.LegacyName;
+
+            user.NickName = nickName;
+            hasChanged = true;
+        }
+
+        if(colour.HasValue && user.Colour != colour.Value) {
+            user.Colour = colour.Value;
+            hasChanged = true;
+        }
+
+        if(status.HasValue && user.Status != status.Value) {
+            user.Status = status.Value;
+            hasChanged = true;
+        }
+
+        if(statusText != null && !user.StatusText.Equals(statusText)) {
+            user.StatusText = statusText;
+            hasChanged = true;
+        }
+
+        if(rank != null && user.Rank != rank) {
+            user.Rank = (int)rank;
+            hasChanged = true;
+        }
+
+        if(perms.HasValue && user.Permissions != perms) {
+            user.Permissions = perms.Value;
+            hasChanged = true;
+        }
+
+        if(hasChanged) {
+            if(!string.IsNullOrWhiteSpace(previousName))
+                await SendToUserChannels(user, new CommandResponseS2CPacket(RandomSnowflake.Next(), LCR.NICKNAME_CHANGE, false, previousName, user.LegacyNameWithStatus));
+
+            await SendToUserChannels(user, new UserUpdateS2CPacket(user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions));
+        }
+    }
+
+    public async Task BanUser(User user, TimeSpan duration, UserDisconnectS2CPacket.Reason reason = UserDisconnectS2CPacket.Reason.Kicked) {
+        if(duration > TimeSpan.Zero) {
+            DateTimeOffset expires = duration >= TimeSpan.MaxValue ? DateTimeOffset.MaxValue : DateTimeOffset.Now + duration;
+            await SendTo(user, new ForceDisconnectS2CPacket(expires));
+        } else
+            await SendTo(user, new ForceDisconnectS2CPacket());
+
+        foreach(Connection conn in Connections)
+            if(conn.User == user)
+                conn.Dispose();
+        Connections.RemoveWhere(conn => conn.IsDisposed);
+
+        await HandleDisconnect(user, reason);
+    }
+
+    public async Task HandleJoin(User user, Channel chan, Connection conn, int maxMsgLength) {
+        if(!IsInChannel(user, chan)) {
+            long msgId = RandomSnowflake.Next();
+            await SendTo(chan, new UserConnectS2CPacket(msgId, DateTimeOffset.Now, user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions));
+            Events.AddEvent(msgId, "user:connect", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
+        }
+
+        await conn.Send(new AuthSuccessS2CPacket(
+            user.UserId,
+            user.LegacyNameWithStatus,
+            user.Colour,
+            user.Rank,
+            user.Permissions,
+            chan.Name,
+            maxMsgLength
+        ));
+        await conn.Send(new ContextUsersS2CPacket(
+            GetChannelUsers(chan).Except([user]).OrderByDescending(u => u.Rank)
+                .Select(u => new ContextUsersS2CPacket.Entry(
+                    u.UserId,
+                    u.LegacyNameWithStatus,
+                    u.Colour,
+                    u.Rank,
+                    u.Permissions,
+                    true
+                ))
+        ));
+
+        foreach(StoredEventInfo msg in Events.GetChannelEventLog(chan.Name))
+            await conn.Send(new ContextMessageS2CPacket(msg));
+
+        await conn.Send(new ContextChannelsS2CPacket(
+            Channels.Where(c => c.Rank <= user.Rank)
+                .Select(c => new ContextChannelsS2CPacket.Entry(c.Name, c.HasPassword, c.IsTemporary))
+        ));
+
+        Users.Add(user);
+
+        ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name));
+        UserLastChannel[user.UserId] = chan;
+    }
+
+    public async Task HandleDisconnect(User user, UserDisconnectS2CPacket.Reason reason = UserDisconnectS2CPacket.Reason.Leave) {
+        await UpdateUser(user, status: UserStatus.Offline);
+        Users.Remove(user);
+        UserLastChannel.Remove(user.UserId);
+
+        Channel[] channels = GetUserChannels(user);
+
+        foreach(Channel chan in channels) {
+            ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name));
+
+            long msgId = RandomSnowflake.Next();
+            await SendTo(chan, new UserDisconnectS2CPacket(msgId, DateTimeOffset.Now, user.UserId, user.LegacyNameWithStatus, reason));
+            Events.AddEvent(msgId, "user:disconnect", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, new { reason = (int)reason }, StoredEventFlags.Log);
+
+            if(chan.IsTemporary && chan.IsOwner(user))
+                await RemoveChannel(chan);
+        }
+    }
+
+    public async Task SwitchChannel(User user, Channel chan, string password) {
+        if(UserLastChannel.TryGetValue(user.UserId, out Channel? ulc) && chan == ulc) {
+            await ForceChannel(user);
+            return;
+        }
+
+        if(!user.Can(UserPermissions.JoinAnyChannel) && chan.IsOwner(user)) {
+            if(chan.Rank > user.Rank) {
+                await SendTo(user, new CommandResponseS2CPacket(RandomSnowflake.Next(), LCR.CHANNEL_INSUFFICIENT_HIERARCHY, true, chan.Name));
                 await ForceChannel(user);
                 return;
             }
 
-            if(!user.Can(UserPermissions.JoinAnyChannel) && chan.IsOwner(user)) {
-                if(chan.Rank > user.Rank) {
-                    await SendTo(user, new CommandResponseS2CPacket(RandomSnowflake.Next(), LCR.CHANNEL_INSUFFICIENT_HIERARCHY, true, chan.Name));
-                    await ForceChannel(user);
-                    return;
-                }
-
-                if(!string.IsNullOrEmpty(chan.Password) && chan.Password != password) {
-                    await SendTo(user, new CommandResponseS2CPacket(RandomSnowflake.Next(), LCR.CHANNEL_INVALID_PASSWORD, true, chan.Name));
-                    await ForceChannel(user);
-                    return;
-                }
+            if(!string.IsNullOrEmpty(chan.Password) && chan.Password != password) {
+                await SendTo(user, new CommandResponseS2CPacket(RandomSnowflake.Next(), LCR.CHANNEL_INVALID_PASSWORD, true, chan.Name));
+                await ForceChannel(user);
+                return;
             }
-
-            await ForceChannelSwitch(user, chan);
         }
 
-        public async Task ForceChannelSwitch(User user, Channel chan) {
-            if(!Channels.Contains(chan))
-                return;
+        await ForceChannelSwitch(user, chan);
+    }
 
-            Channel oldChan = UserLastChannel[user.UserId];
+    public async Task ForceChannelSwitch(User user, Channel chan) {
+        if(!Channels.Contains(chan))
+            return;
 
-            long leaveId = RandomSnowflake.Next();
-            await SendTo(oldChan, new UserChannelLeaveS2CPacket(leaveId, user.UserId));
-            Events.AddEvent(leaveId, "chan:leave", oldChan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
+        Channel oldChan = UserLastChannel[user.UserId];
 
-            long joinId = RandomSnowflake.Next();
-            await SendTo(chan, new UserChannelJoinS2CPacket(joinId, user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions));
-            Events.AddEvent(joinId, "chan:join", chan.Name, user.UserId, user.LegacyName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
+        long leaveId = RandomSnowflake.Next();
+        await SendTo(oldChan, new UserChannelLeaveS2CPacket(leaveId, user.UserId));
+        Events.AddEvent(leaveId, "chan:leave", oldChan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
 
-            await SendTo(user, new ContextClearS2CPacket(ContextClearS2CPacket.Mode.MessagesUsers));
-            await SendTo(user, new ContextUsersS2CPacket(
-                GetChannelUsers(chan).Except([user]).OrderByDescending(u => u.Rank)
-                    .Select(u => new ContextUsersS2CPacket.Entry(
-                        u.UserId,
-                        u.LegacyNameWithStatus,
-                        u.Colour,
-                        u.Rank,
-                        u.Permissions,
-                        true
-                    ))
-            ));
+        long joinId = RandomSnowflake.Next();
+        await SendTo(chan, new UserChannelJoinS2CPacket(joinId, user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions));
+        Events.AddEvent(joinId, "chan:join", chan.Name, user.UserId, user.LegacyName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
 
-            foreach(StoredEventInfo msg in Events.GetChannelEventLog(chan.Name))
-                await SendTo(user, new ContextMessageS2CPacket(msg));
+        await SendTo(user, new ContextClearS2CPacket(ContextClearS2CPacket.Mode.MessagesUsers));
+        await SendTo(user, new ContextUsersS2CPacket(
+            GetChannelUsers(chan).Except([user]).OrderByDescending(u => u.Rank)
+                .Select(u => new ContextUsersS2CPacket.Entry(
+                    u.UserId,
+                    u.LegacyNameWithStatus,
+                    u.Colour,
+                    u.Rank,
+                    u.Permissions,
+                    true
+                ))
+        ));
 
-            await ForceChannel(user, chan);
+        foreach(StoredEventInfo msg in Events.GetChannelEventLog(chan.Name))
+            await SendTo(user, new ContextMessageS2CPacket(msg));
 
-            ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, oldChan.Name));
-            ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name));
-            UserLastChannel[user.UserId] = chan;
+        await ForceChannel(user, chan);
 
-            if(oldChan.IsTemporary && oldChan.IsOwner(user))
-                await RemoveChannel(oldChan);
-        }
+        ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, oldChan.Name));
+        ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name));
+        UserLastChannel[user.UserId] = chan;
 
-        public async Task Send(S2CPacket packet) {
-            foreach(Connection conn in Connections)
-                if(conn.IsAlive && conn.User is not null)
-                    await conn.Send(packet);
-        }
+        if(oldChan.IsTemporary && oldChan.IsOwner(user))
+            await RemoveChannel(oldChan);
+    }
 
-        public async Task SendTo(User user, S2CPacket packet) {
-            foreach(Connection conn in Connections)
-                if(conn.IsAlive && conn.User == user)
-                    await conn.Send(packet);
-        }
-
-        public async Task SendTo(Channel channel, S2CPacket packet) {
-            // might be faster to grab the users first and then cascade into that SendTo
-            IEnumerable<Connection> conns = Connections.Where(c => c.IsAlive && c.User is not null && IsInChannel(c.User, channel));
-            foreach(Connection conn in conns)
+    public async Task Send(S2CPacket packet) {
+        foreach(Connection conn in Connections)
+            if(conn.IsAlive && conn.User is not null)
                 await conn.Send(packet);
-        }
+    }
 
-        public async Task SendToUserChannels(User user, S2CPacket packet) {
-            IEnumerable<Channel> chans = Channels.Where(c => IsInChannel(user, c));
-            IEnumerable<Connection> conns = Connections.Where(conn => conn.IsAlive && conn.User is not null && ChannelUsers.Any(cu => cu.UserId == conn.User.UserId && chans.Any(chan => chan.NameEquals(cu.ChannelName))));
-            foreach(Connection conn in conns)
+    public async Task SendTo(User user, S2CPacket packet) {
+        foreach(Connection conn in Connections)
+            if(conn.IsAlive && conn.User == user)
                 await conn.Send(packet);
-        }
+    }
 
-        public IPAddress[] GetRemoteAddresses(User user) {
-            return [.. Connections.Where(c => c.IsAlive && c.User == user).Select(c => c.RemoteAddress).Distinct()];
-        }
+    public async Task SendTo(Channel channel, S2CPacket packet) {
+        // might be faster to grab the users first and then cascade into that SendTo
+        IEnumerable<Connection> conns = Connections.Where(c => c.IsAlive && c.User is not null && IsInChannel(c.User, channel));
+        foreach(Connection conn in conns)
+            await conn.Send(packet);
+    }
 
-        public async Task ForceChannel(User user, Channel? chan = null) {
-            ArgumentNullException.ThrowIfNull(user);
+    public async Task SendToUserChannels(User user, S2CPacket packet) {
+        IEnumerable<Channel> chans = Channels.Where(c => IsInChannel(user, c));
+        IEnumerable<Connection> conns = Connections.Where(conn => conn.IsAlive && conn.User is not null && ChannelUsers.Any(cu => cu.UserId == conn.User.UserId && chans.Any(chan => chan.NameEquals(cu.ChannelName))));
+        foreach(Connection conn in conns)
+            await conn.Send(packet);
+    }
 
-            if(chan == null && !UserLastChannel.TryGetValue(user.UserId, out chan))
-                throw new ArgumentException("no channel???");
+    public IPAddress[] GetRemoteAddresses(User user) {
+        return [.. Connections.Where(c => c.IsAlive && c.User == user).Select(c => c.RemoteAddress).Distinct()];
+    }
 
-            await SendTo(user, new UserChannelForceJoinS2CPacket(chan.Name));
-        }
+    public async Task ForceChannel(User user, Channel? chan = null) {
+        ArgumentNullException.ThrowIfNull(user);
 
-        public async Task UpdateChannel(Channel channel, bool? temporary = null, int? hierarchy = null, string? password = null) {
-            ArgumentNullException.ThrowIfNull(channel);
-            if(!Channels.Contains(channel))
-                throw new ArgumentException("Provided channel is not registered with this manager.", nameof(channel));
+        if(chan == null && !UserLastChannel.TryGetValue(user.UserId, out chan))
+            throw new ArgumentException("no channel???");
 
-            if(temporary.HasValue)
-                channel.IsTemporary = temporary.Value;
+        await SendTo(user, new UserChannelForceJoinS2CPacket(chan.Name));
+    }
 
-            if(hierarchy.HasValue)
-                channel.Rank = hierarchy.Value;
+    public async Task UpdateChannel(Channel channel, bool? temporary = null, int? hierarchy = null, string? password = null) {
+        ArgumentNullException.ThrowIfNull(channel);
+        if(!Channels.Contains(channel))
+            throw new ArgumentException("Provided channel is not registered with this manager.", nameof(channel));
 
-            if(password != null)
-                channel.Password = password;
+        if(temporary.HasValue)
+            channel.IsTemporary = temporary.Value;
 
-            // TODO: Users that no longer have access to the channel/gained access to the channel by the hierarchy change should receive delete and create packets respectively
-            foreach(User user in Users.Where(u => u.Rank >= channel.Rank))
-                await SendTo(user, new ChannelUpdateS2CPacket(channel.Name, channel.Name, channel.HasPassword, channel.IsTemporary));
-        }
+        if(hierarchy.HasValue)
+            channel.Rank = hierarchy.Value;
 
-        public async Task RemoveChannel(Channel channel) {
-            if(channel == null || Channels.Count < 1)
-                return;
+        if(password != null)
+            channel.Password = password;
 
-            Channel? defaultChannel = Channels.FirstOrDefault();
-            if(defaultChannel is null)
-                return;
+        // TODO: Users that no longer have access to the channel/gained access to the channel by the hierarchy change should receive delete and create packets respectively
+        foreach(User user in Users.Where(u => u.Rank >= channel.Rank))
+            await SendTo(user, new ChannelUpdateS2CPacket(channel.Name, channel.Name, channel.HasPassword, channel.IsTemporary));
+    }
 
-            // Remove channel from the listing
-            Channels.Remove(channel);
+    public async Task RemoveChannel(Channel channel) {
+        if(channel == null || Channels.Count < 1)
+            return;
 
-            // Move all users back to the main channel
-            // TODO: Replace this with a kick. SCv2 supports being in 0 channels, SCv1 should force the user back to DefaultChannel.
-            foreach(User user in GetChannelUsers(channel))
-                await SwitchChannel(user, defaultChannel, string.Empty);
+        Channel? defaultChannel = Channels.FirstOrDefault();
+        if(defaultChannel is null)
+            return;
 
-            // Broadcast deletion of channel
-            foreach(User user in Users.Where(u => u.Rank >= channel.Rank))
-                await SendTo(user, new ChannelDeleteS2CPacket(channel.Name));
-        }
+        // Remove channel from the listing
+        Channels.Remove(channel);
+
+        // Move all users back to the main channel
+        // TODO: Replace this with a kick. SCv2 supports being in 0 channels, SCv1 should force the user back to DefaultChannel.
+        foreach(User user in GetChannelUsers(channel))
+            await SwitchChannel(user, defaultChannel, string.Empty);
+
+        // Broadcast deletion of channel
+        foreach(User user in Users.Where(u => u.Rank >= channel.Rank))
+            await SendTo(user, new ChannelDeleteS2CPacket(channel.Name));
     }
 }
diff --git a/SharpChat/EventStorage/EventStorage.cs b/SharpChat/EventStorage/EventStorage.cs
index ae0f344..db3a47c 100644
--- a/SharpChat/EventStorage/EventStorage.cs
+++ b/SharpChat/EventStorage/EventStorage.cs
@@ -1,20 +1,20 @@
-namespace SharpChat.EventStorage {
-    public interface EventStorage {
-        void AddEvent(
-            long id,
-            string type,
-            string channelName,
-            string senderId,
-            string senderName,
-            ColourInheritable senderColour,
-            int senderRank,
-            string senderNick,
-            UserPermissions senderPerms,
-            object? data = null,
-            StoredEventFlags flags = StoredEventFlags.None
-        );
-        void RemoveEvent(StoredEventInfo evt);
-        StoredEventInfo? GetEvent(long seqId);
-        IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0);
-    }
+namespace SharpChat.EventStorage;
+
+public interface EventStorage {
+    void AddEvent(
+        long id,
+        string type,
+        string channelName,
+        string senderId,
+        string senderName,
+        ColourInheritable senderColour,
+        int senderRank,
+        string senderNick,
+        UserPermissions senderPerms,
+        object? data = null,
+        StoredEventFlags flags = StoredEventFlags.None
+    );
+    void RemoveEvent(StoredEventInfo evt);
+    StoredEventInfo? GetEvent(long seqId);
+    IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0);
 }
diff --git a/SharpChat/EventStorage/MariaDBEventStorage.cs b/SharpChat/EventStorage/MariaDBEventStorage.cs
index 900b9be..69218fc 100644
--- a/SharpChat/EventStorage/MariaDBEventStorage.cs
+++ b/SharpChat/EventStorage/MariaDBEventStorage.cs
@@ -2,130 +2,130 @@ using MySqlConnector;
 using System.Text;
 using System.Text.Json;
 
-namespace SharpChat.EventStorage {
-    public partial class MariaDBEventStorage(string connString) : EventStorage {
-        private string ConnectionString { get; } = connString ?? throw new ArgumentNullException(nameof(connString));
+namespace SharpChat.EventStorage;
 
-        public void AddEvent(
-            long id,
-            string type,
-            string channelName,
-            string senderId,
-            string senderName,
-            ColourInheritable senderColour,
-            int senderRank,
-            string senderNick,
-            UserPermissions senderPerms,
-            object? data = null,
-            StoredEventFlags flags = StoredEventFlags.None
-        ) {
-            RunCommand(
-                "INSERT INTO `sqc_events` (`event_id`, `event_created`, `event_type`, `event_target`, `event_flags`, `event_data`"
-                + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`)"
-                + " VALUES (@id, NOW(), @type, @target, @flags, @data"
-                + ", @sender, @sender_name, @sender_colour, @sender_rank, @sender_nick, @sender_perms)",
-                new MySqlParameter("id", id),
-                new MySqlParameter("type", type),
-                new MySqlParameter("target", string.IsNullOrWhiteSpace(channelName) ? null : channelName),
-                new MySqlParameter("flags", (byte)flags),
-                new MySqlParameter("data", data == null ? "{}" : JsonSerializer.SerializeToUtf8Bytes(data)),
-                new MySqlParameter("sender", long.TryParse(senderId, out long senderId64) && senderId64 > 0 ? senderId64 : null),
-                new MySqlParameter("sender_name", senderName),
-                new MySqlParameter("sender_colour", senderColour.ToMisuzu()),
-                new MySqlParameter("sender_rank", senderRank),
-                new MySqlParameter("sender_nick", string.IsNullOrWhiteSpace(senderNick) ? null : senderNick),
-                new MySqlParameter("sender_perms", senderPerms)
+public partial class MariaDBEventStorage(string connString) : EventStorage {
+    private string ConnectionString { get; } = connString ?? throw new ArgumentNullException(nameof(connString));
+
+    public void AddEvent(
+        long id,
+        string type,
+        string channelName,
+        string senderId,
+        string senderName,
+        ColourInheritable senderColour,
+        int senderRank,
+        string senderNick,
+        UserPermissions senderPerms,
+        object? data = null,
+        StoredEventFlags flags = StoredEventFlags.None
+    ) {
+        RunCommand(
+            "INSERT INTO `sqc_events` (`event_id`, `event_created`, `event_type`, `event_target`, `event_flags`, `event_data`"
+            + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`)"
+            + " VALUES (@id, NOW(), @type, @target, @flags, @data"
+            + ", @sender, @sender_name, @sender_colour, @sender_rank, @sender_nick, @sender_perms)",
+            new MySqlParameter("id", id),
+            new MySqlParameter("type", type),
+            new MySqlParameter("target", string.IsNullOrWhiteSpace(channelName) ? null : channelName),
+            new MySqlParameter("flags", (byte)flags),
+            new MySqlParameter("data", data == null ? "{}" : JsonSerializer.SerializeToUtf8Bytes(data)),
+            new MySqlParameter("sender", long.TryParse(senderId, out long senderId64) && senderId64 > 0 ? senderId64 : null),
+            new MySqlParameter("sender_name", senderName),
+            new MySqlParameter("sender_colour", senderColour.ToMisuzu()),
+            new MySqlParameter("sender_rank", senderRank),
+            new MySqlParameter("sender_nick", string.IsNullOrWhiteSpace(senderNick) ? null : senderNick),
+            new MySqlParameter("sender_perms", senderPerms)
+        );
+    }
+
+    public StoredEventInfo? GetEvent(long seqId) {
+        try {
+            using MySqlDataReader? reader = RunQuery(
+                "SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`"
+                + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`"
+                + ", UNIX_TIMESTAMP(`event_created`) AS `event_created`"
+                + ", UNIX_TIMESTAMP(`event_deleted`) AS `event_deleted`"
+                + " FROM `sqc_events`"
+                + " WHERE `event_id` = @id",
+                new MySqlParameter("id", seqId)
             );
-        }
 
-        public StoredEventInfo? GetEvent(long seqId) {
-            try {
-                using MySqlDataReader? reader = RunQuery(
-                    "SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`"
-                    + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`"
-                    + ", UNIX_TIMESTAMP(`event_created`) AS `event_created`"
-                    + ", UNIX_TIMESTAMP(`event_deleted`) AS `event_deleted`"
-                    + " FROM `sqc_events`"
-                    + " WHERE `event_id` = @id",
-                    new MySqlParameter("id", seqId)
-                );
+            if(reader is null)
+                return null;
 
-                if(reader is null)
-                    return null;
-
-                while(reader.Read()) {
-                    StoredEventInfo evt = ReadEvent(reader);
-                    if(evt != null)
-                        return evt;
-                }
-            } catch(MySqlException ex) {
-                Logger.Write(ex);
+            while(reader.Read()) {
+                StoredEventInfo evt = ReadEvent(reader);
+                if(evt != null)
+                    return evt;
             }
-
-            return null;
+        } catch(MySqlException ex) {
+            Logger.Write(ex);
         }
 
-        private static StoredEventInfo ReadEvent(MySqlDataReader reader) {
-            return new StoredEventInfo(
-                reader.GetInt64("event_id"),
-                Encoding.ASCII.GetString((byte[])reader["event_type"]),
-                reader.IsDBNull(reader.GetOrdinal("event_sender")) ? null : new User(
-                    reader.GetInt64("event_sender").ToString(),
-                    reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString("event_sender_name"),
-                    ColourInheritable.FromMisuzu(reader.GetInt32("event_sender_colour")),
-                    reader.GetInt32("event_sender_rank"),
-                    (UserPermissions)reader.GetInt32("event_sender_perms"),
-                    reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? string.Empty : reader.GetString("event_sender_nick")
-                ),
-                DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")),
-                reader.IsDBNull(reader.GetOrdinal("event_deleted")) ? null : DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_deleted")),
-                reader.IsDBNull(reader.GetOrdinal("event_target")) ? null : Encoding.ASCII.GetString((byte[])reader["event_target"]),
-                JsonDocument.Parse(Encoding.ASCII.GetString((byte[])reader["event_data"])),
-                (StoredEventFlags)reader.GetByte("event_flags")
+        return null;
+    }
+
+    private static StoredEventInfo ReadEvent(MySqlDataReader reader) {
+        return new StoredEventInfo(
+            reader.GetInt64("event_id"),
+            Encoding.ASCII.GetString((byte[])reader["event_type"]),
+            reader.IsDBNull(reader.GetOrdinal("event_sender")) ? null : new User(
+                reader.GetInt64("event_sender").ToString(),
+                reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString("event_sender_name"),
+                ColourInheritable.FromMisuzu(reader.GetInt32("event_sender_colour")),
+                reader.GetInt32("event_sender_rank"),
+                (UserPermissions)reader.GetInt32("event_sender_perms"),
+                reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? string.Empty : reader.GetString("event_sender_nick")
+            ),
+            DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")),
+            reader.IsDBNull(reader.GetOrdinal("event_deleted")) ? null : DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_deleted")),
+            reader.IsDBNull(reader.GetOrdinal("event_target")) ? null : Encoding.ASCII.GetString((byte[])reader["event_target"]),
+            JsonDocument.Parse(Encoding.ASCII.GetString((byte[])reader["event_data"])),
+            (StoredEventFlags)reader.GetByte("event_flags")
+        );
+    }
+
+    public IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0) {
+        List<StoredEventInfo> events = [];
+
+        try {
+            using MySqlDataReader? reader = RunQuery(
+                "SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`"
+                + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`"
+                + ", UNIX_TIMESTAMP(`event_created`) AS `event_created`"
+                + ", UNIX_TIMESTAMP(`event_deleted`) AS `event_deleted`"
+                + " FROM `sqc_events`"
+                + " WHERE `event_deleted` IS NULL AND (`event_target` = @target OR `event_target` IS NULL)"
+                + " AND `event_id` > @offset"
+                + " ORDER BY `event_id` DESC"
+                + " LIMIT @amount",
+                new MySqlParameter("target", channelName),
+                new MySqlParameter("amount", amount),
+                new MySqlParameter("offset", offset)
             );
-        }
+            if(reader is null)
+                return events;
 
-        public IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0) {
-            List<StoredEventInfo> events = [];
-
-            try {
-                using MySqlDataReader? reader = RunQuery(
-                    "SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`"
-                    + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`"
-                    + ", UNIX_TIMESTAMP(`event_created`) AS `event_created`"
-                    + ", UNIX_TIMESTAMP(`event_deleted`) AS `event_deleted`"
-                    + " FROM `sqc_events`"
-                    + " WHERE `event_deleted` IS NULL AND (`event_target` = @target OR `event_target` IS NULL)"
-                    + " AND `event_id` > @offset"
-                    + " ORDER BY `event_id` DESC"
-                    + " LIMIT @amount",
-                    new MySqlParameter("target", channelName),
-                    new MySqlParameter("amount", amount),
-                    new MySqlParameter("offset", offset)
-                );
-                if(reader is null)
-                    return events;
-
-                while(reader.Read()) {
-                    StoredEventInfo evt = ReadEvent(reader);
-                    if(evt != null)
-                        events.Add(evt);
-                }
-            } catch(MySqlException ex) {
-                Logger.Write(ex);
+            while(reader.Read()) {
+                StoredEventInfo evt = ReadEvent(reader);
+                if(evt != null)
+                    events.Add(evt);
             }
-
-            events.Reverse();
-
-            return events;
+        } catch(MySqlException ex) {
+            Logger.Write(ex);
         }
 
-        public void RemoveEvent(StoredEventInfo evt) {
-            ArgumentNullException.ThrowIfNull(evt);
-            RunCommand(
-                "UPDATE IGNORE `sqc_events` SET `event_deleted` = NOW() WHERE `event_id` = @id AND `event_deleted` IS NULL",
-                new MySqlParameter("id", evt.Id)
-            );
-        }
+        events.Reverse();
+
+        return events;
+    }
+
+    public void RemoveEvent(StoredEventInfo evt) {
+        ArgumentNullException.ThrowIfNull(evt);
+        RunCommand(
+            "UPDATE IGNORE `sqc_events` SET `event_deleted` = NOW() WHERE `event_id` = @id AND `event_deleted` IS NULL",
+            new MySqlParameter("id", evt.Id)
+        );
     }
 }
diff --git a/SharpChat/EventStorage/MariaDBEventStorage_Database.cs b/SharpChat/EventStorage/MariaDBEventStorage_Database.cs
index 1035626..055d5c4 100644
--- a/SharpChat/EventStorage/MariaDBEventStorage_Database.cs
+++ b/SharpChat/EventStorage/MariaDBEventStorage_Database.cs
@@ -1,87 +1,87 @@
 using MySqlConnector;
 using SharpChat.Configuration;
 
-namespace SharpChat.EventStorage {
-    public partial class MariaDBEventStorage {
-        public static string BuildConnString(Configuration.Config config) {
-            return BuildConnString(
-                config.ReadValue("host", "localhost"),
-                config.ReadValue("user", string.Empty),
-                config.ReadValue("pass", string.Empty),
-                config.ReadValue("db", "sharpchat")
-            );
+namespace SharpChat.EventStorage;
+
+public partial class MariaDBEventStorage {
+    public static string BuildConnString(Configuration.Config config) {
+        return BuildConnString(
+            config.ReadValue("host", "localhost"),
+            config.ReadValue("user", string.Empty),
+            config.ReadValue("pass", string.Empty),
+            config.ReadValue("db", "sharpchat")
+        );
+    }
+
+    public static string BuildConnString(string? host, string? username, string? password, string? database) {
+        return new MySqlConnectionStringBuilder {
+            Server = host,
+            UserID = username,
+            Password = password,
+            Database = database,
+            OldGuids = false,
+            TreatTinyAsBoolean = false,
+            CharacterSet = "utf8mb4",
+            SslMode = MySqlSslMode.None,
+            ForceSynchronous = true,
+            ConnectionTimeout = 5,
+            DefaultCommandTimeout = 900, // fuck it, 15 minutes
+        }.ToString();
+    }
+
+    private MySqlConnection GetConnection() {
+        MySqlConnection conn = new(ConnectionString);
+        conn.Open();
+        return conn;
+    }
+
+    private int RunCommand(string command, params MySqlParameter[] parameters) {
+        try {
+            using MySqlConnection conn = GetConnection();
+            using MySqlCommand cmd = conn.CreateCommand();
+            if(parameters?.Length > 0)
+                cmd.Parameters.AddRange(parameters);
+            cmd.CommandText = command;
+            return cmd.ExecuteNonQuery();
+        } catch(MySqlException ex) {
+            Logger.Write(ex);
         }
 
-        public static string BuildConnString(string? host, string? username, string? password, string? database) {
-            return new MySqlConnectionStringBuilder {
-                Server = host,
-                UserID = username,
-                Password = password,
-                Database = database,
-                OldGuids = false,
-                TreatTinyAsBoolean = false,
-                CharacterSet = "utf8mb4",
-                SslMode = MySqlSslMode.None,
-                ForceSynchronous = true,
-                ConnectionTimeout = 5,
-                DefaultCommandTimeout = 900, // fuck it, 15 minutes
-            }.ToString();
+        return 0;
+    }
+
+    private MySqlDataReader? RunQuery(string command, params MySqlParameter[] parameters) {
+        try {
+            MySqlConnection conn = GetConnection();
+            MySqlCommand cmd = conn.CreateCommand();
+            if(parameters?.Length > 0)
+                cmd.Parameters.AddRange(parameters);
+            cmd.CommandText = command;
+            return cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
+        } catch(MySqlException ex) {
+            Logger.Write(ex);
         }
 
-        private MySqlConnection GetConnection() {
-            MySqlConnection conn = new(ConnectionString);
-            conn.Open();
-            return conn;
+        return null;
+    }
+
+    private T RunQueryValue<T>(string command, params MySqlParameter[] parameters)
+        where T : struct {
+        try {
+            using MySqlConnection conn = GetConnection();
+            using MySqlCommand cmd = conn.CreateCommand();
+            if(parameters?.Length > 0)
+                cmd.Parameters.AddRange(parameters);
+            cmd.CommandText = command;
+            cmd.Prepare();
+
+            object? raw = cmd.ExecuteScalar();
+            if(raw is T value)
+                return value;
+        } catch(MySqlException ex) {
+            Logger.Write(ex);
         }
 
-        private int RunCommand(string command, params MySqlParameter[] parameters) {
-            try {
-                using MySqlConnection conn = GetConnection();
-                using MySqlCommand cmd = conn.CreateCommand();
-                if(parameters?.Length > 0)
-                    cmd.Parameters.AddRange(parameters);
-                cmd.CommandText = command;
-                return cmd.ExecuteNonQuery();
-            } catch(MySqlException ex) {
-                Logger.Write(ex);
-            }
-
-            return 0;
-        }
-
-        private MySqlDataReader? RunQuery(string command, params MySqlParameter[] parameters) {
-            try {
-                MySqlConnection conn = GetConnection();
-                MySqlCommand cmd = conn.CreateCommand();
-                if(parameters?.Length > 0)
-                    cmd.Parameters.AddRange(parameters);
-                cmd.CommandText = command;
-                return cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
-            } catch(MySqlException ex) {
-                Logger.Write(ex);
-            }
-
-            return null;
-        }
-
-        private T RunQueryValue<T>(string command, params MySqlParameter[] parameters)
-            where T : struct {
-            try {
-                using MySqlConnection conn = GetConnection();
-                using MySqlCommand cmd = conn.CreateCommand();
-                if(parameters?.Length > 0)
-                    cmd.Parameters.AddRange(parameters);
-                cmd.CommandText = command;
-                cmd.Prepare();
-
-                object? raw = cmd.ExecuteScalar();
-                if(raw is T value)
-                    return value;
-            } catch(MySqlException ex) {
-                Logger.Write(ex);
-            }
-
-            return default;
-        }
+        return default;
     }
 }
diff --git a/SharpChat/EventStorage/MariaDBEventStorage_Migrations.cs b/SharpChat/EventStorage/MariaDBEventStorage_Migrations.cs
index 3bfaac4..c62bf02 100644
--- a/SharpChat/EventStorage/MariaDBEventStorage_Migrations.cs
+++ b/SharpChat/EventStorage/MariaDBEventStorage_Migrations.cs
@@ -1,84 +1,84 @@
 using MySqlConnector;
 
-namespace SharpChat.EventStorage {
-    public partial class MariaDBEventStorage {
-        private void DoMigration(string name, Action action) {
-            bool done = RunQueryValue<long>(
-                "SELECT COUNT(*) FROM `sqc_migrations` WHERE `migration_name` = @name",
+namespace SharpChat.EventStorage;
+
+public partial class MariaDBEventStorage {
+    private void DoMigration(string name, Action action) {
+        bool done = RunQueryValue<long>(
+            "SELECT COUNT(*) FROM `sqc_migrations` WHERE `migration_name` = @name",
+            new MySqlParameter("name", name)
+        ) > 0;
+        if(!done) {
+            Logger.Write($"Running migration '{name}'...");
+            action();
+            RunCommand(
+                "INSERT INTO `sqc_migrations` (`migration_name`) VALUES (@name)",
                 new MySqlParameter("name", name)
-            ) > 0;
-            if(!done) {
-                Logger.Write($"Running migration '{name}'...");
-                action();
-                RunCommand(
-                    "INSERT INTO `sqc_migrations` (`migration_name`) VALUES (@name)",
-                    new MySqlParameter("name", name)
-                );
-            }
-        }
-
-        public void RunMigrations() {
-            RunCommand(
-                "CREATE TABLE IF NOT EXISTS `sqc_migrations` ("
-                + "`migration_name` VARCHAR(255) NOT NULL,"
-                + "`migration_completed` TIMESTAMP NOT NULL DEFAULT current_timestamp(),"
-                + "UNIQUE INDEX `migration_name` (`migration_name`),"
-                + "INDEX `migration_completed` (`migration_completed`)"
-                + ") COLLATE='utf8mb4_unicode_ci' ENGINE=InnoDB;"
-            );
-
-            DoMigration("create_events_table", CreateEventsTable);
-            DoMigration("allow_null_target", AllowNullTarget);
-            DoMigration("event_data_as_medium_blob", EventDataAsMediumBlob);
-            DoMigration("event_user_and_nick_name_to_1000", EventUserAndNickNameTo1000);
-        }
-
-        private void EventUserAndNickNameTo1000() {
-            RunCommand(
-                "ALTER TABLE `sqc_events`"
-                + " CHANGE COLUMN `event_sender_name` `event_sender_name` VARCHAR(1000) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_520_ci' AFTER `event_sender`,"
-                + " CHANGE COLUMN `event_sender_nick` `event_sender_nick` VARCHAR(1000) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_520_ci' AFTER `event_sender_rank`;"
-            );
-        }
-
-        private void EventDataAsMediumBlob() {
-            RunCommand(
-                "ALTER TABLE `sqc_events`"
-                + " CHANGE COLUMN `event_data` `event_data` MEDIUMBLOB NULL DEFAULT NULL AFTER `event_flags`;"
-            );
-        }
-
-        private void AllowNullTarget() {
-            RunCommand(
-                "ALTER TABLE `sqc_events`"
-                + " CHANGE COLUMN `event_target` `event_target` VARBINARY(255) NULL AFTER `event_type`;"
-            );
-        }
-
-        private void CreateEventsTable() {
-            RunCommand(
-                "CREATE TABLE `sqc_events` ("
-                + "`event_id` BIGINT(20) NOT NULL,"
-                + "`event_sender` BIGINT(20) UNSIGNED NULL DEFAULT NULL,"
-                + "`event_sender_name` VARCHAR(255) NULL DEFAULT NULL,"
-                + "`event_sender_colour` INT(11) NULL DEFAULT NULL,"
-                + "`event_sender_rank` INT(11) NULL DEFAULT NULL,"
-                + "`event_sender_nick` VARCHAR(255) NULL DEFAULT NULL,"
-                + "`event_sender_perms` INT(11) NULL DEFAULT NULL,"
-                + "`event_created` TIMESTAMP NOT NULL DEFAULT current_timestamp(),"
-                + "`event_deleted` TIMESTAMP NULL DEFAULT NULL,"
-                + "`event_type` VARBINARY(255) NOT NULL,"
-                + "`event_target` VARBINARY(255) NOT NULL,"
-                + "`event_flags` TINYINT(3) UNSIGNED NOT NULL,"
-                + "`event_data` BLOB NULL DEFAULT NULL,"
-                + "PRIMARY KEY (`event_id`),"
-                + "INDEX `event_target` (`event_target`),"
-                + "INDEX `event_type` (`event_type`),"
-                + "INDEX `event_sender` (`event_sender`),"
-                + "INDEX `event_datetime` (`event_created`),"
-                + "INDEX `event_deleted` (`event_deleted`)"
-                + ") COLLATE='utf8mb4_unicode_ci' ENGINE=InnoDB;"
             );
         }
     }
+
+    public void RunMigrations() {
+        RunCommand(
+            "CREATE TABLE IF NOT EXISTS `sqc_migrations` ("
+            + "`migration_name` VARCHAR(255) NOT NULL,"
+            + "`migration_completed` TIMESTAMP NOT NULL DEFAULT current_timestamp(),"
+            + "UNIQUE INDEX `migration_name` (`migration_name`),"
+            + "INDEX `migration_completed` (`migration_completed`)"
+            + ") COLLATE='utf8mb4_unicode_ci' ENGINE=InnoDB;"
+        );
+
+        DoMigration("create_events_table", CreateEventsTable);
+        DoMigration("allow_null_target", AllowNullTarget);
+        DoMigration("event_data_as_medium_blob", EventDataAsMediumBlob);
+        DoMigration("event_user_and_nick_name_to_1000", EventUserAndNickNameTo1000);
+    }
+
+    private void EventUserAndNickNameTo1000() {
+        RunCommand(
+            "ALTER TABLE `sqc_events`"
+            + " CHANGE COLUMN `event_sender_name` `event_sender_name` VARCHAR(1000) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_520_ci' AFTER `event_sender`,"
+            + " CHANGE COLUMN `event_sender_nick` `event_sender_nick` VARCHAR(1000) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_520_ci' AFTER `event_sender_rank`;"
+        );
+    }
+
+    private void EventDataAsMediumBlob() {
+        RunCommand(
+            "ALTER TABLE `sqc_events`"
+            + " CHANGE COLUMN `event_data` `event_data` MEDIUMBLOB NULL DEFAULT NULL AFTER `event_flags`;"
+        );
+    }
+
+    private void AllowNullTarget() {
+        RunCommand(
+            "ALTER TABLE `sqc_events`"
+            + " CHANGE COLUMN `event_target` `event_target` VARBINARY(255) NULL AFTER `event_type`;"
+        );
+    }
+
+    private void CreateEventsTable() {
+        RunCommand(
+            "CREATE TABLE `sqc_events` ("
+            + "`event_id` BIGINT(20) NOT NULL,"
+            + "`event_sender` BIGINT(20) UNSIGNED NULL DEFAULT NULL,"
+            + "`event_sender_name` VARCHAR(255) NULL DEFAULT NULL,"
+            + "`event_sender_colour` INT(11) NULL DEFAULT NULL,"
+            + "`event_sender_rank` INT(11) NULL DEFAULT NULL,"
+            + "`event_sender_nick` VARCHAR(255) NULL DEFAULT NULL,"
+            + "`event_sender_perms` INT(11) NULL DEFAULT NULL,"
+            + "`event_created` TIMESTAMP NOT NULL DEFAULT current_timestamp(),"
+            + "`event_deleted` TIMESTAMP NULL DEFAULT NULL,"
+            + "`event_type` VARBINARY(255) NOT NULL,"
+            + "`event_target` VARBINARY(255) NOT NULL,"
+            + "`event_flags` TINYINT(3) UNSIGNED NOT NULL,"
+            + "`event_data` BLOB NULL DEFAULT NULL,"
+            + "PRIMARY KEY (`event_id`),"
+            + "INDEX `event_target` (`event_target`),"
+            + "INDEX `event_type` (`event_type`),"
+            + "INDEX `event_sender` (`event_sender`),"
+            + "INDEX `event_datetime` (`event_created`),"
+            + "INDEX `event_deleted` (`event_deleted`)"
+            + ") COLLATE='utf8mb4_unicode_ci' ENGINE=InnoDB;"
+        );
+    }
 }
diff --git a/SharpChat/EventStorage/StoredEventFlags.cs b/SharpChat/EventStorage/StoredEventFlags.cs
index 351e86c..4cc6b81 100644
--- a/SharpChat/EventStorage/StoredEventFlags.cs
+++ b/SharpChat/EventStorage/StoredEventFlags.cs
@@ -1,10 +1,10 @@
-namespace SharpChat.EventStorage {
-    [Flags]
-    public enum StoredEventFlags {
-        None = 0,
-        Action = 1,
-        Broadcast = 1 << 1,
-        Log = 1 << 2,
-        Private = 1 << 3,
-    }
+namespace SharpChat.EventStorage;
+
+[Flags]
+public enum StoredEventFlags {
+    None = 0,
+    Action = 1,
+    Broadcast = 1 << 1,
+    Log = 1 << 2,
+    Private = 1 << 3,
 }
diff --git a/SharpChat/EventStorage/StoredEventInfo.cs b/SharpChat/EventStorage/StoredEventInfo.cs
index 922a52b..988e0df 100644
--- a/SharpChat/EventStorage/StoredEventInfo.cs
+++ b/SharpChat/EventStorage/StoredEventInfo.cs
@@ -1,23 +1,23 @@
 using System.Text.Json;
 
-namespace SharpChat.EventStorage {
-    public class StoredEventInfo(
-        long id,
-        string type,
-        User? sender,
-        DateTimeOffset created,
-        DateTimeOffset? deleted,
-        string? channelName,
-        JsonDocument data,
-        StoredEventFlags flags
-    ) {
-        public long Id { get; set; } = id;
-        public string Type { get; set; } = type;
-        public User? Sender { get; set; } = sender;
-        public DateTimeOffset Created { get; set; } = created;
-        public DateTimeOffset? Deleted { get; set; } = deleted;
-        public string? ChannelName { get; set; } = channelName;
-        public StoredEventFlags Flags { get; set; } = flags;
-        public JsonDocument Data { get; set; } = data;
-    }
+namespace SharpChat.EventStorage;
+
+public class StoredEventInfo(
+    long id,
+    string type,
+    User? sender,
+    DateTimeOffset created,
+    DateTimeOffset? deleted,
+    string? channelName,
+    JsonDocument data,
+    StoredEventFlags flags
+) {
+    public long Id { get; set; } = id;
+    public string Type { get; set; } = type;
+    public User? Sender { get; set; } = sender;
+    public DateTimeOffset Created { get; set; } = created;
+    public DateTimeOffset? Deleted { get; set; } = deleted;
+    public string? ChannelName { get; set; } = channelName;
+    public StoredEventFlags Flags { get; set; } = flags;
+    public JsonDocument Data { get; set; } = data;
 }
diff --git a/SharpChat/EventStorage/VirtualEventStorage.cs b/SharpChat/EventStorage/VirtualEventStorage.cs
index 72ccc21..7d74bbd 100644
--- a/SharpChat/EventStorage/VirtualEventStorage.cs
+++ b/SharpChat/EventStorage/VirtualEventStorage.cs
@@ -1,65 +1,65 @@
 using System.Text.Json;
 
-namespace SharpChat.EventStorage {
-    public class VirtualEventStorage : EventStorage {
-        private readonly Dictionary<long, StoredEventInfo> Events = [];
+namespace SharpChat.EventStorage;
 
-        public void AddEvent(
-            long id,
-            string type,
-            string channelName,
-            string senderId,
-            string senderName,
-            ColourInheritable senderColour,
-            int senderRank,
-            string senderNick,
-            UserPermissions senderPerms,
-            object? data = null,
-            StoredEventFlags flags = StoredEventFlags.None
-        ) {
-            Events.Add(
+public class VirtualEventStorage : EventStorage {
+    private readonly Dictionary<long, StoredEventInfo> Events = [];
+
+    public void AddEvent(
+        long id,
+        string type,
+        string channelName,
+        string senderId,
+        string senderName,
+        ColourInheritable senderColour,
+        int senderRank,
+        string senderNick,
+        UserPermissions senderPerms,
+        object? data = null,
+        StoredEventFlags flags = StoredEventFlags.None
+    ) {
+        Events.Add(
+            id,
+            new(
                 id,
-                new(
-                    id,
-                    type,
-                    long.TryParse(senderId, out long senderId64) && senderId64 > 0 
-                        ? new User(
-                            senderId,
-                            senderName,
-                            senderColour,
-                            senderRank,
-                            senderPerms,
-                            senderNick
-                        )
-                        : null,
-                    DateTimeOffset.Now,
-                    null,
-                    channelName,
-                    JsonDocument.Parse(data == null ? "{}" : JsonSerializer.Serialize(data)),
-                    flags
-                )
-            );
+                type,
+                long.TryParse(senderId, out long senderId64) && senderId64 > 0 
+                    ? new User(
+                        senderId,
+                        senderName,
+                        senderColour,
+                        senderRank,
+                        senderPerms,
+                        senderNick
+                    )
+                    : null,
+                DateTimeOffset.Now,
+                null,
+                channelName,
+                JsonDocument.Parse(data == null ? "{}" : JsonSerializer.Serialize(data)),
+                flags
+            )
+        );
+    }
+
+    public StoredEventInfo? GetEvent(long seqId) {
+        return Events.TryGetValue(seqId, out StoredEventInfo? evt) ? evt : null;
+    }
+
+    public void RemoveEvent(StoredEventInfo evt) {
+        ArgumentNullException.ThrowIfNull(evt);
+        Events.Remove(evt.Id);
+    }
+
+    public IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0) {
+        IEnumerable<StoredEventInfo> subset = Events.Values.Where(ev => ev.ChannelName == channelName);
+
+        int start = subset.Count() - offset - amount;
+        if(start < 0) {
+            amount += start;
+            start = 0;
         }
 
-        public StoredEventInfo? GetEvent(long seqId) {
-            return Events.TryGetValue(seqId, out StoredEventInfo? evt) ? evt : null;
-        }
-
-        public void RemoveEvent(StoredEventInfo evt) {
-            ArgumentNullException.ThrowIfNull(evt);
-            Events.Remove(evt.Id);
-        }
-
-        public IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0) {
-            IEnumerable<StoredEventInfo> subset = Events.Values.Where(ev => ev.ChannelName == channelName);
-
-            int start = subset.Count() - offset - amount;
-            if(start < 0) {
-                amount += start;
-                start = 0;
-            }
-
-            return [.. subset.Skip(start).Take(amount)];
-        }
+        return [.. subset.Skip(start).Take(amount)];
     }
 }
diff --git a/SharpChat/Events/ChatEvent.cs b/SharpChat/Events/ChatEvent.cs
index 768b185..a3f0149 100644
--- a/SharpChat/Events/ChatEvent.cs
+++ b/SharpChat/Events/ChatEvent.cs
@@ -1,3 +1,3 @@
-namespace SharpChat.Events {
-    public interface ChatEvent {}
-}
+namespace SharpChat.Events;
+
+public interface ChatEvent {}
diff --git a/SharpChat/Events/MessageCreateEvent.cs b/SharpChat/Events/MessageCreateEvent.cs
index 22bae88..883726c 100644
--- a/SharpChat/Events/MessageCreateEvent.cs
+++ b/SharpChat/Events/MessageCreateEvent.cs
@@ -1,31 +1,31 @@
-namespace SharpChat.Events {
-    public class MessageCreateEvent(
-        long msgId,
-        string channelName,
-        string senderId,
-        string senderName,
-        ColourInheritable senderColour,
-        int senderRank,
-        string senderNickName,
-        UserPermissions senderPerms,
-        DateTimeOffset msgCreated,
-        string msgText,
-        bool isPrivate,
-        bool isAction,
-        bool isBroadcast
-    ) : ChatEvent {
-        public long MessageId { get; } = msgId;
-        public string ChannelName { get; } = channelName;
-        public string SenderId { get; } = senderId;
-        public string SenderName { get; } = senderName;
-        public ColourInheritable SenderColour { get; } = senderColour;
-        public int SenderRank { get; } = senderRank;
-        public string SenderNickName { get; } = senderNickName;
-        public UserPermissions SenderPerms { get; } = senderPerms;
-        public DateTimeOffset MessageCreated { get; } = msgCreated;
-        public string MessageText { get; } = msgText;
-        public bool IsPrivate { get; } = isPrivate;
-        public bool IsAction { get; } = isAction;
-        public bool IsBroadcast { get; } = isBroadcast;
-    }
+namespace SharpChat.Events;
+
+public class MessageCreateEvent(
+    long msgId,
+    string channelName,
+    string senderId,
+    string senderName,
+    ColourInheritable senderColour,
+    int senderRank,
+    string senderNickName,
+    UserPermissions senderPerms,
+    DateTimeOffset msgCreated,
+    string msgText,
+    bool isPrivate,
+    bool isAction,
+    bool isBroadcast
+) : ChatEvent {
+    public long MessageId { get; } = msgId;
+    public string ChannelName { get; } = channelName;
+    public string SenderId { get; } = senderId;
+    public string SenderName { get; } = senderName;
+    public ColourInheritable SenderColour { get; } = senderColour;
+    public int SenderRank { get; } = senderRank;
+    public string SenderNickName { get; } = senderNickName;
+    public UserPermissions SenderPerms { get; } = senderPerms;
+    public DateTimeOffset MessageCreated { get; } = msgCreated;
+    public string MessageText { get; } = msgText;
+    public bool IsPrivate { get; } = isPrivate;
+    public bool IsAction { get; } = isAction;
+    public bool IsBroadcast { get; } = isBroadcast;
 }
diff --git a/SharpChat/Program.cs b/SharpChat/Program.cs
index fa8fe78..7ec67cb 100644
--- a/SharpChat/Program.cs
+++ b/SharpChat/Program.cs
@@ -3,145 +3,145 @@ using SharpChat.EventStorage;
 using SharpChat.Flashii;
 using System.Text;
 
-namespace SharpChat {
-    public class Program {
-        public const string CONFIG = "sharpchat.cfg";
+namespace SharpChat;
 
-        public static void Main() {
-            Console.WriteLine(@"   _____ __                     ________          __ ");
-            Console.WriteLine(@"  / ___// /_  ____ __________  / ____/ /_  ____ _/ /_");
-            Console.WriteLine(@"  \__ \/ __ \/ __ `/ ___/ __ \/ /   / __ \/ __ `/ __/");
-            Console.WriteLine(@" ___/ / / / / /_/ / /  / /_/ / /___/ / / / /_/ / /_  ");
-            Console.WriteLine(@"/____/_/ /_/\__,_/_/  / .___/\____/_/ /_/\__,_/\__/  ");
-            /**/Console.Write(@"                     /__/");
-            if(SharpInfo.IsDebugBuild) {
-                Console.WriteLine();
-                Console.Write(@"== ");
-                Console.Write(SharpInfo.VersionString);
-                Console.WriteLine(@" == DBG ==");
-            } else
-                Console.WriteLine(SharpInfo.VersionStringShort.PadLeft(28, ' '));
+public class Program {
+    public const string CONFIG = "sharpchat.cfg";
 
-            using ManualResetEvent mre = new(false);
-            bool hasCancelled = false;
+    public static void Main() {
+        Console.WriteLine(@"   _____ __                     ________          __ ");
+        Console.WriteLine(@"  / ___// /_  ____ __________  / ____/ /_  ____ _/ /_");
+        Console.WriteLine(@"  \__ \/ __ \/ __ `/ ___/ __ \/ /   / __ \/ __ `/ __/");
+        Console.WriteLine(@" ___/ / / / / /_/ / /  / /_/ / /___/ / / / /_/ / /_  ");
+        Console.WriteLine(@"/____/_/ /_/\__,_/_/  / .___/\____/_/ /_/\__,_/\__/  ");
+        /**/Console.Write(@"                     /__/");
+        if(SharpInfo.IsDebugBuild) {
+            Console.WriteLine();
+            Console.Write(@"== ");
+            Console.Write(SharpInfo.VersionString);
+            Console.WriteLine(@" == DBG ==");
+        } else
+            Console.WriteLine(SharpInfo.VersionStringShort.PadLeft(28, ' '));
 
-            void cancelKeyPressHandler(object? sender, ConsoleCancelEventArgs ev) {
-                Console.CancelKeyPress -= cancelKeyPressHandler;
-                hasCancelled = true;
-                ev.Cancel = true;
-                mre.Set();
-            };
-            Console.CancelKeyPress += cancelKeyPressHandler;
+        using ManualResetEvent mre = new(false);
+        bool hasCancelled = false;
 
-            if(hasCancelled) return;
+        void cancelKeyPressHandler(object? sender, ConsoleCancelEventArgs ev) {
+            Console.CancelKeyPress -= cancelKeyPressHandler;
+            hasCancelled = true;
+            ev.Cancel = true;
+            mre.Set();
+        };
+        Console.CancelKeyPress += cancelKeyPressHandler;
 
-            string configFile = CONFIG;
+        if(hasCancelled) return;
 
-            // If the config file doesn't exist and we're using the default path, run the converter
-            if(!File.Exists(configFile) && configFile == CONFIG)
-                ConvertConfiguration();
+        string configFile = CONFIG;
 
-            using StreamConfig config = StreamConfig.FromPath(configFile);
+        // If the config file doesn't exist and we're using the default path, run the converter
+        if(!File.Exists(configFile) && configFile == CONFIG)
+            ConvertConfiguration();
 
-            if(hasCancelled) return;
+        using StreamConfig config = StreamConfig.FromPath(configFile);
 
-            using HttpClient httpClient = new(new HttpClientHandler() {
-                UseProxy = false,
-            });
-            httpClient.DefaultRequestHeaders.Add("User-Agent", SharpInfo.ProgramName);
+        if(hasCancelled) return;
 
-            if(hasCancelled) return;
+        using HttpClient httpClient = new(new HttpClientHandler() {
+            UseProxy = false,
+        });
+        httpClient.DefaultRequestHeaders.Add("User-Agent", SharpInfo.ProgramName);
 
-            FlashiiClient flashii = new(httpClient, config.ScopeTo("msz"));
+        if(hasCancelled) return;
 
-            if(hasCancelled) return;
+        FlashiiClient flashii = new(httpClient, config.ScopeTo("msz"));
 
-            EventStorage.EventStorage evtStore;
-            if(string.IsNullOrWhiteSpace(config.SafeReadValue("mariadb:host", string.Empty))) {
-                evtStore = new VirtualEventStorage();
-            } else {
-                MariaDBEventStorage mdbes = new(MariaDBEventStorage.BuildConnString(config.ScopeTo("mariadb")));
-                evtStore = mdbes;
-                mdbes.RunMigrations();
-            }
+        if(hasCancelled) return;
 
-            if(hasCancelled) return;
-
-            using SockChatServer scs = new(flashii, flashii, evtStore, config.ScopeTo("chat"));
-            scs.Listen(mre);
-
-            mre.WaitOne();
+        EventStorage.EventStorage evtStore;
+        if(string.IsNullOrWhiteSpace(config.SafeReadValue("mariadb:host", string.Empty))) {
+            evtStore = new VirtualEventStorage();
+        } else {
+            MariaDBEventStorage mdbes = new(MariaDBEventStorage.BuildConnString(config.ScopeTo("mariadb")));
+            evtStore = mdbes;
+            mdbes.RunMigrations();
         }
 
-        private static void ConvertConfiguration() {
-            using Stream s = new FileStream(CONFIG, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
-            s.SetLength(0);
-            s.Flush();
+        if(hasCancelled) return;
 
-            using StreamWriter sw = new(s, new UTF8Encoding(false));
-            sw.WriteLine("# and ; can be used at the start of a line for comments.");
-            sw.WriteLine();
+        using SockChatServer scs = new(flashii, flashii, evtStore, config.ScopeTo("chat"));
+        scs.Listen(mre);
 
-            sw.WriteLine("# General Configuration");
-            sw.WriteLine($"#chat:port            {SockChatServer.DEFAULT_PORT}");
-            sw.WriteLine($"#chat:msgMaxLength    {SockChatServer.DEFAULT_MSG_LENGTH_MAX}");
-            sw.WriteLine($"#chat:connMaxCount    {SockChatServer.DEFAULT_MAX_CONNECTIONS}");
-            sw.WriteLine($"#chat:floodKickLength {SockChatServer.DEFAULT_FLOOD_KICK_LENGTH}");
+        mre.WaitOne();
+    }
+
+    private static void ConvertConfiguration() {
+        using Stream s = new FileStream(CONFIG, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
+        s.SetLength(0);
+        s.Flush();
+
+        using StreamWriter sw = new(s, new UTF8Encoding(false));
+        sw.WriteLine("# and ; can be used at the start of a line for comments.");
+        sw.WriteLine();
+
+        sw.WriteLine("# General Configuration");
+        sw.WriteLine($"#chat:port            {SockChatServer.DEFAULT_PORT}");
+        sw.WriteLine($"#chat:msgMaxLength    {SockChatServer.DEFAULT_MSG_LENGTH_MAX}");
+        sw.WriteLine($"#chat:connMaxCount    {SockChatServer.DEFAULT_MAX_CONNECTIONS}");
+        sw.WriteLine($"#chat:floodKickLength {SockChatServer.DEFAULT_FLOOD_KICK_LENGTH}");
 
 
-            sw.WriteLine();
-            sw.WriteLine("# Channels");
-            sw.WriteLine("chat:channels lounge staff");
-            sw.WriteLine();
+        sw.WriteLine();
+        sw.WriteLine("# Channels");
+        sw.WriteLine("chat:channels lounge staff");
+        sw.WriteLine();
 
-            sw.WriteLine("# Lounge channel settings");
-            sw.WriteLine("chat:channels:lounge:name Lounge");
-            sw.WriteLine("chat:channels:lounge:autoJoin true");
-            sw.WriteLine();
+        sw.WriteLine("# Lounge channel settings");
+        sw.WriteLine("chat:channels:lounge:name Lounge");
+        sw.WriteLine("chat:channels:lounge:autoJoin true");
+        sw.WriteLine();
 
-            sw.WriteLine("# Staff channel settings");
-            sw.WriteLine("chat:channels:staff:name Staff");
-            sw.WriteLine("chat:channels:staff:minRank 5");
+        sw.WriteLine("# Staff channel settings");
+        sw.WriteLine("chat:channels:staff:name Staff");
+        sw.WriteLine("chat:channels:staff:minRank 5");
 
 
-            const string msz_secret = "login_key.txt";
-            const string msz_url = "msz_url.txt";
+        const string msz_secret = "login_key.txt";
+        const string msz_url = "msz_url.txt";
 
-            sw.WriteLine();
-            sw.WriteLine("# Misuzu integration settings");
-            if(File.Exists(msz_secret))
-                sw.WriteLine(string.Format("msz:secret {0}", File.ReadAllText(msz_secret).Trim()));
-            else
-                sw.WriteLine("#msz:secret woomy");
-            if(File.Exists(msz_url))
-                sw.WriteLine(string.Format("msz:url    {0}/_sockchat", File.ReadAllText(msz_url).Trim()));
-            else
-                sw.WriteLine("#msz:url    https://flashii.net/_sockchat");
+        sw.WriteLine();
+        sw.WriteLine("# Misuzu integration settings");
+        if(File.Exists(msz_secret))
+            sw.WriteLine(string.Format("msz:secret {0}", File.ReadAllText(msz_secret).Trim()));
+        else
+            sw.WriteLine("#msz:secret woomy");
+        if(File.Exists(msz_url))
+            sw.WriteLine(string.Format("msz:url    {0}/_sockchat", File.ReadAllText(msz_url).Trim()));
+        else
+            sw.WriteLine("#msz:url    https://flashii.net/_sockchat");
 
 
-            const string mdb_config = @"mariadb.txt";
-            string[] mdbCfg = File.Exists(mdb_config) ? File.ReadAllLines(mdb_config) : [];
+        const string mdb_config = @"mariadb.txt";
+        string[] mdbCfg = File.Exists(mdb_config) ? File.ReadAllLines(mdb_config) : [];
 
-            sw.WriteLine();
-            sw.WriteLine("# MariaDB configuration");
-            if(mdbCfg.Length > 0)
-                sw.WriteLine($"mariadb:host {mdbCfg[0]}");
-            else
-                sw.WriteLine($"#mariadb:host <username>");
-            if(mdbCfg.Length > 1)
-                sw.WriteLine($"mariadb:user {mdbCfg[1]}");
-            else
-                sw.WriteLine($"#mariadb:user <username>");
-            if(mdbCfg.Length > 2)
-                sw.WriteLine($"mariadb:pass {mdbCfg[2]}");
-            else
-                sw.WriteLine($"#mariadb:pass <password>");
-            if(mdbCfg.Length > 3)
-                sw.WriteLine($"mariadb:db   {mdbCfg[3]}");
-            else
-                sw.WriteLine($"#mariadb:db   <database>");
+        sw.WriteLine();
+        sw.WriteLine("# MariaDB configuration");
+        if(mdbCfg.Length > 0)
+            sw.WriteLine($"mariadb:host {mdbCfg[0]}");
+        else
+            sw.WriteLine($"#mariadb:host <username>");
+        if(mdbCfg.Length > 1)
+            sw.WriteLine($"mariadb:user {mdbCfg[1]}");
+        else
+            sw.WriteLine($"#mariadb:user <username>");
+        if(mdbCfg.Length > 2)
+            sw.WriteLine($"mariadb:pass {mdbCfg[2]}");
+        else
+            sw.WriteLine($"#mariadb:pass <password>");
+        if(mdbCfg.Length > 3)
+            sw.WriteLine($"mariadb:db   {mdbCfg[3]}");
+        else
+            sw.WriteLine($"#mariadb:db   <database>");
 
-            sw.Flush();
-        }
+        sw.Flush();
     }
 }
diff --git a/SharpChat/SharpChatWebSocketServer.cs b/SharpChat/SharpChatWebSocketServer.cs
index 423ecc8..701fc34 100644
--- a/SharpChat/SharpChatWebSocketServer.cs
+++ b/SharpChat/SharpChatWebSocketServer.cs
@@ -12,156 +12,156 @@ using System.Text;
 // Fleck's Socket wrapper doesn't provide any way to do this with the normally provided APIs
 // https://github.com/statianzo/Fleck/blob/1.1.0/src/Fleck/WebSocketServer.cs
 
-namespace SharpChat {
-    public class SharpChatWebSocketServer : IWebSocketServer {
+namespace SharpChat;
 
-        private readonly string _scheme;
-        private readonly IPAddress _locationIP;
-        private Action<IWebSocketConnection> _config;
+public class SharpChatWebSocketServer : IWebSocketServer {
 
-        public SharpChatWebSocketServer(string location, bool supportDualStack = true) {
-            Uri uri = new(location);
+    private readonly string _scheme;
+    private readonly IPAddress _locationIP;
+    private Action<IWebSocketConnection> _config;
 
-            Port = uri.Port;
-            Location = location;
-            SupportDualStack = supportDualStack;
+    public SharpChatWebSocketServer(string location, bool supportDualStack = true) {
+        Uri uri = new(location);
 
-            _locationIP = ParseIPAddress(uri);
-            _scheme = uri.Scheme;
-            Socket socket = new(_locationIP.AddressFamily, SocketType.Stream, ProtocolType.IP);
-            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
+        Port = uri.Port;
+        Location = location;
+        SupportDualStack = supportDualStack;
 
-            if(SupportDualStack && Type.GetType("Mono.Runtime") == null && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
-                socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
-            }
+        _locationIP = ParseIPAddress(uri);
+        _scheme = uri.Scheme;
+        Socket socket = new(_locationIP.AddressFamily, SocketType.Stream, ProtocolType.IP);
+        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
 
-            ListenerSocket = new SocketWrapper(socket);
-            SupportedSubProtocols = [];
+        if(SupportDualStack && Type.GetType("Mono.Runtime") == null && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
+            socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
         }
 
-        public ISocket ListenerSocket { get; set; }
-        public string Location { get; private set; }
-        public bool SupportDualStack { get; }
-        public int Port { get; private set; }
-        public X509Certificate2 Certificate { get; set; }
-        public SslProtocols EnabledSslProtocols { get; set; }
-        public IEnumerable<string> SupportedSubProtocols { get; set; }
-        public bool RestartAfterListenError { get; set; }
+        ListenerSocket = new SocketWrapper(socket);
+        SupportedSubProtocols = [];
+    }
 
-        public bool IsSecure {
-            get { return _scheme == "wss" && Certificate != null; }
-        }
+    public ISocket ListenerSocket { get; set; }
+    public string Location { get; private set; }
+    public bool SupportDualStack { get; }
+    public int Port { get; private set; }
+    public X509Certificate2 Certificate { get; set; }
+    public SslProtocols EnabledSslProtocols { get; set; }
+    public IEnumerable<string> SupportedSubProtocols { get; set; }
+    public bool RestartAfterListenError { get; set; }
 
-        public void Dispose() {
-            ListenerSocket.Dispose();
-            GC.SuppressFinalize(this);
-        }
+    public bool IsSecure {
+        get { return _scheme == "wss" && Certificate != null; }
+    }
 
-        private static IPAddress ParseIPAddress(Uri uri) {
-            string ipStr = uri.Host;
+    public void Dispose() {
+        ListenerSocket.Dispose();
+        GC.SuppressFinalize(this);
+    }
 
-            if(ipStr == "0.0.0.0") {
-                return IPAddress.Any;
-            } else if(ipStr == "[0000:0000:0000:0000:0000:0000:0000:0000]") {
-                return IPAddress.IPv6Any;
-            } else {
-                try {
-                    return IPAddress.Parse(ipStr);
-                } catch(Exception ex) {
-                    throw new FormatException("Failed to parse the IP address part of the location. Please make sure you specify a valid IP address. Use 0.0.0.0 or [::] to listen on all interfaces.", ex);
-                }
-            }
-        }
+    private static IPAddress ParseIPAddress(Uri uri) {
+        string ipStr = uri.Host;
 
-        public void Start(Action<IWebSocketConnection> config) {
-            IPEndPoint ipLocal = new(_locationIP, Port);
-            ListenerSocket.Bind(ipLocal);
-            ListenerSocket.Listen(100);
-            Port = ((IPEndPoint)ListenerSocket.LocalEndPoint).Port;
-            FleckLog.Info(string.Format("Server started at {0} (actual port {1})", Location, Port));
-            if(_scheme == "wss") {
-                if(Certificate == null) {
-                    FleckLog.Error("Scheme cannot be 'wss' without a Certificate");
-                    return;
-                }
-
-                // makes dotnet shut up, TLS is handled by NGINX anyway
-                // if(EnabledSslProtocols == SslProtocols.None) {
-                //     EnabledSslProtocols = SslProtocols.Tls;
-                //     FleckLog.Debug("Using default TLS 1.0 security protocol.");
-                // }
-            }
-            ListenForClients();
-            _config = config;
-        }
-
-        private void ListenForClients() {
-            ListenerSocket.Accept(OnClientConnect, e => {
-                FleckLog.Error("Listener socket is closed", e);
-                if(RestartAfterListenError) {
-                    FleckLog.Info("Listener socket restarting");
-                    try {
-                        ListenerSocket.Dispose();
-                        Socket socket = new(_locationIP.AddressFamily, SocketType.Stream, ProtocolType.IP);
-                        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
-                        ListenerSocket = new SocketWrapper(socket);
-                        Start(_config);
-                        FleckLog.Info("Listener socket restarted");
-                    } catch(Exception ex) {
-                        FleckLog.Error("Listener could not be restarted", ex);
-                    }
-                }
-            });
-        }
-
-        private void OnClientConnect(ISocket clientSocket) {
-            if(clientSocket == null) return; // socket closed
-
-            FleckLog.Debug(string.Format("Client connected from {0}:{1}", clientSocket.RemoteIpAddress, clientSocket.RemotePort.ToString()));
-            ListenForClients();
-
-            WebSocketConnection connection = null;
-
-            connection = new WebSocketConnection(
-                clientSocket,
-                _config,
-                bytes => RequestParser.Parse(bytes, _scheme),
-                r => {
-                    try {
-                        return HandlerFactory.BuildHandler(
-                            r, s => connection.OnMessage(s), connection.Close, b => connection.OnBinary(b),
-                            b => connection.OnPing(b), b => connection.OnPong(b)
-                        );
-                    } catch(WebSocketException) {
-                        const string responseMsg = "HTTP/1.1 200 OK\r\n"
-                                                 + "Date: {0}\r\n"
-                                                 + "Server: SharpChat\r\n"
-                                                 + "Content-Length: {1}\r\n"
-                                                 + "Content-Type: text/html; charset=utf-8\r\n"
-                                                 + "Connection: close\r\n"
-                                                 + "\r\n"
-                                                 + "{2}";
-                        string responseBody = File.Exists("http-motd.txt") ? File.ReadAllText("http-motd.txt") : "SharpChat";
-
-                        clientSocket.Stream.Write(Encoding.UTF8.GetBytes(string.Format(
-                            responseMsg, DateTimeOffset.Now.ToString("r"), Encoding.UTF8.GetByteCount(responseBody), responseBody
-                        )));
-                        clientSocket.Close();
-                        return null;
-                    }
-                },
-                s => SubProtocolNegotiator.Negotiate(SupportedSubProtocols, s));
-
-            if(IsSecure) {
-                FleckLog.Debug("Authenticating Secure Connection");
-                clientSocket
-                    .Authenticate(Certificate,
-                                  EnabledSslProtocols,
-                                  connection.StartReceiving,
-                                  e => FleckLog.Warn("Failed to Authenticate", e));
-            } else {
-                connection.StartReceiving();
+        if(ipStr == "0.0.0.0") {
+            return IPAddress.Any;
+        } else if(ipStr == "[0000:0000:0000:0000:0000:0000:0000:0000]") {
+            return IPAddress.IPv6Any;
+        } else {
+            try {
+                return IPAddress.Parse(ipStr);
+            } catch(Exception ex) {
+                throw new FormatException("Failed to parse the IP address part of the location. Please make sure you specify a valid IP address. Use 0.0.0.0 or [::] to listen on all interfaces.", ex);
             }
         }
     }
+
+    public void Start(Action<IWebSocketConnection> config) {
+        IPEndPoint ipLocal = new(_locationIP, Port);
+        ListenerSocket.Bind(ipLocal);
+        ListenerSocket.Listen(100);
+        Port = ((IPEndPoint)ListenerSocket.LocalEndPoint).Port;
+        FleckLog.Info(string.Format("Server started at {0} (actual port {1})", Location, Port));
+        if(_scheme == "wss") {
+            if(Certificate == null) {
+                FleckLog.Error("Scheme cannot be 'wss' without a Certificate");
+                return;
+            }
+
+            // makes dotnet shut up, TLS is handled by NGINX anyway
+            // if(EnabledSslProtocols == SslProtocols.None) {
+            //     EnabledSslProtocols = SslProtocols.Tls;
+            //     FleckLog.Debug("Using default TLS 1.0 security protocol.");
+            // }
+        }
+        ListenForClients();
+        _config = config;
+    }
+
+    private void ListenForClients() {
+        ListenerSocket.Accept(OnClientConnect, e => {
+            FleckLog.Error("Listener socket is closed", e);
+            if(RestartAfterListenError) {
+                FleckLog.Info("Listener socket restarting");
+                try {
+                    ListenerSocket.Dispose();
+                    Socket socket = new(_locationIP.AddressFamily, SocketType.Stream, ProtocolType.IP);
+                    socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
+                    ListenerSocket = new SocketWrapper(socket);
+                    Start(_config);
+                    FleckLog.Info("Listener socket restarted");
+                } catch(Exception ex) {
+                    FleckLog.Error("Listener could not be restarted", ex);
+                }
+            }
+        });
+    }
+
+    private void OnClientConnect(ISocket clientSocket) {
+        if(clientSocket == null) return; // socket closed
+
+        FleckLog.Debug(string.Format("Client connected from {0}:{1}", clientSocket.RemoteIpAddress, clientSocket.RemotePort.ToString()));
+        ListenForClients();
+
+        WebSocketConnection connection = null;
+
+        connection = new WebSocketConnection(
+            clientSocket,
+            _config,
+            bytes => RequestParser.Parse(bytes, _scheme),
+            r => {
+                try {
+                    return HandlerFactory.BuildHandler(
+                        r, s => connection.OnMessage(s), connection.Close, b => connection.OnBinary(b),
+                        b => connection.OnPing(b), b => connection.OnPong(b)
+                    );
+                } catch(WebSocketException) {
+                    const string responseMsg = "HTTP/1.1 200 OK\r\n"
+                                             + "Date: {0}\r\n"
+                                             + "Server: SharpChat\r\n"
+                                             + "Content-Length: {1}\r\n"
+                                             + "Content-Type: text/html; charset=utf-8\r\n"
+                                             + "Connection: close\r\n"
+                                             + "\r\n"
+                                             + "{2}";
+                    string responseBody = File.Exists("http-motd.txt") ? File.ReadAllText("http-motd.txt") : "SharpChat";
+
+                    clientSocket.Stream.Write(Encoding.UTF8.GetBytes(string.Format(
+                        responseMsg, DateTimeOffset.Now.ToString("r"), Encoding.UTF8.GetByteCount(responseBody), responseBody
+                    )));
+                    clientSocket.Close();
+                    return null;
+                }
+            },
+            s => SubProtocolNegotiator.Negotiate(SupportedSubProtocols, s));
+
+        if(IsSecure) {
+            FleckLog.Debug("Authenticating Secure Connection");
+            clientSocket
+                .Authenticate(Certificate,
+                              EnabledSslProtocols,
+                              connection.StartReceiving,
+                              e => FleckLog.Warn("Failed to Authenticate", e));
+        } else {
+            connection.StartReceiving();
+        }
+    }
 }
diff --git a/SharpChat/SockChat/S2CPackets/ContextMessageS2CPacket.cs b/SharpChat/SockChat/S2CPackets/ContextMessageS2CPacket.cs
index 1b8172f..95982fb 100644
--- a/SharpChat/SockChat/S2CPackets/ContextMessageS2CPacket.cs
+++ b/SharpChat/SockChat/S2CPackets/ContextMessageS2CPacket.cs
@@ -1,115 +1,115 @@
 using SharpChat.EventStorage;
 using System.Text;
 
-namespace SharpChat.SockChat.S2CPackets {
-    public class ContextMessageS2CPacket(StoredEventInfo evt, bool notify = false) : S2CPacket {
-        public StoredEventInfo Event { get; private set; } = evt ?? throw new ArgumentNullException(nameof(evt));
+namespace SharpChat.SockChat.S2CPackets;
 
-        public string Pack() {
-            bool isAction = Event.Flags.HasFlag(StoredEventFlags.Action);
-            bool isBroadcast = Event.Flags.HasFlag(StoredEventFlags.Broadcast);
-            bool isPrivate = Event.Flags.HasFlag(StoredEventFlags.Private);
+public class ContextMessageS2CPacket(StoredEventInfo evt, bool notify = false) : S2CPacket {
+    public StoredEventInfo Event { get; private set; } = evt ?? throw new ArgumentNullException(nameof(evt));
 
-            StringBuilder sb = new();
+    public string Pack() {
+        bool isAction = Event.Flags.HasFlag(StoredEventFlags.Action);
+        bool isBroadcast = Event.Flags.HasFlag(StoredEventFlags.Broadcast);
+        bool isPrivate = Event.Flags.HasFlag(StoredEventFlags.Private);
 
-            sb.Append("7\t1\t");
-            sb.Append(Event.Created.ToUnixTimeSeconds());
-            sb.Append('\t');
+        StringBuilder sb = new();
 
-            switch(Event.Type) {
-                case "msg:add":
-                case "SharpChat.Events.ChatMessage":
-                    if(isBroadcast || Event.Sender is null) {
-                        sb.Append("-1\tChatBot\tinherit\t\t0\fsay\f");
-                    } else {
-                        sb.Append(Event.Sender.UserId);
-                        sb.Append('\t');
-                        sb.Append(Event.Sender.LegacyNameWithStatus);
-                        sb.Append('\t');
-                        sb.Append(Event.Sender.Colour);
-                        sb.Append('\t');
-                        sb.Append(Event.Sender.Rank);
-                        sb.Append(' ');
-                        sb.Append(Event.Sender.Permissions.HasFlag(UserPermissions.KickUser) ? '1' : '0');
-                        sb.Append(' ');
-                        sb.Append(Event.Sender.Permissions.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
-                        sb.Append(' ');
-                        sb.Append(Event.Sender.Permissions.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
-                        sb.Append(' ');
-                        sb.Append(Event.Sender.Permissions.HasFlag(UserPermissions.CreateChannel) ? (Event.Sender.Permissions.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
-                        sb.Append('\t');
-                    }
+        sb.Append("7\t1\t");
+        sb.Append(Event.Created.ToUnixTimeSeconds());
+        sb.Append('\t');
 
-                    if(isAction)
-                        sb.Append("<i>");
+        switch(Event.Type) {
+            case "msg:add":
+            case "SharpChat.Events.ChatMessage":
+                if(isBroadcast || Event.Sender is null) {
+                    sb.Append("-1\tChatBot\tinherit\t\t0\fsay\f");
+                } else {
+                    sb.Append(Event.Sender.UserId);
+                    sb.Append('\t');
+                    sb.Append(Event.Sender.LegacyNameWithStatus);
+                    sb.Append('\t');
+                    sb.Append(Event.Sender.Colour);
+                    sb.Append('\t');
+                    sb.Append(Event.Sender.Rank);
+                    sb.Append(' ');
+                    sb.Append(Event.Sender.Permissions.HasFlag(UserPermissions.KickUser) ? '1' : '0');
+                    sb.Append(' ');
+                    sb.Append(Event.Sender.Permissions.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
+                    sb.Append(' ');
+                    sb.Append(Event.Sender.Permissions.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
+                    sb.Append(' ');
+                    sb.Append(Event.Sender.Permissions.HasFlag(UserPermissions.CreateChannel) ? (Event.Sender.Permissions.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
+                    sb.Append('\t');
+                }
 
-                    sb.Append(
-                        (Event.Data.RootElement.GetProperty("text").GetString()?
-                            .Replace("<", "&lt;")
-                            .Replace(">", "&gt;")
-                            .Replace("\n", " <br/> ")
-                            .Replace("\t", "    ")) ?? string.Empty
-                    );
+                if(isAction)
+                    sb.Append("<i>");
 
-                    if(isAction)
-                        sb.Append("</i>");
-                    break;
+                sb.Append(
+                    (Event.Data.RootElement.GetProperty("text").GetString()?
+                        .Replace("<", "&lt;")
+                        .Replace(">", "&gt;")
+                        .Replace("\n", " <br/> ")
+                        .Replace("\t", "    ")) ?? string.Empty
+                );
 
-                case "user:connect":
-                case "SharpChat.Events.UserConnectEvent":
-                    sb.Append("-1\tChatBot\tinherit\t\t0\fjoin\f");
-                    sb.Append(Event.Sender?.LegacyName ?? "?????");
-                    break;
+                if(isAction)
+                    sb.Append("</i>");
+                break;
 
-                case "chan:join":
-                case "SharpChat.Events.UserChannelJoinEvent":
-                    sb.Append("-1\tChatBot\tinherit\t\t0\fjchan\f");
-                    sb.Append(Event.Sender?.LegacyName ?? "?????");
-                    break;
+            case "user:connect":
+            case "SharpChat.Events.UserConnectEvent":
+                sb.Append("-1\tChatBot\tinherit\t\t0\fjoin\f");
+                sb.Append(Event.Sender?.LegacyName ?? "?????");
+                break;
 
-                case "chan:leave":
-                case "SharpChat.Events.UserChannelLeaveEvent":
-                    sb.Append("-1\tChatBot\tinherit\t\t0\flchan\f");
-                    sb.Append(Event.Sender?.LegacyName ?? "?????");
-                    break;
+            case "chan:join":
+            case "SharpChat.Events.UserChannelJoinEvent":
+                sb.Append("-1\tChatBot\tinherit\t\t0\fjchan\f");
+                sb.Append(Event.Sender?.LegacyName ?? "?????");
+                break;
 
-                case "user:disconnect":
-                case "SharpChat.Events.UserDisconnectEvent":
-                    sb.Append("-1\tChatBot\tinherit\t\t0\f");
+            case "chan:leave":
+            case "SharpChat.Events.UserChannelLeaveEvent":
+                sb.Append("-1\tChatBot\tinherit\t\t0\flchan\f");
+                sb.Append(Event.Sender?.LegacyName ?? "?????");
+                break;
 
-                    switch((UserDisconnectS2CPacket.Reason)Event.Data.RootElement.GetProperty("reason").GetByte()) {
-                        case UserDisconnectS2CPacket.Reason.Flood:
-                            sb.Append("flood");
-                            break;
-                        case UserDisconnectS2CPacket.Reason.Kicked:
-                            sb.Append("kick");
-                            break;
-                        case UserDisconnectS2CPacket.Reason.TimeOut:
-                            sb.Append("timeout");
-                            break;
-                        case UserDisconnectS2CPacket.Reason.Leave:
-                        default:
-                            sb.Append("leave");
-                            break;
-                    }
+            case "user:disconnect":
+            case "SharpChat.Events.UserDisconnectEvent":
+                sb.Append("-1\tChatBot\tinherit\t\t0\f");
 
-                    sb.Append('\f');
-                    sb.Append(Event.Sender?.LegacyName ?? "?????");
-                    break;
-            }
+                switch((UserDisconnectS2CPacket.Reason)Event.Data.RootElement.GetProperty("reason").GetByte()) {
+                    case UserDisconnectS2CPacket.Reason.Flood:
+                        sb.Append("flood");
+                        break;
+                    case UserDisconnectS2CPacket.Reason.Kicked:
+                        sb.Append("kick");
+                        break;
+                    case UserDisconnectS2CPacket.Reason.TimeOut:
+                        sb.Append("timeout");
+                        break;
+                    case UserDisconnectS2CPacket.Reason.Leave:
+                    default:
+                        sb.Append("leave");
+                        break;
+                }
 
-            sb.Append('\t');
-            sb.Append(Event.Id);
-            sb.Append('\t');
-            sb.Append(notify ? '1' : '0');
-            sb.AppendFormat(
-                "\t1{0}0{1}{2}",
-                isAction ? '1' : '0',
-                isAction ? '0' : '1',
-                isPrivate ? '1' : '0'
-            );
-
-            return sb.ToString();
+                sb.Append('\f');
+                sb.Append(Event.Sender?.LegacyName ?? "?????");
+                break;
         }
+
+        sb.Append('\t');
+        sb.Append(Event.Id);
+        sb.Append('\t');
+        sb.Append(notify ? '1' : '0');
+        sb.AppendFormat(
+            "\t1{0}0{1}{2}",
+            isAction ? '1' : '0',
+            isAction ? '0' : '1',
+            isPrivate ? '1' : '0'
+        );
+
+        return sb.ToString();
     }
 }
diff --git a/SharpChat/SockChatServer.cs b/SharpChat/SockChatServer.cs
index c823cb7..f4bebf8 100644
--- a/SharpChat/SockChatServer.cs
+++ b/SharpChat/SockChatServer.cs
@@ -7,234 +7,234 @@ using SharpChat.Configuration;
 using SharpChat.SockChat.S2CPackets;
 using System.Net;
 
-namespace SharpChat {
-    public class SockChatServer : IDisposable {
-        public const ushort DEFAULT_PORT = 6770;
-        public const int DEFAULT_MSG_LENGTH_MAX = 5000;
-        public const int DEFAULT_MAX_CONNECTIONS = 5;
-        public const int DEFAULT_FLOOD_KICK_LENGTH = 30;
-        public const int DEFAULT_FLOOD_KICK_EXEMPT_RANK = 9;
+namespace SharpChat;
 
-        public IWebSocketServer Server { get; }
-        public Context Context { get; }
+public class SockChatServer : IDisposable {
+    public const ushort DEFAULT_PORT = 6770;
+    public const int DEFAULT_MSG_LENGTH_MAX = 5000;
+    public const int DEFAULT_MAX_CONNECTIONS = 5;
+    public const int DEFAULT_FLOOD_KICK_LENGTH = 30;
+    public const int DEFAULT_FLOOD_KICK_EXEMPT_RANK = 9;
 
-        private readonly BansClient BansClient;
+    public IWebSocketServer Server { get; }
+    public Context Context { get; }
 
-        private readonly CachedValue<int> MaxMessageLength;
-        private readonly CachedValue<int> MaxConnections;
-        private readonly CachedValue<int> FloodKickLength;
-        private readonly CachedValue<int> FloodKickExemptRank;
+    private readonly BansClient BansClient;
 
-        private readonly List<C2SPacketHandler> GuestHandlers = [];
-        private readonly List<C2SPacketHandler> AuthedHandlers = [];
-        private readonly SendMessageC2SPacketHandler SendMessageHandler;
+    private readonly CachedValue<int> MaxMessageLength;
+    private readonly CachedValue<int> MaxConnections;
+    private readonly CachedValue<int> FloodKickLength;
+    private readonly CachedValue<int> FloodKickExemptRank;
 
-        private bool IsShuttingDown = false;
+    private readonly List<C2SPacketHandler> GuestHandlers = [];
+    private readonly List<C2SPacketHandler> AuthedHandlers = [];
+    private readonly SendMessageC2SPacketHandler SendMessageHandler;
 
-        private static readonly string[] DEFAULT_CHANNELS = ["lounge"];
+    private bool IsShuttingDown = false;
 
-        private Channel DefaultChannel { get; set; }
+    private static readonly string[] DEFAULT_CHANNELS = ["lounge"];
 
-        public SockChatServer(
-            AuthClient authClient,
-            BansClient bansClient,
-            EventStorage.EventStorage evtStore,
-            Config config
-        ) {
-            Logger.Write("Initialising Sock Chat server...");
+    private Channel DefaultChannel { get; set; }
 
-            BansClient = bansClient;
+    public SockChatServer(
+        AuthClient authClient,
+        BansClient bansClient,
+        EventStorage.EventStorage evtStore,
+        Config config
+    ) {
+        Logger.Write("Initialising Sock Chat server...");
 
-            MaxMessageLength = config.ReadCached("msgMaxLength", DEFAULT_MSG_LENGTH_MAX);
-            MaxConnections = config.ReadCached("connMaxCount", DEFAULT_MAX_CONNECTIONS);
-            FloodKickLength = config.ReadCached("floodKickLength", DEFAULT_FLOOD_KICK_LENGTH);
-            FloodKickExemptRank = config.ReadCached("floodKickExemptRank", DEFAULT_FLOOD_KICK_EXEMPT_RANK);
+        BansClient = bansClient;
 
-            Context = new Context(evtStore);
+        MaxMessageLength = config.ReadCached("msgMaxLength", DEFAULT_MSG_LENGTH_MAX);
+        MaxConnections = config.ReadCached("connMaxCount", DEFAULT_MAX_CONNECTIONS);
+        FloodKickLength = config.ReadCached("floodKickLength", DEFAULT_FLOOD_KICK_LENGTH);
+        FloodKickExemptRank = config.ReadCached("floodKickExemptRank", DEFAULT_FLOOD_KICK_EXEMPT_RANK);
 
-            string[]? channelNames = config.ReadValue("channels", DEFAULT_CHANNELS);
-            if(channelNames is not null)
-                foreach(string channelName in channelNames) {
-                    Config channelCfg = config.ScopeTo($"channels:{channelName}");
+        Context = new Context(evtStore);
 
-                    string name = channelCfg.SafeReadValue("name", string.Empty)!;
-                    if(string.IsNullOrWhiteSpace(name))
-                        name = channelName;
+        string[]? channelNames = config.ReadValue("channels", DEFAULT_CHANNELS);
+        if(channelNames is not null)
+            foreach(string channelName in channelNames) {
+                Config channelCfg = config.ScopeTo($"channels:{channelName}");
 
-                    Channel channelInfo = new(
-                        name,
-                        channelCfg.SafeReadValue("password", string.Empty)!,
-                        rank: channelCfg.SafeReadValue("minRank", 0)
-                    );
+                string name = channelCfg.SafeReadValue("name", string.Empty)!;
+                if(string.IsNullOrWhiteSpace(name))
+                    name = channelName;
 
-                    Context.Channels.Add(channelInfo);
-                    DefaultChannel ??= channelInfo;
-                }
+                Channel channelInfo = new(
+                    name,
+                    channelCfg.SafeReadValue("password", string.Empty)!,
+                    rank: channelCfg.SafeReadValue("minRank", 0)
+                );
 
-            if(DefaultChannel is null)
-                throw new Exception("The default channel could not be determined.");
+                Context.Channels.Add(channelInfo);
+                DefaultChannel ??= channelInfo;
+            }
 
-            GuestHandlers.Add(new AuthC2SPacketHandler(authClient, bansClient, DefaultChannel, MaxMessageLength, MaxConnections));
+        if(DefaultChannel is null)
+            throw new Exception("The default channel could not be determined.");
 
-            AuthedHandlers.AddRange([
-                new PingC2SPacketHandler(authClient),
-                SendMessageHandler = new SendMessageC2SPacketHandler(Context.RandomSnowflake, MaxMessageLength),
-            ]);
+        GuestHandlers.Add(new AuthC2SPacketHandler(authClient, bansClient, DefaultChannel, MaxMessageLength, MaxConnections));
 
-            SendMessageHandler.AddCommands([
-                new AFKClientCommand(),
-                new NickClientCommand(),
-                new WhisperClientCommand(),
-                new ActionClientCommand(),
-                new WhoClientCommand(),
-                new JoinChannelClientCommand(),
-                new CreateChannelClientCommand(),
-                new DeleteChannelClientCommand(),
-                new PasswordChannelClientCommand(),
-                new RankChannelClientCommand(),
-                new BroadcastClientCommand(),
-                new DeleteMessageClientCommand(),
-                new KickBanClientCommand(bansClient),
-                new PardonUserClientCommand(bansClient),
-                new PardonAddressClientCommand(bansClient),
-                new BanListClientCommand(bansClient),
-                new RemoteAddressClientCommand(),
-            ]);
+        AuthedHandlers.AddRange([
+            new PingC2SPacketHandler(authClient),
+            SendMessageHandler = new SendMessageC2SPacketHandler(Context.RandomSnowflake, MaxMessageLength),
+        ]);
 
-            ushort port = config.SafeReadValue("port", DEFAULT_PORT);
-            Server = new SharpChatWebSocketServer($"ws://0.0.0.0:{port}");
+        SendMessageHandler.AddCommands([
+            new AFKClientCommand(),
+            new NickClientCommand(),
+            new WhisperClientCommand(),
+            new ActionClientCommand(),
+            new WhoClientCommand(),
+            new JoinChannelClientCommand(),
+            new CreateChannelClientCommand(),
+            new DeleteChannelClientCommand(),
+            new PasswordChannelClientCommand(),
+            new RankChannelClientCommand(),
+            new BroadcastClientCommand(),
+            new DeleteMessageClientCommand(),
+            new KickBanClientCommand(bansClient),
+            new PardonUserClientCommand(bansClient),
+            new PardonAddressClientCommand(bansClient),
+            new BanListClientCommand(bansClient),
+            new RemoteAddressClientCommand(),
+        ]);
+
+        ushort port = config.SafeReadValue("port", DEFAULT_PORT);
+        Server = new SharpChatWebSocketServer($"ws://0.0.0.0:{port}");
+    }
+
+    public void Listen(ManualResetEvent waitHandle) {
+        if(waitHandle != null)
+            SendMessageHandler.AddCommand(new ShutdownRestartClientCommand(waitHandle, () => !IsShuttingDown && (IsShuttingDown = true)));
+
+        Server.Start(sock => {
+            if(IsShuttingDown) {
+                sock.Close(1013);
+                return;
+            }
+
+            Connection conn = new(sock);
+            Context.Connections.Add(conn);
+
+            sock.OnOpen = () => OnOpen(conn).Wait();
+            sock.OnClose = () => OnClose(conn).Wait();
+            sock.OnError = err => OnError(conn, err).Wait();
+            sock.OnMessage = msg => OnMessage(conn, msg).Wait();
+        });
+
+        Logger.Write("Listening...");
+    }
+
+    private async Task OnOpen(Connection conn) {
+        Logger.Write($"Connection opened from {conn.RemoteAddress}:{conn.RemotePort}");
+        await Context.SafeUpdate();
+    }
+
+    private async Task OnError(Connection conn, Exception ex) {
+        Logger.Write($"[{conn.Id} {conn.RemoteAddress}] {ex}");
+        await Context.SafeUpdate();
+    }
+
+    private async Task OnClose(Connection conn) {
+        Logger.Write($"Connection closed from {conn.RemoteAddress}:{conn.RemotePort}");
+
+        Context.ContextAccess.Wait();
+        try {
+            Context.Connections.Remove(conn);
+
+            if(conn.User != null && !Context.Connections.Any(c => c.User == conn.User))
+                await Context.HandleDisconnect(conn.User);
+
+            await Context.Update();
+        } finally {
+            Context.ContextAccess.Release();
         }
+    }
 
-        public void Listen(ManualResetEvent waitHandle) {
-            if(waitHandle != null)
-                SendMessageHandler.AddCommand(new ShutdownRestartClientCommand(waitHandle, () => !IsShuttingDown && (IsShuttingDown = true)));
+    private async Task OnMessage(Connection conn, string msg) {
+        await Context.SafeUpdate();
 
-            Server.Start(sock => {
-                if(IsShuttingDown) {
-                    sock.Close(1013);
-                    return;
-                }
-
-                Connection conn = new(sock);
-                Context.Connections.Add(conn);
-
-                sock.OnOpen = () => OnOpen(conn).Wait();
-                sock.OnClose = () => OnClose(conn).Wait();
-                sock.OnError = err => OnError(conn, err).Wait();
-                sock.OnMessage = msg => OnMessage(conn, msg).Wait();
-            });
-
-            Logger.Write("Listening...");
-        }
-
-        private async Task OnOpen(Connection conn) {
-            Logger.Write($"Connection opened from {conn.RemoteAddress}:{conn.RemotePort}");
-            await Context.SafeUpdate();
-        }
-
-        private async Task OnError(Connection conn, Exception ex) {
-            Logger.Write($"[{conn.Id} {conn.RemoteAddress}] {ex}");
-            await Context.SafeUpdate();
-        }
-
-        private async Task OnClose(Connection conn) {
-            Logger.Write($"Connection closed from {conn.RemoteAddress}:{conn.RemotePort}");
+        // this doesn't affect non-authed connections?????
+        if(conn.User is not null && conn.User.Rank < FloodKickExemptRank) {
+            User? banUser = null;
+            string banAddr = string.Empty;
+            TimeSpan banDuration = TimeSpan.MinValue;
 
             Context.ContextAccess.Wait();
             try {
-                Context.Connections.Remove(conn);
+                if(!Context.UserRateLimiters.TryGetValue(conn.User.UserId, out RateLimiter? rateLimiter))
+                    Context.UserRateLimiters.Add(conn.User.UserId, rateLimiter = new RateLimiter(
+                        User.DEFAULT_SIZE,
+                        User.DEFAULT_MINIMUM_DELAY,
+                        User.DEFAULT_RISKY_OFFSET
+                    ));
 
-                if(conn.User != null && !Context.Connections.Any(c => c.User == conn.User))
-                    await Context.HandleDisconnect(conn.User);
+                rateLimiter.Update();
 
-                await Context.Update();
+                if(rateLimiter.IsExceeded) {
+                    banDuration = TimeSpan.FromSeconds(FloodKickLength);
+                    banUser = conn.User;
+                    banAddr = conn.RemoteAddress.ToString();
+                } else if(rateLimiter.IsRisky) {
+                    banUser = conn.User;
+                }
+
+                if(banUser is not null) {
+                    if(banDuration == TimeSpan.MinValue) {
+                        await Context.SendTo(conn.User, new CommandResponseS2CPacket(Context.RandomSnowflake.Next(), LCR.FLOOD_WARN, false));
+                    } else {
+                        await Context.BanUser(conn.User, banDuration, UserDisconnectS2CPacket.Reason.Flood);
+
+                        if(banDuration > TimeSpan.Zero)
+                            await BansClient.BanCreateAsync(
+                                BanKind.User,
+                                banDuration,
+                                conn.RemoteAddress,
+                                conn.User.UserId,
+                                "Kicked from chat for flood protection.",
+                                IPAddress.IPv6Loopback
+                            );
+
+                        return;
+                    }
+                }
             } finally {
                 Context.ContextAccess.Release();
             }
         }
 
-        private async Task OnMessage(Connection conn, string msg) {
-            await Context.SafeUpdate();
+        C2SPacketHandlerContext context = new(msg, Context, conn);
+        C2SPacketHandler? handler = conn.User is null
+            ? GuestHandlers.FirstOrDefault(h => h.IsMatch(context))
+            : AuthedHandlers.FirstOrDefault(h => h.IsMatch(context));
 
-            // this doesn't affect non-authed connections?????
-            if(conn.User is not null && conn.User.Rank < FloodKickExemptRank) {
-                User? banUser = null;
-                string banAddr = string.Empty;
-                TimeSpan banDuration = TimeSpan.MinValue;
+        if(handler is not null)
+            await handler.Handle(context);
+    }
 
-                Context.ContextAccess.Wait();
-                try {
-                    if(!Context.UserRateLimiters.TryGetValue(conn.User.UserId, out RateLimiter? rateLimiter))
-                        Context.UserRateLimiters.Add(conn.User.UserId, rateLimiter = new RateLimiter(
-                            User.DEFAULT_SIZE,
-                            User.DEFAULT_MINIMUM_DELAY,
-                            User.DEFAULT_RISKY_OFFSET
-                        ));
+    private bool IsDisposed;
 
-                    rateLimiter.Update();
+    ~SockChatServer() {
+        DoDispose();
+    }
 
-                    if(rateLimiter.IsExceeded) {
-                        banDuration = TimeSpan.FromSeconds(FloodKickLength);
-                        banUser = conn.User;
-                        banAddr = conn.RemoteAddress.ToString();
-                    } else if(rateLimiter.IsRisky) {
-                        banUser = conn.User;
-                    }
+    public void Dispose() {
+        DoDispose();
+        GC.SuppressFinalize(this);
+    }
 
-                    if(banUser is not null) {
-                        if(banDuration == TimeSpan.MinValue) {
-                            await Context.SendTo(conn.User, new CommandResponseS2CPacket(Context.RandomSnowflake.Next(), LCR.FLOOD_WARN, false));
-                        } else {
-                            await Context.BanUser(conn.User, banDuration, UserDisconnectS2CPacket.Reason.Flood);
+    private void DoDispose() {
+        if(IsDisposed)
+            return;
+        IsDisposed = true;
+        IsShuttingDown = true;
 
-                            if(banDuration > TimeSpan.Zero)
-                                await BansClient.BanCreateAsync(
-                                    BanKind.User,
-                                    banDuration,
-                                    conn.RemoteAddress,
-                                    conn.User.UserId,
-                                    "Kicked from chat for flood protection.",
-                                    IPAddress.IPv6Loopback
-                                );
+        foreach(Connection conn in Context.Connections)
+            conn.Dispose();
 
-                            return;
-                        }
-                    }
-                } finally {
-                    Context.ContextAccess.Release();
-                }
-            }
-
-            C2SPacketHandlerContext context = new(msg, Context, conn);
-            C2SPacketHandler? handler = conn.User is null
-                ? GuestHandlers.FirstOrDefault(h => h.IsMatch(context))
-                : AuthedHandlers.FirstOrDefault(h => h.IsMatch(context));
-
-            if(handler is not null)
-                await handler.Handle(context);
-        }
-
-        private bool IsDisposed;
-
-        ~SockChatServer() {
-            DoDispose();
-        }
-
-        public void Dispose() {
-            DoDispose();
-            GC.SuppressFinalize(this);
-        }
-
-        private void DoDispose() {
-            if(IsDisposed)
-                return;
-            IsDisposed = true;
-            IsShuttingDown = true;
-
-            foreach(Connection conn in Context.Connections)
-                conn.Dispose();
-
-            Server?.Dispose();
-        }
+        Server?.Dispose();
     }
 }
diff --git a/SharpChat/User.cs b/SharpChat/User.cs
index b87c439..7b0f874 100644
--- a/SharpChat/User.cs
+++ b/SharpChat/User.cs
@@ -2,72 +2,72 @@ using SharpChat.ClientCommands;
 using System.Globalization;
 using System.Text;
 
-namespace SharpChat {
-    public class User(
-        string userId,
-        string userName,
-        ColourInheritable colour,
-        int rank,
-        UserPermissions perms,
-        string nickName = "",
-        UserStatus status = UserStatus.Online,
-        string statusText = ""
-    ) {
-        public const int DEFAULT_SIZE = 30;
-        public const int DEFAULT_MINIMUM_DELAY = 10000;
-        public const int DEFAULT_RISKY_OFFSET = 5;
+namespace SharpChat;
 
-        public string UserId { get; } = userId;
-        public string UserName { get; set; } = userName ?? throw new ArgumentNullException(nameof(userName));
-        public ColourInheritable Colour { get; set; } = colour;
-        public int Rank { get; set; } = rank;
-        public UserPermissions Permissions { get; set; } = perms;
-        public string NickName { get; set; } = nickName;
-        public UserStatus Status { get; set; } = status;
-        public string StatusText { get; set; } = statusText;
+public class User(
+    string userId,
+    string userName,
+    ColourInheritable colour,
+    int rank,
+    UserPermissions perms,
+    string nickName = "",
+    UserStatus status = UserStatus.Online,
+    string statusText = ""
+) {
+    public const int DEFAULT_SIZE = 30;
+    public const int DEFAULT_MINIMUM_DELAY = 10000;
+    public const int DEFAULT_RISKY_OFFSET = 5;
 
-        public string LegacyName => string.IsNullOrWhiteSpace(NickName) ? UserName : $"~{NickName}";
+    public string UserId { get; } = userId;
+    public string UserName { get; set; } = userName ?? throw new ArgumentNullException(nameof(userName));
+    public ColourInheritable Colour { get; set; } = colour;
+    public int Rank { get; set; } = rank;
+    public UserPermissions Permissions { get; set; } = perms;
+    public string NickName { get; set; } = nickName;
+    public UserStatus Status { get; set; } = status;
+    public string StatusText { get; set; } = statusText;
 
-        public string LegacyNameWithStatus {
-            get {
-                StringBuilder sb = new();
+    public string LegacyName => string.IsNullOrWhiteSpace(NickName) ? UserName : $"~{NickName}";
 
-                if(Status == UserStatus.Away) {
-                    string statusText = StatusText.Trim();
-                    StringInfo sti = new(statusText);
-                    if(Encoding.UTF8.GetByteCount(statusText) > AFKClientCommand.MAX_BYTES
-                        || sti.LengthInTextElements > AFKClientCommand.MAX_GRAPHEMES)
-                        statusText = sti.SubstringByTextElements(0, Math.Min(sti.LengthInTextElements, AFKClientCommand.MAX_GRAPHEMES)).Trim();
+    public string LegacyNameWithStatus {
+        get {
+            StringBuilder sb = new();
 
-                    sb.AppendFormat("&lt;{0}&gt;_", statusText.ToUpperInvariant());
-                }
+            if(Status == UserStatus.Away) {
+                string statusText = StatusText.Trim();
+                StringInfo sti = new(statusText);
+                if(Encoding.UTF8.GetByteCount(statusText) > AFKClientCommand.MAX_BYTES
+                    || sti.LengthInTextElements > AFKClientCommand.MAX_GRAPHEMES)
+                    statusText = sti.SubstringByTextElements(0, Math.Min(sti.LengthInTextElements, AFKClientCommand.MAX_GRAPHEMES)).Trim();
 
-                sb.Append(LegacyName);
-
-                return sb.ToString();
+                sb.AppendFormat("&lt;{0}&gt;_", statusText.ToUpperInvariant());
             }
-        }
 
-        public bool Can(UserPermissions perm, bool strict = false) {
-            UserPermissions perms = Permissions & perm;
-            return strict ? perms == perm : perms > 0;
-        }
+            sb.Append(LegacyName);
 
-        public bool NameEquals(string name) {
-            return string.Equals(name, UserName, StringComparison.InvariantCultureIgnoreCase)
-                || string.Equals(name, NickName, StringComparison.InvariantCultureIgnoreCase)
-                || string.Equals(name, LegacyName, StringComparison.InvariantCultureIgnoreCase)
-                || string.Equals(name, LegacyNameWithStatus, StringComparison.InvariantCultureIgnoreCase);
-        }
-
-        public override int GetHashCode() {
-            return UserId.GetHashCode();
-        }
-
-        public static string GetDMChannelName(User user1, User user2) {
-            return string.Compare(user1.UserId, user2.UserId, StringComparison.InvariantCultureIgnoreCase) > 0
-                ? $"@{user2.UserId}-{user1.UserId}"
-                : $"@{user1.UserId}-{user2.UserId}";
+            return sb.ToString();
         }
     }
+
+    public bool Can(UserPermissions perm, bool strict = false) {
+        UserPermissions perms = Permissions & perm;
+        return strict ? perms == perm : perms > 0;
+    }
+
+    public bool NameEquals(string name) {
+        return string.Equals(name, UserName, StringComparison.InvariantCultureIgnoreCase)
+            || string.Equals(name, NickName, StringComparison.InvariantCultureIgnoreCase)
+            || string.Equals(name, LegacyName, StringComparison.InvariantCultureIgnoreCase)
+            || string.Equals(name, LegacyNameWithStatus, StringComparison.InvariantCultureIgnoreCase);
+    }
+
+    public override int GetHashCode() {
+        return UserId.GetHashCode();
+    }
+
+    public static string GetDMChannelName(User user1, User user2) {
+        return string.Compare(user1.UserId, user2.UserId, StringComparison.InvariantCultureIgnoreCase) > 0
+            ? $"@{user2.UserId}-{user1.UserId}"
+            : $"@{user1.UserId}-{user2.UserId}";
+    }
 }
diff --git a/SharpChat/UserStatus.cs b/SharpChat/UserStatus.cs
index 447333b..632e109 100644
--- a/SharpChat/UserStatus.cs
+++ b/SharpChat/UserStatus.cs
@@ -1,7 +1,7 @@
-namespace SharpChat {
-    public enum UserStatus {
-        Online,
-        Away,
-        Offline,
-    }
+namespace SharpChat;
+
+public enum UserStatus {
+    Online,
+    Away,
+    Offline,
 }
diff --git a/SharpChatCommon/Auth/AuthClient.cs b/SharpChatCommon/Auth/AuthClient.cs
index c0e8528..56fbf4c 100644
--- a/SharpChatCommon/Auth/AuthClient.cs
+++ b/SharpChatCommon/Auth/AuthClient.cs
@@ -1,8 +1,8 @@
 using System.Net;
 
-namespace SharpChat.Auth {
-    public interface AuthClient {
-        Task<AuthResult> AuthVerifyAsync(IPAddress remoteAddr, string scheme, string token);
-        Task AuthBumpUsersOnlineAsync(IEnumerable<(IPAddress remoteAddr, string userId)> entries);
-    }
+namespace SharpChat.Auth;
+
+public interface AuthClient {
+    Task<AuthResult> AuthVerifyAsync(IPAddress remoteAddr, string scheme, string token);
+    Task AuthBumpUsersOnlineAsync(IEnumerable<(IPAddress remoteAddr, string userId)> entries);
 }
diff --git a/SharpChatCommon/Auth/AuthFailedException.cs b/SharpChatCommon/Auth/AuthFailedException.cs
index 460d00a..5426c42 100644
--- a/SharpChatCommon/Auth/AuthFailedException.cs
+++ b/SharpChatCommon/Auth/AuthFailedException.cs
@@ -1,3 +1,3 @@
-namespace SharpChat.Auth {
-    public class AuthFailedException(string message) : Exception(message) {}
-}
+namespace SharpChat.Auth;
+
+public class AuthFailedException(string message) : Exception(message) {}
diff --git a/SharpChatCommon/Auth/AuthResult.cs b/SharpChatCommon/Auth/AuthResult.cs
index 95efcae..075e735 100644
--- a/SharpChatCommon/Auth/AuthResult.cs
+++ b/SharpChatCommon/Auth/AuthResult.cs
@@ -1,9 +1,9 @@
-namespace SharpChat.Auth {
-    public interface AuthResult {
-        string UserId { get; }
-        string UserName { get; }
-        ColourInheritable UserColour { get; }
-        int UserRank { get; }
-        UserPermissions UserPermissions { get; }
-    }
+namespace SharpChat.Auth;
+
+public interface AuthResult {
+    string UserId { get; }
+    string UserName { get; }
+    ColourInheritable UserColour { get; }
+    int UserRank { get; }
+    UserPermissions UserPermissions { get; }
 }
diff --git a/SharpChatCommon/Bans/BanInfo.cs b/SharpChatCommon/Bans/BanInfo.cs
index 412364e..92cbe9a 100644
--- a/SharpChatCommon/Bans/BanInfo.cs
+++ b/SharpChatCommon/Bans/BanInfo.cs
@@ -1,9 +1,9 @@
-namespace SharpChat.Bans {
-    public interface BanInfo {
-        BanKind Kind { get; }
-        bool IsPermanent { get; }
-        DateTimeOffset ExpiresAt { get; }
-        public bool HasExpired => !IsPermanent && DateTimeOffset.UtcNow >= ExpiresAt;
-        string ToString();
-    }
+namespace SharpChat.Bans;
+
+public interface BanInfo {
+    BanKind Kind { get; }
+    bool IsPermanent { get; }
+    DateTimeOffset ExpiresAt { get; }
+    public bool HasExpired => !IsPermanent && DateTimeOffset.UtcNow >= ExpiresAt;
+    string ToString();
 }
diff --git a/SharpChatCommon/Bans/BanKind.cs b/SharpChatCommon/Bans/BanKind.cs
index cea18cd..30f4096 100644
--- a/SharpChatCommon/Bans/BanKind.cs
+++ b/SharpChatCommon/Bans/BanKind.cs
@@ -1,6 +1,6 @@
-namespace SharpChat.Bans {
-    public enum BanKind {
-        User,
-        IPAddress,
-    }
+namespace SharpChat.Bans;
+
+public enum BanKind {
+    User,
+    IPAddress,
 }
diff --git a/SharpChatCommon/Bans/BansClient.cs b/SharpChatCommon/Bans/BansClient.cs
index 34b3ed7..bf8ee38 100644
--- a/SharpChatCommon/Bans/BansClient.cs
+++ b/SharpChatCommon/Bans/BansClient.cs
@@ -1,18 +1,18 @@
 using System.Net;
 
-namespace SharpChat.Bans {
-    public interface BansClient {
-        Task BanCreateAsync(
-            BanKind kind,
-            TimeSpan duration,
-            IPAddress remoteAddr,
-            string? userId = null,
-            string? reason = null,
-            IPAddress? issuerRemoteAddr = null,
-            string? issuerUserId = null
-        );
-        Task<bool> BanRevokeAsync(BanInfo info);
-        Task<BanInfo?> BanGetAsync(string? userIdOrName = null, IPAddress? remoteAddr = null);
-        Task<BanInfo[]> BanGetListAsync();
-    }
+namespace SharpChat.Bans;
+
+public interface BansClient {
+    Task BanCreateAsync(
+        BanKind kind,
+        TimeSpan duration,
+        IPAddress remoteAddr,
+        string? userId = null,
+        string? reason = null,
+        IPAddress? issuerRemoteAddr = null,
+        string? issuerUserId = null
+    );
+    Task<bool> BanRevokeAsync(BanInfo info);
+    Task<BanInfo?> BanGetAsync(string? userIdOrName = null, IPAddress? remoteAddr = null);
+    Task<BanInfo[]> BanGetListAsync();
 }
diff --git a/SharpChatCommon/Bans/IPAddressBanInfo.cs b/SharpChatCommon/Bans/IPAddressBanInfo.cs
index ebae8e2..17746c1 100644
--- a/SharpChatCommon/Bans/IPAddressBanInfo.cs
+++ b/SharpChatCommon/Bans/IPAddressBanInfo.cs
@@ -1,7 +1,7 @@
 using System.Net;
 
-namespace SharpChat.Bans {
-    public interface IPAddressBanInfo : BanInfo {
-        IPAddress Address { get; }
-    }
+namespace SharpChat.Bans;
+
+public interface IPAddressBanInfo : BanInfo {
+    IPAddress Address { get; }
 }
diff --git a/SharpChatCommon/Bans/UserBanInfo.cs b/SharpChatCommon/Bans/UserBanInfo.cs
index 1050007..2088d05 100644
--- a/SharpChatCommon/Bans/UserBanInfo.cs
+++ b/SharpChatCommon/Bans/UserBanInfo.cs
@@ -1,7 +1,7 @@
-namespace SharpChat.Bans {
-    public interface UserBanInfo : BanInfo {
-        string UserId { get; }
-        string UserName { get; }
-        ColourInheritable UserColour { get; }
-    }
+namespace SharpChat.Bans;
+
+public interface UserBanInfo : BanInfo {
+    string UserId { get; }
+    string UserName { get; }
+    ColourInheritable UserColour { get; }
 }
diff --git a/SharpChatCommon/ColourInheritable.cs b/SharpChatCommon/ColourInheritable.cs
index 62241f6..fa4558b 100644
--- a/SharpChatCommon/ColourInheritable.cs
+++ b/SharpChatCommon/ColourInheritable.cs
@@ -1,14 +1,14 @@
-namespace SharpChat {
-    public readonly record struct ColourInheritable(ColourRgb? rgb) {
-        public static readonly ColourInheritable None = new(null);
-        public override string ToString() => rgb.HasValue ? rgb.Value.ToString() : "inherit";
+namespace SharpChat;
 
-        public static ColourInheritable FromRaw(int? raw) => raw is null ? None : new(new ColourRgb(raw.Value));
-        public static ColourInheritable FromRgb(byte red, byte green, byte blue) => new(ColourRgb.FromRgb(red, green, blue));
+public readonly record struct ColourInheritable(ColourRgb? rgb) {
+    public static readonly ColourInheritable None = new(null);
+    public override string ToString() => rgb.HasValue ? rgb.Value.ToString() : "inherit";
 
-        // these should go Away
-        private const int MSZ_INHERIT = 0x40000000;
-        public int ToMisuzu() => rgb.HasValue ? rgb.Value.Raw : MSZ_INHERIT;
-        public static ColourInheritable FromMisuzu(int msz) => (msz & MSZ_INHERIT) > 0 ? None : new(new ColourRgb(msz & 0xFFFFFF));
-    }
+    public static ColourInheritable FromRaw(int? raw) => raw is null ? None : new(new ColourRgb(raw.Value));
+    public static ColourInheritable FromRgb(byte red, byte green, byte blue) => new(ColourRgb.FromRgb(red, green, blue));
+
+    // these should go Away
+    private const int MSZ_INHERIT = 0x40000000;
+    public int ToMisuzu() => rgb.HasValue ? rgb.Value.Raw : MSZ_INHERIT;
+    public static ColourInheritable FromMisuzu(int msz) => (msz & MSZ_INHERIT) > 0 ? None : new(new ColourRgb(msz & 0xFFFFFF));
 }
diff --git a/SharpChatCommon/ColourRgb.cs b/SharpChatCommon/ColourRgb.cs
index 79d349e..bc3df3f 100644
--- a/SharpChatCommon/ColourRgb.cs
+++ b/SharpChatCommon/ColourRgb.cs
@@ -1,9 +1,9 @@
-namespace SharpChat {
-    public readonly record struct ColourRgb(int Raw) {
-        public byte Red => (byte)((Raw >> 16) & 0xFF);
-        public byte Green => (byte)((Raw >> 8) & 0xFF);
-        public byte Blue => (byte)(Raw & 0xFF);
-        public override string ToString() => string.Format("#{0:x2}{1:x2}{2:x2}", Red, Green, Blue);
-        public static ColourRgb FromRgb(byte red, byte green, byte blue) => new(red << 16 | green << 8 | blue);
-    }
+namespace SharpChat;
+
+public readonly record struct ColourRgb(int Raw) {
+    public byte Red => (byte)((Raw >> 16) & 0xFF);
+    public byte Green => (byte)((Raw >> 8) & 0xFF);
+    public byte Blue => (byte)(Raw & 0xFF);
+    public override string ToString() => string.Format("#{0:x2}{1:x2}{2:x2}", Red, Green, Blue);
+    public static ColourRgb FromRgb(byte red, byte green, byte blue) => new(red << 16 | green << 8 | blue);
 }
diff --git a/SharpChatCommon/Configuration/CachedValue.cs b/SharpChatCommon/Configuration/CachedValue.cs
index feaf1d5..214d42c 100644
--- a/SharpChatCommon/Configuration/CachedValue.cs
+++ b/SharpChatCommon/Configuration/CachedValue.cs
@@ -1,34 +1,34 @@
-namespace SharpChat.Configuration {
-    public class CachedValue<T>(Config config, string name, TimeSpan lifetime, T? fallback) {
-        private Config Config { get; } = config ?? throw new ArgumentNullException(nameof(config));
-        private string Name { get; } = name ?? throw new ArgumentNullException(nameof(name));
-        private object ConfigAccess { get; } = new();
+namespace SharpChat.Configuration;
 
-        private object? CurrentValue { get; set; } = default(T);
-        private DateTimeOffset LastRead { get; set; }
+public class CachedValue<T>(Config config, string name, TimeSpan lifetime, T? fallback) {
+    private Config Config { get; } = config ?? throw new ArgumentNullException(nameof(config));
+    private string Name { get; } = name ?? throw new ArgumentNullException(nameof(name));
+    private object ConfigAccess { get; } = new();
 
-        public T? Value {
-            get {
-                lock(ConfigAccess) { // this lock doesn't really make sense since it doesn't affect other config calls
-                    DateTimeOffset now = DateTimeOffset.Now;
-                    if((now - LastRead) >= lifetime) {
-                        LastRead = now;
-                        CurrentValue = Config.ReadValue(Name, fallback);
-                        Logger.Debug($"Read {Name} ({CurrentValue})");
-                    }
+    private object? CurrentValue { get; set; } = default(T);
+    private DateTimeOffset LastRead { get; set; }
+
+    public T? Value {
+        get {
+            lock(ConfigAccess) { // this lock doesn't really make sense since it doesn't affect other config calls
+                DateTimeOffset now = DateTimeOffset.Now;
+                if((now - LastRead) >= lifetime) {
+                    LastRead = now;
+                    CurrentValue = Config.ReadValue(Name, fallback);
+                    Logger.Debug($"Read {Name} ({CurrentValue})");
                 }
-                return (T?)CurrentValue;
             }
-        }
-
-        public static implicit operator T?(CachedValue<T?> val) => val.Value;
-
-        public void Refresh() {
-            LastRead = DateTimeOffset.MinValue;
-        }
-
-        public override string ToString() {
-            return Value?.ToString() ?? string.Empty;
+            return (T?)CurrentValue;
         }
     }
+
+    public static implicit operator T?(CachedValue<T?> val) => val.Value;
+
+    public void Refresh() {
+        LastRead = DateTimeOffset.MinValue;
+    }
+
+    public override string ToString() {
+        return Value?.ToString() ?? string.Empty;
+    }
 }
diff --git a/SharpChatCommon/Configuration/Config.cs b/SharpChatCommon/Configuration/Config.cs
index 04c4140..068061a 100644
--- a/SharpChatCommon/Configuration/Config.cs
+++ b/SharpChatCommon/Configuration/Config.cs
@@ -1,29 +1,29 @@
-namespace SharpChat.Configuration {
-    public interface Config : IDisposable {
-        /// <summary>
-        /// Creates a proxy object that forces all names to start with the given prefix.
-        /// </summary>
-        Config ScopeTo(string prefix);
+namespace SharpChat.Configuration;
 
-        /// <summary>
-        /// Reads a raw (string) value from the config.
-        /// </summary>
-        string? ReadValue(string name, string? fallback = null);
+public interface Config : IDisposable {
+    /// <summary>
+    /// Creates a proxy object that forces all names to start with the given prefix.
+    /// </summary>
+    Config ScopeTo(string prefix);
 
-        /// <summary>
-        /// Reads and casts value from the config.
-        /// </summary>
-        /// <exception cref="ConfigTypeException">Type conversion failed.</exception>
-        T? ReadValue<T>(string name, T? fallback = default);
+    /// <summary>
+    /// Reads a raw (string) value from the config.
+    /// </summary>
+    string? ReadValue(string name, string? fallback = null);
 
-        /// <summary>
-        /// Reads and casts a value from the config. Returns fallback when type conversion fails.
-        /// </summary>
-        T? SafeReadValue<T>(string name, T? fallback);
+    /// <summary>
+    /// Reads and casts value from the config.
+    /// </summary>
+    /// <exception cref="ConfigTypeException">Type conversion failed.</exception>
+    T? ReadValue<T>(string name, T? fallback = default);
 
-        /// <summary>
-        /// Creates an object that caches the read value for a certain amount of time, avoiding disk reads for frequently used non-static values.
-        /// </summary>
-        CachedValue<T> ReadCached<T>(string name, T? fallback = default, TimeSpan? lifetime = null);
-    }
+    /// <summary>
+    /// Reads and casts a value from the config. Returns fallback when type conversion fails.
+    /// </summary>
+    T? SafeReadValue<T>(string name, T? fallback);
+
+    /// <summary>
+    /// Creates an object that caches the read value for a certain amount of time, avoiding disk reads for frequently used non-static values.
+    /// </summary>
+    CachedValue<T> ReadCached<T>(string name, T? fallback = default, TimeSpan? lifetime = null);
 }
diff --git a/SharpChatCommon/Configuration/ConfigExceptions.cs b/SharpChatCommon/Configuration/ConfigExceptions.cs
index 6e851bf..8d67762 100644
--- a/SharpChatCommon/Configuration/ConfigExceptions.cs
+++ b/SharpChatCommon/Configuration/ConfigExceptions.cs
@@ -1,9 +1,9 @@
-namespace SharpChat.Configuration {
-    public abstract class ConfigException : Exception {
-        public ConfigException(string message) : base(message) { }
-        public ConfigException(string message, Exception ex) : base(message, ex) { }
-    }
+namespace SharpChat.Configuration;
 
-    public class ConfigLockException() : ConfigException("Unable to acquire lock for reading configuration.") {}
-    public class ConfigTypeException(Exception ex) : ConfigException("Given type does not match the value in the configuration.", ex) {}
+public abstract class ConfigException : Exception {
+    public ConfigException(string message) : base(message) { }
+    public ConfigException(string message, Exception ex) : base(message, ex) { }
 }
+
+public class ConfigLockException() : ConfigException("Unable to acquire lock for reading configuration.") {}
+public class ConfigTypeException(Exception ex) : ConfigException("Given type does not match the value in the configuration.", ex) {}
diff --git a/SharpChatCommon/Configuration/ScopedConfig.cs b/SharpChatCommon/Configuration/ScopedConfig.cs
index 735496b..86b4770 100644
--- a/SharpChatCommon/Configuration/ScopedConfig.cs
+++ b/SharpChatCommon/Configuration/ScopedConfig.cs
@@ -1,34 +1,34 @@
-namespace SharpChat.Configuration {
-    public class ScopedConfig(Config config, string prefix) : Config {
-        private Config Config { get; } = config ?? throw new ArgumentNullException(nameof(config));
-        private string Prefix { get; } = prefix ?? throw new ArgumentNullException(nameof(prefix));
+namespace SharpChat.Configuration;
 
-        private string GetName(string name) {
-            return Prefix + name;
-        }
+public class ScopedConfig(Config config, string prefix) : Config {
+    private Config Config { get; } = config ?? throw new ArgumentNullException(nameof(config));
+    private string Prefix { get; } = prefix ?? throw new ArgumentNullException(nameof(prefix));
 
-        public string? ReadValue(string name, string? fallback = null) {
-            return Config.ReadValue(GetName(name), fallback);
-        }
+    private string GetName(string name) {
+        return Prefix + name;
+    }
 
-        public T? ReadValue<T>(string name, T? fallback = default) {
-            return Config.ReadValue(GetName(name), fallback);
-        }
+    public string? ReadValue(string name, string? fallback = null) {
+        return Config.ReadValue(GetName(name), fallback);
+    }
 
-        public T? SafeReadValue<T>(string name, T? fallback) {
-            return Config.SafeReadValue(GetName(name), fallback);
-        }
+    public T? ReadValue<T>(string name, T? fallback = default) {
+        return Config.ReadValue(GetName(name), fallback);
+    }
 
-        public Config ScopeTo(string prefix) {
-            return Config.ScopeTo(GetName(prefix));
-        }
+    public T? SafeReadValue<T>(string name, T? fallback) {
+        return Config.SafeReadValue(GetName(name), fallback);
+    }
 
-        public CachedValue<T> ReadCached<T>(string name, T? fallback = default, TimeSpan? lifetime = null) {
-            return Config.ReadCached(GetName(name), fallback, lifetime);
-        }
+    public Config ScopeTo(string prefix) {
+        return Config.ScopeTo(GetName(prefix));
+    }
 
-        public void Dispose() {
-            GC.SuppressFinalize(this);
-        }
+    public CachedValue<T> ReadCached<T>(string name, T? fallback = default, TimeSpan? lifetime = null) {
+        return Config.ReadCached(GetName(name), fallback, lifetime);
+    }
+
+    public void Dispose() {
+        GC.SuppressFinalize(this);
     }
 }
diff --git a/SharpChatCommon/Configuration/StreamConfig.cs b/SharpChatCommon/Configuration/StreamConfig.cs
index 3c6887e..78b389f 100644
--- a/SharpChatCommon/Configuration/StreamConfig.cs
+++ b/SharpChatCommon/Configuration/StreamConfig.cs
@@ -1,115 +1,115 @@
 using System.Text;
 
-namespace SharpChat.Configuration {
-    public class StreamConfig : Config {
-        private Stream Stream { get; }
-        private StreamReader StreamReader { get; }
-        private Mutex Lock { get; }
+namespace SharpChat.Configuration;
 
-        private const int LOCK_TIMEOUT = 10000;
+public class StreamConfig : Config {
+    private Stream Stream { get; }
+    private StreamReader StreamReader { get; }
+    private Mutex Lock { get; }
 
-        private static readonly TimeSpan CACHE_LIFETIME = TimeSpan.FromMinutes(15);
+    private const int LOCK_TIMEOUT = 10000;
 
-        public StreamConfig(Stream stream) {
-            Stream = stream ?? throw new ArgumentNullException(nameof(stream));
-            if(!Stream.CanRead)
-                throw new ArgumentException("Provided stream must be readable.", nameof(stream));
-            if(!Stream.CanSeek)
-                throw new ArgumentException("Provided stream must be seekable.", nameof(stream));
-            StreamReader = new StreamReader(stream, new UTF8Encoding(false), false);
-            Lock = new Mutex();
-        }
+    private static readonly TimeSpan CACHE_LIFETIME = TimeSpan.FromMinutes(15);
 
-        public static StreamConfig FromPath(string fileName) {
-            return new StreamConfig(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite));
-        }
+    public StreamConfig(Stream stream) {
+        Stream = stream ?? throw new ArgumentNullException(nameof(stream));
+        if(!Stream.CanRead)
+            throw new ArgumentException("Provided stream must be readable.", nameof(stream));
+        if(!Stream.CanSeek)
+            throw new ArgumentException("Provided stream must be seekable.", nameof(stream));
+        StreamReader = new StreamReader(stream, new UTF8Encoding(false), false);
+        Lock = new Mutex();
+    }
 
-        public string? ReadValue(string name, string? fallback = null) {
-            if(!Lock.WaitOne(LOCK_TIMEOUT)) // don't catch this, if this happens something is Very Wrong
-                throw new ConfigLockException();
+    public static StreamConfig FromPath(string fileName) {
+        return new StreamConfig(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite));
+    }
 
-            try {
-                Stream.Seek(0, SeekOrigin.Begin);
+    public string? ReadValue(string name, string? fallback = null) {
+        if(!Lock.WaitOne(LOCK_TIMEOUT)) // don't catch this, if this happens something is Very Wrong
+            throw new ConfigLockException();
 
-                string? line;
-                while((line = StreamReader.ReadLine()) != null) {
-                    if(string.IsNullOrWhiteSpace(line))
-                        continue;
+        try {
+            Stream.Seek(0, SeekOrigin.Begin);
 
-                    line = line.TrimStart();
-                    if(line.StartsWith(';') || line.StartsWith('#'))
-                        continue;
+            string? line;
+            while((line = StreamReader.ReadLine()) != null) {
+                if(string.IsNullOrWhiteSpace(line))
+                    continue;
 
-                    string[] parts = line.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
-                    if(parts.Length < 2 || !string.Equals(parts[0], name))
-                        continue;
+                line = line.TrimStart();
+                if(line.StartsWith(';') || line.StartsWith('#'))
+                    continue;
 
-                    return parts[1];
-                }
-            } finally {
-                Lock.ReleaseMutex();
+                string[] parts = line.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
+                if(parts.Length < 2 || !string.Equals(parts[0], name))
+                    continue;
+
+                return parts[1];
             }
+        } finally {
+            Lock.ReleaseMutex();
+        }
 
+        return fallback;
+    }
+
+    public T? ReadValue<T>(string name, T? fallback = default) {
+        object? value = ReadValue(name);
+        if(value == null)
             return fallback;
+
+        Type type = typeof(T);
+        if(value is string strVal) {
+            if(type == typeof(bool))
+                value = !string.Equals(strVal, "0", StringComparison.InvariantCultureIgnoreCase)
+                    && !string.Equals(strVal, "false", StringComparison.InvariantCultureIgnoreCase);
+            else if(type == typeof(string[]))
+                value = strVal.Split(' ');
         }
 
-        public T? ReadValue<T>(string name, T? fallback = default) {
-            object? value = ReadValue(name);
-            if(value == null)
-                return fallback;
-
-            Type type = typeof(T);
-            if(value is string strVal) {
-                if(type == typeof(bool))
-                    value = !string.Equals(strVal, "0", StringComparison.InvariantCultureIgnoreCase)
-                        && !string.Equals(strVal, "false", StringComparison.InvariantCultureIgnoreCase);
-                else if(type == typeof(string[]))
-                    value = strVal.Split(' ');
-            }
-
-            try {
-                return (T)Convert.ChangeType(value, type);
-            } catch(InvalidCastException ex) {
-                throw new ConfigTypeException(ex);
-            }
-        }
-
-        public T? SafeReadValue<T>(string name, T? fallback) {
-            try {
-                return ReadValue(name, fallback);
-            } catch(ConfigTypeException) {
-                return fallback;
-            }
-        }
-
-        public Config ScopeTo(string prefix) {
-            if(string.IsNullOrWhiteSpace(prefix))
-                throw new ArgumentException("Prefix must exist.", nameof(prefix));
-            if(prefix[^1] != ':')
-                prefix += ':';
-
-            return new ScopedConfig(this, prefix);
-        }
-
-        public CachedValue<T> ReadCached<T>(string name, T? fallback = default, TimeSpan? lifetime = null) {
-            return new CachedValue<T>(this, name, lifetime ?? CACHE_LIFETIME, fallback);
-        }
-
-        private bool IsDisposed;
-        ~StreamConfig()
-            => DoDispose();
-        public void Dispose() {
-            DoDispose();
-            GC.SuppressFinalize(this);
-        }
-        private void DoDispose() {
-            if(IsDisposed)
-                return;
-            IsDisposed = true;
-
-            StreamReader.Dispose();
-            Stream.Dispose();
-            Lock.Dispose();
+        try {
+            return (T)Convert.ChangeType(value, type);
+        } catch(InvalidCastException ex) {
+            throw new ConfigTypeException(ex);
         }
     }
+
+    public T? SafeReadValue<T>(string name, T? fallback) {
+        try {
+            return ReadValue(name, fallback);
+        } catch(ConfigTypeException) {
+            return fallback;
+        }
+    }
+
+    public Config ScopeTo(string prefix) {
+        if(string.IsNullOrWhiteSpace(prefix))
+            throw new ArgumentException("Prefix must exist.", nameof(prefix));
+        if(prefix[^1] != ':')
+            prefix += ':';
+
+        return new ScopedConfig(this, prefix);
+    }
+
+    public CachedValue<T> ReadCached<T>(string name, T? fallback = default, TimeSpan? lifetime = null) {
+        return new CachedValue<T>(this, name, lifetime ?? CACHE_LIFETIME, fallback);
+    }
+
+    private bool IsDisposed;
+    ~StreamConfig()
+        => DoDispose();
+    public void Dispose() {
+        DoDispose();
+        GC.SuppressFinalize(this);
+    }
+    private void DoDispose() {
+        if(IsDisposed)
+            return;
+        IsDisposed = true;
+
+        StreamReader.Dispose();
+        Stream.Dispose();
+        Lock.Dispose();
+    }
 }
diff --git a/SharpChatCommon/Logger.cs b/SharpChatCommon/Logger.cs
index e2354d9..f5603c1 100644
--- a/SharpChatCommon/Logger.cs
+++ b/SharpChatCommon/Logger.cs
@@ -1,33 +1,33 @@
-using System.Diagnostics;
+using System.Diagnostics;
 using System.Text;
 
-namespace SharpChat {
-    public static class Logger {
-        public static void Write(string str) {
-            Console.WriteLine(string.Format("[{1}] {0}", str, DateTime.Now));
-        }
+namespace SharpChat;
 
-        public static void Write(byte[] bytes) {
-            Write(Encoding.UTF8.GetString(bytes));
-        }
+public static class Logger {
+    public static void Write(string str) {
+        Console.WriteLine(string.Format("[{1}] {0}", str, DateTime.Now));
+    }
 
-        public static void Write(object obj) {
-            Write(obj?.ToString() ?? string.Empty);
-        }
+    public static void Write(byte[] bytes) {
+        Write(Encoding.UTF8.GetString(bytes));
+    }
 
-        [Conditional("DEBUG")]
-        public static void Debug(string str) {
-            Write(str);
-        }
+    public static void Write(object obj) {
+        Write(obj?.ToString() ?? string.Empty);
+    }
 
-        [Conditional("DEBUG")]
-        public static void Debug(byte[] bytes) {
-            Write(bytes);
-        }
+    [Conditional("DEBUG")]
+    public static void Debug(string str) {
+        Write(str);
+    }
 
-        [Conditional("DEBUG")]
-        public static void Debug(object obj) {
-            Write(obj);
-        }
+    [Conditional("DEBUG")]
+    public static void Debug(byte[] bytes) {
+        Write(bytes);
+    }
+
+    [Conditional("DEBUG")]
+    public static void Debug(object obj) {
+        Write(obj);
     }
 }
diff --git a/SharpChatCommon/RNG.cs b/SharpChatCommon/RNG.cs
index c540c20..92e7863 100644
--- a/SharpChatCommon/RNG.cs
+++ b/SharpChatCommon/RNG.cs
@@ -1,82 +1,82 @@
-using System.Buffers;
+using System.Buffers;
 using System.Security.Cryptography;
 
-namespace SharpChat {
-    public static class RNG {
-        public const string CHARS = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789";
+namespace SharpChat;
 
-        private static Random NormalRandom { get; } = new();
-        private static RandomNumberGenerator SecureRandom { get; } = RandomNumberGenerator.Create();
+public static class RNG {
+    public const string CHARS = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789";
 
-        public static int Next() {
-            return NormalRandom.Next();
-        }
+    private static Random NormalRandom { get; } = new();
+    private static RandomNumberGenerator SecureRandom { get; } = RandomNumberGenerator.Create();
 
-        public static int Next(int max) {
-            return NormalRandom.Next(max);
-        }
+    public static int Next() {
+        return NormalRandom.Next();
+    }
 
-        public static int Next(int min, int max) {
-            return NormalRandom.Next(min, max);
-        }
+    public static int Next(int max) {
+        return NormalRandom.Next(max);
+    }
 
-        public static void NextBytes(byte[] buffer) {
+    public static int Next(int min, int max) {
+        return NormalRandom.Next(min, max);
+    }
+
+    public static void NextBytes(byte[] buffer) {
+        SecureRandom.GetBytes(buffer);
+    }
+
+    public static int SecureNext() {
+        return SecureNext(int.MaxValue);
+    }
+
+    public static int SecureNext(int max) {
+        return SecureNext(0, max);
+    }
+
+    public static int SecureNext(int min, int max) {
+        --max;
+        if(min == max)
+            return min;
+
+        uint umax = (uint)max - (uint)min;
+        uint num;
+
+        byte[] buffer = ArrayPool<byte>.Shared.Rent(4);
+        try {
             SecureRandom.GetBytes(buffer);
-        }
+            num = BitConverter.ToUInt32(buffer);
 
-        public static int SecureNext() {
-            return SecureNext(int.MaxValue);
-        }
+            if(umax != uint.MaxValue) {
+                ++umax;
 
-        public static int SecureNext(int max) {
-            return SecureNext(0, max);
-        }
+                if((umax & (umax - 1)) != 0) {
+                    uint limit = uint.MaxValue - (uint.MaxValue & umax) - 1;
 
-        public static int SecureNext(int min, int max) {
-            --max;
-            if(min == max)
-                return min;
-
-            uint umax = (uint)max - (uint)min;
-            uint num;
-
-            byte[] buffer = ArrayPool<byte>.Shared.Rent(4);
-            try {
-                SecureRandom.GetBytes(buffer);
-                num = BitConverter.ToUInt32(buffer);
-
-                if(umax != uint.MaxValue) {
-                    ++umax;
-
-                    if((umax & (umax - 1)) != 0) {
-                        uint limit = uint.MaxValue - (uint.MaxValue & umax) - 1;
-
-                        while(num > limit) {
-                            SecureRandom.GetBytes(buffer);
-                            num = BitConverter.ToUInt32(buffer);
-                        }
+                    while(num > limit) {
+                        SecureRandom.GetBytes(buffer);
+                        num = BitConverter.ToUInt32(buffer);
                     }
                 }
-            } finally {
-                ArrayPool<byte>.Shared.Return(buffer);
             }
-
-            return (int)((num % umax) + min);
+        } finally {
+            ArrayPool<byte>.Shared.Return(buffer);
         }
 
-        private static string RandomStringInternal(Func<int, int> next, int length) {
-            char[] str = new char[length];
-            for(int i = 0; i < length; ++i)
-                str[i] = CHARS[next(CHARS.Length)];
-            return new string(str);
-        }
+        return (int)((num % umax) + min);
+    }
 
-        public static string RandomString(int length) {
-            return RandomStringInternal(Next, length);
-        }
+    private static string RandomStringInternal(Func<int, int> next, int length) {
+        char[] str = new char[length];
+        for(int i = 0; i < length; ++i)
+            str[i] = CHARS[next(CHARS.Length)];
+        return new string(str);
+    }
 
-        public static string SecureRandomString(int length) {
-            return RandomStringInternal(SecureNext, length);
-        }
+    public static string RandomString(int length) {
+        return RandomStringInternal(Next, length);
+    }
+
+    public static string SecureRandomString(int length) {
+        return RandomStringInternal(SecureNext, length);
     }
 }
diff --git a/SharpChatCommon/RateLimiter.cs b/SharpChatCommon/RateLimiter.cs
index f1cd118..16493ed 100644
--- a/SharpChatCommon/RateLimiter.cs
+++ b/SharpChatCommon/RateLimiter.cs
@@ -1,35 +1,35 @@
-namespace SharpChat {
-    public class RateLimiter {
-        private readonly int Size;
-        private readonly int MinimumDelay;
-        private readonly int RiskyOffset;
-        private readonly long[] TimePoints;
+namespace SharpChat;
 
-        public RateLimiter(int size, int minDelay, int riskyOffset = 0) {
-            if(size < 2)
-                throw new ArgumentException("Size is too small.", nameof(size));
-            if(minDelay < 1000)
-                throw new ArgumentException("Minimum delay is inhuman.", nameof(minDelay));
-            if(riskyOffset != 0) {
-                if(riskyOffset >= size)
-                    throw new ArgumentException("Risky offset may not be greater or equal to the size.", nameof(riskyOffset));
-                else if(riskyOffset < 0)
-                    throw new ArgumentException("Risky offset may not be negative.", nameof(riskyOffset));
-            }
+public class RateLimiter {
+    private readonly int Size;
+    private readonly int MinimumDelay;
+    private readonly int RiskyOffset;
+    private readonly long[] TimePoints;
 
-            Size = size;
-            MinimumDelay = minDelay;
-            RiskyOffset = riskyOffset;
-            TimePoints = new long[Size];
+    public RateLimiter(int size, int minDelay, int riskyOffset = 0) {
+        if(size < 2)
+            throw new ArgumentException("Size is too small.", nameof(size));
+        if(minDelay < 1000)
+            throw new ArgumentException("Minimum delay is inhuman.", nameof(minDelay));
+        if(riskyOffset != 0) {
+            if(riskyOffset >= size)
+                throw new ArgumentException("Risky offset may not be greater or equal to the size.", nameof(riskyOffset));
+            else if(riskyOffset < 0)
+                throw new ArgumentException("Risky offset may not be negative.", nameof(riskyOffset));
         }
 
-        public bool IsRisky => TimePoints[RiskyOffset] != 0 && TimePoints[RiskyOffset + 1] != 0 && TimePoints[RiskyOffset] + MinimumDelay >= TimePoints[Size - 1];
-        public bool IsExceeded => TimePoints[0] != 0 && TimePoints[1] != 0 && TimePoints[0] + MinimumDelay >= TimePoints[Size - 1];
+        Size = size;
+        MinimumDelay = minDelay;
+        RiskyOffset = riskyOffset;
+        TimePoints = new long[Size];
+    }
 
-        public void Update() {
-            for(int i = 1; i < Size; ++i)
-                TimePoints[i - 1] = TimePoints[i];
-            TimePoints[Size - 1] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
-        }
+    public bool IsRisky => TimePoints[RiskyOffset] != 0 && TimePoints[RiskyOffset + 1] != 0 && TimePoints[RiskyOffset] + MinimumDelay >= TimePoints[Size - 1];
+    public bool IsExceeded => TimePoints[0] != 0 && TimePoints[1] != 0 && TimePoints[0] + MinimumDelay >= TimePoints[Size - 1];
+
+    public void Update() {
+        for(int i = 1; i < Size; ++i)
+            TimePoints[i - 1] = TimePoints[i];
+        TimePoints[Size - 1] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
     }
 }
diff --git a/SharpChatCommon/SharpInfo.cs b/SharpChatCommon/SharpInfo.cs
index c187d98..f234e46 100644
--- a/SharpChatCommon/SharpInfo.cs
+++ b/SharpChatCommon/SharpInfo.cs
@@ -1,36 +1,36 @@
 using System.Reflection;
 using System.Text;
 
-namespace SharpChat {
-    public static class SharpInfo {
-        private const string NAME = @"SharpChat";
-        private const string UNKNOWN = @"XXXXXXXXXX";
+namespace SharpChat;
 
-        public static string VersionString { get; }
-        public static string VersionStringShort { get; }
-        public static bool IsDebugBuild { get; }
+public static class SharpInfo {
+    private const string NAME = @"SharpChat";
+    private const string UNKNOWN = @"XXXXXXXXXX";
 
-        public static string ProgramName { get; }
+    public static string VersionString { get; }
+    public static string VersionStringShort { get; }
+    public static bool IsDebugBuild { get; }
 
-        static SharpInfo() {
+    public static string ProgramName { get; }
+
+    static SharpInfo() {
 #if DEBUG
-            IsDebugBuild = true;
+        IsDebugBuild = true;
 #endif
 
-            try {
-                using Stream s = Assembly.GetEntryAssembly()!.GetManifestResourceStream(@"SharpChat.version.txt")!;
-                using StreamReader sr = new(s);
-                VersionString = sr.ReadLine()!.Trim();
-                VersionStringShort = VersionString.Length > 10 ? VersionString[..10] : VersionString;
-            } catch {
-                VersionStringShort = VersionString = UNKNOWN;
-            }
-
-            StringBuilder sb = new();
-            sb.Append(NAME);
-            sb.Append('/');
-            sb.Append(VersionStringShort);
-            ProgramName = sb.ToString();
+        try {
+            using Stream s = Assembly.GetEntryAssembly()!.GetManifestResourceStream(@"SharpChat.version.txt")!;
+            using StreamReader sr = new(s);
+            VersionString = sr.ReadLine()!.Trim();
+            VersionStringShort = VersionString.Length > 10 ? VersionString[..10] : VersionString;
+        } catch {
+            VersionStringShort = VersionString = UNKNOWN;
         }
+
+        StringBuilder sb = new();
+        sb.Append(NAME);
+        sb.Append('/');
+        sb.Append(VersionStringShort);
+        ProgramName = sb.ToString();
     }
 }
diff --git a/SharpChatCommon/Snowflake/RandomSnowflake.cs b/SharpChatCommon/Snowflake/RandomSnowflake.cs
index 02f149e..2030bf1 100644
--- a/SharpChatCommon/Snowflake/RandomSnowflake.cs
+++ b/SharpChatCommon/Snowflake/RandomSnowflake.cs
@@ -1,13 +1,13 @@
-using System.Security.Cryptography;
+using System.Security.Cryptography;
 
-namespace SharpChat.Snowflake {
-    public class RandomSnowflake(
-        SnowflakeGenerator? generator = null
-    ) {
-        public readonly SnowflakeGenerator Generator = generator ?? new SnowflakeGenerator();
+namespace SharpChat.Snowflake;
 
-        public long Next(DateTimeOffset? at = null) {
-            return Generator.Next(Math.Abs(BitConverter.ToInt64(RandomNumberGenerator.GetBytes(8))), at);
-        }
+public class RandomSnowflake(
+    SnowflakeGenerator? generator = null
+) {
+    public readonly SnowflakeGenerator Generator = generator ?? new SnowflakeGenerator();
+
+    public long Next(DateTimeOffset? at = null) {
+        return Generator.Next(Math.Abs(BitConverter.ToInt64(RandomNumberGenerator.GetBytes(8))), at);
     }
 }
diff --git a/SharpChatCommon/Snowflake/SnowflakeGenerator.cs b/SharpChatCommon/Snowflake/SnowflakeGenerator.cs
index 57eadf4..dcc66f1 100644
--- a/SharpChatCommon/Snowflake/SnowflakeGenerator.cs
+++ b/SharpChatCommon/Snowflake/SnowflakeGenerator.cs
@@ -1,35 +1,35 @@
-namespace SharpChat.Snowflake {
-    public class SnowflakeGenerator {
-        public const long MASK = 0x7FFFFFFFFFFFFFFF;
-        // previous default epoch was 1588377600000, but snowflakes are much larger than SharpIds
-        public const long EPOCH = 1356998400000;
-        public const byte SHIFT = 16;
+namespace SharpChat.Snowflake;
 
-        public readonly long Epoch;
-        public readonly byte Shift;
-        public readonly long TimestampMask;
-        public readonly long SequenceMask;
+public class SnowflakeGenerator {
+    public const long MASK = 0x7FFFFFFFFFFFFFFF;
+    // previous default epoch was 1588377600000, but snowflakes are much larger than SharpIds
+    public const long EPOCH = 1356998400000;
+    public const byte SHIFT = 16;
 
-        public SnowflakeGenerator(long epoch = EPOCH, byte shift = SHIFT) {
-            if(epoch is < 0 or > MASK)
-                throw new ArgumentException("Epoch must be a positive int64.", nameof(epoch));
-            if(shift is < 1 or > 63)
-                throw new ArgumentException("Shift must be between or equal to 1 and 63", nameof(shift));
+    public readonly long Epoch;
+    public readonly byte Shift;
+    public readonly long TimestampMask;
+    public readonly long SequenceMask;
 
-            Epoch = epoch;
-            Shift = shift;
+    public SnowflakeGenerator(long epoch = EPOCH, byte shift = SHIFT) {
+        if(epoch is < 0 or > MASK)
+            throw new ArgumentException("Epoch must be a positive int64.", nameof(epoch));
+        if(shift is < 1 or > 63)
+            throw new ArgumentException("Shift must be between or equal to 1 and 63", nameof(shift));
 
-            // i think Index only does this as a hack for how integers work in PHP but its gonna run Once per application instance lol
-            TimestampMask = ~(~0L << (63 - shift));
-            SequenceMask = ~(~0L << shift);
-        }
+        Epoch = epoch;
+        Shift = shift;
 
-        public long Now(DateTimeOffset? at = null) {
-            return Math.Max(0, (at ?? DateTimeOffset.UtcNow).ToUnixTimeMilliseconds() - Epoch);
-        }
+        // i think Index only does this as a hack for how integers work in PHP but its gonna run Once per application instance lol
+        TimestampMask = ~(~0L << (63 - shift));
+        SequenceMask = ~(~0L << shift);
+    }
 
-        public long Next(long sequence, DateTimeOffset? at = null) {
-            return ((Now(at) & TimestampMask) << Shift) | (sequence & SequenceMask);
-        }
+    public long Now(DateTimeOffset? at = null) {
+        return Math.Max(0, (at ?? DateTimeOffset.UtcNow).ToUnixTimeMilliseconds() - Epoch);
+    }
+
+    public long Next(long sequence, DateTimeOffset? at = null) {
+        return ((Now(at) & TimestampMask) << Shift) | (sequence & SequenceMask);
     }
 }
diff --git a/SharpChatCommon/UserPermissions.cs b/SharpChatCommon/UserPermissions.cs
index 4104501..2424083 100644
--- a/SharpChatCommon/UserPermissions.cs
+++ b/SharpChatCommon/UserPermissions.cs
@@ -1,24 +1,24 @@
-namespace SharpChat {
-    [Flags]
-    public enum UserPermissions : int {
-        KickUser            = 0x00000001,
-        BanUser             = 0x00000002,
-        //SilenceUser       = 0x00000004,
-        Broadcast           = 0x00000008,
-        SetOwnNickname      = 0x00000010,
-        SetOthersNickname   = 0x00000020,
-        CreateChannel       = 0x00000040,
-        DeleteChannel       = 0x00010000,
-        SetChannelPermanent = 0x00000080,
-        SetChannelPassword  = 0x00000100,
-        SetChannelHierarchy = 0x00000200,
-        JoinAnyChannel      = 0x00020000,
-        SendMessage         = 0x00000400,
-        DeleteOwnMessage    = 0x00000800,
-        DeleteAnyMessage    = 0x00001000,
-        EditOwnMessage      = 0x00002000,
-        EditAnyMessage      = 0x00004000,
-        SeeIPAddress        = 0x00008000,
-        ViewLogs            = 0x00040000,
-    }
+namespace SharpChat;
+
+[Flags]
+public enum UserPermissions : int {
+    KickUser            = 0x00000001,
+    BanUser             = 0x00000002,
+    //SilenceUser       = 0x00000004,
+    Broadcast           = 0x00000008,
+    SetOwnNickname      = 0x00000010,
+    SetOthersNickname   = 0x00000020,
+    CreateChannel       = 0x00000040,
+    DeleteChannel       = 0x00010000,
+    SetChannelPermanent = 0x00000080,
+    SetChannelPassword  = 0x00000100,
+    SetChannelHierarchy = 0x00000200,
+    JoinAnyChannel      = 0x00020000,
+    SendMessage         = 0x00000400,
+    DeleteOwnMessage    = 0x00000800,
+    DeleteAnyMessage    = 0x00001000,
+    EditOwnMessage      = 0x00002000,
+    EditAnyMessage      = 0x00004000,
+    SeeIPAddress        = 0x00008000,
+    ViewLogs            = 0x00040000,
 }