add more robust parsing tests
This commit is contained in:
parent
5ca368ea22
commit
32d3731be9
|
@ -9,7 +9,7 @@ namespace ChatSharp.Events
|
|||
{
|
||||
internal IrcNoticeEventArgs(IrcMessage message)
|
||||
{
|
||||
if (message.Parameters.Length != 2)
|
||||
if (message.Parameters.Count != 2)
|
||||
throw new IrcProtocolException("NOTICE was delivered in incorrect format");
|
||||
Message = message;
|
||||
}
|
||||
|
@ -29,6 +29,6 @@ namespace ChatSharp.Events
|
|||
/// The source of the notice (often a user).
|
||||
/// </summary>
|
||||
/// <value>The source.</value>
|
||||
public string Source => Message.Prefix;
|
||||
public string Source => Message.Source;
|
||||
}
|
||||
}
|
|
@ -59,7 +59,7 @@ namespace ChatSharp.Handlers
|
|||
// Check if the enabled capabilities count is the same as the ones
|
||||
// acknowledged by the server.
|
||||
if (client.IsNegotiatingCapabilities &&
|
||||
client.Capabilities.Enabled.Count() == acceptedCaps.Count() && !client.IsAuthenticatingSasl)
|
||||
client.Capabilities.Enabled.Count() == acceptedCaps.Length && !client.IsAuthenticatingSasl)
|
||||
{
|
||||
client.SendRawMessage("CAP END");
|
||||
client.IsNegotiatingCapabilities = false;
|
||||
|
@ -76,7 +76,7 @@ namespace ChatSharp.Handlers
|
|||
// Check if the disabled capabilities count is the same as the ones
|
||||
// rejected by the server.
|
||||
if (client.IsNegotiatingCapabilities &&
|
||||
client.Capabilities.Disabled.Count() == rejectedCaps.Count())
|
||||
client.Capabilities.Disabled.Count() == rejectedCaps.Length)
|
||||
{
|
||||
client.SendRawMessage("CAP END");
|
||||
client.IsNegotiatingCapabilities = false;
|
||||
|
@ -103,7 +103,7 @@ namespace ChatSharp.Handlers
|
|||
wantCaps.AddRange(newCaps.Where(cap =>
|
||||
client.Capabilities.Contains(cap) && !client.Capabilities[cap].IsEnabled));
|
||||
|
||||
client.SendRawMessage(string.Format("CAP REQ :{0}", string.Join(" ", wantCaps)));
|
||||
client.SendRawMessage($"CAP REQ :{string.Join(" ", wantCaps)}");
|
||||
break;
|
||||
case "DEL":
|
||||
var disabledCaps = message.Parameters[2].Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries)
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace ChatSharp.Handlers
|
|||
{
|
||||
public static void HandleJoin(IrcClient client, IrcMessage message)
|
||||
{
|
||||
var user = client.Users.GetOrAdd(message.Prefix);
|
||||
var user = client.Users.GetOrAdd(message.Source);
|
||||
var channel = client.Channels.GetOrAdd(message.Parameters[0]);
|
||||
|
||||
if (channel != null)
|
||||
|
@ -51,7 +51,7 @@ namespace ChatSharp.Handlers
|
|||
if (!client.Channels.Contains(message.Parameters[0]))
|
||||
return; // we aren't in this channel, ignore
|
||||
|
||||
var user = client.Users.Get(message.Prefix);
|
||||
var user = client.Users.Get(message.Source);
|
||||
var channel = client.Channels[message.Parameters[0]];
|
||||
|
||||
if (user.Channels.Contains(channel))
|
||||
|
@ -92,10 +92,7 @@ namespace ChatSharp.Handlers
|
|||
|
||||
if (!user.Channels.Contains(channel))
|
||||
user.Channels.Add(channel);
|
||||
if (!user.ChannelModes.ContainsKey(channel))
|
||||
user.ChannelModes.Add(channel, modes);
|
||||
else
|
||||
user.ChannelModes[channel] = modes;
|
||||
user.ChannelModes[channel] = modes;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -117,10 +114,7 @@ namespace ChatSharp.Handlers
|
|||
|
||||
if (!user.Channels.Contains(channel))
|
||||
user.Channels.Add(channel);
|
||||
if (!user.ChannelModes.ContainsKey(channel))
|
||||
user.ChannelModes.Add(channel, modes);
|
||||
else
|
||||
user.ChannelModes[channel] = modes;
|
||||
user.ChannelModes[channel] = modes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -165,7 +159,7 @@ namespace ChatSharp.Handlers
|
|||
var kicked = channel.Users[message.Parameters[1]];
|
||||
if (kicked.Channels.Contains(channel))
|
||||
kicked.Channels.Remove(channel);
|
||||
client.OnUserKicked(new KickEventArgs(channel, new IrcUser(message.Prefix),
|
||||
client.OnUserKicked(new KickEventArgs(channel, new IrcUser(message.Source),
|
||||
kicked, message.Parameters[2]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace ChatSharp.Handlers
|
|||
{
|
||||
public static void HandleBanListPart(IrcClient client, IrcMessage message)
|
||||
{
|
||||
var parameterString = message.RawMessage[message.RawMessage.IndexOf(' ')..];
|
||||
var parameterString = message.Format()[message.Format().IndexOf(' ')..];
|
||||
var parameters = parameterString[parameterString.IndexOf(' ')..].Split(' ');
|
||||
var request = client.RequestManager.PeekOperation("GETMODE b " + parameters[1]);
|
||||
var list = (MaskCollection)request.State;
|
||||
|
@ -20,7 +20,7 @@ namespace ChatSharp.Handlers
|
|||
|
||||
public static void HandleExceptionListPart(IrcClient client, IrcMessage message)
|
||||
{
|
||||
var parameterString = message.RawMessage[(message.RawMessage.IndexOf(' ') + 1)..];
|
||||
var parameterString = message.Format()[(message.Format().IndexOf(' ') + 1)..];
|
||||
var parameters = parameterString[(parameterString.IndexOf(' ') + 1)..].Split(' ');
|
||||
var request = client.RequestManager.PeekOperation("GETMODE e " + parameters[1]);
|
||||
var list = (MaskCollection)request.State;
|
||||
|
@ -36,7 +36,7 @@ namespace ChatSharp.Handlers
|
|||
|
||||
public static void HandleInviteListPart(IrcClient client, IrcMessage message)
|
||||
{
|
||||
var parameterString = message.RawMessage[(message.RawMessage.IndexOf(' ') + 1)..];
|
||||
var parameterString = message.Format()[(message.Format().IndexOf(' ') + 1)..];
|
||||
var parameters = parameterString[(parameterString.IndexOf(' ') + 1)..].Split(' ');
|
||||
var request = client.RequestManager.PeekOperation("GETMODE I " + parameters[1]);
|
||||
var list = (MaskCollection)request.State;
|
||||
|
@ -52,7 +52,7 @@ namespace ChatSharp.Handlers
|
|||
|
||||
public static void HandleQuietListPart(IrcClient client, IrcMessage message)
|
||||
{
|
||||
var parameterString = message.RawMessage[(message.RawMessage.IndexOf(' ') + 1)..];
|
||||
var parameterString = message.Format()[(message.Format().IndexOf(' ') + 1)..];
|
||||
var parameters = parameterString[(parameterString.IndexOf(' ') + 1)..].Split(' ');
|
||||
var request = client.RequestManager.PeekOperation("GETMODE q " + parameters[1]);
|
||||
var list = (MaskCollection)request.State;
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace ChatSharp.Handlers
|
|||
|
||||
public static void HandleMOTD(IrcClient client, IrcMessage message)
|
||||
{
|
||||
if (message.Parameters.Length != 2)
|
||||
if (message.Parameters.Count != 2)
|
||||
throw new IrcProtocolException("372 MOTD message is incorrectly formatted.");
|
||||
var part = message.Parameters[1][2..];
|
||||
MOTD += part + Environment.NewLine;
|
||||
|
|
|
@ -92,7 +92,7 @@ namespace ChatSharp.Handlers
|
|||
|
||||
public static void HandleNick(IrcClient client, IrcMessage message)
|
||||
{
|
||||
var user = client.Users.Get(message.Prefix);
|
||||
var user = client.Users.Get(message.Source);
|
||||
var oldNick = user.Nick;
|
||||
user.Nick = message.Parameters[0];
|
||||
|
||||
|
@ -106,7 +106,7 @@ namespace ChatSharp.Handlers
|
|||
|
||||
public static void HandleQuit(IrcClient client, IrcMessage message)
|
||||
{
|
||||
var user = new IrcUser(message.Prefix);
|
||||
var user = new IrcUser(message.Source);
|
||||
if (client.User.Nick != user.Nick)
|
||||
{
|
||||
client.Users.Remove(user);
|
||||
|
@ -182,17 +182,16 @@ namespace ChatSharp.Handlers
|
|||
continue;
|
||||
}
|
||||
|
||||
if (channel.Mode == null)
|
||||
channel.Mode = string.Empty;
|
||||
channel.Mode ??= string.Empty;
|
||||
// TODO: Support the ones here that aren't done properly
|
||||
if (client.ServerInfo.SupportedChannelModes.ParameterizedSettings.Contains(c))
|
||||
{
|
||||
client.OnModeChanged(new ModeChangeEventArgs(channel.Name, new IrcUser(message.Prefix),
|
||||
client.OnModeChanged(new ModeChangeEventArgs(channel.Name, new IrcUser(message.Source),
|
||||
(add ? "+" : "-") + c + " " + message.Parameters[i++]));
|
||||
}
|
||||
else if (client.ServerInfo.SupportedChannelModes.ChannelLists.Contains(c))
|
||||
{
|
||||
client.OnModeChanged(new ModeChangeEventArgs(channel.Name, new IrcUser(message.Prefix),
|
||||
client.OnModeChanged(new ModeChangeEventArgs(channel.Name, new IrcUser(message.Source),
|
||||
(add ? "+" : "-") + c + " " + message.Parameters[i++]));
|
||||
}
|
||||
else if (client.ServerInfo.SupportedChannelModes.ChannelUserModes.Contains(c))
|
||||
|
@ -218,7 +217,7 @@ namespace ChatSharp.Handlers
|
|||
user.ChannelModes[channel] = null;
|
||||
}
|
||||
|
||||
client.OnModeChanged(new ModeChangeEventArgs(channel.Name, new IrcUser(message.Prefix),
|
||||
client.OnModeChanged(new ModeChangeEventArgs(channel.Name, new IrcUser(message.Source),
|
||||
(add ? "+" : "-") + c + " " + message.Parameters[i++]));
|
||||
}
|
||||
|
||||
|
@ -234,7 +233,7 @@ namespace ChatSharp.Handlers
|
|||
channel.Mode = channel.Mode.Replace(c.ToString(), string.Empty);
|
||||
}
|
||||
|
||||
client.OnModeChanged(new ModeChangeEventArgs(channel.Name, new IrcUser(message.Prefix),
|
||||
client.OnModeChanged(new ModeChangeEventArgs(channel.Name, new IrcUser(message.Source),
|
||||
(add ? "+" : "-") + c));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,13 +19,12 @@ namespace ChatSharp.Handlers
|
|||
{
|
||||
var chunk = b64Bytes.Take(400).ToArray();
|
||||
b64Bytes = b64Bytes.Skip(400).ToArray();
|
||||
client.SendRawMessage(string.Format("AUTHENTICATE {0}", Encoding.UTF8.GetString(chunk)));
|
||||
client.SendRawMessage($"AUTHENTICATE {Encoding.UTF8.GetString(chunk)}");
|
||||
}
|
||||
|
||||
if (b64Bytes.Length > 0)
|
||||
client.SendRawMessage(string.Format("AUTHENTICATE {0}", Encoding.UTF8.GetString(b64Bytes)));
|
||||
else
|
||||
client.SendRawMessage("AUTHENTICATE +");
|
||||
client.SendRawMessage(b64Bytes.Length > 0
|
||||
? $"AUTHENTICATE {Encoding.UTF8.GetString(b64Bytes)}"
|
||||
: "AUTHENTICATE +");
|
||||
|
||||
client.IsAuthenticatingSasl = false;
|
||||
}
|
||||
|
|
|
@ -6,8 +6,7 @@ namespace ChatSharp.Handlers
|
|||
{
|
||||
public static void HandleISupport(IrcClient client, IrcMessage message)
|
||||
{
|
||||
if (client.ServerInfo == null)
|
||||
client.ServerInfo = new ServerInfo();
|
||||
client.ServerInfo ??= new ServerInfo();
|
||||
foreach (var item in message.Parameters)
|
||||
{
|
||||
string key, value;
|
||||
|
@ -86,12 +85,11 @@ namespace ChatSharp.Handlers
|
|||
break;
|
||||
}
|
||||
else
|
||||
switch (key.ToUpper())
|
||||
client.ServerInfo.ExtendedWho = key.ToUpper() switch
|
||||
{
|
||||
case "WHOX":
|
||||
client.ServerInfo.ExtendedWho = true;
|
||||
break;
|
||||
}
|
||||
"WHOX" => true,
|
||||
_ => client.ServerInfo.ExtendedWho
|
||||
};
|
||||
}
|
||||
|
||||
client.OnServerInfoReceived(new SupportsEventArgs(client.ServerInfo));
|
||||
|
@ -101,9 +99,8 @@ namespace ChatSharp.Handlers
|
|||
{
|
||||
// 004 sendak.freenode.net ircd-seven-1.1.3 DOQRSZaghilopswz CFILMPQbcefgijklmnopqrstvz bkloveqjfI
|
||||
// TODO: Figure out how to properly handle this message
|
||||
if (client.ServerInfo == null)
|
||||
client.ServerInfo = new ServerInfo();
|
||||
if (message.Parameters.Length >= 5)
|
||||
client.ServerInfo ??= new ServerInfo();
|
||||
if (message.Parameters.Count >= 5)
|
||||
foreach (var c in message.Parameters[4])
|
||||
if (!client.ServerInfo.SupportedChannelModes.ChannelUserModes.Contains(c))
|
||||
client.ServerInfo.SupportedChannelModes.ChannelUserModes += c.ToString();
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace ChatSharp.Handlers
|
|||
{
|
||||
public static void HandleWhoIsUser(IrcClient client, IrcMessage message)
|
||||
{
|
||||
if (message.Parameters != null && message.Parameters.Length >= 6)
|
||||
if (message.Parameters != null && message.Parameters.Count >= 6)
|
||||
{
|
||||
var whois = (WhoIs)client.RequestManager.PeekOperation("WHOIS " + message.Parameters[1]).State;
|
||||
whois.User.Nick = message.Parameters[1];
|
||||
|
@ -212,7 +212,7 @@ namespace ChatSharp.Handlers
|
|||
whox.User.RealName = message.Parameters[fieldIdx];
|
||||
fieldIdx++;
|
||||
}
|
||||
} while (fieldIdx < message.Parameters.Length - 1);
|
||||
} while (fieldIdx < message.Parameters.Count - 1);
|
||||
|
||||
whoxList.Add(whox);
|
||||
}
|
||||
|
@ -255,13 +255,13 @@ namespace ChatSharp.Handlers
|
|||
|
||||
public static void HandleAccount(IrcClient client, IrcMessage message)
|
||||
{
|
||||
var user = client.Users.GetOrAdd(message.Prefix);
|
||||
var user = client.Users.GetOrAdd(message.Source);
|
||||
user.Account = message.Parameters[0];
|
||||
}
|
||||
|
||||
public static void HandleChangeHost(IrcClient client, IrcMessage message)
|
||||
{
|
||||
var user = client.Users.Get(message.Prefix);
|
||||
var user = client.Users.Get(message.Source);
|
||||
|
||||
// Only handle CHGHOST for users we know
|
||||
if (user != null)
|
||||
|
|
|
@ -85,9 +85,9 @@ namespace ChatSharp
|
|||
if (Channels.Contains(channel))
|
||||
throw new InvalidOperationException("Client is already present in channel.");
|
||||
|
||||
var joinCmd = string.Format("JOIN {0}", channel);
|
||||
var joinCmd = $"JOIN {channel}";
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
joinCmd += string.Format(" {0}", key);
|
||||
joinCmd += $" {key}";
|
||||
|
||||
SendRawMessage(joinCmd, channel);
|
||||
|
||||
|
@ -187,7 +187,7 @@ namespace ChatSharp
|
|||
|
||||
var whoQuery = string.Format("WHO {0} {1}%{2},{3}", target, flags.AsString(), _fields.AsString(),
|
||||
queryType);
|
||||
var queryKey = string.Format("WHO {0} {1} {2:D}", target, queryType, _fields);
|
||||
var queryKey = $"WHO {target} {queryType} {_fields:D}";
|
||||
|
||||
RequestManager.QueueOperation(queryKey,
|
||||
new RequestOperation(whox, ro => { callback?.Invoke((List<ExtendedWho>)ro.State); }));
|
||||
|
@ -197,7 +197,7 @@ namespace ChatSharp
|
|||
{
|
||||
var whox = new List<ExtendedWho>();
|
||||
|
||||
var whoQuery = string.Format("WHO {0}", target);
|
||||
var whoQuery = $"WHO {target}";
|
||||
|
||||
RequestManager.QueueOperation(whoQuery,
|
||||
new RequestOperation(whox, ro => { callback?.Invoke((List<ExtendedWho>)ro.State); }));
|
||||
|
|
|
@ -91,10 +91,7 @@ namespace ChatSharp
|
|||
if (parts.Length > 2 || parts.Length == 0)
|
||||
throw new FormatException("Server address is not in correct format ('hostname:port')");
|
||||
ServerHostname = parts[0];
|
||||
if (parts.Length > 1)
|
||||
ServerPort = int.Parse(parts[1]);
|
||||
else
|
||||
ServerPort = 6667;
|
||||
ServerPort = parts.Length > 1 ? int.Parse(parts[1]) : 6667;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,7 +194,7 @@ namespace ChatSharp
|
|||
/// </summary>
|
||||
public void ConnectAsync()
|
||||
{
|
||||
if (Socket != null && Socket.Connected)
|
||||
if (Socket is { Connected: true })
|
||||
throw new InvalidOperationException("Socket is already connected to server.");
|
||||
Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
ReadBuffer = new byte[ReadBufferLength];
|
||||
|
@ -259,10 +256,9 @@ namespace ChatSharp
|
|||
NetworkStream = new NetworkStream(Socket);
|
||||
if (UseSSL)
|
||||
{
|
||||
if (IgnoreInvalidSSL)
|
||||
NetworkStream = new SslStream(NetworkStream, false, (a, b, c, d) => true);
|
||||
else
|
||||
NetworkStream = new SslStream(NetworkStream);
|
||||
NetworkStream = IgnoreInvalidSSL
|
||||
? new SslStream(NetworkStream, false, (a, b, c, d) => true)
|
||||
: new SslStream(NetworkStream);
|
||||
((SslStream)NetworkStream).AuthenticateAsClient(ServerHostname);
|
||||
}
|
||||
|
||||
|
@ -334,9 +330,9 @@ namespace ChatSharp
|
|||
{
|
||||
OnRawMessageReceived(new RawMessageEventArgs(rawMessage, false));
|
||||
var message = new IrcMessage(rawMessage);
|
||||
if (Handlers.ContainsKey(message.Command.ToUpper()))
|
||||
if (Handlers.TryGetValue(message.Command, out var handler))
|
||||
{
|
||||
Handlers[message.Command.ToUpper()](this, message);
|
||||
handler(this, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -370,7 +366,7 @@ namespace ChatSharp
|
|||
/// </summary>
|
||||
public void SendIrcMessage(IrcMessage message)
|
||||
{
|
||||
SendRawMessage(message.RawMessage);
|
||||
SendRawMessage(message.Format());
|
||||
}
|
||||
|
||||
private void MessageSent(IAsyncResult result)
|
||||
|
|
|
@ -1,130 +1,243 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace ChatSharp
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents a raw IRC message. This is a low-level construct - PrivateMessage is used
|
||||
/// to represent messages sent from users.
|
||||
/// </summary>
|
||||
public class IrcMessage
|
||||
public class IrcMessage : IEquatable<IrcMessage>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes and decodes an IRC message, given the raw message from the server.
|
||||
/// </summary>
|
||||
public IrcMessage(string rawMessage)
|
||||
private static readonly string[] TagUnescaped = {"\\", " ", ";", "\r", "\n"};
|
||||
private static readonly string[] TagEscaped = {@"\\", "\\s", "\\:", "\\r", "\\n"};
|
||||
|
||||
public IrcMessage()
|
||||
{
|
||||
RawMessage = rawMessage;
|
||||
Tags = Array.Empty<KeyValuePair<string, string>>();
|
||||
}
|
||||
|
||||
public IrcMessage(string command, params string[] parameters)
|
||||
{
|
||||
Command = command.ToUpperInvariant();
|
||||
Parameters = parameters.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse and tokenize an IRC message, given the raw message from the server.
|
||||
/// </summary>
|
||||
public IrcMessage(string line)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line)) throw new ArgumentNullException(nameof(line));
|
||||
|
||||
if (rawMessage.StartsWith("@"))
|
||||
string[] split;
|
||||
|
||||
if (line.StartsWith('@'))
|
||||
{
|
||||
var rawTags = rawMessage[1..rawMessage.IndexOf(' ')];
|
||||
rawMessage = rawMessage[(rawMessage.IndexOf(' ') + 1)..];
|
||||
Tags = new Dictionary<string, string>();
|
||||
|
||||
// Parse tags as key value pairs
|
||||
var tags = new List<KeyValuePair<string, string>>();
|
||||
foreach (var rawTag in rawTags.Split(';'))
|
||||
{
|
||||
var replacedTag = rawTag.Replace(@"\:", ";");
|
||||
// The spec declares `@a=` as a tag with an empty value, while `@b;` as a tag with a null value
|
||||
KeyValuePair<string, string> tag = new KeyValuePair<string, string>(replacedTag, null);
|
||||
split = line.Split(" ", 2);
|
||||
var messageTags = split[0];
|
||||
line = split[1];
|
||||
|
||||
if (replacedTag.Contains("="))
|
||||
foreach (var part in messageTags[1..].Split(';'))
|
||||
if (part.Contains('=', StringComparison.Ordinal))
|
||||
{
|
||||
var key = replacedTag.Substring(0, replacedTag.IndexOf("=", StringComparison.Ordinal));
|
||||
var value = replacedTag[(replacedTag.IndexOf("=", StringComparison.Ordinal) + 1)..];
|
||||
tag = new KeyValuePair<string, string>(key, value);
|
||||
split = part.Split('=', 2);
|
||||
Tags[split[0]] = UnescapeTag(split[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Tags[part] = null;
|
||||
}
|
||||
|
||||
tags.Add(tag);
|
||||
}
|
||||
|
||||
Tags = tags.ToArray();
|
||||
}
|
||||
|
||||
if (rawMessage.StartsWith(":"))
|
||||
string trailing;
|
||||
if (line.Contains(" :", StringComparison.Ordinal))
|
||||
{
|
||||
Prefix = rawMessage[1..rawMessage.IndexOf(' ')];
|
||||
rawMessage = rawMessage[(rawMessage.IndexOf(' ') + 1)..];
|
||||
}
|
||||
|
||||
if (rawMessage.Contains(' '))
|
||||
{
|
||||
Command = rawMessage.Remove(rawMessage.IndexOf(' '));
|
||||
rawMessage = rawMessage[(rawMessage.IndexOf(' ') + 1)..];
|
||||
// Parse parameters
|
||||
var parameters = new List<string>();
|
||||
while (!string.IsNullOrEmpty(rawMessage))
|
||||
{
|
||||
if (rawMessage.StartsWith(":"))
|
||||
{
|
||||
parameters.Add(rawMessage[1..]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!rawMessage.Contains(' '))
|
||||
{
|
||||
parameters.Add(rawMessage);
|
||||
rawMessage = string.Empty;
|
||||
break;
|
||||
}
|
||||
|
||||
parameters.Add(rawMessage.Remove(rawMessage.IndexOf(' ')));
|
||||
rawMessage = rawMessage[(rawMessage.IndexOf(' ') + 1)..];
|
||||
}
|
||||
|
||||
Parameters = parameters.ToArray();
|
||||
split = line.Split(" :", 2);
|
||||
line = split[0];
|
||||
trailing = split[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Violates RFC 1459, but we'll parse it anyway
|
||||
Command = rawMessage;
|
||||
Parameters = Array.Empty<string>();
|
||||
trailing = null;
|
||||
}
|
||||
|
||||
Parameters = line.Contains(' ', StringComparison.Ordinal)
|
||||
? line.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList()
|
||||
: new List<string> {line};
|
||||
|
||||
if (Parameters[0].StartsWith(':'))
|
||||
{
|
||||
Source = Parameters[0][1..];
|
||||
Parameters.RemoveAt(0);
|
||||
}
|
||||
|
||||
if (Parameters.Count > 0)
|
||||
{
|
||||
Command = Parameters[0].ToUpper(CultureInfo.InvariantCulture);
|
||||
Parameters.RemoveAt(0);
|
||||
}
|
||||
|
||||
if (trailing != null) Parameters.Add(trailing);
|
||||
|
||||
// Parse server-time message tag.
|
||||
// Fallback to server-info if both znc.in/server-info and the former exists.
|
||||
//
|
||||
// znc.in/server-time tag
|
||||
if (Tags.Any(tag => tag.Key == "t"))
|
||||
if (Tags?.Any(tag => tag.Key == "t") ?? false)
|
||||
{
|
||||
var tag = Tags.SingleOrDefault(x => x.Key == "t");
|
||||
Timestamp = new Timestamp(tag.Value, true);
|
||||
}
|
||||
// server-time tag
|
||||
else if (Tags.Any(tag => tag.Key == "time"))
|
||||
else if (Tags?.Any(tag => tag.Key == "time") ?? false)
|
||||
{
|
||||
var tag = Tags.SingleOrDefault(x => x.Key == "time");
|
||||
Timestamp = new Timestamp(tag.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(IrcMessage other)
|
||||
{
|
||||
if (other == null) return false;
|
||||
|
||||
return Format() == other.Format();
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Format().GetHashCode(StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as IrcMessage);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Unescape ircv3 tag
|
||||
/// </summary>
|
||||
/// <param name="val">escaped string</param>
|
||||
/// <returns>unescaped string</returns>
|
||||
private static string UnescapeTag(string val)
|
||||
{
|
||||
var unescaped = new StringBuilder();
|
||||
|
||||
var graphemeIterator = StringInfo.GetTextElementEnumerator(val);
|
||||
graphemeIterator.Reset();
|
||||
|
||||
while (graphemeIterator.MoveNext())
|
||||
{
|
||||
var current = graphemeIterator.GetTextElement();
|
||||
|
||||
if (current == @"\")
|
||||
try
|
||||
{
|
||||
graphemeIterator.MoveNext();
|
||||
var next = graphemeIterator.GetTextElement();
|
||||
var pair = current + next;
|
||||
unescaped.Append(TagEscaped.Contains(pair)
|
||||
? TagUnescaped[Array.IndexOf(TagEscaped, pair)]
|
||||
: next);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
else
|
||||
unescaped.Append(current);
|
||||
}
|
||||
|
||||
return unescaped.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The unparsed message.
|
||||
/// Escape strings for use in ircv3 tags
|
||||
/// </summary>
|
||||
public string RawMessage { get; }
|
||||
/// <param name="val">string to escape</param>
|
||||
/// <returns>escaped string</returns>
|
||||
private static string EscapeTag(string val)
|
||||
{
|
||||
for (var i = 0; i < TagUnescaped.Length; ++i)
|
||||
val = val?.Replace(TagUnescaped[i], TagEscaped[i], StringComparison.Ordinal);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats self <see cref="IrcMessage" /> 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
|
||||
.OrderBy(k => k)
|
||||
.Select(key =>
|
||||
string.IsNullOrWhiteSpace(Tags[key]) ? key : $"{key}={EscapeTag(Tags[key])}")
|
||||
.ToList();
|
||||
|
||||
outs.Add($"@{string.Join(";", tags)}");
|
||||
}
|
||||
|
||||
if (Source != null) outs.Add($":{Source}");
|
||||
|
||||
outs.Add(Command);
|
||||
|
||||
if (Parameters != null && Parameters.Any())
|
||||
{
|
||||
var last = Parameters[^1];
|
||||
var withoutLast = Parameters.SkipLast(1).ToList();
|
||||
|
||||
foreach (var p in withoutLast)
|
||||
{
|
||||
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(withoutLast);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(last) || last.Contains(' ', StringComparison.Ordinal) ||
|
||||
last.StartsWith(':'))
|
||||
last = $":{last}";
|
||||
|
||||
outs.Add(last);
|
||||
}
|
||||
|
||||
return string.Join(" ", outs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The message prefix.
|
||||
/// The message source.
|
||||
/// </summary>
|
||||
public string Prefix { get; }
|
||||
public string Source { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The message command.
|
||||
/// </summary>
|
||||
public string Command { get; }
|
||||
public string Command { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional parameters supplied with the message.
|
||||
/// </summary>
|
||||
public string[] Parameters { get; }
|
||||
public List<string> Parameters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The message tags.
|
||||
/// </summary>
|
||||
public KeyValuePair<string, string>[] Tags { get; }
|
||||
public Dictionary<string, string> Tags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The message timestamp in ISO 8601 format.
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace ChatSharp
|
|||
/// </summary>
|
||||
public class IrcUser : IEquatable<IrcUser>
|
||||
{
|
||||
private readonly string _source;
|
||||
internal IrcUser()
|
||||
{
|
||||
Channels = new ChannelCollection();
|
||||
|
@ -18,18 +19,29 @@ namespace ChatSharp
|
|||
/// <summary>
|
||||
/// Constructs an IrcUser given a hostmask or nick.
|
||||
/// </summary>
|
||||
public IrcUser(string host) : this()
|
||||
public IrcUser(string source) : this()
|
||||
{
|
||||
if (!host.Contains("@") && !host.Contains("!"))
|
||||
if (source == null) return;
|
||||
|
||||
_source = source;
|
||||
|
||||
if (source.Contains('@', StringComparison.Ordinal))
|
||||
{
|
||||
Nick = host;
|
||||
var split = source.Split('@');
|
||||
|
||||
Nick = split[0];
|
||||
Hostname = split[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
var mask = host.Split('@', '!');
|
||||
Nick = mask[0];
|
||||
User = mask[1];
|
||||
Hostname = mask.Length <= 2 ? "" : mask[2];
|
||||
Nick = source;
|
||||
}
|
||||
|
||||
if (Nick.Contains('!', StringComparison.Ordinal))
|
||||
{
|
||||
var userSplit = Nick.Split('!');
|
||||
Nick = userSplit[0];
|
||||
User = userSplit[1];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,22 +56,6 @@ namespace ChatSharp
|
|||
Mode = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an IRC user given a nick, user, and password.
|
||||
/// </summary>
|
||||
public IrcUser(string nick, string user, string password) : this(nick, user)
|
||||
{
|
||||
Password = password;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an IRC user given a nick, user, password, and real name.
|
||||
/// </summary>
|
||||
public IrcUser(string nick, string user, string password, string realName) : this(nick, user, password)
|
||||
{
|
||||
RealName = realName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The user's nick.
|
||||
/// </summary>
|
||||
|
@ -110,14 +106,15 @@ namespace ChatSharp
|
|||
/// <summary>
|
||||
/// This user's hostmask (nick!user@host).
|
||||
/// </summary>
|
||||
public string Hostmask => Nick + "!" + User + "@" + Hostname;
|
||||
public string Hostmask => $"{Nick}!{User}@{Hostname}";
|
||||
|
||||
/// <summary>
|
||||
/// True if this user is equal to another (compares hostmasks).
|
||||
/// </summary>
|
||||
public bool Equals(IrcUser other)
|
||||
{
|
||||
return other.Hostmask == Hostmask;
|
||||
if (other == null) return false;
|
||||
return other._source == _source;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -143,8 +140,7 @@ namespace ChatSharp
|
|||
/// </summary>
|
||||
public static bool Match(string mask, string value)
|
||||
{
|
||||
if (value == null)
|
||||
value = string.Empty;
|
||||
value ??= string.Empty;
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
for (; j < value.Length && i < mask.Length; j++)
|
||||
|
@ -179,9 +175,7 @@ namespace ChatSharp
|
|||
/// </summary>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is IrcUser user)
|
||||
return Equals(user);
|
||||
return false;
|
||||
return Equals(obj as IrcUser);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -189,7 +183,7 @@ namespace ChatSharp
|
|||
/// </summary>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Hostmask.GetHashCode();
|
||||
return _source.GetHashCode(StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -197,7 +191,7 @@ namespace ChatSharp
|
|||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return Hostmask;
|
||||
return _source;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ namespace ChatSharp
|
|||
Source = message.Parameters[0];
|
||||
Message = message.Parameters[1];
|
||||
|
||||
User = client.Users.GetOrAdd(message.Prefix);
|
||||
User = client.Users.GetOrAdd(message.Source);
|
||||
if (serverInfo.ChannelTypes.Any(c => Source.StartsWith(c.ToString())))
|
||||
IsChannelMessage = true;
|
||||
else
|
||||
|
|
|
@ -115,8 +115,8 @@ namespace ChatSharp
|
|||
public List<char?> GetModesForNick(string nick)
|
||||
{
|
||||
var supportedPrefixes = Prefixes[1];
|
||||
List<char?> modeList = new List<char?>();
|
||||
List<char> nickPrefixes = new List<char>();
|
||||
var modeList = new List<char?>();
|
||||
var nickPrefixes = new List<char>();
|
||||
|
||||
foreach (var prefix in supportedPrefixes)
|
||||
if (nick.Contains(prefix))
|
||||
|
|
|
@ -2,7 +2,20 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting"/>
|
||||
<Using Include="System"/>
|
||||
<Using Include="System.Collections.Generic"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ChatSharp\ChatSharp.csproj"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="YamlDotNet" Version="15.1.2"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Data\msg-*.yaml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,28 @@
|
|||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace ChatSharp.Tests.Data;
|
||||
|
||||
public class JoinModel
|
||||
{
|
||||
public List<Test> Tests { get; set; }
|
||||
|
||||
public class Test
|
||||
{
|
||||
[YamlMember(Alias = "desc")] public string Description { get; set; }
|
||||
|
||||
public Atoms Atoms { get; set; }
|
||||
|
||||
public List<string> Matches { get; set; }
|
||||
}
|
||||
|
||||
public class Atoms
|
||||
{
|
||||
public Dictionary<string, string> Tags { get; set; }
|
||||
|
||||
public string Source { get; set; }
|
||||
|
||||
public string Verb { get; set; }
|
||||
|
||||
public List<string> Params { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
namespace ChatSharp.Tests.Data;
|
||||
|
||||
public class SplitModel
|
||||
{
|
||||
public List<Test> Tests { get; set; }
|
||||
|
||||
public class Test
|
||||
{
|
||||
public string Input { get; set; }
|
||||
public JoinModel.Atoms Atoms { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
# IRC parser tests
|
||||
# joining atoms into sendable messages
|
||||
|
||||
# Written in 2015 by Daniel Oaks <daniel@danieloaks.net>
|
||||
#
|
||||
# To the extent possible under law, the author(s) have dedicated all copyright
|
||||
# and related and neighboring rights to this software to the public domain
|
||||
# worldwide. This software is distributed without any warranty.
|
||||
#
|
||||
# You should have received a copy of the CC0 Public Domain Dedication along
|
||||
# with this software. If not, see
|
||||
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
|
||||
# some of the tests here originate from grawity's test vectors, which is WTFPL v2 licensed
|
||||
# https://github.com/grawity/code/tree/master/lib/tests
|
||||
# some of the tests here originate from Mozilla's test vectors, which is public domain
|
||||
# https://dxr.mozilla.org/comm-central/source/chat/protocols/irc/test/test_ircMessage.js
|
||||
# some of the tests here originate from SaberUK's test vectors, which he's indicated I am free to include here
|
||||
# https://github.com/SaberUK/ircparser/tree/master/test
|
||||
|
||||
tests:
|
||||
# the desc string holds a description of the test, if it exists
|
||||
|
||||
# the atoms dict has the keys:
|
||||
# * tags: tags dict
|
||||
# tags with no value are an empty string
|
||||
# * source: source string, without single leading colon
|
||||
# * verb: verb string
|
||||
# * params: params split up as a list
|
||||
# if the params key does not exist, assume it is empty
|
||||
# if any other keys do no exist, assume they are null
|
||||
# a key that is null does not exist or is not specified with the
|
||||
# given input string
|
||||
|
||||
# matches is a list of messages that match
|
||||
|
||||
# simple tests
|
||||
- desc: Simple test with verb and params.
|
||||
atoms:
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- "asdf"
|
||||
matches:
|
||||
- "foo bar baz asdf"
|
||||
- "foo bar baz :asdf"
|
||||
|
||||
# with no regular params
|
||||
- desc: Simple test with source and no params.
|
||||
atoms:
|
||||
source: "src"
|
||||
verb: "AWAY"
|
||||
matches:
|
||||
- ":src AWAY"
|
||||
|
||||
- desc: Simple test with source and empty trailing param.
|
||||
atoms:
|
||||
source: "src"
|
||||
verb: "AWAY"
|
||||
params:
|
||||
- ""
|
||||
matches:
|
||||
- ":src AWAY :"
|
||||
|
||||
# with source
|
||||
- desc: Simple test with source.
|
||||
atoms:
|
||||
source: "coolguy"
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- "asdf"
|
||||
matches:
|
||||
- ":coolguy foo bar baz asdf"
|
||||
- ":coolguy foo bar baz :asdf"
|
||||
|
||||
# with trailing param
|
||||
- desc: Simple test with trailing param.
|
||||
atoms:
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- "asdf quux"
|
||||
matches:
|
||||
- "foo bar baz :asdf quux"
|
||||
|
||||
- desc: Simple test with empty trailing param.
|
||||
atoms:
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- ""
|
||||
matches:
|
||||
- "foo bar baz :"
|
||||
|
||||
- desc: Simple test with trailing param containing colon.
|
||||
atoms:
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- ":asdf"
|
||||
matches:
|
||||
- "foo bar baz ::asdf"
|
||||
|
||||
# with source and trailing param
|
||||
- desc: Test with source and trailing param.
|
||||
atoms:
|
||||
source: "coolguy"
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- "asdf quux"
|
||||
matches:
|
||||
- ":coolguy foo bar baz :asdf quux"
|
||||
|
||||
- desc: Test with trailing containing beginning+end whitespace.
|
||||
atoms:
|
||||
source: "coolguy"
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- " asdf quux "
|
||||
matches:
|
||||
- ":coolguy foo bar baz : asdf quux "
|
||||
|
||||
- desc: Test with trailing containing what looks like another trailing param.
|
||||
atoms:
|
||||
source: "coolguy"
|
||||
verb: "PRIVMSG"
|
||||
params:
|
||||
- "bar"
|
||||
- "lol :) "
|
||||
matches:
|
||||
- ":coolguy PRIVMSG bar :lol :) "
|
||||
|
||||
- desc: Simple test with source and empty trailing.
|
||||
atoms:
|
||||
source: "coolguy"
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- ""
|
||||
matches:
|
||||
- ":coolguy foo bar baz :"
|
||||
|
||||
- desc: Trailing contains only spaces.
|
||||
atoms:
|
||||
source: "coolguy"
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- " "
|
||||
matches:
|
||||
- ":coolguy foo bar baz : "
|
||||
|
||||
- desc: Param containing tab (tab is not considered SPACE for message splitting).
|
||||
atoms:
|
||||
source: "coolguy"
|
||||
verb: "foo"
|
||||
params:
|
||||
- "b\tar"
|
||||
- "baz"
|
||||
matches:
|
||||
- ":coolguy foo b\tar baz"
|
||||
- ":coolguy foo b\tar :baz"
|
||||
|
||||
# with tags
|
||||
- desc: Tag with no value and space-filled trailing.
|
||||
atoms:
|
||||
tags:
|
||||
"asd": ""
|
||||
source: "coolguy"
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- " "
|
||||
matches:
|
||||
- "@asd :coolguy foo bar baz : "
|
||||
|
||||
- desc: Tags with escaped values.
|
||||
atoms:
|
||||
verb: "foo"
|
||||
tags:
|
||||
"a": "b\\and\nk"
|
||||
"d": "gh;764"
|
||||
matches:
|
||||
- "@a=b\\\\and\\nk;d=gh\\:764 foo"
|
||||
- "@d=gh\\:764;a=b\\\\and\\nk foo"
|
||||
|
||||
- desc: Tags with escaped values and params.
|
||||
atoms:
|
||||
verb: "foo"
|
||||
tags:
|
||||
"a": "b\\and\nk"
|
||||
"d": "gh;764"
|
||||
params:
|
||||
- "par1"
|
||||
- "par2"
|
||||
matches:
|
||||
- "@a=b\\\\and\\nk;d=gh\\:764 foo par1 par2"
|
||||
- "@a=b\\\\and\\nk;d=gh\\:764 foo par1 :par2"
|
||||
- "@d=gh\\:764;a=b\\\\and\\nk foo par1 par2"
|
||||
- "@d=gh\\:764;a=b\\\\and\\nk foo par1 :par2"
|
||||
|
||||
- desc: Tag with long, strange values (including LF and newline).
|
||||
atoms:
|
||||
tags:
|
||||
foo: "\\\\;\\s \r\n"
|
||||
verb: "COMMAND"
|
||||
matches:
|
||||
- "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND"
|
|
@ -0,0 +1,343 @@
|
|||
# IRC parser tests
|
||||
# splitting messages into usable atoms
|
||||
|
||||
# Written in 2015 by Daniel Oaks <daniel@danieloaks.net>
|
||||
#
|
||||
# To the extent possible under law, the author(s) have dedicated all copyright
|
||||
# and related and neighboring rights to this software to the public domain
|
||||
# worldwide. This software is distributed without any warranty.
|
||||
#
|
||||
# You should have received a copy of the CC0 Public Domain Dedication along
|
||||
# with this software. If not, see
|
||||
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
|
||||
# some of the tests here originate from grawity's test vectors, which is WTFPL v2 licensed
|
||||
# https://github.com/grawity/code/tree/master/lib/tests
|
||||
# some of the tests here originate from Mozilla's test vectors, which is public domain
|
||||
# https://dxr.mozilla.org/comm-central/source/chat/protocols/irc/test/test_ircMessage.js
|
||||
# some of the tests here originate from SaberUK's test vectors, which he's indicated I am free to include here
|
||||
# https://github.com/SaberUK/ircparser/tree/master/test
|
||||
|
||||
# we follow RFC1459 with regards to multiple ascii spaces splitting atoms:
|
||||
# The prefix, command, and all parameters are
|
||||
# separated by one (or more) ASCII space character(s) (0x20).
|
||||
# because doing it as RFC2812 says (strictly as a single ascii space) isn't sane
|
||||
|
||||
tests:
|
||||
# input is the string coming directly from the server to parse
|
||||
|
||||
# the atoms dict has the keys:
|
||||
# * tags: tags dict
|
||||
# tags with no value are an empty string
|
||||
# * source: source string, without single leading colon
|
||||
# * verb: verb string
|
||||
# * params: params split up as a list
|
||||
# if the params key does not exist, assume it is empty
|
||||
# if any other keys do no exist, assume they are null
|
||||
# a key that is null does not exist or is not specified with the
|
||||
# given input string
|
||||
|
||||
# simple
|
||||
- input: "foo bar baz asdf"
|
||||
atoms:
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- "asdf"
|
||||
|
||||
# with source
|
||||
- input: ":coolguy foo bar baz asdf"
|
||||
atoms:
|
||||
source: "coolguy"
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- "asdf"
|
||||
|
||||
# with trailing param
|
||||
- input: "foo bar baz :asdf quux"
|
||||
atoms:
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- "asdf quux"
|
||||
|
||||
- input: "foo bar baz :"
|
||||
atoms:
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- ""
|
||||
|
||||
- input: "foo bar baz ::asdf"
|
||||
atoms:
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- ":asdf"
|
||||
|
||||
# with source and trailing param
|
||||
- input: ":coolguy foo bar baz :asdf quux"
|
||||
atoms:
|
||||
source: "coolguy"
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- "asdf quux"
|
||||
|
||||
- input: ":coolguy foo bar baz : asdf quux "
|
||||
atoms:
|
||||
source: "coolguy"
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- " asdf quux "
|
||||
|
||||
- input: ":coolguy PRIVMSG bar :lol :) "
|
||||
atoms:
|
||||
source: "coolguy"
|
||||
verb: "PRIVMSG"
|
||||
params:
|
||||
- "bar"
|
||||
- "lol :) "
|
||||
|
||||
- input: ":coolguy foo bar baz :"
|
||||
atoms:
|
||||
source: "coolguy"
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- ""
|
||||
|
||||
- input: ":coolguy foo bar baz : "
|
||||
atoms:
|
||||
source: "coolguy"
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
- " "
|
||||
|
||||
# with tags
|
||||
- input: "@a=b;c=32;k;rt=ql7 foo"
|
||||
atoms:
|
||||
verb: "foo"
|
||||
tags:
|
||||
"a": "b"
|
||||
"c": "32"
|
||||
"k":
|
||||
"rt": "ql7"
|
||||
|
||||
# with escaped tags
|
||||
- input: "@a=b\\\\and\\nk;c=72\\s45;d=gh\\:764 foo"
|
||||
atoms:
|
||||
verb: "foo"
|
||||
tags:
|
||||
"a": "b\\and\nk"
|
||||
"c": "72 45"
|
||||
"d": "gh;764"
|
||||
|
||||
# with tags and source
|
||||
- input: "@c;h=;a=b :quux ab cd"
|
||||
atoms:
|
||||
tags:
|
||||
"c":
|
||||
"h": ""
|
||||
"a": "b"
|
||||
source: "quux"
|
||||
verb: "ab"
|
||||
params:
|
||||
- "cd"
|
||||
|
||||
# different forms of last param
|
||||
- input: ":src JOIN #chan"
|
||||
atoms:
|
||||
source: "src"
|
||||
verb: "JOIN"
|
||||
params:
|
||||
- "#chan"
|
||||
|
||||
- input: ":src JOIN :#chan"
|
||||
atoms:
|
||||
source: "src"
|
||||
verb: "JOIN"
|
||||
params:
|
||||
- "#chan"
|
||||
|
||||
# with and without last param
|
||||
- input: ":src AWAY"
|
||||
atoms:
|
||||
source: "src"
|
||||
verb: "AWAY"
|
||||
|
||||
- input: ":src AWAY "
|
||||
atoms:
|
||||
source: "src"
|
||||
verb: "AWAY"
|
||||
|
||||
# tab is not considered <SPACE>
|
||||
- input: ":cool\tguy foo bar baz"
|
||||
atoms:
|
||||
source: "cool\tguy"
|
||||
verb: "foo"
|
||||
params:
|
||||
- "bar"
|
||||
- "baz"
|
||||
|
||||
# with weird control codes in the source
|
||||
- input: ":coolguy!ag@net\x035w\x03ork.admin PRIVMSG foo :bar baz"
|
||||
atoms:
|
||||
source: "coolguy!ag@net\x035w\x03ork.admin"
|
||||
verb: "PRIVMSG"
|
||||
params:
|
||||
- "foo"
|
||||
- "bar baz"
|
||||
|
||||
- input: ":coolguy!~ag@n\x02et\x0305w\x0fork.admin PRIVMSG foo :bar baz"
|
||||
atoms:
|
||||
source: "coolguy!~ag@n\x02et\x0305w\x0fork.admin"
|
||||
verb: "PRIVMSG"
|
||||
params:
|
||||
- "foo"
|
||||
- "bar baz"
|
||||
|
||||
- input: "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4= :irc.example.com COMMAND param1 param2 :param3 param3"
|
||||
atoms:
|
||||
tags:
|
||||
tag1: "value1"
|
||||
tag2:
|
||||
vendor1/tag3: "value2"
|
||||
vendor2/tag4: ""
|
||||
source: "irc.example.com"
|
||||
verb: "COMMAND"
|
||||
params:
|
||||
- "param1"
|
||||
- "param2"
|
||||
- "param3 param3"
|
||||
|
||||
- input: ":irc.example.com COMMAND param1 param2 :param3 param3"
|
||||
atoms:
|
||||
source: "irc.example.com"
|
||||
verb: "COMMAND"
|
||||
params:
|
||||
- "param1"
|
||||
- "param2"
|
||||
- "param3 param3"
|
||||
|
||||
- input: "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4 COMMAND param1 param2 :param3 param3"
|
||||
atoms:
|
||||
tags:
|
||||
tag1: "value1"
|
||||
tag2:
|
||||
vendor1/tag3: "value2"
|
||||
vendor2/tag4:
|
||||
verb: "COMMAND"
|
||||
params:
|
||||
- "param1"
|
||||
- "param2"
|
||||
- "param3 param3"
|
||||
|
||||
- input: "COMMAND"
|
||||
atoms:
|
||||
verb: "COMMAND"
|
||||
|
||||
# yaml encoding + slashes is fun
|
||||
- input: "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND"
|
||||
atoms:
|
||||
tags:
|
||||
foo: "\\\\;\\s \r\n"
|
||||
verb: "COMMAND"
|
||||
|
||||
# broken messages from unreal
|
||||
- input: ":gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal characters"
|
||||
atoms:
|
||||
source: "gravel.mozilla.org"
|
||||
verb: "432"
|
||||
params:
|
||||
- "#momo"
|
||||
- "Erroneous Nickname: Illegal characters"
|
||||
|
||||
- input: ":gravel.mozilla.org MODE #tckk +n "
|
||||
atoms:
|
||||
source: "gravel.mozilla.org"
|
||||
verb: "MODE"
|
||||
params:
|
||||
- "#tckk"
|
||||
- "+n"
|
||||
|
||||
- input: ":services.esper.net MODE #foo-bar +o foobar "
|
||||
atoms:
|
||||
source: "services.esper.net"
|
||||
verb: "MODE"
|
||||
params:
|
||||
- "#foo-bar"
|
||||
- "+o"
|
||||
- "foobar"
|
||||
|
||||
# tag values should be parsed char-at-a-time to prevent wayward replacements.
|
||||
- input: "@tag1=value\\\\ntest COMMAND"
|
||||
atoms:
|
||||
tags:
|
||||
tag1: "value\\ntest"
|
||||
verb: "COMMAND"
|
||||
|
||||
# If a tag value has a slash followed by a character which doesn't need
|
||||
# to be escaped, the slash should be dropped.
|
||||
- input: "@tag1=value\\1 COMMAND"
|
||||
atoms:
|
||||
tags:
|
||||
tag1: "value1"
|
||||
verb: "COMMAND"
|
||||
|
||||
# A slash at the end of a tag value should be dropped
|
||||
- input: "@tag1=value1\\ COMMAND"
|
||||
atoms:
|
||||
tags:
|
||||
tag1: "value1"
|
||||
verb: "COMMAND"
|
||||
|
||||
# Duplicate tags: Parsers SHOULD disregard all but the final occurence
|
||||
- input: "@tag1=1;tag2=3;tag3=4;tag1=5 COMMAND"
|
||||
atoms:
|
||||
tags:
|
||||
tag1: "5"
|
||||
tag2: "3"
|
||||
tag3: "4"
|
||||
verb: "COMMAND"
|
||||
|
||||
# vendored tags can have the same name as a non-vendored tag
|
||||
- input: "@tag1=1;tag2=3;tag3=4;tag1=5;vendor/tag2=8 COMMAND"
|
||||
atoms:
|
||||
tags:
|
||||
tag1: "5"
|
||||
tag2: "3"
|
||||
tag3: "4"
|
||||
vendor/tag2: "8"
|
||||
verb: "COMMAND"
|
||||
|
||||
# Some parsers handle /MODE in a special way, make sure they do it right
|
||||
- input: ":SomeOp MODE #channel :+i"
|
||||
atoms:
|
||||
source: "SomeOp"
|
||||
verb: "MODE"
|
||||
params:
|
||||
- "#channel"
|
||||
- "+i"
|
||||
|
||||
- input: ":SomeOp MODE #channel +oo SomeUser :AnotherUser"
|
||||
atoms:
|
||||
source: "SomeOp"
|
||||
verb: "MODE"
|
||||
params:
|
||||
- "#channel"
|
||||
- "+oo"
|
||||
- "SomeUser"
|
||||
- "AnotherUser"
|
|
@ -1,143 +1,138 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
namespace ChatSharp.Tests;
|
||||
|
||||
namespace ChatSharp.Tests
|
||||
[TestClass]
|
||||
public class IrcMessageTests
|
||||
{
|
||||
[TestClass]
|
||||
public class IrcMessageTests
|
||||
[TestMethod]
|
||||
public void NewValidMessage()
|
||||
{
|
||||
[TestMethod]
|
||||
public void NewValidMessage()
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = new IrcMessage(":user!~ident@host PRIVMSG target :Lorem ipsum dolor sit amet");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.Fail("Expected no exception, got: {0}", e.Message);
|
||||
}
|
||||
_ = new IrcMessage(":user!~ident@host PRIVMSG target :Lorem ipsum dolor sit amet");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewValidMessage_Command()
|
||||
catch (Exception e)
|
||||
{
|
||||
IrcMessage fromMessage = new(":user!~ident@host PRIVMSG target :Lorem ipsum dolor sit amet");
|
||||
Assert.AreEqual(fromMessage.Command, "PRIVMSG");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewValidMessage_Prefix()
|
||||
{
|
||||
IrcMessage fromMessage = new(":user!~ident@host PRIVMSG target :Lorem ipsum dolor sit amet");
|
||||
Assert.AreEqual(fromMessage.Prefix, "user!~ident@host");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewValidMessage_Params()
|
||||
{
|
||||
IrcMessage fromMessage = new(@":user!~ident@host PRIVMSG target :Lorem ipsum dolor sit amet");
|
||||
var compareParams = new[] { "target", "Lorem ipsum dolor sit amet" };
|
||||
CollectionAssert.AreEqual(fromMessage.Parameters, compareParams);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewValidMessage_Tags()
|
||||
{
|
||||
IrcMessage fromMessage =
|
||||
new("@a=123;b=456;c=789 :user!~ident@host PRIVMSG target :Lorem ipsum dolor sit amet");
|
||||
var compareTags = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("a", "123"),
|
||||
new KeyValuePair<string, string>("b", "456"),
|
||||
new KeyValuePair<string, string>("c", "789")
|
||||
};
|
||||
CollectionAssert.AreEqual(fromMessage.Tags, compareTags);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewValidMessage_Tags02()
|
||||
{
|
||||
IrcMessage fromMessage = new("@aaa=bbb;ccc;example.com/ddd=eee :nick!ident@host.com PRIVMSG me :Hello");
|
||||
var compareTags = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("aaa", "bbb"),
|
||||
new KeyValuePair<string, string>("ccc", null),
|
||||
new KeyValuePair<string, string>("example.com/ddd", "eee")
|
||||
};
|
||||
CollectionAssert.AreEqual(fromMessage.Tags, compareTags);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewValidMessage_TagsWithSemicolon()
|
||||
{
|
||||
IrcMessage fromMessage =
|
||||
new(@"@a=123\:456;b=456\:789;c=789\:123 :user!~ident@host PRIVMSG target :Lorem ipsum dolor sit amet");
|
||||
var compareTags = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("a", "123;456"),
|
||||
new KeyValuePair<string, string>("b", "456;789"),
|
||||
new KeyValuePair<string, string>("c", "789;123")
|
||||
};
|
||||
CollectionAssert.AreEqual(fromMessage.Tags, compareTags);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewValidMessage_TagsNoValue()
|
||||
{
|
||||
IrcMessage fromMessage = new("@a=;b :nick!ident@host.com PRIVMSG me :Hello");
|
||||
var compareTags = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("a", ""),
|
||||
new KeyValuePair<string, string>("b", null)
|
||||
};
|
||||
CollectionAssert.AreEqual(fromMessage.Tags, compareTags);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Timestamp_CompareISOString()
|
||||
{
|
||||
IrcMessage[] messages =
|
||||
{
|
||||
new("@time=2011-10-19T16:40:51.620Z :Angel!angel@example.org PRIVMSG Wiz :Hello"),
|
||||
new("@time=2012-06-30T23:59:59.419Z :John!~john@1.2.3.4 JOIN #chan")
|
||||
};
|
||||
|
||||
string[] timestamps =
|
||||
{
|
||||
"2011-10-19T16:40:51.620Z",
|
||||
"2012-06-30T23:59:59.419Z"
|
||||
};
|
||||
|
||||
Assert.AreEqual(messages[0].Timestamp.ToISOString(), timestamps[0]);
|
||||
Assert.AreEqual(messages[1].Timestamp.ToISOString(), timestamps[1]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Timestamp_FromTimestamp()
|
||||
{
|
||||
IrcMessage[] messages =
|
||||
[
|
||||
new("@t=1504923966 :Angel!angel@example.org PRIVMSG Wiz :Hello"),
|
||||
new("@t=1504923972 :John!~john@1.2.3.4 JOIN #chan")
|
||||
];
|
||||
|
||||
string[] timestamps =
|
||||
[
|
||||
"2017-09-09T02:26:06.000Z",
|
||||
"2017-09-09T02:26:12.000Z"
|
||||
];
|
||||
|
||||
Assert.AreEqual(messages[0].Timestamp.ToISOString(), timestamps[0]);
|
||||
Assert.AreEqual(messages[1].Timestamp.ToISOString(), timestamps[1]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Timestamp_FailOnLeap()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentException>(() =>
|
||||
new IrcMessage("@time=2012-06-30T23:59:60.419Z :John!~john@1.2.3.4 JOIN #chan"));
|
||||
Assert.Fail("Expected no exception, got: {0}", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewValidMessage_Command()
|
||||
{
|
||||
IrcMessage fromMessage = new(":user!~ident@host PRIVMSG target :Lorem ipsum dolor sit amet");
|
||||
Assert.AreEqual("PRIVMSG", fromMessage.Command);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewValidMessage_Prefix()
|
||||
{
|
||||
IrcMessage fromMessage = new(":user!~ident@host PRIVMSG target :Lorem ipsum dolor sit amet");
|
||||
Assert.AreEqual("user!~ident@host", fromMessage.Source);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewValidMessage_Params()
|
||||
{
|
||||
IrcMessage fromMessage = new(":user!~ident@host PRIVMSG target :Lorem ipsum dolor sit amet");
|
||||
var compareParams = new[] { "target", "Lorem ipsum dolor sit amet" };
|
||||
CollectionAssert.AreEqual(compareParams, fromMessage.Parameters);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewValidMessage_Tags()
|
||||
{
|
||||
IrcMessage fromMessage =
|
||||
new("@a=123;b=456;c=789 :user!~ident@host PRIVMSG target :Lorem ipsum dolor sit amet");
|
||||
var compareTags = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("a", "123"),
|
||||
new KeyValuePair<string, string>("b", "456"),
|
||||
new KeyValuePair<string, string>("c", "789")
|
||||
};
|
||||
CollectionAssert.AreEqual(compareTags, fromMessage.Tags);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewValidMessage_Tags02()
|
||||
{
|
||||
IrcMessage fromMessage = new("@aaa=bbb;ccc;example.com/ddd=eee :nick!ident@host.com PRIVMSG me :Hello");
|
||||
var compareTags = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("aaa", "bbb"),
|
||||
new KeyValuePair<string, string>("ccc", null),
|
||||
new KeyValuePair<string, string>("example.com/ddd", "eee")
|
||||
};
|
||||
CollectionAssert.AreEqual(fromMessage.Tags, compareTags);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewValidMessage_TagsWithSemicolon()
|
||||
{
|
||||
IrcMessage fromMessage =
|
||||
new(@"@a=123\:456;b=456\:789;c=789\:123 :user!~ident@host PRIVMSG target :Lorem ipsum dolor sit amet");
|
||||
var compareTags = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("a", "123;456"),
|
||||
new KeyValuePair<string, string>("b", "456;789"),
|
||||
new KeyValuePair<string, string>("c", "789;123")
|
||||
};
|
||||
CollectionAssert.AreEqual(fromMessage.Tags, compareTags);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewValidMessage_TagsNoValue()
|
||||
{
|
||||
IrcMessage fromMessage = new("@a=;b :nick!ident@host.com PRIVMSG me :Hello");
|
||||
var compareTags = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("a", ""),
|
||||
new KeyValuePair<string, string>("b", null)
|
||||
};
|
||||
CollectionAssert.AreEqual(fromMessage.Tags, compareTags);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Timestamp_CompareISOString()
|
||||
{
|
||||
IrcMessage[] messages =
|
||||
[
|
||||
new("@time=2011-10-19T16:40:51.620Z :Angel!angel@example.org PRIVMSG Wiz :Hello"),
|
||||
new("@time=2012-06-30T23:59:59.419Z :John!~john@1.2.3.4 JOIN #chan")
|
||||
];
|
||||
|
||||
string[] timestamps =
|
||||
[
|
||||
"2011-10-19T16:40:51.620Z",
|
||||
"2012-06-30T23:59:59.419Z"
|
||||
];
|
||||
|
||||
Assert.AreEqual(messages[0].Timestamp.ToISOString(), timestamps[0]);
|
||||
Assert.AreEqual(messages[1].Timestamp.ToISOString(), timestamps[1]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Timestamp_FromTimestamp()
|
||||
{
|
||||
IrcMessage[] messages =
|
||||
[
|
||||
new("@t=1504923966 :Angel!angel@example.org PRIVMSG Wiz :Hello"),
|
||||
new("@t=1504923972 :John!~john@1.2.3.4 JOIN #chan")
|
||||
];
|
||||
|
||||
string[] timestamps =
|
||||
[
|
||||
"2017-09-09T02:26:06.000Z",
|
||||
"2017-09-09T02:26:12.000Z"
|
||||
];
|
||||
|
||||
Assert.AreEqual(messages[0].Timestamp.ToISOString(), timestamps[0]);
|
||||
Assert.AreEqual(messages[1].Timestamp.ToISOString(), timestamps[1]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Timestamp_FailOnLeap()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentException>(() =>
|
||||
new IrcMessage("@time=2012-06-30T23:59:60.419Z :John!~john@1.2.3.4 JOIN #chan"));
|
||||
}
|
||||
}
|
|
@ -1,74 +1,71 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
namespace ChatSharp.Tests;
|
||||
|
||||
namespace ChatSharp.Tests
|
||||
[TestClass]
|
||||
public class IrcUserTests
|
||||
{
|
||||
[TestClass]
|
||||
public class IrcUserTests
|
||||
[TestMethod]
|
||||
public void GetUserModes_NotNull_FiveModes()
|
||||
{
|
||||
[TestMethod]
|
||||
public void GetUserModes_NotNull_FiveModes()
|
||||
{
|
||||
IrcUser user = new("~&@%+aji", "user");
|
||||
IrcClient client = new("irc.address", user);
|
||||
IrcUser user = new("~&@%+aji", "user");
|
||||
IrcClient client = new("irc.address", user);
|
||||
|
||||
var userModes = client.ServerInfo.GetModesForNick(user.Nick);
|
||||
var userModes = client.ServerInfo.GetModesForNick(user.Nick);
|
||||
|
||||
Assert.IsTrue(userModes.Count == 5);
|
||||
}
|
||||
Assert.IsTrue(userModes.Count == 5);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetUserModes_NotNull_FourModes()
|
||||
{
|
||||
IrcUser user = new("&@%+aji", "user");
|
||||
IrcClient client = new("irc.address", user);
|
||||
[TestMethod]
|
||||
public void GetUserModes_NotNull_FourModes()
|
||||
{
|
||||
IrcUser user = new("&@%+aji", "user");
|
||||
IrcClient client = new("irc.address", user);
|
||||
|
||||
var userModes = client.ServerInfo.GetModesForNick(user.Nick);
|
||||
var userModes = client.ServerInfo.GetModesForNick(user.Nick);
|
||||
|
||||
Assert.IsTrue(userModes.Count == 4);
|
||||
}
|
||||
Assert.IsTrue(userModes.Count == 4);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetUserModes_NotNull_ThreeModes()
|
||||
{
|
||||
IrcUser user = new("@%+aji", "user");
|
||||
IrcClient client = new("irc.address", user);
|
||||
[TestMethod]
|
||||
public void GetUserModes_NotNull_ThreeModes()
|
||||
{
|
||||
IrcUser user = new("@%+aji", "user");
|
||||
IrcClient client = new("irc.address", user);
|
||||
|
||||
var userModes = client.ServerInfo.GetModesForNick(user.Nick);
|
||||
var userModes = client.ServerInfo.GetModesForNick(user.Nick);
|
||||
|
||||
Assert.IsTrue(userModes.Count == 3);
|
||||
}
|
||||
Assert.IsTrue(userModes.Count == 3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetUserModes_NotNull_TwoModes()
|
||||
{
|
||||
IrcUser user = new("%+aji", "user");
|
||||
IrcClient client = new("irc.address", user);
|
||||
[TestMethod]
|
||||
public void GetUserModes_NotNull_TwoModes()
|
||||
{
|
||||
IrcUser user = new("%+aji", "user");
|
||||
IrcClient client = new("irc.address", user);
|
||||
|
||||
var userModes = client.ServerInfo.GetModesForNick(user.Nick);
|
||||
var userModes = client.ServerInfo.GetModesForNick(user.Nick);
|
||||
|
||||
Assert.IsTrue(userModes.Count == 2);
|
||||
}
|
||||
Assert.IsTrue(userModes.Count == 2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetUserModes_NotNull_OneMode()
|
||||
{
|
||||
IrcUser user = new("+aji", "user");
|
||||
IrcClient client = new("irc.address", user);
|
||||
[TestMethod]
|
||||
public void GetUserModes_NotNull_OneMode()
|
||||
{
|
||||
IrcUser user = new("+aji", "user");
|
||||
IrcClient client = new("irc.address", user);
|
||||
|
||||
var userModes = client.ServerInfo.GetModesForNick(user.Nick);
|
||||
var userModes = client.ServerInfo.GetModesForNick(user.Nick);
|
||||
|
||||
Assert.IsTrue(userModes.Count == 1);
|
||||
}
|
||||
Assert.IsTrue(userModes.Count == 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetUserModes_IsNull()
|
||||
{
|
||||
IrcUser user = new("aji", "user");
|
||||
IrcClient client = new("irc.address", user);
|
||||
[TestMethod]
|
||||
public void GetUserModes_IsNull()
|
||||
{
|
||||
IrcUser user = new("aji", "user");
|
||||
IrcClient client = new("irc.address", user);
|
||||
|
||||
var userModes = client.ServerInfo.GetModesForNick(user.Nick);
|
||||
var userModes = client.ServerInfo.GetModesForNick(user.Nick);
|
||||
|
||||
Assert.IsTrue(userModes.Count == 0);
|
||||
}
|
||||
Assert.IsTrue(userModes.Count == 0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using System.Globalization;
|
||||
using System.IO;
|
||||
using ChatSharp.Tests.Data;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace ChatSharp.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class ParsingTests
|
||||
{
|
||||
private static T LoadYaml<T>(string path)
|
||||
{
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(CamelCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
|
||||
return deserializer.Deserialize<T>(File.ReadAllText(path));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Split()
|
||||
{
|
||||
foreach (var test in LoadYaml<SplitModel>("Data/msg-split.yaml").Tests)
|
||||
{
|
||||
var message = new IrcMessage(test.Input);
|
||||
var atoms = test.Atoms;
|
||||
|
||||
Assert.AreEqual(atoms.Verb.ToUpper(CultureInfo.InvariantCulture), message.Command,
|
||||
$"command failed on: '{test.Input}'");
|
||||
Assert.AreEqual(atoms.Source, message.Source,
|
||||
$"source failed on: '{test.Input}' ");
|
||||
CollectionAssert.AreEqual(atoms.Tags, message.Tags,
|
||||
$"tags failed on: '{test.Input}' ");
|
||||
CollectionAssert.AreEqual(atoms.Params ?? [], message.Parameters,
|
||||
$"params failed on: '{test.Input}' ");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Join()
|
||||
{
|
||||
foreach (var test in LoadYaml<JoinModel>("Data/msg-join.yaml").Tests)
|
||||
{
|
||||
var atoms = test.Atoms;
|
||||
var line = new IrcMessage
|
||||
{
|
||||
Command = atoms.Verb,
|
||||
Parameters = atoms.Params,
|
||||
Source = atoms.Source,
|
||||
Tags = atoms.Tags,
|
||||
}.Format();
|
||||
|
||||
Assert.IsTrue(test.Matches.Contains(line), test.Description);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,10 @@
|
|||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="ChatSharp"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ChatSharp\ChatSharp.csproj"/>
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using ChatSharp;
|
||||
|
||||
var client = new IrcClient("irc.libera.chat", new("chatsharp", "chatsharp"));
|
||||
var client = new IrcClient("irc.libera.chat", new("chatsharp"));
|
||||
|
||||
client.ConnectionComplete += (_, _) => client.JoinChannel("##chatsharp");
|
||||
client.ChannelMessageReceived += (s, e) =>
|
||||
|
@ -12,7 +10,7 @@ client.ChannelMessageReceived += (s, e) =>
|
|||
}
|
||||
else if (e.PrivateMessage.Message.StartsWith(".ban "))
|
||||
{
|
||||
if (!channel.UsersByMode['@'].Contains(client.User))
|
||||
if (!channel.UsersByMode?['@'].Contains(client.User) ?? false)
|
||||
{
|
||||
channel.SendMessage("I'm not an op here!");
|
||||
return;
|
||||
|
@ -21,6 +19,7 @@ client.ChannelMessageReceived += (s, e) =>
|
|||
var target = e.PrivateMessage.Message[5..];
|
||||
client.WhoIs(target, whois => channel.ChangeMode($"+b *!*@{whois.User.Hostname}"));
|
||||
}
|
||||
Console.WriteLine($"> {e.IrcMessage.Format()}");
|
||||
};
|
||||
|
||||
client.ConnectAsync();
|
||||
|
|
Loading…
Reference in New Issue