.net 6, tidy up some warnings

This commit is contained in:
Ben Harris 2022-04-12 16:14:48 -04:00
parent d83294c7bc
commit b82b8c259a
54 changed files with 1374 additions and 1208 deletions

View File

@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatSharp", "ChatSharp\Chat
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatSharp.Tests", "ChatSharpTests\ChatSharp.Tests.csproj", "{24D593EC-CA81-41DC-9FE7-A434DDDE229D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{9F4A4C6C-8C21-471C-96FC-A932EF12002A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -29,6 +31,14 @@ Global
{24D593EC-CA81-41DC-9FE7-A434DDDE229D}.Release|Any CPU.Build.0 = Release|Any CPU
{24D593EC-CA81-41DC-9FE7-A434DDDE229D}.Release|x86.ActiveCfg = Release|Any CPU
{24D593EC-CA81-41DC-9FE7-A434DDDE229D}.Release|x86.Build.0 = Release|Any CPU
{9F4A4C6C-8C21-471C-96FC-A932EF12002A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F4A4C6C-8C21-471C-96FC-A932EF12002A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F4A4C6C-8C21-471C-96FC-A932EF12002A}.Debug|x86.ActiveCfg = Debug|Any CPU
{9F4A4C6C-8C21-471C-96FC-A932EF12002A}.Debug|x86.Build.0 = Debug|Any CPU
{9F4A4C6C-8C21-471C-96FC-A932EF12002A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F4A4C6C-8C21-471C-96FC-A932EF12002A}.Release|Any CPU.Build.0 = Release|Any CPU
{9F4A4C6C-8C21-471C-96FC-A932EF12002A}.Release|x86.ActiveCfg = Release|Any CPU
{9F4A4C6C-8C21-471C-96FC-A932EF12002A}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -1,23 +1,24 @@
using System.Collections.Generic;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace ChatSharp
{
/// <summary>
/// A list of capabilities supported by the library, along with the enabled and disabled
/// capabilities after negotiating with the server.
/// A list of capabilities supported by the library, along with the enabled and disabled
/// capabilities after negotiating with the server.
/// </summary>
public class CapabilityPool : IEnumerable<IrcCapability>
{
private List<IrcCapability> Capabilities { get; set; }
internal CapabilityPool()
{
Capabilities = new List<IrcCapability>();
Capabilities = new();
}
private List<IrcCapability> Capabilities { get; }
/// <summary>
/// Gets the IrcCapability with the specified name.
/// Gets the IrcCapability with the specified name.
/// </summary>
public IrcCapability this[string name]
{
@ -30,17 +31,48 @@ namespace ChatSharp
}
}
/// <summary>
/// Gets a list of enabled capabilities after negotiating capabilities with the server.
/// </summary>
/// <returns></returns>
internal IEnumerable<string> Enabled
{
get { return Capabilities.Where(cap => cap.IsEnabled).Select(x => x.Name); }
}
/// <summary>
/// Gets a list of disabled capabilities after negotiating capabilities with the server.
/// </summary>
/// <returns></returns>
internal IEnumerable<string> Disabled
{
get { return Capabilities.Where(cap => cap.IsEnabled).Select(x => x.Name); }
}
/// <summary>
/// Enumerates over the capabilities in this collection.
/// </summary>
public IEnumerator<IrcCapability> GetEnumerator()
{
return Capabilities.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
internal void Add(string name)
{
if (Capabilities.Any(cap => cap.Name == name))
return;
Capabilities.Add(new IrcCapability(name));
Capabilities.Add(new(name));
}
internal void AddRange(IEnumerable<string> range)
{
foreach (string item in range)
foreach (var item in range)
Add(item);
}
@ -50,7 +82,7 @@ namespace ChatSharp
}
/// <summary>
/// Enables the specified capability.
/// Enables the specified capability.
/// </summary>
internal void Enable(string name)
{
@ -61,7 +93,7 @@ namespace ChatSharp
}
/// <summary>
/// Disables the specified capability.
/// Disables the specified capability.
/// </summary>
internal void Disable(string name)
{
@ -72,37 +104,13 @@ namespace ChatSharp
}
/// <summary>
/// Checks if the specified capability is enabled.
/// Checks if the specified capability is enabled.
/// </summary>
internal bool IsEnabled(string name)
{
return Capabilities.Any(cap => cap.Name == name && cap.IsEnabled);
}
/// <summary>
/// Gets a list of enabled capabilities after negotiating capabilities with the server.
/// </summary>
/// <returns></returns>
internal IEnumerable<string> Enabled
{
get
{
return Capabilities.Where(cap => cap.IsEnabled).Select(x => x.Name);
}
}
/// <summary>
/// Gets a list of disabled capabilities after negotiating capabilities with the server.
/// </summary>
/// <returns></returns>
internal IEnumerable<string> Disabled
{
get
{
return Capabilities.Where(cap => cap.IsEnabled).Select(x => x.Name);
}
}
internal bool Contains(string name)
{
return Capabilities.Any(cap => cap.Name == name);
@ -123,18 +131,5 @@ namespace ChatSharp
Add(name);
return this[name];
}
/// <summary>
/// Enumerates over the capabilities in this collection.
/// </summary>
public IEnumerator<IrcCapability> GetEnumerator()
{
return Capabilities.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
}

View File

@ -6,13 +6,13 @@ using System.Linq;
namespace ChatSharp
{
/// <summary>
/// A collection of IRC channels a user is present in.
/// A collection of IRC channels a user is present in.
/// </summary>
public class ChannelCollection : IEnumerable<IrcChannel>
{
internal ChannelCollection()
{
Channels = new List<IrcChannel>();
Channels = new();
}
internal ChannelCollection(IrcClient client) : this()
@ -20,8 +20,44 @@ namespace ChatSharp
Client = client;
}
private IrcClient Client { get; set; }
private List<IrcChannel> Channels { get; set; }
private IrcClient Client { get; }
private List<IrcChannel> Channels { get; }
/// <summary>
/// Gets the channel at the given index.
/// </summary>
public IrcChannel this[int index] => Channels[index];
/// <summary>
/// Gets the channel by the given channel name, including channel prefix (i.e. '#')
/// </summary>
public IrcChannel this[string name]
{
get
{
var channel =
Channels.FirstOrDefault(c => string.Equals(c.Name, name, StringComparison.OrdinalIgnoreCase));
if (channel == null)
throw new KeyNotFoundException();
return channel;
}
}
/// <summary>
/// Gets an for the channels in this collection.
/// </summary>
public IEnumerator<IrcChannel> GetEnumerator()
{
return Channels.GetEnumerator();
}
/// <summary>
/// Gets an for the channels in this collection.
/// </summary>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
internal void Add(IrcChannel channel)
{
@ -36,7 +72,7 @@ namespace ChatSharp
}
/// <summary>
/// Join the specified channel. Only applicable for your own user.
/// Join the specified channel. Only applicable for your own user.
/// </summary>
public void Join(string name)
{
@ -47,61 +83,20 @@ namespace ChatSharp
}
/// <summary>
/// Returns true if the channel by the given name, including channel prefix (i.e. '#'), is in this collection.
/// Returns true if the channel by the given name, including channel prefix (i.e. '#'), is in this collection.
/// </summary>
public bool Contains(string name)
{
return Channels.Any(c => c.Name == name);
}
/// <summary>
/// Gets the channel at the given index.
/// </summary>
public IrcChannel this[int index]
{
get
{
return Channels[index];
}
}
/// <summary>
/// Gets the channel by the given channel name, including channel prefix (i.e. '#')
/// </summary>
public IrcChannel this[string name]
{
get
{
var channel = Channels.FirstOrDefault(c => string.Equals(c.Name, name, StringComparison.OrdinalIgnoreCase));
if (channel == null)
throw new KeyNotFoundException();
return channel;
}
}
internal IrcChannel GetOrAdd(string name)
{
if (this.Contains(name))
if (Contains(name))
return this[name];
var channel = new IrcChannel(Client, name);
this.Add(channel);
Add(channel);
return channel;
}
/// <summary>
/// Gets an for the channels in this collection.
/// </summary>
public IEnumerator<IrcChannel> GetEnumerator()
{
return Channels.GetEnumerator();
}
/// <summary>
/// Gets an for the channels in this collection.
/// </summary>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
}

View File

@ -1,17 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net5.0</TargetFrameworks>
<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>
</PropertyGroup>
<ItemGroup>
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
<PropertyGroup>
<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>
</PropertyGroup>
<ItemGroup>
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
</Project>

View File

@ -1,8 +1,8 @@
namespace ChatSharp
{
/// <summary>
/// Customize the behavior of the ChatSharp client, including enabling or disabling
/// "helper" features.
/// Customize the behavior of the ChatSharp client, including enabling or disabling
/// "helper" features.
/// </summary>
public class ClientSettings
{
@ -16,29 +16,33 @@ namespace ChatSharp
}
/// <summary>
/// If true, the client will WHOIS itself upon joining, which will populate the hostname in
/// IrcClient.User. This will allow you, for example, to use IrcUser.Match(...) on yourself
/// to see if you match any masks.
/// If true, the client will WHOIS itself upon joining, which will populate the hostname in
/// IrcClient.User. This will allow you, for example, to use IrcUser.Match(...) on yourself
/// to see if you match any masks.
/// </summary>
public bool WhoIsOnConnect { get; set; }
/// <summary>
/// If true, the client will MODE any channel it joins, populating IrcChannel.Mode. If false,
/// IrcChannel.Mode will be null until the mode is explicitly requested.
/// If true, the client will MODE any channel it joins, populating IrcChannel.Mode. If false,
/// IrcChannel.Mode will be null until the mode is explicitly requested.
/// </summary>
public bool ModeOnJoin { get; set; }
/// <summary>
/// If true, the library will generate a random nick with alphanumerical characters if it
/// encounters a NICK error.
/// If true, the library will generate a random nick with alphanumerical characters if it
/// encounters a NICK error.
/// </summary>
public bool GenerateRandomNickIfRefused { get; set; }
/// <summary>
/// If true, the library will WHOIS everyone in a channel upon joining. This procedure can
/// take several minutes on larger channels.
/// If true, the library will WHOIS everyone in a channel upon joining. This procedure can
/// take several minutes on larger channels.
/// </summary>
public bool WhoIsOnJoin { get; set; }
/// <summary>
/// The delay, in seconds, between each WHOIS when WhoIsOnJoin is true.
/// The delay, in seconds, between each WHOIS when WhoIsOnJoin is true.
/// </summary>
public int JoinWhoIsDelay { get; set; }
}
}
}

View File

@ -3,18 +3,18 @@ using System;
namespace ChatSharp.Events
{
/// <summary>
/// Generic event args for events regarding channels.
/// Generic event args for events regarding channels.
/// </summary>
public class ChannelEventArgs : EventArgs
{
/// <summary>
/// The channel this event regards.
/// </summary>
public IrcChannel Channel { get; set; }
internal ChannelEventArgs(IrcChannel channel)
{
Channel = channel;
}
/// <summary>
/// The channel this event regards.
/// </summary>
public IrcChannel Channel { get; set; }
}
}
}

View File

@ -3,28 +3,30 @@ using System;
namespace ChatSharp.Events
{
/// <summary>
/// Details of a channel topic event.
/// Details of a channel topic event.
/// </summary>
public class ChannelTopicEventArgs : EventArgs
{
/// <summary>
/// The channel whose topic has changed.
/// </summary>
public IrcChannel Channel { get; set; }
/// <summary>
/// The new topic
/// </summary>
public string Topic { get; set; }
/// <summary>
/// The original topic.
/// </summary>
public string OldTopic { get; set; }
internal ChannelTopicEventArgs(IrcChannel channel, string oldTopic, string topic)
{
Channel = channel;
Topic = topic;
OldTopic = oldTopic;
}
/// <summary>
/// The channel whose topic has changed.
/// </summary>
public IrcChannel Channel { get; set; }
/// <summary>
/// The new topic
/// </summary>
public string Topic { get; set; }
/// <summary>
/// The original topic.
/// </summary>
public string OldTopic { get; set; }
}
}
}

View File

@ -3,23 +3,24 @@ using System;
namespace ChatSharp.Events
{
/// <summary>
/// Generic event args for events regarding users in channels.
/// Generic event args for events regarding users in channels.
/// </summary>
public class ChannelUserEventArgs : EventArgs
{
/// <summary>
/// The channel this event regards.
/// </summary>
public IrcChannel Channel { get; set; }
/// <summary>
/// The user this event regards.
/// </summary>
public IrcUser User { get; set; }
internal ChannelUserEventArgs(IrcChannel channel, IrcUser user)
{
Channel = channel;
User = user;
}
/// <summary>
/// The channel this event regards.
/// </summary>
public IrcChannel Channel { get; set; }
/// <summary>
/// The user this event regards.
/// </summary>
public IrcUser User { get; set; }
}
}
}

View File

@ -3,43 +3,45 @@ using System;
namespace ChatSharp.Events
{
/// <summary>
/// Describes an invalid nick event.
/// Describes an invalid nick event.
/// </summary>
public class ErronousNickEventArgs : EventArgs
public class ErroneousNickEventArgs : EventArgs
{
private static Random random;
private static string GenerateRandomNick()
{
const string nickCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static Random _random;
if (random == null)
random = new Random();
var nick = new char[8];
for (int i = 0; i < nick.Length; i++)
nick[i] = nickCharacters[random.Next(nickCharacters.Length)];
return new string(nick);
}
/// <summary>
/// The nick that was not accepted by the server.
/// </summary>
/// <value>The invalid nick.</value>
public string InvalidNick { get; set; }
/// <summary>
/// The nick ChatSharp intends to use instead.
/// </summary>
/// <value>The new nick.</value>
public string NewNick { get; set; }
/// <summary>
/// Set to true to instruct ChatSharp NOT to send a valid nick.
/// </summary>
public bool DoNotHandle { get; set; }
internal ErronousNickEventArgs(string invalidNick)
internal ErroneousNickEventArgs(string invalidNick)
{
InvalidNick = invalidNick;
NewNick = GenerateRandomNick();
DoNotHandle = false;
}
/// <summary>
/// The nick that was not accepted by the server.
/// </summary>
/// <value>The invalid nick.</value>
public string InvalidNick { get; set; }
/// <summary>
/// The nick ChatSharp intends to use instead.
/// </summary>
/// <value>The new nick.</value>
public string NewNick { get; set; }
/// <summary>
/// Set to true to instruct ChatSharp NOT to send a valid nick.
/// </summary>
public bool DoNotHandle { get; set; }
private static string GenerateRandomNick()
{
const string nickCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
_random ??= new();
var nick = new char[8];
for (var i = 0; i < nick.Length; i++)
nick[i] = nickCharacters[_random.Next(nickCharacters.Length)];
return new(nick);
}
}
}
}

View File

@ -3,18 +3,18 @@ using System;
namespace ChatSharp.Events
{
/// <summary>
/// Raised when a Error occurs.
/// Raised when a Error occurs.
/// </summary>
public class ErrorEventArgs : EventArgs
{
/// <summary>
/// The error that has occured.
/// </summary>
public Exception Error { get; set; }
internal ErrorEventArgs(Exception error)
{
Error = error;
}
/// <summary>
/// The error that has occured.
/// </summary>
public Exception Error { get; set; }
}
}
}

View File

@ -3,18 +3,18 @@ using System;
namespace ChatSharp.Events
{
/// <summary>
/// Raised when a IRC Error reply occurs. See rfc1459 6.1 for details.
/// Raised when a IRC Error reply occurs. See rfc1459 6.1 for details.
/// </summary>
public class ErrorReplyEventArgs : EventArgs
{
/// <summary>
/// The IRC error reply that has occured.
/// </summary>
public IrcMessage Message { get; set; }
internal ErrorReplyEventArgs(IrcMessage message)
{
Message = message;
}
/// <summary>
/// The IRC error reply that has occured.
/// </summary>
public IrcMessage Message { get; set; }
}
}
}

View File

@ -3,30 +3,32 @@ using System;
namespace ChatSharp.Events
{
/// <summary>
/// Event describing an IRC notice.
/// Event describing an IRC notice.
/// </summary>
public class IrcNoticeEventArgs : EventArgs
{
/// <summary>
/// The IRC message that describes this NOTICE.
/// </summary>
/// <value>The message.</value>
public IrcMessage Message { get; set; }
/// <summary>
/// The text of the notice.
/// </summary>
public string Notice { get { return Message.Parameters[1]; } }
/// <summary>
/// The source of the notice (often a user).
/// </summary>
/// <value>The source.</value>
public string Source { get { return Message.Prefix; } }
internal IrcNoticeEventArgs(IrcMessage message)
{
if (message.Parameters.Length != 2)
throw new IrcProtocolException("NOTICE was delivered in incorrect format");
Message = message;
}
/// <summary>
/// The IRC message that describes this NOTICE.
/// </summary>
/// <value>The message.</value>
public IrcMessage Message { get; set; }
/// <summary>
/// The text of the notice.
/// </summary>
public string Notice => Message.Parameters[1];
/// <summary>
/// The source of the notice (often a user).
/// </summary>
/// <value>The source.</value>
public string Source => Message.Prefix;
}
}
}

View File

@ -1,9 +1,9 @@
using System;
namespace ChatSharp
namespace ChatSharp.Events
{
/// <summary>
/// Raised when a user (possibly yourself) is kicked from a channel.
/// Raised when a user (possibly yourself) is kicked from a channel.
/// </summary>
public class KickEventArgs : EventArgs
{
@ -16,21 +16,23 @@ namespace ChatSharp
}
/// <summary>
/// The channel the user was kicked from.
/// The channel the user was kicked from.
/// </summary>
public IrcChannel Channel { get; set; }
/// <summary>
/// The user who issued the kick.
/// The user who issued the kick.
/// </summary>
public IrcUser Kicker { get; set; }
/// <summary>
/// The user that was kicked.
/// The user that was kicked.
/// </summary>
public IrcUser Kicked { get; set; }
/// <summary>
/// The reason provided for the kick (may be null).
/// The reason provided for the kick (may be null).
/// </summary>
public string Reason { get; set; }
}
}
}

View File

@ -3,29 +3,31 @@ using System;
namespace ChatSharp.Events
{
/// <summary>
/// Describes a change to a channel or user mode.
/// Describes a change to a channel or user mode.
/// </summary>
public class ModeChangeEventArgs : EventArgs
{
/// <summary>
/// The target of this change (a channel or user).
/// </summary>
/// <value>The target.</value>
public string Target { get; set; }
/// <summary>
/// The user who issued the change.
/// </summary>
public IrcUser User { get; set; }
/// <summary>
/// The mode change string.
/// </summary>
public string Change { get; set; }
internal ModeChangeEventArgs(string target, IrcUser user, string change)
{
Target = target;
User = user;
Change = change;
}
/// <summary>
/// The target of this change (a channel or user).
/// </summary>
/// <value>The target.</value>
public string Target { get; set; }
/// <summary>
/// The user who issued the change.
/// </summary>
public IrcUser User { get; set; }
/// <summary>
/// The mode change string.
/// </summary>
public string Change { get; set; }
}
}
}

View File

@ -3,20 +3,22 @@ using System;
namespace ChatSharp.Events
{
/// <summary>
/// Raised when a user has changed their nick.
/// Raised when a user has changed their nick.
/// </summary>
public class NickChangedEventArgs : EventArgs
{
/// <summary>
/// The user whose nick changed.
/// The user whose nick changed.
/// </summary>
public IrcUser User { get; set; }
/// <summary>
/// The original nick.
/// The original nick.
/// </summary>
public string OldNick { get; set; }
/// <summary>
/// The new nick.
/// The new nick.
/// </summary>
public string NewNick { get; set; }
}

View File

@ -3,25 +3,26 @@ using System;
namespace ChatSharp.Events
{
/// <summary>
/// Describes a private message we have received.
/// The term "private message" is misleading - this describes both messages sent user-to-user,
/// and messages sent to a channel.
/// Describes a private message we have received.
/// The term "private message" is misleading - this describes both messages sent user-to-user,
/// and messages sent to a channel.
/// </summary>
public class PrivateMessageEventArgs : EventArgs
{
/// <summary>
/// The IRC message received.
/// </summary>
public IrcMessage IrcMessage { get; set; }
/// <summary>
/// The private message received.
/// </summary>
public PrivateMessage PrivateMessage { get; set; }
internal PrivateMessageEventArgs(IrcClient client, IrcMessage ircMessage, ServerInfo serverInfo)
{
IrcMessage = ircMessage;
PrivateMessage = new PrivateMessage(client, IrcMessage, serverInfo);
PrivateMessage = new(client, IrcMessage, serverInfo);
}
/// <summary>
/// The IRC message received.
/// </summary>
public IrcMessage IrcMessage { get; set; }
/// <summary>
/// The private message received.
/// </summary>
public PrivateMessage PrivateMessage { get; set; }
}
}
}

View File

@ -3,23 +3,24 @@ using System;
namespace ChatSharp.Events
{
/// <summary>
/// Describes a raw IRC message we have sent or received.
/// Describes a raw IRC message we have sent or received.
/// </summary>
public class RawMessageEventArgs : EventArgs
{
/// <summary>
/// The text of the raw IRC message.
/// </summary>
public string Message { get; set; }
/// <summary>
/// True if this message is going from ChatSharp to the server.
/// </summary>
public bool Outgoing { get; set; }
internal RawMessageEventArgs(string message, bool outgoing)
{
Message = message;
Outgoing = outgoing;
}
/// <summary>
/// The text of the raw IRC message.
/// </summary>
public string Message { get; set; }
/// <summary>
/// True if this message is going from ChatSharp to the server.
/// </summary>
public bool Outgoing { get; set; }
}
}
}

View File

@ -3,18 +3,18 @@ using System;
namespace ChatSharp.Events
{
/// <summary>
/// Raised when we have received the MOTD from the server.
/// Raised when we have received the MOTD from the server.
/// </summary>
public class ServerMOTDEventArgs : EventArgs
{
/// <summary>
/// The message of the day.
/// </summary>
public string MOTD { get; set; }
internal ServerMOTDEventArgs(string motd)
{
MOTD = motd;
}
/// <summary>
/// The message of the day.
/// </summary>
public string MOTD { get; set; }
}
}
}

View File

@ -4,18 +4,18 @@ using System.Net.Sockets;
namespace ChatSharp.Events
{
/// <summary>
/// Raised when a SocketError occurs.
/// Raised when a SocketError occurs.
/// </summary>
public class SocketErrorEventArgs : EventArgs
{
/// <summary>
/// The error that has occured.
/// </summary>
public SocketError SocketError { get; set; }
internal SocketErrorEventArgs(SocketError socketError)
{
SocketError = socketError;
}
/// <summary>
/// The error that has occured.
/// </summary>
public SocketError SocketError { get; set; }
}
}
}

View File

@ -3,18 +3,18 @@ using System;
namespace ChatSharp.Events
{
/// <summary>
/// Describes the features the server supports.
/// Describes the features the server supports.
/// </summary>
public class SupportsEventArgs : EventArgs
{
/// <summary>
/// The server's supported featureset.
/// </summary>
public ServerInfo ServerInfo { get; set; }
internal SupportsEventArgs(ServerInfo serverInfo)
{
ServerInfo = serverInfo;
}
/// <summary>
/// The server's supported featureset.
/// </summary>
public ServerInfo ServerInfo { get; set; }
}
}
}

View File

@ -3,19 +3,18 @@
namespace ChatSharp.Events
{
/// <summary>
/// Generic event args that represent an event regarding a user.
/// Generic event args that represent an event regarding a user.
/// </summary>
public class UserEventArgs : EventArgs
{
/// <summary>
/// The user this regards.
/// </summary>
public IrcUser User { get; set; }
internal UserEventArgs(IrcUser user)
{
User = user;
}
}
}
/// <summary>
/// The user this regards.
/// </summary>
public IrcUser User { get; set; }
}
}

View File

@ -3,19 +3,19 @@
namespace ChatSharp.Events
{
/// <summary>
/// Describes the response to a WHO (WHOX protocol) query. Note that ChatSharp may generate WHO
/// queries that the user did not ask for.
/// Describes the response to a WHO (WHOX protocol) query. Note that ChatSharp may generate WHO
/// queries that the user did not ask for.
/// </summary>
public class WhoxReceivedEventArgs : EventArgs
{
/// <summary>
/// The WHOIS response from the server.
/// </summary>
public ExtendedWho[] WhoxResponse { get; set; }
internal WhoxReceivedEventArgs(ExtendedWho[] whoxResponse)
{
WhoxResponse = whoxResponse;
}
/// <summary>
/// The WHOIS response from the server.
/// </summary>
public ExtendedWho[] WhoxResponse { get; set; }
}
}
}

View File

@ -3,19 +3,19 @@ using System;
namespace ChatSharp.Events
{
/// <summary>
/// Describes the response to a WHOIS query. Note that ChatSharp may generate WHOIS
/// queries that the consumer did not ask for.
/// Describes the response to a WHOIS query. Note that ChatSharp may generate WHOIS
/// queries that the consumer did not ask for.
/// </summary>
public class WhoIsReceivedEventArgs : EventArgs
{
/// <summary>
/// The WHOIS response from the server.
/// </summary>
public WhoIs WhoIsResponse { get; set; }
internal WhoIsReceivedEventArgs(WhoIs whoIsResponse)
{
WhoIsResponse = whoIsResponse;
}
/// <summary>
/// The WHOIS response from the server.
/// </summary>
public WhoIs WhoIsResponse { get; set; }
}
}
}

View File

@ -18,7 +18,7 @@ namespace ChatSharp.Handlers
client.IsNegotiatingCapabilities = true;
// Parse server capabilities
var serverCapsString = message.Parameters[2] == "*" ? message.Parameters[3] : message.Parameters[2];
serverCaps.AddRange(serverCapsString.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
serverCaps.AddRange(serverCapsString.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
// CAP 3.2 multiline support. Send CAP requests on the last CAP LS line.
// The last CAP LS line doesn't have * set as Parameters[2]
@ -30,18 +30,22 @@ namespace ChatSharp.Handlers
// Check if we have to request any capability to be enabled.
// If not, end the capability negotiation.
if (requestedCaps.Count > 0)
{
client.SendRawMessage("CAP REQ :{0}", string.Join(" ", requestedCaps));
}
else
{
client.SendRawMessage("CAP END");
client.IsNegotiatingCapabilities = false;
}
}
break;
case "ACK":
// Get the accepted capabilities
var acceptedCaps = message.Parameters[2].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string acceptedCap in acceptedCaps)
var acceptedCaps =
message.Parameters[2].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var acceptedCap in acceptedCaps)
{
client.Capabilities.Enable(acceptedCap);
// Begin SASL authentication
@ -54,7 +58,8 @@ 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)
if (client.IsNegotiatingCapabilities &&
client.Capabilities.Enabled.Count() == acceptedCaps.Count() && !client.IsAuthenticatingSasl)
{
client.SendRawMessage("CAP END");
client.IsNegotiatingCapabilities = false;
@ -63,13 +68,15 @@ namespace ChatSharp.Handlers
break;
case "NAK":
// Get the rejected capabilities
var rejectedCaps = message.Parameters[2].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string acceptedCap in rejectedCaps)
var rejectedCaps =
message.Parameters[2].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var acceptedCap in rejectedCaps)
client.Capabilities.Disable(acceptedCap);
// 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())
if (client.IsNegotiatingCapabilities &&
client.Capabilities.Disabled.Count() == rejectedCaps.Count())
{
client.SendRawMessage("CAP END");
client.IsNegotiatingCapabilities = false;
@ -78,16 +85,14 @@ namespace ChatSharp.Handlers
break;
case "LIST":
var activeCapsString = message.Parameters[2] == "*" ? message.Parameters[3] : message.Parameters[2];
var activeCaps = activeCapsString.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
var activeCaps = activeCapsString.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
// Check which cap we have that isn't active but the server lists
// as active.
foreach (string cap in activeCaps)
{
foreach (var cap in activeCaps)
if (client.Capabilities.Contains(cap))
if (!client.Capabilities[cap].IsEnabled)
client.Capabilities.Enable(cap);
}
break;
case "NEW":
@ -95,12 +100,14 @@ namespace ChatSharp.Handlers
var wantCaps = new List<string>();
// Check which new capabilities we support and send a REQ for them
wantCaps.AddRange(newCaps.Where(cap => client.Capabilities.Contains(cap) && !client.Capabilities[cap].IsEnabled));
wantCaps.AddRange(newCaps.Where(cap =>
client.Capabilities.Contains(cap) && !client.Capabilities[cap].IsEnabled));
client.SendRawMessage(string.Format("CAP REQ :{0}", string.Join(" ", wantCaps)));
break;
case "DEL":
var disabledCaps = message.Parameters[2].Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries).ToList();
var disabledCaps = message.Parameters[2].Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries)
.ToList();
// Disable each recently server-disabled capability
disabledCaps.ForEach(
@ -111,9 +118,7 @@ namespace ChatSharp.Handlers
}
);
break;
default:
break;
}
}
}
}
}

View File

@ -1,4 +1,3 @@
using ChatSharp.Events;
using System;
using System.Linq;
using System.Threading;
@ -20,13 +19,13 @@ namespace ChatSharp.Handlers
// account-notify capability
if (client.Capabilities.IsEnabled("account-notify"))
client.Who(user.Nick, WhoxFlag.None, WhoxField.Nick | WhoxField.AccountName, (whoQuery) =>
client.Who(user.Nick, WhoxFlag.None, WhoxField.Nick | WhoxField.AccountName, whoQuery =>
{
if (whoQuery.Count == 1)
user.Account = whoQuery[0].User.Account;
});
client.OnUserJoinedChannel(new ChannelUserEventArgs(channel, user));
client.OnUserJoinedChannel(new(channel, user));
}
}
@ -35,7 +34,7 @@ namespace ChatSharp.Handlers
var channel = client.Channels.GetOrAdd(message.Parameters[1]);
var old = channel._Topic;
channel._Topic = message.Parameters[2];
client.OnChannelTopicReceived(new ChannelTopicEventArgs(channel, old, channel._Topic));
client.OnChannelTopicReceived(new(channel, old, channel._Topic));
}
public static void HandleGetEmptyTopic(IrcClient client, IrcMessage message)
@ -43,7 +42,7 @@ namespace ChatSharp.Handlers
var channel = client.Channels.GetOrAdd(message.Parameters[1]);
var old = channel._Topic;
channel._Topic = message.Parameters[2];
client.OnChannelTopicReceived(new ChannelTopicEventArgs(channel, old, channel._Topic));
client.OnChannelTopicReceived(new(channel, old, channel._Topic));
}
public static void HandlePart(IrcClient client, IrcMessage message)
@ -59,7 +58,7 @@ namespace ChatSharp.Handlers
if (user.ChannelModes.ContainsKey(channel))
user.ChannelModes.Remove(channel);
client.OnUserPartedChannel(new ChannelUserEventArgs(channel, user));
client.OnUserPartedChannel(new(channel, user));
}
public static void HandleUserListPart(IrcClient client, IrcMessage message)
@ -74,9 +73,9 @@ namespace ChatSharp.Handlers
continue;
// Parse hostmask
var nick = hostmask.Substring(0, hostmask.IndexOf("!"));
var ident = hostmask[(nick.Length + 1)..hostmask.LastIndexOf("@")];
var hostname = hostmask[(hostmask.LastIndexOf("@") + 1)..];
var nick = hostmask.Substring(0, hostmask.IndexOf("!", StringComparison.Ordinal));
var ident = hostmask[(nick.Length + 1)..hostmask.LastIndexOf("@", StringComparison.Ordinal)];
var hostname = hostmask[(hostmask.LastIndexOf("@", StringComparison.Ordinal) + 1)..];
// Get user modes
var modes = client.ServerInfo.GetModesForNick(nick);
@ -128,19 +127,21 @@ namespace ChatSharp.Handlers
public static void HandleUserListEnd(IrcClient client, IrcMessage message)
{
var channel = client.Channels[message.Parameters[1]];
client.OnChannelListReceived(new ChannelEventArgs(channel));
client.OnChannelListReceived(new(channel));
if (client.Settings.ModeOnJoin)
{
try
{
client.GetMode(channel.Name, c => { /* no-op */ });
client.GetMode(channel.Name, _ =>
{
/* no-op */
});
}
catch { /* who cares */ }
}
if (client.Settings.WhoIsOnJoin)
{
Task.Factory.StartNew(() => WhoIsChannel(channel, client, 0));
}
catch
{
/* who cares */
}
if (client.Settings.WhoIsOnJoin) Task.Factory.StartNew(() => WhoIsChannel(channel, client, 0));
}
private static void WhoIsChannel(IrcChannel channel, IrcClient client, int index)
@ -148,13 +149,13 @@ namespace ChatSharp.Handlers
// Note: joins and parts that happen during this will cause strange behavior here
Thread.Sleep(client.Settings.JoinWhoIsDelay * 1000);
var user = channel.Users[index];
client.WhoIs(user.Nick, (whois) =>
{
user.User = whois.User.User;
user.Hostname = whois.User.Hostname;
user.RealName = whois.User.RealName;
Task.Factory.StartNew(() => WhoIsChannel(channel, client, index + 1));
});
client.WhoIs(user.Nick, whois =>
{
user.User = whois.User.User;
user.Hostname = whois.User.Hostname;
user.RealName = whois.User.RealName;
Task.Factory.StartNew(() => WhoIsChannel(channel, client, index + 1));
});
}
public static void HandleKick(IrcClient client, IrcMessage message)
@ -163,8 +164,8 @@ namespace ChatSharp.Handlers
var kicked = channel.Users[message.Parameters[1]];
if (kicked.Channels.Contains(channel))
kicked.Channels.Remove(channel);
client.OnUserKicked(new KickEventArgs(channel, new IrcUser(message.Prefix),
client.OnUserKicked(new(channel, new(message.Prefix),
kicked, message.Parameters[2]));
}
}
}
}

View File

@ -1,16 +1,16 @@
namespace ChatSharp.Handlers
{
/// <summary>
/// IRC error replies handler. See rfc1459 6.1.
/// IRC error replies handler. See rfc1459 6.1.
/// </summary>
internal static class ErrorHandlers
{
/// <summary>
/// IRC Error replies handler. See rfc1459 6.1.
/// IRC Error replies handler. See rfc1459 6.1.
/// </summary>
public static void HandleError(IrcClient client, IrcMessage message)
{
client.OnErrorReply(new Events.ErrorReplyEventArgs(message));
client.OnErrorReply(new(message));
}
}
}
}

View File

@ -8,7 +8,7 @@ namespace ChatSharp.Handlers
var parameters = parameterString[parameterString.IndexOf(' ')..].Split(' ');
var request = client.RequestManager.PeekOperation("GETMODE b " + parameters[1]);
var list = (MaskCollection)request.State;
list.Add(new Mask(parameters[2], client.Users.GetOrAdd(parameters[3]),
list.Add(new(parameters[2], client.Users.GetOrAdd(parameters[3]),
IrcClient.DateTimeFromIrcTime(int.Parse(parameters[4]))));
}
@ -24,7 +24,7 @@ namespace ChatSharp.Handlers
var parameters = parameterString[(parameterString.IndexOf(' ') + 1)..].Split(' ');
var request = client.RequestManager.PeekOperation("GETMODE e " + parameters[1]);
var list = (MaskCollection)request.State;
list.Add(new Mask(parameters[2], client.Users.GetOrAdd(parameters[3]),
list.Add(new(parameters[2], client.Users.GetOrAdd(parameters[3]),
IrcClient.DateTimeFromIrcTime(int.Parse(parameters[4]))));
}
@ -40,7 +40,7 @@ namespace ChatSharp.Handlers
var parameters = parameterString[(parameterString.IndexOf(' ') + 1)..].Split(' ');
var request = client.RequestManager.PeekOperation("GETMODE I " + parameters[1]);
var list = (MaskCollection)request.State;
list.Add(new Mask(parameters[2], client.Users.GetOrAdd(parameters[3]),
list.Add(new(parameters[2], client.Users.GetOrAdd(parameters[3]),
IrcClient.DateTimeFromIrcTime(int.Parse(parameters[4]))));
}
@ -56,7 +56,7 @@ namespace ChatSharp.Handlers
var parameters = parameterString[(parameterString.IndexOf(' ') + 1)..].Split(' ');
var request = client.RequestManager.PeekOperation("GETMODE q " + parameters[1]);
var list = (MaskCollection)request.State;
list.Add(new Mask(parameters[2], client.Users.GetOrAdd(parameters[3]),
list.Add(new(parameters[2], client.Users.GetOrAdd(parameters[3]),
IrcClient.DateTimeFromIrcTime(int.Parse(parameters[4]))));
}
@ -66,4 +66,4 @@ namespace ChatSharp.Handlers
request.Callback?.Invoke(request);
}
}
}
}

View File

@ -1,4 +1,3 @@
using ChatSharp.Events;
using System;
namespace ChatSharp.Handlers
@ -18,21 +17,21 @@ namespace ChatSharp.Handlers
throw new IrcProtocolException("372 MOTD message is incorrectly formatted.");
var part = message.Parameters[1][2..];
MOTD += part + Environment.NewLine;
client.OnMOTDPartReceived(new ServerMOTDEventArgs(part));
client.OnMOTDPartReceived(new(part));
}
public static void HandleEndOfMOTD(IrcClient client, IrcMessage message)
{
client.OnMOTDReceived(new ServerMOTDEventArgs(MOTD));
client.OnConnectionComplete(new EventArgs());
client.OnMOTDReceived(new(MOTD));
client.OnConnectionComplete(new());
// Verify our identity
VerifyOurIdentity(client);
}
public static void HandleMOTDNotFound(IrcClient client, IrcMessage message)
{
client.OnMOTDReceived(new ServerMOTDEventArgs(MOTD));
client.OnConnectionComplete(new EventArgs());
client.OnMOTDReceived(new(MOTD));
client.OnConnectionComplete(new());
VerifyOurIdentity(client);
}
@ -40,15 +39,13 @@ namespace ChatSharp.Handlers
private static void VerifyOurIdentity(IrcClient client)
{
if (client.Settings.WhoIsOnConnect)
{
client.WhoIs(client.User.Nick, whois =>
{
client.User.Nick = whois.User.Nick;
client.User.User = whois.User.User;
client.User.Hostname = whois.User.Hostname;
client.User.RealName = whois.User.RealName;
});
}
{
client.User.Nick = whois.User.Nick;
client.User.User = whois.User.User;
client.User.Hostname = whois.User.Hostname;
client.User.RealName = whois.User.RealName;
});
}
}
}
}

View File

@ -1,6 +1,5 @@
using ChatSharp.Events;
using System.Collections.Generic;
using System.Linq;
using ChatSharp.Events;
namespace ChatSharp.Handlers
{
@ -66,13 +65,17 @@ namespace ChatSharp.Handlers
client.SetHandler("005", ServerHandlers.HandleISupport);
// Error replies rfc1459 6.1
client.SetHandler("401", ErrorHandlers.HandleError);//ERR_NOSUCHNICK "<nickname> :No such nick/channel"
client.SetHandler("402", ErrorHandlers.HandleError);//ERR_NOSUCHSERVER "<server name> :No such server"
client.SetHandler("403", ErrorHandlers.HandleError);//ERR_NOSUCHCHANNEL "<channel name> :No such channel"
client.SetHandler("404", ErrorHandlers.HandleError);//ERR_CANNOTSENDTOCHAN "<channel name> :Cannot send to channel"
client.SetHandler("405", ErrorHandlers.HandleError);//ERR_TOOMANYCHANNELS "<channel name> :You have joined too many \ channels"
client.SetHandler("406", ErrorHandlers.HandleError);//ERR_WASNOSUCHNICK "<nickname> :There was no such nickname"
client.SetHandler("407", ErrorHandlers.HandleError);//ERR_TOOMANYTARGETS "<target> :Duplicate recipients. No message \
client.SetHandler("401", ErrorHandlers.HandleError); //ERR_NOSUCHNICK "<nickname> :No such nick/channel"
client.SetHandler("402", ErrorHandlers.HandleError); //ERR_NOSUCHSERVER "<server name> :No such server"
client.SetHandler("403", ErrorHandlers.HandleError); //ERR_NOSUCHCHANNEL "<channel name> :No such channel"
client.SetHandler("404",
ErrorHandlers.HandleError); //ERR_CANNOTSENDTOCHAN "<channel name> :Cannot send to channel"
client.SetHandler("405",
ErrorHandlers.HandleError); //ERR_TOOMANYCHANNELS "<channel name> :You have joined too many \ channels"
client.SetHandler("406",
ErrorHandlers.HandleError); //ERR_WASNOSUCHNICK "<nickname> :There was no such nickname"
client.SetHandler("407",
ErrorHandlers.HandleError); //ERR_TOOMANYTARGETS "<target> :Duplicate recipients. No message \
// Capability handlers
client.SetHandler("CAP", CapabilityHandlers.HandleCapability);
@ -92,7 +95,7 @@ namespace ChatSharp.Handlers
var oldNick = user.Nick;
user.Nick = message.Parameters[0];
client.OnNickChanged(new NickChangedEventArgs
client.OnNickChanged(new()
{
User = user,
OldNick = oldNick,
@ -106,7 +109,7 @@ namespace ChatSharp.Handlers
if (client.User.Nick != user.Nick)
{
client.Users.Remove(user);
client.OnUserQuit(new UserEventArgs(user));
client.OnUserQuit(new(user));
}
}
@ -118,7 +121,7 @@ namespace ChatSharp.Handlers
public static void HandleNotice(IrcClient client, IrcMessage message)
{
client.OnNoticeReceived(new IrcNoticeEventArgs(message));
client.OnNoticeReceived(new(message));
}
public static void HandlePrivmsg(IrcClient client, IrcMessage message)
@ -133,7 +136,7 @@ namespace ChatSharp.Handlers
public static void HandleErronousNick(IrcClient client, IrcMessage message)
{
var eventArgs = new ErronousNickEventArgs(client.User.Nick);
var eventArgs = new ErroneousNickEventArgs(client.User.Nick);
if (message.Command == "433") // Nick in use
client.OnNickInUse(eventArgs);
// else ... TODO
@ -143,8 +146,8 @@ namespace ChatSharp.Handlers
public static void HandleMode(IrcClient client, IrcMessage message)
{
string target, mode = null;
int i = 2;
string target, mode;
var i = 2;
if (message.Command == "MODE")
{
target = message.Parameters[0];
@ -156,50 +159,51 @@ namespace ChatSharp.Handlers
mode = message.Parameters[2];
i++;
}
// Handle change
bool add = true;
var add = true;
if (target.StartsWith("#"))
{
var channel = client.Channels[target];
try
{
foreach (char c in mode)
foreach (var c in mode)
{
if (c == '+')
{
add = true;
continue;
}
if (c == '-')
{
add = false;
continue;
}
if (channel.Mode == null)
channel.Mode = string.Empty;
// TODO: Support the ones here that aren't done properly
if (client.ServerInfo.SupportedChannelModes.ParameterizedSettings.Contains(c))
{
client.OnModeChanged(new ModeChangeEventArgs(channel.Name, new IrcUser(message.Prefix),
client.OnModeChanged(new(channel.Name, new(message.Prefix),
(add ? "+" : "-") + c + " " + message.Parameters[i++]));
}
else if (client.ServerInfo.SupportedChannelModes.ChannelLists.Contains(c))
{
client.OnModeChanged(new ModeChangeEventArgs(channel.Name, new IrcUser(message.Prefix),
client.OnModeChanged(new(channel.Name, new(message.Prefix),
(add ? "+" : "-") + c + " " + message.Parameters[i++]));
}
else if (client.ServerInfo.SupportedChannelModes.ChannelUserModes.Contains(c))
{
if (!channel.UsersByMode.ContainsKey(c))
{
channel.UsersByMode.Add(c,
new UserPoolView(channel.Users.Where(u =>
new(channel.Users.Where(u =>
{
if (!u.ChannelModes.ContainsKey(channel))
u.ChannelModes.Add(channel, new List<char?>());
u.ChannelModes.Add(channel, new());
return u.ChannelModes[channel].Contains(c);
})));
}
var user = new IrcUser(message.Parameters[i]);
if (add)
{
@ -212,9 +216,11 @@ namespace ChatSharp.Handlers
if (channel.UsersByMode[c].Contains(user.Nick))
user.ChannelModes[channel] = null;
}
client.OnModeChanged(new ModeChangeEventArgs(channel.Name, new IrcUser(message.Prefix),
client.OnModeChanged(new(channel.Name, new(message.Prefix),
(add ? "+" : "-") + c + " " + message.Parameters[i++]));
}
if (client.ServerInfo.SupportedChannelModes.Settings.Contains(c))
{
if (add)
@ -223,13 +229,19 @@ namespace ChatSharp.Handlers
channel.Mode += c.ToString();
}
else
{
channel.Mode = channel.Mode.Replace(c.ToString(), string.Empty);
client.OnModeChanged(new ModeChangeEventArgs(channel.Name, new IrcUser(message.Prefix),
}
client.OnModeChanged(new(channel.Name, new(message.Prefix),
(add ? "+" : "-") + c));
}
}
}
catch { }
catch
{
}
if (message.Command == "324")
{
var operation = client.RequestManager.DequeueOperation("MODE " + channel.Name);
@ -239,17 +251,17 @@ namespace ChatSharp.Handlers
else
{
// TODO: Handle user modes other than ourselves?
foreach (char c in mode)
{
foreach (var c in mode)
if (add)
{
if (!client.User.Mode.Contains(c))
client.User.Mode += c;
}
else
{
client.User.Mode = client.User.Mode.Replace(c.ToString(), string.Empty);
}
}
}
}
}
}
}

View File

@ -9,7 +9,6 @@ namespace ChatSharp.Handlers
public static void HandleAuthentication(IrcClient client, IrcMessage message)
{
if (client.IsAuthenticatingSasl)
{
if (message.Parameters[0] == "+")
{
// Based off irc-framework implementation
@ -22,6 +21,7 @@ namespace ChatSharp.Handlers
b64Bytes = b64Bytes.Skip(400).ToArray();
client.SendRawMessage(string.Format("AUTHENTICATE {0}", Encoding.UTF8.GetString(chunk)));
}
if (b64Bytes.Length > 0)
client.SendRawMessage(string.Format("AUTHENTICATE {0}", Encoding.UTF8.GetString(b64Bytes)));
else
@ -29,7 +29,6 @@ namespace ChatSharp.Handlers
client.IsAuthenticatingSasl = false;
}
}
}
public static void HandleError(IrcClient client, IrcMessage message)
@ -41,4 +40,4 @@ namespace ChatSharp.Handlers
}
}
}
}
}

View File

@ -1,5 +1,3 @@
using ChatSharp.Events;
namespace ChatSharp.Handlers
{
internal static class ServerHandlers
@ -7,7 +5,7 @@ namespace ChatSharp.Handlers
public static void HandleISupport(IrcClient client, IrcMessage message)
{
if (client.ServerInfo == null)
client.ServerInfo = new ServerInfo();
client.ServerInfo = new();
foreach (var item in message.Parameters)
{
string key, value;
@ -21,10 +19,10 @@ namespace ChatSharp.Handlers
key = item.Remove(item.IndexOf('='));
value = item[(item.IndexOf('=') + 1)..];
}
// TODO: Consider doing this differently
// TODO: Allow users to specify other things to handle
if (!string.IsNullOrEmpty(value))
{
switch (key.ToUpper())
{
case "PREFIX":
@ -54,13 +52,14 @@ namespace ChatSharp.Handlers
case "MAXLIST":
var limits = value.Split(',');
client.ServerInfo.ModeListLimits = new ServerInfo.ModeListLimit[limits.Length];
for (int i = 0; i < limits.Length; i++)
for (var i = 0; i < limits.Length; i++)
{
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 ServerInfo.ModeListLimit(mode, limit);
client.ServerInfo.ModeListLimits[i] = new(mode, limit);
}
break;
case "NETWORK":
client.ServerInfo.NetworkName = value;
@ -84,18 +83,16 @@ namespace ChatSharp.Handlers
client.ServerInfo.MaxAwayLength = int.Parse(value);
break;
}
}
else
{
switch (key.ToUpper())
{
case "WHOX":
client.ServerInfo.ExtendedWho = true;
break;
}
}
}
client.OnServerInfoReceived(new SupportsEventArgs(client.ServerInfo));
client.OnServerInfoReceived(new(client.ServerInfo));
}
public static void HandleMyInfo(IrcClient client, IrcMessage message)
@ -103,15 +100,11 @@ namespace ChatSharp.Handlers
// 004 sendak.freenode.net ircd-seven-1.1.3 DOQRSZaghilopswz CFILMPQbcefgijklmnopqrstvz bkloveqjfI
// TODO: Figure out how to properly handle this message
if (client.ServerInfo == null)
client.ServerInfo = new ServerInfo();
client.ServerInfo = new();
if (message.Parameters.Length >= 5)
{
foreach (var c in message.Parameters[4])
{
if (!client.ServerInfo.SupportedChannelModes.ChannelUserModes.Contains(c))
client.ServerInfo.SupportedChannelModes.ChannelUserModes += c.ToString();
}
}
}
}
}
}

View File

@ -55,7 +55,7 @@ namespace ChatSharp.Handlers
{
var whois = (WhoIs)client.RequestManager.PeekOperation("WHOIS " + message.Parameters[1]).State;
var channels = message.Parameters[2].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < channels.Length; i++)
for (var i = 0; i < channels.Length; i++)
if (!channels[i].StartsWith("#"))
channels[i] = channels[i][1..];
whois.Channels = whois.Channels.Concat(channels).ToArray();
@ -68,21 +68,20 @@ namespace ChatSharp.Handlers
if (!client.Users.Contains(whois.User.Nick))
client.Users.Add(whois.User);
request.Callback?.Invoke(request);
client.OnWhoIsReceived(new Events.WhoIsReceivedEventArgs(whois));
client.OnWhoIsReceived(new(whois));
}
public static void HandleWho(IrcClient client, IrcMessage message)
{
// A standard WHO request (numeric 352) is just like a WHOX request, except that it has less fields.
foreach (var query in client.RequestManager.PendingOperations.Where(kvp => kvp.Key.StartsWith("WHO ")))
{
if (query.Key != string.Empty && query.Value != null)
{
var whoList = (List<ExtendedWho>)client.RequestManager.PeekOperation(query.Key).State;
var who = new ExtendedWho
{
Channel = message.Parameters[1],
User = new IrcUser
User = new()
{
User = message.Parameters[2],
Nick = message.Parameters[5]
@ -95,29 +94,28 @@ namespace ChatSharp.Handlers
var supposedRealName = message.Parameters[7];
// Parsing IRC spec craziness: the hops field is included in the real name field
var hops = supposedRealName.Substring(0, supposedRealName.IndexOf(" "));
var hops = supposedRealName[..supposedRealName.IndexOf(" ", StringComparison.Ordinal)];
who.Hops = int.Parse(hops);
var realName = supposedRealName[(supposedRealName.IndexOf(" ") + 1)..];
var realName = supposedRealName[(supposedRealName.IndexOf(" ", StringComparison.Ordinal) + 1)..];
who.User.RealName = realName;
whoList.Add(who);
}
}
}
public static void HandleWhox(IrcClient client, IrcMessage message)
{
int msgQueryType = int.Parse(message.Parameters[1]);
var msgQueryType = int.Parse(message.Parameters[1]);
var whoxQuery = new KeyValuePair<string, RequestOperation>();
foreach (var query in client.RequestManager.PendingOperations.Where(kvp => kvp.Key.StartsWith("WHO ")))
{
// Parse query to retrieve querytype
string key = query.Key;
string[] queryParts = key.Split(new[] { ' ' });
var key = query.Key;
var queryParts = key.Split(new[] { ' ' });
int queryType = int.Parse(queryParts[2]);
var queryType = int.Parse(queryParts[2]);
// Check querytype against message querytype
if (queryType == msgQueryType) whoxQuery = query;
@ -128,12 +126,12 @@ namespace ChatSharp.Handlers
var whoxList = (List<ExtendedWho>)client.RequestManager.PeekOperation(whoxQuery.Key).State;
var whox = new ExtendedWho();
string key = whoxQuery.Key;
string[] queryParts = key.Split(new[] { ' ' });
var key = whoxQuery.Key;
var queryParts = key.Split(new[] { ' ' });
// Handle what fields were included in the WHOX request
WhoxField fields = (WhoxField)int.Parse(queryParts[3]);
int fieldIdx = 1;
var fields = (WhoxField)int.Parse(queryParts[3]);
var fieldIdx = 1;
do
{
if ((fields & WhoxField.QueryType) != 0)
@ -213,8 +211,8 @@ namespace ChatSharp.Handlers
whox.User.RealName = message.Parameters[fieldIdx];
fieldIdx++;
}
}
while (fieldIdx < message.Parameters.Length - 1);
} while (fieldIdx < message.Parameters.Length - 1);
whoxList.Add(whox);
}
}
@ -228,7 +226,8 @@ 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
.Where(kvp => kvp.Key.StartsWith("WHO " + message.Parameters[1])).FirstOrDefault();
var request = client.RequestManager.DequeueOperation(query.Key);
var whoxList = (List<ExtendedWho>)request.State;
@ -237,11 +236,12 @@ namespace ChatSharp.Handlers
client.Users.Add(whox.User);
request.Callback?.Invoke(request);
client.OnWhoxReceived(new Events.WhoxReceivedEventArgs(whoxList.ToArray()));
client.OnWhoxReceived(new(whoxList.ToArray()));
}
else
{
var query = client.RequestManager.PendingOperations.Where(kvp => kvp.Key == "WHO " + message.Parameters[1]).FirstOrDefault();
var query = client.RequestManager.PendingOperations
.Where(kvp => kvp.Key == "WHO " + message.Parameters[1]).FirstOrDefault();
var request = client.RequestManager.DequeueOperation(query.Key);
var whoList = (List<ExtendedWho>)request.State;
@ -250,7 +250,7 @@ namespace ChatSharp.Handlers
client.Users.Add(who.User);
request.Callback?.Invoke(request);
client.OnWhoxReceived(new Events.WhoxReceivedEventArgs(whoList.ToArray()));
client.OnWhoxReceived(new(whoList.ToArray()));
}
}
@ -277,4 +277,4 @@ namespace ChatSharp.Handlers
}
}
}
}
}

View File

@ -1,22 +1,12 @@
namespace ChatSharp
{
/// <summary>
/// A IRC capability.
/// A IRC capability.
/// </summary>
public class IrcCapability
{
/// <summary>
/// The capability identifier.
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// The state of the capability after negotiating with the server.
/// Disabled by default when the capability is created.
/// </summary>
public bool IsEnabled { get; internal set; }
/// <summary>
/// Constructs a capability given a name.
/// Constructs a capability given a name.
/// </summary>
/// <param name="name"></param>
public IrcCapability(string name)
@ -24,5 +14,16 @@
Name = name;
IsEnabled = false;
}
/// <summary>
/// The capability identifier.
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// The state of the capability after negotiating with the server.
/// Disabled by default when the capability is created.
/// </summary>
public bool IsEnabled { get; internal set; }
}
}
}

View File

@ -4,22 +4,27 @@ using System.Linq;
namespace ChatSharp
{
/// <summary>
/// An IRC channel.
/// An IRC channel.
/// </summary>
public class IrcChannel
{
private IrcClient Client { get; set; }
internal string _Topic;
internal IrcChannel(IrcClient client, string name)
{
Client = client;
Name = name;
Users = new(client.Users.Where(u => u.Channels.Contains(this)));
}
private IrcClient Client { get; }
/// <summary>
/// The channel topic. Will send a TOPIC command if set.
/// The channel topic. Will send a TOPIC command if set.
/// </summary>
public string Topic
{
get
{
return _Topic;
}
get => _Topic;
set
{
Client.SetTopic(Name, value);
@ -28,32 +33,28 @@ namespace ChatSharp
}
/// <summary>
/// The name, including the prefix (i.e. #), of this channel.
/// The name, including the prefix (i.e. #), of this channel.
/// </summary>
/// <value>The name.</value>
public string Name { get; internal set; }
/// <summary>
/// The channel mode. May be null if we have not received the mode yet.
/// The channel mode. May be null if we have not received the mode yet.
/// </summary>
public string Mode { get; internal set; }
/// <summary>
/// The users in this channel.
/// The users in this channel.
/// </summary>
public UserPoolView Users { get; private set; }
public UserPoolView Users { get; }
/// <summary>
/// Users in this channel, grouped by mode. Users with no special mode are grouped under null.
/// Users in this channel, grouped by mode. Users with no special mode are grouped under null.
/// </summary>
public Dictionary<char?, UserPoolView> UsersByMode { get; set; }
internal IrcChannel(IrcClient client, string name)
{
Client = client;
Name = name;
Users = new UserPoolView(client.Users.Where(u => u.Channels.Contains(this)));
}
/// <summary>
/// Invites a user to this channel.
/// Invites a user to this channel.
/// </summary>
public void Invite(string nick)
{
@ -61,7 +62,7 @@ namespace ChatSharp
}
/// <summary>
/// Kicks a user from this channel.
/// Kicks a user from this channel.
/// </summary>
public void Kick(string nick)
{
@ -69,7 +70,7 @@ namespace ChatSharp
}
/// <summary>
/// Kicks a user from this channel, giving a reason for the kick.
/// Kicks a user from this channel, giving a reason for the kick.
/// </summary>
public void Kick(string nick, string reason)
{
@ -77,7 +78,7 @@ namespace ChatSharp
}
/// <summary>
/// Parts this channel.
/// Parts this channel.
/// </summary>
public void Part()
{
@ -85,7 +86,7 @@ namespace ChatSharp
}
/// <summary>
/// Parts this channel, giving a reason for your departure.
/// Parts this channel, giving a reason for your departure.
/// </summary>
public void Part(string reason)
{
@ -93,7 +94,7 @@ namespace ChatSharp
}
/// <summary>
/// Sends a PRIVMSG to this channel.
/// Sends a PRIVMSG to this channel.
/// </summary>
public void SendMessage(string message)
{
@ -101,7 +102,7 @@ namespace ChatSharp
}
/// <summary>
/// Set the channel mode.
/// Set the channel mode.
/// </summary>
public void ChangeMode(string change)
{
@ -109,7 +110,7 @@ namespace ChatSharp
}
/// <summary>
/// True if this channel is equal to another (compares names).
/// True if this channel is equal to another (compares names).
/// </summary>
/// <returns></returns>
public bool Equals(IrcChannel other)
@ -118,7 +119,7 @@ namespace ChatSharp
}
/// <summary>
/// True if this channel is equal to another (compares names).
/// True if this channel is equal to another (compares names).
/// </summary>
/// <returns></returns>
public override bool Equals(object obj)
@ -129,7 +130,7 @@ namespace ChatSharp
}
/// <summary>
/// Returns the hash code of the channel's name.
/// Returns the hash code of the channel's name.
/// </summary>
/// <returns></returns>
public override int GetHashCode()
@ -137,4 +138,4 @@ namespace ChatSharp
return Name.GetHashCode();
}
}
}
}

View File

@ -7,7 +7,7 @@ namespace ChatSharp
public partial class IrcClient
{
/// <summary>
/// Changes your nick.
/// Changes your nick.
/// </summary>
public void Nick(string newNick)
{
@ -16,43 +16,49 @@ namespace ChatSharp
}
/// <summary>
/// Sends a message to one or more destinations (channels or users).
/// Sends a message to one or more destinations (channels or users).
/// </summary>
public void SendMessage(string message, params string[] destinations)
{
const string illegalCharacters = "\r\n\0";
if (destinations == null || !destinations.Any()) throw new InvalidOperationException("Message must have at least one target.");
if (illegalCharacters.Any(message.Contains)) throw new ArgumentException("Illegal characters are present in message.", nameof(message));
string to = string.Join(",", destinations);
if (destinations == null || !destinations.Any())
throw new InvalidOperationException("Message must have at least one target.");
if (illegalCharacters.Any(message.Contains))
throw new ArgumentException("Illegal characters are present in message.", nameof(message));
var to = string.Join(",", destinations);
SendRawMessage("PRIVMSG {0} :{1}{2}", to, PrivmsgPrefix, message);
}
/// <summary>
/// Sends a CTCP action (i.e. "* SirCmpwn waves hello") to one or more destinations.
/// Sends a CTCP action (i.e. "* SirCmpwn waves hello") to one or more destinations.
/// </summary>
public void SendAction(string message, params string[] destinations)
{
const string illegalCharacters = "\r\n\0";
if (destinations == null || !destinations.Any()) throw new InvalidOperationException("Message must have at least one target.");
if (illegalCharacters.Any(message.Contains)) throw new ArgumentException("Illegal characters are present in message.", nameof(message));
string to = string.Join(",", destinations);
if (destinations == null || !destinations.Any())
throw new InvalidOperationException("Message must have at least one target.");
if (illegalCharacters.Any(message.Contains))
throw new ArgumentException("Illegal characters are present in message.", nameof(message));
var to = string.Join(",", destinations);
SendRawMessage("PRIVMSG {0} :\x0001ACTION {1}{2}\x0001", to, PrivmsgPrefix, message);
}
/// <summary>
/// Sends a NOTICE to one or more destinations (channels or users).
/// Sends a NOTICE to one or more destinations (channels or users).
/// </summary>
public void SendNotice(string message, params string[] destinations)
{
const string illegalCharacters = "\r\n\0";
if (destinations == null || !destinations.Any()) throw new InvalidOperationException("Message must have at least one target.");
if (illegalCharacters.Any(message.Contains)) throw new ArgumentException("Illegal characters are present in message.", nameof(message));
string to = string.Join(",", destinations);
if (destinations == null || !destinations.Any())
throw new InvalidOperationException("Message must have at least one target.");
if (illegalCharacters.Any(message.Contains))
throw new ArgumentException("Illegal characters are present in message.", nameof(message));
var to = string.Join(",", destinations);
SendRawMessage("NOTICE {0} :{1}{2}", to, PrivmsgPrefix, message);
}
/// <summary>
/// Leaves the specified channel.
/// Leaves the specified channel.
/// </summary>
public void PartChannel(string channel)
{
@ -62,7 +68,7 @@ namespace ChatSharp
}
/// <summary>
/// Leaves the specified channel, giving a reason for your departure.
/// Leaves the specified channel, giving a reason for your departure.
/// </summary>
public void PartChannel(string channel, string reason)
{
@ -72,14 +78,14 @@ namespace ChatSharp
}
/// <summary>
/// Joins the specified channel.
/// Joins the specified channel.
/// </summary>
public void JoinChannel(string channel, string key = null)
{
if (Channels.Contains(channel))
throw new InvalidOperationException("Client is already present in channel.");
string joinCmd = string.Format("JOIN {0}", channel);
var joinCmd = string.Format("JOIN {0}", channel);
if (!string.IsNullOrEmpty(key))
joinCmd += string.Format(" {0}", key);
@ -89,21 +95,19 @@ namespace ChatSharp
var flags = WhoxField.Nick | WhoxField.Hostname | WhoxField.AccountName | WhoxField.Username;
if (Capabilities.IsEnabled("account-notify"))
Who(channel, WhoxFlag.None, flags, (whoList) =>
Who(channel, WhoxFlag.None, flags, whoList =>
{
if (whoList.Count > 0)
{
foreach (var whoQuery in whoList)
{
var user = Users.GetOrAdd(whoQuery.User.Hostmask);
user.Account = whoQuery.User.Account;
}
}
});
}
/// <summary>
/// Sets the topic for the specified channel.
/// Sets the topic for the specified channel.
/// </summary>
public void SetTopic(string channel, string topic)
{
@ -113,7 +117,7 @@ namespace ChatSharp
}
/// <summary>
/// Retrieves the topic for the specified channel.
/// Retrieves the topic for the specified channel.
/// </summary>
public void GetTopic(string channel)
{
@ -121,7 +125,7 @@ namespace ChatSharp
}
/// <summary>
/// Kicks the specified user from the specified channel.
/// Kicks the specified user from the specified channel.
/// </summary>
public void KickUser(string channel, string user)
{
@ -129,7 +133,7 @@ namespace ChatSharp
}
/// <summary>
/// Kicks the specified user from the specified channel.
/// Kicks the specified user from the specified channel.
/// </summary>
public void KickUser(string channel, string user, string reason)
{
@ -137,7 +141,7 @@ namespace ChatSharp
}
/// <summary>
/// Invites the specified user to the specified channel.
/// Invites the specified user to the specified channel.
/// </summary>
public void InviteUser(string channel, string user)
{
@ -145,7 +149,7 @@ namespace ChatSharp
}
/// <summary>
/// Sends a WHOIS query asking for information on the given nick.
/// Sends a WHOIS query asking for information on the given nick.
/// </summary>
public void WhoIs(string nick)
{
@ -153,22 +157,19 @@ namespace ChatSharp
}
/// <summary>
/// Sends a WHOIS query asking for information on the given nick, and a callback
/// to run when we have received the response.
/// Sends a WHOIS query asking for information on the given nick, and a callback
/// to run when we have received the response.
/// </summary>
public void WhoIs(string nick, Action<WhoIs> callback)
{
var whois = new WhoIs();
RequestManager.QueueOperation("WHOIS " + nick, new RequestOperation(whois, ro =>
{
callback?.Invoke((WhoIs)ro.State);
}));
RequestManager.QueueOperation("WHOIS " + nick, new(whois, ro => { callback?.Invoke((WhoIs)ro.State); }));
SendRawMessage("WHOIS {0}", nick);
}
/// <summary>
/// Sends an extended WHO query asking for specific information about a single user
/// or the users in a channel, and runs a callback when we have received the response.
/// Sends an extended WHO query asking for specific information about a single user
/// or the users in a channel, and runs a callback when we have received the response.
/// </summary>
public void Who(string target, WhoxFlag flags, WhoxField fields, Action<List<ExtendedWho>> callback)
{
@ -177,38 +178,35 @@ namespace ChatSharp
var whox = new List<ExtendedWho>();
// Generate random querytype for WHO query
int queryType = RandomNumber.Next(0, 999);
var queryType = RandomNumber.Next(0, 999);
// Add the querytype field if it wasn't defined
var _fields = fields;
if ((fields & WhoxField.QueryType) == 0)
_fields |= WhoxField.QueryType;
string whoQuery = string.Format("WHO {0} {1}%{2},{3}", target, flags.AsString(), _fields.AsString(), queryType);
string queryKey = string.Format("WHO {0} {1} {2:D}", target, queryType, _fields);
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);
RequestManager.QueueOperation(queryKey, new RequestOperation(whox, ro =>
{
callback?.Invoke((List<ExtendedWho>)ro.State);
}));
RequestManager.QueueOperation(queryKey,
new(whox, ro => { callback?.Invoke((List<ExtendedWho>)ro.State); }));
SendRawMessage(whoQuery);
}
else
{
var whox = new List<ExtendedWho>();
string whoQuery = string.Format("WHO {0}", target);
var whoQuery = string.Format("WHO {0}", target);
RequestManager.QueueOperation(whoQuery, new RequestOperation(whox, ro =>
{
callback?.Invoke((List<ExtendedWho>)ro.State);
}));
RequestManager.QueueOperation(whoQuery,
new(whox, ro => { callback?.Invoke((List<ExtendedWho>)ro.State); }));
SendRawMessage(whoQuery);
}
}
/// <summary>
/// Requests the mode of a channel from the server.
/// Requests the mode of a channel from the server.
/// </summary>
public void GetMode(string channel)
{
@ -216,20 +214,20 @@ namespace ChatSharp
}
/// <summary>
/// Requests the mode of a channel from the server, and passes it to a callback later.
/// Requests the mode of a channel from the server, and passes it to a callback later.
/// </summary>
public void GetMode(string channel, Action<IrcChannel> callback)
{
RequestManager.QueueOperation("MODE " + channel, new RequestOperation(channel, ro =>
{
var c = Channels[(string)ro.State];
callback?.Invoke(c);
}));
RequestManager.QueueOperation("MODE " + channel, new(channel, ro =>
{
var c = Channels[(string)ro.State];
callback?.Invoke(c);
}));
SendRawMessage("MODE {0}", channel);
}
/// <summary>
/// Sets the mode of a target.
/// Sets the mode of a target.
/// </summary>
public void ChangeMode(string target, string change)
{
@ -237,17 +235,17 @@ namespace ChatSharp
}
/// <summary>
/// Gets a collection of masks from a channel by a mode. This can be used, for example,
/// to get a list of bans.
/// Gets a collection of masks from a channel by a mode. This can be used, for example,
/// to get a list of bans.
/// </summary>
public void GetModeList(string channel, char mode, Action<MaskCollection> callback)
{
RequestManager.QueueOperation("GETMODE " + mode + " " + channel, new RequestOperation(new MaskCollection(), ro =>
{
var c = (MaskCollection)ro.State;
callback?.Invoke(c);
}));
RequestManager.QueueOperation("GETMODE " + mode + " " + channel, new(new MaskCollection(), ro =>
{
var c = (MaskCollection)ro.State;
callback?.Invoke(c);
}));
SendRawMessage("MODE {0} {1}", channel, mode);
}
}
}
}

View File

@ -1,5 +1,3 @@
using ChatSharp.Events;
using ChatSharp.Handlers;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -8,23 +6,174 @@ using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Timers;
using ChatSharp.Events;
using ChatSharp.Handlers;
using ErrorEventArgs = ChatSharp.Events.ErrorEventArgs;
namespace ChatSharp
{
/// <summary>
/// An IRC client.
/// An IRC client.
/// </summary>
public sealed partial class IrcClient
{
/// <summary>
/// A raw IRC message handler.
/// A raw IRC message handler.
/// </summary>
public delegate void MessageHandler(IrcClient client, IrcMessage message);
private Dictionary<string, MessageHandler> Handlers { get; set; }
private const int ReadBufferLength = 1024;
/// <summary>
/// Sets a custom handler for an IRC message. This applies to the low level IRC protocol,
/// not for private messages.
/// Creates a new IRC client, but will not connect until ConnectAsync is called.
/// </summary>
/// <param name="serverAddress">Server address including port in the form of "hostname:port".</param>
/// <param name="user">The IRC user to connect as.</param>
/// <param name="useSSL">Connect with SSL if true.</param>
public IrcClient(string serverAddress, IrcUser user, bool useSSL = false)
{
User = user ?? throw new ArgumentNullException(nameof(user));
ServerAddress = serverAddress ?? throw new ArgumentNullException(nameof(serverAddress));
Encoding = Encoding.UTF8;
Settings = new();
Handlers = new();
MessageHandlers.RegisterDefaultHandlers(this);
RequestManager = new();
UseSSL = useSSL;
WriteQueue = new();
ServerInfo = new();
PrivmsgPrefix = "";
Channels = User.Channels = new(this);
// Add self to user pool
Users = new() { User };
Capabilities = new();
// List of supported capabilities
Capabilities.AddRange(new[]
{
"server-time", "multi-prefix", "cap-notify", "znc.in/server-time", "znc.in/server-time-iso",
"account-notify", "chghost", "userhost-in-names", "sasl"
});
IsNegotiatingCapabilities = false;
IsAuthenticatingSasl = false;
RandomNumber = new();
}
private Dictionary<string, MessageHandler> Handlers { get; }
internal static Random RandomNumber { get; private set; }
private byte[] ReadBuffer { get; set; }
private int ReadBufferIndex { get; set; }
private string ServerHostname { get; set; }
private int ServerPort { get; set; }
private Timer PingTimer { get; set; }
private Socket Socket { get; set; }
private ConcurrentQueue<string> WriteQueue { get; }
private bool IsWriting { get; set; }
internal RequestManager RequestManager { get; set; }
internal string ServerNameFromPing { get; set; }
/// <summary>
/// The address this client is connected to, or will connect to. Setting this
/// after the client is connected will not cause a reconnect.
/// </summary>
public string ServerAddress
{
get => ServerHostname + ":" + ServerPort;
internal set
{
var parts = value.Split(':');
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;
}
}
/// <summary>
/// The low level TCP stream for this client.
/// </summary>
public Stream NetworkStream { get; set; }
/// <summary>
/// If true, SSL will be used to connect.
/// </summary>
public bool UseSSL { get; }
/// <summary>
/// If true, invalid SSL certificates are ignored.
/// </summary>
public bool IgnoreInvalidSSL { get; set; }
/// <summary>
/// The character encoding to use for the connection. Defaults to UTF-8.
/// </summary>
/// <value>The encoding.</value>
public Encoding Encoding { get; set; }
/// <summary>
/// The user this client is logged in as.
/// </summary>
/// <value>The user.</value>
public IrcUser User { get; set; }
/// <summary>
/// The channels this user is joined to.
/// </summary>
public ChannelCollection Channels { get; }
/// <summary>
/// Settings that control the behavior of ChatSharp.
/// </summary>
public ClientSettings Settings { get; set; }
/// <summary>
/// Information about the server we are connected to. Servers may not send us this information,
/// but it's required for ChatSharp to function, so by default this is a guess. Handle
/// IrcClient.ServerInfoReceived if you'd like to know when it's populated with real information.
/// </summary>
public ServerInfo ServerInfo { get; set; }
/// <summary>
/// A string to prepend to all PRIVMSGs sent. Many IRC bots prefix their messages with \u200B, to
/// indicate to other bots that you are a bot.
/// </summary>
public string PrivmsgPrefix { get; set; }
/// <summary>
/// A list of users on this network that we are aware of.
/// </summary>
public UserPool Users { get; set; }
/// <summary>
/// A list of capabilities supported by the library, along with enabled and disabled capabilities
/// after negotiating with the server.
/// </summary>
public CapabilityPool Capabilities { get; set; }
/// <summary>
/// Set to true when the client is negotiating IRC capabilities with the server.
/// If set to False, capability negotiation is finished.
/// </summary>
public bool IsNegotiatingCapabilities { get; internal set; }
/// <summary>
/// Set to True when the client is authenticating with SASL.
/// If set to False, SASL authentication is finished.
/// </summary>
public bool IsAuthenticatingSasl { get; internal set; }
/// <summary>
/// Sets a custom handler for an IRC message. This applies to the low level IRC protocol,
/// not for private messages.
/// </summary>
public void SetHandler(string message, MessageHandler handler)
{
@ -43,165 +192,32 @@ namespace ChatSharp
return new DateTime(1970, 1, 1).AddSeconds(time);
}
internal static Random RandomNumber { get; private set; }
private const int ReadBufferLength = 1024;
private byte[] ReadBuffer { get; set; }
private int ReadBufferIndex { get; set; }
private string ServerHostname { get; set; }
private int ServerPort { get; set; }
private Timer PingTimer { get; set; }
private Socket Socket { get; set; }
private ConcurrentQueue<string> WriteQueue { get; set; }
private bool IsWriting { get; set; }
internal RequestManager RequestManager { get; set; }
internal string ServerNameFromPing { get; set; }
/// <summary>
/// The address this client is connected to, or will connect to. Setting this
/// after the client is connected will not cause a reconnect.
/// </summary>
public string ServerAddress
{
get
{
return ServerHostname + ":" + ServerPort;
}
internal set
{
string[] parts = value.Split(':');
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;
}
}
/// <summary>
/// The low level TCP stream for this client.
/// </summary>
public Stream NetworkStream { get; set; }
/// <summary>
/// If true, SSL will be used to connect.
/// </summary>
public bool UseSSL { get; private set; }
/// <summary>
/// If true, invalid SSL certificates are ignored.
/// </summary>
public bool IgnoreInvalidSSL { get; set; }
/// <summary>
/// The character encoding to use for the connection. Defaults to UTF-8.
/// </summary>
/// <value>The encoding.</value>
public Encoding Encoding { get; set; }
/// <summary>
/// The user this client is logged in as.
/// </summary>
/// <value>The user.</value>
public IrcUser User { get; set; }
/// <summary>
/// The channels this user is joined to.
/// </summary>
public ChannelCollection Channels { get; private set; }
/// <summary>
/// Settings that control the behavior of ChatSharp.
/// </summary>
public ClientSettings Settings { get; set; }
/// <summary>
/// Information about the server we are connected to. Servers may not send us this information,
/// but it's required for ChatSharp to function, so by default this is a guess. Handle
/// IrcClient.ServerInfoReceived if you'd like to know when it's populated with real information.
/// </summary>
public ServerInfo ServerInfo { get; set; }
/// <summary>
/// A string to prepend to all PRIVMSGs sent. Many IRC bots prefix their messages with \u200B, to
/// indicate to other bots that you are a bot.
/// </summary>
public string PrivmsgPrefix { get; set; }
/// <summary>
/// A list of users on this network that we are aware of.
/// </summary>
public UserPool Users { get; set; }
/// <summary>
/// A list of capabilities supported by the library, along with enabled and disabled capabilities
/// after negotiating with the server.
/// </summary>
public CapabilityPool Capabilities { get; set; }
/// <summary>
/// Set to true when the client is negotiating IRC capabilities with the server.
/// If set to False, capability negotiation is finished.
/// </summary>
public bool IsNegotiatingCapabilities { get; internal set; }
/// <summary>
/// Set to True when the client is authenticating with SASL.
/// If set to False, SASL authentication is finished.
/// </summary>
public bool IsAuthenticatingSasl { get; internal set; }
/// <summary>
/// Creates a new IRC client, but will not connect until ConnectAsync is called.
/// </summary>
/// <param name="serverAddress">Server address including port in the form of "hostname:port".</param>
/// <param name="user">The IRC user to connect as.</param>
/// <param name="useSSL">Connect with SSL if true.</param>
public IrcClient(string serverAddress, IrcUser user, bool useSSL = false)
{
User = user ?? throw new ArgumentNullException(nameof(user));
ServerAddress = serverAddress ?? throw new ArgumentNullException(nameof(serverAddress));
Encoding = Encoding.UTF8;
Settings = new ClientSettings();
Handlers = new Dictionary<string, MessageHandler>();
MessageHandlers.RegisterDefaultHandlers(this);
RequestManager = new RequestManager();
UseSSL = useSSL;
WriteQueue = new ConcurrentQueue<string>();
ServerInfo = new ServerInfo();
PrivmsgPrefix = "";
Channels = User.Channels = new ChannelCollection(this);
// Add self to user pool
Users = new UserPool { User };
Capabilities = new CapabilityPool();
// List of supported capabilities
Capabilities.AddRange(new string[] {
"server-time", "multi-prefix", "cap-notify", "znc.in/server-time", "znc.in/server-time-iso",
"account-notify", "chghost", "userhost-in-names", "sasl"
});
IsNegotiatingCapabilities = false;
IsAuthenticatingSasl = false;
RandomNumber = new Random();
}
/// <summary>
/// Connects to the IRC server.
/// Connects to the IRC server.
/// </summary>
public void ConnectAsync()
{
if (Socket != null && Socket.Connected) throw new InvalidOperationException("Socket is already connected to server.");
Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
if (Socket != null && Socket.Connected)
throw new InvalidOperationException("Socket is already connected to server.");
Socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ReadBuffer = new byte[ReadBufferLength];
ReadBufferIndex = 0;
PingTimer = new Timer(30000);
PingTimer.Elapsed += (sender, e) =>
PingTimer = new(30000);
PingTimer.Elapsed += (_, _) =>
{
if (!string.IsNullOrEmpty(ServerNameFromPing))
SendRawMessage("PING :{0}", ServerNameFromPing);
};
var checkQueue = new Timer(1000);
checkQueue.Elapsed += (sender, e) =>
checkQueue.Elapsed += (_, _) =>
{
string nextMessage;
if (!WriteQueue.IsEmpty)
{
while (!WriteQueue.TryDequeue(out nextMessage)) ;
string nextMessage;
while (!WriteQueue.TryDequeue(out nextMessage))
{
}
SendRawMessage(nextMessage);
}
};
@ -210,7 +226,7 @@ namespace ChatSharp
}
/// <summary>
/// Send a QUIT message and disconnect.
/// Send a QUIT message and disconnect.
/// </summary>
public void Quit()
{
@ -218,7 +234,7 @@ namespace ChatSharp
}
/// <summary>
/// Send a QUIT message with a reason and disconnect.
/// Send a QUIT message with a reason and disconnect.
/// </summary>
public void Quit(string reason)
{
@ -244,7 +260,7 @@ namespace ChatSharp
if (UseSSL)
{
if (IgnoreInvalidSSL)
NetworkStream = new SslStream(NetworkStream, false, (sender, certificate, chain, policyErrors) => true);
NetworkStream = new SslStream(NetworkStream, false, (_, _, _, _) => true);
else
NetworkStream = new SslStream(NetworkStream);
((SslStream)NetworkStream).AuthenticateAsClient(ServerHostname);
@ -263,11 +279,11 @@ namespace ChatSharp
}
catch (SocketException e)
{
OnNetworkError(new SocketErrorEventArgs(e.SocketErrorCode));
OnNetworkError(new(e.SocketErrorCode));
}
catch (Exception e)
{
OnError(new Events.ErrorEventArgs(e));
OnError(new(e));
}
}
@ -275,7 +291,7 @@ namespace ChatSharp
{
if (NetworkStream == null)
{
OnNetworkError(new SocketErrorEventArgs(SocketError.NotConnected));
OnNetworkError(new(SocketError.NotConnected));
return;
}
@ -287,7 +303,7 @@ namespace ChatSharp
catch (IOException e)
{
if (e.InnerException is SocketException socketException)
OnNetworkError(new SocketErrorEventArgs(socketException.SocketErrorCode));
OnNetworkError(new(socketException.SocketErrorCode));
else
throw;
return;
@ -296,41 +312,42 @@ namespace ChatSharp
ReadBufferIndex = 0;
while (length > 0)
{
int messageLength = Array.IndexOf(ReadBuffer, (byte)'\n', 0, length);
var messageLength = Array.IndexOf(ReadBuffer, (byte)'\n', 0, length);
if (messageLength == -1) // Incomplete message
{
ReadBufferIndex = length;
break;
}
messageLength++;
var message = Encoding.GetString(ReadBuffer, 0, messageLength - 2); // -2 to remove \r\n
HandleMessage(message);
Array.Copy(ReadBuffer, messageLength, ReadBuffer, 0, length - messageLength);
length -= messageLength;
}
NetworkStream.BeginRead(ReadBuffer, ReadBufferIndex, ReadBuffer.Length - ReadBufferIndex, DataReceived, null);
NetworkStream.BeginRead(ReadBuffer, ReadBufferIndex, ReadBuffer.Length - ReadBufferIndex, DataReceived,
null);
}
private void HandleMessage(string rawMessage)
{
OnRawMessageReceived(new RawMessageEventArgs(rawMessage, false));
OnRawMessageReceived(new(rawMessage, false));
var message = new IrcMessage(rawMessage);
if (Handlers.ContainsKey(message.Command.ToUpper()))
Handlers[message.Command.ToUpper()](this, message);
else
{
// TODO: Fire an event or something
Handlers[message.Command.ToUpper()](this, message);
}
}
/// <summary>
/// Send a raw IRC message. Behaves like /quote in most IRC clients.
/// Send a raw IRC message. Behaves like /quote in most IRC clients.
/// </summary>
public void SendRawMessage(string message, params object[] format)
{
if (NetworkStream == null)
{
OnNetworkError(new SocketErrorEventArgs(SocketError.NotConnected));
OnNetworkError(new(SocketError.NotConnected));
return;
}
@ -349,7 +366,7 @@ namespace ChatSharp
}
/// <summary>
/// Send a raw IRC message. Behaves like /quote in most IRC clients.
/// Send a raw IRC message. Behaves like /quote in most IRC clients.
/// </summary>
public void SendIrcMessage(IrcMessage message)
{
@ -360,7 +377,7 @@ namespace ChatSharp
{
if (NetworkStream == null)
{
OnNetworkError(new SocketErrorEventArgs(SocketError.NotConnected));
OnNetworkError(new(SocketError.NotConnected));
IsWriting = false;
return;
}
@ -372,7 +389,7 @@ namespace ChatSharp
catch (IOException e)
{
if (e.InnerException is SocketException socketException)
OnNetworkError(new SocketErrorEventArgs(socketException.SocketErrorCode));
OnNetworkError(new(socketException.SocketErrorCode));
else
throw;
return;
@ -382,208 +399,258 @@ namespace ChatSharp
IsWriting = false;
}
OnRawMessageSent(new RawMessageEventArgs((string)result.AsyncState, true));
OnRawMessageSent(new((string)result.AsyncState, true));
string nextMessage;
if (!WriteQueue.IsEmpty)
{
while (!WriteQueue.TryDequeue(out nextMessage)) ;
while (!WriteQueue.TryDequeue(out nextMessage))
{
}
SendRawMessage(nextMessage);
}
}
/// <summary>
/// IRC Error Replies. rfc1459 6.1.
/// IRC Error Replies. rfc1459 6.1.
/// </summary>
public event EventHandler<Events.ErrorReplyEventArgs> ErrorReply;
internal void OnErrorReply(Events.ErrorReplyEventArgs e)
public event EventHandler<ErrorReplyEventArgs> ErrorReply;
internal void OnErrorReply(ErrorReplyEventArgs e)
{
ErrorReply?.Invoke(this, e);
}
/// <summary>
/// Raised for errors.
/// Raised for errors.
/// </summary>
public event EventHandler<Events.ErrorEventArgs> Error;
internal void OnError(Events.ErrorEventArgs e)
public event EventHandler<ErrorEventArgs> Error;
internal void OnError(ErrorEventArgs e)
{
Error?.Invoke(this, e);
}
/// <summary>
/// Raised for socket errors. ChatSharp does not automatically reconnect.
/// Raised for socket errors. ChatSharp does not automatically reconnect.
/// </summary>
public event EventHandler<SocketErrorEventArgs> NetworkError;
internal void OnNetworkError(SocketErrorEventArgs e)
{
NetworkError?.Invoke(this, e);
}
/// <summary>
/// Occurs when a raw message is sent.
/// Occurs when a raw message is sent.
/// </summary>
public event EventHandler<RawMessageEventArgs> RawMessageSent;
internal void OnRawMessageSent(RawMessageEventArgs e)
{
RawMessageSent?.Invoke(this, e);
}
/// <summary>
/// Occurs when a raw message received.
/// Occurs when a raw message received.
/// </summary>
public event EventHandler<RawMessageEventArgs> RawMessageReceived;
internal void OnRawMessageReceived(RawMessageEventArgs e)
{
RawMessageReceived?.Invoke(this, e);
}
/// <summary>
/// Occurs when a notice received.
/// Occurs when a notice received.
/// </summary>
public event EventHandler<IrcNoticeEventArgs> NoticeReceived;
internal void OnNoticeReceived(IrcNoticeEventArgs e)
{
NoticeReceived?.Invoke(this, e);
}
/// <summary>
/// Occurs when the server has sent us part of the MOTD.
/// Occurs when the server has sent us part of the MOTD.
/// </summary>
public event EventHandler<ServerMOTDEventArgs> MOTDPartReceived;
internal void OnMOTDPartReceived(ServerMOTDEventArgs e)
{
MOTDPartReceived?.Invoke(this, e);
}
/// <summary>
/// Occurs when the entire server MOTD has been received.
/// Occurs when the entire server MOTD has been received.
/// </summary>
public event EventHandler<ServerMOTDEventArgs> MOTDReceived;
internal void OnMOTDReceived(ServerMOTDEventArgs e)
{
MOTDReceived?.Invoke(this, e);
}
/// <summary>
/// Occurs when a private message received. This can be a channel OR a user message.
/// Occurs when a private message received. This can be a channel OR a user message.
/// </summary>
public event EventHandler<PrivateMessageEventArgs> PrivateMessageReceived;
internal void OnPrivateMessageReceived(PrivateMessageEventArgs e)
{
PrivateMessageReceived?.Invoke(this, e);
}
/// <summary>
/// Occurs when a message is received in an IRC channel.
/// Occurs when a message is received in an IRC channel.
/// </summary>
public event EventHandler<PrivateMessageEventArgs> ChannelMessageReceived;
internal void OnChannelMessageReceived(PrivateMessageEventArgs e)
{
ChannelMessageReceived?.Invoke(this, e);
}
/// <summary>
/// Occurs when a message is received from a user.
/// Occurs when a message is received from a user.
/// </summary>
public event EventHandler<PrivateMessageEventArgs> UserMessageReceived;
internal void OnUserMessageReceived(PrivateMessageEventArgs e)
{
UserMessageReceived?.Invoke(this, e);
}
/// <summary>
/// Raised if the nick you've chosen is in use. By default, ChatSharp will pick a
/// random nick to use instead. Set ErronousNickEventArgs.DoNotHandle to prevent this.
/// Raised if the nick you've chosen is in use. By default, ChatSharp will pick a
/// random nick to use instead. Set ErronousNickEventArgs.DoNotHandle to prevent this.
/// </summary>
public event EventHandler<ErronousNickEventArgs> NickInUse;
internal void OnNickInUse(ErronousNickEventArgs e)
public event EventHandler<ErroneousNickEventArgs> NickInUse;
internal void OnNickInUse(ErroneousNickEventArgs e)
{
NickInUse?.Invoke(this, e);
}
/// <summary>
/// Occurs when a user or channel mode is changed.
/// Occurs when a user or channel mode is changed.
/// </summary>
public event EventHandler<ModeChangeEventArgs> ModeChanged;
internal void OnModeChanged(ModeChangeEventArgs e)
{
ModeChanged?.Invoke(this, e);
}
/// <summary>
/// Occurs when a user joins a channel.
/// Occurs when a user joins a channel.
/// </summary>
public event EventHandler<ChannelUserEventArgs> UserJoinedChannel;
internal void OnUserJoinedChannel(ChannelUserEventArgs e)
{
UserJoinedChannel?.Invoke(this, e);
}
/// <summary>
/// Occurs when a user parts a channel.
/// Occurs when a user parts a channel.
/// </summary>
public event EventHandler<ChannelUserEventArgs> UserPartedChannel;
internal void OnUserPartedChannel(ChannelUserEventArgs e)
{
UserPartedChannel?.Invoke(this, e);
}
/// <summary>
/// Occurs when we have received the list of users present in a channel.
/// Occurs when we have received the list of users present in a channel.
/// </summary>
public event EventHandler<ChannelEventArgs> ChannelListReceived;
internal void OnChannelListReceived(ChannelEventArgs e)
{
ChannelListReceived?.Invoke(this, e);
}
/// <summary>
/// Occurs when we have received the topic of a channel.
/// Occurs when we have received the topic of a channel.
/// </summary>
public event EventHandler<ChannelTopicEventArgs> ChannelTopicReceived;
internal void OnChannelTopicReceived(ChannelTopicEventArgs e)
{
ChannelTopicReceived?.Invoke(this, e);
}
/// <summary>
/// Occurs when the IRC connection is established and it is safe to begin interacting with the server.
/// Occurs when the IRC connection is established and it is safe to begin interacting with the server.
/// </summary>
public event EventHandler<EventArgs> ConnectionComplete;
internal void OnConnectionComplete(EventArgs e)
{
ConnectionComplete?.Invoke(this, e);
}
/// <summary>
/// Occurs when we receive server info (such as max nick length).
/// Occurs when we receive server info (such as max nick length).
/// </summary>
public event EventHandler<SupportsEventArgs> ServerInfoReceived;
internal void OnServerInfoReceived(SupportsEventArgs e)
{
ServerInfoReceived?.Invoke(this, e);
}
/// <summary>
/// Occurs when a user is kicked.
/// Occurs when a user is kicked.
/// </summary>
public event EventHandler<KickEventArgs> UserKicked;
internal void OnUserKicked(KickEventArgs e)
{
UserKicked?.Invoke(this, e);
}
/// <summary>
/// Occurs when a WHOIS response is received.
/// Occurs when a WHOIS response is received.
/// </summary>
public event EventHandler<WhoIsReceivedEventArgs> WhoIsReceived;
internal void OnWhoIsReceived(WhoIsReceivedEventArgs e)
{
WhoIsReceived?.Invoke(this, e);
}
/// <summary>
/// Occurs when a user has changed their nick.
/// Occurs when a user has changed their nick.
/// </summary>
public event EventHandler<NickChangedEventArgs> NickChanged;
internal void OnNickChanged(NickChangedEventArgs e)
{
NickChanged?.Invoke(this, e);
}
/// <summary>
/// Occurs when a user has quit.
/// Occurs when a user has quit.
/// </summary>
public event EventHandler<UserEventArgs> UserQuit;
internal void OnUserQuit(UserEventArgs e)
{
UserQuit?.Invoke(this, e);
}
/// <summary>
/// Occurs when a WHO (WHOX protocol) is received.
/// Occurs when a WHO (WHOX protocol) is received.
/// </summary>
public event EventHandler<WhoxReceivedEventArgs> WhoxReceived;
internal void OnWhoxReceived(WhoxReceivedEventArgs e)
{
WhoxReceived?.Invoke(this, e);
}
}
}
}

View File

@ -1,46 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace ChatSharp
{
/// <summary>
/// Represents a raw IRC message. This is a low-level construct - PrivateMessage is used
/// to represent messages sent from users.
/// Represents a raw IRC message. This is a low-level construct - PrivateMessage is used
/// to represent messages sent from users.
/// </summary>
public class IrcMessage
{
/// <summary>
/// The unparsed message.
/// </summary>
public string RawMessage { get; private set; }
/// <summary>
/// The message prefix.
/// </summary>
public string Prefix { get; private set; }
/// <summary>
/// The message command.
/// </summary>
public string Command { get; private set; }
/// <summary>
/// Additional parameters supplied with the message.
/// </summary>
public string[] Parameters { get; private set; }
/// <summary>
/// The message tags.
/// </summary>
public KeyValuePair<string, string>[] Tags { get; private set; }
/// <summary>
/// The message timestamp in ISO 8601 format.
/// </summary>
public Timestamp Timestamp { get; private set; }
/// <summary>
/// Initializes and decodes an IRC message, given the raw message from the server.
/// Initializes and decodes an IRC message, given the raw message from the server.
/// </summary>
public IrcMessage(string rawMessage)
{
RawMessage = rawMessage;
Tags = System.Array.Empty<KeyValuePair<string, string>>();
Tags = Array.Empty<KeyValuePair<string, string>>();
if (rawMessage.StartsWith("@"))
{
@ -49,7 +25,7 @@ namespace ChatSharp
// Parse tags as key value pairs
var tags = new List<KeyValuePair<string, string>>();
foreach (string rawTag in rawTags.Split(';'))
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
@ -57,9 +33,9 @@ namespace ChatSharp
if (replacedTag.Contains("="))
{
string key = replacedTag.Substring(0, replacedTag.IndexOf("="));
string value = replacedTag[(replacedTag.IndexOf("=") + 1)..];
tag = new KeyValuePair<string, string>(key, value);
var key = replacedTag.Substring(0, replacedTag.IndexOf("=", StringComparison.Ordinal));
var value = replacedTag[(replacedTag.IndexOf("=", StringComparison.Ordinal) + 1)..];
tag = new(key, value);
}
tags.Add(tag);
@ -87,22 +63,25 @@ namespace ChatSharp
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();
}
else
{
// Violates RFC 1459, but we'll parse it anyway
Command = rawMessage;
Parameters = System.Array.Empty<string>();
Parameters = Array.Empty<string>();
}
// Parse server-time message tag.
@ -112,14 +91,44 @@ namespace ChatSharp
if (Tags.Any(tag => tag.Key == "t"))
{
var tag = Tags.SingleOrDefault(x => x.Key == "t");
Timestamp = new Timestamp(tag.Value, true);
Timestamp = new(tag.Value, true);
}
// server-time tag
else if (Tags.Any(tag => tag.Key == "time"))
{
var tag = Tags.SingleOrDefault(x => x.Key == "time");
Timestamp = new Timestamp(tag.Value);
Timestamp = new(tag.Value);
}
}
/// <summary>
/// The unparsed message.
/// </summary>
public string RawMessage { get; }
/// <summary>
/// The message prefix.
/// </summary>
public string Prefix { get; }
/// <summary>
/// The message command.
/// </summary>
public string Command { get; }
/// <summary>
/// Additional parameters supplied with the message.
/// </summary>
public string[] Parameters { get; }
/// <summary>
/// The message tags.
/// </summary>
public KeyValuePair<string, string>[] Tags { get; }
/// <summary>
/// The message timestamp in ISO 8601 format.
/// </summary>
public Timestamp Timestamp { get; }
}
}
}

View File

@ -3,7 +3,7 @@ using System;
namespace ChatSharp
{
/// <summary>
/// Raised when the server complains about IRC protocol errors.
/// Raised when the server complains about IRC protocol errors.
/// </summary>
public class IrcProtocolException : Exception
{
@ -13,7 +13,6 @@ namespace ChatSharp
internal IrcProtocolException(string message) : base(message)
{
}
}
}
}

View File

@ -4,42 +4,40 @@ using System.Collections.Generic;
namespace ChatSharp
{
/// <summary>
/// A user connected to IRC.
/// A user connected to IRC.
/// </summary>
public class IrcUser : IEquatable<IrcUser>
{
internal IrcUser()
{
Channels = new ChannelCollection();
ChannelModes = new Dictionary<IrcChannel, List<char?>>();
Channels = new();
ChannelModes = new();
Account = "*";
}
/// <summary>
/// Constructs an IrcUser given a hostmask or nick.
/// Constructs an IrcUser given a hostmask or nick.
/// </summary>
public IrcUser(string host) : this()
{
if (!host.Contains("@") && !host.Contains("!"))
{
Nick = host;
}
else
{
string[] mask = host.Split('@', '!');
var mask = host.Split('@', '!');
Nick = mask[0];
User = mask[1];
if (mask.Length <= 2)
{
Hostname = "";
}
else
{
Hostname = mask[2];
}
}
}
/// <summary>
/// Constructs an IrcUser given a nick and user.
/// Constructs an IrcUser given a nick and user.
/// </summary>
public IrcUser(string nick, string user) : this()
{
@ -50,7 +48,7 @@ namespace ChatSharp
}
/// <summary>
/// Constructs an IRC user given a nick, user, and password.
/// Constructs an IRC user given a nick, user, and password.
/// </summary>
public IrcUser(string nick, string user, string password) : this(nick, user)
{
@ -58,7 +56,7 @@ namespace ChatSharp
}
/// <summary>
/// Constructs an IRC user given a nick, user, password, and real name.
/// 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)
{
@ -66,59 +64,68 @@ namespace ChatSharp
}
/// <summary>
/// The user's nick.
/// The user's nick.
/// </summary>
public string Nick { get; internal set; }
/// <summary>
/// The user's user (an IRC construct, a string that identifies your username).
/// The user's user (an IRC construct, a string that identifies your username).
/// </summary>
public string User { get; internal set; }
/// <summary>
/// The user's password. Will not be set on anyone but your own user.
/// The user's password. Will not be set on anyone but your own user.
/// </summary>
public string Password { get; internal set; }
/// <summary>
/// The user's mode.
/// The user's mode.
/// </summary>
/// <value>The mode.</value>
public string Mode { get; internal set; }
/// <summary>
/// The user's real name.
/// The user's real name.
/// </summary>
/// <value>The name of the real.</value>
public string RealName { get; internal set; }
/// <summary>
/// The user's hostname.
/// The user's hostname.
/// </summary>
public string Hostname { get; internal set; }
/// <summary>
/// Channels this user is present in. Note that this only includes channels you are
/// also present in, even after a successful WHOIS.
/// Channels this user is present in. Note that this only includes channels you are
/// also present in, even after a successful WHOIS.
/// </summary>
/// <value>The channels.</value>
public ChannelCollection Channels { get; set; }
/// <summary>
/// The user's account. If 0 or *, the user is not logged in.
/// Otherwise, the user is logged in with services.
/// The user's account. If 0 or *, the user is not logged in.
/// Otherwise, the user is logged in with services.
/// </summary>
public string Account { get; set; }
internal Dictionary<IrcChannel, List<char?>> ChannelModes { get; set; }
/// <summary>
/// This user's hostmask (nick!user@host).
/// This user's hostmask (nick!user@host).
/// </summary>
public string Hostmask
public string Hostmask => Nick + "!" + User + "@" + Hostname;
/// <summary>
/// True if this user is equal to another (compares hostmasks).
/// </summary>
public bool Equals(IrcUser other)
{
get
{
return Nick + "!" + User + "@" + Hostname;
}
return other.Hostmask == Hostmask;
}
/// <summary>
/// Returns true if the user matches the given mask. Can be used to check if a ban applies
/// to this user, for example.
/// Returns true if the user matches the given mask. Can be used to check if a ban applies
/// to this user, for example.
/// </summary>
public bool Match(string mask)
{
@ -130,28 +137,33 @@ namespace ChatSharp
if (Match(parts[0], Nick) && Match(parts[1], User) && Match(parts[2], Hostname))
return true;
}
return false;
}
/// <summary>
/// Checks if the given hostmask matches the given mask.
/// Checks if the given hostmask matches the given mask.
/// </summary>
public static bool Match(string mask, string value)
{
if (value == null)
value = string.Empty;
int i = 0;
int j = 0;
var i = 0;
var j = 0;
for (; j < value.Length && i < mask.Length; j++)
{
if (mask[i] == '?')
{
i++;
}
else if (mask[i] == '*')
{
i++;
if (i >= mask.Length)
return true;
while (++j < value.Length && value[j] != mask[i]) ;
while (++j < value.Length && value[j] != mask[i])
{
}
if (j-- == value.Length)
return false;
}
@ -161,20 +173,12 @@ namespace ChatSharp
return false;
i++;
}
}
return i == mask.Length && j == value.Length;
}
/// <summary>
/// True if this user is equal to another (compares hostmasks).
/// </summary>
public bool Equals(IrcUser other)
{
return other.Hostmask == Hostmask;
}
/// <summary>
/// True if this user is equal to another (compares hostmasks).
/// True if this user is equal to another (compares hostmasks).
/// </summary>
public override bool Equals(object obj)
{
@ -184,7 +188,7 @@ namespace ChatSharp
}
/// <summary>
/// Returns the hash code of the user's hostmask.
/// Returns the hash code of the user's hostmask.
/// </summary>
public override int GetHashCode()
{
@ -192,11 +196,11 @@ namespace ChatSharp
}
/// <summary>
/// Returns the user's hostmask.
/// Returns the user's hostmask.
/// </summary>
public override string ToString()
{
return Hostmask;
}
}
}
}

View File

@ -3,8 +3,8 @@ using System;
namespace ChatSharp
{
/// <summary>
/// A mask that can be used to match against a user's hostmask in a channel list,
/// such as banned users.
/// A mask that can be used to match against a user's hostmask in a channel list,
/// such as banned users.
/// </summary>
public class Mask
{
@ -16,17 +16,19 @@ namespace ChatSharp
}
/// <summary>
/// The user who created this mask.
/// The user who created this mask.
/// </summary>
public IrcUser Creator { get; set; }
/// <summary>
/// The time this mask was added to the channel list.
/// The time this mask was added to the channel list.
/// </summary>
/// <value>The creation time.</value>
public DateTime CreationTime { get; set; }
/// <summary>
/// The mask string.
/// The mask string.
/// </summary>
public string Value { get; set; }
}
}
}

View File

@ -5,83 +5,24 @@ using System.Linq;
namespace ChatSharp
{
/// <summary>
/// A collection of masks from a channel list.
/// A collection of masks from a channel list.
/// </summary>
public class MaskCollection : IEnumerable<Mask>
{
internal MaskCollection()
{
Masks = new List<Mask>();
Masks = new();
}
private List<Mask> Masks { get; set; }
private List<Mask> Masks { get; }
/// <summary>
/// Adds a mask to the collection. This only modifies the local mask list, changes are
/// not flushed to the server.
/// Returns the mask at the requested index.
/// </summary>
public void Add(Mask mask)
{
Masks.Add(mask);
}
public Mask this[int index] => Masks[index];
/// <summary>
/// Removes a mask from the collection. This only modifies the local mask list, changes are
/// not flushed to the server.
/// </summary>
public void Remove(Mask mask)
{
Masks.Remove(mask);
}
/// <summary>
/// True if this collection includes the given mask.
/// </summary>
public bool Contains(Mask mask)
{
return Masks.Contains(mask);
}
/// <summary>
/// True if this collection includes any masks that are equal to the given mask.
/// </summary>
public bool ContainsMask(Mask mask)
{
return Masks.Any(m => m.Value == mask.Value);
}
/// <summary>
/// Returns the mask at the requested index.
/// </summary>
public Mask this[int index]
{
get
{
return Masks[index];
}
}
/// <summary>
/// True if any mask matches the given user.
/// </summary>
public bool ContainsMatch(IrcUser user)
{
return Masks.Any(m => user.Match(m.Value));
}
/// <summary>
/// Returns the mask that matches the given user.
/// </summary>
public Mask GetMatch(IrcUser user)
{
var match = Masks.FirstOrDefault(m => user.Match(m.Value));
if (match == null)
throw new KeyNotFoundException("No mask matches the specified user.");
return match;
}
/// <summary>
/// Enumerates over the masks in this collection.
/// Enumerates over the masks in this collection.
/// </summary>
public IEnumerator<Mask> GetEnumerator()
{
@ -92,5 +33,58 @@ namespace ChatSharp
{
return GetEnumerator();
}
/// <summary>
/// Adds a mask to the collection. This only modifies the local mask list, changes are
/// not flushed to the server.
/// </summary>
public void Add(Mask mask)
{
Masks.Add(mask);
}
/// <summary>
/// Removes a mask from the collection. This only modifies the local mask list, changes are
/// not flushed to the server.
/// </summary>
public void Remove(Mask mask)
{
Masks.Remove(mask);
}
/// <summary>
/// True if this collection includes the given mask.
/// </summary>
public bool Contains(Mask mask)
{
return Masks.Contains(mask);
}
/// <summary>
/// True if this collection includes any masks that are equal to the given mask.
/// </summary>
public bool ContainsMask(Mask mask)
{
return Masks.Any(m => m.Value == mask.Value);
}
/// <summary>
/// True if any mask matches the given user.
/// </summary>
public bool ContainsMatch(IrcUser user)
{
return Masks.Any(m => user.Match(m.Value));
}
/// <summary>
/// Returns the mask that matches the given user.
/// </summary>
public Mask GetMatch(IrcUser user)
{
var match = Masks.FirstOrDefault(m => user.Match(m.Value));
if (match == null)
throw new KeyNotFoundException("No mask matches the specified user.");
return match;
}
}
}
}

View File

@ -3,7 +3,7 @@ using System.Linq;
namespace ChatSharp
{
/// <summary>
/// Represents an IRC message sent from user-to-user or user-to-channel.
/// Represents an IRC message sent from user-to-user or user-to-channel.
/// </summary>
public class PrivateMessage
{
@ -20,20 +20,23 @@ namespace ChatSharp
}
/// <summary>
/// The user that sent this message.
/// The user that sent this message.
/// </summary>
public IrcUser User { get; set; }
/// <summary>
/// The message text.
/// The message text.
/// </summary>
public string Message { get; set; }
/// <summary>
/// The source of the message (a nick or a channel name).
/// The source of the message (a nick or a channel name).
/// </summary>
public string Source { get; set; }
/// <summary>
/// True if this message was posted to a channel.
/// True if this message was posted to a channel.
/// </summary>
public bool IsChannelMessage { get; set; }
}
}
}

View File

@ -8,10 +8,10 @@ namespace ChatSharp
{
public RequestManager()
{
PendingOperations = new Dictionary<string, RequestOperation>();
PendingOperations = new();
}
internal Dictionary<string, RequestOperation> PendingOperations { get; private set; }
internal Dictionary<string, RequestOperation> PendingOperations { get; }
public void QueueOperation(string key, RequestOperation operation)
{
@ -22,7 +22,8 @@ namespace ChatSharp
public RequestOperation PeekOperation(string key)
{
var realKey = PendingOperations.Keys.FirstOrDefault(k => string.Equals(k, key, StringComparison.OrdinalIgnoreCase));
var realKey =
PendingOperations.Keys.FirstOrDefault(k => string.Equals(k, key, StringComparison.OrdinalIgnoreCase));
return PendingOperations[realKey];
}
@ -36,13 +37,13 @@ namespace ChatSharp
internal class RequestOperation
{
public object State { get; set; }
public Action<RequestOperation> Callback { get; set; }
public RequestOperation(object state, Action<RequestOperation> callback)
{
State = state;
Callback = callback;
}
public object State { get; set; }
public Action<RequestOperation> Callback { get; set; }
}
}
}

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace ChatSharp
{
/// <summary>
/// Information provided by the server about its featureset.
/// Information provided by the server about its featureset.
/// </summary>
public class ServerInfo
{
@ -11,13 +11,94 @@ namespace ChatSharp
{
// Guess for some defaults
Prefixes = new[] { "ovhaq", "@+%&~" };
SupportedChannelModes = new ChannelModes();
SupportedChannelModes = new();
IsGuess = true;
ExtendedWho = false;
}
/// <summary>
/// Gets the mode for a given channel user list prefix.
/// ChatSharp makes some assumptions about what the server supports in order to function properly.
/// If it has not received a 005 message giving it accurate information, this value will be true.
/// </summary>
public bool IsGuess { get; internal set; }
/// <summary>
/// Nick prefixes for special modes in channel user lists
/// </summary>
public string[] Prefixes { get; internal set; }
/// <summary>
/// Supported channel prefixes (i.e. '#')
/// </summary>
public char[] ChannelTypes { get; internal set; }
/// <summary>
/// Channel modes supported by this server
/// </summary>
public ChannelModes SupportedChannelModes { get; set; }
/// <summary>
/// The maximum number of MODE changes possible with a single command
/// </summary>
public int? MaxModesPerCommand { get; set; }
/// <summary>
/// The maximum number of channels a user may join
/// </summary>
public int? MaxChannelsPerUser { get; set; } // TODO: Support more than just # channels
/// <summary>
/// Maximum length of user nicks on this server
/// </summary>
public int? MaxNickLength { get; set; }
/// <summary>
/// The limits imposed on list modes, such as +b
/// </summary>
public ModeListLimit[] ModeListLimits { get; set; }
/// <summary>
/// The name of the network, as identified by the server
/// </summary>
public string NetworkName { get; set; }
/// <summary>
/// Set to ban exception character if this server supports ban exceptions
/// </summary>
public char? SupportsBanExceptions { get; set; }
/// <summary>
/// Set to invite exception character if this server supports invite exceptions
/// </summary>
public char? SupportsInviteExceptions { get; set; }
/// <summary>
/// Set to maximum topic length for this server
/// </summary>
public int? MaxTopicLength { get; set; }
/// <summary>
/// Set to the maximum length of a KICK comment
/// </summary>
public int? MaxKickCommentLength { get; set; }
/// <summary>
/// Set to the maximum length of a channel name
/// </summary>
public int? MaxChannelNameLength { get; set; }
/// <summary>
/// Set to the maximum length of an away message
/// </summary>
public int? MaxAwayLength { get; set; }
/// <summary>
/// Server supports WHOX (WHO extension)
/// </summary>
public bool ExtendedWho { get; set; }
/// <summary>
/// Gets the mode for a given channel user list prefix.
/// </summary>
public char? GetModeForPrefix(char prefix)
{
@ -27,8 +108,8 @@ namespace ChatSharp
}
/// <summary>
/// Gets the channel modes for a given user nick.
/// Returns an empty array if user has no modes.
/// Gets the channel modes for a given user nick.
/// Returns an empty array if user has no modes.
/// </summary>
/// <returns></returns>
public List<char?> GetModesForNick(string nick)
@ -37,8 +118,7 @@ namespace ChatSharp
List<char?> modeList = new();
List<char> nickPrefixes = new();
foreach (char prefix in supportedPrefixes)
{
foreach (var prefix in supportedPrefixes)
if (nick.Contains(prefix))
{
_ = nick.Remove(nick.IndexOf(prefix));
@ -50,79 +130,12 @@ namespace ChatSharp
modeList.Add(mode);
}
}
}
return modeList;
}
/// <summary>
/// ChatSharp makes some assumptions about what the server supports in order to function properly.
/// If it has not received a 005 message giving it accurate information, this value will be true.
/// </summary>
public bool IsGuess { get; internal set; }
/// <summary>
/// Nick prefixes for special modes in channel user lists
/// </summary>
public string[] Prefixes { get; internal set; }
/// <summary>
/// Supported channel prefixes (i.e. '#')
/// </summary>
public char[] ChannelTypes { get; internal set; }
/// <summary>
/// Channel modes supported by this server
/// </summary>
public ChannelModes SupportedChannelModes { get; set; }
/// <summary>
/// The maximum number of MODE changes possible with a single command
/// </summary>
public int? MaxModesPerCommand { get; set; }
/// <summary>
/// The maximum number of channels a user may join
/// </summary>
public int? MaxChannelsPerUser { get; set; } // TODO: Support more than just # channels
/// <summary>
/// Maximum length of user nicks on this server
/// </summary>
public int? MaxNickLength { get; set; }
/// <summary>
/// The limits imposed on list modes, such as +b
/// </summary>
public ModeListLimit[] ModeListLimits { get; set; }
/// <summary>
/// The name of the network, as identified by the server
/// </summary>
public string NetworkName { get; set; }
/// <summary>
/// Set to ban exception character if this server supports ban exceptions
/// </summary>
public char? SupportsBanExceptions { get; set; }
/// <summary>
/// Set to invite exception character if this server supports invite exceptions
/// </summary>
public char? SupportsInviteExceptions { get; set; }
/// <summary>
/// Set to maximum topic length for this server
/// </summary>
public int? MaxTopicLength { get; set; }
/// <summary>
/// Set to the maximum length of a KICK comment
/// </summary>
public int? MaxKickCommentLength { get; set; }
/// <summary>
/// Set to the maximum length of a channel name
/// </summary>
public int? MaxChannelNameLength { get; set; }
/// <summary>
/// Set to the maximum length of an away message
/// </summary>
public int? MaxAwayLength { get; set; }
/// <summary>
/// Server supports WHOX (WHO extension)
/// </summary>
public bool ExtendedWho { get; set; }
/// <summary>
/// Modes a server supports that are applicable to channels.
/// Modes a server supports that are applicable to channels.
/// </summary>
public class ChannelModes
{
@ -137,29 +150,33 @@ namespace ChatSharp
}
/// <summary>
/// Modes that are used for lists (i.e. bans).
/// Modes that are used for lists (i.e. bans).
/// </summary>
public string ChannelLists { get; internal set; }
/// <summary>
/// Modes that can be set on a user of a channel (i.e. ops, voice, etc).
/// Modes that can be set on a user of a channel (i.e. ops, voice, etc).
/// </summary>
public string ChannelUserModes { get; set; }
/// <summary>
/// Modes that take a parameter (i.e. +k).
/// Modes that take a parameter (i.e. +k).
/// </summary>
public string ParameterizedSettings { get; internal set; }
/// <summary>
/// Modes that take an optional parameter (i.e. +f).
/// Modes that take an optional parameter (i.e. +f).
/// </summary>
public string OptionallyParameterizedSettings { get; internal set; }
/// <summary>
/// Modes that change channel settings.
/// Modes that change channel settings.
/// </summary>
public string Settings { get; internal set; }
}
/// <summary>
/// Limits imposed on channel lists, such as the maximum bans per channel.
/// Limits imposed on channel lists, such as the maximum bans per channel.
/// </summary>
public class ModeListLimit
{
@ -170,13 +187,14 @@ namespace ChatSharp
}
/// <summary>
/// The mode character this applies to (i.e. 'b')
/// The mode character this applies to (i.e. 'b')
/// </summary>
public char Mode { get; internal set; }
/// <summary>
/// The maximum entries for this list.
/// The maximum entries for this list.
/// </summary>
public int Maximum { get; internal set; }
}
}
}
}

View File

@ -4,31 +4,24 @@ using System.Globalization;
namespace ChatSharp
{
/// <summary>
/// Represents a message timestamp received from a server.
/// Represents a message timestamp received from a server.
/// </summary>
public class Timestamp
{
/// <summary>
/// A date representation of the timestamp.
/// </summary>
public DateTime Date { get; internal set; }
/// <summary>
/// A unix epoch representation of the timestamp.
/// </summary>
public double UnixTimestamp { get; internal set; }
/// <summary>
/// Initializes and parses the timestamp received from the server.
/// Initializes and parses the timestamp received from the server.
/// </summary>
/// <param name="date"></param>
/// <param name="compatibility">Enable pre-ZNC 1.0 compatibility. In previous versions of the tag,
/// servers sent a unix timestamp instead of a ISO 8601 string.</param>
/// <param name="compatibility">
/// Enable pre-ZNC 1.0 compatibility. In previous versions of the tag,
/// servers sent a unix timestamp instead of a ISO 8601 string.
/// </param>
internal Timestamp(string date, bool compatibility = false)
{
if (!compatibility)
{
DateTime parsedDate;
if (!DateTime.TryParse(date, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out parsedDate))
if (!DateTime.TryParse(date, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind,
out var parsedDate))
throw new ArgumentException("The date string was provided in an invalid format.", date);
Date = parsedDate;
@ -36,8 +29,7 @@ namespace ChatSharp
}
else
{
double parsedTimestamp;
if (!double.TryParse(date, out parsedTimestamp))
if (!double.TryParse(date, out var parsedTimestamp))
throw new ArgumentException("The timestamp string was provided in an invalid format.", date);
UnixTimestamp = parsedTimestamp;
@ -46,15 +38,25 @@ namespace ChatSharp
}
/// <summary>
/// True if this timestamp is equal to another (compares unix timestamps).
/// A date representation of the timestamp.
/// </summary>
public DateTime Date { get; }
/// <summary>
/// A unix epoch representation of the timestamp.
/// </summary>
public double UnixTimestamp { get; }
/// <summary>
/// True if this timestamp is equal to another (compares unix timestamps).
/// </summary>
public bool Equals(Timestamp other)
{
return other.UnixTimestamp == UnixTimestamp;
return (int)other.UnixTimestamp == (int)UnixTimestamp;
}
/// <summary>
/// True if this timestamp is equal to another (compares unix timestamps).
/// True if this timestamp is equal to another (compares unix timestamps).
/// </summary>
public override bool Equals(object obj)
{
@ -64,7 +66,7 @@ namespace ChatSharp
}
/// <summary>
/// Returns a ISO 8601 string representation of the timestamp.
/// Returns a ISO 8601 string representation of the timestamp.
/// </summary>
public string ToISOString()
{
@ -72,11 +74,11 @@ namespace ChatSharp
}
/// <summary>
/// Returns the hash code of the unix timestamp.
/// Returns the hash code of the unix timestamp.
/// </summary>
public override int GetHashCode()
{
return UnixTimestamp.GetHashCode();
}
}
}
}

View File

@ -1,24 +1,25 @@
using System.Collections.Generic;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace ChatSharp
{
/// <summary>
/// A pool of users the client is aware of on the network. IrcUser objects in this
/// pool are shared across the entire library (e.g. a PrivateMessage will reuse an
/// IrcUser object from this poll).
/// A pool of users the client is aware of on the network. IrcUser objects in this
/// pool are shared across the entire library (e.g. a PrivateMessage will reuse an
/// IrcUser object from this poll).
/// </summary>
public class UserPool : IEnumerable<IrcUser>
{
private List<IrcUser> Users { get; set; }
internal UserPool()
{
Users = new List<IrcUser>();
Users = new();
}
private List<IrcUser> Users { get; }
/// <summary>
/// Gets the IrcUser with the specified nick.
/// Gets the IrcUser with the specified nick.
/// </summary>
public IrcUser this[string nick]
{
@ -31,6 +32,19 @@ namespace ChatSharp
}
}
/// <summary>
/// Enumerates over the users in this collection.
/// </summary>
public IEnumerator<IrcUser> GetEnumerator()
{
return Users.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
internal void Add(IrcUser user)
{
if (Users.Any(u => u.Hostmask == user.Hostmask))
@ -50,9 +64,9 @@ namespace ChatSharp
}
/// <summary>
/// Returns true if any user in the pool matches this mask. Note that not all users
/// in the user pool will be fully populated, even if you set ClientSettings.WhoIsOnJoin
/// to true (it takes time to whois everyone in your channels).
/// Returns true if any user in the pool matches this mask. Note that not all users
/// in the user pool will be fully populated, even if you set ClientSettings.WhoIsOnJoin
/// to true (it takes time to whois everyone in your channels).
/// </summary>
public bool ContainsMask(string mask)
{
@ -60,7 +74,7 @@ namespace ChatSharp
}
/// <summary>
/// Returns true if any user in the pool has the specified nick.
/// Returns true if any user in the pool has the specified nick.
/// </summary>
public bool Contains(string nick)
{
@ -68,7 +82,7 @@ namespace ChatSharp
}
/// <summary>
/// Returns true if the given IrcUser is in the pool.
/// Returns true if the given IrcUser is in the pool.
/// </summary>
public bool Contains(IrcUser user)
{
@ -87,6 +101,7 @@ namespace ChatSharp
ret.Hostname = user.Hostname;
return ret;
}
Add(user);
return user;
}
@ -98,18 +113,5 @@ namespace ChatSharp
return this[user.Nick];
throw new KeyNotFoundException();
}
/// <summary>
/// Enumerates over the users in this collection.
/// </summary>
public IEnumerator<IrcUser> GetEnumerator()
{
return Users.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -1,22 +1,23 @@
using System.Collections.Generic;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace ChatSharp
{
/// <summary>
/// A filtered view of the user pool.
/// A filtered view of the user pool.
/// </summary>
public class UserPoolView : IEnumerable<IrcUser>
{
private IEnumerable<IrcUser> Users { get; set; }
internal UserPoolView(IEnumerable<IrcUser> users)
{
Users = users;
}
private IEnumerable<IrcUser> Users { get; }
/// <summary>
/// Gets the IrcUser with the specified nick.
/// Gets the IrcUser with the specified nick.
/// </summary>
public IrcUser this[string nick]
{
@ -29,18 +30,25 @@ namespace ChatSharp
}
}
internal IrcUser this[int index]
internal IrcUser this[int index] => Users.ToList()[index];
/// <summary>
/// Enumerates over the users in this collection (with the filter applied).
/// </summary>
public IEnumerator<IrcUser> GetEnumerator()
{
get
{
return Users.ToList()[index];
}
return Users.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Returns true if any user in the pool matches this mask. Note that not all users
/// in the user pool will be fully populated, even if you set ClientSettings.WhoIsOnJoin
/// to true (it takes time to whois everyone in your channels).
/// Returns true if any user in the pool matches this mask. Note that not all users
/// in the user pool will be fully populated, even if you set ClientSettings.WhoIsOnJoin
/// to true (it takes time to whois everyone in your channels).
/// </summary>
public bool ContainsMask(string mask)
{
@ -48,7 +56,7 @@ namespace ChatSharp
}
/// <summary>
/// Returns true if any user in the pool has the specified nick.
/// Returns true if any user in the pool has the specified nick.
/// </summary>
public bool Contains(string nick)
{
@ -56,25 +64,11 @@ namespace ChatSharp
}
/// <summary>
/// Returns true if the given IrcUser is in the pool.
/// Returns true if the given IrcUser is in the pool.
/// </summary>
public bool Contains(IrcUser user)
{
return Users.Any(u => u.Hostmask == user.Hostmask);
}
/// <summary>
/// Enumerates over the users in this collection (with the filter applied).
/// </summary>
public IEnumerator<IrcUser> GetEnumerator()
{
return Users.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
}
}

View File

@ -1,47 +1,55 @@
using System;
namespace ChatSharp
{
/// <summary>
/// The results of an IRC WHOIS query. Depending on the capabilities of the server you're connected to,
/// some of these fields may be null.
/// The results of an IRC WHOIS query. Depending on the capabilities of the server you're connected to,
/// some of these fields may be null.
/// </summary>
public class WhoIs
{
internal WhoIs()
{
User = new IrcUser();
User = new();
SecondsIdle = -1;
Channels = System.Array.Empty<string>();
Channels = Array.Empty<string>();
}
/// <summary>
/// A fully populated IrcUser, including hostname, real name, etc.
/// A fully populated IrcUser, including hostname, real name, etc.
/// </summary>
public IrcUser User { get; set; }
/// <summary>
/// A list of channels this user is joined to. Depending on the IRC network you connect to,
/// this may omit channels that you are not present in.
/// A list of channels this user is joined to. Depending on the IRC network you connect to,
/// this may omit channels that you are not present in.
/// </summary>
public string[] Channels { get; set; }
/// <summary>
/// If true, the whois'd user is a network operator.
/// If true, the whois'd user is a network operator.
/// </summary>
public bool IrcOp { get; set; }
/// <summary>
/// Seconds since this user last interacted with IRC.
/// Seconds since this user last interacted with IRC.
/// </summary>
public int SecondsIdle { get; set; }
/// <summary>
/// The server this user is connected to.
/// The server this user is connected to.
/// </summary>
public string Server { get; set; }
/// <summary>
/// Additional information about the server this user is connected to.
/// Additional information about the server this user is connected to.
/// </summary>
/// <value>The server info.</value>
public string ServerInfo { get; set; }
/// <summary>
/// The nickserv account this user is logged into, if applicable.
/// The nickserv account this user is logged into, if applicable.
/// </summary>
public string LoggedInAs { get; set; }
}
}
}

View File

@ -1,9 +1,10 @@
using System;
namespace ChatSharp
{
/// <summary>
/// The results of an IRC WHO (WHOX protocol) query. Depending on what information you request,
/// some of these fields may be null.
/// The results of an IRC WHO (WHOX protocol) query. Depending on what information you request,
/// some of these fields may be null.
/// </summary>
public class ExtendedWho
{
@ -11,7 +12,7 @@ namespace ChatSharp
{
QueryType = -1;
Channel = "*";
User = new IrcUser();
User = new();
IP = string.Empty;
Server = string.Empty;
Flags = string.Empty;
@ -21,166 +22,199 @@ namespace ChatSharp
}
/// <summary>
/// Type of the query. Defaults to a randomly generated number so ChatSharp can keep
/// track of WHOX queries it issues.
/// Type of the query. Defaults to a randomly generated number so ChatSharp can keep
/// track of WHOX queries it issues.
/// </summary>
public int QueryType { get; internal set; }
/// <summary>
/// Channel name
/// Channel name
/// </summary>
public string Channel { get; internal set; }
/// <summary>
/// User
/// User
/// </summary>
public IrcUser User { get; internal set; }
/// <summary>
/// Numeric IP address of the user (unresolved hostname)
/// Numeric IP address of the user (unresolved hostname)
/// </summary>
public string IP { get; internal set; }
/// <summary>
/// Server name
/// Server name
/// </summary>
public string Server { get; internal set; }
/// <summary>
/// User flags
/// User flags
/// </summary>
public string Flags { get; internal set; }
/// <summary>
/// Distance, in hops
/// Distance, in hops
/// </summary>
public int Hops { get; internal set; }
/// <summary>
/// Time the user has been idle for
/// Time the user has been idle for
/// </summary>
public int TimeIdle { get; internal set; }
/// <summary>
/// OP level of the user in the channel
/// OP level of the user in the channel
/// </summary>
public string OpLevel { get; internal set; }
}
/// <summary>
/// Field matching flags for WHOX protocol.
/// Field matching flags for WHOX protocol.
/// </summary>
[Flags]
public enum WhoxFlag
{
/// <summary>
/// Do not match any flag at all. By doing so, ircds defaults to 'nuhsr'
/// (everything except the numeric IP).
/// Do not match any flag at all. By doing so, ircds defaults to 'nuhsr'
/// (everything except the numeric IP).
/// </summary>
None = 0,
/// <summary>
/// Matches nick (in nick!user@host)
/// Matches nick (in nick!user@host)
/// </summary>
Nick = 1,
/// <summary>
/// Matches username (in nick!user@host)
/// Matches username (in nick!user@host)
/// </summary>
Username = 2,
/// <summary>
/// Matches hostname (in nick!user@host)
/// Matches hostname (in nick!user@host)
/// </summary>
Hostname = 4,
/// <summary>
/// Matches numeric IPs
/// Matches numeric IPs
/// </summary>
NumericIp = 8,
/// <summary>
/// Matches server name
/// Matches server name
/// </summary>
ServerName = 16,
/// <summary>
/// Matches informational text
/// Matches informational text
/// </summary>
Info = 32,
/// <summary>
/// Matches account name
/// Matches account name
/// </summary>
AccountName = 64,
/// <summary>
/// Matches visible and invisble users in a channel
/// Matches visible and invisble users in a channel
/// </summary>
DelayedChanMembers = 128,
/// <summary>
/// Matches IRC operators
/// Matches IRC operators
/// </summary>
IrcOp = 256,
/// <summary>
/// Special purpose flag, normally only IRC ops have access to it.
/// Special purpose flag, normally only IRC ops have access to it.
/// </summary>
Special = 512,
/// <summary>
/// Matches all of the flags defined.
/// Matches all of the flags defined.
/// </summary>
All = ~0
}
/// <summary>
/// Information fields for WHOX protocol.
/// Information fields for WHOX protocol.
/// </summary>
[Flags]
public enum WhoxField
{
/// <summary>
/// Do not include any field at all.
/// By doing so, ircds defaults to sending a normal WHO reply.
/// Do not include any field at all.
/// By doing so, ircds defaults to sending a normal WHO reply.
/// </summary>
None = 0,
/// <summary>
/// Includes the querytype in the reply
/// Includes the querytype in the reply
/// </summary>
QueryType = 1,
/// <summary>
/// Includes the first channel name
/// Includes the first channel name
/// </summary>
Channel = 2,
/// <summary>
/// Includes the userID (username)
/// Includes the userID (username)
/// </summary>
Username = 4,
/// <summary>
/// Includes the IP
/// Includes the IP
/// </summary>
UserIp = 8,
/// <summary>
/// Includes the user's hostname
/// Includes the user's hostname
/// </summary>
Hostname = 16,
/// <summary>
/// Includes the server name
/// Includes the server name
/// </summary>
ServerName = 32,
/// <summary>
/// Includes the user's nick
/// Includes the user's nick
/// </summary>
Nick = 64,
/// <summary>
/// Includes all flags a user has
/// Includes all flags a user has
/// </summary>
Flags = 128,
/// <summary>
/// Includes the "distance" in hops
/// Includes the "distance" in hops
/// </summary>
Hops = 256,
/// <summary>
/// Includes the idle time (0 for remote users)
/// Includes the idle time (0 for remote users)
/// </summary>
TimeIdle = 512,
/// <summary>
/// Includes the user's account name
/// Includes the user's account name
/// </summary>
AccountName = 1024,
/// <summary>
/// Includes the user's op level in the channel
/// Includes the user's op level in the channel
/// </summary>
OpLevel = 2048,
/// <summary>
/// Includes the user's real name
/// Includes the user's real name
/// </summary>
RealName = 4096,
/// <summary>
/// Includes all fields defined
/// Includes all fields defined
/// </summary>
All = ~0
}
@ -254,7 +288,6 @@ namespace ChatSharp
result = string.Empty;
return result;
}
}
}
}

View File

@ -1,17 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ChatSharp\ChatSharp.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" />
<PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.3.246501">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="MSTest.TestFramework" Version="2.2.7" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<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"/>
</ItemGroup>
</Project>

View File

@ -1,6 +1,6 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ChatSharp.Tests
{
@ -12,7 +12,7 @@ namespace ChatSharp.Tests
{
try
{
IrcMessage fromMessage = new(@":user!~ident@host PRIVMSG target :Lorem ipsum dolor sit amet");
_ = new IrcMessage(@":user!~ident@host PRIVMSG target :Lorem ipsum dolor sit amet");
}
catch (Exception e)
{
@ -38,15 +38,16 @@ namespace ChatSharp.Tests
public void NewValidMessage_Params()
{
IrcMessage fromMessage = new(@":user!~ident@host PRIVMSG target :Lorem ipsum dolor sit amet");
string[] compareParams = new string[] { "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");
KeyValuePair<string, string>[] compareTags = new KeyValuePair<string, string>[]
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"),
@ -59,11 +60,11 @@ namespace ChatSharp.Tests
public void NewValidMessage_Tags02()
{
IrcMessage fromMessage = new("@aaa=bbb;ccc;example.com/ddd=eee :nick!ident@host.com PRIVMSG me :Hello");
KeyValuePair<string, string>[] compareTags = new KeyValuePair<string, string>[]
var compareTags = new[]
{
new KeyValuePair<string, string>("aaa", "bbb"),
new KeyValuePair<string, string>("ccc", null),
new KeyValuePair<string, string>("example.com/ddd", "eee"),
new KeyValuePair<string, string>("example.com/ddd", "eee")
};
CollectionAssert.AreEqual(fromMessage.Tags, compareTags);
}
@ -71,12 +72,13 @@ namespace ChatSharp.Tests
[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");
KeyValuePair<string, string>[] compareTags = new KeyValuePair<string, string>[]
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"),
new KeyValuePair<string, string>("c", "789;123")
};
CollectionAssert.AreEqual(fromMessage.Tags, compareTags);
}
@ -85,10 +87,10 @@ namespace ChatSharp.Tests
public void NewValidMessage_TagsNoValue()
{
IrcMessage fromMessage = new("@a=;b :nick!ident@host.com PRIVMSG me :Hello");
KeyValuePair<string, string>[] compareTags = new KeyValuePair<string, string>[]
var compareTags = new[]
{
new KeyValuePair<string, string>("a", ""),
new KeyValuePair<string, string>("b", null),
new KeyValuePair<string, string>("b", null)
};
CollectionAssert.AreEqual(fromMessage.Tags, compareTags);
}
@ -96,12 +98,14 @@ namespace ChatSharp.Tests
[TestMethod]
public void Timestamp_CompareISOString()
{
IrcMessage[] messages = {
new IrcMessage("@time=2011-10-19T16:40:51.620Z :Angel!angel@example.org PRIVMSG Wiz :Hello"),
new IrcMessage("@time=2012-06-30T23:59:59.419Z :John!~john@1.2.3.4 JOIN #chan")
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 = {
string[] timestamps =
{
"2011-10-19T16:40:51.620Z",
"2012-06-30T23:59:59.419Z"
};
@ -113,12 +117,14 @@ namespace ChatSharp.Tests
[TestMethod]
public void Timestamp_FromTimestamp()
{
IrcMessage[] messages = {
new IrcMessage("@t=1504923966 :Angel!angel@example.org PRIVMSG Wiz :Hello"),
new IrcMessage("@t=1504923972 :John!~john@1.2.3.4 JOIN #chan")
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 = {
string[] timestamps =
{
"2017-09-09T02:26:06.000Z",
"2017-09-09T02:26:12.000Z"
};
@ -130,7 +136,8 @@ namespace ChatSharp.Tests
[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.ThrowsException<ArgumentException>(() =>
new IrcMessage("@time=2012-06-30T23:59:60.419Z :John!~john@1.2.3.4 JOIN #chan"));
}
}
}
}

View File

@ -71,4 +71,4 @@ namespace ChatSharp.Tests
Assert.IsTrue(userModes.Count == 0);
}
}
}
}

View File

@ -16,4 +16,4 @@ using System.Runtime.InteropServices;
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -42,13 +42,11 @@ On Linux/Mac:
On Windows, use Visual Studio or similar and build in Release mode.
Regardless of platform, you'll receive binaries in `ChatSharp/bin/Release/`.
ChatSharp has no dependencies.
Regardless of platform, you'll receive binaries in `ChatSharp/bin/Release/`. ChatSharp has no dependencies.
## Support
Open a [Github issue](https://github.com/SirCmpwn/ChatSharp/issues) describing
your problem.
Open a [Github issue](https://github.com/SirCmpwn/ChatSharp/issues) describing your problem.
## Development / Contributing