123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714 |
- 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 Combot.IRCServices.Messaging;
- using Combot.IRCServices.Commanding;
- using Combot.IRCServices.TCP;
-
- namespace Combot.IRCServices
- {
- 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);
- }
- }
- }
- }
|