about summary refs log blame commit diff
path: root/IrcTokens/Line.cs
blob: 6198c04cca1e03819a76cdee8f6adc942f0072b0 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
















                                                            
                                         
 











                                                                                                                                                                  
























































































                                                                                                                                  
                                       










                                                  
                                                                                                 
                                          
                                                                                                      


                                      
                                                                                                  







                                          
using System;
using System.Collections.Generic;
using System.Linq;

namespace IrcTokens
{
    /// <summary>
    /// Tools to represent, parse, and format IRC lines
    /// </summary>
    public class Line
    {
        public Dictionary<string, string> Tags { get; set; }
        public string Source { get; set; }
        public string Command { get; set; }
        public List<string> Params { get; set; }

        private Hostmask _hostmask;
        private readonly string _rawLine;

        public override string ToString() =>
            $"Line(source={Source}, command={Command}, tags={string.Join(";", Tags.Select(kvp => $"{kvp.Key}={kvp.Value}"))}, params={string.Join(",", Params)})";

        public override int GetHashCode() => Format().GetHashCode();

        public override bool Equals(object obj)
        {
            if (obj == null || GetType() != obj.GetType())
                return false;

            return Format() == ((Line) obj).Format();
        }

        public Hostmask Hostmask =>
            _hostmask ??= new Hostmask(Source);

        public Line() { }

        /// <summary>
        /// Build new <see cref="Line"/> object parsed from <param name="line">a string</param>. Analogous to irctokens.tokenise()
        /// </summary>
        /// <param name="line">irc line to parse</param>
        public Line(string line)
        {
            _rawLine = line;
            string[] split;

            if (line.StartsWith('@'))
            {
                Tags = new Dictionary<string, string>();

                split = line.Split(" ");
                var messageTags = split[0];
                line = string.Join(" ", split.Skip(1));

                foreach (var part in messageTags.Substring(1).Split(';'))
                {
                    if (part.Contains('='))
                    {
                        split = part.Split('=');
                        Tags[split[0]] = Protocol.UnescapeTag(split[1]);
                    }
                    else
                    {
                        Tags[part] = string.Empty;
                    }
                }
            }

            string trailing;
            if (line.Contains(" :"))
            {
                split = line.Split(" :");
                line = split[0];
                trailing = string.Join(" :", split.Skip(1));
            }
            else
            {
                trailing = null;
            }

            Params = line.Contains(' ')
                ? line.Split(' ').Where(p => !string.IsNullOrWhiteSpace(p)).ToList()
                : new List<string> {line};

            if (Params[0].StartsWith(':'))
            {
                Source = Params[0].Substring(1);
                Params.RemoveAt(0);
            }

            if (Params.Count > 0)
            {
                Command = Params[0].ToUpper();
                Params.RemoveAt(0);
            }

            if (trailing != null)
            {
                Params.Add(trailing);
            }
        }

        /// <summary>
        /// Format a <see cref="Line"/> as a standards-compliant IRC line
        /// </summary>
        /// <returns>formatted irc line</returns>
        public string Format()
        {
            var outs = new List<string>();

            if (Tags != null && Tags.Any())
            {
                var tags = Tags.Keys
                    .Select(key => string.IsNullOrWhiteSpace(Tags[key]) ? key : $"{key}={Protocol.EscapeTag(Tags[key])}")
                    .ToList();

                outs.Add($"@{string.Join(";", tags)}");
            }

            if (Source != null)
                outs.Add($":{Source}");

            outs.Add(Command);

            if (Params != null && Params.Any())
            {
                var last = Params[^1];
                Params.RemoveAt(Params.Count - 1);

                foreach (var p in Params)
                {
                    if (p.Contains(' '))
                        throw new ArgumentException("non-last parameters cannot have spaces", p);
                    if (p.StartsWith(':'))
                        throw new ArgumentException("non-last parameters cannot start with colon", p);
                }
                outs.AddRange(Params);

                if (string.IsNullOrWhiteSpace(last) || last.Contains(' ') || last.StartsWith(':'))
                    last = $":{last}";
                outs.Add(last);
            }

            return string.Join(" ", outs);
        }
    }
}