152 lines
4.7 KiB
C#
152 lines
4.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
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(StringComparison.Ordinal);
|
|
|
|
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)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(line))
|
|
throw new ArgumentNullException(nameof(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('=', StringComparison.Ordinal))
|
|
{
|
|
split = part.Split('=', 2);
|
|
Tags[split[0]] = Protocol.UnescapeTag(split[1]);
|
|
}
|
|
else
|
|
{
|
|
Tags[part] = string.Empty;
|
|
}
|
|
}
|
|
}
|
|
|
|
string trailing;
|
|
if (line.Contains(" :", StringComparison.Ordinal))
|
|
{
|
|
split = line.Split(" :", 2);
|
|
line = split[0];
|
|
trailing = split[1];
|
|
}
|
|
else
|
|
{
|
|
trailing = null;
|
|
}
|
|
|
|
Params = line.Contains(' ', StringComparison.Ordinal)
|
|
? line.Split(' ', StringSplitOptions.RemoveEmptyEntries).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(CultureInfo.InvariantCulture);
|
|
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(' ', StringComparison.Ordinal))
|
|
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(' ', StringComparison.Ordinal) || last.StartsWith(':'))
|
|
last = $":{last}";
|
|
outs.Add(last);
|
|
}
|
|
|
|
return string.Join(" ", outs);
|
|
}
|
|
}
|
|
}
|