Compare commits
7 Commits
5c2b0faf52
...
32d3731be9
Author | SHA1 | Date |
---|---|---|
Ben Harris | 32d3731be9 | |
Ben Harris | 5ca368ea22 | |
Ben Harris | 1e1da72ed2 | |
Ben Harris | 80259bbad1 | |
Ben Harris | f862c2cb93 | |
Ben Harris | d2ce230b41 | |
Ben Harris | 53add8700f |
|
@ -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"><SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<Solution />
|
||||
</SessionState></s:String>
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=64875d9c_002Daf58_002D4d04_002D996b_002Ddf4baee8e592/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=64875d9c_002Daf58_002D4d04_002D996b_002Ddf4baee8e592/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<Solution />
|
||||
</SessionState></s:String></wpf:ResourceDictionary>
|
|
@ -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)
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace ChatSharp
|
|||
{
|
||||
internal ChannelCollection()
|
||||
{
|
||||
Channels = new();
|
||||
Channels = new List<IrcChannel>();
|
||||
}
|
||||
|
||||
internal ChannelCollection(IrcClient client) : this()
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]))));
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ namespace ChatSharp
|
|||
{
|
||||
internal MaskCollection()
|
||||
{
|
||||
Masks = new();
|
||||
Masks = new List<Mask>();
|
||||
}
|
||||
|
||||
private List<Mask> Masks { get; }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace ChatSharp
|
|||
{
|
||||
internal UserPool()
|
||||
{
|
||||
Users = new();
|
||||
Users = new List<IrcUser>();
|
||||
}
|
||||
|
||||
private List<IrcUser> Users { get; }
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace ChatSharp
|
|||
{
|
||||
internal WhoIs()
|
||||
{
|
||||
User = new();
|
||||
User = new IrcUser();
|
||||
SecondsIdle = -1;
|
||||
Channels = Array.Empty<string>();
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace ChatSharp
|
|||
{
|
||||
QueryType = -1;
|
||||
Channel = "*";
|
||||
User = new();
|
||||
User = new IrcUser();
|
||||
IP = string.Empty;
|
||||
Server = string.Empty;
|
||||
Flags = string.Empty;
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")]
|
|
@ -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"/>
|
||||
|
|
|
@ -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