From aef26a40cbc6835aae0d293a523f5a7681b9a436 Mon Sep 17 00:00:00 2001 From: Ben Harris Date: Thu, 14 May 2020 17:13:46 -0400 Subject: wow lots of tests passing! --- IrcStates/Channel.cs | 2 +- IrcStates/Extensions.cs | 2 +- IrcStates/ISupport.cs | 23 ++- IrcStates/ISupportChanModes.cs | 18 +- IrcStates/Server.cs | 390 +++++++++++++++++++++++++++++++++-------- IrcStates/Tests/Cap.cs | 5 +- IrcStates/Tests/Mode.cs | 6 +- 7 files changed, 352 insertions(+), 94 deletions(-) diff --git a/IrcStates/Channel.cs b/IrcStates/Channel.cs index 16b4654..21ebb25 100644 --- a/IrcStates/Channel.cs +++ b/IrcStates/Channel.cs @@ -40,7 +40,7 @@ namespace IrcStates { if (!ListModes.ContainsKey(ch)) ListModes[ch] = new List(); - if (ListModes[ch].Contains(param)) ListModes[ch].Add(param ?? string.Empty); + if (!ListModes[ch].Contains(param)) ListModes[ch].Add(param ?? string.Empty); } else { diff --git a/IrcStates/Extensions.cs b/IrcStates/Extensions.cs index e604928..181ac80 100644 --- a/IrcStates/Extensions.cs +++ b/IrcStates/Extensions.cs @@ -30,7 +30,7 @@ namespace IrcStates : Delegate.CreateDelegate(getType(types.ToArray()), target, methodInfo); } - public static void Update(this Dictionary dict, Dictionary other) + public static void UpdateWith(this Dictionary dict, Dictionary other) { if (dict == null || other == null || !other.Any()) return; diff --git a/IrcStates/ISupport.cs b/IrcStates/ISupport.cs index 2fc27dc..db2f537 100644 --- a/IrcStates/ISupport.cs +++ b/IrcStates/ISupport.cs @@ -16,6 +16,7 @@ namespace IrcStates CaseMapping = Casemap.CaseMapping.Rfc1459; Prefix = new ISupportPrefix("(ov)@+"); ChanModes = new ISupportChanModes("b,k,l,imnpst"); + ChanTypes = new List {"#"}; StatusMsg = new List(); Whox = false; } @@ -46,10 +47,14 @@ namespace IrcStates { var split = token.Split('=', 2); var key = split[0]; - var value = split[1]; - - if (split.Length > 1) Raw[key] = value; + var value = string.Empty; + if (split.Length > 1) + { + value = split[1]; + Raw[key] = value; + } + switch (split[0]) { case "NETWORK": @@ -62,7 +67,8 @@ namespace IrcStates Prefix = new ISupportPrefix(value); break; case "STATUSMSG": - StatusMsg = new List {value}; + StatusMsg = new List(); + StatusMsg.AddRange(value.Select(c => c.ToString(CultureInfo.InvariantCulture))); break; case "MODES": Modes = int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); @@ -77,16 +83,17 @@ namespace IrcStates if (Enum.TryParse(value, true, out Casemap.CaseMapping caseMapping)) CaseMapping = caseMapping; break; case "CHANTYPES": - ChanTypes = new List {value}; + ChanTypes = new List(); + ChanTypes.AddRange(value.Select(c => c.ToString(CultureInfo.InvariantCulture))); break; case "CALLERID": - CallerId = string.IsNullOrEmpty(value) ? value : "g"; + CallerId = string.IsNullOrEmpty(value) ? "g" : value; break; case "EXCEPTS": - Excepts = string.IsNullOrEmpty(value) ? value : "e"; + Excepts = string.IsNullOrEmpty(value) ? "e" : value; break; case "INVEX": - Invex = string.IsNullOrEmpty(value) ? value : "I"; + Invex = string.IsNullOrEmpty(value) ? "I" : value; break; case "WHOX": Whox = true; diff --git a/IrcStates/ISupportChanModes.cs b/IrcStates/ISupportChanModes.cs index 9bf565c..74a0579 100644 --- a/IrcStates/ISupportChanModes.cs +++ b/IrcStates/ISupportChanModes.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Globalization; +using System.Linq; namespace IrcStates { @@ -9,10 +11,18 @@ namespace IrcStates if (splitVal == null) return; var split = splitVal.Split(',', 4); - ListModes = new List {split[0]}; - SettingBModes = new List {split[1]}; - SettingCModes = new List {split[2]}; - SettingDModes = new List {split[3]}; + + ListModes = new List(); + ListModes.AddRange(split[0].Select(c => c.ToString(CultureInfo.InvariantCulture))); + + SettingBModes = new List(); + SettingBModes.AddRange(split[1].Select(c => c.ToString(CultureInfo.InvariantCulture))); + + SettingCModes = new List(); + SettingCModes.AddRange(split[2].Select(c => c.ToString(CultureInfo.InvariantCulture))); + + SettingDModes = new List(); + SettingDModes.AddRange(split[3].Select(c => c.ToString(CultureInfo.InvariantCulture))); } public List ListModes { get; set; } diff --git a/IrcStates/Server.cs b/IrcStates/Server.cs index d9022a0..ca812f6 100644 --- a/IrcStates/Server.cs +++ b/IrcStates/Server.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.Design; using System.Globalization; using System.Linq; using IrcTokens; @@ -59,25 +60,26 @@ namespace IrcStates return Casemap.CaseFold(ISupport.CaseMapping, str); } - public bool CaseFoldEquals(string s1, string s2) + private bool CaseFoldEquals(string s1, string s2) { return CaseFold(s1) == CaseFold(s2); } - public bool IsMe(string nickname) + private bool IsMe(string nickname) { return CaseFold(nickname) == NickNameLower; } - public bool HasUser(string nickname) + private bool HasUser(string nickname) { return Users.ContainsKey(CaseFold(nickname)); } - private void AddUser(string nickname, string nicknameLower) + private User AddUser(string nickname, string nicknameLower) { var user = CreateUser(nickname, nicknameLower); Users[nicknameLower] = user; + return user; } private User CreateUser(string nickname, string nicknameLower) @@ -87,18 +89,18 @@ namespace IrcStates return user; } - public bool IsChannel(string target) + private bool IsChannel(string target) { return !string.IsNullOrEmpty(target) && ISupport.ChanTypes.Contains(target[0].ToString(CultureInfo.InvariantCulture)); } - public bool HasChannel(string name) + private bool HasChannel(string name) { return Channels.ContainsKey(CaseFold(name)); } - public Channel GetChannel(string name) + private Channel GetChannel(string name) { return HasChannel(name) ? Channels[name] : null; } @@ -138,7 +140,7 @@ namespace IrcStates if (!user.Channels.Any()) Users.Remove(nickLower); } - if (nickLower == NickNameLower) + if (IsMe(nickName)) { Channels.Remove(channelLower); foreach (var userToRemove in channel.Users.Keys.Select(u => Users[u])) @@ -152,6 +154,53 @@ namespace IrcStates return (emit, user); } + private void SetChannelModes(Channel channel, IEnumerable<(bool, string)> modes, IList 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 List<(Line, Emit)> Recv(byte[] data) { if (data == null) return null; @@ -166,45 +215,49 @@ namespace IrcStates { if (line == null) return null; - switch (line.Command) - { - case Numeric.RPL_WELCOME: return HandleWelcome(line); - case Numeric.RPL_ISUPPORT: return HandleISupport(line); - case Numeric.RPL_MOTDSTART: - case Numeric.RPL_MOTD: - return HandleMotd(line); - case Commands.Nick: return HandleNick(line); - case Commands.Join: return HandleJoin(line); - case Commands.Part: return HandlePart(line); - case Commands.Kick: return HandleKick(line); - case Commands.Quit: return HandleQuit(line); - case Commands.Error: return HandleError(line); - case Numeric.RPL_NAMREPLY: return HandleNames(line); - case Numeric.RPL_CREATIONTIME: return HandleCreationTime(line); - case Commands.Topic: return HandleTopic(line); - case Numeric.RPL_TOPIC: return HandleTopicNumeric(line); - case Numeric.RPL_TOPICWHOTIME: return HandleTopicTime(line); - case Commands.Mode: return HandleMode(line); - case Numeric.RPL_CHANNELMODEIS: return HandleChannelModeIs(line); - case Numeric.RPL_UMODEIS: return HandleUModeIs(line); - case Commands.Privmsg: - case Commands.Notice: - case Commands.Tagmsg: - return HandleMessage(line); - case Numeric.RPL_VISIBLEHOST: return HandleVisibleHost(line); - case Numeric.RPL_WHOREPLY: return HandleWhoReply(line); - case Numeric.RPL_WHOSPCRPL: return HandleWhox(line); - case Numeric.RPL_WHOISUSER: return HandleWhoIsUser(line); - case Commands.Chghost: return HandleChghost(line); - case Commands.Setname: return HandleSetname(line); - case Commands.Away: return HandleAway(line); - case Commands.Account: return HandleAccount(line); - case Commands.Cap: return HandleCap(line); - case Numeric.RPL_LOGGEDIN: return HandleLoggedIn(line); - case Numeric.RPL_LOGGEDOUT: return HandleLoggedOut(line); - } + 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 new Emit(); + return emit; } private Emit HandleSetname(Line line) @@ -213,7 +266,7 @@ namespace IrcStates var realname = line.Params[0]; var nicknameLower = CaseFold(line.Hostmask.NickName); - if (nicknameLower == NickNameLower) + if (IsMe(nicknameLower)) { emit.Self = true; RealName = realname; @@ -235,7 +288,7 @@ namespace IrcStates var away = line.Params.FirstOrDefault(); var nicknameLower = CaseFold(line.Hostmask.NickName); - if (nicknameLower == NickNameLower) + if (IsMe(nicknameLower)) { emit.Self = true; Away = away; @@ -257,7 +310,7 @@ namespace IrcStates var account = line.Params[0].Trim('*'); var nicknameLower = CaseFold(line.Hostmask.NickName); - if (nicknameLower == NickNameLower) + if (IsMe(nicknameLower)) { emit.Self = true; Account = account; @@ -289,24 +342,21 @@ namespace IrcStates tokens[kv[0]] = kv.Length > 1 ? kv[1] : string.Empty; } - var emit = new Emit(); - emit.Subcommand = subcommand; - emit.Finished = !multiline; - emit.Tokens = tokensStr; + var emit = new Emit {Subcommand = subcommand, Finished = !multiline, Tokens = tokensStr}; switch (subcommand) { case "LS": - TempCaps = tokens; + TempCaps.UpdateWith(tokens); if (!multiline) { - AvailableCaps = TempCaps; + AvailableCaps.UpdateWith(TempCaps); TempCaps.Clear(); } break; case "NEW": - AvailableCaps.Update(tokens); + AvailableCaps.UpdateWith(tokens); break; case "DEL": foreach (var key in tokens.Keys.Where(key => AvailableCaps.ContainsKey(key))) @@ -323,7 +373,7 @@ namespace IrcStates var k = key.Substring(1); if (AgreedCaps.Contains(k)) AgreedCaps.Remove(k); } - else if (!AgreedCaps.Contains(key) && !AvailableCaps.ContainsKey(key)) + else if (!AgreedCaps.Contains(key) && AvailableCaps.ContainsKey(key)) { AgreedCaps.Add(key); } @@ -348,7 +398,7 @@ namespace IrcStates var hostname = line.Params[1]; var nicknameLower = CaseFold(line.Hostmask.NickName); - if (nicknameLower == NickNameLower) + if (IsMe(nicknameLower)) { emit.Self = true; UserName = username; @@ -368,42 +418,234 @@ namespace IrcStates private Emit HandleWhoIsUser(Line line) { - throw new NotImplementedException(); + 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) { - throw new NotImplementedException(); + 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) { - throw new NotImplementedException(); + 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) { - throw new NotImplementedException(); + 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) { - throw new NotImplementedException(); + 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(); + 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) { - throw new NotImplementedException(); + 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) { - throw new NotImplementedException(); + 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) { - throw new NotImplementedException(); + 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(); + + 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) @@ -502,7 +744,7 @@ namespace IrcStates if (hostmask.UserName != null) user.UserName = hostmask.UserName; if (hostmask.HostName != null) user.HostName = hostmask.HostName; - if (nickLower == NickNameLower) SelfHostmask(hostmask); + if (IsMe(nickLower)) SelfHostmask(hostmask); foreach (var mode in modes.Select(c => c.ToString(CultureInfo.InvariantCulture))) if (!channelUser.Modes.Contains(mode)) @@ -525,7 +767,7 @@ namespace IrcStates var nickLower = CaseFold(line.Hostmask.NickName); if (line.Params.Any()) emit.Text = line.Params[0]; - if (nickLower == NickNameLower || line.Source == null) + if (IsMe(nickLower) || line.Source == null) { emit.Self = true; Users.Clear(); @@ -556,10 +798,10 @@ namespace IrcStates if (kicked != null) { emit.UserTarget = kicked; - if (kicked.NickNameLower == NickNameLower) emit.Self = true; + if (IsMe(kicked.NickName)) emit.Self = true; - var kickerLower = CaseFold(line.Hostmask.NickName); - if (kickerLower == NickNameLower) emit.SelfSource = true; + var kickerLower = CaseFold(line.Hostmask.NickName); + if (IsMe(kickerLower)) emit.SelfSource = true; emit.UserSource = Users.ContainsKey(kickerLower) ? Users[kickerLower] @@ -575,7 +817,7 @@ namespace IrcStates if (user != null) { emit.User = user; - if (user.NickNameLower == NickNameLower) emit.Self = true; + if (IsMe(user.NickName)) emit.Self = true; } return emit; @@ -593,7 +835,7 @@ namespace IrcStates var nickLower = CaseFold(line.Hostmask.NickName); // handle own join - if (nickLower == NickNameLower) + if (IsMe(nickLower)) { emit.Self = true; if (!HasChannel(channelLower)) @@ -661,7 +903,7 @@ namespace IrcStates } } - if (nickLower == NickNameLower) + if (IsMe(nickLower)) { emit.Self = true; NickName = nick; diff --git a/IrcStates/Tests/Cap.cs b/IrcStates/Tests/Cap.cs index 4f63c55..3ce52f8 100644 --- a/IrcStates/Tests/Cap.cs +++ b/IrcStates/Tests/Cap.cs @@ -30,8 +30,9 @@ namespace IrcStates.Tests _server.Parse(new Line("CAP * LS * :a b")); CollectionAssert.AreEqual(new Dictionary(), _server.AvailableCaps); _server.Parse(new Line("CAP * LS :c")); - CollectionAssert.AreEqual(new Dictionary {{"a", ""}, {"b", ""}, {"c", ""}}, - _server.AvailableCaps); + Assert.IsTrue(_server.AvailableCaps.ContainsKey("a")); + Assert.IsTrue(_server.AvailableCaps.ContainsKey("b")); + Assert.IsTrue(_server.AvailableCaps.ContainsKey("c")); } [TestMethod] diff --git a/IrcStates/Tests/Mode.cs b/IrcStates/Tests/Mode.cs index ba05976..799afd6 100644 --- a/IrcStates/Tests/Mode.cs +++ b/IrcStates/Tests/Mode.cs @@ -63,8 +63,7 @@ namespace IrcStates.Tests _server.Parse(new Line("MODE #chan +b asd!*@*")); var channel = _server.Channels["#chan"]; - CollectionAssert.AreEqual(new Dictionary> {{"b", new List {"asd!*@*"}}}, - channel.ListModes); + CollectionAssert.AreEqual(new List {"asd!*@*"}, channel.ListModes["b"]); } [TestMethod] @@ -150,8 +149,7 @@ namespace IrcStates.Tests var channel = _server.Channels["#chan"]; CollectionAssert.AreEqual(new Dictionary {{"k", "pass"}, {"l", "10"}, {"i", null}}, channel.Modes); - CollectionAssert.AreEqual(new Dictionary> {{"b", new List {"*!*@*"}}}, - channel.ListModes); + CollectionAssert.AreEqual(new List {"*!*@*"}, channel.ListModes["b"]); } [TestMethod] -- cgit 1.4.1