using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Runtime.Serialization; using IrcTokens; namespace IrcStates { public class Server { public const string WhoType = "525"; // randomly generated private readonly StatefulDecoder _decoder; private Dictionary TempCaps; public Server(string name) { Name = name; Registered = false; Modes = new List(); Motd = new List(); _decoder = new StatefulDecoder(); Users = new Dictionary(); Channels = new Dictionary(); ISupport = new ISupport(); HasCap = false; TempCaps = new Dictionary(); AvailableCaps = new Dictionary(); AgreedCaps = new List(); } 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 Modes { get; set; } public List Motd { get; set; } public Dictionary Users { get; set; } public Dictionary Channels { get; set; } public Dictionary AvailableCaps { get; set; } public List 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); } public bool CaseFoldEquals(string s1, string s2) { return CaseFold(s1) == CaseFold(s2); } public bool IsMe(string nickname) { return CaseFold(nickname) == NickNameLower; } public bool HasUser(string nickname) { return Users.ContainsKey(CaseFold(nickname)); } private void AddUser(string nickname, string nicknameLower) { var user = CreateUser(nickname, nicknameLower); Users[nicknameLower] = user; } private User CreateUser(string nickname, string nicknameLower) { var user = new User(); user.SetNickName(nickname, nicknameLower); return user; } public 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)); } public 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 (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; var lines = _decoder.Push(data, 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; 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); } return new Emit(); } private Emit HandleSetname(Line line) { throw new NotImplementedException(); } private Emit HandleAway(Line line) { throw new NotImplementedException(); } private Emit HandleAccount(Line line) { throw new NotImplementedException(); } private Emit HandleCap(Line line) { throw new NotImplementedException(); } private Emit HandleLoggedIn(Line line) { throw new NotImplementedException(); } private Emit HandleChghost(Line line) { throw new NotImplementedException(); } private Emit HandleWhoIsUser(Line line) { throw new NotImplementedException(); } private Emit HandleWhox(Line line) { throw new NotImplementedException(); } private Emit HandleWhoReply(Line line) { throw new NotImplementedException(); } private Emit HandleVisibleHost(Line line) { throw new NotImplementedException(); } private Emit HandleMessage(Line line) { throw new NotImplementedException(); } private Emit HandleUModeIs(Line line) { throw new NotImplementedException(); } private Emit HandleChannelModeIs(Line line) { throw new NotImplementedException(); } private Emit HandleMode(Line line) { throw new NotImplementedException(); } private Emit HandleTopicTime(Line line) { throw new NotImplementedException(); } private Emit HandleTopicNumeric(Line line) { throw new NotImplementedException(); } private Emit HandleTopic(Line line) { throw new NotImplementedException(); } private Emit HandleCreationTime(Line line) { throw new NotImplementedException(); } 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(); 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) { 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 (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) { throw new NotImplementedException(); } private Emit HandleKick(Line line) { 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) { 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) { 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 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 (nickLower == NickNameLower) { 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(); } } }