/// <summary> /// Send a message to all the users in a channel.</summary> /// <remarks> /// Possible Errors /// <list type="bullet"> /// <item><description>ERR_CANNOTSENDTOCHAN</description></item> /// <item><description>ERR_NOTEXTTOSEND</description></item> /// </list> /// </remarks> /// <param name="channel">The target channel.</param> /// <param name="message">A message. If the message is too long it will be broken /// up into smaller piecese which will be sent sequentially.</param> /// <exception cref="ArgumentException">If the channel name is not valid or if the message is null.</exception> /// <seealso cref="Listener.OnPublic"/> public void PublicMessage(string channel, string message) { if (IsEmpty(message)) { throw new ArgumentException("Public message cannot be null or empty."); } if (!Rfc2812Util.IsValidChannelName(channel)) { throw new ArgumentException(channel + " is not a valid channel name."); } lock (this) { // 11 is PRIVMSG + 2 x Spaces + : + CR + LF int max = MAX_COMMAND_SIZE - 11 - channel.Length - MAX_HOSTNAME_LEN - MAX_NICKNAME_LEN; if (message.Length > max) { string[] parts = BreakUpMessage(message, max); foreach (string part in parts) { SendMessage("PRIVMSG", channel, part); } } else { SendMessage("PRIVMSG", channel, message); } } }
/// <summary> /// Join a passworded channel. /// </summary> /// <param name="channel">Channel to join</param> /// <param name="password">The channel's pasword. Cannot be null or empty.</param> /// <exception cref="ArgumentException">If the channel name is not valid or the password is null.</exception> /// <seealso cref="Listener.OnJoin"/> public void Join(string channel, string password) { lock (this) { if (IsEmpty(password)) { ClearBuffer(); throw new ArgumentException("Password cannot be empty or null."); } if (Rfc2812Util.IsValidChannelName(channel)) { Buffer.Append("JOIN"); Buffer.Append(SPACE); Buffer.Append(channel); Buffer.Append(SPACE); //8 is the JOIN + 2 spaces + CR + LF password = Truncate(password, 8); Buffer.Append(password); Connection.SendCommand(Buffer); } else { ClearBuffer(); throw new ArgumentException(channel + " is not a valid channel name."); } } }
/// <summary> /// Request a list of all nicknames on a given channel. /// </summary> /// <remarks> /// Possible Errors /// <list type="bullet"> /// <item><description>ERR_TOOMANYMATCHES</description></item> /// </list> /// </remarks> /// <param name="channels">One or more channel names.</param> /// <example><code> /// //Make the request for a single channel /// connection.Sender.Names( "#test" ); /// </code></example> /// <exception cref="ArgumentException">If the channel name is not valid.</exception> /// <seealso cref="Listener.OnNames"/> public void Names(string channel) { if (!Rfc2812Util.IsValidChannelName(channel)) { throw new ArgumentException(channel + " is not a valid channel name."); } lock (this) { buffer.Append("NAMES"); buffer.Append(SPACE); buffer.Append(channel); connection.SendCommand(buffer); } }
/// <summary> /// Join the specified channel. /// </summary> /// <remarks> /// <para>Once a user has joined a channel, he receives information about /// all commands his server receives affecting the channel. This /// includes JOIN, MODE, KICK, PART, QUIT and of course PRIVMSG/NOTICE. /// This allows channel members to keep track of the other channel /// members, as well as channel modes.</para> /// <para>If a JOIN is successful, the user receives a JOIN message as /// confirmation and is then sent the channel's topic ( <see cref="Listener.OnTopicRequest"/> and /// the list of users who are on the channel ( <see cref="Listener.OnNames"/> ), which /// MUST include the user joining.</para> /// /// Possible Errors /// <list type="bullet"> /// <item><description>ERR_NEEDMOREPARAMS</description></item> /// <item><description>ERR_BANNEDFROMCHAN</description></item> /// <item><description>ERR_INVITEONLYCHAN</description></item> /// <item><description>ERR_BADCHANNELKEY</description></item> /// <item><description>ERR_CHANNELISFULL</description></item> /// <item><description>ERR_BADCHANMASK</description></item> /// <item><description>ERR_NOSUCHCHANNEL</description></item> /// <item><description>ERR_TOOMANYCHANNELS</description></item> /// <item><description>ERR_TOOMANYTARGETS</description></item> /// <item><description>ERR_UNAVAILRESOURCE</description></item> /// </list> /// </remarks> /// <param name="channel">The channel to join. Channel names must begin with '&', '#', '+' or '!'.</param> /// <example><code> /// //Most channels you will see begin with the '#'. The others are reserved /// //for special channels and may not even be available on a particular server. /// connection.Sender.Join("#thresher"); /// </code></example> /// <exception cref="ArgumentException">If the channel name is not valid.</exception> /// <seealso cref="Listener.OnJoin"/> public void Join(string channel) { lock (this) { if (Rfc2812Util.IsValidChannelName(channel)) { Buffer.Append("JOIN"); Buffer.Append(SPACE); Buffer.Append(channel); Connection.SendCommand(Buffer); } else { ClearBuffer(); throw new ArgumentException(channel + " is not a valid channel name."); } } }
/// <summary> /// Send an action message to a channel. /// </summary> /// <remarks> /// This is actually a CTCP command but it is so widely used /// that it is included here. These are the '\me Laughs' type messages. /// </remarks> /// <param name="channel">The target channel.</param> /// <param name="description">A description of the action. If this is too long it will /// be truncated.</param> /// <example><code> /// //Express an emotion... /// connection.Sender.Action("#thresher", "Kicks down the door" ); /// </code></example> /// <exception cref="ArgumentException">If the channel name is not valid. Will /// also be thrown if the description is null or empty.</exception> /// <seealso cref="Listener.OnAction"/> public void Action(string channel, string description) { lock (this) { if (IsEmpty(description)) { ClearBuffer(); throw new ArgumentException("Action description cannot be null or empty."); } if (Rfc2812Util.IsValidChannelName(channel)) { // 19 is PRIVMSG + 2 x Spaces + : + CR + LF + 2xCtcpQuote + ACTION description = Truncate(description, 19 + channel.Length); SendMessage("PRIVMSG", channel, CtcpQuote + "ACTION " + description + CtcpQuote); } else { ClearBuffer(); throw new ArgumentException(channel + " is not a valid channel name."); } } }
/// <summary> /// Parse the message and call the callback methods /// on the listeners. /// /// </summary> /// <param name="tokens">The text received from the IRC server</param> private void ParseCommand(string[] tokens) { //Remove colon user info string tokens[0] = RemoveLeadingColon(tokens[0]); switch (tokens[1]) { case NOTICE: tokens[3] = RemoveLeadingColon(tokens[3]); if (Rfc2812Util.IsValidChannelName(tokens[2])) { if (OnPublicNotice != null) { OnPublicNotice( Rfc2812Util.UserInfoFromString(tokens[0]), tokens[2], CondenseStrings(tokens, 3)); } } else { if (OnPrivateNotice != null) { OnPrivateNotice( Rfc2812Util.UserInfoFromString(tokens[0]), CondenseStrings(tokens, 3)); } } break; case JOIN: if (OnJoin != null) { OnJoin(Rfc2812Util.UserInfoFromString(tokens[0]), RemoveLeadingColon(tokens[2])); } break; case PRIVMSG: tokens[3] = RemoveLeadingColon(tokens[3]); if (tokens[3] == ACTION) { if (Rfc2812Util.IsValidChannelName(tokens[2])) { if (OnAction != null) { int last = tokens.Length - 1; tokens[last] = RemoveTrailingQuote(tokens[last]); OnAction(Rfc2812Util.UserInfoFromString(tokens[0]), tokens[2], CondenseStrings(tokens, 4)); } } else { if (OnPrivateAction != null) { int last = tokens.Length - 1; tokens[last] = RemoveTrailingQuote(tokens[last]); OnPrivateAction(Rfc2812Util.UserInfoFromString(tokens[0]), CondenseStrings(tokens, 4)); } } } else if (channelPattern.IsMatch(tokens[2])) { if (OnPublic != null) { OnPublic(Rfc2812Util.UserInfoFromString(tokens[0]), tokens[2], CondenseStrings(tokens, 3)); } } else { if (OnPrivate != null) { OnPrivate(Rfc2812Util.UserInfoFromString(tokens[0]), CondenseStrings(tokens, 3)); } } break; case NICK: if (OnNick != null) { OnNick(Rfc2812Util.UserInfoFromString(tokens[0]), RemoveLeadingColon(tokens[2])); } break; case PART: if (OnPart != null) { OnPart( Rfc2812Util.UserInfoFromString(tokens[0]), tokens[2], tokens.Length >= 4 ? RemoveLeadingColon(CondenseStrings(tokens, 3)) : ""); } break; case QUIT: if (OnQuit != null) { tokens[2] = RemoveLeadingColon(tokens[2]); OnQuit(Rfc2812Util.UserInfoFromString(tokens[0]), CondenseStrings(tokens, 2)); } break; case INVITE: if (OnInvite != null) { OnInvite( Rfc2812Util.UserInfoFromString(tokens[0]), RemoveLeadingColon(tokens[3])); } break; case KICK: if (OnKick != null) { tokens[4] = RemoveLeadingColon(tokens[4]); OnKick(Rfc2812Util.UserInfoFromString(tokens[0]), tokens[2], tokens[3], CondenseStrings(tokens, 4)); } break; case MODE: if (channelPattern.IsMatch(tokens[2])) { if (OnChannelModeChange != null) { UserInfo who = Rfc2812Util.UserInfoFromString(tokens[0]); OnChannelModeChange(who, tokens[2]); } } break; case KILL: if (OnKill != null) { string reason = ""; if (tokens.Length >= 4) { tokens[3] = RemoveLeadingColon(tokens[3]); reason = CondenseStrings(tokens, 3); } OnKill(Rfc2812Util.UserInfoFromString(tokens[0]), tokens[2], reason); } break; default: if (OnError != null) { OnError(ReplyCode.UnparseableMessage, CondenseStrings(tokens, 0)); } break; } }
/// <summary> /// Parse the message and call the callback methods /// on the listeners. /// /// </summary> /// <param name="tokens">The text received from the IRC server</param> private void ParseCommand(string[] tokens) { //Remove colon user info string tokens[0] = RemoveLeadingColon(tokens[0]); switch (tokens[1]) { case NOTICE: tokens[3] = RemoveLeadingColon(tokens[3]); if (Rfc2812Util.IsValidChannelName(tokens[2])) { if (OnPublicNotice != null) { OnPublicNotice( Rfc2812Util.UserInfoFromString(tokens[0]), tokens[2], CondenseStrings(tokens, 3)); } } else { if (OnPrivateNotice != null) { OnPrivateNotice( Rfc2812Util.UserInfoFromString(tokens[0]), CondenseStrings(tokens, 3)); } } break; case JOIN: if (OnJoin != null) { OnJoin(Rfc2812Util.UserInfoFromString(tokens[0]), RemoveLeadingColon(tokens[2])); } break; case PRIVMSG: tokens[3] = RemoveLeadingColon(tokens[3]); if (tokens[3] == ACTION) { if (Rfc2812Util.IsValidChannelName(tokens[2])) { if (OnAction != null) { int last = tokens.Length - 1; tokens[last] = RemoveTrailingQuote(tokens[last]); OnAction(Rfc2812Util.UserInfoFromString(tokens[0]), tokens[2], CondenseStrings(tokens, 4)); } } else { if (OnPrivateAction != null) { int last = tokens.Length - 1; tokens[last] = RemoveTrailingQuote(tokens[last]); OnPrivateAction(Rfc2812Util.UserInfoFromString(tokens[0]), CondenseStrings(tokens, 4)); } } } else if (channelPattern.IsMatch(tokens[2])) { if (OnPublic != null) { OnPublic(Rfc2812Util.UserInfoFromString(tokens[0]), tokens[2], CondenseStrings(tokens, 3)); } } else { if (OnPrivate != null) { OnPrivate(Rfc2812Util.UserInfoFromString(tokens[0]), CondenseStrings(tokens, 3)); } } break; case NICK: if (OnNick != null) { OnNick(Rfc2812Util.UserInfoFromString(tokens[0]), RemoveLeadingColon(tokens[2])); } break; case TOPIC: if (OnTopicChanged != null) { tokens[3] = RemoveLeadingColon(tokens[3]); OnTopicChanged( Rfc2812Util.UserInfoFromString(tokens[0]), tokens[2], CondenseStrings(tokens, 3)); } break; case PART: if (OnPart != null) { OnPart( Rfc2812Util.UserInfoFromString(tokens[0]), tokens[2], tokens.Length >= 4 ? RemoveLeadingColon(CondenseStrings(tokens, 3)) : ""); } break; case QUIT: if (OnQuit != null) { tokens[2] = RemoveLeadingColon(tokens[2]); OnQuit(Rfc2812Util.UserInfoFromString(tokens[0]), CondenseStrings(tokens, 2)); } break; case INVITE: if (OnInvite != null) { OnInvite( Rfc2812Util.UserInfoFromString(tokens[0]), RemoveLeadingColon(tokens[3])); } break; case KICK: if (OnKick != null) { tokens[4] = RemoveLeadingColon(tokens[4]); OnKick(Rfc2812Util.UserInfoFromString(tokens[0]), tokens[2], tokens[3], CondenseStrings(tokens, 4)); } break; case MODE: if (channelPattern.IsMatch(tokens[2])) { if (OnChannelModeChange != null) { UserInfo who = Rfc2812Util.UserInfoFromString(tokens[0]); try { ChannelModeInfo[] modes = ChannelModeInfo.ParseModes(tokens, 3); OnChannelModeChange(who, tokens[2], modes); } catch (Exception) { if (OnError != null) { OnError(ReplyCode.UnparseableMessage, CondenseStrings(tokens, 0)); } Debug.WriteLineIf(Rfc2812Util.IrcTrace.TraceWarning, "[" + Thread.CurrentThread.Name + "] Listener::ParseCommand() Bad IRC MODE string=" + tokens[0]); } } } else { if (OnUserModeChange != null) { tokens[3] = RemoveLeadingColon(tokens[3]); OnUserModeChange(Rfc2812Util.CharToModeAction(tokens[3][0]), Rfc2812Util.CharToUserMode(tokens[3][1])); } } break; case KILL: if (OnKill != null) { string reason = ""; if (tokens.Length >= 4) { tokens[3] = RemoveLeadingColon(tokens[3]); reason = CondenseStrings(tokens, 3); } OnKill(Rfc2812Util.UserInfoFromString(tokens[0]), tokens[2], reason); } break; default: if (OnError != null) { OnError(ReplyCode.UnparseableMessage, CondenseStrings(tokens, 0)); } Debug.WriteLineIf(Rfc2812Util.IrcTrace.TraceWarning, "[" + Thread.CurrentThread.Name + "] Listener::ParseCommand() Unknown IRC command=" + tokens[1]); break; } }