about summary refs log tree commit diff
path: root/IrcStates
diff options
context:
space:
mode:
Diffstat (limited to 'IrcStates')
-rw-r--r--IrcStates/Casemap.cs38
-rw-r--r--IrcStates/Channel.cs67
-rw-r--r--IrcStates/ChannelUser.cs32
-rw-r--r--IrcStates/Commands.cs22
-rw-r--r--IrcStates/Emit.cs24
-rw-r--r--IrcStates/Extensions.cs40
-rw-r--r--IrcStates/ISupport.cs114
-rw-r--r--IrcStates/ISupportChanModes.cs33
-rw-r--r--IrcStates/ISupportPrefix.cs44
-rw-r--r--IrcStates/IrcStates.csproj21
-rw-r--r--IrcStates/Numeric.cs54
-rw-r--r--IrcStates/README.md82
-rw-r--r--IrcStates/Server.cs941
-rw-r--r--IrcStates/ServerDisconnectedException.cs8
-rw-r--r--IrcStates/ServerException.cs8
-rw-r--r--IrcStates/Tests/Cap.cs131
-rw-r--r--IrcStates/Tests/Casemap.cs58
-rw-r--r--IrcStates/Tests/Channel.cs202
-rw-r--r--IrcStates/Tests/Emit.cs117
-rw-r--r--IrcStates/Tests/ISupport.cs210
-rw-r--r--IrcStates/Tests/Mode.cs179
-rw-r--r--IrcStates/Tests/Motd.cs23
-rw-r--r--IrcStates/Tests/Sasl.cs38
-rw-r--r--IrcStates/Tests/User.cs298
-rw-r--r--IrcStates/Tests/Who.cs61
-rw-r--r--IrcStates/User.cs33
26 files changed, 0 insertions, 2878 deletions
diff --git a/IrcStates/Casemap.cs b/IrcStates/Casemap.cs
deleted file mode 100644
index 67867c5..0000000
--- a/IrcStates/Casemap.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-
-namespace IrcStates
-{
-    public static class Casemap
-    {
-        public enum CaseMapping
-        {
-            Rfc1459,
-            Ascii
-        }
-
-        private const string AsciiUpperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
-        private const string AsciiLowerChars = "abcdefghijklmnopqrstuvwxyz";
-        private const string Rfc1459UpperChars = AsciiUpperChars + @"[]~\";
-        private const string Rfc1459LowerChars = AsciiLowerChars + @"{}^|";
-
-        private static string Replace(string s, string upper, string lower)
-        {
-            for (var i = 0; i < upper.Length; ++i) s = s.Replace(upper[i], lower[i]);
-
-            return s;
-        }
-
-        public static string CaseFold(CaseMapping mapping, string s)
-        {
-            if (s != null)
-                return mapping switch
-                {
-                    CaseMapping.Rfc1459 => Replace(s, Rfc1459UpperChars, Rfc1459LowerChars),
-                    CaseMapping.Ascii   => Replace(s, AsciiUpperChars, AsciiLowerChars),
-                    _                   => throw new ArgumentOutOfRangeException(nameof(mapping), mapping, null)
-                };
-
-            return string.Empty;
-        }
-    }
-}
diff --git a/IrcStates/Channel.cs b/IrcStates/Channel.cs
deleted file mode 100644
index 21ebb25..0000000
--- a/IrcStates/Channel.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace IrcStates
-{
-    public class Channel
-    {
-        public Channel()
-        {
-            Users     = new Dictionary<string, ChannelUser>();
-            ListModes = new Dictionary<string, List<string>>();
-            Modes     = new Dictionary<string, string>();
-        }
-
-        public string Name { get; set; }
-        public string NameLower { get; set; }
-        public Dictionary<string, ChannelUser> Users { get; set; }
-        public string Topic { get; set; }
-        public string TopicSetter { get; set; }
-        public DateTime TopicTime { get; set; }
-        public DateTime Created { get; set; }
-        public Dictionary<string, List<string>> ListModes { get; set; }
-        public Dictionary<string, string> Modes { get; set; }
-
-        public override string ToString()
-        {
-            return $"Channel(name={Name})";
-        }
-
-        public void SetName(string name, string nameLower)
-        {
-            Name      = name;
-            NameLower = nameLower;
-        }
-
-        public void AddMode(string ch, string param, bool listMode)
-        {
-            if (listMode)
-            {
-                if (!ListModes.ContainsKey(ch)) ListModes[ch] = new List<string>();
-
-                if (!ListModes[ch].Contains(param)) ListModes[ch].Add(param ?? string.Empty);
-            }
-            else
-            {
-                Modes[ch] = param;
-            }
-        }
-
-        public void RemoveMode(string ch, string param)
-        {
-            if (ListModes.ContainsKey(ch))
-            {
-                if (ListModes[ch].Contains(param))
-                {
-                    ListModes[ch].Remove(param);
-                    if (!ListModes[ch].Any()) ListModes.Remove(ch);
-                }
-            }
-            else if (Modes.ContainsKey(ch))
-            {
-                Modes.Remove(ch);
-            }
-        }
-    }
-}
diff --git a/IrcStates/ChannelUser.cs b/IrcStates/ChannelUser.cs
deleted file mode 100644
index fab881c..0000000
--- a/IrcStates/ChannelUser.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System.Collections.Generic;
-
-namespace IrcStates
-{
-    public class ChannelUser
-    {
-        public ChannelUser()
-        {
-            Modes = new List<string>();
-        }
-
-        public List<string> Modes { get; set; }
-
-        protected bool Equals(ChannelUser other)
-        {
-            return other != null && Equals(Modes, other.Modes);
-        }
-
-        public override bool Equals(object obj)
-        {
-            if (ReferenceEquals(null, obj)) return false;
-            if (ReferenceEquals(this, obj)) return true;
-            if (obj.GetType() != GetType()) return false;
-            return Equals((ChannelUser) obj);
-        }
-
-        public override int GetHashCode()
-        {
-            return Modes != null ? Modes.GetHashCode() : 0;
-        }
-    }
-}
diff --git a/IrcStates/Commands.cs b/IrcStates/Commands.cs
deleted file mode 100644
index b5bc358..0000000
--- a/IrcStates/Commands.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace IrcStates
-{
-    public static class Commands
-    {
-        public const string Nick = "NICK";
-        public const string Join = "JOIN";
-        public const string Mode = "MODE";
-        public const string Part = "PART";
-        public const string Kick = "KICK";
-        public const string Quit = "QUIT";
-        public const string Error = "ERROR";
-        public const string Topic = "TOPIC";
-        public const string Privmsg = "PRIVMSG";
-        public const string Notice = "NOTICE";
-        public const string Tagmsg = "TAGMSG";
-        public const string Chghost = "CHGHOST";
-        public const string Setname = "SETNAME";
-        public const string Away = "AWAY";
-        public const string Account = "ACCOUNT";
-        public const string Cap = "CAP";
-    }
-}
diff --git a/IrcStates/Emit.cs b/IrcStates/Emit.cs
deleted file mode 100644
index 9ca9cb5..0000000
--- a/IrcStates/Emit.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System.Collections.Generic;
-
-namespace IrcStates
-{
-    public class Emit
-    {
-        public string Command { get; set; }
-        public string Subcommand { get; set; }
-        public string Text { get; set; }
-        public List<string> Tokens { get; set; }
-        public bool Finished { get; set; }
-        public bool Self { get; set; }
-        public bool SelfSource { get; set; }
-        public bool SelfTarget { get; set; }
-        public User User { get; set; }
-        public User UserSource { get; set; }
-        public User UserTarget { get; set; }
-        public List<User> Users { get; set; }
-        public Channel Channel { get; set; }
-        public Channel ChannelSource { get; set; }
-        public Channel ChannelTarget { get; set; }
-        public string Target { get; set; }
-    }
-}
diff --git a/IrcStates/Extensions.cs b/IrcStates/Extensions.cs
deleted file mode 100644
index 181ac80..0000000
--- a/IrcStates/Extensions.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Reflection;
-
-namespace IrcStates
-{
-    public static class Extensions
-    {
-        public static Delegate CreateDelegate(this MethodInfo methodInfo, object target)
-        {
-            if (methodInfo == null) return null;
-
-            var types = methodInfo.GetParameters().Select(p => p.ParameterType);
-
-            Func<Type[], Type> getType;
-            if (methodInfo.ReturnType == typeof(void))
-            {
-                getType = Expression.GetActionType;
-            }
-            else
-            {
-                getType = Expression.GetFuncType;
-                types   = types.Concat(new[] {methodInfo.ReturnType});
-            }
-
-            return methodInfo.IsStatic
-                ? Delegate.CreateDelegate(getType(types.ToArray()), methodInfo)
-                : Delegate.CreateDelegate(getType(types.ToArray()), target, methodInfo);
-        }
-
-        public static void UpdateWith<TKey, TValue>(this Dictionary<TKey, TValue> dict, Dictionary<TKey, TValue> other)
-        {
-            if (dict == null || other == null || !other.Any()) return;
-
-            foreach (var (key, value) in other) dict[key] = value;
-        }
-    }
-}
diff --git a/IrcStates/ISupport.cs b/IrcStates/ISupport.cs
deleted file mode 100644
index f481f5f..0000000
--- a/IrcStates/ISupport.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-// ReSharper disable InconsistentNaming
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-
-namespace IrcStates
-{
-    public class ISupport
-    {
-        public ISupport()
-        {
-            Raw         = new Dictionary<string, string>();
-            Modes       = 3;
-            CaseMapping = Casemap.CaseMapping.Rfc1459;
-            Prefix      = new ISupportPrefix("(ov)@+");
-            ChanModes   = new ISupportChanModes("b,k,l,imnpst");
-            ChanTypes   = new List<string> {"#"};
-            StatusMsg   = new List<string>();
-            Whox        = false;
-        }
-
-        public Dictionary<string, string> Raw { get; set; }
-        public string Network { get; set; }
-        public ISupportChanModes ChanModes { get; set; }
-        public ISupportPrefix Prefix { get; set; }
-        public int? Modes { get; set; }
-        public Casemap.CaseMapping CaseMapping { get; set; }
-        public List<string> ChanTypes { get; set; }
-        public List<string> StatusMsg { get; set; }
-        public string CallerId { get; set; }
-        public string Excepts { get; set; }
-        public string Invex { get; set; }
-        public int? Monitor { get; set; }
-        public int? Watch { get; set; }
-        public bool Whox { get; set; }
-
-        public void Parse(IEnumerable<string> tokens)
-        {
-            if (tokens == null) return;
-
-            // remove first and last
-            tokens = tokens.Skip(1).SkipLast(1);
-
-            foreach (var token in tokens)
-            {
-                var split = token.Split('=', 2);
-                var key   = split[0];
-
-                var value = string.Empty;
-                if (split.Length > 1)
-                {
-                    value = split[1];
-                    Raw[key] = value;
-                }
-                
-                switch (split[0])
-                {
-                    case "NETWORK":
-                        Network = value;
-                        break;
-                    case "CHANMODES":
-                        ChanModes = new ISupportChanModes(value);
-                        break;
-                    case "PREFIX":
-                        Prefix = new ISupportPrefix(value);
-                        break;
-                    case "STATUSMSG":
-                        StatusMsg = new List<string>();
-                        StatusMsg.AddRange(value.Select(c => c.ToString(CultureInfo.InvariantCulture)));
-                        break;
-                    case "MODES":
-                        if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var modes))
-                            Modes = modes;
-                        else
-                            Modes = -1;
-                        break;
-                    case "MONITOR":
-                        if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var monitor))
-                            Monitor = monitor;
-                        else
-                            Monitor = -1;
-                        break;
-                    case "WATCH":
-                        if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var watch))
-                            Watch = watch;
-                        else
-                            Watch = -1;
-                        break;
-                    case "CASEMAPPING":
-                        if (Enum.TryParse(value, true, out Casemap.CaseMapping caseMapping)) CaseMapping = caseMapping;
-                        break;
-                    case "CHANTYPES":
-                        ChanTypes = new List<string>();
-                        ChanTypes.AddRange(value.Select(c => c.ToString(CultureInfo.InvariantCulture)));
-                        break;
-                    case "CALLERID":
-                        CallerId = string.IsNullOrEmpty(value) ? "g" : value;
-                        break;
-                    case "EXCEPTS":
-                        Excepts = string.IsNullOrEmpty(value) ? "e" : value;
-                        break;
-                    case "INVEX":
-                        Invex = string.IsNullOrEmpty(value) ? "I" : value;
-                        break;
-                    case "WHOX":
-                        Whox = true;
-                        break;
-                }
-            }
-        }
-    }
-}
diff --git a/IrcStates/ISupportChanModes.cs b/IrcStates/ISupportChanModes.cs
deleted file mode 100644
index 74a0579..0000000
--- a/IrcStates/ISupportChanModes.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-
-namespace IrcStates
-{
-    public class ISupportChanModes
-    {
-        public ISupportChanModes(string splitVal)
-        {
-            if (splitVal == null) return;
-
-            var split = splitVal.Split(',', 4);
-            
-            ListModes = new List<string>();
-            ListModes.AddRange(split[0].Select(c => c.ToString(CultureInfo.InvariantCulture)));
-            
-            SettingBModes = new List<string>();
-            SettingBModes.AddRange(split[1].Select(c => c.ToString(CultureInfo.InvariantCulture)));
-            
-            SettingCModes = new List<string>();
-            SettingCModes.AddRange(split[2].Select(c => c.ToString(CultureInfo.InvariantCulture)));
-            
-            SettingDModes = new List<string>();
-            SettingDModes.AddRange(split[3].Select(c => c.ToString(CultureInfo.InvariantCulture)));
-        }
-
-        public List<string> ListModes { get; set; }
-        public List<string> SettingBModes { get; set; }
-        public List<string> SettingCModes { get; set; }
-        public List<string> SettingDModes { get; set; }
-    }
-}
diff --git a/IrcStates/ISupportPrefix.cs b/IrcStates/ISupportPrefix.cs
deleted file mode 100644
index fb5114b..0000000
--- a/IrcStates/ISupportPrefix.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-
-namespace IrcStates
-{
-    public class ISupportPrefix
-    {
-        public ISupportPrefix(string splitVal)
-        {
-            if (splitVal == null) throw new ArgumentNullException(nameof(splitVal));
-
-            var split = splitVal.Substring(1).Split(')', 2);
-            Modes = new List<string>();
-            Modes.AddRange(split[0].Select(c => c.ToString(CultureInfo.InvariantCulture)));
-            Prefixes = new List<string>();
-            Prefixes.AddRange(split[1].Select(c => c.ToString(CultureInfo.InvariantCulture)));
-        }
-
-        public List<string> Modes { get; set; }
-        public List<string> Prefixes { get; set; }
-
-        public string FromMode(char mode)
-        {
-            return FromMode(mode.ToString(CultureInfo.InvariantCulture));
-        }
-
-        public string FromMode(string mode)
-        {
-            return Modes.Contains(mode) ? Prefixes[Modes.IndexOf(mode)] : null;
-        }
-
-        public string FromPrefix(char prefix)
-        {
-            return FromPrefix(prefix.ToString(CultureInfo.InvariantCulture));
-        }
-
-        public string FromPrefix(string prefix)
-        {
-            return Prefixes.Contains(prefix) ? Modes[Prefixes.IndexOf(prefix)] : null;
-        }
-    }
-}
diff --git a/IrcStates/IrcStates.csproj b/IrcStates/IrcStates.csproj
deleted file mode 100644
index 7500c8b..0000000
--- a/IrcStates/IrcStates.csproj
+++ /dev/null
@@ -1,21 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
-      <PrivateAssets>all</PrivateAssets>
-      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
-    </PackageReference>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.0" />
-    <PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
-    <PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <ProjectReference Include="..\IrcTokens\IrcTokens.csproj" />
-  </ItemGroup>
-
-</Project>
diff --git a/IrcStates/Numeric.cs b/IrcStates/Numeric.cs
deleted file mode 100644
index 8639c12..0000000
--- a/IrcStates/Numeric.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-// ReSharper disable InconsistentNaming
-
-namespace IrcStates
-{
-    public static class Numeric
-    {
-#pragma warning disable CA1707 // Identifiers should not contain underscores
-        public const string RPL_WELCOME = "001";
-        public const string RPL_ISUPPORT = "005";
-        public const string RPL_MOTD = "372";
-        public const string RPL_MOTDSTART = "375";
-        public const string RPL_UMODEIS = "221";
-        public const string RPL_VISIBLEHOST = "396";
-
-        public const string RPL_CHANNELMODEIS = "324";
-        public const string RPL_CREATIONTIME = "329";
-        public const string RPL_TOPIC = "332";
-        public const string RPL_TOPICWHOTIME = "333";
-
-        public const string RPL_WHOREPLY = "352";
-        public const string RPL_WHOSPCRPL = "354";
-        public const string RPL_ENDOFWHO = "315";
-        public const string RPL_NAMREPLY = "353";
-        public const string RPL_ENDOFNAMES = "366";
-
-        public const string RPL_BANLIST = "367";
-        public const string RPL_ENDOFBANLIST = "368";
-        public const string RPL_QUIETLIST = "728";
-        public const string RPL_ENDOFQUIETLIST = "729";
-
-        public const string RPL_LOGGEDIN = "900";
-        public const string RPL_LOGGEDOUT = "901";
-        public const string RPL_SASLSUCCESS = "903";
-        public const string ERR_SASLFAIL = "904";
-        public const string ERR_SASLTOOLONG = "905";
-        public const string ERR_SASLABORTED = "906";
-        public const string ERR_SASLALREADY = "907";
-        public const string RPL_SASLMECHS = "908";
-
-        public const string RPL_WHOISUSER = "311";
-        public const string RPL_WHOISSERVER = "312";
-        public const string RPL_WHOISOPERATOR = "313";
-        public const string RPL_WHOISIDLE = "317";
-        public const string RPL_WHOISCHANNELS = "319";
-        public const string RPL_WHOISACCOUNT = "330";
-        public const string RPL_WHOISHOST = "378";
-        public const string RPL_WHOISMODES = "379";
-        public const string RPL_WHOISSECURE = "671";
-        public const string RPL_ENDOFWHOIS = "318";
-
-        public const string ERR_NOSUCHCHANNEL = "403";
-#pragma warning restore CA1707 // Identifiers should not contain underscores
-    }
-}
diff --git a/IrcStates/README.md b/IrcStates/README.md
deleted file mode 100644
index 05daa8c..0000000
--- a/IrcStates/README.md
+++ /dev/null
@@ -1,82 +0,0 @@
-# IrcStates
-
-port of [jesopo/ircstates](https://github.com/jesopo/ircstates)
-
-bare bones irc client state
-
-see the full example in [StatesSample/Client.cs](../Examples/States/Client.cs)
-
-    internal class Client
-    {
-        private readonly byte[] _bytes;
-        private readonly StatefulEncoder _encoder;
-        private readonly string _host;
-        private readonly string _nick;
-        private readonly int _port;
-        private readonly Server _server;
-        private readonly Socket _socket;
-
-        public Client(string host, int port, string nick)
-        {
-            _server  = new Server("test");
-            _socket  = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
-            _encoder = new StatefulEncoder();
-            _host    = host;
-            _port    = port;
-            _nick    = nick;
-            _bytes   = new byte[1024];
-        }
-
-        private void Send(string raw)
-        {
-            _encoder.Push(new Line(raw));
-        }
-
-        public void Start()
-        {
-            _socket.Connect(_host, _port);
-            while (!_socket.Connected) Thread.Sleep(1000);
-
-            Send("USER test 0 * test");
-            Send($"NICK {_nick}");
-
-            while (true)
-            {
-                while (_encoder.PendingBytes.Any())
-                {
-                    var bytesSent = _socket.Send(_encoder.PendingBytes);
-                    var sentLines = _encoder.Pop(bytesSent);
-                    foreach (var line in sentLines) Console.WriteLine($"> {line.Format()}");
-                }
-
-                var bytesReceived = _socket.Receive(_bytes);
-                if (bytesReceived == 0)
-                {
-                    Console.WriteLine("! disconnected");
-                    _socket.Shutdown(SocketShutdown.Both);
-                    _socket.Close();
-                    break;
-                }
-
-                var receivedLines = _server.Receive(_bytes, bytesReceived);
-                foreach (var (line, _) in receivedLines)
-                {
-                    Console.WriteLine($"< {line.Format()}");
-
-                    switch (line.Command)
-                    {
-                        case Commands.Privmsg:
-                            if (line.Params[1].Contains(_server.NickName))
-                                Send($"PRIVMSG {line.Params[0]} :hi {line.Hostmask.NickName}!");
-                            break;
-                        case "PING":
-                            Send($"PONG :{line.Params[0]}");
-                            break;
-                        case Numeric.RPL_WELCOME:
-                            if (!_server.HasChannel("#test")) Send("JOIN #test");
-                            break;
-                    }
-                }
-            }
-        }
-    }
\ No newline at end of file
diff --git a/IrcStates/Server.cs b/IrcStates/Server.cs
deleted file mode 100644
index 2e80b75..0000000
--- a/IrcStates/Server.cs
+++ /dev/null
@@ -1,941 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.Design;
-using System.Globalization;
-using System.Linq;
-using IrcTokens;
-
-namespace IrcStates
-{
-    public class Server
-    {
-        public const string WhoType = "525"; // randomly generated
-        private readonly StatefulDecoder _decoder;
-
-        private Dictionary<string, string> TempCaps;
-
-        public Server(string name)
-        {
-            Name          = name;
-            Registered    = false;
-            Modes         = new List<string>();
-            Motd          = new List<string>();
-            _decoder      = new StatefulDecoder();
-            Users         = new Dictionary<string, User>();
-            Channels      = new Dictionary<string, Channel>();
-            ISupport      = new ISupport();
-            HasCap        = false;
-            TempCaps      = new Dictionary<string, string>();
-            AvailableCaps = new Dictionary<string, string>();
-            AgreedCaps    = new List<string>();
-        }
-
-        public string Name { get; set; }
-        public string NickName { get; set; }
-        public string NickNameLower { get; set; }
-        public string UserName { get; set; }
-        public string HostName { get; set; }
-        public string RealName { get; set; }
-        public string Account { get; set; }
-        public string Away { get; set; }
-
-        public bool Registered { get; set; }
-        public List<string> Modes { get; set; }
-        public List<string> Motd { get; set; }
-        public Dictionary<string, User> Users { get; set; }
-        public Dictionary<string, Channel> Channels { get; set; }
-        public Dictionary<string, string> AvailableCaps { get; set; }
-        public List<string> AgreedCaps { get; set; }
-
-        public ISupport ISupport { get; set; }
-        public bool HasCap { get; set; }
-
-        public override string ToString()
-        {
-            return $"Server(name={Name})";
-        }
-
-        public string CaseFold(string str)
-        {
-            return Casemap.CaseFold(ISupport.CaseMapping, str);
-        }
-
-        private bool CaseFoldEquals(string s1, string s2)
-        {
-            return CaseFold(s1) == CaseFold(s2);
-        }
-
-        private bool IsMe(string nickname)
-        {
-            return CaseFold(nickname) == NickNameLower;
-        }
-
-        private bool HasUser(string nickname)
-        {
-            return Users.ContainsKey(CaseFold(nickname));
-        }
-
-        private User AddUser(string nickname, string nicknameLower)
-        {
-            var user = CreateUser(nickname, nicknameLower);
-            Users[nicknameLower] = user;
-            return user;
-        }
-
-        private User CreateUser(string nickname, string nicknameLower)
-        {
-            var user = new User();
-            user.SetNickName(nickname, nicknameLower);
-            return user;
-        }
-
-        private bool IsChannel(string target)
-        {
-            return !string.IsNullOrEmpty(target) &&
-                   ISupport.ChanTypes.Contains(target[0].ToString(CultureInfo.InvariantCulture));
-        }
-
-        public bool HasChannel(string name)
-        {
-            return Channels.ContainsKey(CaseFold(name));
-        }
-
-        private Channel GetChannel(string name)
-        {
-            return HasChannel(name) ? Channels[name] : null;
-        }
-
-        private ChannelUser UserJoin(Channel channel, User user)
-        {
-            var channelUser = new ChannelUser();
-            user.Channels.Add(CaseFold(channel.Name));
-            channel.Users[user.NickNameLower] = channelUser;
-            return channelUser;
-        }
-
-        private void SelfHostmask(Hostmask hostmask)
-        {
-            NickName = hostmask.NickName;
-            if (hostmask.UserName != null) UserName = hostmask.UserName;
-            if (hostmask.HostName != null) HostName = hostmask.HostName;
-        }
-
-        private (Emit, User) UserPart(Line line, string nickName, string channelName, int reasonIndex)
-        {
-            var emit                                            = new Emit();
-            var channelLower                                    = CaseFold(channelName);
-            if (line.Params.Count >= reasonIndex + 1) emit.Text = line.Params[reasonIndex];
-
-            User user = null;
-            if (HasChannel(channelName))
-            {
-                var channel = Channels[channelLower];
-                emit.Channel = channel;
-                var nickLower = CaseFold(nickName);
-                if (HasUser(nickLower))
-                {
-                    user = Users[nickLower];
-                    user.Channels.Remove(channelLower);
-                    channel.Users.Remove(nickLower);
-                    if (!user.Channels.Any()) Users.Remove(nickLower);
-                }
-
-                if (IsMe(nickName))
-                {
-                    Channels.Remove(channelLower);
-                    foreach (var userToRemove in channel.Users.Keys.Select(u => Users[u]))
-                    {
-                        userToRemove.Channels.Remove(channelLower);
-                        if (!userToRemove.Channels.Any()) Users.Remove(userToRemove.NickNameLower);
-                    }
-                }
-            }
-
-            return (emit, user);
-        }
-
-        private void SetChannelModes(Channel channel, IEnumerable<(bool, string)> modes, IList<string> parameters)
-        {
-            foreach (var (add, c) in modes)
-            {
-                var listMode = ISupport.ChanModes.ListModes.Contains(c);
-                if (ISupport.Prefix.Modes.Contains(c))
-                {
-                    var nicknameLower = CaseFold(parameters.First());
-                    parameters.RemoveAt(0);
-                    if (!HasUser(nicknameLower)) continue;
-
-                    var channelUser = channel.Users[nicknameLower];
-                    if (add)
-                    {
-                        if (!channelUser.Modes.Contains(c))
-                        {
-                            channelUser.Modes.Add(c);
-                        }
-                    }
-                    else if (channelUser.Modes.Contains(c))
-                    {
-                        channelUser.Modes.Remove(c);
-                    }
-                }
-                else if (add && (listMode ||
-                                 ISupport.ChanModes.SettingBModes.Contains(c) ||
-                                 ISupport.ChanModes.SettingCModes.Contains(c)))
-                {
-                    channel.AddMode(c, parameters.First(), listMode);
-                    parameters.RemoveAt(0);
-                }
-                else if (!add && (listMode || ISupport.ChanModes.SettingBModes.Contains(c)))
-                {
-                    channel.RemoveMode(c, parameters.First());
-                    parameters.RemoveAt(0);
-                }
-                else if (add)
-                {
-                    channel.AddMode(c, null, false);
-                }
-                else
-                {
-                    channel.RemoveMode(c, null);
-                }
-            }
-        }
-
-        public IEnumerable<(Line, Emit)> Receive(byte[] data, int length)
-        {
-            if (data == null) return null;
-
-            var lines = _decoder.Push(data, length);
-            if (lines == null) throw new ServerDisconnectedException();
-
-            return lines.Select(l => (l, Parse(l))).ToList();
-        }
-
-        public Emit Parse(Line line)
-        {
-            if (line == null) return null;
-
-            var emit = line.Command switch
-            {
-                Numeric.RPL_WELCOME       => HandleWelcome(line),
-                Numeric.RPL_ISUPPORT      => HandleISupport(line),
-                Numeric.RPL_MOTDSTART     => HandleMotd(line),
-                Numeric.RPL_MOTD          => HandleMotd(line),
-                Commands.Nick             => HandleNick(line),
-                Commands.Join             => HandleJoin(line),
-                Commands.Part             => HandlePart(line),
-                Commands.Kick             => HandleKick(line),
-                Commands.Quit             => HandleQuit(line),
-                Commands.Error            => HandleError(line),
-                Numeric.RPL_NAMREPLY      => HandleNames(line),
-                Numeric.RPL_CREATIONTIME  => HandleCreationTime(line),
-                Commands.Topic            => HandleTopic(line),
-                Numeric.RPL_TOPIC         => HandleTopicNumeric(line),
-                Numeric.RPL_TOPICWHOTIME  => HandleTopicTime(line),
-                Commands.Mode             => HandleMode(line),
-                Numeric.RPL_CHANNELMODEIS => HandleChannelModeIs(line),
-                Numeric.RPL_UMODEIS       => HandleUModeIs(line),
-                Commands.Privmsg          => HandleMessage(line),
-                Commands.Notice           => HandleMessage(line),
-                Commands.Tagmsg           => HandleMessage(line),
-                Numeric.RPL_VISIBLEHOST   => HandleVisibleHost(line),
-                Numeric.RPL_WHOREPLY      => HandleWhoReply(line),
-                Numeric.RPL_WHOSPCRPL     => HandleWhox(line),
-                Numeric.RPL_WHOISUSER     => HandleWhoIsUser(line),
-                Commands.Chghost          => HandleChghost(line),
-                Commands.Setname          => HandleSetname(line),
-                Commands.Away             => HandleAway(line),
-                Commands.Account          => HandleAccount(line),
-                Commands.Cap              => HandleCap(line),
-                Numeric.RPL_LOGGEDIN      => HandleLoggedIn(line),
-                Numeric.RPL_LOGGEDOUT     => HandleLoggedOut(line),
-                _                         => null
-            };
-
-            if (emit != null)
-                emit.Command = line.Command;
-            else
-                emit = new Emit();
-
-            return emit;
-        }
-
-        private Emit HandleSetname(Line line)
-        {
-            var emit          = new Emit();
-            var realname      = line.Params[0];
-            var nicknameLower = CaseFold(line.Hostmask.NickName);
-
-            if (IsMe(nicknameLower))
-            {
-                emit.Self = true;
-                RealName  = realname;
-            }
-
-            if (Users.ContainsKey(nicknameLower))
-            {
-                var user = Users[nicknameLower];
-                emit.User     = user;
-                user.RealName = realname;
-            }
-
-            return emit;
-        }
-
-        private Emit HandleAway(Line line)
-        {
-            var emit          = new Emit();
-            var away          = line.Params.FirstOrDefault();
-            var nicknameLower = CaseFold(line.Hostmask.NickName);
-
-            if (IsMe(nicknameLower))
-            {
-                emit.Self = true;
-                Away      = away;
-            }
-
-            if (Users.ContainsKey(nicknameLower))
-            {
-                var user = Users[nicknameLower];
-                emit.User = user;
-                user.Away = away;
-            }
-
-            return emit;
-        }
-
-        private Emit HandleAccount(Line line)
-        {
-            var emit          = new Emit();
-            var account       = line.Params[0].Trim('*');
-            var nicknameLower = CaseFold(line.Hostmask.NickName);
-
-            if (IsMe(nicknameLower))
-            {
-                emit.Self = true;
-                Account   = account;
-            }
-
-            if (Users.ContainsKey(nicknameLower))
-            {
-                var user = Users[nicknameLower];
-                emit.User    = user;
-                user.Account = account;
-            }
-
-            return emit;
-        }
-
-        private Emit HandleCap(Line line)
-        {
-            HasCap = true;
-            var subcommand = line.Params[1].ToUpperInvariant();
-            var multiline  = line.Params[2] == "*";
-            var caps       = line.Params[multiline ? 3 : 2];
-
-            var tokens    = new Dictionary<string, string>();
-            var tokensStr = new List<string>();
-            foreach (var cap in caps.Split(' ', StringSplitOptions.RemoveEmptyEntries))
-            {
-                tokensStr.Add(cap);
-                var kv = cap.Split('=', 2);
-                tokens[kv[0]] = kv.Length > 1 ? kv[1] : string.Empty;
-            }
-
-            var emit = new Emit {Subcommand = subcommand, Finished = !multiline, Tokens = tokensStr};
-
-            switch (subcommand)
-            {
-                case "LS":
-                    TempCaps.UpdateWith(tokens);
-                    if (!multiline)
-                    {
-                        AvailableCaps.UpdateWith(TempCaps);
-                        TempCaps.Clear();
-                    }
-
-                    break;
-                case "NEW":
-                    AvailableCaps.UpdateWith(tokens);
-                    break;
-                case "DEL":
-                    foreach (var key in tokens.Keys.Where(key => AvailableCaps.ContainsKey(key)))
-                    {
-                        AvailableCaps.Remove(key);
-                        if (AgreedCaps.Contains(key)) AgreedCaps.Remove(key);
-                    }
-
-                    break;
-                case "ACK":
-                    foreach (var key in tokens.Keys)
-                        if (key.StartsWith('-'))
-                        {
-                            var k = key.Substring(1);
-                            if (AgreedCaps.Contains(k)) AgreedCaps.Remove(k);
-                        }
-                        else if (!AgreedCaps.Contains(key) && AvailableCaps.ContainsKey(key))
-                        {
-                            AgreedCaps.Add(key);
-                        }
-
-                    break;
-            }
-
-            return emit;
-        }
-
-        private Emit HandleLoggedIn(Line line)
-        {
-            SelfHostmask(new Hostmask(line.Params[1]));
-            Account = line.Params[2];
-            return new Emit();
-        }
-
-        private Emit HandleChghost(Line line)
-        {
-            var emit          = new Emit();
-            var username      = line.Params[0];
-            var hostname      = line.Params[1];
-            var nicknameLower = CaseFold(line.Hostmask.NickName);
-
-            if (IsMe(nicknameLower))
-            {
-                emit.Self = true;
-                UserName  = username;
-                HostName  = hostname;
-            }
-
-            if (Users.ContainsKey(nicknameLower))
-            {
-                var user = Users[nicknameLower];
-                emit.User     = user;
-                user.UserName = username;
-                user.HostName = hostname;
-            }
-
-            return emit;
-        }
-
-        private Emit HandleWhoIsUser(Line line)
-        {
-            var emit     = new Emit();
-            var nickname = line.Params[1];
-            var username = line.Params[2];
-            var hostname = line.Params[3];
-            var realname = line.Params[5];
-
-            if (IsMe(nickname))
-            {
-                emit.Self = true;
-                UserName  = username;
-                HostName  = hostname;
-                RealName  = realname;
-            }
-
-            if (HasUser(nickname))
-            {
-                var user = Users[CaseFold(nickname)];
-                emit.User     = user;
-                user.UserName = username;
-                user.HostName = hostname;
-                user.RealName = realname;
-            }
-
-            return emit;
-        }
-
-        private Emit HandleWhox(Line line)
-        {
-            var emit = new Emit();
-            if (line.Params[1] == WhoType && line.Params.Count == 8)
-            {
-                var nickname = line.Params[5];
-                var username = line.Params[2];
-                var hostname = line.Params[4];
-                var realname = line.Params[7];
-                var account  = line.Params[6] == "0" ? null : line.Params[6];
-
-                if (IsMe(nickname))
-                {
-                    emit.Self = true;
-                    UserName  = username;
-                    HostName  = hostname;
-                    RealName  = realname;
-                    Account   = account;
-                }
-
-                if (HasUser(nickname))
-                {
-                    var user = Users[CaseFold(nickname)];
-                    emit.User     = user;
-                    user.UserName = username;
-                    user.HostName = hostname;
-                    user.RealName = realname;
-                    user.Account  = account;
-                }
-            }
-
-            return emit;
-        }
-
-        private Emit HandleWhoReply(Line line)
-        {
-            var emit     = new Emit {Target = line.Params[1]};
-            var nickname = line.Params[5];
-            var username = line.Params[2];
-            var hostname = line.Params[3];
-            var realname = line.Params[7].Split(' ', 2)[1];
-
-            if (IsMe(nickname))
-            {
-                emit.Self = true;
-                UserName  = username;
-                HostName  = hostname;
-                RealName  = realname;
-            }
-
-            if (HasUser(nickname))
-            {
-                var user = Users[CaseFold(nickname)];
-                emit.User     = user;
-                user.UserName = username;
-                user.HostName = hostname;
-                user.RealName = realname;
-            }
-
-            return emit;
-        }
-
-        private Emit HandleVisibleHost(Line line)
-        {
-            var split = line.Params[1].Split('@', 2);
-            switch (split.Length)
-            {
-                case 1:
-                    HostName = split[0];
-                    break;
-                case 2:
-                    HostName = split[1];
-                    UserName = split[0];
-                    break;
-            }
-
-            return new Emit();
-        }
-
-        private Emit HandleMessage(Line line)
-        {
-            var emit                       = new Emit();
-            var message                    = line.Params.Count > 1 ? line.Params[1] : null;
-            if (message != null) emit.Text = message;
-
-            var nickLower = CaseFold(line.Hostmask.NickName);
-            if (IsMe(nickLower))
-            {
-                emit.SelfSource = true;
-                SelfHostmask(line.Hostmask);
-            }
-
-            var user = HasUser(nickLower)
-                ? Users[nickLower]
-                : AddUser(line.Hostmask.NickName, nickLower);
-            emit.User = user;
-
-            if (line.Hostmask.UserName != null) user.UserName = line.Hostmask.UserName;
-            if (line.Hostmask.HostName != null) user.HostName = line.Hostmask.HostName;
-
-            var target    = line.Params[0];
-            var statusMsg = new List<string>();
-            while (target.Length > 0)
-            {
-                var t = target[0].ToString(CultureInfo.InvariantCulture);
-                if (ISupport.StatusMsg.Contains(t))
-                {
-                    statusMsg.Add(t);
-                    target = target.Substring(1);
-                }
-                else
-                    break;
-            }
-
-            emit.Target = line.Params[0];
-
-            if (IsChannel(target) && HasChannel(target))
-            {
-                emit.Channel = Channels[CaseFold(target)];
-            }
-            else if (IsMe(target))
-            {
-                emit.SelfTarget = true;
-            }
-
-            return emit;
-        }
-
-        private Emit HandleUModeIs(Line line)
-        {
-            foreach (var c in line.Params[1]
-                .TrimStart('+')
-                .Select(m => m.ToString(CultureInfo.InvariantCulture))
-                .Where(m => !Modes.Contains(m)))
-            {
-                Modes.Add(c);
-            }
-
-            return new Emit();
-        }
-
-        private Emit HandleChannelModeIs(Line line)
-        {
-            var emit = new Emit();
-            if (HasChannel(line.Params[1]))
-            {
-                var channel = Channels[CaseFold(line.Params[1])];
-                emit.Channel = channel;
-                var modes = line.Params[2]
-                    .TrimStart('+')
-                    .Select(p => (true, p.ToString(CultureInfo.InvariantCulture)));
-                var parameters = line.Params.Skip(3).ToList();
-                SetChannelModes(channel, modes, parameters);
-            }
-
-            return emit;
-        }
-
-        private Emit HandleMode(Line line)
-        {
-            var emit       = new Emit();
-            var target     = line.Params[0];
-            var modeString = line.Params[1];
-            var parameters = line.Params.Skip(2).ToList();
-
-            var modifier = '+';
-            var modes    = new List<(bool, string)>();
-            var tokens   = new List<string>();
-
-            foreach (var c in modeString)
-            {
-                if (new[] {'+', '-'}.Contains(c))
-                {
-                    modifier = c;
-                }
-                else
-                {
-                    modes.Add((modifier == '+', c.ToString(CultureInfo.InvariantCulture)));
-                    tokens.Add($"{modifier}{c}");
-                }
-            }
-
-            emit.Tokens = tokens;
-
-            if (IsMe(target))
-            {
-                emit.SelfTarget = true;
-                foreach (var (add, c) in modes)
-                {
-                    if (add && !Modes.Contains(c))
-                        Modes.Add(c);
-                    else if (Modes.Contains(c)) Modes.Remove(c);
-                }
-            }
-            else if (HasChannel(target))
-            {
-                var channel = GetChannel(CaseFold(target));
-                emit.Channel = channel;
-                SetChannelModes(channel, modes, parameters);
-            }
-
-            return emit;
-        }
-
-        private Emit HandleTopicTime(Line line)
-        {
-            var emit         = new Emit();
-            var channelLower = CaseFold(line.Params[1]);
-            if (Channels.ContainsKey(channelLower))
-            {
-                var channel = Channels[channelLower];
-                emit.Channel        = channel;
-                channel.TopicSetter = line.Params[2];
-                channel.TopicTime = DateTimeOffset
-                    .FromUnixTimeSeconds(int.Parse(line.Params[3], CultureInfo.InvariantCulture)).DateTime;
-            }
-
-            return emit;
-        }
-
-        private Emit HandleTopicNumeric(Line line)
-        {
-            var emit         = new Emit();
-            var channelLower = CaseFold(line.Params[1]);
-            if (Channels.ContainsKey(channelLower))
-            {
-                var channel = Channels[channelLower];
-                emit.Channel                 = channel;
-                Channels[channelLower].Topic = line.Params[2];
-            }
-
-            return emit;
-        }
-
-        private Emit HandleTopic(Line line)
-        {
-            var emit         = new Emit();
-            var channelLower = CaseFold(line.Params[0]);
-            if (Channels.ContainsKey(channelLower))
-            {
-                var channel = Channels[channelLower];
-                emit.Channel        = channel;
-                channel.Topic       = line.Params[1];
-                channel.TopicSetter = line.Hostmask.ToString();
-                channel.TopicTime   = DateTime.UtcNow;
-            }
-
-            return emit;
-        }
-
-        private Emit HandleCreationTime(Line line)
-        {
-            var emit         = new Emit();
-            var channelLower = CaseFold(line.Params[1]);
-            if (Channels.ContainsKey(channelLower))
-            {
-                var channel = Channels[channelLower];
-                emit.Channel = channel;
-                channel.Created = DateTimeOffset
-                    .FromUnixTimeSeconds(int.Parse(line.Params[2], CultureInfo.InvariantCulture)).DateTime;
-            }
-
-            return emit;
-        }
-
-        private Emit HandleNames(Line line)
-        {
-            var emit         = new Emit();
-            var channelLower = CaseFold(line.Params[2]);
-
-            if (!Channels.ContainsKey(channelLower)) return emit;
-            var channel = Channels[channelLower];
-            emit.Channel = channel;
-            var nicknames = line.Params[3].Split(' ', StringSplitOptions.RemoveEmptyEntries);
-            var users     = new List<User>();
-            emit.Users = users;
-
-            foreach (var nick in nicknames)
-            {
-                var modes = "";
-                foreach (var c in nick)
-                {
-                    var mode = ISupport.Prefix.FromPrefix(c);
-                    if (mode != null)
-                        modes += mode;
-                    else
-                        break;
-                }
-
-                var hostmask  = new Hostmask(nick.Substring(modes.Length));
-                var nickLower = CaseFold(hostmask.NickName);
-                if (!Users.ContainsKey(nickLower)) AddUser(hostmask.NickName, nickLower);
-
-                var user = Users[nickLower];
-                users.Add(user);
-                var channelUser = UserJoin(channel, user);
-
-                if (hostmask.UserName != null) user.UserName = hostmask.UserName;
-                if (hostmask.HostName != null) user.HostName = hostmask.HostName;
-
-                if (IsMe(nickLower)) SelfHostmask(hostmask);
-
-                foreach (var mode in modes.Select(c => c.ToString(CultureInfo.InvariantCulture)))
-                    if (!channelUser.Modes.Contains(mode))
-                        channelUser.Modes.Add(mode);
-            }
-
-            return emit;
-        }
-
-        private Emit HandleError(Line line)
-        {
-            Users.Clear();
-            Channels.Clear();
-            return new Emit();
-        }
-
-        private Emit HandleQuit(Line line)
-        {
-            var emit                         = new Emit();
-            var nickLower                    = CaseFold(line.Hostmask.NickName);
-            if (line.Params.Any()) emit.Text = line.Params[0];
-
-            if (IsMe(nickLower) || line.Source == null)
-            {
-                emit.Self = true;
-                Users.Clear();
-                Channels.Clear();
-            }
-            else if (Users.ContainsKey(nickLower))
-            {
-                var user = Users[nickLower];
-                Users.Remove(nickLower);
-                emit.User = user;
-                foreach (var channel in user.Channels.Select(c => Channels[c]))
-                    channel.Users.Remove(user.NickNameLower);
-            }
-
-            return emit;
-        }
-
-        private Emit HandleLoggedOut(Line line)
-        {
-            Account = null;
-            SelfHostmask(new Hostmask(line.Params[1]));
-            return new Emit();
-        }
-
-        private Emit HandleKick(Line line)
-        {
-            var (emit, kicked) = UserPart(line, line.Params[1], line.Params[0], 2);
-            if (kicked != null)
-            {
-                emit.UserTarget = kicked;
-                if (IsMe(kicked.NickName)) emit.Self = true;
-
-                var kickerLower                        = CaseFold(line.Hostmask.NickName);
-                if (IsMe(kickerLower)) emit.SelfSource = true;
-
-                emit.UserSource = Users.ContainsKey(kickerLower)
-                    ? Users[kickerLower]
-                    : CreateUser(line.Hostmask.NickName, kickerLower);
-            }
-
-            return emit;
-        }
-
-        private Emit HandlePart(Line line)
-        {
-            var (emit, user) = UserPart(line, line.Hostmask.NickName, line.Params[0], 1);
-            if (user != null)
-            {
-                emit.User = user;
-                if (IsMe(user.NickName)) emit.Self = true;
-            }
-
-            return emit;
-        }
-
-
-        private Emit HandleJoin(Line line)
-        {
-            var extended = line.Params.Count == 3;
-            var account  = extended ? line.Params[1].Trim('*') : null;
-            var realname = extended ? line.Params[2] : null;
-            var emit     = new Emit();
-
-            var channelLower = CaseFold(line.Params[0]);
-            var nickLower    = CaseFold(line.Hostmask.NickName);
-
-            // handle own join
-            if (IsMe(nickLower))
-            {
-                emit.Self = true;
-                if (!HasChannel(channelLower))
-                {
-                    var channel = new Channel();
-                    channel.SetName(line.Params[0], channelLower);
-                    Channels[channelLower] = channel;
-                }
-
-                SelfHostmask(line.Hostmask);
-                if (extended)
-                {
-                    Account  = account;
-                    RealName = realname;
-                }
-            }
-
-            if (HasChannel(channelLower))
-            {
-                var channel = Channels[channelLower];
-                emit.Channel = channel;
-
-                if (!HasUser(nickLower)) AddUser(line.Hostmask.NickName, nickLower);
-
-                var user = Users[nickLower];
-                emit.User = user;
-                if (line.Hostmask.UserName != null) user.UserName = line.Hostmask.UserName;
-                if (line.Hostmask.HostName != null) user.HostName = line.Hostmask.HostName;
-                if (extended)
-                {
-                    user.Account  = account;
-                    user.RealName = realname;
-                }
-
-                UserJoin(channel, user);
-            }
-
-            return emit;
-        }
-
-
-        private Emit HandleNick(Line line)
-        {
-            var nick      = line.Params[0];
-            var nickLower = CaseFold(line.Hostmask.NickName);
-
-            var emit = new Emit();
-
-            if (Users.ContainsKey(nickLower))
-            {
-                var user = Users[nickLower];
-                Users.Remove(nickLower);
-                emit.User = user;
-
-                var oldNickLower = user.NickNameLower;
-                var newNickLower = CaseFold(nick);
-                user.SetNickName(nick, newNickLower);
-                Users[newNickLower] = user;
-                foreach (var channelLower in user.Channels)
-                {
-                    var channel     = Channels[channelLower];
-                    var channelUser = channel.Users[oldNickLower];
-                    channel.Users.Remove(oldNickLower);
-                    channel.Users[newNickLower] = channelUser;
-                }
-            }
-
-            if (IsMe(nickLower))
-            {
-                emit.Self     = true;
-                NickName      = nick;
-                NickNameLower = CaseFold(nick);
-            }
-
-            return emit;
-        }
-
-        private Emit HandleMotd(Line line)
-        {
-            if (line.Command == Numeric.RPL_MOTDSTART) Motd.Clear();
-
-            var emit = new Emit {Text = line.Params[1]};
-            Motd.Add(line.Params[1]);
-            return emit;
-        }
-
-        private Emit HandleISupport(Line line)
-        {
-            ISupport = new ISupport();
-            ISupport.Parse(line.Params);
-            return new Emit();
-        }
-
-
-        private Emit HandleWelcome(Line line)
-        {
-            NickName      = line.Params[0];
-            NickNameLower = CaseFold(line.Params[0]);
-            Registered    = true;
-            return new Emit();
-        }
-    }
-}
diff --git a/IrcStates/ServerDisconnectedException.cs b/IrcStates/ServerDisconnectedException.cs
deleted file mode 100644
index c3e014f..0000000
--- a/IrcStates/ServerDisconnectedException.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using System;
-
-namespace IrcStates
-{
-    public class ServerDisconnectedException : Exception
-    {
-    }
-}
diff --git a/IrcStates/ServerException.cs b/IrcStates/ServerException.cs
deleted file mode 100644
index 7164e76..0000000
--- a/IrcStates/ServerException.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using System;
-
-namespace IrcStates
-{
-    public class ServerException : Exception
-    {
-    }
-}
diff --git a/IrcStates/Tests/Cap.cs b/IrcStates/Tests/Cap.cs
deleted file mode 100644
index 3ce52f8..0000000
--- a/IrcStates/Tests/Cap.cs
+++ /dev/null
@@ -1,131 +0,0 @@
-using System.Collections.Generic;
-using IrcTokens;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace IrcStates.Tests
-{
-    [TestClass]
-    public class Cap
-    {
-        private Server _server;
-
-        [TestInitialize]
-        public void TestInitialize()
-        {
-            _server = new Server("test");
-        }
-
-        [TestMethod]
-        public void LSOneLine()
-        {
-            Assert.IsFalse(_server.HasCap);
-            CollectionAssert.AreEqual(new Dictionary<string, string>(), _server.AvailableCaps);
-            _server.Parse(new Line("CAP * LS :a b"));
-            CollectionAssert.AreEqual(new Dictionary<string, string> {{"a", ""}, {"b", ""}}, _server.AvailableCaps);
-        }
-
-        [TestMethod]
-        public void LSTwoLines()
-        {
-            _server.Parse(new Line("CAP * LS * :a b"));
-            CollectionAssert.AreEqual(new Dictionary<string, string>(), _server.AvailableCaps);
-            _server.Parse(new Line("CAP * LS :c"));
-            Assert.IsTrue(_server.AvailableCaps.ContainsKey("a"));
-            Assert.IsTrue(_server.AvailableCaps.ContainsKey("b"));
-            Assert.IsTrue(_server.AvailableCaps.ContainsKey("c"));
-        }
-
-        [TestMethod]
-        public void LSValues()
-        {
-            _server.Parse(new Line("CAP * LS :a b= c=1"));
-            CollectionAssert.AreEqual(new Dictionary<string, string> {{"a", ""}, {"b", ""}, {"c", "1"}},
-                _server.AvailableCaps);
-        }
-
-        [TestMethod]
-        public void ACKOneLine()
-        {
-            _server.Parse(new Line("CAP * LS :a b"));
-            _server.Parse(new Line("CAP * ACK :a b"));
-            CollectionAssert.AreEqual(new List<string> {"a", "b"}, _server.AgreedCaps);
-        }
-
-        [TestMethod]
-        public void ACKTwoLines()
-        {
-            _server.Parse(new Line("CAP * LS :a b c"));
-            _server.Parse(new Line("CAP * ACK * :a b"));
-            _server.Parse(new Line("CAP * ACK :c"));
-            CollectionAssert.AreEqual(new List<string> {"a", "b", "c"}, _server.AgreedCaps);
-        }
-
-        [TestMethod]
-        public void ACKNotLS()
-        {
-            _server.Parse(new Line("CAP * LS a"));
-            _server.Parse(new Line("CAP * ACK b"));
-            CollectionAssert.AreEqual(new List<string>(), _server.AgreedCaps);
-        }
-
-        [TestMethod]
-        public void NEWNoLS()
-        {
-            _server.Parse(new Line("CAP * NEW :a"));
-            CollectionAssert.AreEqual(new Dictionary<string, string> {{"a", ""}}, _server.AvailableCaps);
-        }
-
-        [TestMethod]
-        public void NEWOneLine()
-        {
-            _server.Parse(new Line("CAP * LS :a"));
-            _server.Parse(new Line("CAP * NEW :b"));
-            CollectionAssert.AreEqual(new Dictionary<string, string> {{"a", ""}, {"b", ""}}, _server.AvailableCaps);
-        }
-
-        [TestMethod]
-        public void NEWTwoLines()
-        {
-            _server.Parse(new Line("CAP * LS :a"));
-            _server.Parse(new Line("CAP * NEW :b c"));
-            CollectionAssert.AreEqual(new Dictionary<string, string> {{"a", ""}, {"b", ""}, {"c", ""}},
-                _server.AvailableCaps);
-        }
-
-        [TestMethod]
-        public void DELNotAcked()
-        {
-            _server.Parse(new Line("CAP * DEL a"));
-        }
-
-        [TestMethod]
-        public void DELOneLS()
-        {
-            _server.Parse(new Line("CAP * LS :a"));
-            _server.Parse(new Line("CAP * ACK :a"));
-            _server.Parse(new Line("CAP * DEL :a"));
-            CollectionAssert.AreEqual(new Dictionary<string, string>(), _server.AvailableCaps);
-            CollectionAssert.AreEqual(new List<string>(), _server.AgreedCaps);
-        }
-
-        [TestMethod]
-        public void DELTwoLS()
-        {
-            _server.Parse(new Line("CAP * LS :a b"));
-            _server.Parse(new Line("CAP * ACK :a b"));
-            _server.Parse(new Line("CAP * DEL :a"));
-            CollectionAssert.AreEqual(new Dictionary<string, string> {{"b", ""}}, _server.AvailableCaps);
-            CollectionAssert.AreEqual(new List<string> {"b"}, _server.AgreedCaps);
-        }
-
-        [TestMethod]
-        public void DELTwoDEL()
-        {
-            _server.Parse(new Line("CAP * LS :a b"));
-            _server.Parse(new Line("CAP * ACK :a b"));
-            _server.Parse(new Line("CAP * DEL :a b"));
-            CollectionAssert.AreEqual(new Dictionary<string, string>(), _server.AvailableCaps);
-            CollectionAssert.AreEqual(new List<string>(), _server.AgreedCaps);
-        }
-    }
-}
diff --git a/IrcStates/Tests/Casemap.cs b/IrcStates/Tests/Casemap.cs
deleted file mode 100644
index 6022593..0000000
--- a/IrcStates/Tests/Casemap.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using IrcTokens;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace IrcStates.Tests
-{
-    [TestClass]
-    public class Casemap
-    {
-        [TestMethod]
-        public void Rfc1459()
-        {
-            var lower = IrcStates.Casemap.CaseFold(IrcStates.Casemap.CaseMapping.Rfc1459, @"ÀTEST[]~\");
-            Assert.AreEqual("Àtest{}^|", lower);
-        }
-
-        [TestMethod]
-        public void Ascii()
-        {
-            var lower = IrcStates.Casemap.CaseFold(IrcStates.Casemap.CaseMapping.Ascii, @"ÀTEST[]~\");
-            Assert.AreEqual(@"Àtest[]~\", lower);
-        }
-
-        [TestMethod]
-        public void CommandJoin()
-        {
-            var server = new Server("test");
-            server.Parse(new Line("001 nickname"));
-            server.Parse(new Line(":Nickname JOIN #Chan"));
-            server.Parse(new Line(":Other JOIN #Chan"));
-
-            Assert.IsTrue(server.Users.ContainsKey("nickname"));
-            Assert.IsFalse(server.Users.ContainsKey("Nickname"));
-            Assert.IsTrue(server.Users.ContainsKey("other"));
-            Assert.IsFalse(server.Users.ContainsKey("Other"));
-            Assert.IsTrue(server.Channels.ContainsKey("#chan"));
-            Assert.IsFalse(server.Channels.ContainsKey("#Chan"));
-
-            var channel = server.Channels["#chan"];
-            Assert.AreEqual("#Chan", channel.Name);
-        }
-
-        [TestMethod]
-        public void CommandNick()
-        {
-            var server = new Server("test");
-            server.Parse(new Line("001 nickname"));
-            server.Parse(new Line(":nickname JOIN #chan"));
-            var user = server.Users["nickname"];
-            server.Parse(new Line(":nickname NICK NewNickname"));
-            Assert.AreEqual(1, server.Users.Count);
-            Assert.IsTrue(server.Users.ContainsKey("newnickname"));
-            Assert.AreEqual("NewNickname", user.NickName);
-            Assert.AreEqual("newnickname", user.NickNameLower);
-            Assert.AreEqual("NewNickname", server.NickName);
-            Assert.AreEqual("newnickname", server.NickNameLower);
-        }
-    }
-}
diff --git a/IrcStates/Tests/Channel.cs b/IrcStates/Tests/Channel.cs
deleted file mode 100644
index 64a845c..0000000
--- a/IrcStates/Tests/Channel.cs
+++ /dev/null
@@ -1,202 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using IrcTokens;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace IrcStates.Tests
-{
-    [TestClass]
-    public class Channel
-    {
-        private Server _server;
-
-        [TestInitialize]
-        public void TestInitialize()
-        {
-            _server = new Server("test");
-            _server.Parse(new Line("001 nickname"));
-            _server.Parse(new Line(":nickname JOIN #chan"));
-        }
-
-        [TestMethod]
-        public void JoinSelf()
-        {
-            Assert.IsTrue(_server.Channels.ContainsKey("#chan"));
-            Assert.IsTrue(_server.Users.ContainsKey("nickname"));
-            Assert.AreEqual(1, _server.Channels.Count);
-            Assert.AreEqual(1, _server.Users.Count);
-
-            var user = _server.Users["nickname"];
-            var chan = _server.Channels["#chan"];
-            Assert.IsTrue(chan.Users.ContainsKey(user.NickNameLower));
-            var chanUser = chan.Users[user.NickNameLower];
-            CollectionAssert.AreEqual(new List<string> {chan.NameLower}, user.Channels.ToList());
-        }
-
-        [TestMethod]
-        public void JoinOther()
-        {
-            _server.Parse(new Line(":other JOIN #chan"));
-
-            Assert.AreEqual(2, _server.Users.Count);
-            Assert.IsTrue(_server.Users.ContainsKey("other"));
-
-            var channel = _server.Channels["#chan"];
-            Assert.AreEqual(2, channel.Users.Count);
-
-            var user = _server.Users["other"];
-            CollectionAssert.AreEqual(new List<string> {channel.NameLower}, user.Channels.ToList());
-        }
-
-        [TestMethod]
-        public void PartSelf()
-        {
-            _server.Parse(new Line(":nickname PART #chan"));
-
-            Assert.AreEqual(0, _server.Users.Count);
-            Assert.AreEqual(0, _server.Channels.Count);
-        }
-
-        [TestMethod]
-        public void PartOther()
-        {
-            _server.Parse(new Line(":other JOIN #chan"));
-            _server.Parse(new Line(":other PART #chan"));
-
-            var user     = _server.Users["nickname"];
-            var channel  = _server.Channels["#chan"];
-            var chanUser = channel.Users[user.NickNameLower];
-
-            Assert.AreEqual(channel.NameLower, user.Channels.Single());
-            CollectionAssert.AreEqual(new Dictionary<string, IrcStates.User> {{"nickname", user}}, _server.Users);
-            CollectionAssert.AreEqual(new Dictionary<string, IrcStates.Channel> {{"#chan", channel}}, _server.Channels);
-            CollectionAssert.AreEqual(new Dictionary<string, ChannelUser> {{"nickname", chanUser}}, channel.Users);
-        }
-
-        [TestMethod]
-        public void KickSelf()
-        {
-            _server.Parse(new Line(":nickname KICK #chan nickname"));
-
-            Assert.AreEqual(0, _server.Users.Count);
-            Assert.AreEqual(0, _server.Channels.Count);
-        }
-
-        [TestMethod]
-        public void KickOther()
-        {
-            _server.Parse(new Line(":other JOIN #chan"));
-            _server.Parse(new Line(":nickname KICK #chan other"));
-
-            var user     = _server.Users["nickname"];
-            var channel  = _server.Channels["#chan"];
-            var chanUser = channel.Users[user.NickNameLower];
-
-            Assert.AreEqual(1, _server.Users.Count);
-            Assert.AreEqual(1, _server.Channels.Count);
-            Assert.AreEqual(channel.NameLower, user.Channels.Single());
-            CollectionAssert.AreEqual(new Dictionary<string, ChannelUser> {{user.NickNameLower, chanUser}},
-                channel.Users);
-        }
-
-        [TestMethod]
-        public void QuitSelf()
-        {
-            _server.Parse(new Line("QUIT :i'm outta here"));
-            Assert.IsFalse(_server.Users.Any());
-            Assert.IsFalse(_server.Channels.Any());
-        }
-
-        [TestMethod]
-        public void QuitSelfWithSource()
-        {
-            _server.Parse(new Line(":nickname QUIT :i'm outta here"));
-            Assert.IsFalse(_server.Users.Any());
-            Assert.IsFalse(_server.Channels.Any());
-        }
-
-        [TestMethod]
-        public void QuitOther()
-        {
-            _server.Parse(new Line(":other JOIN #chan"));
-            _server.Parse(new Line(":other QUIT :see ya"));
-            Assert.IsFalse(_server.Users.ContainsKey("other"));
-        }
-
-        [TestMethod]
-        public void TopicText()
-        {
-            _server.Parse(new Line("332 * #chan :test"));
-            Assert.AreEqual("test", _server.Channels["#chan"].Topic);
-        }
-
-        [TestMethod]
-        public void TopicSetByAt()
-        {
-            var dt = DateTimeOffset.FromUnixTimeSeconds(1584023277).DateTime;
-            _server.Parse(new Line("333 * #chan other 1584023277"));
-
-            var channel = _server.Channels["#chan"];
-
-            Assert.AreEqual("other", channel.TopicSetter);
-            Assert.AreEqual(dt, channel.TopicTime);
-        }
-
-        [TestMethod]
-        public void TopicCommand()
-        {
-            _server.Parse(new Line("TOPIC #chan :hello there"));
-            Assert.AreEqual("hello there", _server.Channels["#chan"].Topic);
-        }
-
-        [TestMethod]
-        public void CreationDate()
-        {
-            _server.Parse(new Line("329 * #chan 1584041889"));
-            Assert.AreEqual(DateTimeOffset.FromUnixTimeSeconds(1584041889).DateTime, _server.Channels["#chan"].Created);
-        }
-
-        [TestMethod]
-        public void NamesCommand()
-        {
-            _server.Parse(new Line("353 * * #chan :nickname @+other"));
-            Assert.IsTrue(_server.Users.ContainsKey("nickname"));
-            Assert.IsTrue(_server.Users.ContainsKey("other"));
-
-            var user      = _server.Users["other"];
-            var channel   = _server.Channels["#chan"];
-            var chanUser1 = channel.Users[user.NickNameLower];
-            var chanUser2 = channel.Users[_server.NickNameLower];
-
-            Assert.AreEqual(2, channel.Users.Count);
-            CollectionAssert.AreEqual(chanUser1.Modes, channel.Users[user.NickNameLower].Modes);
-            CollectionAssert.AreEqual(chanUser2.Modes, channel.Users[_server.NickNameLower].Modes);
-            CollectionAssert.AreEqual(new List<string> {"o", "v"}, chanUser1.Modes);
-            Assert.AreEqual(channel.NameLower, user.Channels.Single());
-        }
-
-        [TestMethod]
-        public void UserhostInNames()
-        {
-            _server.Parse(new Line("353 * * #chan :nickname!user@host other!user2@host2"));
-            Assert.AreEqual("user", _server.UserName);
-            Assert.AreEqual("host", _server.HostName);
-
-            var user = _server.Users["other"];
-            Assert.AreEqual("user2", user.UserName);
-            Assert.AreEqual("host2", user.HostName);
-        }
-
-        [TestMethod]
-        public void NickAfterJoin()
-        {
-            var user     = _server.Users["nickname"];
-            var channel  = _server.Channels["#chan"];
-            var chanUser = channel.Users[user.NickNameLower];
-            _server.Parse(new Line(":nickname NICK nickname2"));
-            CollectionAssert.AreEqual(new Dictionary<string, ChannelUser> {{user.NickNameLower, chanUser}},
-                channel.Users);
-        }
-    }
-}
diff --git a/IrcStates/Tests/Emit.cs b/IrcStates/Tests/Emit.cs
deleted file mode 100644
index 15e9e61..0000000
--- a/IrcStates/Tests/Emit.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-using System.Collections.Generic;
-using IrcTokens;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace IrcStates.Tests
-{
-    [TestClass]
-    public class Emit
-    {
-        private Server _server;
-
-        [TestInitialize]
-        public void TestInitialize()
-        {
-            _server = new Server("test");
-            _server.Parse(new Line("001 nickname"));
-        }
-
-        [TestMethod]
-        public void EmitJoin()
-        {
-            var emit = _server.Parse(new Line(":nickname JOIN #chan"));
-
-            Assert.AreEqual("JOIN", emit.Command);
-            Assert.IsTrue(emit.Self);
-            Assert.AreEqual(_server.Users["nickname"], emit.User);
-            Assert.AreEqual(_server.Channels["#chan"], emit.Channel);
-
-            emit = _server.Parse(new Line(":other JOIN #chan"));
-            Assert.IsNotNull(emit);
-            Assert.AreEqual("JOIN", emit.Command);
-            Assert.IsFalse(emit.Self);
-            Assert.AreEqual(_server.Users["other"], emit.User);
-            Assert.AreEqual(_server.Channels["#chan"], emit.Channel);
-        }
-
-        [TestMethod]
-        public void EmitPrivmsg()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            var emit = _server.Parse(new Line(":nickname PRIVMSG #chan :hello"));
-
-            Assert.IsNotNull(emit);
-            Assert.AreEqual("PRIVMSG", emit.Command);
-            Assert.AreEqual("hello", emit.Text);
-            Assert.IsTrue(emit.SelfSource);
-            Assert.AreEqual(_server.Users["nickname"], emit.User);
-            Assert.AreEqual(_server.Channels["#chan"], emit.Channel);
-
-            _server.Parse(new Line(":other JOIN #chan"));
-            emit = _server.Parse(new Line(":other PRIVMSG #chan :hello2"));
-
-            Assert.IsNotNull(emit);
-            Assert.AreEqual("PRIVMSG", emit.Command);
-            Assert.AreEqual("hello2", emit.Text);
-            Assert.IsFalse(emit.SelfSource);
-            Assert.AreEqual(_server.Users["other"], emit.User);
-            Assert.AreEqual(_server.Channels["#chan"], emit.Channel);
-        }
-
-        [TestMethod]
-        public void EmitPrivmsgNoJoin()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            var emit = _server.Parse(new Line(":other PRIVMSG #chan :hello"));
-
-            Assert.IsNotNull(emit);
-            Assert.AreEqual("PRIVMSG", emit.Command);
-            Assert.AreEqual("hello", emit.Text);
-            Assert.IsFalse(emit.SelfSource);
-            Assert.IsNotNull(emit.User);
-
-            var channel = _server.Channels["#chan"];
-            Assert.AreEqual(channel, emit.Channel);
-        }
-
-        [TestMethod]
-        public void EmitKick()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-
-            var user    = _server.Users["nickname"];
-            var channel = _server.Channels["#chan"];
-            _server.Parse(new Line(":other JOIN #chan"));
-            var userOther = _server.Users["other"];
-            var emit      = _server.Parse(new Line(":nickname KICK #chan other :reason"));
-
-            Assert.IsNotNull(emit);
-            Assert.AreEqual("KICK", emit.Command);
-            Assert.AreEqual("reason", emit.Text);
-            Assert.IsTrue(emit.SelfSource);
-            Assert.AreEqual(user, emit.UserSource);
-            Assert.AreEqual(userOther, emit.UserTarget);
-            Assert.AreEqual(channel, emit.Channel);
-        }
-
-        [TestMethod]
-        public void EmitMode()
-        {
-            var emit = _server.Parse(new Line("MODE nickname x+i-i+wi-wi"));
-
-            Assert.IsNotNull(emit);
-            Assert.AreEqual("MODE", emit.Command);
-            Assert.IsTrue(emit.SelfTarget);
-            CollectionAssert.AreEqual(new List<string>
-            {
-                "+x",
-                "+i",
-                "-i",
-                "+w",
-                "+i",
-                "-w",
-                "-i"
-            }, emit.Tokens);
-        }
-    }
-}
diff --git a/IrcStates/Tests/ISupport.cs b/IrcStates/Tests/ISupport.cs
deleted file mode 100644
index 6cd2f48..0000000
--- a/IrcStates/Tests/ISupport.cs
+++ /dev/null
@@ -1,210 +0,0 @@
-using System.Collections.Generic;
-using IrcTokens;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-// ReSharper disable InconsistentNaming
-
-namespace IrcStates.Tests
-{
-    [TestClass]
-    public class ISupport
-    {
-        private Server _server;
-
-        [TestInitialize]
-        public void TestInitialize()
-        {
-            _server = new Server("test");
-            _server.Parse(new Line("001 nickname"));
-        }
-
-        [TestMethod]
-        public void ChanModes()
-        {
-            CollectionAssert.AreEqual(new List<string> {"b"}, _server.ISupport.ChanModes.ListModes);
-            CollectionAssert.AreEqual(new List<string> {"k"}, _server.ISupport.ChanModes.SettingBModes);
-            CollectionAssert.AreEqual(new List<string> {"l"}, _server.ISupport.ChanModes.SettingCModes);
-            CollectionAssert.AreEqual(new List<string>
-            {
-                "i",
-                "m",
-                "n",
-                "p",
-                "s",
-                "t"
-            }, _server.ISupport.ChanModes.SettingDModes);
-
-            _server.Parse(new Line("005 * CHANMODES=a,b,c,d *"));
-
-            CollectionAssert.AreEqual(new List<string> {"a"}, _server.ISupport.ChanModes.ListModes);
-            CollectionAssert.AreEqual(new List<string> {"b"}, _server.ISupport.ChanModes.SettingBModes);
-            CollectionAssert.AreEqual(new List<string> {"c"}, _server.ISupport.ChanModes.SettingCModes);
-            CollectionAssert.AreEqual(new List<string> {"d"}, _server.ISupport.ChanModes.SettingDModes);
-        }
-
-        [TestMethod]
-        public void Prefix()
-        {
-            CollectionAssert.AreEqual(new List<string> {"o", "v"}, _server.ISupport.Prefix.Modes);
-            CollectionAssert.AreEqual(new List<string> {"@", "+"}, _server.ISupport.Prefix.Prefixes);
-
-            Assert.AreEqual("@", _server.ISupport.Prefix.FromMode("o"));
-            Assert.IsNull(_server.ISupport.Prefix.FromMode("a"));
-            Assert.AreEqual("o", _server.ISupport.Prefix.FromPrefix("@"));
-            Assert.IsNull(_server.ISupport.Prefix.FromPrefix("&"));
-
-            _server.Parse(new Line("005 * PREFIX=(qaohv)~&@%+ *"));
-            CollectionAssert.AreEqual(new List<string>
-            {
-                "q",
-                "a",
-                "o",
-                "h",
-                "v"
-            }, _server.ISupport.Prefix.Modes);
-            CollectionAssert.AreEqual(new List<string>
-            {
-                "~",
-                "&",
-                "@",
-                "%",
-                "+"
-            }, _server.ISupport.Prefix.Prefixes);
-            Assert.AreEqual("&", _server.ISupport.Prefix.FromMode("a"));
-            Assert.AreEqual("a", _server.ISupport.Prefix.FromPrefix("&"));
-        }
-
-        [TestMethod]
-        public void ChanTypes()
-        {
-            CollectionAssert.AreEqual(new List<string> {"#"}, _server.ISupport.ChanTypes);
-            _server.Parse(new Line("005 * CHANTYPES=#& *"));
-            CollectionAssert.AreEqual(new List<string> {"#", "&"}, _server.ISupport.ChanTypes);
-        }
-
-        [TestMethod]
-        public void Modes()
-        {
-            Assert.AreEqual(3, _server.ISupport.Modes);
-
-            _server.Parse(new Line("005 * MODES *"));
-            Assert.AreEqual(-1, _server.ISupport.Modes);
-
-            _server.Parse(new Line("005 * MODES=5 *"));
-            Assert.AreEqual(5, _server.ISupport.Modes);
-        }
-
-        [TestMethod]
-        public void Rfc1459()
-        {
-            Assert.AreEqual(IrcStates.Casemap.CaseMapping.Rfc1459, _server.ISupport.CaseMapping);
-            _server.Parse(new Line("005 * CASEMAPPING=rfc1459 *"));
-            Assert.AreEqual(IrcStates.Casemap.CaseMapping.Rfc1459, _server.ISupport.CaseMapping);
-            var lower = _server.CaseFold(@"ÀTEST[]~\");
-            Assert.AreEqual("Àtest{}^|", lower);
-        }
-
-        [TestMethod]
-        public void Ascii()
-        {
-            _server.Parse(new Line("005 * CASEMAPPING=ascii *"));
-            Assert.AreEqual(IrcStates.Casemap.CaseMapping.Ascii, _server.ISupport.CaseMapping);
-            var lower = _server.CaseFold(@"ÀTEST[]~\");
-            Assert.AreEqual(@"Àtest[]~\", lower);
-        }
-
-        [TestMethod]
-        public void FallbackToRfc1459()
-        {
-            _server.Parse(new Line("005 * CASEMAPPING=nonexistent *"));
-            Assert.AreEqual(IrcStates.Casemap.CaseMapping.Rfc1459, _server.ISupport.CaseMapping);
-            var lower = _server.CaseFold(@"ÀTEST[]~\");
-            Assert.AreEqual("Àtest{}^|", lower);
-        }
-
-        [TestMethod]
-        public void Network()
-        {
-            Assert.IsNull(_server.ISupport.Network);
-            _server.Parse(new Line("005 * NETWORK=testnet *"));
-            Assert.AreEqual("testnet", _server.ISupport.Network);
-        }
-
-        [TestMethod]
-        public void StatusMsg()
-        {
-            CollectionAssert.AreEqual(new List<string>(), _server.ISupport.StatusMsg);
-            _server.Parse(new Line("005 * STATUSMSG=&@ *"));
-            CollectionAssert.AreEqual(new List<string> {"&", "@"}, _server.ISupport.StatusMsg);
-        }
-
-        [TestMethod]
-        public void CallerId()
-        {
-            Assert.IsNull(_server.ISupport.CallerId);
-
-            _server.Parse(new Line("005 * CALLERID=U *"));
-            Assert.AreEqual("U", _server.ISupport.CallerId);
-
-            _server.Parse(new Line("005 * CALLERID *"));
-            Assert.AreEqual("g", _server.ISupport.CallerId);
-        }
-
-        [TestMethod]
-        public void Excepts()
-        {
-            Assert.IsNull(_server.ISupport.Excepts);
-
-            _server.Parse(new Line("005 * EXCEPTS=U *"));
-            Assert.AreEqual("U", _server.ISupport.Excepts);
-
-            _server.Parse(new Line("005 * EXCEPTS *"));
-            Assert.AreEqual("e", _server.ISupport.Excepts);
-        }
-
-        [TestMethod]
-        public void Invex()
-        {
-            Assert.IsNull(_server.ISupport.Invex);
-
-            _server.Parse(new Line("005 * INVEX=U *"));
-            Assert.AreEqual("U", _server.ISupport.Invex);
-
-            _server.Parse(new Line("005 * INVEX *"));
-            Assert.AreEqual("I", _server.ISupport.Invex);
-        }
-
-        [TestMethod]
-        public void Whox()
-        {
-            Assert.IsFalse(_server.ISupport.Whox);
-
-            _server.Parse(new Line("005 * WHOX *"));
-            Assert.IsTrue(_server.ISupport.Whox);
-        }
-
-        [TestMethod]
-        public void Monitor()
-        {
-            Assert.IsNull(_server.ISupport.Monitor);
-
-            _server.Parse(new Line("005 * MONITOR=123 *"));
-            Assert.AreEqual(123, _server.ISupport.Monitor);
-
-            _server.Parse(new Line("005 * MONITOR *"));
-            Assert.AreEqual(-1, _server.ISupport.Monitor);
-        }
-
-        [TestMethod]
-        public void Watch()
-        {
-            Assert.IsNull(_server.ISupport.Watch);
-
-            _server.Parse(new Line("005 * WATCH=123 *"));
-            Assert.AreEqual(123, _server.ISupport.Watch);
-
-            _server.Parse(new Line("005 * WATCH *"));
-            Assert.AreEqual(-1, _server.ISupport.Watch);
-        }
-    }
-}
diff --git a/IrcStates/Tests/Mode.cs b/IrcStates/Tests/Mode.cs
deleted file mode 100644
index 799afd6..0000000
--- a/IrcStates/Tests/Mode.cs
+++ /dev/null
@@ -1,179 +0,0 @@
-using System.Collections.Generic;
-using IrcTokens;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace IrcStates.Tests
-{
-    [TestClass]
-    public class Mode
-    {
-        private Server _server;
-
-        [TestInitialize]
-        public void TestInitialize()
-        {
-            _server = new Server("test");
-            _server.Parse(new Line("001 nickname"));
-        }
-
-        [TestMethod]
-        public void UModeAdd()
-        {
-            _server.Parse(new Line("MODE nickname +i"));
-            CollectionAssert.AreEqual(new List<string> {"i"}, _server.Modes);
-        }
-
-        [TestMethod]
-        public void UModeRemove()
-        {
-            _server.Parse(new Line("MODE nickname +i"));
-            _server.Parse(new Line("MODE nickname -i"));
-            CollectionAssert.AreEqual(new List<string>(), _server.Modes);
-        }
-
-        [TestMethod]
-        public void PrefixAdd()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line("MODE #chan +ov nickname nickname"));
-
-            var user        = _server.Users["nickname"];
-            var channel     = _server.Channels["#chan"];
-            var channelUser = channel.Users[user.NickNameLower];
-            CollectionAssert.AreEqual(new List<string> {"o", "v"}, channelUser.Modes);
-        }
-
-        [TestMethod]
-        public void PrefixRemove()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line("MODE #chan +ov nickname nickname"));
-            _server.Parse(new Line("MODE #chan -ov nickname nickname"));
-
-            var user        = _server.Users["nickname"];
-            var channel     = _server.Channels["#chan"];
-            var channelUser = channel.Users[user.NickNameLower];
-            CollectionAssert.AreEqual(new List<string>(), channelUser.Modes);
-        }
-
-        [TestMethod]
-        public void ChannelListAdd()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line("MODE #chan +b asd!*@*"));
-
-            var channel = _server.Channels["#chan"];
-            CollectionAssert.AreEqual(new List<string> {"asd!*@*"}, channel.ListModes["b"]);
-        }
-
-        [TestMethod]
-        public void ChannelListRemove()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line("MODE #chan +b asd!*@*"));
-            _server.Parse(new Line("MODE #chan -b asd!*@*"));
-
-            var channel = _server.Channels["#chan"];
-            CollectionAssert.AreEqual(new Dictionary<string, List<string>>(), channel.ListModes);
-        }
-
-        [TestMethod]
-        public void ChannelTypeBAdd()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line("MODE #chan +k password"));
-
-            var channel = _server.Channels["#chan"];
-            CollectionAssert.AreEqual(new Dictionary<string, string> {{"k", "password"}}, channel.Modes);
-        }
-
-        [TestMethod]
-        public void ChannelTypeBRemove()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line("MODE #chan +k password"));
-            _server.Parse(new Line("MODE #chan -k *"));
-
-            var channel = _server.Channels["#chan"];
-            CollectionAssert.AreEqual(new Dictionary<string, string>(), channel.Modes);
-        }
-
-        [TestMethod]
-        public void ChannelTypeCAdd()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line("MODE #chan +l 100"));
-
-            var channel = _server.Channels["#chan"];
-            CollectionAssert.AreEqual(new Dictionary<string, string> {{"l", "100"}}, channel.Modes);
-        }
-
-        [TestMethod]
-        public void ChannelTypeCRemove()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line("MODE #chan +l 100"));
-            _server.Parse(new Line("MODE #chan -l"));
-
-            var channel = _server.Channels["#chan"];
-            CollectionAssert.AreEqual(new Dictionary<string, string>(), channel.Modes);
-        }
-
-        [TestMethod]
-        public void ChannelTypeDAdd()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line("MODE #chan +i"));
-
-            var channel = _server.Channels["#chan"];
-            CollectionAssert.AreEqual(new Dictionary<string, string> {{"i", null}}, channel.Modes);
-        }
-
-        [TestMethod]
-        public void ChannelTypeDRemove()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line("MODE #chan +i"));
-            _server.Parse(new Line("MODE #chan -i"));
-
-            var channel = _server.Channels["#chan"];
-            CollectionAssert.AreEqual(new Dictionary<string, string>(), channel.Modes);
-        }
-
-        [TestMethod]
-        public void ChannelNumeric()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line("324 * #chan +bkli *!*@* pass 10"));
-
-            var channel = _server.Channels["#chan"];
-            CollectionAssert.AreEqual(new Dictionary<string, string> {{"k", "pass"}, {"l", "10"}, {"i", null}},
-                channel.Modes);
-            CollectionAssert.AreEqual(new List<string> {"*!*@*"}, channel.ListModes["b"]);
-        }
-
-        [TestMethod]
-        public void ChannelNumericWithoutPlus()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line("324 * #chan il 10"));
-
-            var channel = _server.Channels["#chan"];
-            CollectionAssert.AreEqual(new Dictionary<string, string> {{"i", null}, {"l", "10"}}, channel.Modes);
-        }
-
-        [TestMethod]
-        public void UserNumeric()
-        {
-            _server.Parse(new Line("221 * +iw"));
-            CollectionAssert.AreEqual(new List<string> {"i", "w"}, _server.Modes);
-        }
-
-        [TestMethod]
-        public void UserNumericWithoutPlus()
-        {
-            _server.Parse(new Line("221 * iw"));
-            CollectionAssert.AreEqual(new List<string> {"i", "w"}, _server.Modes);
-        }
-    }
-}
diff --git a/IrcStates/Tests/Motd.cs b/IrcStates/Tests/Motd.cs
deleted file mode 100644
index 92929a5..0000000
--- a/IrcStates/Tests/Motd.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.Collections.Generic;
-using IrcTokens;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace IrcStates.Tests
-{
-    [TestClass]
-    public class Motd
-    {
-        [TestMethod]
-        public void MessageOfTheDay()
-        {
-            var server = new Server("test");
-            server.Parse(new Line("001 nickname"));
-            server.Parse(new Line("375 * :start of motd"));
-            server.Parse(new Line("372 * :first line of motd"));
-            server.Parse(new Line("372 * :second line of motd"));
-
-            CollectionAssert.AreEqual(new List<string> {"start of motd", "first line of motd", "second line of motd"},
-                server.Motd);
-        }
-    }
-}
diff --git a/IrcStates/Tests/Sasl.cs b/IrcStates/Tests/Sasl.cs
deleted file mode 100644
index f61e2e1..0000000
--- a/IrcStates/Tests/Sasl.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using IrcTokens;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace IrcStates.Tests
-{
-    [TestClass]
-    public class Sasl
-    {
-        private Server _server;
-
-        [TestInitialize]
-        public void TestInitialize()
-        {
-            _server = new Server("test");
-            _server.Parse(new Line("900 * nick!user@host account"));
-        }
-
-        [TestMethod]
-        public void LoggedIn()
-        {
-            Assert.AreEqual("nick", _server.NickName);
-            Assert.AreEqual("user", _server.UserName);
-            Assert.AreEqual("host", _server.HostName);
-            Assert.AreEqual("account", _server.Account);
-        }
-
-        [TestMethod]
-        public void LoggedOut()
-        {
-            _server.Parse(new Line("901 * nick1!user1@host1"));
-
-            Assert.AreEqual("nick1", _server.NickName);
-            Assert.AreEqual("user1", _server.UserName);
-            Assert.AreEqual("host1", _server.HostName);
-            Assert.IsTrue(string.IsNullOrEmpty(_server.Account));
-        }
-    }
-}
diff --git a/IrcStates/Tests/User.cs b/IrcStates/Tests/User.cs
deleted file mode 100644
index 5857cfc..0000000
--- a/IrcStates/Tests/User.cs
+++ /dev/null
@@ -1,298 +0,0 @@
-using IrcTokens;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace IrcStates.Tests
-{
-    [TestClass]
-    public class User
-    {
-        private Server _server;
-
-        [TestInitialize]
-        public void TestInitialize()
-        {
-            _server = new Server("test");
-            _server.Parse(new Line("001 nickname"));
-        }
-
-        [TestMethod]
-        public void Welcome()
-        {
-            Assert.AreEqual("test", _server.Name);
-            Assert.AreEqual("nickname", _server.NickName);
-        }
-
-        [TestMethod]
-        public void NicknameChange()
-        {
-            _server.Parse(new Line(":nickname NICK nickname2"));
-            Assert.AreEqual("nickname2", _server.NickName);
-
-            _server.Parse(new Line(":nickname2 JOIN #chan"));
-            _server.Parse(new Line(":other JOIN #chan"));
-            Assert.IsTrue(_server.Users.ContainsKey("other"));
-
-            _server.Parse(new Line(":other NICK other2"));
-            Assert.IsFalse(_server.Users.ContainsKey("other"));
-            Assert.IsTrue(_server.Users.ContainsKey("other2"));
-        }
-
-        [TestMethod]
-        public void HostmaskJoinBoth()
-        {
-            _server.Parse(new Line(":nickname!user@host JOIN #chan"));
-            Assert.AreEqual("user", _server.UserName);
-            Assert.AreEqual("host", _server.HostName);
-
-            _server.Parse(new Line(":other!user@host JOIN #chan"));
-            var user = _server.Users["other"];
-            Assert.AreEqual("user", user.UserName);
-            Assert.AreEqual("host", user.HostName);
-        }
-
-        [TestMethod]
-        public void HostmaskJoinUser()
-        {
-            _server.Parse(new Line(":nickname!user JOIN #chan"));
-            Assert.AreEqual("user", _server.UserName);
-            Assert.IsNull(_server.HostName);
-
-            _server.Parse(new Line(":other!user JOIN #chan"));
-            var user = _server.Users["other"];
-            Assert.AreEqual("user", user.UserName);
-            Assert.IsNull(user.HostName);
-        }
-
-        [TestMethod]
-        public void HostmaskJoinHost()
-        {
-            _server.Parse(new Line(":nickname@host JOIN #chan"));
-            Assert.IsNull(_server.UserName);
-            Assert.AreEqual("host", _server.HostName);
-
-            _server.Parse(new Line(":other@host JOIN #chan"));
-            var user = _server.Users["other"];
-            Assert.IsNull(user.UserName);
-            Assert.AreEqual("host", user.HostName);
-        }
-
-        [TestMethod]
-        public void ExtendedJoinWithoutExtendedJoin()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            Assert.IsNull(_server.Account);
-            Assert.IsNull(_server.RealName);
-
-            _server.Parse(new Line(":other JOIN #chan"));
-            var user = _server.Users["other"];
-            Assert.IsNull(user.Account);
-            Assert.IsNull(user.RealName);
-        }
-
-        [TestMethod]
-        public void ExtendedJoinWithAccount()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan acc :realname"));
-            Assert.AreEqual("acc", _server.Account);
-            Assert.AreEqual("realname", _server.RealName);
-
-            _server.Parse(new Line(":other JOIN #chan acc2 :realname2"));
-            var user = _server.Users["other"];
-            Assert.AreEqual("acc2", user.Account);
-            Assert.AreEqual("realname2", user.RealName);
-        }
-
-        [TestMethod]
-        public void ExtendedJoinWithoutAccount()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan * :realname"));
-            Assert.AreEqual("", _server.Account);
-            Assert.AreEqual("realname", _server.RealName);
-
-            _server.Parse(new Line(":other JOIN #chan * :realname2"));
-            var user = _server.Users["other"];
-            Assert.AreEqual("", user.Account);
-            Assert.AreEqual("realname2", user.RealName);
-        }
-
-        [TestMethod]
-        public void AccountNotifyWithAccount()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line(":nickname ACCOUNT acc"));
-            Assert.AreEqual("acc", _server.Account);
-
-            _server.Parse(new Line(":other JOIN #chan"));
-            _server.Parse(new Line(":other ACCOUNT acc2"));
-            var user = _server.Users["other"];
-            Assert.AreEqual("acc2", user.Account);
-        }
-
-        [TestMethod]
-        public void AccountNotifyWithoutAccount()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line(":nickname ACCOUNT *"));
-            Assert.AreEqual("", _server.Account);
-
-            _server.Parse(new Line(":other JOIN #chan"));
-            _server.Parse(new Line(":other ACCOUNT *"));
-            var user = _server.Users["other"];
-            Assert.AreEqual("", user.Account);
-        }
-
-        [TestMethod]
-        public void HostmaskPrivmsgBoth()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line(":nickname!user@host PRIVMSG #chan :hi"));
-            Assert.AreEqual("user", _server.UserName);
-            Assert.AreEqual("host", _server.HostName);
-
-            _server.Parse(new Line(":other!user@host PRIVMSG #chan :hi"));
-            var user = _server.Users["other"];
-            Assert.AreEqual("user", user.UserName);
-            Assert.AreEqual("host", user.HostName);
-        }
-
-        [TestMethod]
-        public void HostmaskPrivmsgUser()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line(":nickname!user PRIVMSG #chan :hi"));
-            Assert.AreEqual("user", _server.UserName);
-            Assert.IsNull(_server.HostName);
-
-            _server.Parse(new Line(":other!user PRIVMSG #chan :hi"));
-            var user = _server.Users["other"];
-            Assert.AreEqual("user", user.UserName);
-            Assert.IsNull(user.HostName);
-        }
-
-        [TestMethod]
-        public void HostmaskPrivmsgHost()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line(":nickname@host PRIVMSG #chan :hi"));
-            Assert.IsNull(_server.UserName);
-            Assert.AreEqual("host", _server.HostName);
-
-            _server.Parse(new Line(":other@host PRIVMSG #chan :hi"));
-            var user = _server.Users["other"];
-            Assert.IsNull(user.UserName);
-            Assert.AreEqual("host", user.HostName);
-        }
-
-        [TestMethod]
-        public void VisibleHostWithoutUsername()
-        {
-            _server.Parse(new Line("396 * hostname"));
-            Assert.IsNull(_server.UserName);
-            Assert.AreEqual("hostname", _server.HostName);
-        }
-
-        [TestMethod]
-        public void VisibleHostWithUsername()
-        {
-            _server.Parse(new Line("396 * username@hostname"));
-            Assert.AreEqual("username", _server.UserName);
-            Assert.AreEqual("hostname", _server.HostName);
-        }
-
-        [TestMethod]
-        public void Who()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line(":other JOIN #chan"));
-            _server.Parse(new Line("352 * #chan user host * nickname * :0 real"));
-            _server.Parse(new Line("352 * #chan user2 host2 * other * :0 real2"));
-
-            Assert.AreEqual("user", _server.UserName);
-            Assert.AreEqual("host", _server.HostName);
-            Assert.AreEqual("real", _server.RealName);
-
-            var user = _server.Users["other"];
-            Assert.AreEqual("user2", user.UserName);
-            Assert.AreEqual("host2", user.HostName);
-            Assert.AreEqual("real2", user.RealName);
-        }
-
-        [TestMethod]
-        public void Chghost()
-        {
-            _server.Parse(new Line(":nickname!user@host JOIN #chan"));
-            _server.Parse(new Line(":nickname CHGHOST u h"));
-            Assert.AreEqual("u", _server.UserName);
-            Assert.AreEqual("h", _server.HostName);
-
-            _server.Parse(new Line(":other!user2@host2 JOIN #chan"));
-            _server.Parse(new Line(":other CHGHOST u2 h2"));
-            var user = _server.Users["other"];
-            Assert.AreEqual("u2", user.UserName);
-            Assert.AreEqual("h2", user.HostName);
-        }
-
-        [TestMethod]
-        public void Whois()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line("311 * nickname u h * :r"));
-            Assert.AreEqual("u", _server.UserName);
-            Assert.AreEqual("h", _server.HostName);
-            Assert.AreEqual("r", _server.RealName);
-
-            _server.Parse(new Line(":other JOIN #chan"));
-            _server.Parse(new Line(":other CHGHOST u2 h2"));
-            _server.Parse(new Line("311 * other u2 h2 * :r2"));
-            var user = _server.Users["other"];
-            Assert.AreEqual("u2", user.UserName);
-            Assert.AreEqual("h2", user.HostName);
-            Assert.AreEqual("r2", user.RealName);
-        }
-
-        [TestMethod]
-        public void AwaySet()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line(":other JOIN #chan"));
-            var user = _server.Users["other"];
-            Assert.IsNull(_server.Away);
-            Assert.IsNull(user.Away);
-
-            _server.Parse(new Line(":nickname AWAY :bye bye"));
-            _server.Parse(new Line(":other AWAY :ich geh weg"));
-            Assert.AreEqual("bye bye", _server.Away);
-            Assert.AreEqual("ich geh weg", user.Away);
-        }
-
-        [TestMethod]
-        public void AwayUnset()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line(":other JOIN #chan"));
-            _server.Parse(new Line(":nickname AWAY :bye bye"));
-            _server.Parse(new Line(":nickname AWAY"));
-            _server.Parse(new Line(":other AWAY :ich geh weg"));
-            _server.Parse(new Line(":other AWAY"));
-
-            var user = _server.Users["other"];
-            Assert.IsNull(_server.Away);
-            Assert.IsNull(user.Away);
-        }
-
-        [TestMethod]
-        public void Setname()
-        {
-            _server.Parse(new Line(":nickname JOIN #chan"));
-            _server.Parse(new Line(":other JOIN #chan"));
-            var user = _server.Users["other"];
-            Assert.IsNull(user.RealName);
-            Assert.IsNull(_server.RealName);
-
-            _server.Parse(new Line(":nickname SETNAME :new now know how"));
-            _server.Parse(new Line(":other SETNAME :tyrannosaurus hex"));
-            Assert.AreEqual("new now know how", _server.RealName);
-            Assert.AreEqual("tyrannosaurus hex", user.RealName);
-        }
-    }
-}
diff --git a/IrcStates/Tests/Who.cs b/IrcStates/Tests/Who.cs
deleted file mode 100644
index 7e4e323..0000000
--- a/IrcStates/Tests/Who.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using IrcTokens;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace IrcStates.Tests
-{
-    [TestClass]
-    public class Who
-    {
-        private Server _server;
-
-        [TestInitialize]
-        public void TestInitialize()
-        {
-            _server = new Server("test");
-            _server.Parse(new Line("001 nickname"));
-            _server.Parse(new Line(":nickname JOIN #chan"));
-        }
-
-        [TestMethod]
-        public void WhoResponse()
-        {
-            _server.Parse(new Line("352 * #chan user host server nickname * :0 real"));
-            var user = _server.Users["nickname"];
-
-            Assert.AreEqual("user", user.UserName);
-            Assert.AreEqual("host", _server.HostName);
-            Assert.AreEqual("real", user.RealName);
-
-            Assert.AreEqual(user.UserName, _server.UserName);
-            Assert.AreEqual(user.HostName, _server.HostName);
-            Assert.AreEqual(user.RealName, _server.RealName);
-        }
-
-        [TestMethod]
-        public void Whox()
-        {
-            _server.Parse(new Line($"354 * {Server.WhoType} user realip host nickname account :real"));
-            var user = _server.Users["nickname"];
-
-            Assert.AreEqual("user", user.UserName);
-            Assert.AreEqual("host", user.HostName);
-            Assert.AreEqual("real", user.RealName);
-            Assert.AreEqual("account", user.Account);
-
-            Assert.AreEqual(user.UserName, _server.UserName);
-            Assert.AreEqual(user.HostName, _server.HostName);
-            Assert.AreEqual(user.RealName, _server.RealName);
-            Assert.AreEqual(user.Account, _server.Account);
-        }
-
-        [TestMethod]
-        public void WhoxNoAccount()
-        {
-            _server.Parse(new Line($"354 * {Server.WhoType} user realip host nickname 0 :real"));
-            var user = _server.Users["nickname"];
-
-            Assert.IsNull(user.Account);
-            Assert.AreEqual(user.Account, _server.Account);
-        }
-    }
-}
diff --git a/IrcStates/User.cs b/IrcStates/User.cs
deleted file mode 100644
index 7df331b..0000000
--- a/IrcStates/User.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System.Collections.Generic;
-
-namespace IrcStates
-{
-    public class User
-    {
-        public User()
-        {
-            Channels = new HashSet<string>();
-        }
-
-        public string NickName { get; set; }
-        public string NickNameLower { get; set; }
-
-        public string UserName { get; set; }
-        public string HostName { get; set; }
-        public string RealName { get; set; }
-        public string Account { get; set; }
-        public string Away { get; set; }
-        public HashSet<string> Channels { get; set; }
-
-        public override string ToString()
-        {
-            return $"User(nickname={NickName})";
-        }
-
-        public void SetNickName(string nick, string nickLower)
-        {
-            NickName      = nick;
-            NickNameLower = nickLower;
-        }
-    }
-}