Compare commits

...

7 Commits

39 changed files with 1206 additions and 481 deletions

View File

@ -1,7 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=3892ce1c_002Daad5_002D4870_002D98ea_002D199acb6b67c3/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;Solution /&gt;&#xD;
&lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=64875d9c_002Daf58_002D4d04_002D996b_002Ddf4baee8e592/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=64875d9c_002Daf58_002D4d04_002D996b_002Ddf4baee8e592/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;Solution /&gt;&#xD;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>

View File

@ -12,7 +12,7 @@ namespace ChatSharp
{
internal CapabilityPool()
{
Capabilities = new();
Capabilities = new List<IrcCapability>();
}
private List<IrcCapability> Capabilities { get; }
@ -67,7 +67,7 @@ namespace ChatSharp
if (Capabilities.Any(cap => cap.Name == name))
return;
Capabilities.Add(new(name));
Capabilities.Add(new IrcCapability(name));
}
internal void AddRange(IEnumerable<string> range)

View File

@ -12,7 +12,7 @@ namespace ChatSharp
{
internal ChannelCollection()
{
Channels = new();
Channels = new List<IrcChannel>();
}
internal ChannelCollection(IrcClient client) : this()

View File

@ -1,17 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://tildegit.org/ben/chatsharp</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://tildegit.org/ben/chatsharp</PackageProjectUrl>
<Description>c# irc library</Description>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
<None Include="..\README.md" Pack="true" PackagePath="\"/>
</ItemGroup>
</Project>

View File

@ -37,11 +37,11 @@ namespace ChatSharp.Events
{
const string nickCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
_random ??= new();
_random ??= new Random();
var nick = new char[8];
for (var i = 0; i < nick.Length; i++)
nick[i] = nickCharacters[_random.Next(nickCharacters.Length)];
return new(nick);
return new string(nick);
}
}
}

View File

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

View File

@ -12,7 +12,7 @@ namespace ChatSharp.Events
internal PrivateMessageEventArgs(IrcClient client, IrcMessage ircMessage, ServerInfo serverInfo)
{
IrcMessage = ircMessage;
PrivateMessage = new(client, IrcMessage, serverInfo);
PrivateMessage = new PrivateMessage(client, IrcMessage, serverInfo);
}
/// <summary>

View File

@ -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)

View File

@ -2,6 +2,7 @@ using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ChatSharp.Events;
namespace ChatSharp.Handlers
{
@ -9,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)
@ -25,7 +26,7 @@ namespace ChatSharp.Handlers
user.Account = whoQuery[0].User.Account;
});
client.OnUserJoinedChannel(new(channel, user));
client.OnUserJoinedChannel(new ChannelUserEventArgs(channel, user));
}
}
@ -34,7 +35,7 @@ namespace ChatSharp.Handlers
var channel = client.Channels.GetOrAdd(message.Parameters[1]);
var old = channel._Topic;
channel._Topic = message.Parameters[2];
client.OnChannelTopicReceived(new(channel, old, channel._Topic));
client.OnChannelTopicReceived(new ChannelTopicEventArgs(channel, old, channel._Topic));
}
public static void HandleGetEmptyTopic(IrcClient client, IrcMessage message)
@ -42,7 +43,7 @@ namespace ChatSharp.Handlers
var channel = client.Channels.GetOrAdd(message.Parameters[1]);
var old = channel._Topic;
channel._Topic = message.Parameters[2];
client.OnChannelTopicReceived(new(channel, old, channel._Topic));
client.OnChannelTopicReceived(new ChannelTopicEventArgs(channel, old, channel._Topic));
}
public static void HandlePart(IrcClient client, IrcMessage message)
@ -50,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))
@ -58,7 +59,7 @@ namespace ChatSharp.Handlers
if (user.ChannelModes.ContainsKey(channel))
user.ChannelModes.Remove(channel);
client.OnUserPartedChannel(new(channel, user));
client.OnUserPartedChannel(new ChannelUserEventArgs(channel, user));
}
public static void HandleUserListPart(IrcClient client, IrcMessage message)
@ -91,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
@ -116,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;
}
}
}
@ -127,7 +122,7 @@ namespace ChatSharp.Handlers
public static void HandleUserListEnd(IrcClient client, IrcMessage message)
{
var channel = client.Channels[message.Parameters[1]];
client.OnChannelListReceived(new(channel));
client.OnChannelListReceived(new ChannelEventArgs(channel));
if (client.Settings.ModeOnJoin)
try
{
@ -164,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(channel, new(message.Prefix),
client.OnUserKicked(new KickEventArgs(channel, new IrcUser(message.Source),
kicked, message.Parameters[2]));
}
}

View File

@ -1,3 +1,5 @@
using ChatSharp.Events;
namespace ChatSharp.Handlers
{
/// <summary>
@ -10,7 +12,7 @@ namespace ChatSharp.Handlers
/// </summary>
public static void HandleError(IrcClient client, IrcMessage message)
{
client.OnErrorReply(new(message));
client.OnErrorReply(new ErrorReplyEventArgs(message));
}
}
}

View File

@ -4,11 +4,11 @@ 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;
list.Add(new(parameters[2], client.Users.GetOrAdd(parameters[3]),
list.Add(new Mask(parameters[2], client.Users.GetOrAdd(parameters[3]),
IrcClient.DateTimeFromIrcTime(int.Parse(parameters[4]))));
}
@ -20,11 +20,11 @@ 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;
list.Add(new(parameters[2], client.Users.GetOrAdd(parameters[3]),
list.Add(new Mask(parameters[2], client.Users.GetOrAdd(parameters[3]),
IrcClient.DateTimeFromIrcTime(int.Parse(parameters[4]))));
}
@ -36,11 +36,11 @@ 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;
list.Add(new(parameters[2], client.Users.GetOrAdd(parameters[3]),
list.Add(new Mask(parameters[2], client.Users.GetOrAdd(parameters[3]),
IrcClient.DateTimeFromIrcTime(int.Parse(parameters[4]))));
}
@ -52,11 +52,11 @@ 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;
list.Add(new(parameters[2], client.Users.GetOrAdd(parameters[3]),
list.Add(new Mask(parameters[2], client.Users.GetOrAdd(parameters[3]),
IrcClient.DateTimeFromIrcTime(int.Parse(parameters[4]))));
}

View File

@ -1,4 +1,5 @@
using System;
using ChatSharp.Events;
namespace ChatSharp.Handlers
{
@ -13,25 +14,25 @@ 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;
client.OnMOTDPartReceived(new(part));
client.OnMOTDPartReceived(new ServerMOTDEventArgs(part));
}
public static void HandleEndOfMOTD(IrcClient client, IrcMessage message)
{
client.OnMOTDReceived(new(MOTD));
client.OnConnectionComplete(new());
client.OnMOTDReceived(new ServerMOTDEventArgs(MOTD));
client.OnConnectionComplete(EventArgs.Empty);
// Verify our identity
VerifyOurIdentity(client);
}
public static void HandleMOTDNotFound(IrcClient client, IrcMessage message)
{
client.OnMOTDReceived(new(MOTD));
client.OnConnectionComplete(new());
client.OnMOTDReceived(new ServerMOTDEventArgs(MOTD));
client.OnConnectionComplete(EventArgs.Empty);
VerifyOurIdentity(client);
}

View File

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using ChatSharp.Events;
@ -91,11 +92,11 @@ 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];
client.OnNickChanged(new()
client.OnNickChanged(new NickChangedEventArgs
{
User = user,
OldNick = oldNick,
@ -105,11 +106,11 @@ 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);
client.OnUserQuit(new(user));
client.OnUserQuit(new UserEventArgs(user));
}
}
@ -121,7 +122,7 @@ namespace ChatSharp.Handlers
public static void HandleNotice(IrcClient client, IrcMessage message)
{
client.OnNoticeReceived(new(message));
client.OnNoticeReceived(new IrcNoticeEventArgs(message));
}
public static void HandlePrivmsg(IrcClient client, IrcMessage message)
@ -181,27 +182,26 @@ 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(channel.Name, new(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(channel.Name, new(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))
{
if (!channel.UsersByMode.ContainsKey(c))
channel.UsersByMode.Add(c,
new(channel.Users.Where(u =>
new UserPoolView(channel.Users.Where(u =>
{
if (!u.ChannelModes.ContainsKey(channel))
u.ChannelModes.Add(channel, new());
u.ChannelModes.Add(channel, new List<char?>());
return u.ChannelModes[channel].Contains(c);
})));
var user = new IrcUser(message.Parameters[i]);
@ -217,7 +217,7 @@ namespace ChatSharp.Handlers
user.ChannelModes[channel] = null;
}
client.OnModeChanged(new(channel.Name, new(message.Prefix),
client.OnModeChanged(new ModeChangeEventArgs(channel.Name, new IrcUser(message.Source),
(add ? "+" : "-") + c + " " + message.Parameters[i++]));
}
@ -233,7 +233,7 @@ namespace ChatSharp.Handlers
channel.Mode = channel.Mode.Replace(c.ToString(), string.Empty);
}
client.OnModeChanged(new(channel.Name, new(message.Prefix),
client.OnModeChanged(new ModeChangeEventArgs(channel.Name, new IrcUser(message.Source),
(add ? "+" : "-") + c));
}
}

View File

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

View File

@ -1,11 +1,12 @@
using ChatSharp.Events;
namespace ChatSharp.Handlers
{
internal static class ServerHandlers
{
public static void HandleISupport(IrcClient client, IrcMessage message)
{
if (client.ServerInfo == null)
client.ServerInfo = new();
client.ServerInfo ??= new ServerInfo();
foreach (var item in message.Parameters)
{
string key, value;
@ -57,7 +58,7 @@ namespace ChatSharp.Handlers
var limitedModes = limits[i].Remove(limits[i].IndexOf(':'));
var limit = int.Parse(limits[i][(limits[i].IndexOf(':') + 1)..]);
foreach (var mode in limitedModes)
client.ServerInfo.ModeListLimits[i] = new(mode, limit);
client.ServerInfo.ModeListLimits[i] = new ServerInfo.ModeListLimit(mode, limit);
}
break;
@ -84,24 +85,22 @@ 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(client.ServerInfo));
client.OnServerInfoReceived(new SupportsEventArgs(client.ServerInfo));
}
public static void HandleMyInfo(IrcClient client, IrcMessage message)
{
// 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();
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();

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ChatSharp.Events;
namespace ChatSharp.Handlers
{
@ -8,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];
@ -68,7 +69,7 @@ namespace ChatSharp.Handlers
if (!client.Users.Contains(whois.User.Nick))
client.Users.Add(whois.User);
request.Callback?.Invoke(request);
client.OnWhoIsReceived(new(whois));
client.OnWhoIsReceived(new WhoIsReceivedEventArgs(whois));
}
public static void HandleWho(IrcClient client, IrcMessage message)
@ -81,7 +82,7 @@ namespace ChatSharp.Handlers
var who = new ExtendedWho
{
Channel = message.Parameters[1],
User = new()
User = new IrcUser
{
User = message.Parameters[2],
Nick = message.Parameters[5]
@ -211,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);
}
@ -226,8 +227,7 @@ namespace ChatSharp.Handlers
{
if (client.ServerInfo.ExtendedWho)
{
var query = client.RequestManager.PendingOperations
.Where(kvp => kvp.Key.StartsWith("WHO " + message.Parameters[1])).FirstOrDefault();
var query = client.RequestManager.PendingOperations.FirstOrDefault(kvp => kvp.Key.StartsWith("WHO " + message.Parameters[1]));
var request = client.RequestManager.DequeueOperation(query.Key);
var whoxList = (List<ExtendedWho>)request.State;
@ -236,12 +236,11 @@ namespace ChatSharp.Handlers
client.Users.Add(whox.User);
request.Callback?.Invoke(request);
client.OnWhoxReceived(new(whoxList.ToArray()));
client.OnWhoxReceived(new WhoxReceivedEventArgs(whoxList.ToArray()));
}
else
{
var query = client.RequestManager.PendingOperations
.Where(kvp => kvp.Key == "WHO " + message.Parameters[1]).FirstOrDefault();
var query = client.RequestManager.PendingOperations.FirstOrDefault(kvp => kvp.Key == "WHO " + message.Parameters[1]);
var request = client.RequestManager.DequeueOperation(query.Key);
var whoList = (List<ExtendedWho>)request.State;
@ -250,19 +249,19 @@ namespace ChatSharp.Handlers
client.Users.Add(who.User);
request.Callback?.Invoke(request);
client.OnWhoxReceived(new(whoList.ToArray()));
client.OnWhoxReceived(new WhoxReceivedEventArgs(whoList.ToArray()));
}
}
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)

View File

@ -14,7 +14,7 @@ namespace ChatSharp
{
Client = client;
Name = name;
Users = new(client.Users.Where(u => u.Channels.Contains(this)));
Users = new UserPoolView(client.Users.Where(u => u.Channels.Contains(this)));
}
private IrcClient Client { get; }

View File

@ -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);
@ -163,7 +163,7 @@ namespace ChatSharp
public void WhoIs(string nick, Action<WhoIs> callback)
{
var whois = new WhoIs();
RequestManager.QueueOperation("WHOIS " + nick, new(whois, ro => { callback?.Invoke((WhoIs)ro.State); }));
RequestManager.QueueOperation("WHOIS " + nick, new RequestOperation(whois, ro => { callback?.Invoke((WhoIs)ro.State); }));
SendRawMessage("WHOIS {0}", nick);
}
@ -187,20 +187,20 @@ 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(whox, ro => { callback?.Invoke((List<ExtendedWho>)ro.State); }));
new RequestOperation(whox, ro => { callback?.Invoke((List<ExtendedWho>)ro.State); }));
SendRawMessage(whoQuery);
}
else
{
var whox = new List<ExtendedWho>();
var whoQuery = string.Format("WHO {0}", target);
var whoQuery = $"WHO {target}";
RequestManager.QueueOperation(whoQuery,
new(whox, ro => { callback?.Invoke((List<ExtendedWho>)ro.State); }));
new RequestOperation(whox, ro => { callback?.Invoke((List<ExtendedWho>)ro.State); }));
SendRawMessage(whoQuery);
}
}
@ -218,7 +218,7 @@ namespace ChatSharp
/// </summary>
public void GetMode(string channel, Action<IrcChannel> callback)
{
RequestManager.QueueOperation("MODE " + channel, new(channel, ro =>
RequestManager.QueueOperation("MODE " + channel, new RequestOperation(channel, ro =>
{
var c = Channels[(string)ro.State];
callback?.Invoke(c);
@ -240,7 +240,7 @@ namespace ChatSharp
/// </summary>
public void GetModeList(string channel, char mode, Action<MaskCollection> callback)
{
RequestManager.QueueOperation("GETMODE " + mode + " " + channel, new(new MaskCollection(), ro =>
RequestManager.QueueOperation("GETMODE " + mode + " " + channel, new RequestOperation(new MaskCollection(), ro =>
{
var c = (MaskCollection)ro.State;
callback?.Invoke(c);

View File

@ -35,18 +35,18 @@ namespace ChatSharp
User = user ?? throw new ArgumentNullException(nameof(user));
ServerAddress = serverAddress ?? throw new ArgumentNullException(nameof(serverAddress));
Encoding = Encoding.UTF8;
Settings = new();
Handlers = new();
Settings = new ClientSettings();
Handlers = new Dictionary<string, MessageHandler>();
MessageHandlers.RegisterDefaultHandlers(this);
RequestManager = new();
RequestManager = new RequestManager();
UseSSL = useSSL;
WriteQueue = new();
ServerInfo = new();
WriteQueue = new ConcurrentQueue<string>();
ServerInfo = new ServerInfo();
PrivmsgPrefix = "";
Channels = User.Channels = new(this);
Channels = User.Channels = new ChannelCollection(this);
// Add self to user pool
Users = new() { User };
Capabilities = new();
Users = new UserPool { User };
Capabilities = new CapabilityPool();
// List of supported capabilities
Capabilities.AddRange(new[]
@ -58,7 +58,7 @@ namespace ChatSharp
IsNegotiatingCapabilities = false;
IsAuthenticatingSasl = false;
RandomNumber = new();
RandomNumber = new Random();
}
private Dictionary<string, MessageHandler> Handlers { get; }
@ -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,19 +194,19 @@ 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(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ReadBuffer = new byte[ReadBufferLength];
ReadBufferIndex = 0;
PingTimer = new(30000);
PingTimer.Elapsed += (_, _) =>
PingTimer = new Timer(30000);
PingTimer.Elapsed += (x, y) =>
{
if (!string.IsNullOrEmpty(ServerNameFromPing))
SendRawMessage("PING :{0}", ServerNameFromPing);
};
var checkQueue = new Timer(1000);
checkQueue.Elapsed += (_, _) =>
checkQueue.Elapsed += (x, y) =>
{
if (!WriteQueue.IsEmpty)
{
@ -259,10 +256,9 @@ namespace ChatSharp
NetworkStream = new NetworkStream(Socket);
if (UseSSL)
{
if (IgnoreInvalidSSL)
NetworkStream = new SslStream(NetworkStream, false, (_, _, _, _) => true);
else
NetworkStream = new SslStream(NetworkStream);
NetworkStream = IgnoreInvalidSSL
? new SslStream(NetworkStream, false, (a, b, c, d) => true)
: new SslStream(NetworkStream);
((SslStream)NetworkStream).AuthenticateAsClient(ServerHostname);
}
@ -279,11 +275,11 @@ namespace ChatSharp
}
catch (SocketException e)
{
OnNetworkError(new(e.SocketErrorCode));
OnNetworkError(new SocketErrorEventArgs(e.SocketErrorCode));
}
catch (Exception e)
{
OnError(new(e));
OnError(new ErrorEventArgs(e));
}
}
@ -291,7 +287,7 @@ namespace ChatSharp
{
if (NetworkStream == null)
{
OnNetworkError(new(SocketError.NotConnected));
OnNetworkError(new SocketErrorEventArgs(SocketError.NotConnected));
return;
}
@ -303,7 +299,7 @@ namespace ChatSharp
catch (IOException e)
{
if (e.InnerException is SocketException socketException)
OnNetworkError(new(socketException.SocketErrorCode));
OnNetworkError(new SocketErrorEventArgs(socketException.SocketErrorCode));
else
throw;
return;
@ -332,11 +328,11 @@ namespace ChatSharp
private void HandleMessage(string rawMessage)
{
OnRawMessageReceived(new(rawMessage, false));
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);
}
}
@ -347,7 +343,7 @@ namespace ChatSharp
{
if (NetworkStream == null)
{
OnNetworkError(new(SocketError.NotConnected));
OnNetworkError(new SocketErrorEventArgs(SocketError.NotConnected));
return;
}
@ -370,14 +366,14 @@ namespace ChatSharp
/// </summary>
public void SendIrcMessage(IrcMessage message)
{
SendRawMessage(message.RawMessage);
SendRawMessage(message.Format());
}
private void MessageSent(IAsyncResult result)
{
if (NetworkStream == null)
{
OnNetworkError(new(SocketError.NotConnected));
OnNetworkError(new SocketErrorEventArgs(SocketError.NotConnected));
IsWriting = false;
return;
}
@ -389,7 +385,7 @@ namespace ChatSharp
catch (IOException e)
{
if (e.InnerException is SocketException socketException)
OnNetworkError(new(socketException.SocketErrorCode));
OnNetworkError(new SocketErrorEventArgs(socketException.SocketErrorCode));
else
throw;
return;
@ -399,7 +395,7 @@ namespace ChatSharp
IsWriting = false;
}
OnRawMessageSent(new((string)result.AsyncState, true));
OnRawMessageSent(new RawMessageEventArgs((string)result.AsyncState, true));
string nextMessage;
if (!WriteQueue.IsEmpty)

View File

@ -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(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(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(tag.Value, true);
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(tag.Value);
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.

View File

@ -8,31 +8,40 @@ namespace ChatSharp
/// </summary>
public class IrcUser : IEquatable<IrcUser>
{
private readonly string _source;
internal IrcUser()
{
Channels = new();
ChannelModes = new();
Channels = new ChannelCollection();
ChannelModes = new Dictionary<IrcChannel, List<char?>>();
Account = "*";
}
/// <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];
if (mask.Length <= 2)
Hostname = "";
else
Hostname = mask[2];
Nick = source;
}
if (Nick.Contains('!', StringComparison.Ordinal))
{
var userSplit = Nick.Split('!');
Nick = userSplit[0];
User = userSplit[1];
}
}
@ -47,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>
@ -113,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>
@ -146,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++)
@ -182,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>
@ -192,7 +183,7 @@ namespace ChatSharp
/// </summary>
public override int GetHashCode()
{
return Hostmask.GetHashCode();
return _source.GetHashCode(StringComparison.Ordinal);
}
/// <summary>
@ -200,7 +191,7 @@ namespace ChatSharp
/// </summary>
public override string ToString()
{
return Hostmask;
return _source;
}
}
}

View File

@ -11,7 +11,7 @@ namespace ChatSharp
{
internal MaskCollection()
{
Masks = new();
Masks = new List<Mask>();
}
private List<Mask> Masks { get; }

View File

@ -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

View File

@ -6,18 +6,12 @@ namespace ChatSharp
{
internal class RequestManager
{
public RequestManager()
{
PendingOperations = new();
}
internal Dictionary<string, RequestOperation> PendingOperations { get; }
internal Dictionary<string, RequestOperation> PendingOperations { get; } = new Dictionary<string, RequestOperation>();
public void QueueOperation(string key, RequestOperation operation)
{
if (PendingOperations.ContainsKey(key))
if (!PendingOperations.TryAdd(key, operation))
throw new InvalidOperationException("Operation is already pending.");
PendingOperations.Add(key, operation);
}
public RequestOperation PeekOperation(string key)

View File

@ -11,7 +11,7 @@ namespace ChatSharp
{
// Guess for some defaults
Prefixes = new[] { "ovhaq", "@+%&~" };
SupportedChannelModes = new();
SupportedChannelModes = new ChannelModes();
IsGuess = true;
ExtendedWho = false;
}
@ -115,8 +115,8 @@ namespace ChatSharp
public List<char?> GetModesForNick(string nick)
{
var supportedPrefixes = Prefixes[1];
List<char?> modeList = new();
List<char> nickPrefixes = new();
var modeList = new List<char?>();
var nickPrefixes = new List<char>();
foreach (var prefix in supportedPrefixes)
if (nick.Contains(prefix))

View File

@ -13,7 +13,7 @@ namespace ChatSharp
{
internal UserPool()
{
Users = new();
Users = new List<IrcUser>();
}
private List<IrcUser> Users { get; }

View File

@ -10,7 +10,7 @@ namespace ChatSharp
{
internal WhoIs()
{
User = new();
User = new IrcUser();
SecondsIdle = -1;
Channels = Array.Empty<string>();
}

View File

@ -12,7 +12,7 @@ namespace ChatSharp
{
QueryType = -1;
Channel = "*";
User = new();
User = new IrcUser();
IP = string.Empty;
Server = string.Empty;
Flags = string.Empty;

View File

@ -1,17 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="MSTest.Sdk/3.3.1">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<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="Microsoft.NET.Test.Sdk" Version="17.1.0"/>
<PackageReference Include="MSTest.TestAdapter" Version="2.2.9"/>
<PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.3.310801">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="MSTest.TestFramework" Version="2.2.9"/>
<PackageReference Include="YamlDotNet" Version="15.1.2"/>
</ItemGroup>
<ItemGroup>
<None Update="Data\msg-*.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

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

View File

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

View File

@ -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"

View File

@ -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"

View File

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

View File

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

View File

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

View File

@ -1,19 +0,0 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("ChatSharpTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ChatSharpTests")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("24d593ec-ca81-41dc-9fe7-a434ddde229d")]
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -2,10 +2,14 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Using Include="ChatSharp"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ChatSharp\ChatSharp.csproj"/>

View File

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