about summary refs log blame commit diff
path: root/IrcStates/Server.cs
blob: ea7dd9ec25e4ec6ba952cc2da522ce08102a1463 (plain) (tree)
1
2
3
4
5
6
7
8
                
                                 
                           
                  
                                   


                   


                       
                                                                  

                                                  








                                                    

































                                                                     



                                                               












































                                                                                                 
















































                                                                                                      


















































































































































                                                                                 















































                                                                                                 



                                           


                              



                                          





















                                                                                








                                                














                                                                                                     



                                          







                                                                                         

         
 

                                          















































                                                                                           

         
 

                                          
                                           





                                                             

                                            







                                                           
                                                             







                                                                  

                                     







                                               
                                                                    








                                                        
                                        



                              

                                             
                                           
                                                     
                                 

                              

     
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<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);
        }

        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<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 (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();
        }
    }
}