/// <summary> /// Add a channel to the list of channels that will be displayed upon flushing them to the screen. /// </summary> /// <param name="reply">The LIST reply to create an item from.</param> public void AddChannel(IRCMessage reply) { string cleanTopic = reply.TrailingParameter; cleanTopic = Regex.Replace(cleanTopic, @"(\x03+[0-9]{1,2}(,[0-9]{1,2})*|\x02|\x31|\x29|\x15|\x03)", string.Empty, RegexOptions.IgnoreCase); string[] subitems = new string[] { reply.Parameters[1], reply.Parameters[2], cleanTopic }; this.items.Add(new ListViewItem(subitems)); }
/// <summary> /// Assimilates the message into the appropriate subclass. /// </summary> /// <param name="message">The message to assimilate.</param> /// <returns>The assimilated message, or the original message if it cannot be mapped to a subclass.</returns> internal static Message AssimilateMessage(Message message) { Message retval = message; string className = GetClassNameFromRawCommand(message.Command); if (!string.IsNullOrEmpty(className)) { retval = (Message)Assembly.GetExecutingAssembly().CreateInstance(string.Format("Yaircc.Net.IRC.{0}", className)); retval.Assimilate(message); } return retval; }
/// <summary> /// Creates a new <see cref="Message"/>, encapsulating the data from the payload. /// </summary> /// <param name="payload">The payload to be encapsulated.</param> /// <param name="sourceChannel">The name of the channel the message is being generated on.</param> /// <returns>A <see cref="MessageParseResult"/> containing the result of the operation.</returns> internal static MessageParseResult CreateFromUserInput(string payload, string sourceChannel) { MessageParseResult retval = null; string className = GetClassNameFromUserCommand(payload); if (!string.IsNullOrEmpty(className)) { Message message = (Message)System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(string.Format("Yaircc.Net.IRC.{0}", className)); message.ChannelName = sourceChannel; ParseResult parseResult = message.TryParse(payload); retval = new MessageParseResult(parseResult.Success, parseResult.Message, message); } else { Message message = new Message(); ParseResult parseResult = message.TryParse(payload); retval = new MessageParseResult(parseResult.Success, parseResult.Message, message); } return retval; }
/// <summary> /// Process a message that is categorised as being a numeric reply in the RFC documents. /// </summary> /// <param name="message">The message to process.</param> private void ProcessNumericReply(Message message) { Reply reply = (Reply)int.Parse(message.Command); Action<IRCTabPage> appendMessage = (IRCTabPage tabPage) => { MessageTypeAttribute attributes = IRCMarshal.GetReplyAttributes(reply); MessageType messageType = attributes.MessageType; GlobalSettings settings = GlobalSettings.Instance; string source = attributes.Source; string content; if (attributes.OutputAction == null) { content = message.ToString(attributes.OutputFormat, attributes.ParameterDelimiter, attributes.RemoveFirstParameter); } else { content = attributes.OutputAction.Invoke(message); } if (settings.DebugMode == GlobalSettings.Boolean.Yes) { tabPage.AppendMessage(reply.ToString(), "[RAW]", message.ToString(), MessageType.WarningMessage); } tabPage.AppendMessage(reply.ToString(), source, content, messageType); }; // If we are retrieving list replies we need to populate the channel browser if (reply == Reply.RPL_LISTSTART) { this.parent.InvokeAction(() => this.ChannelBrowser.BeginRefresh(true)); return; } else if (reply == Reply.RPL_LIST) { this.ChannelBrowser.AddChannel(message); return; } else if (reply == Reply.RPL_LISTEND) { this.parent.InvokeAction(() => this.ChannelBrowser.FlushChannels()); return; } // If we are still awaiting the UserHost reply (i.e. we are awaiting confirmation of the full userhost // prefix that we will use to determine the max PRIVMSG lengths) then cache it in the marshal for future // reference. if (this.AwaitingUserHostMessage && reply == Reply.RPL_USERHOST) { this.fullUserHost = message.TrailingParameter; this.AwaitingUserHostMessage = false; // Execute any auto commands. if (!this.hasExecutedAutoCommands && this.AutoCommands.Count > 0) { for (int i = 0; i < this.AutoCommands.Count; i++) { MessageParseResult parseResult = MessageFactory.CreateFromUserInput(this.AutoCommands[i], null); if (parseResult.Success) { this.Send(this.ServerTab, parseResult.IRCMessage); } } this.hasExecutedAutoCommands = true; } if (this.reconnecting) { // Pause the thread for a second to give time for any authentication to // take place and then rejoin the channels. System.Threading.Thread.Sleep(1000); this.Channels.ForEach(i => { if (i.TabPage.TabType == IRCTabType.Channel) { this.Send(this.ServerTab, new JoinMessage(i.Name)); } }); this.reconnecting = false; } return; } // If the user has received a new hidden host then we need to re-evaluate // their full user host mask that will be seen by other clients. if (reply == Reply.RPL_HOSTHIDDEN) { this.AwaitingUserHostMessage = true; this.Send(this.ServerTab, new UserHostMessage(new string[] { this.connection.Nickname })); } // If we have a names reply or an end of names reply, then we need to check the channel // it is in regards to exists in our channel list, and if it does check to see if it // is awaiting a reply from a names request (i.e. it is wanting to refresh the user list). // // If this is indeed the case, we need to force it through to that channel rather than // following the default procedure of going to the selected tab. if ((reply == Reply.RPL_NAMREPLY) || (reply == Reply.RPL_ENDOFNAMES)) { string target = string.Empty; if (reply == Reply.RPL_NAMREPLY) { target = message.Parameters[2]; } else { target = message.Parameters[1]; } IRCChannel channel = this.channels.Find(i => i.Name.Equals(target, StringComparison.OrdinalIgnoreCase)); if (channel != null && channel.ExpectingNamesMessage) { channel.HandleReply(message); return; } } // If the currently selected tab belongs to the channel list for this connection // AND we aren't awaiting a mode message (i.e. connecting to the server) // then marshal the message to the owning channel, otherwise default to the server tab this.TabHost.InvokeAction(() => { IRCChannel selectedChannel = this.Channels.Find(i => this.TabHost.SelectedTab.Equals(i.TabPage)); if ((selectedChannel != null) && (!this.AwaitingModeMessage)) { selectedChannel.HandleReply(message); } else { appendMessage.Invoke(this.ServerTab); } }); // If a nick in use message comes through, we need to revert the nick against the connection // back to the previously assigned nick (if there is one). // If there wasn't one, then we'll append an underscore to the current one and resend the nick message // we couldn't possibly get stuck in a loop, right? if (reply == Reply.ERR_NICKNAMEINUSE) { if (this.previousNickName.Equals(this.Connection.Nickname, StringComparison.OrdinalIgnoreCase)) { this.previousNickName = string.Format("{0}_", this.previousNickName); this.connection.Nickname = this.previousNickName; this.Send(this.ServerTab, new NickMessage(this.previousNickName)); } else { if (!this.AwaitingModeMessage) { this.Connection.Nickname = this.previousNickName; } else { this.previousNickName = string.Format("{0}_", this.connection.Nickname); this.connection.Nickname = this.previousNickName; this.Send(this.ServerTab, new NickMessage(this.previousNickName)); } } } }
/// <summary> /// Process a message that contains a text based command. /// </summary> /// <param name="message">The message to process.</param> private void ProcessMessage(Message message) { message = MessageFactory.AssimilateMessage(message); string target = message.Target.Equals(this.Connection.Nickname, StringComparison.OrdinalIgnoreCase) ? message.Source : message.Target; if (message.MustBeHandledByTarget) { IRCChannel channel = this.Channels.Find(i => i.Name.Equals(target, StringComparison.OrdinalIgnoreCase)); if (channel == null) { channel = this.CreateChannel(target, false); } channel.HandleMessage(message); } else if (message.IsMultiChannelMessage) { for (int i = 0; i < this.Channels.Count; i++) { this.Channels[i].HandleMessage(message); } } else if (message.IsGlobalMessage) { this.Channels.ForEach(i => i.HandleMessage(message)); this.ServerTab.AppendMessage(message.Command, message.Source, message.Content, message.Type); } else { // If the message can be handled by both the server tab // and a channel tab, then we need to check if the currently // focused tab is a channel and if so handle it in the channel. // Otherwise we can just print it in the server tab this.TabHost.InvokeAction(() => { if (this.TabHost.SelectedTab is IRCTabPage) { IRCChannel channel = this.Channels.Find(i => i.TabPage.Equals(this.TabHost.SelectedTab)); if ((channel != null) && (!this.AwaitingModeMessage)) { channel.HandleMessage(message); } else { this.ServerTab.AppendMessage(message.Command, message.Source, message.Content, message.Type); } } }); } // If we are still awaiting the MODE message (i.e. we are awaiting confirmation we have finished connecting) // then also re-join any channels that we have in the Channels collection (i.e. reconnect to any channels) if (this.AwaitingModeMessage && message is ModeMessage) { this.AwaitingModeMessage = false; this.Send(this.ServerTab, new ModeMessage(new string[] { this.Connection.Nickname, this.Connection.Mode })); // Send the user host message so we can determine the full user host string. this.Send(this.ServerTab, new UserHostMessage(new string[] { this.connection.Nickname })); } }
/// <summary> /// Sends the message to the associated server. /// </summary> /// <param name="sender">The source tab page.</param> /// <param name="message">The message to send.</param> public void Send(IRCTabPage sender, Message message) { message = MessageFactory.AssimilateMessage(message); if (this.Connection.IsConnected) { if ((message is PrivMsgMessage) || (message is NoticeMessage)) { IRCTabPage tabPage = null; string source = this.Connection.Nickname; if (message is PrivMsgMessage) { IRCChannel channel = this.Channels.Find(i => i.Name.Equals(message.Target, StringComparison.OrdinalIgnoreCase)); if (channel == null) { channel = this.CreateChannel(message.Target, true); } tabPage = channel.TabPage; } else { source = string.Format("to({0})", message.Target); tabPage = sender; } tabPage.AppendMessage(message.Command, source, message.Content, message.Type); } else if (message is JoinMessage) { IRCChannel channel = this.Channels.Find(i => i.Name.Equals(message.Parameters[0], StringComparison.OrdinalIgnoreCase)); if (channel == null) { channel = this.CreateChannel(message.Parameters[0], true); } else { // If we're already in the channel, just switch tabs. this.TabHost.InvokeAction(() => this.TabHost.SelectedTab = channel.TabPage); } } else if (message is NickMessage) { this.previousNickName = this.connection.Nickname; this.connection.Nickname = (message as NickMessage).NewNickname; } if (message is QuitMessage) { this.disconnecting = true; } this.Connection.Send(message.ToString()); } if (message is QuitMessage) { this.Dispose(); } }
/// <summary> /// Parses a text based message into a Yaircc.Net.IRC.Message. /// </summary> /// <param name="message">A string that meets the RFC 1459 or 2812 standard.</param> /// <returns>A new instance of Yaircc.Net.IRC.Message encapsulating the specified message.</returns> public static Message ParseMessage(string message) { Message retval = new Message(); // The format of the message must meet the RFC 1459 standard // that being: [':' <prefix> <SPACE> ] <command> <params> <crlf> // -- Breakdown of Message Sections -- // <prefix> := <servername> | <nick> [ '!' <user> ] [ '@' <host> ] // <command> := <letter> { <letter> } | <number> <number> <number> // <params> := <SPACE> [ ':' <trailing> | <middle> <params> ] // <middle> := <Any *non-empty* sequence of octets not including SPACE or NUL or CR or LF, the first of which may not be ':'> // <trailing> := <Any, possibly *empty*, sequence of octets not including NUL or CR or LF> try { int prefixEnd = -1; int trailingStart = message.Length; string prefix = null; string command = null; string[] parameters = null; string trailingParameter = null; // Get the prefix if (message.StartsWith(":")) { prefixEnd = message.IndexOf(' '); prefix = message.Substring(1, prefixEnd - 1); } // Grab the trailing parameter trailingStart = message.IndexOf(" :"); if (trailingStart >= 0) { trailingParameter = message.Substring(trailingStart + 2); } else { trailingStart = message.Length; } // Use the prefix end position and trailing parameter start position to get the command and remaining parameters string[] commandAndParameters = message.Substring(prefixEnd + 1, trailingStart - prefixEnd - 1).Split(' '); command = commandAndParameters[0]; // If there were parameters add them all in if (commandAndParameters.Length > 1) { // Loop through the standard parameters and add them to the array parameters = new string[commandAndParameters.Length - 1]; for (int i = 0; i < (commandAndParameters.Length - 1); i++) { parameters[i] = commandAndParameters[i + 1].Trim(new char[] { ' ', '\r', '\n' }); } } if (!string.IsNullOrEmpty(prefix)) { retval.Prefix = prefix; } if (!string.IsNullOrEmpty(command)) { retval.Command = command; } if (parameters != null) { retval.Parameters = parameters; } if (!string.IsNullOrEmpty(trailingParameter)) { retval.TrailingParameter = trailingParameter.Trim(new char[] { '\r', '\n' }); } } catch { } return retval; }
/// <summary> /// Populate the user list from a names message. /// </summary> /// <param name="message">The message to process.</param> private void PopulateChannelNamesFromMessage(Message message) { string[] names = message.TrailingParameter.Split(new char[] { ' ' }); for (int i = 0; i < names.GetLength(0); i++) { if (!string.IsNullOrEmpty(names[i])) { this.users.Add(IRCUser.Parse(names[i])); } } }
/// <summary> /// Do any channel level processing required for the reply. /// </summary> /// <param name="message">The reply message to process.</param> public void HandleReply(Message message) { Reply reply = (Reply)int.Parse(message.Command); MessageTypeAttribute attributes = IRCMarshal.GetReplyAttributes(reply); MessageType messageType = attributes.MessageType; GlobalSettings settings = GlobalSettings.Instance; string source = attributes.Source; string content; if (attributes.OutputAction == null) { content = message.ToString(attributes.OutputFormat, attributes.ParameterDelimiter, attributes.RemoveFirstParameter); } else { content = attributes.OutputAction.Invoke(message); } if (this.expectingNamesMessage && reply == Reply.RPL_NAMREPLY) { this.PopulateChannelNamesFromMessage(message); } else if (this.expectingNamesMessage && reply == Reply.RPL_ENDOFNAMES) { this.expectingNamesMessage = false; if (this.namesPopulated != null) { this.namesPopulated.Invoke(this, this.users); } } else { if (settings.DebugMode == GlobalSettings.Boolean.Yes) { this.tabPage.AppendMessage(reply.ToString(), "[RAW]", message.ToString(), MessageType.WarningMessage); } this.tabPage.AppendMessage(reply.ToString(), source, content, messageType); } }
/// <summary> /// Do any channel level processing required for the message. /// </summary> /// <param name="message">The message to process.</param> public void HandleMessage(Message message) { Action namesRequest = () => { if (!this.expectingNamesMessage) { this.users.Clear(); this.expectingNamesMessage = true; this.marshal.Send(this.TabPage, new NamesMessage(this.Name)); } }; if (message is JoinMessage) { namesRequest.Invoke(); } else if (message is PartMessage) { PartMessage partMessage = message as PartMessage; if (partMessage.NickName.Equals(this.marshal.Connection.Nickname, StringComparison.OrdinalIgnoreCase)) { this.Dispose(); return; } else { IRCUser user = this.users.Find(i => i.NickName.Equals(partMessage.NickName, StringComparison.OrdinalIgnoreCase)); this.TabPage.RemoveUserFromList(user); this.users.Remove(user); } } else if (message is KickMessage) { KickMessage kickMessage = message as KickMessage; IRCUser user = this.users.Find(i => i.NickName.Equals(kickMessage.NickName, StringComparison.OrdinalIgnoreCase)); this.TabPage.RemoveUserFromList(user); this.users.Remove(user); } else if (message is NickMessage) { NickMessage nickMessage = message as NickMessage; IRCUser user = this.users.Find(i => i.NickName.Equals(nickMessage.OldNickname, StringComparison.OrdinalIgnoreCase)); if (user != null) { this.TabPage.RemoveUserFromList(user); user.NickName = nickMessage.NewNickname; this.TabPage.AddUserToList(user, true); } else { return; } } else if (message is QuitMessage) { QuitMessage quitMessage = message as QuitMessage; IRCUser user = this.users.Find(i => i.NickName.Equals(quitMessage.NickName, StringComparison.OrdinalIgnoreCase)); if (user != null) { this.TabPage.RemoveUserFromList(user); this.users.Remove(user); } else { return; } } else if (message is ModeMessage) { namesRequest.Invoke(); } GlobalSettings settings = GlobalSettings.Instance; if (settings.DebugMode == GlobalSettings.Boolean.Yes) { this.TabPage.AppendMessage(message.Command, "[RAW]", message.ToString(), MessageType.WarningMessage); } this.TabPage.AppendMessage(message.Command, message.Source, message.Content, message.Type); }