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