From 023e29ab75f1f413f918346edaddafdf8b1df5ee Mon Sep 17 00:00:00 2001 From: Ben Harris Date: Wed, 13 May 2020 23:34:33 -0400 Subject: [PATCH] implement some stuff --- IrcStates/Channel.cs | 7 ++ IrcStates/ChannelUser.cs | 5 + IrcStates/ISupport.cs | 4 + IrcStates/ISupportPrefix.cs | 10 ++ IrcStates/Server.cs | 220 +++++++++++++++++++++++++++++++++--- IrcStates/Tests/Casemap.cs | 2 +- IrcStates/Tests/Channel.cs | 24 ++++ IrcStates/Tests/User.cs | 7 ++ IrcStates/User.cs | 5 + 9 files changed, 267 insertions(+), 17 deletions(-) diff --git a/IrcStates/Channel.cs b/IrcStates/Channel.cs index b502f2a..16b4654 100644 --- a/IrcStates/Channel.cs +++ b/IrcStates/Channel.cs @@ -6,6 +6,13 @@ namespace IrcStates { public class Channel { + public Channel() + { + Users = new Dictionary(); + ListModes = new Dictionary>(); + Modes = new Dictionary(); + } + public string Name { get; set; } public string NameLower { get; set; } public Dictionary Users { get; set; } diff --git a/IrcStates/ChannelUser.cs b/IrcStates/ChannelUser.cs index 14f4280..220a9c4 100644 --- a/IrcStates/ChannelUser.cs +++ b/IrcStates/ChannelUser.cs @@ -5,5 +5,10 @@ namespace IrcStates public class ChannelUser { public List Modes { get; set; } + + public ChannelUser() + { + Modes = new List(); + } } } diff --git a/IrcStates/ISupport.cs b/IrcStates/ISupport.cs index afb633d..1e89b6d 100644 --- a/IrcStates/ISupport.cs +++ b/IrcStates/ISupport.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; namespace IrcStates { @@ -35,6 +36,9 @@ namespace IrcStates { if (tokens == null) return; + // remove first and last + tokens = tokens.Skip(1).SkipLast(1); + foreach (var token in tokens) { var split = token.Split('=', 2); diff --git a/IrcStates/ISupportPrefix.cs b/IrcStates/ISupportPrefix.cs index b319e04..b535212 100644 --- a/IrcStates/ISupportPrefix.cs +++ b/IrcStates/ISupportPrefix.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; namespace IrcStates { @@ -17,11 +18,20 @@ namespace IrcStates public List Modes { get; set; } public List Prefixes { get; set; } + public string FromMode(char mode) + { + return FromMode(mode.ToString(CultureInfo.InvariantCulture)); + } public string FromMode(string mode) { return Modes.Contains(mode) ? Modes[Modes.IndexOf(mode)] : null; } + public string FromPrefix(char prefix) + { + return FromPrefix(prefix.ToString(CultureInfo.InvariantCulture)); + } + public string FromPrefix(string prefix) { return Prefixes.Contains(prefix) ? Prefixes[Prefixes.IndexOf(prefix)] : null; diff --git a/IrcStates/Server.cs b/IrcStates/Server.cs index a552890..ea7dd9e 100644 --- a/IrcStates/Server.cs +++ b/IrcStates/Server.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Runtime.Serialization; using IrcTokens; namespace IrcStates @@ -103,6 +104,55 @@ namespace IrcStates 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 (nickLower == NickNameLower) + { + 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); + } + public List<(Line, Emit)> Recv(byte[] data) { if (data == null) return null; @@ -250,17 +300,87 @@ namespace IrcStates private Emit HandleNames(Line line) { - throw new NotImplementedException(); + 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(); + 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 (nickLower == NickNameLower) 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) { - throw new NotImplementedException(); + Users.Clear(); + Channels.Clear(); + return new Emit(); } private Emit HandleQuit(Line line) { - throw new NotImplementedException(); + var emit = new Emit(); + var nickLower = CaseFold(line.Hostmask.NickName); + if (line.Params.Any()) emit.Text = line.Params[0]; + + if (nickLower == NickNameLower || 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) @@ -270,29 +390,100 @@ namespace IrcStates private Emit HandleKick(Line line) { - throw new NotImplementedException(); + var (emit, kicked) = UserPart(line, line.Params[1], line.Params[0], 2); + if (kicked != null) + { + emit.UserTarget = kicked; + if (kicked.NickNameLower == NickNameLower) emit.Self = true; + + var kickerLower = CaseFold(line.Hostmask.NickName); + if (kickerLower == NickNameLower) emit.SelfSource = true; + + emit.UserSource = Users.ContainsKey(kickerLower) + ? Users[kickerLower] + : CreateUser(line.Hostmask.NickName, kickerLower); + } + + return emit; } private Emit HandlePart(Line line) { - throw new NotImplementedException(); + var (emit, user) = UserPart(line, line.Hostmask.NickName, line.Params[0], 1); + if (user != null) + { + emit.User = user; + if (user.NickNameLower == NickNameLower) emit.Self = true; + } + + return emit; } + private Emit HandleJoin(Line line) { - throw new NotImplementedException(); + 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 (nickLower == NickNameLower) + { + 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 nick = line.Params[0]; var nickLower = CaseFold(line.Hostmask.NickName); var emit = new Emit(); if (Users.ContainsKey(nickLower)) { - var user = Users[nick]; + var user = Users[nickLower]; + Users.Remove(nickLower); emit.User = user; var oldNickLower = user.NickNameLower; @@ -301,7 +492,7 @@ namespace IrcStates Users[newNickLower] = user; foreach (var channelLower in user.Channels) { - var channel = Channels[channelLower]; + var channel = Channels[channelLower]; var channelUser = channel.Users[oldNickLower]; channel.Users.Remove(oldNickLower); channel.Users[newNickLower] = channelUser; @@ -310,8 +501,8 @@ namespace IrcStates if (nickLower == NickNameLower) { - emit.Self = true; - NickName = nick; + emit.Self = true; + NickName = nick; NickNameLower = CaseFold(nick); } @@ -320,10 +511,7 @@ namespace IrcStates private Emit HandleMotd(Line line) { - if (line.Command == Numeric.RPL_MOTDSTART) - { - Motd.Clear(); - } + if (line.Command == Numeric.RPL_MOTDSTART) Motd.Clear(); var emit = new Emit {Text = line.Params[1]}; Motd.Add(line.Params[1]); @@ -333,7 +521,7 @@ namespace IrcStates private Emit HandleISupport(Line line) { ISupport = new ISupport(); - ISupport.Parse(line.Params.Skip(1).SkipLast(1)); + ISupport.Parse(line.Params); return new Emit(); } diff --git a/IrcStates/Tests/Casemap.cs b/IrcStates/Tests/Casemap.cs index eccc828..6022593 100644 --- a/IrcStates/Tests/Casemap.cs +++ b/IrcStates/Tests/Casemap.cs @@ -51,7 +51,7 @@ namespace IrcStates.Tests 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.NickName); Assert.AreEqual("newnickname", server.NickNameLower); } } diff --git a/IrcStates/Tests/Channel.cs b/IrcStates/Tests/Channel.cs index d793920..fea4ddb 100644 --- a/IrcStates/Tests/Channel.cs +++ b/IrcStates/Tests/Channel.cs @@ -100,6 +100,30 @@ namespace IrcStates.Tests 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() { diff --git a/IrcStates/Tests/User.cs b/IrcStates/Tests/User.cs index 4c78255..5857cfc 100644 --- a/IrcStates/Tests/User.cs +++ b/IrcStates/Tests/User.cs @@ -15,6 +15,13 @@ namespace IrcStates.Tests _server.Parse(new Line("001 nickname")); } + [TestMethod] + public void Welcome() + { + Assert.AreEqual("test", _server.Name); + Assert.AreEqual("nickname", _server.NickName); + } + [TestMethod] public void NicknameChange() { diff --git a/IrcStates/User.cs b/IrcStates/User.cs index b9a9570..7df331b 100644 --- a/IrcStates/User.cs +++ b/IrcStates/User.cs @@ -4,6 +4,11 @@ namespace IrcStates { public class User { + public User() + { + Channels = new HashSet(); + } + public string NickName { get; set; } public string NickNameLower { get; set; }