Browse Source

Initial Commit of IRC Library

master
Teknikode 3 years ago
parent
commit
e8988d4318

+ 22
- 0
IRCSharp.sln View File

@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IRCSharp", "IRCSharp\IRCSharp.csproj", "{65FCBF1C-8C9E-4688-BECC-185D9030899F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{65FCBF1C-8C9E-4688-BECC-185D9030899F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65FCBF1C-8C9E-4688-BECC-185D9030899F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65FCBF1C-8C9E-4688-BECC-185D9030899F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65FCBF1C-8C9E-4688-BECC-185D9030899F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

+ 143
- 0
IRCSharp/Channel.cs View File

@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;

namespace IRCSharp
{
public class Channel
{
public string Name { get; set; }
public string Topic { get; set; }
public string Key { get; set; }
public DateTime Registration { get; set; }
public List<string> Bans { get; set; }
public List<ChannelMode> Modes { get; set; }
public List<Nick> Nicks { get; set; }

public Channel()
{
Name = string.Empty;
Topic = string.Empty;
Key = string.Empty;
Registration = DateTime.Now;
Bans = new List<string>();
Modes = new List<ChannelMode>();
Nicks = new List<Nick>();
}

public void AddNick(Nick nick)
{
Nicks.Add(nick);
}

public void AddNicks(List<Nick> nicks)
{
Nicks.AddRange(nicks);
}

public void RemoveNick(Nick nick)
{
if (Nicks.Contains(nick))
{
Nicks.Remove(nick);
}
}

public void RemoveNick(string nickname)
{
if (Nicks.Exists(nick => nick.Nickname == nickname))
{
Nicks.Remove(Nicks.Find(nick => nick.Nickname == nickname));
}
}

public void RemoveNicks(List<Nick> nicks)
{
foreach (Nick nick in nicks)
{
RemoveNick(nick);
}
}

public void RemoveNicks(List<string> nicks)
{
foreach (string nick in nicks)
{
RemoveNick(nick);
}
}

public Nick GetNick(string nickname)
{
Nick foundNick = Nicks.Find(nick => nick.Nickname == nickname);
return foundNick;
}

public List<Nick> GetNicks(List<string> nicknames)
{
List<Nick> foundNicks = new List<Nick>();
foreach (string nickname in nicknames)
{
Nick foundNick = GetNick(nickname);
if (foundNick != null)
{
foundNicks.Add(foundNick);
}
}
return foundNicks;
}

public void AddMode(ChannelMode mode)
{
if (!Modes.Contains(mode))
{
Modes.Add(mode);
}
}

public void AddModes(List<ChannelMode> modes)
{
foreach (ChannelMode mode in modes)
{
AddMode(mode);
}
}

public void RemoveMode(ChannelMode mode)
{
if (Modes.Contains(mode))
{
Modes.Remove(mode);
}
}

public void RemoveModes(List<ChannelMode> modes)
{
foreach (ChannelMode mode in modes)
{
RemoveMode(mode);
}
}

public void AddBan(string mask)
{
if (!Bans.Contains(mask))
{
Bans.Add(mask);
}
}

public void RemoveBan(string mask)
{
Bans.Remove(mask);
}

public static bool IsChannel(string channel)
{
if (channel.StartsWith("&") || channel.StartsWith("#"))
{
return true;
}
return false;
}
}
}

+ 459
- 0
IRCSharp/Commanding/CommandTypes.cs View File

@@ -0,0 +1,459 @@
using System;
using System.Collections.Generic;

namespace IRCSharp.Commanding
{
abstract public class ICommand : EventArgs
{
public DateTime TimeStamp { get; set; }

public ICommand()
{
TimeStamp = DateTime.Now;
}
}

public class PrivateMessageCommand : ICommand
{
public string Recipient { get; set; }
public string Message { get; set; }

public PrivateMessageCommand()
{
Recipient = string.Empty;
Message = string.Empty;
}
}

public class PrivateNoticeCommand : ICommand
{
public string Recipient { get; set; }
public string Message { get; set; }

public PrivateNoticeCommand()
{
Recipient = string.Empty;
Message = string.Empty;
}
}

public class CTCPMessageCommand : ICommand
{
public string Recipient { get; set; }
public string Command { get; set; }
public string Arguments { get; set; }

public CTCPMessageCommand()
{
Recipient = string.Empty;
Command = string.Empty;
Arguments = string.Empty;
}
}

public class CTCPNoticeCommand : ICommand
{
public string Recipient { get; set; }
public string Command { get; set; }
public string Arguments { get; set; }

public CTCPNoticeCommand()
{
Recipient = string.Empty;
Command = string.Empty;
Arguments = string.Empty;
}
}

public class PasswordCommand : ICommand
{
public string Password { get; set; }

public PasswordCommand()
{
Password = string.Empty;
}
}

public class NickCommand : ICommand
{
public string Nick { get; set; }

public NickCommand()
{
Nick = string.Empty;
}
}

public class UserCommand : ICommand
{
public string Username { get; set; }
public string Hostname { get; set; }
public string Servername { get; set; }
public string Realname { get; set; }

public UserCommand()
{
Username = string.Empty;
Hostname = string.Empty;
Hostname = string.Empty;
Realname = string.Empty;
}
}

public class OperCommand : ICommand
{
public string Username { get; set; }
public string Password { get; set; }

public OperCommand()
{
Username = string.Empty;
Password = string.Empty;
}
}

public class QuitCommand : ICommand
{
public string Message { get; set; }

public QuitCommand()
{
Message = string.Empty;
}
}

public class JoinCommand : ICommand
{
public string Channel { get; set; }
public string Key { get; set; }

public JoinCommand()
{
Channel = string.Empty;
Key = string.Empty;
}
}

public class PartCommand : ICommand
{
public string Channel { get; set; }

public PartCommand()
{
Channel = string.Empty;
}
}

public class ChannelModeCommand : ICommand
{
public string Channel { get; set; }
public ChannelModeInfo Mode { get; set; }

public ChannelModeCommand()
{
Channel = string.Empty;
Mode = new ChannelModeInfo();
}
}

public class UserModeCommand : ICommand
{
public string Nick { get; set; }
public UserModeInfo Mode { get; set; }

public UserModeCommand()
{
Nick = string.Empty;
Mode = new UserModeInfo();
}
}

public class TopicCommand : ICommand
{
public string Channel { get; set; }
public string Topic { get; set; }

public TopicCommand()
{
Channel = string.Empty;
Topic = string.Empty;
}
}

public class NamesCommand : ICommand
{
public string Channel { get; set; }

public NamesCommand()
{
Channel = string.Empty;
}
}

public class ListCommand : ICommand
{
public string Channel { get; set; }

public ListCommand()
{
Channel = string.Empty;
}
}

public class InviteCommand : ICommand
{
public string Channel { get; set; }
public string Nick { get; set; }

public InviteCommand()
{
Channel = string.Empty;
Nick = string.Empty;
}
}

public class KickCommand : ICommand
{
public string Channel { get; set; }
public string Nick { get; set; }
public string Reason { get; set; }

public KickCommand()
{
Channel = string.Empty;
Nick = string.Empty;
Reason = string.Empty;
}
}

public class VersionCommand : ICommand
{
public string Server { get; set; }

public VersionCommand()
{
Server = string.Empty;
}
}

public class StatsCommand : ICommand
{
public string Stat { get; set; }
public string Parameter { get; set; }

public StatsCommand()
{
Stat = string.Empty;
Parameter = string.Empty;
}
}

public class LinksCommand : ICommand
{
public string Mask { get; set; }
public string Server { get; set; }

public LinksCommand()
{
Mask = string.Empty;
Server = string.Empty;
}
}

public class TimeCommand : ICommand
{
public string Server { get; set; }

public TimeCommand()
{
Server = string.Empty;
}
}

public class ConnectCommand : ICommand
{
public string Originator { get; set; }
public int Port { get; set; }
public string Server { get; set; }

public ConnectCommand()
{
Originator = string.Empty;
Port = 0;
Server = string.Empty;
}
}

public class TraceCommand : ICommand
{
public string Target { get; set; }

public TraceCommand()
{
Target = string.Empty;
}
}

public class AdminCommand : ICommand
{
public string Host { get; set; }

public AdminCommand()
{
Host = string.Empty;
}
}

public class InfoCommand : ICommand
{
public string Host { get; set; }

public InfoCommand()
{
Host = string.Empty;
}
}

public class WhoCommand : ICommand
{
public string Host { get; set; }

public WhoCommand()
{
Host = string.Empty;
}
}

public class WhoisCommand : ICommand
{
public string Server { get; set; }
public string Nick { get; set; }

public WhoisCommand()
{
Server = string.Empty;
Nick = string.Empty;
}
}

public class WhowasCommand : ICommand
{
public string Nick { get; set; }
public int Entries { get; set; }
public string Server { get; set; }

public WhowasCommand()
{
Nick = string.Empty;
Entries = 0;
Server = string.Empty;
}
}

public class KillCommand : ICommand
{
public string Nick { get; set; }
public string Comment { get; set; }

public KillCommand()
{
Nick = string.Empty;
Comment = string.Empty;
}
}

public class PingCommand : ICommand
{
public string Recipient { get; set; }

public PingCommand()
{
Recipient = string.Empty;
}
}

public class PongCommand : ICommand
{
public string Sender { get; set; }
public string Recipient { get; set; }

public PongCommand()
{
Sender = string.Empty;
Recipient = string.Empty;
}
}

public class AwayCommand : ICommand
{
public string Message { get; set; }

public AwayCommand()
{
Message = string.Empty;
}
}

public class RehashCommand : ICommand
{
public RehashCommand() { }
}

public class RestartCommand : ICommand
{
public RestartCommand() { }
}

public class SummonCommand : ICommand
{
public string Nick { get; set; }
public string Host { get; set; }

public SummonCommand()
{
Nick = string.Empty;
Host = string.Empty;
}
}

public class UsersCommand : ICommand
{
public string Server { get; set; }

public UsersCommand()
{
Server = string.Empty;
}
}

public class WallopsCommand : ICommand
{
public string Message { get; set; }

public WallopsCommand()
{
Message = string.Empty;
}
}

public class UserhostCommand : ICommand
{
public string Nicks { get; set; }

public UserhostCommand()
{
Nicks = string.Empty;
}
}

public class IsonCommand : ICommand
{
public string Nicks { get; set; }

public IsonCommand()
{
Nicks = string.Empty;
}
}
}

+ 1039
- 0
IRCSharp/Commanding/Commands.cs
File diff suppressed because it is too large
View File


+ 62
- 0
IRCSharp/Helper.cs View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IRCSharp
{
public static class Helper
{
public static string ModeToString(this UserModeInfo mode)
{
return string.Format("{0}{1}", (mode.Set) ? "+" : "-", mode.Mode.ToString());
}

public static string ModesToString(this List<UserModeInfo> modes)
{
string modeStr = string.Empty;
bool curSet = true;
foreach (UserModeInfo mode in modes)
{
bool addSet = false;
if (curSet != mode.Set)
addSet = true;
modeStr += ((addSet) ? ((curSet) ? "+" : "-") : string.Empty) + mode.Mode.ToString();
curSet = mode.Set;
}
return modeStr;
}

public static string ModeToString(this ChannelModeInfo mode)
{
string param = string.Empty;
if (!string.IsNullOrEmpty(mode.Parameter))
param = " " + mode.Parameter;
return string.Format("{0}{1}{2}", (mode.Set) ? "+" : "-", mode.Mode.ToString(), param);
}

public static string ModesToString(this List<ChannelModeInfo> modes)
{
string modeStr = string.Empty;
bool curSet = true;
bool addSet = true;
foreach (ChannelModeInfo mode in modes)
{
modeStr += ((addSet) ? ((mode.Set) ? "+" : "-") : string.Empty) + mode.Mode.ToString();
addSet = (curSet == mode.Set);
curSet = mode.Set;
}
string param = string.Empty;
foreach (ChannelModeInfo mode in modes)
{
if (!string.IsNullOrEmpty(mode.Parameter))
param += " " + mode.Parameter;
}

modeStr += param;

return modeStr;
}
}
}

+ 714
- 0
IRCSharp/IRC.cs View File

@@ -0,0 +1,714 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using IRCSharp.Messaging;
using IRCSharp.Commanding;
using IRCSharp.TCP;

namespace IRCSharp
{
public partial class IRC
{
public List<Channel> Channels = new List<Channel>();
public Messages Message;
public Commands Command;
public event Action ConnectEvent;
public event Action DisconnectEvent;
public event Action<TCPError> TCPErrorEvent;
public event Action<Exception> ExceptionThrown;
public string Nickname;
public Dictionary<string, PrivilegeMode> PrivilegeMapping = new Dictionary<string, PrivilegeMode>() { { "+", PrivilegeMode.v }, { "%", PrivilegeMode.h }, { "@", PrivilegeMode.o }, { "&", PrivilegeMode.a }, { "~", PrivilegeMode.q }, { "!", PrivilegeMode.q } };

private int ReadTimeout;
private int AllowedFailedReads;
private int MessageSendDelay;
private Thread TCPReader;
private Thread KeepAlive;
private DateTime LastMessageSend;
private event Action<string> TCPMessageEvent;
private readonly TCPInterface _TCP;
private readonly ReaderWriterLockSlim ChannelRWLock;
private readonly ReaderWriterLockSlim MessageSendLock;

public IRC(int maxMessageLength, int messageSendDelay = 0, int readTimeout = 5000, int allowedFailedReads = 0)
{
Nickname = string.Empty;
ChannelRWLock = new ReaderWriterLockSlim();
MessageSendLock = new ReaderWriterLockSlim();
ReadTimeout = readTimeout;
AllowedFailedReads = allowedFailedReads;
LastMessageSend = DateTime.Now;
MessageSendDelay = messageSendDelay;

_TCP = new TCPInterface();
Message = new Messages(this);
Command = new Commands(this, maxMessageLength, messageSendDelay);

TCPMessageEvent += Message.ParseTCPMessage;
_TCP.TCPConnectionEvent += HandleTCPConnection;
_TCP.TCPErrorEvent += HandleTCPError;
Message.ErrorMessageEvent += HandleErrorMessage;
Message.PingEvent += HandlePing;
Message.ServerReplyEvent += HandleReply;
Message.ChannelModeChangeEvent += HandleChannelModeChange;
Message.UserModeChangeEvent += HandleUserModeChange;
Message.NickChangeEvent += HandleNickChange;
Message.JoinChannelEvent += HandleJoin;
Message.PartChannelEvent += HandlePart;
Message.KickEvent += HandleKick;
Message.QuitEvent += HandleQuit;
}

/// <summary>
/// Starts a TCP connection to the specified host.
/// </summary>
/// <param name="IP">The IP address of the host.</param>
/// <param name="port">The port for the tcp connection.</param>
/// <param name="readTimeout">The timeout for read operations in milliseconds.</param>
/// <param name="allowedFailedCount">Number of times a read can fail before disconnecting.</param>
/// <returns></returns>
public bool Connect(IPAddress IP, int port)
{
bool result = false;
try
{
if (!_TCP.Connected)
{
result = _TCP.Connect(IP, port, ReadTimeout, AllowedFailedReads);
if (result)
{
TCPReader = new Thread(ReadTCPMessages);
TCPReader.IsBackground = true;
TCPReader.Start();

KeepAlive = new Thread(() => CheckConnection(IP, port));
KeepAlive.IsBackground = true;
KeepAlive.Start();

if (ConnectEvent != null)
{
ConnectEvent();
}
}
}
}
catch(Exception ex)
{
ThrowException(ex);
}
return result;
}

/// <summary>
/// Disconencts from the active TCP connection.
/// </summary>
/// <returns></returns>
public void Disconnect()
{
try
{
if (_TCP.Connected)
{
_TCP.Disconnect();
}

if (KeepAlive.IsAlive)
{
KeepAlive.Join();
}

if (TCPReader.IsAlive)
{
TCPReader.Join();
}

ChannelRWLock.EnterWriteLock();
Channels = new List<Channel>();
ChannelRWLock.ExitWriteLock();

if (DisconnectEvent != null)
{
DisconnectEvent();
}
}
catch (Exception ex)
{
ThrowException(ex, "Disconnect Exception");
}
}

/// <summary>
/// Logs in the specified nick using their Username and Realname.
/// </summary>
/// <param name="serverName">The server's name.</param>
/// <param name="nick">The nick information for the login.</param>
public void Login(string serverName, Nick nick)
{
try
{
Nickname = nick.Nickname;
Command.SendNick(nick.Nickname);
Command.SendUser(nick.Username, nick.Host, serverName, nick.Realname);
}
catch (Exception ex)
{
ThrowException(ex, "Login Exception.");
}
}

/// <summary>
/// Parses a given mode and parameter string to generate a channel mode list.
/// </summary>
/// <param name="modeString">The mode string that contains the mode info.</param>
/// <param name="parameterString">The parameter string that is associated with the mode info.</param>
/// <returns></returns>
public List<ChannelModeInfo> ParseChannelModeString(string modeString, string parameterString)
{
List<ChannelModeInfo> modeInfos = new List<ChannelModeInfo>();
try
{
string[] modeArgs = parameterString.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
char[] modeInfo = modeString.ToCharArray();
bool set = true;
int argIndex = 0;
foreach (char mode in modeInfo)
{
if (mode.Equals('-'))
{
set = false;
}
else if (mode.Equals('+'))
{
set = true;
}
else
{
ChannelModeInfo newMode = new ChannelModeInfo();
newMode.Set = set;
ChannelMode foundMode;
bool validMode = Enum.TryParse(mode.ToString(), false, out foundMode);
if (validMode)
{
newMode.Mode = foundMode;
if (modeArgs.GetUpperBound(0) >= argIndex)
{
switch (newMode.Mode)
{
case ChannelMode.k:
case ChannelMode.l:
case ChannelMode.b:
case ChannelMode.e:
case ChannelMode.I:
case ChannelMode.v:
case ChannelMode.h:
case ChannelMode.o:
case ChannelMode.a:
case ChannelMode.q:
newMode.Parameter = modeArgs[argIndex];
argIndex++;
break;
default:
newMode.Parameter = string.Empty;
break;
}
}
else
{
newMode.Parameter = string.Empty;
}
modeInfos.Add(newMode);
}
}
}
}
catch (Exception ex)
{
ThrowException(ex, "Unable to parse Channel Mode.");
}
return modeInfos;
}

public List<UserModeInfo> ParseUserModeString(string modeString)
{
List<UserModeInfo> userModes = new List<UserModeInfo>();
try
{
bool set = true;
char[] modeArr = modeString.ToCharArray();
for (int i = 1; i <= modeArr.GetUpperBound(0); i++)
{
UserModeInfo newMode = new UserModeInfo();
if (modeArr[i].Equals('-'))
{
set = false;
}
else if (modeArr[i].Equals('+'))
{
set = true;
}
else if (modeArr[i].Equals('*'))
{
newMode.Mode = UserMode.o;
newMode.Set = set;
userModes.Add(newMode);
}
else
{
UserMode foundMode;
bool validMode = Enum.TryParse(modeArr[i].ToString(), false, out foundMode);
if (validMode)
{
newMode.Mode = foundMode;
newMode.Set = set;
userModes.Add(newMode);
}
}
}
}
catch (Exception ex)
{
ThrowException(ex, "Unable to parse User Mode.");
}
return userModes;
}

private void ReadTCPMessages()
{
while (_TCP.Connected)
{
string response = ReadTCPMessage();
if (TCPMessageEvent != null && response != null && response != string.Empty)
{
TCPMessageEvent(response);
}
}
}

private string ReadTCPMessage()
{
if (_TCP.Connected)
{
return _TCP.Read();
}
return null;
}

internal void SendTCPMessage(string message)
{
if (_TCP.Connected)
{
MessageSendLock.EnterWriteLock();
TimeSpan sinceLastMessage = (DateTime.Now - LastMessageSend);
if (sinceLastMessage.TotalMilliseconds < MessageSendDelay)
{
Thread.Sleep((int)(MessageSendDelay - sinceLastMessage.TotalMilliseconds));
}
LastMessageSend = DateTime.Now;
string replaceWith = string.Empty;
string parsedMessage = message.Replace("\r\n", replaceWith).Replace("\n", replaceWith).Replace("\r", replaceWith);
_TCP.Write(parsedMessage);
MessageSendLock.ExitWriteLock();
}
}

private void CheckConnection(IPAddress IP, int port)
{
int diconnectCount = 0;
bool disconnectActivated = false;
while (_TCP.Connected)
{
Thread.Sleep(5000);
bool stillConnected = NetworkInterface.GetIsNetworkAvailable();

if (stillConnected)
{
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
s.Connect(IP, port);
}
catch
{
stillConnected = false;
}
}

if (!stillConnected)
{
diconnectCount++;
}
else
{
diconnectCount = 0;
}

if (diconnectCount >= 5 && !disconnectActivated)
{
disconnectActivated = true;
Task.Run(() =>
{
Disconnect();
});
}
}
}

/// <summary>
/// Responds with PONG on a PING with the specified arguments.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HandlePing(object sender, PingInfo e)
{
Command.SendPong(e.Message);
}

private void HandleTCPConnection(int e)
{
if (DisconnectEvent != null)
{
DisconnectEvent();
}
}

private void HandleTCPError(TCPError e)
{
if (TCPErrorEvent != null)
{
TCPErrorEvent(e);
}
}

private void HandleErrorMessage(object sender, ErrorMessage e)
{
Disconnect();
}

private void HandleReply(object sender, IReply e)
{
if (e.GetType() == typeof(ServerReplyMessage))
{
ServerReplyMessage msg = (ServerReplyMessage)e;
switch (msg.ReplyCode)
{
// If we get a WHO response, we parse and add the nicks to the specified channel if they are not there already.
case IRCReplyCode.RPL_WHOREPLY:
ChannelRWLock.EnterWriteLock();
string[] msgSplit = msg.Message.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (msgSplit.GetUpperBound(0) > 0)
{
string target = msgSplit[0];
if (target.StartsWith("&") || target.StartsWith("#"))
{
if (msgSplit.GetUpperBound(0) >= 7)
{
string nickname = msgSplit[4];
string realname = msgSplit[7];
string username = msgSplit[1];
string host = msgSplit[2];
string modeString = msgSplit[5];
Channel channel = Channels.Find(chan => chan.Name == target);
if (channel != null)
{
Nick nick = channel.GetNick(nickname);
bool nickFound = true;
if (nick == null)
{
nickFound = false;
nick = new Nick();
}
nick.Nickname = nickname;
nick.Host = host;
nick.Realname = realname;
nick.Username = username;
nick.Modes = new List<UserMode>();
nick.Privileges = new List<PrivilegeMode>();
char[] modeArr = modeString.ToCharArray();
for (int i = 1; i <= modeArr.GetUpperBound(0); i++)
{
if (PrivilegeMapping.ContainsKey(modeArr[i].ToString()))
{
nick.Privileges.Add(PrivilegeMapping[modeArr[i].ToString()]);
}
else if (modeArr[i].ToString() == "*")
{
nick.Modes.Add(UserMode.o);
}
else
{
UserMode foundMode;
bool valid = Enum.TryParse(modeArr[i].ToString(), false, out foundMode);
if (valid)
{
nick.Modes.Add(foundMode);
}
}
}
if (!nickFound)
{
channel.AddNick(nick);
}
}
}
}
}
ChannelRWLock.ExitWriteLock();
break;
// On a topic reply, update the channel's topic
case IRCReplyCode.RPL_TOPIC:
ChannelRWLock.EnterWriteLock();
string[] topicSplit = msg.Message.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (topicSplit.GetUpperBound(0) > 0)
{
string topicChan = topicSplit[0];
Channel topicChannel = Channels.Find(chan => chan.Name == topicChan);
if (topicChannel != null)
{
topicChannel.Topic = topicSplit[1].Remove(0, 1);
}
}
ChannelRWLock.ExitWriteLock();
break;
default:
break;
}
}
else
{
ServerErrorMessage msg = (ServerErrorMessage)e;
}
}

/// <summary>
/// Update a channel's mode.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HandleChannelModeChange(object sender, ChannelModeChangeInfo e)
{
ChannelRWLock.EnterWriteLock();
Channel channel = Channels.Find(chan => chan.Name == e.Channel);
if (channel != null)
{
foreach (ChannelModeInfo mode in e.Modes)
{
switch (mode.Mode)
{
case ChannelMode.v:
case ChannelMode.h:
case ChannelMode.o:
case ChannelMode.a:
case ChannelMode.q:
Nick changedNick = channel.GetNick(mode.Parameter);
if (changedNick != null)
{
PrivilegeMode priv;
Enum.TryParse(mode.Mode.ToString(), out priv);
if (mode.Set)
{
changedNick.AddPrivilege(priv);
}
else
{
changedNick.RemovePrivilege(priv);
}
}
break;
case ChannelMode.b:
if (mode.Set)
{
channel.AddBan(mode.Parameter);
}
else
{
channel.RemoveBan(mode.Parameter);
}
break;
case ChannelMode.k:
if (mode.Set)
{
channel.AddMode(mode.Mode);
channel.Key = mode.Parameter;
}
else
{
channel.RemoveMode(mode.Mode);
channel.Key = string.Empty;
}
break;
default:
if (mode.Set)
{
channel.AddMode(mode.Mode);
}
else
{
channel.RemoveMode(mode.Mode);
}
break;
}
}
Command.SendWho(channel.Name);
}
ChannelRWLock.ExitWriteLock();
}

/// <summary>
/// Update a nick's mode.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HandleUserModeChange(object sender, UserModeChangeInfo e)
{
ChannelRWLock.EnterWriteLock();
for (int i = 0; i < Channels.Count; i++)
{
Nick changedNick = Channels[i].GetNick(e.Nick.Nickname);
if (changedNick != null)
{
foreach (UserModeInfo mode in e.Modes)
{
if (mode.Set)
{
changedNick.AddMode(mode.Mode);
}
else
{
changedNick.RemoveMode(mode.Mode);
}
}
}
}
ChannelRWLock.ExitWriteLock();
}

/// <summary>
/// Update a nick to use their new nickname.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HandleNickChange(object sender, NickChangeInfo e)
{
ChannelRWLock.EnterWriteLock();
for (int i = 0; i < Channels.Count; i++)
{
Nick newNick = Channels[i].GetNick(e.OldNick.Nickname);
if (newNick != null)
{
if (e.OldNick.Nickname == Nickname)
{
Nickname = e.NewNick.Nickname;
}
newNick.Nickname = e.NewNick.Nickname;
}
}
ChannelRWLock.ExitWriteLock();
}

/// <summary>
/// Add a nick to a channel on join.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HandleJoin(object sender, JoinChannelInfo e)
{
ChannelRWLock.EnterWriteLock();
Channel channel = Channels.Find(chan => chan.Name == e.Channel);
if (channel != null)
{
channel.AddNick(e.Nick);
}
else
{
Channel newChannel = new Channel();
newChannel.Name = e.Channel;
newChannel.Nicks.Add(e.Nick);
Channels.Add(newChannel);
Command.SendWho(newChannel.Name);
}
ChannelRWLock.ExitWriteLock();
}

/// <summary>
/// Remove a nick from a channel on part.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HandlePart(object sender, PartChannelInfo e)
{
ChannelRWLock.EnterWriteLock();
Channel channel = Channels.Find(chan => chan.Name == e.Channel);
if (channel != null)
{
if (e.Nick.Nickname == Nickname)
{
Channels.Remove(channel);
}
else
{
channel.RemoveNick(e.Nick.Nickname);
}
}
ChannelRWLock.ExitWriteLock();
}

/// <summary>
/// Remove a nick from a channel on kick.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HandleKick(object sender, KickInfo e)
{
ChannelRWLock.EnterWriteLock();
Channel channel = Channels.Find(chan => chan.Name == e.Channel);
if (channel != null)
{
if (e.KickedNick.Nickname == Nickname)
{
Channels.Remove(channel);
}
else
{
channel.RemoveNick(e.KickedNick.Nickname);
}
}
ChannelRWLock.ExitWriteLock();
}

/// <summary>
/// Remove a nick from all channels on quit.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HandleQuit(object sender, QuitInfo e)
{
ChannelRWLock.EnterWriteLock();
for (int i = 0; i < Channels.Count; i++)
{
Channels[i].RemoveNick(e.Nick.Nickname);
}
ChannelRWLock.ExitWriteLock();
}

private void ThrowException(Exception ex)
{
ThrowException(ex, "Irc Service threw exception.");
}

private void ThrowException(Exception ex, string message)
{
Exception newEx = new Exception(message, ex);
if (ExceptionThrown != null)
{
ExceptionThrown(newEx);
}
}
}
}

+ 110
- 0
IRCSharp/IRCSharp.csproj View File

@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{65FCBF1C-8C9E-4688-BECC-185D9030899F}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>IRCSharp</RootNamespace>
<AssemblyName>IRCSharp</AssemblyName>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<IsWebBootstrapper>false</IsWebBootstrapper>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\Bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Channel.cs" />
<Compile Include="Commanding\Commands.cs" />
<Compile Include="Commanding\CommandTypes.cs" />
<Compile Include="Helper.cs" />
<Compile Include="IRC.cs" />
<Compile Include="Messaging\GetError.cs" />
<Compile Include="Messaging\GetReply.cs" />
<Compile Include="Messaging\Messages.cs" />
<Compile Include="Messaging\MessageTypes.cs" />
<Compile Include="Nick.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TCP\TCPInterface.cs" />
<Compile Include="TCP\TCPTypes.cs" />
<Compile Include="Types.cs" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.5.1">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.5.1 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\GitVersionTask.3.6.3\build\dotnet\GitVersionTask.targets" Condition="Exists('..\packages\GitVersionTask.3.6.3\build\dotnet\GitVersionTask.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\GitVersionTask.3.6.3\build\dotnet\GitVersionTask.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\GitVersionTask.3.6.3\build\dotnet\GitVersionTask.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

+ 13
- 0
IRCSharp/Messaging/GetError.cs View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Threading;

namespace IRCSharp.Messaging
{
public class GetError
{
public ManualResetEventSlim Ready = new ManualResetEventSlim(false);
public string Match = string.Empty;
public IRCErrorCode Error = new IRCErrorCode();
public ServerErrorMessage Result = new ServerErrorMessage();
}
}

+ 13
- 0
IRCSharp/Messaging/GetReply.cs View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Threading;

namespace IRCSharp.Messaging
{
public class GetReply
{
public ManualResetEventSlim Ready = new ManualResetEventSlim(false);
public string Match = string.Empty;
public IRCReplyCode Reply = new IRCReplyCode();
public ServerReplyMessage Result = new ServerReplyMessage();
}
}

+ 290
- 0
IRCSharp/Messaging/MessageTypes.cs View File

@@ -0,0 +1,290 @@
using System;
using System.Collections.Generic;

namespace IRCSharp.Messaging
{
abstract public class IMessage : EventArgs
{
public DateTime TimeStamp { get; set; }

public IMessage()
{
TimeStamp = DateTime.Now;
}
}

abstract public class IReply : IMessage
{
public string Message { get; set; }
}

public class ServerReplyMessage : IReply
{
public IRCReplyCode ReplyCode { get; set; }
}

public class ServerErrorMessage : IReply
{
public IRCErrorCode ErrorCode { get; set; }
}

public class ErrorMessage : IMessage
{
public string Message { get; set; }
}

public class ChannelMessage : IMessage
{
public string Channel { get; set; }
public Nick Sender { get; set; }
public string Message { get; set; }
}

public class PrivateMessage : IMessage
{
public Nick Sender { get; set; }
public string Message { get; set; }
}

public class ServerNotice : IMessage
{
public string Message { get; set; }
}

public class ChannelNotice : IMessage
{
public string Channel { get; set; }
public Nick Sender { get; set; }
public string Message { get; set; }
}

public class PrivateNotice : IMessage
{
public Nick Sender { get; set; }
public string Message { get; set; }
}

public class CTCPMessage : IMessage
{
public string Location { get; set; }
public Nick Sender { get; set; }
public string Command { get; set; }
public string Arguments { get; set; }
}

public class TopicChangeInfo : IMessage
{
public string Channel { get; set; }
public Nick Nick { get; set; }
public string Topic { get; set; }
}

public class ChannelModeChangeInfo : IMessage
{
public string Channel { get; set; }
public Nick Nick { get; set; }
public List<ChannelModeInfo> Modes { get; set; }
}

public class UserModeChangeInfo : IMessage
{
public Nick Nick { get; set; }
public List<UserModeInfo> Modes { get; set; }
}

public class NickChangeInfo : IMessage
{
public Nick OldNick { get; set; }
public Nick NewNick { get; set; }
}

public class InviteChannelInfo : IMessage
{
public string Channel { get; set; }
public Nick Requester { get; set; }
public Nick Recipient { get; set; }
}

public class JoinChannelInfo : IMessage
{
public string Channel { get; set; }
public Nick Nick { get; set; }
}

public class PartChannelInfo : IMessage
{
public string Channel { get; set; }
public Nick Nick { get; set; }
}

public class KickInfo : IMessage
{
public string Channel { get; set; }
public Nick Nick { get; set; }
public Nick KickedNick { get; set; }
public string Reason { get; set; }
}

public class QuitInfo : IMessage
{
public Nick Nick { get; set; }
public string Message { get; set; }
}

public class PingInfo : IMessage
{
public string Message { get; set; }
}

public class PongInfo : IMessage
{
public string Message { get; set; }
}

// IRC Reply Codes //
public enum IRCReplyCode
{
RPL_WELCOME = 1,
RPL_YOURHOST = 2,
RPL_CREATED = 3,
RPL_MYINFO = 4,
RPL_BOUNCE = 5,
RPL_TRACELINK = 200,
RPL_TRACECONNECTING = 201,
RPL_TRACEHANDSHAKE = 202,
RPL_TRACEUNKNOWN = 203,
RPL_TRACEOPERATOR = 204,
RPL_TRACEUSER = 205,
RPL_TRACESERVER = 206,
RPL_TRACESERVICE = 207,
RPL_TRACENEWTYPE = 208,
RPL_TRACECLASS = 209,
RPL_TRACERECONNECT = 210,
RPL_STATSLINKINFO = 211,
RPL_STATSCOMMANDS = 212,
RPL_ENDOFSTATS = 219,
RPL_UMODEIS = 221,
RPL_SERVLIST = 234,
RPL_SERVLISTEND = 235,
RPL_STATSUPTIME = 242,
RPL_STATSOLINE = 243,
RPL_LUSERCLIENT = 251,
RPL_LUSEROP = 252,
RPL_LUSERUNKNOWN = 253,
RPL_LUSERCHANNELS = 254,
RPL_LUSERME = 255,
RPL_ADMINME = 256,
RPL_ADMINLOC1 = 257,
RPL_ADMINLOC2 = 258,
RPL_ADMINEMAIL = 259,
RPL_TRACELOG = 261,
RPL_TRACEEND = 262,
RPL_TRYAGAIN = 263,
RPL_AWAY = 301,
RPL_USERHOST = 302,
RPL_ISON = 303,
RPL_UNAWAY = 305,
RPL_NOWAWAY = 306,
RPL_WHOISUSER = 311,
RPL_WHOISSERVER = 312,
RPL_WHOISOPERATOR = 313,
RPL_WHOWASUSER = 314,
RPL_ENDOFWHO = 315,
RPL_WHOISIDLE = 317,
RPL_ENDOFWHOIS = 318,
RPL_WHOISCHANNELS = 319,
RPL_LISTSTART = 321,
RPL_LIST = 322,
RPL_LISTEND = 323,
RPL_CHANNELMODEIS = 324,
RPL_UNIQOPIS = 325,
RPL_NOTOPIC = 331,
RPL_TOPIC = 332,
RPL_INVITING = 341,
RPL_SUMMONING = 342,
RPL_INVITELIST = 346,
RPL_ENDOFINVITELIST = 347,
RPL_EXCEPTLIST = 348,
RPL_ENDOFEXCEPTLIST = 349,
RPL_VERSION = 351,
RPL_WHOREPLY = 352,
RPL_NAMREPLY = 353,
RPL_LINKS = 364,
RPL_ENDOFLINKS = 365,
RPL_ENDOFNAMES = 366,
RPL_BANLIST = 367,
RPL_ENDOFBANLIST = 368,
RPL_ENDOFWHOWAS = 369,
RPL_INFO = 371,
RPL_MOTD = 372,
RPL_ENDOFINFO = 374,
RPL_MOTDSTART = 375,
RPL_ENDOFMOTD = 376,
RPL_YOUREOPER = 381,
RPL_REHASHING = 382,
RPL_YOURESERVICE = 383,
RPL_TIME = 391,
RPL_USERSSTART = 392,
RPL_USERS = 393,
RPL_ENDOFUSERS = 394,
RPL_NOUSERS = 395
}

// IRC Error Codes //
public enum IRCErrorCode
{
ERR_NOSUCHNICK = 401,
ERR_NOSUCHSERVER = 402,
ERR_NOSUCHCHANNEL = 403,
ERR_CANNOTSENDTOCHAN = 404,
ERR_TOOMANYCHANNELS = 405,
ERR_WASNOSUCHNICK = 406,
ERR_TOOMANYTARGETS = 407,
ERR_NOSUCHSERVICE = 408,
ERR_NOORIGIN = 409,
ERR_NORECIPIENT = 411,
ERR_NOTEXTTOSEND = 412,
ERR_NOTOPLEVEL = 413,
ERR_WILDTOPLEVEL = 414,
ERR_BADMASK = 415,
ERR_UNKNOWNCOMMAND = 421,
ERR_NOMOTD = 422,
ERR_NOADMININFO = 423,
ERR_FILEERROR = 424,
ERR_NONICKNAMEGIVEN = 431,
ERR_ERRONEUSNICKNAME = 432,
ERR_NICKNAMEINUSE = 433,
ERR_NICKCOLLISION = 436,
ERR_UNAVAILRESOURCE = 437,
ERR_USERNOTINCHANNEL = 441,
ERR_NOTONCHANNEL = 442,
ERR_USERONCHANNEL = 443,
ERR_NOLOGIN = 444,
ERR_SUMMONDISABLED = 445,
ERR_USERSDISABLED = 446,
ERR_NOTREGISTERED = 451,
ERR_NEEDMOREPARAMS = 461,
ERR_ALREADYREGISTRED = 462,
ERR_NOPERMFORHOST = 463,
ERR_PASSWDMISMATCH = 464,
ERR_YOUREBANNEDCREEP = 465,
ERR_YOUWILLBEBANNED = 466,
ERR_KEYSET = 467,
ERR_CHANNELISFULL = 471,
ERR_UNKNOWNMODE = 472,
ERR_INVITEONLYCHAN = 473,
ERR_BANNEDFROMCHAN = 474,
ERR_BADCHANNELKEY = 475,
ERR_BADCHANMASK = 476,
ERR_NOCHANMODES = 477,
ERR_BANLISTFULL = 478,
ERR_NOPRIVILEGES = 481,
ERR_CHANOPRIVSNEEDED = 482,
ERR_CANTKILLSERVER = 483,
ERR_RESTRICTED = 484,
ERR_UNIQOPPRIVSNEEDED = 485,
ERR_NOOPERHOST = 491,
ERR_UMODEUNKNOWNFLAG = 501,
ERR_USERSDONTMATCH = 502
}
}

+ 525
- 0
IRCSharp/Messaging/Messages.cs View File

@@ -0,0 +1,525 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace IRCSharp.Messaging
{
public class Messages
{
public event EventHandler<string> RawMessageEvent;
public event EventHandler<IReply> ServerReplyEvent;
public event EventHandler<ErrorMessage> ErrorMessageEvent;
public event EventHandler<ChannelMessage> ChannelMessageReceivedEvent;
public event EventHandler<PrivateMessage> PrivateMessageReceivedEvent;
public event EventHandler<ServerNotice> ServerNoticeReceivedEvent;
public event EventHandler<ChannelNotice> ChannelNoticeReceivedEvent;
public event EventHandler<PrivateNotice> PrivateNoticeReceivedEvent;
public event EventHandler<CTCPMessage> CTCPMessageReceivedEvent;
public event EventHandler<CTCPMessage> CTCPNoticeReceivedEvent;
public event EventHandler<TopicChangeInfo> TopicChangeEvent;
public event EventHandler<ChannelModeChangeInfo> ChannelModeChangeEvent;
public event EventHandler<UserModeChangeInfo> UserModeChangeEvent;
public event EventHandler<NickChangeInfo> NickChangeEvent;
public event EventHandler<InviteChannelInfo> InviteChannelEvent;
public event EventHandler<JoinChannelInfo> JoinChannelEvent;
public event EventHandler<PartChannelInfo> PartChannelEvent;
public event EventHandler<KickInfo> KickEvent;
public event EventHandler<QuitInfo> QuitEvent;
public event EventHandler<PingInfo> PingEvent;
public event EventHandler<PongInfo> PongEvent;

private IRC _IRC;

internal Messages(IRC irc)
{
_IRC = irc;
}

/// <summary>
/// Parses the raw messages coming from the server and triggers an event based on the type of message.
/// </summary>
/// <param name="tcpMessage">The raw string read from the TCP stream.</param>
internal async void ParseTCPMessage(string tcpMessage)
{
DateTime messageTime = DateTime.Now;
Regex messageRegex = new Regex(@"^:(?<Sender>[^\s]+)\s(?<Type>[^\s]+)\s(?<Recipient>[^\s]+)\s?:?(?<Args>.*)", RegexOptions.None);
Regex senderRegex = new Regex(@"^(?<Nick>[^\s]+)!(?<Realname>[^\s]+)@(?<Host>[^\s]+)", RegexOptions.None);
Regex pingRegex = new Regex(@"^PING :(?<Message>.+)", RegexOptions.None);
Regex pongRegex = new Regex(@"^PONG :(?<Message>.+)", RegexOptions.None);
Regex errorRegex = new Regex(@"^ERROR :(?<Message>.+)", RegexOptions.None);
Regex CTCPRegex = new Regex(@"^\u0001(?<Command>[^\s]+)\s?(?<Args>.*)\u0001", RegexOptions.None);

string[] messages = tcpMessage.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);

foreach (string message in messages)
{
if (messageRegex.IsMatch(message))
{
Match match = messageRegex.Match(message);
string type = match.Groups["Type"].Value;
string sender = match.Groups["Sender"].Value;
string recipient = match.Groups["Recipient"].Value;
string args = match.Groups["Args"].Value;
Match senderMatch = senderRegex.Match(sender);
string senderNick = sender;
string senderRealname = null;
string senderHost = null;
if (senderMatch.Success)
{
senderNick = senderMatch.Groups["Nick"].Value;
senderRealname = senderMatch.Groups["Realname"].Value;
senderHost = senderMatch.Groups["Host"].Value;
}

int replyCode;
if (int.TryParse(type, out replyCode))
{
// The message was a reply to a command sent
if (Enum.IsDefined(typeof(IRCReplyCode), replyCode))
{
await Task.Run(() =>
{
if (ServerReplyEvent != null)
{
ServerReplyEvent(this,
new ServerReplyMessage()
{
TimeStamp = messageTime,
ReplyCode = (IRCReplyCode) replyCode,
Message = args
});
}
});
}
else if (Enum.IsDefined(typeof(IRCErrorCode), replyCode))
{
await Task.Run(() =>
{
if (ServerReplyEvent != null)
{
ServerReplyEvent(this,
new ServerErrorMessage()
{
TimeStamp = messageTime,
ErrorCode = (IRCErrorCode) replyCode,
Message = args
});
}
});
}
}
else
{
switch (type)
{
// The message was a private message to a channel or nick
case "PRIVMSG":
if (CTCPRegex.IsMatch(args))
{
Match ctcpMatch = CTCPRegex.Match(args);
CTCPMessage ctcpMessage = new CTCPMessage();
ctcpMessage.Location = recipient;
ctcpMessage.Sender = new Nick()
{
Nickname = senderNick,
Realname = senderRealname,
Host = senderHost
};
ctcpMessage.Command = ctcpMatch.Groups["Command"].Value;
ctcpMessage.Arguments = ctcpMatch.Groups["Args"].Value;

await Task.Run(() =>
{
if (CTCPMessageReceivedEvent != null)
{
CTCPMessageReceivedEvent(this, ctcpMessage);
}
});
}
else
{
if (Channel.IsChannel(recipient))
{
ChannelMessage msg = new ChannelMessage();
msg.Channel = recipient;
msg.Sender = new Nick()
{
Nickname = senderNick,
Realname = senderRealname,
Host = senderHost
};
msg.Message = args;

await Task.Run(() =>
{
if (ChannelMessageReceivedEvent != null)
{
ChannelMessageReceivedEvent(this, msg);
}
});
}
else
{
PrivateMessage msg = new PrivateMessage();
msg.Sender = new Nick()
{
Nickname = senderNick,
Realname = senderRealname,
Host = senderHost
};
msg.Message = args;

await Task.Run(() =>
{
if (PrivateMessageReceivedEvent != null)
{
PrivateMessageReceivedEvent(this, msg);
}
});
}
}
break;
// The message was a notice to a channel or nick
case "NOTICE":
if (CTCPRegex.IsMatch(args))
{
Match ctcpMatch = CTCPRegex.Match(args);
CTCPMessage ctcpMessage = new CTCPMessage();
ctcpMessage.Sender = new Nick()
{
Nickname = senderNick,
Realname = senderRealname,
Host = senderHost
};
ctcpMessage.Command = ctcpMatch.Groups["Command"].Value;
ctcpMessage.Arguments = ctcpMatch.Groups["Args"].Value;

await Task.Run(() =>
{
if (CTCPNoticeReceivedEvent != null)
{
CTCPNoticeReceivedEvent(this, ctcpMessage);
}
});
}
if (Channel.IsChannel(recipient))
{
ChannelNotice msg = new ChannelNotice();
msg.Channel = recipient;
msg.Sender = new Nick() { Nickname = senderNick, Realname = senderRealname, Host = senderHost };
msg.Message = args;

await Task.Run(() =>
{
if (ChannelNoticeReceivedEvent != null)
{
ChannelNoticeReceivedEvent(this, msg);
}
});
}
else
{
PrivateNotice msg = new PrivateNotice();
msg.Sender = new Nick() { Nickname = senderNick, Realname = senderRealname, Host = senderHost };
msg.Message = args;

await Task.Run(() =>
{
if (PrivateNoticeReceivedEvent != null)
{
PrivateNoticeReceivedEvent(this, msg);
}
});
}
break;
// The message was a mode change message for a channel or nick
case "MODE":
if (Channel.IsChannel(recipient))
{
ChannelModeChangeInfo modeMsg = new ChannelModeChangeInfo();
modeMsg.Modes = new List<ChannelModeInfo>();
modeMsg.Channel = recipient;
modeMsg.Nick = new Nick() { Nickname = senderNick, Realname = senderRealname, Host = senderHost };

if (!string.IsNullOrEmpty(args))
{
string[] modeArgs = args.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
List<string> argList = modeArgs.ToList();
argList.RemoveAt(0);
modeMsg.Modes.AddRange(_IRC.ParseChannelModeString(modeArgs[0].TrimStart(':'), string.Join(" ", argList)));
}

await Task.Run(() =>
{
if (ChannelModeChangeEvent != null)
{
ChannelModeChangeEvent(this, modeMsg);
}
});
}
else
{
UserModeChangeInfo modeMsg = new UserModeChangeInfo();
modeMsg.Modes = new List<UserModeInfo>();
modeMsg.Nick = new Nick() { Nickname = senderNick, Realname = senderRealname, Host = senderHost };

if (!string.IsNullOrEmpty(args))
{
string[] modeArgs = args.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
char[] modeInfo = modeArgs[0].TrimStart(':').ToCharArray();
bool set = true;
foreach (char mode in modeInfo)
{
if (mode.Equals('-'))
{
set = false;
}
else if (mode.Equals('+'))
{
set = true;
}
else if (!string.IsNullOrEmpty(mode.ToString()))
{
UserModeInfo newMode = new UserModeInfo();
newMode.Set = set;
UserMode md;
Enum.TryParse(mode.ToString(), out md);
newMode.Mode = md;
modeMsg.Modes.Add(newMode);
}
}
}

await Task.Run(() =>
{
if (UserModeChangeEvent != null)
{
UserModeChangeEvent(this, modeMsg);
}
});
}
break;
// The message was a topic change for a channel
case "TOPIC":
TopicChangeInfo topicMsg = new TopicChangeInfo();
topicMsg.Channel = recipient;
topicMsg.Nick = new Nick() { Nickname = senderNick, Realname = senderRealname, Host = senderHost };
topicMsg.Topic = args;

await Task.Run(() =>
{
if (TopicChangeEvent != null)
{
TopicChangeEvent(this, topicMsg);
}