Sender.cs :  » Chat-Servers » Thresher » Sharkbite » Irc » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » Chat Servers » Thresher 
Thresher » Sharkbite » Irc » Sender.cs
/*
 * Thresher IRC client library
 * Copyright (C) 2002 Aaron Hunter <thresher@sharkbite.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * 
 * See the gpl.txt file located in the top-level-directory of
 * the archive of this library for complete text of license.
*/

using System;
using System.Text;
using System.IO;

namespace Sharkbite.Irc{
  /// <summary>
  /// This class is used to send all the IRC commands except for CTCP and DCC
  /// messages. Instances of this class are retrieved as properties of the Connection
  /// object. All methods in this class are thread safe.
  /// </summary>
  /// <remarks>
  /// <para>Due to the asynchronous nature of IRC, none of these commands 
  /// have a return value. To get that value (or possibly an error) the client must
  /// handle the corresponding event. For example, to check if a user is online
  /// the client would send <see cref="Sender.Ison"/> then check the value of the 
  /// <see cref="Listener.OnIson"/> event to receive the answer.</para>
  /// <para>When a command can return an error, the possible error replies
  /// are listed. An error message will be sent via the <see cref="Listener.OnError"/> event
  /// with one of the listed error codes as a parameter. When checking for these 
  /// errors use the constants from <see cref="ReplyCode"/>.
  /// </para> 
  /// <para>The maximum length of any command string sent to the 
  /// server is 512 characters.</para>
  /// </remarks>
  /// <example><code>
  /// //Create a Connection object which will automatically create its own Sender
  /// Connection connection = new Connection( args, false, false );  
  /// //Send commands using the Connection object and its Sender instance.
  /// //No need to keep a separate reference to the Sender object
  /// connection.Sender.PublicMessage("#thresher", "hello");
  /// </code></example>
  public class Sender : CommandBuilder
  {
    /// <summary>
    /// Create a new Sender for a specific connection.
    /// </summary>
    internal Sender(Connection connection ) : base( connection) {}

    private bool IsEmpty( string aString ) 
    {
      return aString == null || aString.Trim().Length == 0;
    }

    /// <summary>
    /// Truncate parameters which cause a command line
    /// to be too long.
    /// </summary>
    /// <param name="parameter">The command parameter</param>
    /// <param name="commandLength">The length of the command plus whitespace</param>
    /// <returns></returns>
    private string Truncate( string parameter, int commandLength ) 
    {
      int max = MAX_COMMAND_SIZE - commandLength;
      if (parameter.Length > max ) 
      {
        return parameter.Substring(0, max);
      }
      else 
      {
        return parameter;
      }
    }

    private bool TooLong( StringBuilder buffer ) 
    {
      //2 for CR LF
      return (buffer.Length + 2) > MAX_COMMAND_SIZE;
    }

    /// <summary>
    /// The USER command is only used at the beginning of Connection to specify
    /// the username, hostname and realname of a new user.
    /// </summary>
    /// <param name="args">The user Connection data</param>
    internal void User( ConnectionArgs args ) 
    {
      lock( this )
      {
        Buffer.Append("USER");
        Buffer.Append(SPACE);
        Buffer.Append( args.UserName );
        Buffer.Append(SPACE);
        Buffer.Append( args.ModeMask );
        Buffer.Append(SPACE);
        Buffer.Append('*');
        Buffer.Append(SPACE);
        Buffer.Append( args.RealName );
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>
    /// A client session is terminated with a quit message.
    /// </summary>
    /// <remarks> 
    /// <para>The server
    /// acknowledges this by sending an ERROR message to the client. 
    /// </para>
    /// <para>Before closing the Connection with the IRC server this method
    /// will call <c>Listener.beforeDisconnect()</c> and after
    /// the Connection is closed it will call <c> Listener.OnDisconnect()</c>
    /// </para>
    /// </remarks>
    /// <param name="reason">Reason for quitting.</param>
    internal void Quit(string reason) 
    {
      lock( this ) 
      {
        Buffer.Append("QUIT");
        if( IsEmpty( reason ) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Quite reason cannot be null or empty.");
        }
        Buffer.Append(SPACE_COLON);
        if (reason.Length > 502) 
        {
          reason = reason.Substring(0, 504);
        }
        Buffer.Append(reason);
        Connection.SendCommand( Buffer );
      }
    }
      /// <summary>
    /// A PONG message is a reply to server PING message. Only called by
    /// the Connection object to keep the Connection alive.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    /// <list type="bullet">
    ///       <item><description>ERR_NOORIGIN</description></item>
    ///       <item><description>ERR_NOSUCHSERVER</description></item>
    /// </list>
    /// </remarks>
    /// <param name="message">The text sent by the IRC server in the PING message.</param>
    internal void Pong(string message) 
    {
      //Not synchronized because it will only be called during on OnPing event by
      //the dispatch thread
      Buffer.Append("PONG");
      Buffer.Append(SPACE);
      Buffer.Append(message);
      Connection.SendAutomaticReply( Buffer );
    }
    /// <summary>
    /// The PASS command is used to set a 'Connection password'. 
    /// </summary>
    /// <remarks>
    /// The optional password can and MUST be set before any attempt to register
    /// the Connection is made. Currently this requires that user send a
    /// PASS command before sending the NICK/USER combination.
    /// </remarks>
    internal void Pass( string password ) 
    {
      lock( this )
      {
        Buffer.Append("PASS");
        Buffer.Append(SPACE);
        Buffer.Append(password);
        Connection.SendCommand( Buffer );
      }
    }  
    /// <summary>
    /// User registration consists of 3 commands:
    /// 1. PASS
    /// 2. NICK
    /// 3. USER
    /// Pass will rarely fail but the proposed Nick might already be taken in
    /// which case the client will have to register by manually calling Nick
    /// and User.
    /// </summary>
    internal void RegisterConnection( ConnectionArgs args ) 
    {
      Pass( args.ServerPassword );
      Nick( args.Nick );
      User( args );
    }

    /// <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 '&amp;', '#', '+' 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>
    /// 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>
    /// Change the user's nickname.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///   <list type="bullet">
    ///     <item><description>ERR_NONICKNAMEGIVEN</description></item>
    ///     <item><description>ERR_ERRONEUSNICKNAME</description></item>
    ///     <item><description>ERR_NICKNAMEINUSE</description></item>
    ///     <item><description>ERR_NICKCOLLISION</description></item>
    ///     <item><description>ERR_UNAVAILRESOURCE</description></item>
    ///     <item><description>ERR_RESTRICTED</description></item>
    ///   </list>
    /// </remarks>
    /// <param name="newNick"> The new nickname</param>
    /// <example><code>
    /// //Make sure and verify that the nick is valid and of the right length
    /// string nick = GetUserInput();
    /// if( Rfc2812Util.IsValidNick( connection, nick) ) { 
    /// connection.Sender.Nick( nick );
    /// }
    /// </code></example>
    /// <exception cref="ArgumentException">If the nickname is not valid.</exception> 
    /// <seealso cref="Listener.OnNick"/>
    public void Nick( string newNick ) 
    {
      lock( this ) 
      {
        if ( Rfc2812Util.IsValidNick(newNick) ) 
        {
          Buffer.Append("NICK");
          Buffer.Append(SPACE);
          Buffer.Append(newNick);
          Connection.SendCommand( Buffer );
        }
        else 
        {
          ClearBuffer();
          throw new ArgumentException(newNick + " is not a valid nickname.");
        }
      }
    }
    /// <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" );
    /// //Make the request for several channels at once
    /// connection.Sender.Names( "#test","#alpha","#bravo" );
    /// </code></example>
    /// <exception cref="ArgumentException">If any of the channels are not valid.</exception> 
    /// <seealso cref="Listener.OnNames"/>
    public void Names( params string[] channels ) 
    {
      lock( this ) 
      {
        if ( Rfc2812Util.IsValidChannelList( channels ) ) 
        {
          Buffer.Append("NAMES");
          Buffer.Append(SPACE);
          Buffer.Append( String.Join(",", channels) );
          if( TooLong( Buffer ) ) 
          {
            ClearBuffer();
            throw new ArgumentException("Channels are too long.");
          }
          Connection.SendCommand( Buffer );
        }
        else 
        {
          ClearBuffer();
          throw new ArgumentException("One of the channel names is not valid.");
        }
      }
    }
    /// <summary>
    /// Request a list of all visible channels along with their users. If the server allows this
    /// kind of request then expect a rather large reply. 
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///   <list type="bullet">
    ///     <item><description>ERR_TOOMANYMATCHES</description></item>
    ///   </list>
    /// </remarks> 
    /// <seealso cref="Listener.OnNames"/> 
    public void AllNames() 
    {
      lock( this ) 
      {
        Buffer.Append("NAMES");
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>Request basic information about a channel, i.e. number
    /// of visible users and topic.</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.List( "#test" );
    /// //Make the request for several channels at once
    /// connection.Sender.List( "#test","#alpha",#"bravo" );
    /// </code></example>
    /// <exception cref="ArgumentException">If any of the channels are not valid.</exception> 
    /// <seealso cref="Listener.OnList"/> 
    public void List(params string[] channels ) 
    {
      lock( this ) 
      {
        if (Rfc2812Util.IsValidChannelList(channels)) 
        {
          Buffer.Append("LIST");
          Buffer.Append(SPACE);
          Buffer.Append( String.Join(",", channels) );
          if( TooLong( Buffer ) ) 
          {
            ClearBuffer();
            throw new ArgumentException("Channels are too long.");
          }
          Connection.SendCommand( Buffer );
        }
        else 
        {
          ClearBuffer();
          throw new ArgumentException("One of the channel names is not valid.");
        }
      }
    }
    /// <summary>
    /// Request basic information for all the channels on the current
    /// network.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///   <list type="bullet">
    ///     <item><description>ERR_TOOMANYMATCHES</description></item>
    /// </list>
    /// </remarks> 
    /// <seealso cref="Listener.OnList"/>
    public void AllList() 
    {
      lock( this ) 
      {
        Buffer.Append("LIST");
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>Change the topic of the given channel.</summary>
    /// <remarks>
    /// Possible Errors
    /// <list type="bullet">
    ///     <item><description>ERR_NEEDMOREPARAMS</description></item>
    ///     <item><description>ERR_NOTONCHANNEL</description></item>
    ///     <item><description>ERR_CHANOPRIVSNEEDED</description></item>
    ///     <item><description>ERR_NOCHANMODES</description></item>
    /// </list>
    /// </remarks>
    /// <param name="channel">The target channel.</param>
    /// <param name="newTopic">The new topic.</param>
    /// <example><code>
    /// connection.Sender.ChangeTopic( "#thresher","Beta 27 Released" );
    /// </code></example>  
    /// <exception cref="ArgumentException">If the channel name is not valid or the topic is null.</exception> 
    /// <seealso cref="Listener.OnTopicChanged"/> 
    public void ChangeTopic(string channel, string newTopic) 
    {
      lock( this ) 
      {
        if (IsEmpty( newTopic ) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Topic cannot be empty or null.");
        }
        if (Rfc2812Util.IsValidChannelName(channel)) 
        {
          Buffer.Append("TOPIC");
          Buffer.Append(SPACE);
          Buffer.Append(channel);
          Buffer.Append(SPACE_COLON);
          // 9 is TOPIC + 2 x Spaces + : + CR = LF
          newTopic = Truncate( newTopic, 9 + channel.Length );
          Buffer.Append(newTopic);
          Connection.SendCommand( Buffer );
        }
        else 
        {
          ClearBuffer();
          throw new ArgumentException(channel + " is not a valid channel name.");
        }
      }
    }
    /// <summary>Clear the channel's topic.</summary>
    /// <remarks>
    /// Possible Errors
    /// <list type="bullet">
    ///     <item><description>ERR_NEEDMOREPARAMS</description></item>
    ///     <item><description>ERR_NOTONCHANNEL</description></item>
    ///     <item><description>ERR_CHANOPRIVSNEEDED</description></item>
    ///     <item><description>ERR_NOCHANMODES</description></item>
    /// </list>
    /// </remarks>
    /// <param name="channel">The target channel.</param>
    /// <exception cref="ArgumentException">If the channel name is not valid.</exception> 
    /// <seealso cref="Listener.OnTopicChanged"/> 
    public void ClearTopic(string channel) 
    {
      lock( this ) 
      {
        if (Rfc2812Util.IsValidChannelName(channel)) 
        {
          Buffer.Append("TOPIC");
          Buffer.Append(SPACE);
          Buffer.Append(channel);
          Buffer.Append(SPACE_COLON);
          Buffer.Append(SPACE);
          Connection.SendCommand( Buffer );
        }
        else 
        {
          ClearBuffer();
          throw new ArgumentException(channel + " is not a valid channel name.");
        }
      }
    }
    /// <summary>Request the topic for the given channel.</summary>
    /// <remarks>
    /// <para>
    /// The reply will be sent via the <see cref="Listener.OnTopicRequest"/> event. If there is no topic
    /// then <see cref="Listener.OnError"/> will be called with a code of <see cref="ReplyCode.RPL_NOTOPIC"/>.
    /// </para>
    /// Possible Errors
    /// <list type="bullet">
    ///     <item><description>ERR_NEEDMOREPARAMS</description></item>
    ///     <item><description>ERR_NOTONCHANNEL</description></item>
    ///     <item><description>ERR_CHANOPRIVSNEEDED</description></item>
    ///     <item><description>ERR_NOCHANMODES</description></item>
    /// </list>
    /// </remarks>
    /// <param name="channel">The target channel.</param>
    /// <exception cref="ArgumentException">If the channel name is not valid.</exception> 
    /// <seealso cref="Listener.OnTopicRequest"/> 
    public void RequestTopic(string channel) 
    {
      lock( this ) 
      {
        if (Rfc2812Util.IsValidChannelName(channel)) 
        {
          Buffer.Append("TOPIC");
          Buffer.Append(SPACE);
          Buffer.Append(channel);
          Connection.SendCommand( Buffer );
        }
        else 
        {
          ClearBuffer();
          throw new ArgumentException(channel + " is not a valid channel name.");
        }
      }
    }
    /// <summary>
    /// Leave the given channel.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    /// <list type="bullet">
    ///     <item><description>ERR_NEEDMOREPARAMS</description></item>
    ///     <item><description>ERR_NOSUCHCHANNEL</description></item>
    ///     <item><description>ERR_NOTONCHANNEL</description></item>
    /// </list>
    /// </remarks>
    /// <param name="reason">A goodbye message.</param>
    /// <param name="channels">One or more channels to leave.</param>
    /// <example><code>
    /// //Leave a single channel
    /// connection.Sender.Part("Goodbye", "#test" );
    /// //Leave several at once
    /// connection.Sender.Part( "Goodbye", "#test","#alpha",#"bravo" );
    /// </code></example>
    /// <exception cref="ArgumentException">If the channel name is not valid or the reason is null.</exception> 
    /// <seealso cref="Listener.OnPart"/> 
    public void Part( string reason, params string[] channels ) 
    {
      lock( this ) 
      {
        if ( IsEmpty( reason ) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Part reason cannot be empty or null.");
        }
        if (Rfc2812Util.IsValidChannelList(channels)) 
        {
          Buffer.Append("PART");
          Buffer.Append(SPACE);
          string channelList = String.Join(",", channels);
          Buffer.Append( channelList );
          Buffer.Append(SPACE_COLON);
          // 9 is PART + 2 x Spaces + : + CR + LF
          reason = Truncate( reason, 9 );
          Buffer.Append(reason);
          Connection.SendCommand( Buffer );
        }
        else 
        {
          ClearBuffer();
          throw new ArgumentException("One of the channels names is not valid.");
        }
      }
    }
    /// <summary>
    /// Leave a channel without giving a reason.
    /// </summary>
    /// <param name="channel">The channel to leave.</param>
    /// <exception cref="ArgumentException">If the channel name is not valid.</exception> 
    /// <seealso cref="Listener.OnPart"/> 
    public void Part( string channel ) 
    {
      lock( this ) 
      {
        if (Rfc2812Util.IsValidChannelName( channel ) ) 
        {
          Buffer.Append("PART");
          Buffer.Append(SPACE);
          Buffer.Append( channel );
          Connection.SendCommand( Buffer );
        }
        else 
        {
          ClearBuffer();
          throw new ArgumentException( channel + " is not a valid channel name.");
        }
      }
    }
    /// <summary>Send a notice to a channel.</summary>
    /// <remarks>
    /// <para>The difference between a notice and a normal message is that 
    /// automatic replies must never be sent in response to a notice. This rule 
    /// applies to servers too - they must not send any error reply back to the 
    /// client on receipt of a notice. The object of this rule is to avoid loops
    /// between clients automatically sending something in response to
    /// something it received. See <see cref="Sender.PublicMessage"/> for possible errors.</para>
    /// </remarks>
    /// <param name="channel">The target channel.</param>
    /// <param name="message">Text message. If the text is too large to be sent in one
    /// piece it will be broken up into smaller strings which will then
    /// be sent individually.</param>
    /// <exception cref="ArgumentException">If the channel name is not valid or the message is empty or null.</exception> 
    /// <seealso cref="Listener.OnPublicNotice"/> 
    public void PublicNotice(string channel, string message) 
    {
      lock( this ) 
      {
        if ( IsEmpty( message) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Notice message cannot be null or empty.");
        }
        if (Rfc2812Util.IsValidChannelName(channel) ) 
        {
          // 11 is NOTICE + 2 x Spaces + : + CR + LF
          int max = MAX_COMMAND_SIZE - 11 - channel.Length;
          if (message.Length > max) 
          {
            string[] parts = BreakUpMessage( message, max );
            foreach( string part in parts )
            {
              SendMessage("NOTICE", channel, part);
            }
          }
          else 
          {
            SendMessage("NOTICE", channel, message);
          }
        }
        else 
        {
          ClearBuffer();
          throw new ArgumentException(channel + " is not a valid channel name.");
        }
      }
    }
    /// <summary>Send a notice to a user.</summary>
    /// <remarks>
    /// <para>The difference between a notice and a normal message is that 
    /// automatic replies must never be sent in response to a notice. This rule 
    /// applies to servers too - they must not send any error reply back to the 
    /// client on receipt of a notice. The object of this rule is to avoid loops
    /// between clients automatically sending something in response to
    /// something it received. See <see cref="Sender.PrivateMessage"/> for possible errors.</para>
    /// </remarks>
    /// <param name="nick">The target nickname.</param>
    /// <param name="message">Text message. If the text is too large to be sent in one
    /// piece it will be broken up into smaller strings which will then
    /// be sent individually.</param>
    /// <exception cref="ArgumentException">If the nick is not valid or the message is empty or null.</exception> 
    /// <seealso cref="Listener.OnPrivateNotice"/> 
    public void PrivateNotice(string nick, string message) 
    {
      lock( this ) 
      {
        if ( IsEmpty(message ) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Notice message cannot be empty or null.");
        }
        if ( Rfc2812Util.IsValidNick(nick)) 
        {
          // 11 is NOTICE + 2 x Spaces + : + CR + LF
          int max = MAX_COMMAND_SIZE - 11 - nick.Length;
          if (message.Length > max) 
          {
            string[] parts = BreakUpMessage( message, max );
            foreach( string part in parts )
            {
              SendMessage("NOTICE", nick, part);
            }
          }
          else 
          {
            SendMessage("NOTICE", nick, message);
          }
        }
        else 
        {
          ClearBuffer();
          throw new ArgumentException(nick + " is not a valid nickname.");
        }
      }
    }
    /// <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) 
    {
      lock( this )
      {
        if ( IsEmpty( message ) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Public message cannot be null or empty.");
        }
        if (Rfc2812Util.IsValidChannelName(channel)) 
        {
          // 11 is PRIVMSG + 2 x Spaces + : + CR + LF
          int max = MAX_COMMAND_SIZE - 11 - channel.Length;
          if (message.Length > max) 
          {
            string[] parts = BreakUpMessage( message, max );
            foreach( string part in parts )
            {
              SendMessage("PRIVMSG", channel, part );
            }
          }
          else 
          {
            SendMessage("PRIVMSG", channel, message);
          }
        }
        else 
        {
          ClearBuffer();
          throw new ArgumentException(channel + " is not a valid channel name.");
        }
      }
    }
    /// <summary>
    /// Send a message to a user.</summary>
    /// <remarks>
    /// <para>If the target user status is away, the <see cref="Listener.OnAway"/> event will be
    /// called along with the away message if any.
    /// </para>
    /// Possible Errors
    /// <list type="bullet">
    ///     <item><description>ERR_NORECIPIENT</description></item>
    ///     <item><description>ERR_NOTEXTTOSEND</description></item>
    ///     <item><description>ERR_NOSUCHNICK</description></item>
    /// </list>
    /// </remarks>
    /// <param name="nick">The target user.</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 nickname is not valid or if the message is null or empty.</exception> 
    /// <seealso cref="Listener.OnPrivate"/> 
    public void PrivateMessage(string nick, string message) 
    {
      lock( this )
      {
        if ( IsEmpty( message ) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Private message cannot be null or empty.");
        }
        if (Rfc2812Util.IsValidNick(nick)) 
        {
          // 11 is PRIVMSG + 2 x Spaces + : + CR + LF
          int max = MAX_COMMAND_SIZE - 11 - nick.Length;
          if (message.Length > max) 
          {
            string[] parts = BreakUpMessage( message, max );
            foreach( string part in parts )
            {
              SendMessage("PRIVMSG", nick, part );
            }
          }
          else 
          {
            SendMessage("PRIVMSG", nick, message);
          }
        }
        else 
        {
          ClearBuffer();
          throw new ArgumentException(nick + " is not a valid nickname.");
        }
      }
    }
    /// <summary>
    /// Invite a user to a channel.</summary>
    /// <remarks>
    /// <para>The parameter nickname is the nickname of the person to be invited to
    /// the target channel channel. There is no requirement that the
    /// channel the target user is being invited to must exist or be a valid
    /// channel. However, if the channel exists, only members of the channel
    /// are allowed to invite other users. When the channel has invite-only
    /// flag set, only channel operators may an invite.</para>
    /// 
    /// <para>Only the user inviting and the user being invited will receive
    /// notification of the invitation. Other channel members are not
    /// notified. (This is unlike the mode changes, and is occasionally the
    /// source of trouble for users.)</para>
    /// 
    /// <para>After the invite is sent the IRC server will signal that it 
    /// was correctly received by calling <see cref="Listener.OnInviteSent"/>.
    /// </para>
    /// 
    /// Possible Errors
    /// <list type="bullet">
    ///     <item><description>ERR_NEEDMOREPARAMS</description></item>
    ///     <item><description>ERR_NOSUCHNICK</description></item>
    ///     <item><description>ERR_NOTONCHANNEL</description></item>
    ///     <item><description>ERR_USERONCHANNEL</description></item>
    ///     <item><description>ERR_CHANOPRIVSNEEDED</description></item>
    /// </list>
    /// </remarks>
    /// <param name="who">The nick of the person to invite</param>
    /// <param name="channel">The channel they are invited to join.</param>
    /// <exception cref="ArgumentException">If the nickname or channel is not valid.</exception> 
    /// <seealso cref="Listener.OnInviteSent"/> 
    /// <seealso cref="Listener.OnInvite"/> 
    public void Invite(string who, string channel) 
    {
      lock( this )
      {
        if (!Rfc2812Util.IsValidNick(who)) 
        {
          ClearBuffer();
          throw new ArgumentException(who + " is not a valid nickname.");
        }
        if (!Rfc2812Util.IsValidChannelName(channel)) 
        {
          ClearBuffer();
          throw new ArgumentException(channel + " is not a valid channel.");
        }
        Buffer.Append("INVITE");
        Buffer.Append(SPACE);
        Buffer.Append(who);
        Buffer.Append(SPACE);
        Buffer.Append(channel);
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>Kick a user off a channel.</summary>
    /// <remarks>
    /// Possible Replies
    /// <list type="bullet">
    ///     <item><description>ERR_NEEDMOREPARAMS</description></item>
    ///     <item><description>ERR_NOSUCHCHANNEL</description></item>
    ///     <item><description>ERR_BADCHANMASK</description></item>
    ///     <item><description>ERR_CHANOPRIVSNEEDED</description></item>
    ///     <item><description>ERR_USERNOTINCHANNEL</description></item>
    ///     <item><description>ERR_NOTONCHANNEL</description></item>
    ///   </list>
    /// </remarks>
    /// <param name="nicks">One or more users to kick.</param>
    /// <param name="channel">Which channel to kick the user from.</param>
    /// <param name="reason">Why.</param>
    /// <example><code>
    /// //Kick a single user
    /// connection.Sender.Kick("#thresher","No Perl please","lwall" );
    /// //Kicks several users at once
    /// connection.Sender.Kick( "#thresher", "Bye", "John","Dick","Harry" );
    /// </code></example>
    /// <exception cref="ArgumentException">If the nickname or channel is not valid or the reason is null.</exception> 
    /// <seealso cref="Listener.OnKick"/> 
    public void Kick(string channel, string reason, params string[] nicks) 
    {
      lock( this )
      {
        if (!Rfc2812Util.IsValidNicklList( nicks)) 
        {
          ClearBuffer();
          throw new ArgumentException("One of the nicknames is invalid.");
        }
        if (!Rfc2812Util.IsValidChannelName(channel)) 
        {
          ClearBuffer();
          throw new ArgumentException(channel + " is not a valid channel.");
        }
        if ( IsEmpty( reason ) ) 
        {
          ClearBuffer();
          throw new ArgumentException("The reason for kicking cannot be null.");
        }
        string nickList = String.Join(",", nicks);
        // 10 is KICK + 3 x Spaces + : + CR + LF
        reason = Truncate( reason, 10 + channel.Length + nickList.Length );
        Buffer.Append("KICK");
        Buffer.Append(SPACE);
        Buffer.Append(channel);
        Buffer.Append(SPACE);
        Buffer.Append(nickList);
        Buffer.Append(SPACE_COLON);
        Buffer.Append(reason);
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>
    /// Request to know if a user is currenlty on IRC.</summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NEEDMOREPARAMS</description></item>
    ///     </list>
    /// </remarks>
    /// <param name="nick">Target user.</param>
    /// <example><code>
    /// //Query for a nick
    /// connection.Sender.Ison( "joe" );
    /// </code></example>
    /// <exception cref="ArgumentException">If the nickname is not valid.</exception> 
    /// <seealso cref="Listener.OnIson"/> 
    public void Ison( string nick ) 
    {
      lock( this )
      {
        if (!Rfc2812Util.IsValidNick(nick)) 
        {
          ClearBuffer();
          throw new ArgumentException( nick + " is not a valid nick.");
        }
        Buffer.Append("ISON");
        Buffer.Append(SPACE);
        Buffer.Append(nick );
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>
    /// Request a list of users who match a given query.</summary>
    /// <remarks>
    /// Possible Errors
    /// <list type="bullet">
    ///     <item><description>ERR_TOOMANYLINES</description></item>
    /// </list>
    /// </remarks>
    /// <param name="mask">The mask passed to Who is matched against a users' host, 
    /// real name or nickname. It uses the wildcard system of matching
    /// where the '*' can stand for any number of characters and '?' stands for any single
    /// character. The query will only match against one component so it is not possible
    /// to match against both nick and host for example.
    /// </param>
    /// <param name="operatorsOnly"> True if the query should only try to match the mask
    /// to users designated operators.</param>
    /// <example><code>
    /// //Find all users from Russia, i.e. who have .ru in their hostnames
    /// connection.Sender.Who("*.ru", false );
    /// //Find all users from clan [DX], i.e. have '[DX]' in their nick
    /// connection.Sender.Who("[DX]*", false );
    /// </code></example>
    /// <exception cref="ArgumentException">If the mask is null,empty, or too long.</exception> 
    /// <seealso cref="Listener.OnWho"/> 
    public void Who( string mask, bool operatorsOnly ) 
    {
      lock( this )
      {
        //7 is WHO + Space +O + CR + LF
        int max = MAX_COMMAND_SIZE - 7;
        if( IsEmpty( mask) ||
          mask.Length > max ) 
        {
          ClearBuffer();
          throw new ArgumentException("Who mask is invalid.");
        }
        Buffer.Append("WHO");
        Buffer.Append(SPACE);
        Buffer.Append(mask);
        if( operatorsOnly ) 
        {
          Buffer.Append( SPACE ) ;
          Buffer.Append("o");
        }
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>
    /// Request a list all visible users (whose mode is not +i) and those 
    /// who don't have a common channel with the requesting 
    /// client are listed. 
    /// </summary>
    /// <seealso cref="Listener.OnWho"/> 
    public void AllWho() 
    {
      lock( this )
      {
        Buffer.Append("WHO");
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>
    /// Request detailed information about a given user.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NOSUCHNICK</description></item>
    ///     </list>
    /// </remarks>
    /// <param name="nick">The nick of the query subject.</param>
    /// <exception cref="ArgumentException">If the nick is invalid.</exception> 
    /// <seealso cref="Listener.OnWhois"/> 
    /// <seealso cref="WhoisInfo"/> 
    public void Whois( string nick )
    {
      lock( this )
      {
        if (!Rfc2812Util.IsValidNick(nick)) 
        {
          ClearBuffer();
          throw new ArgumentException(nick + " is not a valid nickname.");
        }
        Buffer.Append("WHOIS");
        Buffer.Append(SPACE);
        Buffer.Append(nick);
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>
    /// Set the user status to away and set an automatic reply 
    /// to any private message.
    /// </summary>
    /// <remarks>
    /// This functions as a kind of IRC answering machine. It is normally used
    /// to indicate that the user is away from IRC.
    /// </remarks>
    /// <param name="message">The message that will be sent back to others when you
    /// are away. Overly long message will be truncated.</param>
    /// <exception cref="ArgumentException">If the message is null or empty.</exception> 
    /// <seealso cref="Listener.OnAway"/> 
    public void Away( string message) 
    {
      lock( this ) 
      {
        if ( IsEmpty( message ) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Away message cannot be empty or null.");
        }
        Buffer.Append("AWAY");
        Buffer.Append(SPACE_COLON);
        // 8 is AWAY + Space + : + CR + LF
        message = Truncate( message, 8);
        Buffer.Append(message);
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>
    /// Turns off the away status and the accompanying message.
    /// </summary>
    public void UnAway() 
    {
      lock( this ) 
      {
        Buffer.Append("AWAY");
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>
    /// Request information about a user who is no longer on IRC.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NONICKNAMEGIVEN</description></item>
    ///       <item><description>ERR_WASNOSUCHNICK</description></item>
    ///     </list>
    /// </remarks>
    /// <param name="nick">Target nick</param>
    /// <exception cref="ArgumentException">If the nick is invalid.</exception> 
    /// <seealso cref="Listener.OnWhowas"/> 
    public void Whowas( string nick )
    {
      lock( this ) 
      {
        if (!Rfc2812Util.IsValidNick(nick)) 
        {
          ClearBuffer();
          throw new ArgumentException(nick + " is not a valid nickname.");
        }
        Buffer.Append("WHOWAS");
        Buffer.Append( SPACE );
        Buffer.Append( nick );
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>
    /// Request information about a user who is no longer on IRC
    /// but with a maximum number of responses.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NONICKNAMEGIVEN</description></item>
    ///       <item><description>ERR_WASNOSUCHNICK</description></item>
    ///     </list>
    /// </remarks>
    /// <param name="count">The maximum number of replies the IRC server
    /// should send back.</param>
    /// <param name="nick">Target nick</param>
    /// <exception cref="ArgumentException">If the nick is invalid or if count is less 
    /// than or equal to zero.</exception> 
    /// <seealso cref="Listener.OnWhowas"/> 
    public void Whowas( string nick, int count )
    {
      lock( this ) 
      {
        if (!Rfc2812Util.IsValidNick(nick)) 
        {
          ClearBuffer();
          throw new ArgumentException(nick + " is not a valid nickname.");
        }
        if( count < 1 ) 
        {
          ClearBuffer();
          throw new ArgumentException("Count must be more than zero.");
        }
        Buffer.Append("WHOWAS");
        Buffer.Append( SPACE );
        Buffer.Append( nick );
        Buffer.Append( SPACE );
        Buffer.Append( count );
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>
    /// Request the modes set for this user.
    /// </summary>
    /// <seealso cref="Listener.OnUserModeRequest"/>
    /// <seealso cref="UserMode"/>
    public void RequestUserModes() 
    {
      lock( this ) 
      {
        Buffer.Append("MODE");
        Buffer.Append( SPACE );
        Buffer.Append( Connection.ConnectionData.Nick );
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>Changes this client's mode. To change another nick's mode
    /// use <see cref="ChangeChannelMode"/>.</summary>
    /// <remarks>
    /// Away cannot be set here but should be set using <see cref="Sender.Away"/> 
    /// or removed using <see cref="Sender.UnAway"/>.
    /// </remarks>
    /// <param name="action">Add or remove a mode.</param>
    /// <param name="mode">The mode to be changed.</param>
    /// <example><code>
    /// //Turn off invisibility
    /// connection.Sender.ChangeUserMode( ModeAction.Remove, UserMode.Invisible );
    /// //Turn on wallops (and get a lot of IRC garbage)
    /// connection.Sender.ChangeUserMode( ModeAction.Add, UserMode.Wallops );
    /// </code></example>
    /// <exception cref="ArgumentException">If the UserMode parameter is Away.</exception> 
    /// <seealso cref="Listener.OnUserModeChange"/>
    public void ChangeUserMode( ModeAction action, UserMode mode ) 
    {
      lock( this ) 
      {
        if ( mode == UserMode.Away ) 
        {
          ClearBuffer();
          throw new ArgumentException("Away mode can only be changed with the Away and Unaway commands.");
        }
        Buffer.Append("MODE");
        Buffer.Append( SPACE );
        Buffer.Append( Connection.ConnectionData.Nick );
        Buffer.Append( SPACE );
        Buffer.Append( Rfc2812Util.ModeActionToChar( action ) );
        Buffer.Append( Rfc2812Util.UserModeToChar( mode ) );
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>
    /// Change a channel's mode.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NEEDMOREPARAMS</description></item>
    ///       <item><description>ERR_KEYSET</description></item>
    ///       <item><description>ERR_NOCHANMODES</description></item>
    ///       <item><description>ERR_CHANOPRIVSNEEDED</description></item>
    ///       <item><description>ERR_USERNOTINCHANNEL</description></item>
    ///       <item><description>ERR_UNKNOWNMODE</description></item>
    ///     </list>
    /// </remarks>
    /// <param name="channel">The target channel.</param>
    /// <param name="action">Add or remove.</param>
    /// <param name="mode">The target mode.</param>
    /// <param name="param">An optional parameter for certain modes. If the mode 
    /// does not require one this should be null.</param>
    /// <example><code>
    /// //Give 'nick' the ability to talk on a moderated channel, i.e. add Voice
    /// connection.Sender.ChangeChannelMode("#thresher", ModeAction.Add, ChannelMode.Voice,"nick" );
    /// //Make a channel private
    /// connection.Sender.ChangeChannelMode( "#thresher", ModeAction.Add, ChannelMode.Private, null );
    /// </code></example>
    /// <exception cref="ArgumentException">If the channel name is invalid.</exception> 
    /// <seealso cref="Listener.OnChannelModeChange"/>
    public void ChangeChannelMode( string channel, ModeAction action, ChannelMode mode, string param ) 
    {
      lock( this )
      {
        if (!Rfc2812Util.IsValidChannelName(channel)) 
        {
          ClearBuffer();
          throw new ArgumentException(channel + " is not a valid channel.");
        }
        Buffer.Append("MODE");
        Buffer.Append(SPACE);
        Buffer.Append( channel );
        Buffer.Append(SPACE);
        Buffer.Append( Rfc2812Util.ModeActionToChar( action ) );
        Buffer.Append( Rfc2812Util.ChannelModeToChar( mode ) );
        if( !IsEmpty( param ) ) 
        {
          Buffer.Append(SPACE);
          Buffer.Append( param );
        }
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>
    /// Request the list of users that a channel keeps for the given mode.. 
    /// </summary>
    /// <remarks>
    /// Each channel maintains a list of those banned, those excepted from a ban,
    /// those on automatic invite, and the channel creator. Use this method to retreieve one
    /// of those lists.
    /// </remarks>
    /// <param name="channel">The taregt channel.</param>
    /// <param name="mode">Must be one of:
    /// Ban, Exception, Invitation, or ChannelCreator.
    /// </param>
    /// <example><code>
    /// //Request the channel's banned list
    /// connection.Sender.RequestChannelList("#thresher", ChannelMode.Ban );
    /// </code></example>
    /// <exception cref="ArgumentException">If the channel is invalid or the ChannelMode is
    /// not one of the 4 allowed types.</exception> 
    /// <seealso cref="Listener.OnChannelList"/>
    public void RequestChannelList( string channel, ChannelMode mode ) 
    {
      lock( this )
      {
        if (!Rfc2812Util.IsValidChannelName(channel)) 
        {
          ClearBuffer();
          throw new ArgumentException(channel + " is not a valid channel.");
        }
        if( mode != ChannelMode.Ban &&
          mode != ChannelMode.Exception &&
          mode != ChannelMode.Invitation &&
          mode != ChannelMode.ChannelCreator ) 
        {
          ClearBuffer();
          throw new ArgumentException( Enum.GetName( typeof(ChannelMode), mode ) + " is not a valid channel mode for this request.");
        }
        Buffer.Append("MODE");
        Buffer.Append(SPACE);
        Buffer.Append( channel );
        Buffer.Append(SPACE);
        Buffer.Append( Rfc2812Util.ChannelModeToChar( mode ) );
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>
    /// Request the modes of a channel.
    /// </summary>
    /// <param name="channel">The target channel.</param>
    /// <exception cref="ArgumentException">If the channel is invalid, null, or empty.</exception> 
    /// <seealso cref="Listener.OnChannelModeRequest"/>
    public void RequestChannelModes( string channel ) 
    {
      lock( this )
      {
        if (!Rfc2812Util.IsValidChannelName(channel)) 
        {
          ClearBuffer();
          throw new ArgumentException(channel + " is not a valid channel.");
        }
        Buffer.Append("MODE");
        Buffer.Append(SPACE);
        Buffer.Append( channel );
        Connection.SendCommand( Buffer );
      }
    }
    /// <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>
    /// Send an action message to a user instead of a channel.
    /// </summary>
    /// <param name="nick">The target user.</param>
    /// <param name="description">A description of the action. If this is too long it will
    /// be truncated.</param>
    /// <exception cref="ArgumentException">If the nickname is not valid. Will
    /// also be thrown if the description is null or empty.</exception>
    /// <seealso cref="Listener.OnPrivateAction"/>
    public void PrivateAction(string nick, string description ) 
    {
      lock( this )
      {
        if ( IsEmpty( description) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Action description cannot be null or empty.");
        }
        if (Rfc2812Util.IsValidNick(nick)) 
        {
          // 19 is PRIVMSG + 2 x Spaces + : + CR + LF + 2xCtcpQuote + ACTION
          description = Truncate( description, 19 + nick.Length );
          SendMessage("PRIVMSG", nick, CtcpQuote + "ACTION " + description + CtcpQuote );
        }
        else 
        {
          ClearBuffer();
          throw new ArgumentException(nick + " is not a valid nickname.");
        }
      }
    }
    /// <summary>Register this connection with the IRC server.</summary>
    /// <remarks>
    /// This method should be called when the initial attempt
    /// to register with the IRC server fails because the nick is already
    /// taken. To be informed when this fails you must be subscribed
    /// to <see cref="Listener.OnNickError"/>. If <see cref="Connection.HandleNickTaken"/>
    /// is set to true (which is its default value) then Thresher will automatically
    /// create an alternate nick and use that. The new nick can be retrieved
    /// by calling <see cref="Connection.ConnectionData.Nick"/>.
    /// </remarks>
    /// <param name="newNick">The changed nick name.</param>
    /// <seealso cref="NameGenerator"/>
    public void Register( string newNick ) 
    {
      Connection.connectionArgs.Nick = newNick;
      Nick( Connection.connectionArgs.Nick );
      User( Connection.connectionArgs );
    }
    /// <summary>
    /// Send an arbitrary text message to the IRC server.
    /// </summary>
    /// <remarks>
    /// Messages that are too long will be truncated. There is no corresponding 
    /// events so it will be necessary to check for standard reply codes and possibly
    /// errors.
    /// </remarks>
    /// <param name="message">A text message.</param>
    /// <exception cref="ArgumentException">If the message is null or empty.</exception> 
    public void Raw( string message ) 
    {
      lock( this )
      {
        if ( IsEmpty( message ) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Message cannot be null or empty.");
        }
        if (message.Length > MAX_COMMAND_SIZE ) 
        {
          message = message.Substring( 0, MAX_COMMAND_SIZE );
        }
        Buffer.Append( message );
        Connection.SendCommand( Buffer );
      }
    }
    /// <summary>
    /// Request the version of the IRC server program.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NOSUCHSERVER</description></item>
    ///     </list>
    /// </remarks>
    /// <seealso cref="Listener.OnVersion"/>
    public void Version() 
    {
      Version( null );
    }
    /// <summary>
    /// Used to query the version of the IRC server program.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NOSUCHSERVER</description></item>
    ///     </list>
    /// </remarks>
    /// <param name="targetServer">The FQDN of the IRC server to query. Wildcards are allowed.
    /// Must be a server part of the same IRC network this connection is connected to.</param>
    /// <seealso cref="Listener.OnVersion"/>
    public void Version( string targetServer ) 
    {
      lock( this )
      {
        Buffer.Append("VERSION");
        if ( !IsEmpty(targetServer) ) 
        {
          //10 is VERSION + 1 x Spaces + CR + LF 
          targetServer = Truncate( targetServer, 10);
          Buffer.Append( SPACE );
          Buffer.Append( targetServer );
        }
        Connection.SendCommand( Buffer );        
      }
    }
    /// <summary>
    /// Request the "Message Of The Day" from the current server.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NOMOTD</description></item>
    ///     </list>
    /// </remarks>
    /// <seealso cref="Listener.OnMotd"/>
    public void Motd() 
    {
      Motd( null );
    }
    /// <summary>
    /// Request the "Message Of The Day" from the given server.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NOMOTD</description></item>
    ///     </list>
    /// </remarks>
    /// <param name="targetServer">The FQDN of the IRC server to query. Wildcards are allowed.
    /// Must be a server part of the same IRC network this connection is connected to.</param>
    /// <seealso cref="Listener.OnMotd"/>
    public void Motd( string targetServer ) 
    {
      lock( this )
      {
        Buffer.Append("MOTD");
        if ( !IsEmpty(targetServer) ) 
        {
          //7 is MOTD + 1 x Spaces + CR + LF 
          targetServer = Truncate( targetServer, 7);
          Buffer.Append( SPACE );
          Buffer.Append( targetServer );
        }
        Connection.SendCommand( Buffer );        
      }
    }
    /// <summary>
    /// Request the local time from the current server.
    /// </summary>
    /// <seealso cref="Listener.OnTime"/>
    public void Time() 
    {
      Time( null );
    }
    /// <summary>
    /// Request the local time from the given server.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NOSUCHSERVER</description></item>
    ///     </list>
    /// </remarks>
    /// <param name="targetServer">The FQDN of the IRC server to query. Wildcards are allowed.
    /// Must be a server part of the same IRC network this connection is connected to.</param>
    /// <seealso cref="Listener.OnTime"/>
    public void Time( string targetServer ) 
    {
      lock( this )
      {
        Buffer.Append("TIME");
        if ( !IsEmpty(targetServer) ) 
        {
          //8 is TIME + 1 x Spaces + CR + LF 
          targetServer = Truncate( targetServer, 8);
          Buffer.Append( SPACE );
          Buffer.Append( targetServer );
        }
        Connection.SendCommand( Buffer );        
      }
    }
    /// <summary>
    /// Send a message to all users who have the 'w' user mode set.</summary>
    /// <remarks>
    /// This will likely be forbidden to all but IRC
    /// OPS.
    /// </remarks>
    /// <param name="message">Any text message.</param>
    /// <exception cref="ArgumentException">If the message is empty or null.</exception>
    public void Wallops( string message ) 
    {
      lock( this )
      {
        if ( IsEmpty( message ) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Wallops message cannot be null or empty.");
        }
        Buffer.Append("WALLOPS");
        // 11 is WALLOPS + 1 x Spaces + CR + LF
        message = Truncate( message, 10 );
        Buffer.Append( SPACE );
        Buffer.Append( message );
        Connection.SendCommand( Buffer );        
      }
    }
    /// <summary>
    /// Request information about the software
    /// of the current IRC server.
    /// </summary>
    /// <remarks>
    /// This returns information describing the
    /// server: its version, when it was compiled, the patchlevel, when it
    /// was started, and any other miscellaneous information which may be
    ///   considered relevant.
    /// </remarks>
    /// <seealso cref="Listener.OnInfo"/>
    public void Info() 
    {
      Info( null );
    }
    /// <summary>
    /// Request information about the software
    /// of the target IRC server.
    /// </summary>
    /// <remarks>
    /// <para>This returns information describing the
    /// server: its version, when it was compiled, the patchlevel, when it
    /// was started, and any other miscellaneous information which may be
    ///   considered relevant.</para>
    ///   
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NOSUCHSERVER</description></item>
    ///     </list>
    /// </remarks>
    /// <param name="target">Either a user nickname or a specific IRC server connected
    /// to the current network. If it is a nickname then return the information about
    /// the server to which 'nick' is connected. Can include wildcards.</param>
    /// <example><code>
    /// //Query a specific server
    /// connection.Sender.Info( "sunray.sharkbite.org" );
    /// //Query the server Bob is connected to
    /// connection.Sender.Info("Bob");
    /// </code></example>
    /// <seealso cref="Listener.OnInfo"/>
    public void Info( string target ) 
    {
      lock( this )
      {
        Buffer.Append("INFO");
        if ( !IsEmpty(target) ) 
        {
          //7 is INFO + 1 x Spaces + CR + LF 
          target = Truncate( target, 7);
          Buffer.Append( SPACE );
          Buffer.Append( target );
        }
        Connection.SendCommand( Buffer );        
      }
    }
    /// <summary>
    /// Request information about the administrator
    /// of the current IRC server.
    /// </summary>
    /// <remarks>
    /// This returns information such as the administrator's
    /// email address, geographical location and whatever else
    /// the IRC is configured to send as a response.
    /// </remarks>
    /// <seealso cref="Listener.OnAdmin"/>
    public void Admin() 
    {
      Admin( null );
    }
    /// <summary>
    /// Request information about the administrator
    /// of the target IRC server.
    /// </summary>
    /// <remarks>
    /// <para> This returns information such as the administrator's
    /// email address, geographical location and whatever else
    /// the IRC is configured to send as a response.
    /// </para>
    ///   
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NOSUCHSERVER</description></item>
    ///     </list>
    /// </remarks>
    /// <param name="target">Either a user nickname or a specific IRC server connected
    /// to the current network. If it is a nickname then return the information about
    /// the server to which 'nick' is connected. Can include wildcards.</param>
    /// <example><code>
    /// //Request info about the administrator of the specified server
    /// connection.Sender.Admin( "sunray.sharkbite.org" );
    /// //Request info about the administrator of the server Bob is connected to
    /// connection.Sender.Admin("Bob");
    /// </code></example>
    /// <seealso cref="Listener.OnAdmin"/>
    public void Admin( string target ) 
    {
      lock( this )
      {
        Buffer.Append("ADMIN");
        if ( !IsEmpty(target) ) 
        {
          //8 is INFO + 1 x Spaces + CR + LF 
          target = Truncate( target, 8);
          Buffer.Append( SPACE );
          Buffer.Append( target );
        }
        Connection.SendCommand( Buffer );        
      }
    }
    
    /// <summary>
    /// Request statistics about the size of the IRC network.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NOSUCHSERVER</description></item>
    ///     </list>
    /// </remarks>
    /// <seealso cref="Listener.OnLusers"/>
    public void Lusers() 
    {
      Lusers( null, null );
    }

    /// <summary>
    /// Request statistics about the size of the IRC network.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NOSUCHSERVER</description></item>
    ///     </list>
    /// </remarks>
    /// <param name="hostMask">Limits the kinds of servers included in the response by
    /// specifiying a hostname string. Can include wildcards.</param>
    /// <param name="targetServer">Specifies the server that should process the request. Can be null
    /// to indicate that the current server should handle the request. Can include wildcards.</param>
    /// <example><code>
    /// //Request stats from the current server
    /// connection.Sender.Lusers();
    /// //Request stats about all servers ending in '.net' from the current server
    /// connection.Sender.Lusers("*.net", null );
    /// //Request stats about all servers ending in '.net' from 'west.gamesnet.net'
    /// connection.Sender.Lusers("*.net", "west.gamesnet.net");
    /// </code></example>
    /// <exception cref="ArgumentException">If the host mask and server names are too long.</exception>
    /// <seealso cref="Listener.OnLusers"/>
    public void Lusers( string hostMask, string targetServer ) 
    {
      lock( this )
      {
        Buffer.Append("LUSERS");
        if ( !IsEmpty(hostMask) ) 
        {
          Buffer.Append( SPACE );
          Buffer.Append( hostMask );
        }
        if( !IsEmpty(targetServer) ) 
        {
          Buffer.Append( SPACE );
          Buffer.Append( targetServer );
        }
        
        if( TooLong( Buffer ) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Hostmask and TargetServer are too long.");
        }
        
        Connection.SendCommand( Buffer );        
      }
    }

    /// <summary>
    /// Request all server names which are known by the current server.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NOSUCHSERVER</description></item>
    ///     </list>
    /// </remarks>
    /// <seealso cref="Listener.OnLinks"/>
    public void Links() 
    {
      Links( null );
    }

    /// <summary>
    /// Request all server names which are known by the target server
    /// and which match a given host mask.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NOSUCHSERVER</description></item>
    ///     </list>
    /// </remarks>
    /// <param name="masks">Either a single string which acts as a host
    /// mask filter for the query. Or two strings with the first as host mask
    /// and the second a target server. Any other arguments will be ignored.</param>
    /// <example><code>
    /// //Request names from the current server
    /// connection.Sender.Links();
    /// //Request names of all servers ending in '.net' from the current server
    /// connection.Sender.Links("*.edu" );
    /// //Request names of all servers ending in '.edu' from '*.gnome.org' servers
    /// connection.Sender.Links("*.edu", "*.gnome.org");
    /// </code></example>
    /// <exception cref="ArgumentException">If the masks are too long.</exception>
    /// <seealso cref="Listener.OnLinks"/>
    public void Links( params string[] masks ) 
    {
      lock( this )
      {
        Buffer.Append("LINKS");
        if( masks != null ) 
        {
          Buffer.Append( SPACE );
          Buffer.Append(  masks[0] );
  
          if( masks.Length >= 2 ) 
          {
            Buffer.Append( SPACE );
            Buffer.Append(  masks[1] );
          }
        }
        
        if( TooLong( Buffer ) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Masks are too long.");
        }
        Connection.SendCommand( Buffer );        
      }
    }


    /// <summary>
    /// Request certain kinds of statistics about the current server.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NOSUCHSERVER</description></item>
    ///     </list>
    /// </remarks>
    /// <param name="query">The type of query to send. See <see cref="StatsQuery"/> for choice.</param>
    /// <example><code>
    /// //Request server link stats
    /// connection.Sender.Stats( StatsQuery.Connections );
    /// </code></example>
    /// <seealso cref="Listener.OnStats"/>
    public void Stats( StatsQuery query ) 
    {
      Stats( query, null );
    }

    /// <summary>
    /// Request certain kinds of statistics about the current server.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NOSUCHSERVER</description></item>
    ///     </list>
    /// </remarks>
    /// <param name="query">The type of query to send. See <see cref="StatsQuery"/> for choice.</param>
    /// <param name="targetServer">Specifies the server that should process the request. Can include wildcards.</param>
    /// <example><code>
    /// //Request list of Operators from the server 'irc.gnome.org'
    /// connection.Sender.Stats( StatsQuery.Operators, "irc.gnome.org" );
    /// </code></example>
    /// <exception cref="ArgumentException">If the target server name is too long.</exception>
    /// <seealso cref="Listener.OnStats"/>
    public void Stats( StatsQuery query, string targetServer ) 
    {
      lock( this )
      {
        Buffer.Append("STATS");
        Buffer.Append( SPACE );
        Buffer.Append( Rfc2812Util.StatsQueryToChar( query ) );
        if( targetServer != null ) 
        {
          Buffer.Append( SPACE );
          Buffer.Append( targetServer );  
        }
        if( TooLong( Buffer ) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Target server name is too long.");
        }
        Connection.SendCommand( Buffer );        
      }
    }

    /// <summary>
    /// Forcefully disconnect a user form the IRC server. This can only be used
    /// by Operators.
    /// </summary>
    /// <remarks>
    /// Possible Errors
    ///     <list type="bullet">
    ///       <item><description>ERR_NOPRIVILEGES</description></item>
    ///       <item><description>ERR_NEEDMOREPARAMS</description></item>
    ///       <item><description>ERR_NOSUCHNICK</description></item>
    ///       <item><description>ERR_CANTKILLSERVER</description></item>
    ///     </list>
    /// </remarks>
    /// <param name="nick">User to kill</param>
    /// <param name="reason">The reason for disconnecting the user.</param>
    /// <exception cref="ArgumentException">If the nick is not valid or the reason is null.</exception> 
    /// <seealso cref="Listener.OnKill"/>
    public void Kill(string nick, string reason) 
    {
      lock( this ) 
      {
        if ( IsEmpty( nick) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Nick cannot be empty or null.");
        }
        if ( IsEmpty( reason) ) 
        {
          ClearBuffer();
          throw new ArgumentException("Reason cannot be empty or null.");
        }
        if (Rfc2812Util.IsValidNick(nick)) 
        {
          Buffer.Append("KILL");
          Buffer.Append(SPACE);
          Buffer.Append(nick);
          Buffer.Append(SPACE);
          Buffer.Append( reason );
          Connection.SendCommand( Buffer );
        }
        else 
        {
          ClearBuffer();
          throw new ArgumentException(nick + " is not a valid nick name.");
        }
      }
    }

  }
}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.